Creative Coding with WebGL
Hi! I'm Lea Rosema
- Frontend Developer at SinnerSchrader, Part of Accenture Interactive
- https://lea.codes/
- https://codepen.io/terabaud/
- @terabaud
Slides to my talk
What is WebGL?
- it's not a 3D engine
- it's low level a rasterization engine
- drawing points, lines, triangles, super fast
- runs on the GPU
How to WebGL?
- Vanilla JS WebGL
- Libraries on top of WebGL, eg. ThreeJS
- Game engines, running in WebAssembly+WebGL
- In this talk we're doing Vanilla JS WebGL :)
WebGL pipeline
(roughly)
- Buffers
- Vertex shader (processes buffer data)
- Rasterization (demo)
- Fragment shader (like: tixyland)
- Pixels on Screen 🟥 🟩 🟦
GL Shader Language
- Vertex and fragment shader are executed on the GPU
- GPU-specific language GL Shader language (GLSL)
- C++-like language with a
void main()
- built-in vector/matrix datatypes
- Rough language overview
- In detail: book of shaders
Vertex shader code
attribute vec4 position;
void main() {
gl_Position = position;
}
- input via the position attribute from a buffer
- the shader is run as many times as there's data
- the vertex position output via
gl_Position
Fragment shader code
precision highp float;
void main() {
vec2 p = gl_FragCoord.xy;
gl_FragColor = vec4(1.0, 0.5, 0, 1.0);
}
- The fragment shader is run for each fragment (pixel)
- pixel coordinate from
gl_FragCoord
- the output color is set in
gl_FragColor
Passing Data from JS
attribute
: the vertex shader pulls a value from a buffer and stores it in hereuniform
: pass variables you set in JS before you execute the shadervarying
: pass values from the vertex shader to the fragment shader and interpolate values
JS
Get the WebGL Context
const gl = canvas.getContext('webgl');
...just like initializing a 2D canvas
JS: Compile Shaders
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentCode);
gl.compileShader(fragmentShader);
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexCode);
gl.compileShader(vertexShader);
Like in C++, compile your shaders first.
JS: Create the program
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram();
Like in C++, link. Together they form a WebGLProgram
.
JS: Defining attributes for the vertex shader
const positionLoc = gl.getAttribLocation(program, 'position');
gl.enableVertexAttribArray(positionLoc);
Activate your attribute via enableVertexAttribArray
Assign a buffer to the attribute
// provide 2D data for a triangle
const data = [-1, -1, -1, 1, 1, -1];
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
Create a buffer and provide data in a Float32Array
Set the attribute pointer
gl.vertexAttribPointer(
positionLoc, // pointer to attribute
2, // record size
gl.FLOAT, // type
false, // normalized
0,
0
);
This points the attribute to your data buffer
Passing uniform variables
const uTime = gl.getUniformLocation(program, 'time');
gl.uniform1f(uTime, tickCount);
- Possible types: floats, ints, bools, vectors, matrices
- Pass variables from JavaScript to WebGL
- For example: pass the screen resolution, elapsed time, mouse position
Running it in JS
Draw
gl.drawArrays(gl.TRIANGLES);
Examples
Get pixel coordinates
uniform vec2 resolution;
vec2 p = (gl_FragCoord.xy / resolution - .5) * 2.;
// aspect ratio correction
float aspect = resolution.x / resolution.y;
p.x *= aspect;
Coordinates from varying
Vertex shader
attribute vec4 position;
varying vec4 vPosition;
vposition = position;
Fragment Shader
// contains interpolated values
varying vec4 vPosition;
Making 2D shapes with fragment shaders
- via Signed distance fields (SDFs)
- basically a function
- takes a point and returns distance to the nearest object
- if it returns a number less than zero, the point is inside an object
SDF distance functions
float sdCircle(vec2 p, float radius) {
return length(p) - radius;
}
float scene(vec2 p) {
return sdCircle(p, 1.);
}
Combining shapes
float merge(float a, float b) {
return min(a, b);
}
float substract(float a, float b) {
return max(-a, b);
}
float symmetricDiff(float a, float b) {
return max(min(a, b), -max(a, b));
}
Demos
Transforming coordinates
Rotate
vec2 rotate(vec2 p, float a) {
return vec2(p.x * cos(a) - p.y * sin(a),
p.x * sin(a) + p.y * cos(a));
}
Transforming coordinates
Repeat
vec2 repeat(in vec2 p, in vec2 c) {
return mod(p, c) - 0.5 * c;
}