SPH
Filmic.cpp
Go to the documentation of this file.
1 #include "gui/objects/Filmic.h"
2 
4 
5 
6 float FilmicMapping::CurveSegment::eval(const float x) const {
7  float x0 = (x - offsetX) * scaleX;
8  float y0 = 0.0f;
9 
10  // log(0) is undefined but our function should evaluate to 0. There are better ways to handle this,
11  // but it's doing it the slow way here for clarity.
12  if (x0 > 0) {
13  y0 = expf(lnA + B * logf(x0));
14  }
15 
16  return y0 * scaleY + offsetY;
17 }
18 
20 
21 void FilmicMapping::create(const UserParams& userParams) {
22  DirectParams directParams;
23  this->getDirectParams(directParams, userParams);
24  this->create(directParams);
25 }
26 
27 float FilmicMapping::operator()(const float x) const {
28  const float normX = x * invW;
29  int index = (normX < x0) ? 0 : ((normX < x1) ? 1 : 2);
30  CurveSegment segment = segments[index];
31  return segment.eval(normX);
32 }
33 
34 // find a function of the form:
35 // f(x) = e^(lnA + Bln(x))
36 // where
37 // f(0) = 0; not really a constraint
38 // f(x0) = y0
39 // f'(x0) = m
40 static void SolveAB(float& lnA, float& B, float x0, float y0, float m) {
41  B = (m * x0) / y0;
42  lnA = logf(y0) - B * logf(x0);
43 }
44 
45 // convert to y=mx+b
46 void AsSlopeIntercept(float& m, float& b, float x0, float x1, float y0, float y1) {
47  float dy = (y1 - y0);
48  float dx = (x1 - x0);
49  if (dx == 0)
50  m = 1.0f;
51  else
52  m = dy / dx;
53 
54  b = y0 - x0 * m;
55 }
56 
57 // f(x) = (mx+b)^g
58 // f'(x) = gm(mx+b)^(g-1)
59 float EvalDerivativeLinearGamma(float m, float b, float g, float x) {
60  float ret = g * m * powf(m * x + b, g - 1.0f);
61  return ret;
62 }
63 
64 void FilmicMapping::create(const DirectParams& srcParams) {
65  DirectParams params = srcParams;
66 
67  // dstCurve.Reset();
68  W = srcParams.W;
69  invW = 1.0f / srcParams.W;
70 
71  // normalize params to 1.0 range
72  params.W = 1.0f;
73  params.x0 /= srcParams.W;
74  params.x1 /= srcParams.W;
75  params.overshootX = srcParams.overshootX / srcParams.W;
76 
77  float toeM = 0.0f;
78  float shoulderM = 0.0f;
79  {
80  float m, b;
81  AsSlopeIntercept(m, b, params.x0, params.x1, params.y0, params.y1);
82 
83  float g = srcParams.gamma;
84 
85  // base function of linear section plus gamma is
86  // y = (mx+b)^g
87 
88  // which we can rewrite as
89  // y = exp(g*ln(m) + g*ln(x+b/m))
90 
91  // and our evaluation function is (skipping the if parts):
92  /*
93  float x0 = (x - m_offsetX)*m_scaleX;
94  y0 = expf(m_lnA + m_B*logf(x0));
95  return y0*m_scaleY + m_offsetY;
96  */
97 
98  CurveSegment midSegment;
99  midSegment.offsetX = -(b / m);
100  midSegment.offsetY = 0.0f;
101  midSegment.scaleX = 1.0f;
102  midSegment.scaleY = 1.0f;
103  midSegment.lnA = g * logf(m);
104  midSegment.B = g;
105 
106  segments[1] = midSegment;
107 
108  toeM = EvalDerivativeLinearGamma(m, b, g, params.x0);
109  shoulderM = EvalDerivativeLinearGamma(m, b, g, params.x1);
110 
111  // apply gamma to endpoints
112  params.y0 = max(1e-5f, powf(params.y0, params.gamma));
113  params.y1 = max(1e-5f, powf(params.y1, params.gamma));
114 
115  params.overshootY = powf(1.0f + params.overshootY, params.gamma) - 1.0f;
116  }
117 
118  x0 = params.x0;
119  x1 = params.x1;
120  y0 = params.y0;
121  y1 = params.y1;
122 
123  // toe section
124  {
125  CurveSegment toeSegment;
126  toeSegment.offsetX = 0;
127  toeSegment.offsetY = 0.0f;
128  toeSegment.scaleX = 1.0f;
129  toeSegment.scaleY = 1.0f;
130 
131  SolveAB(toeSegment.lnA, toeSegment.B, params.x0, params.y0, toeM);
132  segments[0] = toeSegment;
133  }
134 
135  // shoulder section
136  {
137  // use the simple version that is usually too flat
138  CurveSegment shoulderSegment;
139 
140  float x0 = (1.0f + params.overshootX) - params.x1;
141  float y0 = (1.0f + params.overshootY) - params.y1;
142 
143  float lnA = 0.0f;
144  float B = 0.0f;
145  SolveAB(lnA, B, x0, y0, shoulderM);
146 
147  shoulderSegment.offsetX = (1.0f + params.overshootX);
148  shoulderSegment.offsetY = (1.0f + params.overshootY);
149 
150  shoulderSegment.scaleX = -1.0f;
151  shoulderSegment.scaleY = -1.0f;
152  shoulderSegment.lnA = lnA;
153  shoulderSegment.B = B;
154 
155  segments[2] = shoulderSegment;
156  }
157 
158  // Normalize so that we hit 1.0 at our white point. We wouldn't have do this if we
159  // skipped the overshoot part.
160  {
161  // evaluate shoulder at the end of the curve
162  float scale = segments[2].eval(1.0f);
163  float invScale = 1.0f / scale;
164 
165  segments[0].offsetY *= invScale;
166  segments[0].scaleY *= invScale;
167 
168  segments[1].offsetY *= invScale;
169  segments[1].scaleY *= invScale;
170 
171  segments[2].offsetY *= invScale;
172  segments[2].scaleY *= invScale;
173  }
174 }
175 
176 void FilmicMapping::getDirectParams(DirectParams& dstParams, const UserParams& srcParams) {
177  dstParams = DirectParams();
178 
179  float toeStrength = srcParams.toeStrength;
180  float toeLength = srcParams.toeLength;
181  float shoulderStrength = srcParams.shoulderStrength;
182  float shoulderLength = srcParams.shoulderLength;
183 
184  float shoulderAngle = srcParams.shoulderAngle;
185  float gamma = srcParams.gamma;
186 
187  // This is not actually the display gamma. It's just a UI space to avoid having to
188  // enter small numbers for the input.
189  float perceptualGamma = 2.2f;
190 
191  // constraints
192  {
193  toeLength = powf(clamp(toeLength, 0.f, 1.f), perceptualGamma);
194  toeStrength = clamp(toeStrength, 0.f, 1.f);
195  shoulderAngle = clamp(shoulderAngle, 0.f, 1.f);
196  shoulderLength = max(1e-5f, clamp(shoulderLength, 0.f, 1.f));
197 
198  shoulderStrength = max(0.0f, shoulderStrength);
199  }
200 
201  // apply base params
202  {
203  // toe goes from 0 to 0.5
204  float x0 = toeLength * .5f;
205  float y0 = (1.0f - toeStrength) * x0; // lerp from 0 to x0
206 
207  float remainingY = 1.0f - y0;
208 
209  float initialW = x0 + remainingY;
210 
211  float y1_offset = (1.0f - shoulderLength) * remainingY;
212  float x1 = x0 + y1_offset;
213  float y1 = y0 + y1_offset;
214 
215  // filmic shoulder strength is in F stops
216  float extraW = exp2f(shoulderStrength) - 1.0f;
217 
218  float W = initialW + extraW;
219 
220  dstParams.x0 = x0;
221  dstParams.y0 = y0;
222  dstParams.x1 = x1;
223  dstParams.y1 = y1;
224  dstParams.W = W;
225 
226  // bake the linear to gamma space conversion
227  dstParams.gamma = gamma;
228  }
229 
230  dstParams.overshootX = (dstParams.W * 2.0f) * shoulderAngle * shoulderStrength;
231  dstParams.overshootY = 0.5f * shoulderAngle * shoulderStrength;
232 }
233 
234 
NAMESPACE_SPH_BEGIN
Definition: BarnesHut.cpp:13
float EvalDerivativeLinearGamma(float m, float b, float g, float x)
Definition: Filmic.cpp:59
void AsSlopeIntercept(float &m, float &b, float x0, float x1, float y0, float y1)
Definition: Filmic.cpp:46
constexpr INLINE T max(const T &f1, const T &f2)
Definition: MathBasic.h:20
constexpr INLINE T clamp(const T &f, const T &f1, const T &f2)
Definition: MathBasic.h:35
#define NAMESPACE_SPH_END
Definition: Object.h:12
float operator()(const float x) const
Definition: Filmic.cpp:27
void create(const UserParams &userParams)
Definition: Filmic.cpp:21