Exposure/Contrast Math

I’m trying to reproduce the VFB exposure settings in Nuke. But the exposure and contrast settings seem to be following some non-obvious math. Are there some published specs on how to recreate those in comp?

It feels pretty close but not exactly like a:

Where Contrast == X
=========
Multiply = 1/X
Gamma = X

But not exactly.

The exposure math is:

RGB * ( if (exposure > 0) then { 2^( exposure )} else { 1 / 4 ^( abs( exposure / 2 ))} )

Highlight burn is similar to a (log base 2 / 2) with a scalar factor but not exactly. Not exactly sure what the formula is yet.

Contrast is close to but not quite:


cFactor = if (constrast > 0) then {1/(1.0000001-constrast)} else {constrast+1.000001}
if (rgb <=0.5) then { rgb*2^cFactor / 2} 
else { 1 - ((((1.5-rgb)*2)-1)^cFactor / 2) }

I’ll update as I get closer.

I’ll get you the math, just need to get in front of a computer…

Best regards,
Vlado

Here you go:```
void ExposureWnd::ContChange(float pos) {
contrast=pos;

// Calculate the bias
const float eps=1e-3f;
float bias=-contrast*0.5f+0.5f;
bias=clamp(bias, eps, 1.0f-eps);
contrastBiasMult=1.0f/bias-2.0f;

// Calculate the slope of the contrast function at t>=1.0
contrastSlope=bias/(1.0f-bias);

exponotify->ExpoChanged();

}

// Schlick’s bias function with a precomputed multiplier. It’s used for the contrast correction and it’s defined in [0.0, 0.5].
template
static T schlickBiasPrecomputedMult(T t, float biasMult) {
return t/(biasMult*(1.0f-2.0f*t)+1.0f);
}

// Nonlinear contrast correction function which preserves the detail in dark and bright areas.
template
static T contrastFunc(T c, float slope, float biasMult) {
T res;

// Use Schlick's gain function in [0.0, 1.0] (which is a bias function in [0.0, 0.5] and a reflected bias in [0.5, 1.0]).
// Outside [0.0, 1.0] use a linear function with a slope equal to the slope of the gain function at 0.0 and 1.0.
if (c<=0.0f) res=c*slope;
else if (c<0.5f) res=schlickBiasPrecomputedMult(c, biasMult);
else if (c<1.0f) res=1.0f-schlickBiasPrecomputedMult(1.0f-c, biasMult);
else res=(c-1.0f)*slope+1.0f;

return res;

}

int ExposureWnd::Correct(VUtils::Color *colors, const VUtils::Color alpha, int count, int x, int y, int notForDisplay) {
float mv=powf(2.0f, exposure);
for (int i=0; i<count; i++) {
Color res = colors[i];
//apply exposure
res
=mv;

	//apply highlight burn 
	res=res*(Color(1.0f, 1.0f, 1.0f)+res*sqr(highlight))/(Color(1.0f, 1.0f, 1.0f)+res);

	//apply contrast
	res.r=contrastFunc(res.r, contrastSlope, contrastBiasMult);
	res.g=contrastFunc(res.g, contrastSlope, contrastBiasMult);
	res.b=contrastFunc(res.b, contrastSlope, contrastBiasMult);

	res.clampMin();
	colors[i] = res;
}
return 0;

}


Best regards,
Vlado

Thanks Vlado.

For future generations here is a Nuke Gizmo implementation that will match the VFB settings.


Group {
 name VrayExposure
 selected true
 xpos 539
 ypos 22
 addUserKnob {20 User}
 addUserKnob {41 exposure l Exposure T ExposureNode.exposure}
 addUserKnob {41 highlight l "Highlight Burn" T HighlightBurnExpression.highlight}
 addUserKnob {41 contrast l Contrast T ContrastExpression.contrast}
}
 Input {
  inputs 0
  name Input1
  xpos 683
  ypos -19
 }
 Multiply {
  channels rgb
  value {{this.exposure>0?pow(2,this.exposure):1/pow(4,(abs(this.exposure/2))) x1 0.28}}
  name ExposureNode
  xpos 683
  ypos 35
  addUserKnob {20 User}
  addUserKnob {7 exposure l Exposure R -5 5}
 }
 Expression {
  expr0 r*((1+(r*highlightSquare))/(1+r))
  expr1 g*((1+(g*highlightSquare))/(1+g))
  expr2 b*((1+(b*highlightSquare))/(1+b))
  name HighlightBurnExpression
  selected true
  xpos 683
  ypos 79
  addUserKnob {20 User}
  addUserKnob {7 highlight l "Highlight Burn"}
  highlight 1
  addUserKnob {7 highlightSquare l "Highlight Square"}
  highlightSquare {{pow(highlight,2)}}
 }
 Expression {
  expr0 (r>1)?((r-1)*this.contrastSlope+1):((r>0)?((r<0.5)?(r/(this.contrastBiasMult*(1.0-(2.0*r))+1)):(1-((1-r)/(this.contrastBiasMult*(1.0-(2.0*(1-r)))+1)))):(r*contrastSlope))
  expr1 (g>1)?((g-1)*this.contrastSlope+1):((g>0)?((g<0.5)?(g/(this.contrastBiasMult*(1.0-(2.0*g))+1)):(1-((1-g)/(this.contrastBiasMult*(1.0-(2.0*(1-g)))+1)))):(g*contrastSlope))
  expr2 (b>1)?((b-1)*this.contrastSlope+1):((b>0)?((b<0.5)?(b/(this.contrastBiasMult*(1.0-(2.0*b))+1)):(1-((1-b)/(this.contrastBiasMult*(1.0-(2.0*(1-b)))+1)))):(b*contrastSlope))
  name ContrastExpression
  xpos 683
  ypos 119
  addUserKnob {20 User}
  addUserKnob {7 contrast l Contrast R -1 1}
  addUserKnob {7 bias}
  bias {{1-(max(min((contrast*0.5)+0.5,.999),0.001))}}
  addUserKnob {7 contrastBiasMult R -1 1000}
  contrastBiasMult {{(1.0/bias)-2.0 x1 0}}
  addUserKnob {7 contrastSlope}
  contrastSlope {{bias/(1.0-bias)}}
 }
 Output {
  name Output1
  xpos 683
  ypos 185
 }
end_group

VRay_Exposure.zip (791 Bytes)

sick! will need to try this gizmo at work. thanks!