Create a canvas.
<canvas id="c">
By default, a canvas has a width of 300px and a height of 150px. That is tiny, you might want to increase it a bit.
For example, this page asks the canvas HTML element to fill its container, and also automatically compute the corresponding height so as to maintain a 2:1 aspect ratio (that is, be twice as wide as it is high).
canvas {
width: 100%;
aspect-ratio: 2/1;
}
Setup things so that the following JavaScript code runs after your document has
loaded. For example, this page uses a <script type="module>
block. Simply doing that guarantees that our code will run after the DOM has
loaded.
<script type="module">
// Rest of our code will go here...
</script>
Get hold of the canvas, and ask for it for its DOM width and height. The canvas is still living in delusion that its width and height are at their default value (300, 150), so we also need to tell the canvas, hey buddy, wake up, this is the space you're occupying on the page (DOM).
But that's not all. If you're viewing the page on a high density display, your
screen will be showing multiple pixels for each CSS pixel. For example, I'm
currently writing this on a device that has a devicePixelRatio
of
2. You're currently reading this on device that has a DPR of .
There are many ways of, and caveats to, using the DPR. To keep things minimal, let’s just multiply by it so that our example does not look blurry.
const canvas = document.getElementById("c");
canvas.width = canvas.clientWidth * devicePixelRatio;
canvas.height = canvas.clientHeight * devicePixelRatio;
So far, we've not done anything WebGL specific.
Now let's get hands our on the WebGL context, and paint it blue-ish.
const gl = canvas.getContext("webgl");
gl.clearColor(0, 0.5, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
Now onto the shaders. We need two, a vertex shader, which defines the area we're going to draw in, and a fragment shader, which defines the color each pixel in this area will have.
For now, let's create shaders that draw a point, and color it white.
const vs = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vs, `
void main() {
gl_Position = vec4(0., 0., 0., 1.);
gl_PointSize = 100.;
}`);
gl.compileShader(vs);
const fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fs, `
void main() {
gl_FragColor = vec4(1., 1., 1., 1.);
}`);
gl.compileShader(fs);
And tell the WebGL context of our canvas to use them.
const p = gl.createProgram();
gl.attachShader(p, vs);
gl.attachShader(p, fs);
gl.linkProgram(p);
gl.detachShader(p, vs);
gl.detachShader(p, fs);
gl.deleteShader(vs);
gl.deleteShader(fs);
Let us give WebGL a chance to speak, if it has something to say.
const log = gl.getProgramInfoLog(p);
if (log) console.error(log);
A common mistake is to omit the dots when specifying numbers in GLSL. GLSL is not JavaScript, and those dots are mandatory – for GLSL
1
and1.0
mean different things.Tip:
1.
is a shorthand for1.0
Everything's in place, let us ask GLSL to draw.
gl.useProgram(p);
gl.drawArrays(gl.POINTS, 0, 1);
Since we are not animating, we won't need our program subsequently, so we can also clean it up.
gl.useProgram(null);
gl.deleteProgram(p);
That's it, that's all it took to create that WebGL canvas you saw at the top of this page. Our lovely white dot on a blue ocean of nothingness.
Here is a 499 character standalone HTML file (shown below in an iframe) that shows an slowly pulsing WebGL box.
<canvas id=c><script>g=document.getElementById(`c`).getContext(`webgl`);a=35633
S=c=>(g.shaderSource(s=g.createShader(a--),
`precision highp float;uniform float t;void main(){gl_`+c+`0.,1.);}`),g.compileShader(s),s)
g.attachShader(p=g.createProgram(),S(`PointSize=100.;gl_Position=vec4(0.,0.,`))
g.attachShader(p,S(`FragColor=vec4(gl_FragCoord.x,sin(t),`));g.linkProgram(p);g.useProgram(p)
d=n=>(g.uniform1f(g.getUniformLocation(p,`t`),n/a),g.drawArrays(0,0,1),requestAnimationFrame(d))
d()</script>