Skip to content

Instantly share code, notes, and snippets.

@greggman
Last active May 3, 2026 07:38
Show Gist options
  • Select an option

  • Save greggman/0924097afdde605db6cc551dd664dd8e to your computer and use it in GitHub Desktop.

Select an option

Save greggman/0924097afdde605db6cc551dd664dd8e to your computer and use it in GitHub Desktop.
WebGL: lerp rgb colors in hsl space.
html, body {
margin: 0;
height: 100%;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
<canvas></canvas>
import * as twgl from 'https://twgljs.org/dist/5.x/twgl-full.module.js';
const { m4 } = twgl;
const vs = `#version 300 es
layout(location = 0) in vec4 position;
layout(location = 1) in float l;
out float v_l;
uniform mat4 matrix;
void main() {
gl_Position = matrix * vec4(position);
v_l = l;
}
`
const fs1 = `#version 300 es
precision highp float;
in float v_l;
out vec4 fragColor;
void main() {
fragColor = vec4(v_l, v_l, v_l, 1);
}
`
const fs2 = `#version 300 es
precision highp float;
in float v_l;
out vec4 fragColor;
uniform vec4 color1;
uniform vec4 color2;
void main() {
fragColor = mix(color1, color2, v_l);
}
`;
const fs3 = `#version 300 es
precision highp float;
in float v_l;
out vec4 fragColor;
uniform vec4 color1;
uniform vec4 color2;
float Epsilon = 1e-10;
vec3 rgbToHcv(vec3 rgb) {
// Based on work by Sam Hocevar and Emil Persson
vec4 P = mix(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.0 * C + Epsilon) + Q.z);
return vec3(H, C, Q.x);
}
vec3 rgbToHsl(vec3 rgb) {
vec3 hcv = rgbToHcv(rgb);
float L = hcv.z - hcv.y * 0.5;
float S = hcv.y / (1.0 - abs(L * 2.0 - 1.0) + Epsilon);
return vec3(hcv.x, S, L);
}
vec3 hslToRgb(vec3 hsl) {
vec3 c = vec3(fract(hsl.x), clamp(vec2(hsl.y, hsl.z), vec2(0), vec2(1)));
vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, vec3(0), vec3(1));
return c.z + c.y * (rgb - 0.5) * (1.0 - abs(2.0 * c.z - 1.0));
}
float mixCircular(float a, float b, float t) {
float diff = fract(b - a + 0.5) - 0.5;
return fract(a + diff * t);
}
void main() {
vec3 hsl1 = rgbToHsl(color1.rgb);
vec3 hsl2 = rgbToHsl(color2.rgb);
vec3 hsl = vec3(
mixCircular(hsl1.x, hsl2.x, v_l), // lerp the hue around a circle
mix(hsl1.yz, hsl2.yz, v_l)); // lerp the sat and lightness as normal
vec3 rgb = hslToRgb(hsl);
fragColor = vec4(rgb, 1);
}
`
const canvas = document.querySelector("canvas");
twgl.resizeCanvasToDisplaySize(canvas);
const gl = canvas.getContext('webgl2', {alpha: false});
const posLoc = 0; // set in shader
const lLoc = 1; // set in shader
const position = new Float32Array([
-1, -1, 1, -1, -1, 1,
-1, 1, 1, -1, 1, 1,
]);
const l = new Float32Array([
0, 1, 0,
0, 1, 1,
]);
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
gl.enableVertexAttribArray(posLoc);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
const lBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, lBuffer);
gl.bufferData(gl.ARRAY_BUFFER, l, gl.STATIC_DRAW);
gl.enableVertexAttribArray(lLoc);
gl.vertexAttribPointer(lLoc, 1, gl.FLOAT, false, 0, 0);
const programInfo1 = twgl.createProgramInfo(gl, [vs, fs1]);
const programInfo2 = twgl.createProgramInfo(gl, [vs, fs2]);
const programInfo3 = twgl.createProgramInfo(gl, [vs, fs3]);
gl.useProgram(programInfo1.program);
twgl.setUniforms(programInfo1, {
matrix: m4.scale(m4.translation([0, 0.5, 0]), [1, 0.2, 1]),
});
gl.drawArrays(gl.TRIANGLES, 0, 6);
gl.useProgram(programInfo2.program);
twgl.setUniforms(programInfo2, {
matrix: m4.scale(m4.translation([0, 0, 0]), [1, 0.2, 1]),
color1: [1, 0, 0, 1],
color2: [0, 0, 1, 1],
});
gl.drawArrays(gl.TRIANGLES, 0, 6);
gl.useProgram(programInfo3.program);
twgl.setUniforms(programInfo3, {
matrix: m4.scale(m4.translation([0, -0.5, 0]), [1, 0.2, 1]),
color1: [1, 0, 0, 1],
color2: [0, 0, 1, 1],
});
gl.drawArrays(gl.TRIANGLES, 0, 6);
{"name":"WebGL: lerp rgb colors in hsl space.","settings":{},"filenames":["index.html","index.css","index.js"]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment