0
\$\begingroup\$

I want to return a colour for a position t, given an arbitrary number of colour stops - a stop is comprised of a position, and a colour.

Like selecting a colour from a multiple-colour gradient, linearly interpolated between stops.

Positions and point t are floating point numbers and can be negative. Positions and their respective colours are ordered lowest to highest in the arrays.

I would like to avoid conditional statements, as this is for use within a shader.

Code would ideally be in the form:

float3 MultiColorLerp(float positions[], float3[] colors, float t) {
    // Returns the color at the point t.
}
\$\endgroup\$
  • \$\begingroup\$ What have you tried? \$\endgroup\$ – Tyyppi_77 Nov 5 '18 at 11:19
  • \$\begingroup\$ So you have a line with points (positions) each point has an assigned color, and given a position on this line, you want to find the interpolated color? \$\endgroup\$ – TomTsagk Nov 5 '18 at 11:22
  • \$\begingroup\$ The best way to get a gradient without conditionals in a shader is to write your gradient into a texture and sample it. \$\endgroup\$ – DMGregory Nov 5 '18 at 13:21
  • \$\begingroup\$ @DMGregory Agreed, that seems the best approach. I'll create the desired texture before involving this shader. \$\endgroup\$ – user3781883 Nov 5 '18 at 13:29
  • \$\begingroup\$ Unity also includes a prebuilt gradient class. I understand if you need your own implementation, but it's worth looking into. link: docs.unity3d.com/ScriptReference/Gradient.html \$\endgroup\$ – Zebraman Nov 6 '18 at 14:06
0
\$\begingroup\$

Since we don't know the size of the arrays here, we need to pass that as a parameter, and we'll need at least a little conditional branching to control the loop.

We can technically avoid other if-type branching using ther step function to mask-to-zero any colours from segments of the gradient that t isn't in, but I don't recommend it. This does all the work of rendering a pixel from every span of the gradient instead of just the span we're in. You might well find that a little branching to find the right span and compute just that one colour is more efficient.

float3 MultiColorLerp(float positions[], float3[] colors, float t, int numStops) {
    // If t is less than all stops, use the first color.
    float3 color = colors[0] * step(t, positions[0]);

    // If t is greater than all stops, use the last color.
    color += colors[numStops - 1] * step(positions[numStops - 1], t);

    // Compute blended color for each span, and mask out all but the span containing t
    int start;
    float inRange;
    float s;
    for(int end = 1; end < numStops; end++) {
        start = end - 1;
        inRange = step(positions[start], t) * step(t, positions[end]);
        s = (t - positions[start])/(positions[end]- positions[start]);

        color += inRange * lerp(colors[start], colors[end], s);
    }
}

Compare this to a version where we're more accepting of branches:

float3 MultiColorLerp(float positions[], float3[] colors, float t, int numStops) {
    // Handle t outside the gradient's range.
    if(t <= positions[0])
        return colors[0];

    if(t >= positions[numStops - 1])
        return colors[numStops - 1])

    // Find the span t sits within.
    int end = 1;
    while(positions[end] < t)
        end++;

    // Blend colors for this span.
    float s = (t - positions[end - 1])/(positions[end] - positions[end - 1]);

    return lerp(colors[end - 1], colors[end], s);
}

Since these branches will be reasonably coherent (within any one span of the gradient, all adjacent fragments will take the same path — it's only where the gradient crosses one of its position keys that you'll have neighbours branching inconsistently), I'd be willing to bet a strategy like this one would actually perform better, especially if your gradients have many stops.

\$\endgroup\$
  • \$\begingroup\$ Your second solution is indeed performing a little better than the first. The first solution also produced a strange result when t or the positions were negative numbers - appears to be fixed by clamping the final result between 0 and 1. Thanks for your input, much appreciated :) \$\endgroup\$ – user3781883 Nov 5 '18 at 15:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.