Public
Edited
Nov 22, 2021
1 fork
23 stars
Insert cell
Insert cell
Insert cell
example = {
const gl = html`<canvas width=400 height=400>`.getContext('webgl2');

const data = Float32Array.of( // 4 vertices with 4 components each
-0.7, 0.7, 0, 1,
-0.7, -0.7, 0, 0,
0.7, -0.7, 1, 0,
0.7, 0.7, 1, 1
);
const indices = Uint16Array.of( // 2 triangles
0, 1, 2,
0, 2, 3
);
const state = createDrawState(gl, {
attributes: {
a_pos_uv: {data, size: 4}
},
indices,
vert: `
uniform float u_time;
in vec4 a_pos_uv;
out vec4 v_color;
void main() {
vec2 rotation = vec2(sin(u_time), cos(u_time));
v_color = vec4(a_pos_uv.zw, 0.5 + rotation.x * 0.5, 1);
gl_Position = vec4(mat2(rotation.y, -rotation.x, rotation) * a_pos_uv.xy, 0, 1);
}`,
frag: `
precision mediump float;
in vec4 v_color;
out vec4 color;

void main() {
color = v_color;
}`
});

state.use();

return animate(gl, () => {
gl.uniform1f(state.uniforms.u_time, performance.now() / 1000);
state.draw();
});
}
Insert cell
Insert cell
// create an object encapsulating state for a draw call — shaders, buffers, attributes, uniforms
function createDrawState(gl, {
vert,
frag,
attributes,
indices,
count,
offset = 0,
type = gl.TRIANGLES
}) {
const program = createProgram(gl, vert, frag);
const uniforms = getUniformLocations(gl, program);
const vao = createVertexArray(gl, program, attributes, indices);
if (!count && ArrayBuffer.isView(indices)) count = indices.length;

return {
uniforms,
use() {
gl.useProgram(program);
gl.bindVertexArray(vao);
},
draw() {
if (indices) {
gl.drawElements(type, count, gl.UNSIGNED_SHORT, offset);
} else {
gl.drawArrays(type, offset, count);
}
}
};
}
Insert cell
// compile shaders and link into a program
function createProgram(gl, vertexSrc, fragmentSrc) {
const vert = gl.createShader(gl.VERTEX_SHADER);
const frag = gl.createShader(gl.FRAGMENT_SHADER);

const pragma = '#version 300 es\n';
gl.shaderSource(vert, pragma + vertexSrc);
gl.shaderSource(frag, pragma + fragmentSrc);

gl.compileShader(vert);
gl.compileShader(frag);

const program = gl.createProgram();
gl.attachShader(program, vert);
gl.attachShader(program, frag);
gl.linkProgram(program);

if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const progLog = gl.getProgramInfoLog(program);
const vertLog = gl.getShaderInfoLog(vert);
const fragLog = gl.getShaderInfoLog(frag);
throw new Error([progLog, vertLog, fragLog].filter(Boolean).join('\n'));
}

gl.deleteShader(vert);
gl.deleteShader(frag);

return program;
}
Insert cell
// create a vertex array object with the given attribute layout and vertex/index buffers
function createVertexArray(gl, program, attributes, indices) {
const vao = gl.createVertexArray();
const names = Object.keys(attributes);

gl.bindVertexArray(vao);

for (let i = 0; i < names.length; i++) {
let {
size = 1,
type = gl.FLOAT,
normalize = false,
stride = 0,
offset = 0,
buffer,
data
} = attributes[names[i]];

if (data && !buffer) buffer = createBuffer(gl, data);

gl.bindAttribLocation(program, i, names[i]);
gl.enableVertexAttribArray(i);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(i, size, type, normalize, stride, offset);
}

if (indices) {
if (ArrayBuffer.isView(indices)) indices = createIndexBuffer(gl, indices);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices);
}
return vao;
}
Insert cell
// create an array buffer with data
function createBuffer(gl, data, usage = gl.STATIC_DRAW, type = gl.ARRAY_BUFFER) {
const buffer = gl.createBuffer();
gl.bindBuffer(type, buffer);
gl.bufferData(type, data, usage);
return buffer;
}
Insert cell
// create an index buffer
function createIndexBuffer(gl, data, usage = gl.STATIC_DRAW) {
return createBuffer(gl, data, usage, gl.ELEMENT_ARRAY_BUFFER);
}
Insert cell
// get an object with all uniform locations for a program for easy access
function getUniformLocations(gl, program) {
const numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
const locations = {};
for (let i = 0; i < numUniforms; i++) {
const {name} = gl.getActiveUniform(program, i);
locations[name] = gl.getUniformLocation(program, name);
}
return locations;
}
Insert cell
// set up a requestAnimationFrame-based render loop, also handling resource disposal
function animate(gl, fn) {
let frameId = requestAnimationFrame(function draw() {
fn();
frameId = requestAnimationFrame(draw);
});
Inputs.disposal(gl.canvas).then(() => {
cancelAnimationFrame(frameId);
gl.getExtension('WEBGL_lose_context').loseContext();
});
return gl.canvas;
}
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