2

I have a shader function which functions like the "color ramp" node in Blender3D. It takes as input a float between zero and one and outputs a color that is a blend of any number of colors in a specified order:

blender3D color ramp
blender3D color ramp

Here is my code:

fixed4 ColorRamp(float factor){
            float positions[5] = {_Pos0, _Pos1, _Pos2, _Pos3, _Pos4}; 
            fixed4 colors[5] = {_Color0, _Color1, _Color2, _Color3, _Color4}; 

            fixed4 outColor = _Color0;

            for(int i = 0; i <4; i++){

                if(factor >= positions[i] && factor <positions[i+1]){
                    fixed localFactor = (factor - positions[i])/(positions[i+1] - positions[i]);
                    outColor =  lerp(colors[i], colors[i+1], localFactor);
                }else if (factor > positions [_NumColors-1]){
                    outColor = colors[_NumColors -1];
                }
            }
            return outColor;
        }

It works. But it sucks, for a few reasons:

  1. the number of colors in the ramp are hardcoded. Adding a list of them in the Unity editor would be far better.
  2. if/else statements. I understand that these are terrible for performance and are to be avoided.
  3. the array of colors is declared in the function, which means a new array has to be created for every fragment (I think).

My main question is the first one: How can I change the number of colors in the ramp without having to edit a hardcoded value?

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
dixiepig
  • 123
  • 1
  • 4

3 Answers3

1

Brice is right. I can tell you straight away - nobody ever uses gradients of this type for shaders, simply because it offers virtually no advantages over just using a 1x128px color ramp texture. Use a lookup texture like this:

fixed4 col = tex2D(_MainTex, i.uv); // Sample the input texture
half lum = dot(col.rgb, fixed3(0.3, 0.59, 0.11)); // Color to luminosity
col = tex2D(_LookUpTex, float2(lum, 0)); // Get value from color ramp

If you want to create a handy inspector for the user, just generate a lookup texture from the gradient instead and assign it to the shader through script at OnValidate(). It is trivial to generate one by just evaluating the gradient at x/127.0 for each x between 0 and 127. Unity has a special Gradient class which gives you a gradient editor in the inspector when exposed.

Kalle Halvarsson
  • 1,240
  • 1
  • 7
  • 15
  • are multiple 1 pixel high textures optimal on a GPU or would it better to create a 128x128 texture atlas packed with gradients? – Tooster Aug 24 '23 at 08:47
0
  1. It's impossible. Shader would be automatically compile. And compiler not support cycles as is. Your cycle is just syntactic sugar, and while compiling it would be expanded to determined numbers of iteration.
  2. If/else are not terrible for performance. It is bad only for testing because it increase cyclomatic complexity.
  3. You can declare arrays in "Property" block.

Sorry, but you van't use arrays in property block. You will declare it outside the method.

float positions[5]; 
fixed4 colors[5]; 

fixed4 ColorRamp(float factor){
    positions[0] = _Pos0;
    positions[0] = _Pos2;
    positions[0] = _Pos3;
    positions[0] = _Pos4;
    positions[0] = _Pos5;

    colors[0] = _Color0;
    colors[1] = _Color1;
    colors[2] = _Color2;
    colors[3] = _Color3;
    colors[4] = _Color4;

    fixed4 outColor = _Color0;

    for(int i = 0; i <4; i++)
    {
        if(factor >= positions[i] && factor <positions[i+1])
        {
            fixed localFactor = (factor - positions[i])/(positions[i+1] - positions[i]);
            outColor =  lerp(colors[i], colors[i+1], localFactor);
        }
        else if (factor > positions [_NumColors-1])
        {
            outColor = colors[_NumColors -1];
        }
    }
    return outColor;
}

But you must initialize arrays external from unity scritpt.

float[] array = new float[] {1, 2, 3, 4, 5}
material.SetFloatArray("positions", array );
Vector4[] colors = new Vector4[] { new Vector4(), new Vector4(), new Vector4(), new Vector4(), new Vector4() };
material.SetVectorArray("colors", colors);
Sergiy Klimkov
  • 530
  • 7
  • 15
  • Thank you! Could you please provide an example of declaring arrays in the Property block? I ran in to trouble both with passing arrays from the Unity editor, and with creating arrays from properties that could be passed from the Unity editor. – dixiepig Oct 16 '17 at 15:00
0

The easy answer is: bake your gradient into a texture

Brice V.
  • 861
  • 4
  • 7