Skip to content

Commit

Permalink
lib+blender: Attempt to implement Color Ramp node
Browse files Browse the repository at this point in the history
This was one of those nodes I only sort of grokked while using Blender.
Now was a good excuse to learn how it works both inside and out :]

I'm not convinced my implementation works correctly yet, I didn't write
tests yet either.
I still have many more nodes to implement, so I'll try to go for a
depth-first approach here and worry about validating correctness later.
  • Loading branch information
vkoskiv committed Jan 13, 2024
1 parent 0f36725 commit d648912
Show file tree
Hide file tree
Showing 14 changed files with 379 additions and 18 deletions.
39 changes: 39 additions & 0 deletions bindings/nodes/color.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,33 @@ class _color_arg_color_mix(ct.Structure):
("factor", ct.POINTER(_value))
]

class ramp_element(ct.Structure):
_fields_ = [
("color", cr_color),
("position", ct.c_float)
]

class color_mode(IntEnum):
mode_rgb = 0
mode_hsv = 1
mode_hsl = 2

class interpolation(IntEnum):
ease = 0
cardinal = 1
linear = 2
b_spline = 3
constant = 4

class _color_arg_color_ramp(ct.Structure):
_fields_ = [
("factor", ct.POINTER(_value)),
("color_mode", ct.c_int),
("interpolation", ct.c_int),
("elements", ct.POINTER(ramp_element)),
("element_count", ct.c_int)
]

class _color_arg(ct.Union):
_fields_ = [
("constant", cr_color),
Expand All @@ -97,6 +124,7 @@ class _color_arg(ct.Union):
("vec_to_color", _color_arg_vec_to_color),
("gradient", _color_arg_gradient),
("color_mix", _color_arg_color_mix),
("color_ramp", _color_arg_color_ramp),
]

class _color_type(IntEnum):
Expand All @@ -113,6 +141,7 @@ class _color_type(IntEnum):
vec_to_color = 10
gradient = 11
color_mix = 12
color_ramp = 13

_color._anonymous_ = ("arg",)
_color._fields_ = [
Expand Down Expand Up @@ -226,3 +255,13 @@ def __init__(self, a, b, factor):
self.factor = factor
self.cr_struct.type = _color_type.color_mix
self.cr_struct.color_mix = _color_arg_color_mix(self.a.castref(), self.b.castref(), self.factor.castref())

class NodeColorRamp(NodeColorBase):
def __init__(self, factor, color_mode, interpolation, elements):
super().__init__()
self.factor = factor
self.color_mode = color_mode
self.interpolation = interpolation
self.elements = (ramp_element * len(elements))(*elements)
self.element_count = len(elements)
self.cr_struct.color_ramp = _color_arg_color_ramp(self.factor.castref(), self.color_mode, self.interpolation, self.elements, self.element_count)
40 changes: 40 additions & 0 deletions bindings/nodes/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,43 @@ def parse_color(input, group_inputs):
val = parse_value(input.inputs['Value'], group_inputs)
fac = parse_value(input.inputs['Fac'], group_inputs)
return NodeColorHSVTransform(color, hue, sat, val, fac)
case 'ShaderNodeValToRGB':
# Confusing name, this is the Color Ramp node.
factor = parse_value(input.inputs['Fac'], group_inputs)
color = input.color
ramp = input.color_ramp
# ramp has: hue_interpolation, interpolation, color_mode
elements = []
for element in ramp.elements:
blc = element.color
color = cr_color(blc[0], blc[1], blc[2], blc[3])
elements.append(ramp_element(color, element.position))
# element has: position, color, alpha
def match_interpolation(bl_mode):
match bl_mode:
case 'EASE':
return interpolation.ease
case 'CARDINAL':
return interpolation.cardinal
case 'LINEAR':
return interpolation.linear
case 'B_SPLINE':
return interpolation.b_spline
case 'CONSTANT':
return interpolation.constant
case _:
print("cm WTF: {}".format(bl_mode))
def match_color_mode(bl_int):
match bl_int:
case 'RGB':
return color_mode.mode_rgb
case 'HSV':
return color_mode.mode_hsv
case 'HSL':
return color_mode.mode_hsl
case _:
print("interp WTF: {}".format(bl_int))
return NodeColorRamp(factor, match_color_mode(ramp.color_mode), match_interpolation(ramp.interpolation), elements)
# case 'ShaderNodeCombineRGB':
# return warning_color
# case 'ShaderNodeCombineHSL':
Expand Down Expand Up @@ -234,6 +271,9 @@ def parse_value(input, group_inputs):
ior = parse_value(input.inputs[0], group_inputs)
normal = parse_vector(input.inputs[1], group_inputs)
return NodeValueFresnel(ior, normal)
case 'ShaderNodeValToRGB':
color = parse_color(input, group_inputs)
return NodeValueGrayscale(color)
case _:
print("Unknown value node of type {}, maybe fix.".format(input.bl_idname))
return NodeValueConstant(0.0)
Expand Down
25 changes: 25 additions & 0 deletions include/c-ray/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ struct cr_color_node {
cr_cn_vec_to_color,
cr_cn_gradient,
cr_cn_color_mix,
cr_cn_color_ramp,
} type;

union {
Expand Down Expand Up @@ -189,6 +190,30 @@ struct cr_color_node {
struct cr_color_node *b;
struct cr_value_node *factor;
} color_mix;

struct cr_color_ramp_params {
struct cr_value_node *factor;
enum cr_color_mode {
cr_mode_rgb,
cr_mode_hsv,
cr_mode_hsl,
} color_mode;

enum cr_interpolation {
cr_ease, // TODO
cr_cardinal, // TODO
cr_linear,
cr_b_spline, // TODO
cr_constant,
} interpolation;

struct ramp_element {
struct cr_color color;
float position; // [0.0,1.0]
} *elements;

int element_count;
} color_ramp;
} arg;
};

Expand Down
48 changes: 48 additions & 0 deletions src/common/node_parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,38 @@ static struct cr_color_node *cn_alloc(struct cr_color_node d) {
return desc;
}

static enum cr_color_mode get_color_mode(char *mode_str) {
if (stringEquals(mode_str, "rgb")) return cr_mode_rgb;
if (stringEquals(mode_str, "hsv")) return cr_mode_hsv;
if (stringEquals(mode_str, "hsl")) return cr_mode_hsl;
return cr_mode_rgb;
}

static enum cr_interpolation get_interpolation(char *interp_str) {
if (stringEquals(interp_str, "ease")) return cr_ease;
if (stringEquals(interp_str, "cardinal")) return cr_cardinal;
if (stringEquals(interp_str, "linear")) return cr_linear;
if (stringEquals(interp_str, "b_spline")) return cr_b_spline;
if (stringEquals(interp_str, "constant")) return cr_constant;
return cr_linear;
}

static struct ramp_element elem_parse(const struct cJSON *elem_in) {
struct color c = color_parse(cJSON_GetObjectItem(elem_in, "color"));
float pos = cJSON_GetNumberValue(cJSON_GetObjectItem(elem_in, "position"));
return (struct ramp_element){ .color = { c.red, c.green, c.blue, c.alpha }, .position = pos };
}

static struct ramp_element *get_elements(const struct cJSON *arr) {
size_t count = cJSON_GetArraySize(arr);
if (!count) return NULL;
struct ramp_element *elems = calloc(count, sizeof(*elems));
for (size_t i = 0; i < count; ++i) {
elems[i] = elem_parse(cJSON_GetArrayItem(arr, i));
}
return elems;
}

struct cr_color_node *cr_color_node_build(const struct cJSON *desc) {
if (!desc) return NULL;

Expand Down Expand Up @@ -376,6 +408,18 @@ struct cr_color_node *cr_color_node_build(const struct cJSON *desc) {
}
});
}
if (stringEquals(type->valuestring, "color_ramp")) {
return cn_alloc((struct cr_color_node){
.type = cr_cn_color_ramp,
.arg.color_ramp = {
.factor = cr_value_node_build(cJSON_GetObjectItem(desc, "factor")),
.color_mode = get_color_mode(cJSON_GetStringValue(cJSON_GetObjectItem(desc, "color_mode"))),
.interpolation = get_interpolation(cJSON_GetStringValue(cJSON_GetObjectItem(desc, "interpolation"))),
.elements = get_elements(cJSON_GetObjectItem(desc, "elements")),
.element_count = cJSON_GetArraySize(cJSON_GetObjectItem(desc, "elements"))
}
});
}
}

logr(warning, "Failed to parse textureNode. Here's a dump:\n");
Expand Down Expand Up @@ -442,6 +486,10 @@ void cr_color_node_free(struct cr_color_node *d) {
cr_color_node_free(d->arg.color_mix.b);
cr_value_node_free(d->arg.color_mix.factor);
break;
case cr_cn_color_ramp:
cr_value_node_free(d->arg.color_ramp.factor);
if (d->arg.color_ramp.elements)
free(d->arg.color_ramp.elements);
}
free(d);
}
Expand Down
16 changes: 16 additions & 0 deletions src/common/vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,19 @@ static inline float schlick(float cosine, float IOR) {
r0 = r0 * r0;
return r0 + (1.0f - r0) * powf((1.0f - cosine), 5.0f);
}

static inline float lerp(float min, float max, float t) {
return ((1.0f - t) * min) + (t * max);
}

static inline float inv_lerp(float min, float max, float between) {
return (between - min) / (max - min);
}

static inline struct vector vec_lerp(const struct vector a, const struct vector b, const float t) {
return (struct vector){
lerp(a.x, b.x, t),
lerp(a.y, b.y, t),
lerp(a.z, b.z, t)
};
}
12 changes: 11 additions & 1 deletion src/lib/api/c-ray.c
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,17 @@ struct cr_color_node *color_deepcopy(const struct cr_color_node *in) {
out->arg.color_mix.a = color_deepcopy(in->arg.color_mix.a);
out->arg.color_mix.b = color_deepcopy(in->arg.color_mix.b);
out->arg.color_mix.factor = value_deepcopy(in->arg.color_mix.factor);
default:
break;
case cr_cn_color_ramp:
out->arg.color_ramp.factor = value_deepcopy(in->arg.color_ramp.factor);
out->arg.color_ramp.color_mode = in->arg.color_ramp.color_mode;
out->arg.color_ramp.interpolation = in->arg.color_ramp.interpolation;
out->arg.color_ramp.element_count = in->arg.color_ramp.element_count;
int ct = out->arg.color_ramp.element_count;
out->arg.color_ramp.elements = calloc(ct, sizeof(*out->arg.color_ramp.elements));
for (int i = 0; i < ct; ++i) out->arg.color_ramp.elements[i] = in->arg.color_ramp.elements[i];
break;
default: // FIXME: default remove
break;
}
return out;
Expand Down
12 changes: 0 additions & 12 deletions src/lib/datatypes/spline.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,6 @@ struct spline {
struct vector d;
};

float lerp(const float a, const float b, const float t) {
return (1.0f - t) * a + t * b;
}

struct vector vec_lerp(const struct vector a, const struct vector b, const float t) {
return (struct vector){
lerp(a.x, b.x, t),
lerp(a.y, b.y, t),
lerp(a.z, b.z, t)
};
}

struct spline *spline_new(struct vector a, struct vector b, struct vector c, struct vector d) {
struct spline *new = calloc(1, sizeof(*new));
*new = (struct spline){ a, b, c, d };
Expand Down
1 change: 1 addition & 0 deletions src/lib/datatypes/tile.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ struct render_tile *tile_next_interactive(struct renderer *r, struct tile_set *s
mutex_release(set->tile_mutex);
return NULL;
}
// FIXME: Use an atomic conditional for this, instead of polling here
timer_sleep_ms(32);
goto again;
}
Expand Down
10 changes: 9 additions & 1 deletion src/lib/nodes/colornode.c
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,16 @@ const struct colorNode *build_color_node(struct cr_scene *s_ext, const struct cr
build_color_node(s_ext, desc->arg.color_mix.a),
build_color_node(s_ext, desc->arg.color_mix.b),
build_value_node(s_ext, desc->arg.color_mix.factor));
default:
case cr_cn_color_ramp:
return new_color_ramp(&s,
build_value_node(s_ext, desc->arg.color_ramp.factor),
desc->arg.color_ramp.color_mode,
desc->arg.color_ramp.interpolation,
desc->arg.color_ramp.elements,
desc->arg.color_ramp.element_count);
default: // FIXME: default remove
return NULL;
};
return NULL;
}

1 change: 1 addition & 0 deletions src/lib/nodes/colornode.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct colorNode {
#include "converter/combinergb.h"
#include "converter/combinehsl.h"
#include "converter/combinehsv.h"
#include "converter/color_ramp.h"
#include "textures/hsv_transform.h"

// const struct colorNode *unknownTextureNode(const struct node_storage *s);
Expand Down
Loading

0 comments on commit d648912

Please sign in to comment.