Published
Edited
Dec 16, 2020
Insert cell
md`# WebGL Triangle #4: Instancing(transform matrix)`
Insert cell
canvas = DOM.canvas(width, height);
Insert cell
{
// drawWithoutInstancing()
drawWithInstancing()
}
Insert cell
vao = {
const vao = gl.createVertexArray()
gl.bindVertexArray(vao)
{
// connect buffers and attributes
const numComponents = 2;
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(
attributeLocations.position,
numComponents,
type,
normalize,
stride,
offset);
gl.enableVertexAttribArray(attributeLocations.position);
}
{
// connect buffers and attributes
const numComponents = 4;
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.vertexAttribPointer(
attributeLocations.color,
numComponents,
type,
normalize,
stride,
offset);
gl.enableVertexAttribArray(attributeLocations.color);
}
{
// EBO
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo)
}
return vao
}
Insert cell
vaoForInstancing = {
const vao = gl.createVertexArray()
gl.bindVertexArray(vao)
{
// connect buffers and attributes
const numComponents = 2;
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(
attributeLocationsForInstancing.position,
numComponents,
type,
normalize,
stride,
offset);
gl.enableVertexAttribArray(attributeLocationsForInstancing.position);
}
{
// connect buffers and attributes
const numComponents = 4;
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.vertexAttribPointer(
attributeLocationsForInstancing.color,
numComponents,
type,
normalize,
stride,
offset);
gl.enableVertexAttribArray(attributeLocationsForInstancing.color);
}
{
// transform's attribute
// 4 consecutive attribute slots
gl.bindBuffer(gl.ARRAY_BUFFER, transformsBuffer);
for (let i = 0; i < 4; i++) {
const loc = attributeLocationsForInstancing.transform + i;
gl.enableVertexAttribArray(loc);
const numComponents = 4;
const type = gl.FLOAT;
const normalize = false;
const stride = 4 * 16; // 4 byte for float * 16 element in matrix
const offset = i * 16; // 4 byte for float * 4 float per vec4
gl.vertexAttribPointer(
loc,
numComponents,
type,
normalize,
stride,
offset);
gl.vertexAttribDivisor(loc, 1) // new added, every 1 instance, change this attribute
}
}
{
// EBO
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo)
}
return vao
}
Insert cell
function drawWithInstancing() {
// clear
gl.clearColor(1., 1., 1., 1.);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

// use program
gl.useProgram(programForInstancing);

// bind VAO
gl.bindVertexArray(vaoForInstancing)

{
// connect uniforms
gl.uniform2f(uniformLocationsForInstancing.resolution, width, height);
}
// draw
const offset = 0;
const vertexCount = 6;
const indexType = gl.UNSIGNED_SHORT;
const instanceCount = allTransforms.length;
gl.drawElementsInstanced(gl.TRIANGLES, vertexCount, indexType, offset, instanceCount)
}
Insert cell
function drawWithoutInstancing() {
// clear
gl.clearColor(1., 1., 1., 1.);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

// use program
gl.useProgram(program);

// bind VAO
gl.bindVertexArray(vao)

{
// connect uniforms
gl.uniform2f(uniformLocations.resolution, width, height);
}
// loop all transforms and set uniform
allTransforms.forEach(transform => {

gl.uniformMatrix4fv(uniformLocations.transform, false, new Float32Array(transform));
// draw
const offset = 0;
const vertexCount = 6;
const indexType = gl.UNSIGNED_SHORT;
gl.drawElements(gl.TRIANGLES, vertexCount, indexType, offset)
})
}
Insert cell
Insert cell
indices = [
0, 1, 2,
0, 1, 3
]
Insert cell
Insert cell
allTransforms = {
const res = []
const d = {x: 100, y: 200}
const sizeDelta = 0.1
const cnt = {x: 5, y: 3}
const base = {x: 100, y: 100}
const sizeBase = 0.2
for (let i = 0; i < cnt.x; i++) {
for (let j = 0; j < cnt.y; j++) {
const currTransform = transform.slice()
currTransform[0] = sizeBase + (i + j) * sizeDelta
currTransform[5] = sizeBase + (i + j) * sizeDelta
currTransform[12] = base.x + i * d.y
currTransform[13] = base.y + j * d.y
res.push(currTransform)
}
}
return res
}
Insert cell
transformsBuffer = {
const array = allTransforms.flat()
return createBuffer(gl, array)
}
Insert cell
positionBuffer = createBuffer(gl, data.positions)
Insert cell
colorBuffer = createBuffer(gl, data.colors)
Insert cell
ebo = createIndexBuffer(gl, indices)
Insert cell
attributeLocationsForInstancing = {
return {
position: gl.getAttribLocation(programForInstancing, "in_position"),
color: gl.getAttribLocation(programForInstancing, "in_color"),
transform: gl.getAttribLocation(programForInstancing, "in_transform")
}
}
Insert cell
attributeLocations = {
return {
position: gl.getAttribLocation(program, "in_position"),
color: gl.getAttribLocation(program, "in_color")
}
}
Insert cell
uniformLocationsForInstancing = {
return {
resolution: gl.getUniformLocation(programForInstancing, "u_resolution"),
}
}
Insert cell
uniformLocations = {
return {
resolution: gl.getUniformLocation(program, "u_resolution"),
transform: gl.getUniformLocation(program, "u_transform"),
}
}
Insert cell
programForInstancing = createProgram(gl, vertexShaderSourceForInstancing, fragmentShaderSource)
Insert cell
program = createProgram(gl, vertexShaderSource, fragmentShaderSource)
Insert cell
gl = canvas.getContext('webgl2')
Insert cell
height = width * 0.75
Insert cell
width
Insert cell
Insert cell
Insert cell
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