algorithm – “Smoothing” colors in mutliple-color gradient in fragment shader

TL;DR How to better avoid some of the colors in the gradient looking like brighter/darker lines and make them blend in with neighboring colors. Not too concerned with performance/color accuracy but don’t like how my algorithm muddies up colors at higher blend settings.

Longer explanation

I initially wrote a straight-forward gradient algorithm which linearly transitions between colors. This works fine however there was one aspect that I didn’t like about the result: the points in the gradient where there is just the pure color stood out too much.

For example, on the image below the white color in the middle looks like a bright line – I would like to somehow soften this – going for aesthetics over color accuracy.

enter image description here

Idea behind algorithm attempt to smooth out “peaks”

The simple linear case, assuming 2 colors, can be represented as a graph with the proportion of color 1 represented by a straight line going from (0, 1) to (1, 0) (y = 1 – x) and another one (color 2) going from (0, 0) to (1, 1) (y = x). My idea is to limit the lowest and highest points, so that for example the lines become (0, 0.8) to (1, 0.1) and (0, 0.1) to (1, 0.8) and hence there is never just the single color shown. The top boundary is limited at 1 – (2 * bottom) because when there are more than 2 colors, the middle colors will have 2 neighbors.

The algorithm and resulting gradients are below. Even with high proportions of neighboring colors mixed in, I still think the “lines” (light red near the top, dark purple in the middle and light green near the bottom on the right sample) are too visible (this had an input of 10 colors, which my algorithm unfortunately also muddies up):

enter image description hereenter image description here

Is there a better approach to blur the boundaries? I thought about using a quadratic/curved function, but it seems like that will just lead to an even wider plateau, or a narrow but sharp peak depending on which way it’s facing.

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 iResolution;

//input - array of colors
uniform vec3 iColors(10);
//input - actual number of colors in the array
uniform int iNumColors; 

void main(void) {
    // x, y co-ordinates, 0 to 1
    vec2 uv = gl_FragCoord.xy / iResolution.xy;
    // flip y axis so first color is on top
    uv.y = 1.0 - uv.y;
    float transparency = 1.0;

    //if only one color, return uniform color
    if (iNumColors == 1) {
        gl_FragColor = vec4(iColors(0), transparency);
        return;
    }

    // init to black - rgb channels 0 to 1
    vec3 color = vec3(0.);
    
    float colorWidth = 1.0 / float(iNumColors - 1);

    for (int i = 0; i < iNumColors; i++) {
        // location of "peak" of current color
        float midPoint = float(i) * colorWidth;
        float colorValue;
        
        float overlap = 0.25;
        float top = 1. - (overlap * 2.);
        float bottom = overlap;
        float height = top - bottom;
        
        float gradient = height / colorWidth;
        float yOffset;
        if (uv.y >= midPoint) {
            gradient *= -1.0;
            yOffset = top + (height * float(i));
        } else {
            yOffset = bottom - (height * float(i - 1));
        }
        colorValue = yOffset + (gradient * uv.y);
        color += iColors(i) * max(0., colorValue);
    }
    gl_FragColor = vec4(color, transparency);
}