GLSL has C's if/else, for, while, do/while, break, continue and return, plus switch in GLSL ES 3.00. Conditions must be bool (if (n) on an int fails with "boolean expression expected"), and GLSL ES 1.00 guaranteed only for loops with constant bounds. The fragment shader adds discard, which drops the fragment so nothing is written. Here it cuts rounded corners into BookNest's covers, and a for loop paints three title bars on each:
<canvas id="c" width="1200" height="300"
style="width:600px;height:150px;background:#ece8de"></canvas>
<script src="webgl-helper.js"></script>
<script>
GLH.paint(document.getElementById('c'), `#version 300 es
precision mediump float;
in vec2 vUV;
out vec4 outColor;
const vec3 covers[6] = vec3[6](
vec3(0.12, 0.37, 0.55), vec3(0.36, 0.25, 0.60), vec3(0.88, 0.60, 0.06),
vec3(0.25, 0.49, 0.23), vec3(0.71, 0.27, 0.18), vec3(0.16, 0.62, 0.56));
void main() {
vec2 px = vUV * vec2(600.0, 150.0); // CSS pixels, origin bottom left
vec2 local = vec2(mod(px.x, 100.0), px.y); // position inside this book's cell
vec2 d = abs(local - vec2(50.0, 75.0)) - vec2(28.0, 55.0);
if (length(max(d, 0.0)) > 10.0) discard; // outside the rounded rectangle
vec3 color = covers[int(px.x / 100.0)] * mix(0.8, 1.15, vUV.y);
for (int line = 0; line < 3; line++) { // three title bars near the top
float y = 118.0 - float(line) * 9.0;
float width = line == 0 ? 26.0 : 18.0;
if (abs(local.y - y) < 2.5 && abs(local.x - 50.0) < width)
color = mix(color, vec3(1.0), 0.75); // lighten the bar
}
outColor = vec4(color, 1.0);
}`);
window.__done = true;
</script>
A shader that may discard stops the GPU from depth-testing before shading (early-Z), so keep it for cut-outs.
<!doctype html>
<style>
body { margin: 0; background: #f7f4ee; }
canvas { display: block; width: 100%; max-width: 600px; background: #ece8de; }
</style>
<canvas id="c" width="1200" height="300"></canvas>
<script>
// Paint a whole canvas with one fragment shader: a full-screen quad passes vUV (0..1).
function paint(canvas, fsSource) {
const gl = canvas.getContext('webgl2');
const program = gl.createProgram();
const sources = [[gl.VERTEX_SHADER, `#version 300 es
in vec2 aPos; out vec2 vUV;
void main() { vUV = aPos * 0.5 + 0.5; gl_Position = vec4(aPos, 0.0, 1.0); }`], [gl.FRAGMENT_SHADER, fsSource]];
for (const [type, src] of sources) {
const s = gl.createShader(type);
gl.shaderSource(s, src); gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
gl.attachShader(program, s);
}
gl.bindAttribLocation(program, 0, 'aPos');
gl.linkProgram(program);
gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, 1,1, -1,-1, 1,1, -1,1]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, 6); // discarded fragments stay transparent: the CSS background shows
}
paint(document.getElementById('c'), `#version 300 es
precision mediump float;
in vec2 vUV;
out vec4 outColor;
const vec3 covers[6] = vec3[6](
vec3(0.12, 0.37, 0.55), vec3(0.36, 0.25, 0.60), vec3(0.88, 0.60, 0.06),
vec3(0.25, 0.49, 0.23), vec3(0.71, 0.27, 0.18), vec3(0.16, 0.62, 0.56));
void main() {
vec2 px = vUV * vec2(600.0, 150.0); // CSS pixels, origin bottom left
vec2 local = vec2(mod(px.x, 100.0), px.y); // position inside this book's cell
vec2 d = abs(local - vec2(50.0, 75.0)) - vec2(28.0, 55.0);
if (length(max(d, 0.0)) > 10.0) discard; // outside the rounded rectangle
vec3 color = covers[int(px.x / 100.0)] * mix(0.8, 1.15, vUV.y);
for (int line = 0; line < 3; line++) { // three title bars near the top
float y = 118.0 - float(line) * 9.0;
float width = line == 0 ? 26.0 : 18.0;
if (abs(local.y - y) < 2.5 && abs(local.x - 50.0) < width)
color = mix(color, vec3(1.0), 0.75); // lighten the bar
}
int stars = int(px.x / 100.0) % 3 + 3; // a while loop draws rating dots
int i = 0;
while (i < stars) {
if (distance(local, vec2(30.0 + float(i) * 10.0, 38.0)) < 3.0) color = vec3(1.0, 0.9, 0.5);
i++;
}
outColor = vec4(color, 1.0);
}`);
</script>