"Hello, world!"
You may need to read the previous articles about attributes and program structure in order to understand this one.
The program described below draws one monocolored triangle using a minimal WebGL shader program.
Initialization step
The initialization step is run once at the beginning of the program.
Rendering context
To draw on a canvas that already exists in the DOM, first get that canvas in JavaScript:
const canvas = document.querySelector("canvas");
μGL provides a function called makeFullscreenCanvas
that replaces all content in the DOM with a fullscreen canvas.
const canvas = makeFullscreenCanvas();
The rendering context can be obtained via a method on the canvas.
const gl = canvas.getContext("webgl2");
Shader program
The vertex shader for this shader program will have only one attribute that stores vertex position data.
#version 300 es
in vec4 a_position;
void main() {
gl_Position = a_position;
}
The fragment shader for this shader program will set all fragments to a static color.
#version 300 es
precision highp float;
out vec4 outColor;
void main() {
outColor = vec4(1, 0, 0, 1);
}
Assuming that the vertex shader's source code and the fragment shader's source code have been put into string
variables called vss
and fss
, respectively, the shader program can be linked like this:
const program = Program.fromSource(gl, vss, fss);
If you aren't using μGL, see the introductory article for the "vanilla" way to do this.
If your shader program is not linking or compiling correctly, make sure that there is no white space or any
comments before the #version 300 es
line in the shader strings. For example, the following shader string will
not work because it starts with a new line:
const vss = `
#version 300 es
in vec4 a_position;
void main() {
gl_Position = a_position;
}`;
Variable locations
μGL automatically creates a map of variable names to their locations when it links a shader program, so no
additional code is required to get variable locations. These maps are called program.uniforms
for uniforms and
program.attributes
for attributes.
If you aren't using μGL, see the attributes and uniforms articles for the "vanilla" ways to do this.
Buffers
This program will draw only one shape (a triangle) with one attribute (a_position
) per shape, so only one buffer is
needed. In μGL, the following line of code creates a buffer.
const trianglePositionBuffer = new Buffer(gl,
new Float32Array([0, 0, 0, 0.5, 0.7, 0]));
If you aren't using μGL, see the attributes article for the "vanilla" way to do this.
Vertex array objects
This program will only draw one shape (a triangle), so only one vertex array object (VAO) is needed. In μGL, the following block of code creates a VAO.
const triangleVao = new VAO(program, [
new AttributeState("a_position",
trianglePositionBuffer, 2)
]);
Render step
The render step is run once each frame. The requestAnimationFrame
function (vanilla JavaScript) can be used to loop
through the render step as fast as the browser is able to render it.
function renderStep() {
requestAnimationFrame(renderStep);
// Render step code goes here.
}
requestAnimationFrame(renderStep);
Clearing the viewport
The WebGL API provides a different function to clear each buffer of the viewport. The only viewport buffer that is being used by this program is the color buffer, which stores the color of each pixel.
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
μGL provides a function called clearContext
that clears all of the viewport's buffers to the provided values. It
becomes more useful in later programs, when multiple viewport buffers are used.
clearContext(gl, new Color(0, 0, 0, 0));
Resizing the viewport
Like images, canvases have two sizes. One is the physical (display) size, which is set by CSS and represents the actual size of the canvas on the document. The other is the number of pixels in the canvas (the resolution of the canvas). In order to prevent the canvas from appearing blurry, its resolution must be resized to match its display size.
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
μGL provides a function called resizeContext
that has similar functionality to the code above but is more performant.
resizeContext(gl);
Global state
Once the canvas has been resized, the WebGL API must be used to pass the new resolution of the canvas to WebGL so that WebGL can properly convert from clip space coordinates to screen space coordinates (pixels).
gl.viewport(0, 0, canvas.width, canvas.height);
μGL's resizeContext
automatically handles this.
Rasterizing
Since this program doesn't use any uniforms, all of the rasterization work can be done with one method call.
vao.draw();
If you aren't using μGL, see the attributes article for the "vanilla" way to do this.
Final code
Combining all of the above, the final code looks like this:
/*
Import μGL. In the browser, this will be imported from a
CDN link such as:
- Skypack: "https://cdn.skypack.dev/uugl"
- jsDelivr: "https://cdn.jsdelivr.net/npm/uugl"
- unpkg: "https://unpkg.com/uugl"
When μGL is installed locally, it is imported as "uugl."
*/
import {
Program,
Buffer,
VAO,
AttributeState,
clearContext,
Color,
resizeContext
} from "uugl";
/*
The following also works:
import Program from "uugl/program";
import Buffer from "uugl/buffer";
// Et cetera.
*/
const gl = document
.querySelector("canvas")
.getContext("webgl2");
const program = Program.fromSource(gl,
`#version 300 es
in vec4 a_position;
void main() { gl_Position = a_position; }`,
`#version 300 es
precision highp float;
out vec4 outColor;
void main() { outColor = vec4(1, 0, 0, 1); }`);
const buffer = new Buffer(gl,
new Float32Array([0, 0, 0, 0.5, 0.7, 0]));
const vao = new VAO(program,
[new AttributeState("a_position", buffer, 2)]);
function renderStep() {
requestAnimationFrame(renderStep);
clearContext(gl, new Color(0, 0, 0, 0));
resizeContext(gl);
vao.draw();
}
requestAnimationFrame(renderStep);
The next article is about varyings.