Cycles: Implement extrapolation for RGB curves
Previously RGB Curves node will clamp input to 0..1 which is rather useless when one wants to use HDR image textures and do bit of correction on them. Now kernel code supports extrapolation of baked LUT based on first/last two table points and performs linear extrapolation. The only tricky part is to guess the range to bake the LUT for. Currently it's using simple approach -- minmax of the input curves. While this behaves ok for the simple cases it's easy to trick the system up causing incorrect results. Not sure we can solve those issues in a general case and since the new code is giving more expected results it's not that bad actually. In the worst case artist migh always create explicit point to make sure LUT is created for the needed HDR range. Reviewers: brecht, juicyfruit Subscribers: sebastian_k Differential Revision: https://developer.blender.org/D1658
This commit is contained in:
@@ -220,8 +220,13 @@ static ShaderNode *add_node(Scene *scene,
|
|||||||
/* existing blender nodes */
|
/* existing blender nodes */
|
||||||
if(b_node.is_a(&RNA_ShaderNodeRGBCurve)) {
|
if(b_node.is_a(&RNA_ShaderNodeRGBCurve)) {
|
||||||
BL::ShaderNodeRGBCurve b_curve_node(b_node);
|
BL::ShaderNodeRGBCurve b_curve_node(b_node);
|
||||||
|
BL::CurveMapping mapping(b_curve_node.mapping());
|
||||||
RGBCurvesNode *curves = new RGBCurvesNode();
|
RGBCurvesNode *curves = new RGBCurvesNode();
|
||||||
curvemapping_color_to_array(b_curve_node.mapping(), curves->curves, RAMP_TABLE_SIZE, true);
|
curvemapping_color_to_array(mapping,
|
||||||
|
curves->curves,
|
||||||
|
RAMP_TABLE_SIZE,
|
||||||
|
true);
|
||||||
|
curvemapping_minmax(mapping, true, &curves->min_x, &curves->max_x);
|
||||||
node = curves;
|
node = curves;
|
||||||
}
|
}
|
||||||
if(b_node.is_a(&RNA_ShaderNodeVectorCurve)) {
|
if(b_node.is_a(&RNA_ShaderNodeVectorCurve)) {
|
||||||
|
@@ -62,6 +62,29 @@ static inline void colorramp_to_array(BL::ColorRamp ramp, float4 *data, int size
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void curvemap_minmax_curve(/*const*/ BL::CurveMap& curve,
|
||||||
|
float *min_x,
|
||||||
|
float *max_x)
|
||||||
|
{
|
||||||
|
*min_x = min(*min_x, curve.points[0].location()[0]);
|
||||||
|
*max_x = max(*max_x, curve.points[curve.points.length() - 1].location()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void curvemapping_minmax(/*const*/ BL::CurveMapping& cumap,
|
||||||
|
bool rgb_curve,
|
||||||
|
float *min_x,
|
||||||
|
float *max_x)
|
||||||
|
{
|
||||||
|
/* const int num_curves = cumap.curves.length(); */ /* Gives linking error so far. */
|
||||||
|
const int num_curves = rgb_curve? 4: 3;
|
||||||
|
*min_x = FLT_MAX;
|
||||||
|
*max_x = -FLT_MAX;
|
||||||
|
for(int i = 0; i < num_curves; ++i) {
|
||||||
|
BL::CurveMap map(cumap.curves[i]);
|
||||||
|
curvemap_minmax_curve(map, min_x, max_x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static inline void curvemapping_to_array(BL::CurveMapping cumap, float *data, int size)
|
static inline void curvemapping_to_array(BL::CurveMapping cumap, float *data, int size)
|
||||||
{
|
{
|
||||||
cumap.update();
|
cumap.update();
|
||||||
@@ -72,8 +95,29 @@ static inline void curvemapping_to_array(BL::CurveMapping cumap, float *data, in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void curvemapping_color_to_array(BL::CurveMapping cumap, float4 *data, int size, bool rgb_curve)
|
static inline void curvemapping_color_to_array(BL::CurveMapping cumap,
|
||||||
|
float4 *data,
|
||||||
|
int size,
|
||||||
|
bool rgb_curve)
|
||||||
{
|
{
|
||||||
|
float min_x = 0.0f, max_x = 1.0f;
|
||||||
|
/* TODO(sergey): Vector curve mapping is still clipping to 0..1. */
|
||||||
|
if(rgb_curve) {
|
||||||
|
/* TODO(sergey): There is no easy way to automatically guess what is
|
||||||
|
* the range to be used here for the case when mapping is applied on
|
||||||
|
* top of another mapping (i.e. R curve applied on top of common
|
||||||
|
* one).
|
||||||
|
*
|
||||||
|
* Using largest possible range form all curves works correct for the
|
||||||
|
* cases like vector curves and should be good enough heuristic for
|
||||||
|
* the color curves as well.
|
||||||
|
*
|
||||||
|
* There might be some better estimations here tho.
|
||||||
|
*/
|
||||||
|
curvemapping_minmax(cumap, rgb_curve, &min_x, &max_x);
|
||||||
|
}
|
||||||
|
const float range_x = max_x - min_x;
|
||||||
|
|
||||||
cumap.update();
|
cumap.update();
|
||||||
|
|
||||||
BL::CurveMap mapR = cumap.curves[0];
|
BL::CurveMap mapR = cumap.curves[0];
|
||||||
@@ -84,7 +128,7 @@ static inline void curvemapping_color_to_array(BL::CurveMapping cumap, float4 *d
|
|||||||
BL::CurveMap mapI = cumap.curves[3];
|
BL::CurveMap mapI = cumap.curves[3];
|
||||||
|
|
||||||
for(int i = 0; i < size; i++) {
|
for(int i = 0; i < size; i++) {
|
||||||
float t = (float)i/(float)(size-1);
|
float t = min_x + (float)i/(float)(size-1) * range_x;
|
||||||
|
|
||||||
data[i][0] = mapR.evaluate(mapI.evaluate(t));
|
data[i][0] = mapR.evaluate(mapI.evaluate(t));
|
||||||
data[i][1] = mapG.evaluate(mapI.evaluate(t));
|
data[i][1] = mapG.evaluate(mapI.evaluate(t));
|
||||||
@@ -93,7 +137,7 @@ static inline void curvemapping_color_to_array(BL::CurveMapping cumap, float4 *d
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for(int i = 0; i < size; i++) {
|
for(int i = 0; i < size; i++) {
|
||||||
float t = (float)i/(float)(size-1);
|
float t = min_x + (float)i/(float)(size-1) * range_x;
|
||||||
|
|
||||||
data[i][0] = mapR.evaluate(t);
|
data[i][0] = mapR.evaluate(t);
|
||||||
data[i][1] = mapG.evaluate(t);
|
data[i][1] = mapG.evaluate(t);
|
||||||
|
@@ -19,6 +19,21 @@
|
|||||||
|
|
||||||
float ramp_lookup(color ramp[RAMP_TABLE_SIZE], float at, int component)
|
float ramp_lookup(color ramp[RAMP_TABLE_SIZE], float at, int component)
|
||||||
{
|
{
|
||||||
|
if (at < 0.0 || at > 1.0) {
|
||||||
|
float t0, dy;
|
||||||
|
if(at < 0.0) {
|
||||||
|
t0 = ramp[0][component];
|
||||||
|
dy = t0 - ramp[1][component];
|
||||||
|
at = -at;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
t0 = ramp[RAMP_TABLE_SIZE - 1][component];
|
||||||
|
dy = t0 - ramp[RAMP_TABLE_SIZE - 2][component];
|
||||||
|
at = at - 1.0;
|
||||||
|
}
|
||||||
|
return t0 + dy * at * (RAMP_TABLE_SIZE - 1);
|
||||||
|
}
|
||||||
|
|
||||||
float f = clamp(at, 0.0, 1.0) * (RAMP_TABLE_SIZE - 1);
|
float f = clamp(at, 0.0, 1.0) * (RAMP_TABLE_SIZE - 1);
|
||||||
|
|
||||||
/* clamp int as well in case of NaN */
|
/* clamp int as well in case of NaN */
|
||||||
@@ -37,14 +52,18 @@ float ramp_lookup(color ramp[RAMP_TABLE_SIZE], float at, int component)
|
|||||||
|
|
||||||
shader node_rgb_curves(
|
shader node_rgb_curves(
|
||||||
color ramp[RAMP_TABLE_SIZE] = {0.0},
|
color ramp[RAMP_TABLE_SIZE] = {0.0},
|
||||||
|
float min_x = 0.0,
|
||||||
|
float max_x = 1.0,
|
||||||
|
|
||||||
color ColorIn = 0.0,
|
color ColorIn = 0.0,
|
||||||
float Fac = 0.0,
|
float Fac = 0.0,
|
||||||
output color ColorOut = 0.0)
|
output color ColorOut = 0.0)
|
||||||
{
|
{
|
||||||
ColorOut[0] = ramp_lookup(ramp, ColorIn[0], 0);
|
color c = (ColorIn - color(min_x, min_x, min_x)) / (max_x - min_x);
|
||||||
ColorOut[1] = ramp_lookup(ramp, ColorIn[1], 1);
|
|
||||||
ColorOut[2] = ramp_lookup(ramp, ColorIn[2], 2);
|
ColorOut[0] = ramp_lookup(ramp, c[0], 0);
|
||||||
|
ColorOut[1] = ramp_lookup(ramp, c[1], 1);
|
||||||
|
ColorOut[2] = ramp_lookup(ramp, c[2], 2);
|
||||||
|
|
||||||
ColorOut = mix(ColorIn, ColorOut, Fac);
|
ColorOut = mix(ColorIn, ColorOut, Fac);
|
||||||
}
|
}
|
||||||
|
@@ -19,8 +19,27 @@
|
|||||||
|
|
||||||
CCL_NAMESPACE_BEGIN
|
CCL_NAMESPACE_BEGIN
|
||||||
|
|
||||||
ccl_device float4 rgb_ramp_lookup(KernelGlobals *kg, int offset, float f, bool interpolate)
|
ccl_device float4 rgb_ramp_lookup(KernelGlobals *kg,
|
||||||
|
int offset,
|
||||||
|
float f,
|
||||||
|
bool interpolate,
|
||||||
|
bool extrapolate)
|
||||||
{
|
{
|
||||||
|
if((f < 0.0f || f > 1.0f) && extrapolate) {
|
||||||
|
float4 t0, dy;
|
||||||
|
if(f < 0.0f) {
|
||||||
|
t0 = fetch_node_float(kg, offset);
|
||||||
|
dy = t0 - fetch_node_float(kg, offset + 1);
|
||||||
|
f = -f;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
t0 = fetch_node_float(kg, offset + RAMP_TABLE_SIZE - 1);
|
||||||
|
dy = t0 - fetch_node_float(kg, offset + RAMP_TABLE_SIZE - 2);
|
||||||
|
f = f - 1.0f;
|
||||||
|
}
|
||||||
|
return t0 + dy * f * (RAMP_TABLE_SIZE-1);
|
||||||
|
}
|
||||||
|
|
||||||
f = saturate(f)*(RAMP_TABLE_SIZE-1);
|
f = saturate(f)*(RAMP_TABLE_SIZE-1);
|
||||||
|
|
||||||
/* clamp int as well in case of NaN */
|
/* clamp int as well in case of NaN */
|
||||||
@@ -43,7 +62,7 @@ ccl_device void svm_node_rgb_ramp(KernelGlobals *kg, ShaderData *sd, float *stac
|
|||||||
decode_node_uchar4(node.y, &fac_offset, &color_offset, &alpha_offset, NULL);
|
decode_node_uchar4(node.y, &fac_offset, &color_offset, &alpha_offset, NULL);
|
||||||
|
|
||||||
float fac = stack_load_float(stack, fac_offset);
|
float fac = stack_load_float(stack, fac_offset);
|
||||||
float4 color = rgb_ramp_lookup(kg, *offset, fac, interpolate);
|
float4 color = rgb_ramp_lookup(kg, *offset, fac, interpolate, false);
|
||||||
|
|
||||||
if(stack_valid(color_offset))
|
if(stack_valid(color_offset))
|
||||||
stack_store_float3(stack, color_offset, float4_to_float3(color));
|
stack_store_float3(stack, color_offset, float4_to_float3(color));
|
||||||
@@ -55,16 +74,24 @@ ccl_device void svm_node_rgb_ramp(KernelGlobals *kg, ShaderData *sd, float *stac
|
|||||||
|
|
||||||
ccl_device void svm_node_rgb_curves(KernelGlobals *kg, ShaderData *sd, float *stack, uint4 node, int *offset)
|
ccl_device void svm_node_rgb_curves(KernelGlobals *kg, ShaderData *sd, float *stack, uint4 node, int *offset)
|
||||||
{
|
{
|
||||||
uint fac_offset = node.y;
|
uint fac_offset, color_offset, out_offset;
|
||||||
uint color_offset = node.z;
|
decode_node_uchar4(node.y,
|
||||||
uint out_offset = node.w;
|
&fac_offset,
|
||||||
|
&color_offset,
|
||||||
|
&out_offset,
|
||||||
|
NULL);
|
||||||
|
|
||||||
float fac = stack_load_float(stack, fac_offset);
|
float fac = stack_load_float(stack, fac_offset);
|
||||||
float3 color = stack_load_float3(stack, color_offset);
|
float3 color = stack_load_float3(stack, color_offset);
|
||||||
|
|
||||||
float r = rgb_ramp_lookup(kg, *offset, color.x, true).x;
|
const float min_x = __int_as_float(node.z),
|
||||||
float g = rgb_ramp_lookup(kg, *offset, color.y, true).y;
|
max_x = __int_as_float(node.w);
|
||||||
float b = rgb_ramp_lookup(kg, *offset, color.z, true).z;
|
const float range_x = max_x - min_x;
|
||||||
|
color = (color - make_float3(min_x, min_x, min_x)) / range_x;
|
||||||
|
|
||||||
|
float r = rgb_ramp_lookup(kg, *offset, color.x, true, true).x;
|
||||||
|
float g = rgb_ramp_lookup(kg, *offset, color.y, true, true).y;
|
||||||
|
float b = rgb_ramp_lookup(kg, *offset, color.z, true, true).z;
|
||||||
|
|
||||||
color = (1.0f - fac)*color + fac*make_float3(r, g, b);
|
color = (1.0f - fac)*color + fac*make_float3(r, g, b);
|
||||||
stack_store_float3(stack, out_offset, color);
|
stack_store_float3(stack, out_offset, color);
|
||||||
@@ -81,9 +108,9 @@ ccl_device void svm_node_vector_curves(KernelGlobals *kg, ShaderData *sd, float
|
|||||||
float fac = stack_load_float(stack, fac_offset);
|
float fac = stack_load_float(stack, fac_offset);
|
||||||
float3 color = stack_load_float3(stack, color_offset);
|
float3 color = stack_load_float3(stack, color_offset);
|
||||||
|
|
||||||
float r = rgb_ramp_lookup(kg, *offset, (color.x + 1.0f)*0.5f, true).x;
|
float r = rgb_ramp_lookup(kg, *offset, (color.x + 1.0f)*0.5f, true, false).x;
|
||||||
float g = rgb_ramp_lookup(kg, *offset, (color.y + 1.0f)*0.5f, true).y;
|
float g = rgb_ramp_lookup(kg, *offset, (color.y + 1.0f)*0.5f, true, false).y;
|
||||||
float b = rgb_ramp_lookup(kg, *offset, (color.z + 1.0f)*0.5f, true).z;
|
float b = rgb_ramp_lookup(kg, *offset, (color.z + 1.0f)*0.5f, true, false).z;
|
||||||
|
|
||||||
color = (1.0f - fac)*color + fac*make_float3(r*2.0f - 1.0f, g*2.0f - 1.0f, b*2.0f - 1.0f);
|
color = (1.0f - fac)*color + fac*make_float3(r*2.0f - 1.0f, g*2.0f - 1.0f, b*2.0f - 1.0f);
|
||||||
stack_store_float3(stack, out_offset, color);
|
stack_store_float3(stack, out_offset, color);
|
||||||
|
@@ -4316,6 +4316,9 @@ RGBCurvesNode::RGBCurvesNode()
|
|||||||
add_input("Fac", SHADER_SOCKET_FLOAT);
|
add_input("Fac", SHADER_SOCKET_FLOAT);
|
||||||
add_input("Color", SHADER_SOCKET_COLOR);
|
add_input("Color", SHADER_SOCKET_COLOR);
|
||||||
add_output("Color", SHADER_SOCKET_COLOR);
|
add_output("Color", SHADER_SOCKET_COLOR);
|
||||||
|
|
||||||
|
min_x = 0.0f;
|
||||||
|
max_x = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RGBCurvesNode::compile(SVMCompiler& compiler)
|
void RGBCurvesNode::compile(SVMCompiler& compiler)
|
||||||
@@ -4328,7 +4331,12 @@ void RGBCurvesNode::compile(SVMCompiler& compiler)
|
|||||||
compiler.stack_assign(color_in);
|
compiler.stack_assign(color_in);
|
||||||
compiler.stack_assign(color_out);
|
compiler.stack_assign(color_out);
|
||||||
|
|
||||||
compiler.add_node(NODE_RGB_CURVES, fac_in->stack_offset, color_in->stack_offset, color_out->stack_offset);
|
compiler.add_node(NODE_RGB_CURVES,
|
||||||
|
compiler.encode_uchar4(fac_in->stack_offset,
|
||||||
|
color_in->stack_offset,
|
||||||
|
color_out->stack_offset),
|
||||||
|
__float_as_int(min_x),
|
||||||
|
__float_as_int(max_x));
|
||||||
compiler.add_array(curves, RAMP_TABLE_SIZE);
|
compiler.add_array(curves, RAMP_TABLE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4343,6 +4351,8 @@ void RGBCurvesNode::compile(OSLCompiler& compiler)
|
|||||||
}
|
}
|
||||||
|
|
||||||
compiler.parameter_color_array("ramp", ramp, RAMP_TABLE_SIZE);
|
compiler.parameter_color_array("ramp", ramp, RAMP_TABLE_SIZE);
|
||||||
|
compiler.parameter("min_x", min_x);
|
||||||
|
compiler.parameter("max_x", max_x);
|
||||||
compiler.add(this, "node_rgb_curves");
|
compiler.add(this, "node_rgb_curves");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -703,6 +703,7 @@ public:
|
|||||||
virtual int get_group() { return NODE_GROUP_LEVEL_3; }
|
virtual int get_group() { return NODE_GROUP_LEVEL_3; }
|
||||||
|
|
||||||
float4 curves[RAMP_TABLE_SIZE];
|
float4 curves[RAMP_TABLE_SIZE];
|
||||||
|
float min_x, max_x;
|
||||||
};
|
};
|
||||||
|
|
||||||
class VectorCurvesNode : public ShaderNode {
|
class VectorCurvesNode : public ShaderNode {
|
||||||
|
Reference in New Issue
Block a user