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.
}

• What have you tried? – Tyyppi_77 Nov 5 '18 at 11:19
• 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? – TomTsagk Nov 5 '18 at 11:22
• The best way to get a gradient without conditionals in a shader is to write your gradient into a texture and sample it. – DMGregory Nov 5 '18 at 13:21
• @DMGregory Agreed, that seems the best approach. I'll create the desired texture before involving this shader. – user3781883 Nov 5 '18 at 13:29
• 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 – Zebraman Nov 6 '18 at 14:06

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.

• 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 :) – user3781883 Nov 5 '18 at 15:24