Published
Edited
Mar 3, 2021
Insert cell
Insert cell
Insert cell
controls = {
const result = html`<form>
<div>${tex`k`} <input name=a type=range min=1.0 max=100.0 step=0.2></div>
</form>`;
result.a.oninput = () => mutable k = result.a.valueAsNumber;
result.a.value = mutable k;
return result;
}
Insert cell
mutable k = 10.0;
Insert cell
md`
[Shadertoy](https://www.shadertoy.com/) is a wonderful playground for exercising your shader coding skills. It provides you with all the infrastructure of a complete WebGL program, except for the fragment shader code. The vertex shader, however, is fixed and simply draws two triangles that cover the whole canvas. It is up to the user to provide code that computes the color of each pixel. I find it a delight to browse the many examples in the [Shadertoy](https://www.shadertoy.com/) site and marvel at the enormous appeal of this approach.

So how does one draw a primitive, a circle or a triangle, say, using only the fragment shader? The idea is to write code that tests each pixel and decides whether or not it is inside the primitive. Better still is to write a *signed distance function* (SDF) for the primitive, i.e., a function that returns how far the pixel is from the border of the primitive: a negative number if it is inside the primitive, or a positive number if outside. *Inigo Quilez*, the brain behind Shadertoy, has a [great page with several such functions for 2D shapes](https://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm), some of which we reproduce here.

Several modeling operators can be easily expressed with SDFs. For instance, if two shapes are described by SDFs ${tex`A(p)`} and ${tex`B(p)`}, then the shape corresponding to their union and intersection are described by SDFs ${tex`min(A(p),B(p))`} and ${tex`max(A(p),B(p))`}, respectively. Also, by adding or subtracting a constant to an SDF makes the shape shrink or expand, which is a convenient way to produce rounded corners.

Note: in order to understand how these distance functions work, one needs to be familiar with linear algebra concepts such as points and vectors, the dot product operator, etc. Since the functions are written in GLSL, they use several GLSL builtin math functions. *Shaderific* has a [very nice reference page](https://www.shaderific.com/glsl-functions) for these.

`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
vertexLocation = gl.getAttribLocation(program, 'position')
Insert cell
Insert cell
resolutionLocation = gl.getUniformLocation(program, 'iResolution')
Insert cell
offsetLocation = gl.getUniformLocation(program, 'distanceOffset')
Insert cell
gl.uniform
Insert cell
Insert cell
drawFrame = {
loadVertices; // Reactive dependency!
gl.clearColor(0.0, 0.0, 0.0, 1.0); // Opaque black
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.useProgram(program);

gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer( vertexLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vertexLocation);
gl.uniform3fv(resolutionLocation, [canvas.width,canvas.height,1])
gl.uniform1f(offsetLocation, config.offset)
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more