Creating a Gradient Color in Fragment Shader
In response to just the title of your question you might also want to consider doing the mix in other color spaces. Your code is mixing in RGB space but you'll get different results in different spaces.
Example
const gl = document.createElement("canvas").getContext("webgl");
gl.canvas.width = 100;
gl.canvas.height = 100;
gl.viewport(0, 0, 100, 100);
const vsrc = `
void main() {
gl_PointSize = 100.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
const fRGB = `
precision mediump float;
uniform vec3 color1;
uniform vec3 color2;
void main() {
vec2 st = gl_PointCoord;
float mixValue = distance(st, vec2(0, 1));
vec3 color = mix(color1, color2, mixValue);
gl_FragColor = vec4(color, 1);
}
`;
const fHSV = `
precision mediump float;
uniform vec3 color1;
uniform vec3 color2;
// from: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
vec3 rgb2hsv(vec3 c) {
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
vec3 hsv2rgb(vec3 c) {
c = vec3(c.x, clamp(c.yz, 0.0, 1.0));
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
vec2 st = gl_PointCoord;
float mixValue = distance(st, vec2(0, 1));
vec3 hsv1 = rgb2hsv(color1);
vec3 hsv2 = rgb2hsv(color2);
// mix hue in toward closest direction
float hue = (mod(mod((hsv2.x - hsv1.x), 1.) + 1.5, 1.) - 0.5) * mixValue + hsv1.x;
vec3 hsv = vec3(hue, mix(hsv1.yz, hsv2.yz, mixValue));
vec3 color = hsv2rgb(hsv);
gl_FragColor = vec4(color, 1);
}
`;
const fHSL = `
precision mediump float;
uniform vec3 color1;
uniform vec3 color2;
const float Epsilon = 1e-10;
vec3 rgb2hcv(in vec3 RGB)
{
// Based on work by Sam Hocevar and Emil Persson
vec4 P = lerp(vec4(RGB.bg, -1.0, 2.0/3.0), vec4(RGB.gb, 0.0, -1.0/3.0), step(RGB.b, RGB.g));
vec4 Q = mix(vec4(P.xyw, RGB.r), vec4(RGB.r, P.yzx), step(P.x, RGB.r));
float C = Q.x - min(Q.w, Q.y);
float H = abs((Q.w - Q.y) / (6. * C + Epsilon) + Q.z);
return vec3(H, C, Q.x);
}
vec3 rgb2hsl(in vec3 RGB)
{
vec3 HCV = rgb2hcv(RGB);
float L = HCV.z - HCV.y * 0.5;
float S = HCV.y / (1 - abs(L * 2. - 1.) + Epsilon);
return vec3(HCV.x, S, L);
}
vec3 hsl2rgb(vec3 c)
{
c = vec3(fract(c.x), clamp(c.yz, 0.0, 1.0));
vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
return c.z + c.y * (rgb - 0.5) * (1.0 - abs(2.0 * c.z - 1.0));
}
void main() {
vec2 st = gl_PointCoord;
float mixValue = distance(st, vec2(0, 1));
vec3 hsl1 = rgb2hsl(color1);
vec3 hsl2 = rgb2hsl(color2);
// mix hue in toward closest direction
float hue = (mod(mod((hsl2.x - hsl1.x), 1.) + 1.5, 1.) - 0.5) * mixValue + hsl1.x;
vec3 hsl = vec3(hue, mix(hsl1.yz, hsl2.yz, mixValue));
vec3 color = hsl2rgb(hsv);
gl_FragColor = vec4(color, 1);
}
`;
const fLAB = `
precision mediump float;
uniform vec3 color1;
uniform vec3 color2;
// from: https://code.google.com/archive/p/flowabs/source
vec3 rgb2xyz( vec3 c ) {
vec3 tmp;
tmp.x = ( c.r > 0.04045 ) ? pow( ( c.r + 0.055 ) / 1.055, 2.4 ) : c.r / 12.92;
tmp.y = ( c.g > 0.04045 ) ? pow( ( c.g + 0.055 ) / 1.055, 2.4 ) : c.g / 12.92,
tmp.z = ( c.b > 0.04045 ) ? pow( ( c.b + 0.055 ) / 1.055, 2.4 ) : c.b / 12.92;
return 100.0 * tmp *
mat3( 0.4124, 0.3576, 0.1805,
0.2126, 0.7152, 0.0722,
0.0193, 0.1192, 0.9505 );
}
vec3 xyz2lab( vec3 c ) {
vec3 n = c / vec3( 95.047, 100, 108.883 );
vec3 v;
v.x = ( n.x > 0.008856 ) ? pow( n.x, 1.0 / 3.0 ) : ( 7.787 * n.x ) + ( 16.0 / 116.0 );
v.y = ( n.y > 0.008856 ) ? pow( n.y, 1.0 / 3.0 ) : ( 7.787 * n.y ) + ( 16.0 / 116.0 );
v.z = ( n.z > 0.008856 ) ? pow( n.z, 1.0 / 3.0 ) : ( 7.787 * n.z ) + ( 16.0 / 116.0 );
return vec3(( 116.0 * v.y ) - 16.0, 500.0 * ( v.x - v.y ), 200.0 * ( v.y - v.z ));
}
vec3 rgb2lab(vec3 c) {
vec3 lab = xyz2lab( rgb2xyz( c ) );
return vec3( lab.x / 100.0, 0.5 + 0.5 * ( lab.y / 127.0 ), 0.5 + 0.5 * ( lab.z / 127.0 ));
}
vec3 lab2xyz( vec3 c ) {
float fy = ( c.x + 16.0 ) / 116.0;
float fx = c.y / 500.0 + fy;
float fz = fy - c.z / 200.0;
return vec3(
95.047 * (( fx > 0.206897 ) ? fx * fx * fx : ( fx - 16.0 / 116.0 ) / 7.787),
100.000 * (( fy > 0.206897 ) ? fy * fy * fy : ( fy - 16.0 / 116.0 ) / 7.787),
108.883 * (( fz > 0.206897 ) ? fz * fz * fz : ( fz - 16.0 / 116.0 ) / 7.787)
);
}
vec3 xyz2rgb( vec3 c ) {
vec3 v = c / 100.0 * mat3(
3.2406, -1.5372, -0.4986,
-0.9689, 1.8758, 0.0415,
0.0557, -0.2040, 1.0570
);
vec3 r;
r.x = ( v.r > 0.0031308 ) ? (( 1.055 * pow( v.r, ( 1.0 / 2.4 ))) - 0.055 ) : 12.92 * v.r;
r.y = ( v.g > 0.0031308 ) ? (( 1.055 * pow( v.g, ( 1.0 / 2.4 ))) - 0.055 ) : 12.92 * v.g;
r.z = ( v.b > 0.0031308 ) ? (( 1.055 * pow( v.b, ( 1.0 / 2.4 ))) - 0.055 ) : 12.92 * v.b;
return r;
}
vec3 lab2rgb(vec3 c) {
return xyz2rgb( lab2xyz( vec3(100.0 * c.x, 2.0 * 127.0 * (c.y - 0.5), 2.0 * 127.0 * (c.z - 0.5)) ) );
}
void main() {
vec2 st = gl_PointCoord;
float mixValue = distance(st, vec2(0, 1));
vec3 lab1 = rgb2lab(color1);
vec3 lab2 = rgb2lab(color2);
vec3 lab = mix(lab1, lab2, mixValue);
vec3 color = lab2rgb(lab);
gl_FragColor = vec4(color, 1);
}
`;
function draw(gl, shaders, color1, color2, label) {
const programInfo = twgl.createProgramInfo(gl, shaders);
gl.useProgram(programInfo.program);
twgl.setUniforms(programInfo, {
color1: color1,
color2: color2,
});
gl.drawArrays(gl.POINTS, 0, 1);
const div = document.createElement("div");
const img = new Image();
img.src = gl.canvas.toDataURL();
div.appendChild(img);
const inner = document.createElement("span");
inner.textContent = label;
div.appendChild(inner);
document.body.appendChild(div);
}
const color1 = [1.0, 0.55, 0];
const color2 = [0.226, 0.000, 0.615];
draw(gl, [vsrc, fRGB], color1, color2, "rgb");
draw(gl, [vsrc, fHSV], color1, color2, "hsv");
draw(gl, [vsrc, fHSV], color1, color2, "hsl");
draw(gl, [vsrc, fLAB], color1, color2, "lab");
img { border: 1px solid black; margin: 2px; }
span { display: block; }
div { display: inline-block; text-align: center; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
I would also suggest passing your colors in in a ramp texture. An Nx1 texture and using
color = texture2D(
rampTexture,
vec2((mixValue * (rampWidth - 1.) + .5) / rampWidth, 0.5)).rgb;
Then you can easily blend across 2 colors, 3 colors, 20 colors. You can space out the colors as well by repeating colors etc..
Example:
const gl = document.createElement("canvas").getContext("webgl");
gl.canvas.width = 100;
gl.canvas.height = 100;
gl.viewport(0, 0, 100, 100);
const vsrc = `
void main() {
gl_PointSize = 100.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
const fsrc = `
precision mediump float;
uniform sampler2D rampTexture;
uniform float rampWidth;
void main() {
vec2 st = gl_PointCoord;
float mixValue = distance(st, vec2(0, 1));
vec3 color = texture2D(
rampTexture,
vec2((mixValue * (rampWidth - 1.) + .5) / rampWidth, 0.5)).rgb;
gl_FragColor = vec4(color, 1);
}
`;
const programInfo = twgl.createProgramInfo(gl, [vsrc, fsrc]);
gl.useProgram(programInfo.program);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
function draw(gl, ramp, label) {
const width = ramp.length;
gl.bindTexture(gl.TEXTURE_2D, tex);
const level = 0;
const internalFormat = gl.RGB;
const height = 1;
const border = 0;
const format = gl.RGB;
const type = gl.UNSIGNED_BYTE;
const rampData = new Uint8Array([].concat(...ramp).map(v => v * 255));
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border,
format, type, rampData);
twgl.setUniforms(programInfo, {
rampTexture: tex,
rampWidth: width,
});
gl.drawArrays(gl.POINTS, 0, 1);
const div = document.createElement("div");
const img = new Image();
img.src = gl.canvas.toDataURL();
div.appendChild(img);
const inner = document.createElement("span");
inner.textContent = label;
div.appendChild(inner);
document.body.appendChild(div);
}
const color1 = [1.0, 0.55, 0];
const color2 = [0.226, 0.000, 0.615];
const r = [1, 0, 0];
const g = [0, 1, 0];
const b = [0, 0, 1];
const w = [1, 1, 1];
draw(gl, [color1, color2], "color1->color2");
draw(gl, [r, g], "red->green");
draw(gl, [r, g, b], "r->g->b");
draw(gl, [r, b, r, b, r], "r->b->r->b->r");
draw(gl, [g, b, b, b, g], "g->b->b->b->g");
img { border: 1px solid black; margin: 2px; }
span { display: block; }
div { display: inline-block; text-align: center; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
Note: a 1 dimensional 256x1 texture is how Chrome, Firefox, and Android render both linear and radial gradients. See src
Ther is a simple mistake when you set up the color value. You used 1.9 for the value of red color channel instead of 1.0, when you set up the orange color.
Change your code to:
vec3 color1 = vec3(1.0, 0.55, 0.0); // 1.0 insted of 1.9
Note, the final color channels are clamped to [0, 1], but since you use mix
to interpolate the colors, a color channel above 1.0 raises the part of the color in the gradient.
Preview:
var ShaderProgram = {};
ShaderProgram.Create = function( shaderList ) {
var shaderObjs = [];
for ( var i_sh = 0; i_sh < shaderList.length; ++ i_sh ) {
var shderObj = this.CompileShader( shaderList[i_sh].source, shaderList[i_sh].stage );
if ( shderObj == 0 )
return 0;
shaderObjs.push( shderObj );
}
var progObj = this.LinkProgram( shaderObjs )
if ( progObj != 0 ) {
progObj.attribIndex = {};
var noOfAttributes = gl.getProgramParameter( progObj, gl.ACTIVE_ATTRIBUTES );
for ( var i_n = 0; i_n < noOfAttributes; ++ i_n ) {
var name = gl.getActiveAttrib( progObj, i_n ).name;
progObj.attribIndex[name] = gl.getAttribLocation( progObj, name );
}
progObj.unifomLocation = {};
var noOfUniforms = gl.getProgramParameter( progObj, gl.ACTIVE_UNIFORMS );
for ( var i_n = 0; i_n < noOfUniforms; ++ i_n ) {
var name = gl.getActiveUniform( progObj, i_n ).name;
progObj.unifomLocation[name] = gl.getUniformLocation( progObj, name );
}
}
return progObj;
}
ShaderProgram.AttributeIndex = function( progObj, name ) { return progObj.attribIndex[name]; }
ShaderProgram.UniformLocation = function( progObj, name ) { return progObj.unifomLocation[name]; }
ShaderProgram.Use = function( progObj ) { gl.useProgram( progObj ); }
ShaderProgram.SetUniformF2 = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform2fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.CompileShader = function( source, shaderStage ) {
var shaderScript = document.getElementById(source);
if (shaderScript) {
source = "";
var node = shaderScript.firstChild;
while (node) {
if (node.nodeType == 3) source += node.textContent;
node = node.nextSibling;
}
}
var shaderObj = gl.createShader( shaderStage );
gl.shaderSource( shaderObj, source );
gl.compileShader( shaderObj );
var status = gl.getShaderParameter( shaderObj, gl.COMPILE_STATUS );
if ( !status ) alert(gl.getShaderInfoLog(shaderObj));
return status ? shaderObj : 0;
}
ShaderProgram.LinkProgram = function( shaderObjs ) {
var prog = gl.createProgram();
for ( var i_sh = 0; i_sh < shaderObjs.length; ++ i_sh )
gl.attachShader( prog, shaderObjs[i_sh] );
gl.linkProgram( prog );
status = gl.getProgramParameter( prog, gl.LINK_STATUS );
if ( !status ) alert("Could not initialise shaders");
gl.useProgram( null );
return status ? prog : 0;
}
function drawScene(){
var canvas = document.getElementById( "ogl-canvas" );
var vp = [canvas.width, canvas.height];
gl.viewport( 0, 0, canvas.width, canvas.height );
gl.enable( gl.DEPTH_TEST );
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
ShaderProgram.Use( progDraw );
ShaderProgram.SetUniformF2( progDraw, "u_resolution", vp )
gl.enableVertexAttribArray( progDraw.inPos );
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
gl.disableVertexAttribArray( progDraw.pos );
}
var gl;
var prog;
var bufObj = {};
function sceneStart() {
var canvas = document.getElementById( "ogl-canvas");
gl = canvas.getContext( "experimental-webgl", { premultipliedAlpha: true } );
if ( !gl )
return;
progDraw = ShaderProgram.Create(
[ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
{ source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
] );
progDraw.inPos = gl.getAttribLocation( progDraw, "inPos" );
if ( prog == 0 )
return;
var pos = [ -1, -1, 1, -1, 1, 1, -1, 1 ];
var inx = [ 0, 1, 2, 0, 2, 3 ];
bufObj.pos = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( pos ), gl.STATIC_DRAW );
bufObj.inx = gl.createBuffer();
bufObj.inx.len = inx.length;
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( inx ), gl.STATIC_DRAW );
setInterval(drawScene, 50);
}
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 inPos;
varying vec2 vertPos;
void main()
{
vertPos = inPos;
gl_Position = vec4( inPos.xy, 0.0, 1.0 );
}
</script>
<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec2 vertPos;
uniform vec2 u_resolution;
void main()
{
vec2 st = gl_FragCoord.xy/u_resolution.xy;
vec3 color1 = vec3(1.0,0.55,0);
vec3 color2 = vec3(0.226,0.000,0.615);
float mixValue = distance(st,vec2(0,1));
vec3 color = mix(color1,color2,mixValue);
gl_FragColor = vec4(color,1.0);
}
</script>
<body onload="sceneStart();">
<canvas id="ogl-canvas" style="border: none;" width="256" height="256"></canvas>
</body>