Skip to content

Instantly share code, notes, and snippets.

@greggman
Last active April 28, 2025 06:53
Show Gist options
  • Save greggman/786eaae29aef4044d9bef33fe3312d79 to your computer and use it in GitHub Desktop.
Save greggman/786eaae29aef4044d9bef33fe3312d79 to your computer and use it in GitHub Desktop.
WebGL: Draw quads using instancing with position data in f32 texture

WebGL: Draw quads using instancing with position data in f32 texture

view on jsgist

html, body {
margin: 0;
height: 100%;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
<canvas></canvas>
import 'https://cdn.jsdelivr.net/npm/@petamoriken/[email protected]/+esm'
import 'https://greggman.github.io/webgl-lint/webgl-lint.js';
import * as twgl from 'https://twgljs.org/dist/6.x/twgl-full.module.js';
const gl = document.querySelector('canvas').getContext('webgl2');
const useF32 = false; // false = use INT_10_10_10_2_REV, true = use FLOAT
const useRGB = true; // false = use RGBA32F, true = use RGB32F
const vs = `#version 300 es
uniform sampler2D tex;
layout(location = 0) in vec4 px;
layout(location = 1) in vec4 py;
layout(location = 2) in vec4 pc;
out vec3 v_color;
void main() {
float i = float(gl_InstanceID);
float x = texelFetch(tex, ivec2(i, 0), 0).x;
float y = texelFetch(tex, ivec2(i, 1), 0).x;
gl_Position = vec4(px.x * 0.1 + x, py.y * 0.1 + y, 0, 1);
v_color = vec3(pc.z / float(0x200) * 0.5 + 0.5);
}
`
const fs = `#version 300 es
precision highp float;
in vec3 v_color;
out vec4 fragColor;
void main() {
fragColor = vec4(v_color, 1);
}
`;
const unsigned = v => v > 0 ? v : (0x1000000 + v) & 0x3ff;
const r = (min, max) => Math.random() * (max - min) + min;
const remap = (v, fromMin, fromMax, toMin, toMax) => (v - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin;
const toInt10 = v => unsigned(Math.round(remap(v, -1, 1, -0x200, 0x1ff)));
const quadData = new ArrayBuffer(6 * 4 * 3);
const asF32 = new Float32Array(quadData);
const asF16 = new Float16Array(quadData);
const asU32 = new Uint32Array(quadData);
const quad = [
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
];
for (let i = 0; i < quad.length; i += 2) {
const o = i / 2;
if (useF32) {
asF32[o * 3 + 0] = quad[i];
asF32[o * 3 + 1] = quad[i + 1];
asF32[o * 3 + 2] = r(-0x200, 0x1ff);
} else {
const y = toInt10(quad[i + 1]);
const c = toInt10(r(-1, 1));
asF16[o * 6 + 0] = quad[i];
asU32[o * 3 + 1] = (y << 20) | (y << 10) | y;
asU32[o * 3 + 2] = (c << 20) | (c << 10) | c;
}
}
const vBuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vBuf);
gl.bufferData(gl.ARRAY_BUFFER, quadData, gl.STATIC_DRAW);
const iBuf = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iBuf);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 3, 4, 5]), gl.STATIC_DRAW);
const prg = twgl.createProgram(gl, [vs, fs]);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
const numQuads = 15;
const positions = new Float32Array(numQuads * 4 * 2);
const tMul = useRGB ? 3 : 4;
for (let i = 0; i < numQuads; ++i) {
const offset0 = (numQuads * 0 + i) * tMul;
const offset1 = (numQuads * 1 + i) * tMul;
positions[offset0 + 0] = r(-1, 1);
positions[offset1 + 0] = r(-1, 1);
}
gl.texImage2D(gl.TEXTURE_2D, 0, useRGB ? gl.RGB32F : gl.RGBA32F, numQuads, 2, 0, useRGB ? gl.RGB : gl.RGBA, gl.FLOAT, positions);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
function render() {
gl.clearColor(0.3, 0.2, 0.5, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.useProgram(prg);
gl.enableVertexAttribArray(0);
gl.enableVertexAttribArray(1);
gl.enableVertexAttribArray(2);
if (useF32) {
gl.vertexAttribPointer(0, 1, gl.FLOAT, false, 12, 0);
gl.vertexAttribPointer(1, 1, gl.FLOAT, false, 12, 4);
gl.vertexAttribPointer(2, 1, gl.FLOAT, false, 12, 8);
} else {
gl.vertexAttribPointer(0, 1, gl.HALF_FLOAT, false, 12, 0);
gl.vertexAttribPointer(1, 4, gl.INT_2_10_10_10_REV, true, 12, 4);
gl.vertexAttribPointer(2, 4, gl.INT_2_10_10_10_REV, false, 12, 8);
}
gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, numQuads);
}
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
const canvas = entry.target;
canvas.width = entry.contentBoxSize[0].inlineSize;
canvas.height = entry.contentBoxSize[0].blockSize;
}
render();
});
observer.observe(gl.canvas);
{"name":"WebGL: Draw quads using instancing with position data in f32 texture","settings":{},"filenames":["index.html","index.css","index.js"]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment