Public
Edited
Jul 11, 2024
1 fork
1 star
Insert cell
Insert cell
Insert cell
// our target canvas
canvas = DOM.canvas(width, 500)
Insert cell
Insert cell
md`
<br>
The <code>WebGLRenderingContext</code> is how we can access the shader functionality of a canvas element.`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// a utiliy function to create a shader, that we can use for both shaders
createAndCompileShader = (gl, type, source) => {
// create a new empty shader object of the given type (gl.VERTEX_SHADER
// or gl.FRAGMENT_SHADER) using the given gl context
const shader = gl.createShader(type);

// add the sourcecode to the empty shader object
gl.shaderSource(shader, source);
// compile that shader
gl.compileShader(shader);

// if something goes wrong, delete the shader from the context and throw an error
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
gl.deleteShader(shader);
throw new Error(`Couldn't compile shader`);
}

return shader;
}
Insert cell
// the vertex shader (coordinates)
vertexShader = createAndCompileShader(gl, gl.VERTEX_SHADER, vsSource)
Insert cell
// the fragment shader (colors)
fragmentShader = createAndCompileShader(gl, gl.FRAGMENT_SHADER, fsSource)
Insert cell
Insert cell
createShaderProgram = (gl, vertexShader, fragmentShader) => {
// create a new empty shader program object using the given gl context
const shaderProgram = gl.createProgram();
// attach our shaders and link them
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);

// If creating the shader program failed, throw error and delete the program
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
throw new Error(`Couldn't link shader`);
gl.deleteProgram(shaderProgram);
}
return shaderProgram;
}
Insert cell
shaderProgram = createShaderProgram(gl, vertexShader, fragmentShader)
Insert cell
Insert cell
initialize = {
// Tell WebGL how to convert from clip space to pixels
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Clear the canvas
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);

// Tell it to use our program (pair of shaders)
gl.useProgram(shaderProgram);
/* To store data on the GPU, we have to use the --gl.ARRAY_BUFFER--. These are the five steps of
passing data to the Vertex Shader:
(1) Create a buffer object (gl.createBuffer()).
(2) Bind the buffer object to a target (gl.bindBuffer()).
(3) Write data into the buffer object (gl.bufferData()).
(4) Assign the buffer object to an attribute variable.
(4.1) Lookup the variable name (gl.getAttribLocation())
(4.2) Point the buffer to attribute (gl.vertexAttribPointer())
(5) Enable assignment (gl.enableVertexAttribArray()).
*/
// (1) Create a buffer object.
// If we give a variable name to the buffer, we can rebind to this buffer at a later point.
const positionBuffer = gl.createBuffer();
// (2) Bind the created buffer to gl.ARRAY_BUFFER
// (think of it as "set this buffer as the currently active ARRAY_BUFFER")
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

// (3) Write the position data to the currently bound ARRAY_BUFFER
// Hint: WebGL will always write to and read from the currently bound buffer. That means you can create
// multiple buffers (e.g. for positions, colors, normals etc.) and pass their data to WebGL by binding
// them to gl.ARRAY_BUFFER
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([ // the vertices of our triangle
-0.5, 0.5,
0.5, 0.5,
-0.5, -0.5,
]),
gl.STATIC_DRAW // => Contents of the buffer are likely to be used often and not change often.
// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferData
);

// (4.1) Look up where the vertex data needs to go (The variable name of the vertex shader).
// Hint: Looking up attribute locations (and uniform locations) is something
// you should do during initialization, not in your render loop.
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, "a_position");
// (4.2) Tell the attribute how to get data out of the currently bound buffer
const size = 2; // 2 components per iteration (x and y)
const type = gl.FLOAT; // the data is 32bit floats
const normalize = false; // don't normalize the data
const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
const bufferOffset = 0; // offset in bytes of the first component in the vertex attribute array. Must be a // multiple of the byte length of type
gl.vertexAttribPointer(
positionAttributeLocation,
size,
type,
normalize,
stride,
bufferOffset)

// (5) Turn on the attribute
gl.enableVertexAttribArray(positionAttributeLocation);
// draw
const primitiveType = gl.TRIANGLES;
const offset = 0;
const count = 3; // 3 vertices per triangle
gl.drawArrays(primitiveType, offset, count);
// logGLVersionAndGraphicsCard(gl);
}
Insert cell
Insert cell
Insert cell
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