Published
Edited
Oct 17, 2020
2 forks
Insert cell
Insert cell
canvas = document.createElement('canvas')
Insert cell
canvas.width = 800
Insert cell
canvas.height = 450
Insert cell
Insert cell
Insert cell
Insert cell
cubeVtxShaderSource = `
// Use high-precision floating-point values
precision highp float;

// Attributes are unique to each vertex
attribute vec3 position; // (X, Y, Z) coordinates in 3D space

// Uniforms can be updated once before the shader is invoked
// (i.e. before a draw call is issued, with drawArrays/drawElements/etc)
uniform mat4 projMat; // Projects from 3D (perspective) into 2D
uniform mat4 modelViewMat; // Applies camera and model transformations

void main(void) {
// Multiply the input vertex position by matrices, to project it into 2D screen space
// Write our vertex position to the built-in output variable gl_Position
gl_Position = projMat * modelViewMat * vec4(position, 1.0);
}
`
Insert cell
Insert cell
Insert cell
cubeFragShaderSource = `
// Use high-precision floating-point values
precision highp float;

void main(void) {
// Write our color to the built-in output variable gl_FragColor
gl_FragColor = vec4(0.9, 0.4, 0.6, 1.0);
}
`
Insert cell
Insert cell
Insert cell
function compileShader (gl, type, source) {
// Obtain a shader resource from WebGL
const shader = gl.createShader(type)
// Send our shader's source code to the WebGL implementation
gl.shaderSource(shader, source)
// Tell WebGL to compile our shader
gl.compileShader(shader)

// If our shader failed to compile...
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
// Get the shader's log from WebGL and write it to the browser console
console.error('Shader compilation failed: ' + gl.getShaderInfoLog(shader))
// Delete the shader resource (it is no longer useful)
gl.deleteShader(shader)
// We don't have a useful shader to return, so just return null (nothing)
return null
}

// Return the compiled shader
return shader
}
Insert cell
Insert cell
Insert cell
function initShaderProgram (gl, vtxSource, fragSource) {
// Compile our vertex shader from it's source
const vtxShader = compileShader(gl, gl.VERTEX_SHADER, vtxSource)
// Compile our fragment shader from it's source
const fragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragSource)

// Obtain a shader program resource from WebGL
const program = gl.createProgram()
// Attach our vertex shader to the program
gl.attachShader(program, vtxShader)
// Attach our fragment shader to the program
gl.attachShader(program, fragShader)
// Link two attached shaders into one single program
// This is similar to how you might compile two C++ files separately, but
// pass them both to the linker to resolve dependencies and generate a
// single executable
gl.linkProgram(program)

// If our shader program failed to link (most likely because of an
// incompatibility between the two shaders in varyings/etc, or because we
// misused or didn't use appropriate built-in variables and functions)...
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
// Get the shader program's log from WebGL and write it to the browser console
console.error('Shader program linking failed: ' + gl.getProgramInfoLog(program))
// Delete shader and shader program resources
gl.deleteShader(vtxShader)
gl.deleteShader(fragShader)
gl.deleteProgram(program)
// We don't have a useful shader program to return, so just return null (nothing)
return null
}

return program
}
Insert cell
Insert cell
Insert cell
gl = canvas.getContext('webgl')
Insert cell
Insert cell
shaderProgram = initShaderProgram(gl, cubeVtxShaderSource, cubeFragShaderSource)
Insert cell
Insert cell
Insert cell
shaderProgramInfo = ({
attribs: {
position: gl.getAttribLocation(shaderProgram, 'position'),
},
uniforms: {
projMat: gl.getUniformLocation(shaderProgram, 'projMat'),
modelViewMat: gl.getUniformLocation(shaderProgram, 'modelViewMat'),
},
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function createBuffer (gl, target, data, mode) {
const buffer = gl.createBuffer()
gl.bindBuffer(target, buffer)
gl.bufferData(target, data, mode)
return buffer
}
Insert cell
Insert cell
vtxPosBuf = createBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(cubeVtxPosArray), gl.STATIC_DRAW)
Insert cell
Insert cell
indexBuf = createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeIndexArray), gl.STATIC_DRAW)
Insert cell
Insert cell
Insert cell
glMatrix = require('https://cdn.jsdelivr.net/npm/gl-matrix@3.3.0/gl-matrix-min.min.js')
Insert cell
Insert cell
projMat = glMatrix.mat4.create()
Insert cell
(glMatrix.mat4.perspective(projMat, 45 * Math.PI / 180, canvas.width / canvas.height, 0.1, 1000.0))
Insert cell
Insert cell
viewMat = glMatrix.mat4.create()
Insert cell
(glMatrix.mat4.translate(viewMat, viewMat, [ 0.0, 0.0, -7.0 ]))
Insert cell
Insert cell
modelMat = glMatrix.mat4.create()
Insert cell
Insert cell
Insert cell
function animateModelMatrix () {
glMatrix.mat4.identity(modelMat)
glMatrix.mat4.rotateX(modelMat, modelMat, Math.sin(Date.now() / 750.0) * 2)
glMatrix.mat4.rotateY(modelMat, modelMat, Math.cos(Date.now() / 750.0) * 2)
}
Insert cell
Insert cell
modelViewMat = glMatrix.mat4.create()
Insert cell
Insert cell
Insert cell
(gl.viewport(0, 0, canvas.width, canvas.height))
Insert cell
(gl.clearColor(0.1, 0.1, 0.1, 1.0))
Insert cell
(gl.enable(gl.DEPTH_TEST))
Insert cell
Insert cell
function drawScene () {
// It's considered best practice to schedule another animation frame
// immediately when the current frame begins
window.requestAnimationFrame(drawScene)

// Call the function we declared earlier to animate the model matrix
animateModelMatrix()
// Multiply view matrix * model matrix and write result to modelViewMatrix
glMatrix.mat4.multiply(modelViewMat, viewMat, modelMat)

// Clear the screen to empty
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

// Use our shader program to draw this model
gl.useProgram(shaderProgram)

// Pass our matrix data to the shader program
// 4fv = 4D array of 4D floating-point vectors
gl.uniformMatrix4fv(shaderProgramInfo.uniforms.projMat, false, projMat)
gl.uniformMatrix4fv(shaderProgramInfo.uniforms.modelViewMat, false, modelViewMat)

// Bind our vertex position buffer
gl.bindBuffer(gl.ARRAY_BUFFER, vtxPosBuf)
// Specify layout of our vertex buffer.
// This flexibility offered by this function is most useful if you're using
// interleaved buffers, which we aren't.
//
// Arguments: attribute location, number of components per attribute, data type, normalized, stride
// Stride = "offset in bytes between the beginning of consecutive vertex attributes"
// 0 sets stride = number of components per attribute * component data type size
// Offset = "offset in bytes of the first component in the vertex attribute array"
// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/vertexAttribPointer
gl.vertexAttribPointer(shaderProgramInfo.attribs.position, 3, gl.FLOAT, false, 0, 0)
// Enable our attribute location (basically: we bound useful data to it and
// the shader is going to access it)
gl.enableVertexAttribArray(shaderProgramInfo.attribs.position)

// Bind our index buffer
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuf)
// Issue a draw call!
//
// A "draw call" is a special API call that actually draws content on the screen
// So far, we've spent hundreds of lines of code setting up the pipeline, but
// nothing has been rendered yet.
// This call tells WebGL that we're done configuring things and want to use the
// resources we've bound to produce some graphical output.
//
// Arguments: mode (primitive type), number of elements, element data type, offset
// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements
gl.drawElements(gl.TRIANGLES, cubeIndexArray.length, gl.UNSIGNED_SHORT, 0)
}
Insert cell
Insert cell
(window.requestAnimationFrame(drawScene))
Insert cell
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