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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more