In WebGL - when instancing geometry, is it possible to pass per-vertex attribute information for each instance?Calculate surface areaWorking around gl_PointSize limitations in three.js / webGLMethod for enumerating polyhedra vertex positions, normals, and texture coordinates GLSLVertex Displacement Doesn't Work in Three.jsOpenGL 4.4/ES 3.0 and caching vertex data between passesWebGL - Rendering multiple copies of a single object in different locations in shadersWebGL display loaded model without matrixTHREEjs: GLSL Vertex Position to CPUHow to store and access per fragment attributes in WebGLGet modified mesh displaced by displacementMap in three.js
Why A=2 and B=1 in the call signs for Spirit and Opportunity?
Heat lost in ideal capacitor charging
How to determine if a hyphen (-) exists inside a column
Are cells guaranteed to get at least one mitochondrion when they divide?
How would a developer who mostly fixed bugs for years at a company call out their contributions in their CV?
Is superuser the same as root?
Is my plasma cannon concept viable?
The disk image is 497GB smaller than the target device
A burglar's sunglasses, a lady's odyssey
Can a UK national work as a paid shop assistant in the USA?
What are nvme namespaces? How do they work?
How does the Earth's center produce heat?
...And they were stumped for a long time
Why did other houses not demand this?
Which European Languages are not Indo-European?
What weight should be given to writers groups critiques?
Dad jokes are fun
“For nothing” = “pour rien”?
Why would a rational buyer offer to buy with no conditions precedent?
Is it legal to have an abortion in another state or abroad?
Low voltage shutdown with regulator using microcontroller
If I arrive in the UK, and then head to mainland Europe, does my Schengen visa 90 day limit start when I arrived in the UK, or mainland Europe?
Are there any German nonsense poems (Jabberwocky)?
What is the use case for non-breathable waterproof pants?
In WebGL - when instancing geometry, is it possible to pass per-vertex attribute information for each instance?
Calculate surface areaWorking around gl_PointSize limitations in three.js / webGLMethod for enumerating polyhedra vertex positions, normals, and texture coordinates GLSLVertex Displacement Doesn't Work in Three.jsOpenGL 4.4/ES 3.0 and caching vertex data between passesWebGL - Rendering multiple copies of a single object in different locations in shadersWebGL display loaded model without matrixTHREEjs: GLSL Vertex Position to CPUHow to store and access per fragment attributes in WebGLGet modified mesh displaced by displacementMap in three.js
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty height:90px;width:728px;box-sizing:border-box;
I am trying to recreate through raw WebGL the following three.js example with instanced geometry; though, after testing it looks like I may just have to draw multiple meshes like in the sample but I thought I would ask here first to double check.
The question now is essentially - is it possible to pass per-vertex data for each instance of the geometry that gets rendered?
Hopefully that makes sense but if it doesn't -
- The geometry I'm rendering has 4 vertices.
- For each instance of the geometry - I need to assign uv coords to each vertex of each instance of the geometry
- it seems that setting a stride or offset does not appear to work.
Basically what is happening now is given the current data which looks something like
[s1,t1,s2,t2,s3,t3,s4,t4,s1,t1,s2,t2,s3,t3,s4,t4] // enough uvs for 2 instances, each s and t value makes up 1 uv coord for 1 vertex
Instead of data being read and assigned in blocks like this
// instance 1 // instance 2
[[s1,t1,s2,t2,s3,t3,s4,t4], [s1,t1,s2,t2,s3,t3,s4,t4]]
// Each s and t forms 1 uv coord for 1 vertex
Data appears to be instead being read like this
[s1,t1,s2,t2,s3,t3,s4,t4,s1,t1,s2,t2,s3,t3,s4,t4]
// Each s and t pair is assigned to every vertex of each instance
Is it at all possible to get things working like in the second block? If not that's fine but thought I ought to ask.
glsl webgl
add a comment |
I am trying to recreate through raw WebGL the following three.js example with instanced geometry; though, after testing it looks like I may just have to draw multiple meshes like in the sample but I thought I would ask here first to double check.
The question now is essentially - is it possible to pass per-vertex data for each instance of the geometry that gets rendered?
Hopefully that makes sense but if it doesn't -
- The geometry I'm rendering has 4 vertices.
- For each instance of the geometry - I need to assign uv coords to each vertex of each instance of the geometry
- it seems that setting a stride or offset does not appear to work.
Basically what is happening now is given the current data which looks something like
[s1,t1,s2,t2,s3,t3,s4,t4,s1,t1,s2,t2,s3,t3,s4,t4] // enough uvs for 2 instances, each s and t value makes up 1 uv coord for 1 vertex
Instead of data being read and assigned in blocks like this
// instance 1 // instance 2
[[s1,t1,s2,t2,s3,t3,s4,t4], [s1,t1,s2,t2,s3,t3,s4,t4]]
// Each s and t forms 1 uv coord for 1 vertex
Data appears to be instead being read like this
[s1,t1,s2,t2,s3,t3,s4,t4,s1,t1,s2,t2,s3,t3,s4,t4]
// Each s and t pair is assigned to every vertex of each instance
Is it at all possible to get things working like in the second block? If not that's fine but thought I ought to ask.
glsl webgl
1
"The geometry I'm rendering has 4 vertices." OK, let me stop you right there. Don't do instancing just to render quads. Just repeat the position data; you'll almost certainly be better off, performance-wise.
– Nicol Bolas
Mar 24 at 6:04
Agree with what Nicol said. It's faster to repeat the data than to use instancing so the less vertices per instance the better it is to just repeat the data.
– gman
Mar 24 at 15:34
ah got it - thanks for the tip Nicol.
– sortofsleepy
Mar 24 at 22:53
add a comment |
I am trying to recreate through raw WebGL the following three.js example with instanced geometry; though, after testing it looks like I may just have to draw multiple meshes like in the sample but I thought I would ask here first to double check.
The question now is essentially - is it possible to pass per-vertex data for each instance of the geometry that gets rendered?
Hopefully that makes sense but if it doesn't -
- The geometry I'm rendering has 4 vertices.
- For each instance of the geometry - I need to assign uv coords to each vertex of each instance of the geometry
- it seems that setting a stride or offset does not appear to work.
Basically what is happening now is given the current data which looks something like
[s1,t1,s2,t2,s3,t3,s4,t4,s1,t1,s2,t2,s3,t3,s4,t4] // enough uvs for 2 instances, each s and t value makes up 1 uv coord for 1 vertex
Instead of data being read and assigned in blocks like this
// instance 1 // instance 2
[[s1,t1,s2,t2,s3,t3,s4,t4], [s1,t1,s2,t2,s3,t3,s4,t4]]
// Each s and t forms 1 uv coord for 1 vertex
Data appears to be instead being read like this
[s1,t1,s2,t2,s3,t3,s4,t4,s1,t1,s2,t2,s3,t3,s4,t4]
// Each s and t pair is assigned to every vertex of each instance
Is it at all possible to get things working like in the second block? If not that's fine but thought I ought to ask.
glsl webgl
I am trying to recreate through raw WebGL the following three.js example with instanced geometry; though, after testing it looks like I may just have to draw multiple meshes like in the sample but I thought I would ask here first to double check.
The question now is essentially - is it possible to pass per-vertex data for each instance of the geometry that gets rendered?
Hopefully that makes sense but if it doesn't -
- The geometry I'm rendering has 4 vertices.
- For each instance of the geometry - I need to assign uv coords to each vertex of each instance of the geometry
- it seems that setting a stride or offset does not appear to work.
Basically what is happening now is given the current data which looks something like
[s1,t1,s2,t2,s3,t3,s4,t4,s1,t1,s2,t2,s3,t3,s4,t4] // enough uvs for 2 instances, each s and t value makes up 1 uv coord for 1 vertex
Instead of data being read and assigned in blocks like this
// instance 1 // instance 2
[[s1,t1,s2,t2,s3,t3,s4,t4], [s1,t1,s2,t2,s3,t3,s4,t4]]
// Each s and t forms 1 uv coord for 1 vertex
Data appears to be instead being read like this
[s1,t1,s2,t2,s3,t3,s4,t4,s1,t1,s2,t2,s3,t3,s4,t4]
// Each s and t pair is assigned to every vertex of each instance
Is it at all possible to get things working like in the second block? If not that's fine but thought I ought to ask.
glsl webgl
glsl webgl
asked Mar 23 at 23:27
sortofsleepysortofsleepy
2716
2716
1
"The geometry I'm rendering has 4 vertices." OK, let me stop you right there. Don't do instancing just to render quads. Just repeat the position data; you'll almost certainly be better off, performance-wise.
– Nicol Bolas
Mar 24 at 6:04
Agree with what Nicol said. It's faster to repeat the data than to use instancing so the less vertices per instance the better it is to just repeat the data.
– gman
Mar 24 at 15:34
ah got it - thanks for the tip Nicol.
– sortofsleepy
Mar 24 at 22:53
add a comment |
1
"The geometry I'm rendering has 4 vertices." OK, let me stop you right there. Don't do instancing just to render quads. Just repeat the position data; you'll almost certainly be better off, performance-wise.
– Nicol Bolas
Mar 24 at 6:04
Agree with what Nicol said. It's faster to repeat the data than to use instancing so the less vertices per instance the better it is to just repeat the data.
– gman
Mar 24 at 15:34
ah got it - thanks for the tip Nicol.
– sortofsleepy
Mar 24 at 22:53
1
1
"The geometry I'm rendering has 4 vertices." OK, let me stop you right there. Don't do instancing just to render quads. Just repeat the position data; you'll almost certainly be better off, performance-wise.
– Nicol Bolas
Mar 24 at 6:04
"The geometry I'm rendering has 4 vertices." OK, let me stop you right there. Don't do instancing just to render quads. Just repeat the position data; you'll almost certainly be better off, performance-wise.
– Nicol Bolas
Mar 24 at 6:04
Agree with what Nicol said. It's faster to repeat the data than to use instancing so the less vertices per instance the better it is to just repeat the data.
– gman
Mar 24 at 15:34
Agree with what Nicol said. It's faster to repeat the data than to use instancing so the less vertices per instance the better it is to just repeat the data.
– gman
Mar 24 at 15:34
ah got it - thanks for the tip Nicol.
– sortofsleepy
Mar 24 at 22:53
ah got it - thanks for the tip Nicol.
– sortofsleepy
Mar 24 at 22:53
add a comment |
1 Answer
1
active
oldest
votes
It depends on what you mean by
is it possible to pass per-vertex data for each instance of the geometry that gets rendered?
If you mean via attributes then the answer is no. You can get data per vertex or data per instance but not data per vertex per instance.
But you also get 2 extra inputs, gl_VertexID
and gl_InstanceID
(in WebGL2) or you can add your own which you could use to either compute UVs or use to look up data in a texture to effectively achieve your desired result.
For example let's say you have 20x10 cubes. You could do something like
attribute vec4 position; // 36 cube positions
attribute vec2 uv; // 36 cube UVs
attribute float instanceId; // 1 per instance
uniform float cubesAcross; // set to 20
uniform float cubesDown; // set to 10
varying v_uv;
void main()
float cubeX = mod(instanceId, cubesAcross);
float cubeY = floor(instanceId / cubesAcross);
v_vu = (vec2(cubeX, cubeY) + uv) / vec2(cubesAcross, cubesDown);
gl_Position = ...whatever you were doing for position before...
example:
"use strict";
function main()
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext)
return alert('need ANGLE_instanced_arrays');
twgl.addExtensionsToContext(gl);
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
attribute mat4 matrix;
attribute float instanceId;
uniform float cubesAcross; // set to 20
uniform float cubesDown; // set to 10
varying vec2 v_texcoord;
void main()
gl_Position = matrix * position;
float cubeX = mod(instanceId, cubesAcross);
float cubeY = floor(instanceId / cubesAcross);
v_texcoord = (vec2(cubeX, cubeY) + texcoord) / vec2(cubesAcross, cubesDown);
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
void main()
gl_FragColor = vec4(v_texcoord, 0, 1);
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const cubesAcross = 20;
const cubesDown = 10;
const numCubes = cubesAcross * cubesDown;
// matrix per instance
const matrixData = new Float32Array(16 * numCubes);
const matrices = [];
const instanceIds = new Float32Array(numCubes);
for (let i = 0; i < numCubes; ++i)
instanceIds[i] = i;
// make a typedarray view for each matrix
matrices.push(matrixData.subarray(i * 16, (i + 1) * 16));
const arrays =
position: [
1, 1, -1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, 1, 1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
1, 1, 1,
-1, 1, 1,
-1, -1, 1,
1, -1, 1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
],
texcoord: [
1, 0,
0, 0,
0, 1,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1,
0, 1,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1,
1, 0,
0, 0,
1, 1,
0, 1,
0, 0,
1, 0,
1, 0,
0, 0,
0, 1,
1, 1,
],
indices: [
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23,
],
instanceId: numComponents: 1, data: instanceIds, divisor: 1 ,
matrix: numComponents: 16, data: matrices, divisor: 1 ,
;
// create buffers, upload data (gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
function render(time) gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 24, 76];
const target = [18, 10, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
// update the instance for each matrix
const spacing = 2.5 + Math.sin(time) * .5;
let i = 0;
for (let y = 0; y < cubesDown; ++y)
for (let x = 0; x < cubesAcross; ++x)
const matrix = matrices[i++];
m4.translate(viewProjection, [
x * spacing,
y * spacing,
0,
], matrix);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer, ext.vertexAttribDivisorANGLE
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo,
cubesAcross,
cubesDown,
);
// upload instance matrices to buffer
gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.matrix.buffer);
gl.bufferData(gl.ARRAY_BUFFER, matrixData, gl.DYNAMIC_DRAW);
ext.drawElementsInstancedANGLE(
gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0, numCubes);
requestAnimationFrame(render);
requestAnimationFrame(render);
main();
body
margin: 0;
canvas
width: 100vw;
height: 100vh;
display: block;
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
If you actually want unique per vertex per instance data then put it in a texture and pass in a vertexId as well. Then use both vertexId and instanceId to compute a texture coordinate
attribute float instanceId;
attribute float vertexId;
uniform sampler2D dataTexture;
uniform vec2 dataTextureSize;
varying vec2 v_texcoord;
void main() {
// each row is for an instance, each texel
// per vertex. Of course if you want more data
// per vertex then multiply vertexId by the number of
// vec4s of data you need. If you need more instances
// then compute a more complex offset based off instanceId
vec2 uv = (vec2(vertexId, instanceId) + .5) / dataTextureSize;
vec4 data = texture2D(dataTexture, uv);
v_texcoord = data.xy;
"use strict";
function main()
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext1 = gl.getExtension('OES_texture_float');
if (!ext1)
return alert('need OES_texture_float');
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext)
return alert('need ANGLE_instanced_arrays');
twgl.addExtensionsToContext(gl);
const vs = `
attribute vec4 position;
attribute mat4 matrix;
attribute float instanceId;
attribute float vertexId;
uniform sampler2D dataTexture;
uniform vec2 dataTextureSize;
varying vec2 v_texcoord;
void main()
gl_Position = matrix * position;
// each row is for an instance, each texel
// per vertex. Of course if you want more data
// per vertex then multiply vertexId by the number of
// vec4s of data you need. If you need more instances
// then compute a more complex offset based off instanceId
vec2 uv = (vec2(vertexId, instanceId) + .5) / dataTextureSize;
vec4 data = texture2D(dataTexture, uv);
v_texcoord = data.xy;
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
void main()
gl_FragColor = vec4(v_texcoord, 0, 1);
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const cubesAcross = 20;
const cubesDown = 10;
const numCubes = cubesAcross * cubesDown;
// matrix per instance
const matrixData = new Float32Array(16 * numCubes);
const matrices = [];
const instanceIds = new Float32Array(numCubes);
for (let i = 0; i < numCubes; ++i)
instanceIds[i] = i;
// make a typedarray view for each matrix
matrices.push(matrixData.subarray(i * 16, (i + 1) * 16));
const arrays =
position: [
1, 1, -1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, 1, 1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
1, 1, 1,
-1, 1, 1,
-1, -1, 1,
1, -1, 1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
],
vertexId:
numComponents: 1,
data: [
0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15,
16, 17, 18, 19,
20, 21, 21, 23,
],
,
indices: [
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23,
],
instanceId: numComponents: 1, data: instanceIds, divisor: 1 ,
matrix: numComponents: 16, data: matrices, divisor: 1 ,
;
// create buffers, upload data (gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
// put UV data in texture
const uvs = [];
for (let y = 0; y < cubesDown; ++y)
const v0 = (y ) / cubesDown;
const v1 = (y + 1) / cubesDown;
for (let x = 0; x < cubesAcross; ++x)
const u0 = (x ) / cubesAcross;
const u1 = (x + 1) / cubesAcross;
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
const texWidth = 24; // width = 24 vertices * 1 texel per
const texHeight = numCubes; // height = numInstances
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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.texImage2D(
gl.TEXTURE_2D,
0, // level
gl.RGBA,
texWidth,
texHeight,
0, // border
gl.RGBA,
gl.FLOAT,
new Float32Array(uvs));
function render(time) gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 24, 76];
const target = [18, 10, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
// update the instance for each matrix
const spacing = 2.5 + Math.sin(time) * .5;
let i = 0;
for (let y = 0; y < cubesDown; ++y)
for (let x = 0; x < cubesAcross; ++x)
const matrix = matrices[i++];
m4.translate(viewProjection, [
x * spacing,
y * spacing,
0,
], matrix);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer, ext.vertexAttribDivisorANGLE
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo,
dataTexture: tex,
dataTextureSize: [texWidth, texHeight],
);
// upload instance matrices to buffer
gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.matrix.buffer);
gl.bufferData(gl.ARRAY_BUFFER, matrixData, gl.DYNAMIC_DRAW);
ext.drawElementsInstancedANGLE(
gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0, numCubes);
requestAnimationFrame(render);
requestAnimationFrame(render);
main();
body
margin: 0;
canvas
width: 100vw;
height: 100vh;
display: block;
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
Thanks for the help and many thanks for the detailed sample! Figured it would have to end up something along these lines if I didn't just re-render things.
– sortofsleepy
Mar 24 at 22:51
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55319323%2fin-webgl-when-instancing-geometry-is-it-possible-to-pass-per-vertex-attribute%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
It depends on what you mean by
is it possible to pass per-vertex data for each instance of the geometry that gets rendered?
If you mean via attributes then the answer is no. You can get data per vertex or data per instance but not data per vertex per instance.
But you also get 2 extra inputs, gl_VertexID
and gl_InstanceID
(in WebGL2) or you can add your own which you could use to either compute UVs or use to look up data in a texture to effectively achieve your desired result.
For example let's say you have 20x10 cubes. You could do something like
attribute vec4 position; // 36 cube positions
attribute vec2 uv; // 36 cube UVs
attribute float instanceId; // 1 per instance
uniform float cubesAcross; // set to 20
uniform float cubesDown; // set to 10
varying v_uv;
void main()
float cubeX = mod(instanceId, cubesAcross);
float cubeY = floor(instanceId / cubesAcross);
v_vu = (vec2(cubeX, cubeY) + uv) / vec2(cubesAcross, cubesDown);
gl_Position = ...whatever you were doing for position before...
example:
"use strict";
function main()
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext)
return alert('need ANGLE_instanced_arrays');
twgl.addExtensionsToContext(gl);
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
attribute mat4 matrix;
attribute float instanceId;
uniform float cubesAcross; // set to 20
uniform float cubesDown; // set to 10
varying vec2 v_texcoord;
void main()
gl_Position = matrix * position;
float cubeX = mod(instanceId, cubesAcross);
float cubeY = floor(instanceId / cubesAcross);
v_texcoord = (vec2(cubeX, cubeY) + texcoord) / vec2(cubesAcross, cubesDown);
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
void main()
gl_FragColor = vec4(v_texcoord, 0, 1);
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const cubesAcross = 20;
const cubesDown = 10;
const numCubes = cubesAcross * cubesDown;
// matrix per instance
const matrixData = new Float32Array(16 * numCubes);
const matrices = [];
const instanceIds = new Float32Array(numCubes);
for (let i = 0; i < numCubes; ++i)
instanceIds[i] = i;
// make a typedarray view for each matrix
matrices.push(matrixData.subarray(i * 16, (i + 1) * 16));
const arrays =
position: [
1, 1, -1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, 1, 1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
1, 1, 1,
-1, 1, 1,
-1, -1, 1,
1, -1, 1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
],
texcoord: [
1, 0,
0, 0,
0, 1,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1,
0, 1,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1,
1, 0,
0, 0,
1, 1,
0, 1,
0, 0,
1, 0,
1, 0,
0, 0,
0, 1,
1, 1,
],
indices: [
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23,
],
instanceId: numComponents: 1, data: instanceIds, divisor: 1 ,
matrix: numComponents: 16, data: matrices, divisor: 1 ,
;
// create buffers, upload data (gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
function render(time) gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 24, 76];
const target = [18, 10, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
// update the instance for each matrix
const spacing = 2.5 + Math.sin(time) * .5;
let i = 0;
for (let y = 0; y < cubesDown; ++y)
for (let x = 0; x < cubesAcross; ++x)
const matrix = matrices[i++];
m4.translate(viewProjection, [
x * spacing,
y * spacing,
0,
], matrix);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer, ext.vertexAttribDivisorANGLE
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo,
cubesAcross,
cubesDown,
);
// upload instance matrices to buffer
gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.matrix.buffer);
gl.bufferData(gl.ARRAY_BUFFER, matrixData, gl.DYNAMIC_DRAW);
ext.drawElementsInstancedANGLE(
gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0, numCubes);
requestAnimationFrame(render);
requestAnimationFrame(render);
main();
body
margin: 0;
canvas
width: 100vw;
height: 100vh;
display: block;
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
If you actually want unique per vertex per instance data then put it in a texture and pass in a vertexId as well. Then use both vertexId and instanceId to compute a texture coordinate
attribute float instanceId;
attribute float vertexId;
uniform sampler2D dataTexture;
uniform vec2 dataTextureSize;
varying vec2 v_texcoord;
void main() {
// each row is for an instance, each texel
// per vertex. Of course if you want more data
// per vertex then multiply vertexId by the number of
// vec4s of data you need. If you need more instances
// then compute a more complex offset based off instanceId
vec2 uv = (vec2(vertexId, instanceId) + .5) / dataTextureSize;
vec4 data = texture2D(dataTexture, uv);
v_texcoord = data.xy;
"use strict";
function main()
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext1 = gl.getExtension('OES_texture_float');
if (!ext1)
return alert('need OES_texture_float');
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext)
return alert('need ANGLE_instanced_arrays');
twgl.addExtensionsToContext(gl);
const vs = `
attribute vec4 position;
attribute mat4 matrix;
attribute float instanceId;
attribute float vertexId;
uniform sampler2D dataTexture;
uniform vec2 dataTextureSize;
varying vec2 v_texcoord;
void main()
gl_Position = matrix * position;
// each row is for an instance, each texel
// per vertex. Of course if you want more data
// per vertex then multiply vertexId by the number of
// vec4s of data you need. If you need more instances
// then compute a more complex offset based off instanceId
vec2 uv = (vec2(vertexId, instanceId) + .5) / dataTextureSize;
vec4 data = texture2D(dataTexture, uv);
v_texcoord = data.xy;
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
void main()
gl_FragColor = vec4(v_texcoord, 0, 1);
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const cubesAcross = 20;
const cubesDown = 10;
const numCubes = cubesAcross * cubesDown;
// matrix per instance
const matrixData = new Float32Array(16 * numCubes);
const matrices = [];
const instanceIds = new Float32Array(numCubes);
for (let i = 0; i < numCubes; ++i)
instanceIds[i] = i;
// make a typedarray view for each matrix
matrices.push(matrixData.subarray(i * 16, (i + 1) * 16));
const arrays =
position: [
1, 1, -1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, 1, 1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
1, 1, 1,
-1, 1, 1,
-1, -1, 1,
1, -1, 1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
],
vertexId:
numComponents: 1,
data: [
0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15,
16, 17, 18, 19,
20, 21, 21, 23,
],
,
indices: [
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23,
],
instanceId: numComponents: 1, data: instanceIds, divisor: 1 ,
matrix: numComponents: 16, data: matrices, divisor: 1 ,
;
// create buffers, upload data (gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
// put UV data in texture
const uvs = [];
for (let y = 0; y < cubesDown; ++y)
const v0 = (y ) / cubesDown;
const v1 = (y + 1) / cubesDown;
for (let x = 0; x < cubesAcross; ++x)
const u0 = (x ) / cubesAcross;
const u1 = (x + 1) / cubesAcross;
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
const texWidth = 24; // width = 24 vertices * 1 texel per
const texHeight = numCubes; // height = numInstances
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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.texImage2D(
gl.TEXTURE_2D,
0, // level
gl.RGBA,
texWidth,
texHeight,
0, // border
gl.RGBA,
gl.FLOAT,
new Float32Array(uvs));
function render(time) gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 24, 76];
const target = [18, 10, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
// update the instance for each matrix
const spacing = 2.5 + Math.sin(time) * .5;
let i = 0;
for (let y = 0; y < cubesDown; ++y)
for (let x = 0; x < cubesAcross; ++x)
const matrix = matrices[i++];
m4.translate(viewProjection, [
x * spacing,
y * spacing,
0,
], matrix);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer, ext.vertexAttribDivisorANGLE
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo,
dataTexture: tex,
dataTextureSize: [texWidth, texHeight],
);
// upload instance matrices to buffer
gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.matrix.buffer);
gl.bufferData(gl.ARRAY_BUFFER, matrixData, gl.DYNAMIC_DRAW);
ext.drawElementsInstancedANGLE(
gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0, numCubes);
requestAnimationFrame(render);
requestAnimationFrame(render);
main();
body
margin: 0;
canvas
width: 100vw;
height: 100vh;
display: block;
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
Thanks for the help and many thanks for the detailed sample! Figured it would have to end up something along these lines if I didn't just re-render things.
– sortofsleepy
Mar 24 at 22:51
add a comment |
It depends on what you mean by
is it possible to pass per-vertex data for each instance of the geometry that gets rendered?
If you mean via attributes then the answer is no. You can get data per vertex or data per instance but not data per vertex per instance.
But you also get 2 extra inputs, gl_VertexID
and gl_InstanceID
(in WebGL2) or you can add your own which you could use to either compute UVs or use to look up data in a texture to effectively achieve your desired result.
For example let's say you have 20x10 cubes. You could do something like
attribute vec4 position; // 36 cube positions
attribute vec2 uv; // 36 cube UVs
attribute float instanceId; // 1 per instance
uniform float cubesAcross; // set to 20
uniform float cubesDown; // set to 10
varying v_uv;
void main()
float cubeX = mod(instanceId, cubesAcross);
float cubeY = floor(instanceId / cubesAcross);
v_vu = (vec2(cubeX, cubeY) + uv) / vec2(cubesAcross, cubesDown);
gl_Position = ...whatever you were doing for position before...
example:
"use strict";
function main()
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext)
return alert('need ANGLE_instanced_arrays');
twgl.addExtensionsToContext(gl);
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
attribute mat4 matrix;
attribute float instanceId;
uniform float cubesAcross; // set to 20
uniform float cubesDown; // set to 10
varying vec2 v_texcoord;
void main()
gl_Position = matrix * position;
float cubeX = mod(instanceId, cubesAcross);
float cubeY = floor(instanceId / cubesAcross);
v_texcoord = (vec2(cubeX, cubeY) + texcoord) / vec2(cubesAcross, cubesDown);
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
void main()
gl_FragColor = vec4(v_texcoord, 0, 1);
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const cubesAcross = 20;
const cubesDown = 10;
const numCubes = cubesAcross * cubesDown;
// matrix per instance
const matrixData = new Float32Array(16 * numCubes);
const matrices = [];
const instanceIds = new Float32Array(numCubes);
for (let i = 0; i < numCubes; ++i)
instanceIds[i] = i;
// make a typedarray view for each matrix
matrices.push(matrixData.subarray(i * 16, (i + 1) * 16));
const arrays =
position: [
1, 1, -1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, 1, 1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
1, 1, 1,
-1, 1, 1,
-1, -1, 1,
1, -1, 1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
],
texcoord: [
1, 0,
0, 0,
0, 1,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1,
0, 1,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1,
1, 0,
0, 0,
1, 1,
0, 1,
0, 0,
1, 0,
1, 0,
0, 0,
0, 1,
1, 1,
],
indices: [
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23,
],
instanceId: numComponents: 1, data: instanceIds, divisor: 1 ,
matrix: numComponents: 16, data: matrices, divisor: 1 ,
;
// create buffers, upload data (gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
function render(time) gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 24, 76];
const target = [18, 10, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
// update the instance for each matrix
const spacing = 2.5 + Math.sin(time) * .5;
let i = 0;
for (let y = 0; y < cubesDown; ++y)
for (let x = 0; x < cubesAcross; ++x)
const matrix = matrices[i++];
m4.translate(viewProjection, [
x * spacing,
y * spacing,
0,
], matrix);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer, ext.vertexAttribDivisorANGLE
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo,
cubesAcross,
cubesDown,
);
// upload instance matrices to buffer
gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.matrix.buffer);
gl.bufferData(gl.ARRAY_BUFFER, matrixData, gl.DYNAMIC_DRAW);
ext.drawElementsInstancedANGLE(
gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0, numCubes);
requestAnimationFrame(render);
requestAnimationFrame(render);
main();
body
margin: 0;
canvas
width: 100vw;
height: 100vh;
display: block;
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
If you actually want unique per vertex per instance data then put it in a texture and pass in a vertexId as well. Then use both vertexId and instanceId to compute a texture coordinate
attribute float instanceId;
attribute float vertexId;
uniform sampler2D dataTexture;
uniform vec2 dataTextureSize;
varying vec2 v_texcoord;
void main() {
// each row is for an instance, each texel
// per vertex. Of course if you want more data
// per vertex then multiply vertexId by the number of
// vec4s of data you need. If you need more instances
// then compute a more complex offset based off instanceId
vec2 uv = (vec2(vertexId, instanceId) + .5) / dataTextureSize;
vec4 data = texture2D(dataTexture, uv);
v_texcoord = data.xy;
"use strict";
function main()
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext1 = gl.getExtension('OES_texture_float');
if (!ext1)
return alert('need OES_texture_float');
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext)
return alert('need ANGLE_instanced_arrays');
twgl.addExtensionsToContext(gl);
const vs = `
attribute vec4 position;
attribute mat4 matrix;
attribute float instanceId;
attribute float vertexId;
uniform sampler2D dataTexture;
uniform vec2 dataTextureSize;
varying vec2 v_texcoord;
void main()
gl_Position = matrix * position;
// each row is for an instance, each texel
// per vertex. Of course if you want more data
// per vertex then multiply vertexId by the number of
// vec4s of data you need. If you need more instances
// then compute a more complex offset based off instanceId
vec2 uv = (vec2(vertexId, instanceId) + .5) / dataTextureSize;
vec4 data = texture2D(dataTexture, uv);
v_texcoord = data.xy;
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
void main()
gl_FragColor = vec4(v_texcoord, 0, 1);
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const cubesAcross = 20;
const cubesDown = 10;
const numCubes = cubesAcross * cubesDown;
// matrix per instance
const matrixData = new Float32Array(16 * numCubes);
const matrices = [];
const instanceIds = new Float32Array(numCubes);
for (let i = 0; i < numCubes; ++i)
instanceIds[i] = i;
// make a typedarray view for each matrix
matrices.push(matrixData.subarray(i * 16, (i + 1) * 16));
const arrays =
position: [
1, 1, -1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, 1, 1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
1, 1, 1,
-1, 1, 1,
-1, -1, 1,
1, -1, 1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
],
vertexId:
numComponents: 1,
data: [
0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15,
16, 17, 18, 19,
20, 21, 21, 23,
],
,
indices: [
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23,
],
instanceId: numComponents: 1, data: instanceIds, divisor: 1 ,
matrix: numComponents: 16, data: matrices, divisor: 1 ,
;
// create buffers, upload data (gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
// put UV data in texture
const uvs = [];
for (let y = 0; y < cubesDown; ++y)
const v0 = (y ) / cubesDown;
const v1 = (y + 1) / cubesDown;
for (let x = 0; x < cubesAcross; ++x)
const u0 = (x ) / cubesAcross;
const u1 = (x + 1) / cubesAcross;
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
const texWidth = 24; // width = 24 vertices * 1 texel per
const texHeight = numCubes; // height = numInstances
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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.texImage2D(
gl.TEXTURE_2D,
0, // level
gl.RGBA,
texWidth,
texHeight,
0, // border
gl.RGBA,
gl.FLOAT,
new Float32Array(uvs));
function render(time) gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 24, 76];
const target = [18, 10, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
// update the instance for each matrix
const spacing = 2.5 + Math.sin(time) * .5;
let i = 0;
for (let y = 0; y < cubesDown; ++y)
for (let x = 0; x < cubesAcross; ++x)
const matrix = matrices[i++];
m4.translate(viewProjection, [
x * spacing,
y * spacing,
0,
], matrix);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer, ext.vertexAttribDivisorANGLE
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo,
dataTexture: tex,
dataTextureSize: [texWidth, texHeight],
);
// upload instance matrices to buffer
gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.matrix.buffer);
gl.bufferData(gl.ARRAY_BUFFER, matrixData, gl.DYNAMIC_DRAW);
ext.drawElementsInstancedANGLE(
gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0, numCubes);
requestAnimationFrame(render);
requestAnimationFrame(render);
main();
body
margin: 0;
canvas
width: 100vw;
height: 100vh;
display: block;
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
Thanks for the help and many thanks for the detailed sample! Figured it would have to end up something along these lines if I didn't just re-render things.
– sortofsleepy
Mar 24 at 22:51
add a comment |
It depends on what you mean by
is it possible to pass per-vertex data for each instance of the geometry that gets rendered?
If you mean via attributes then the answer is no. You can get data per vertex or data per instance but not data per vertex per instance.
But you also get 2 extra inputs, gl_VertexID
and gl_InstanceID
(in WebGL2) or you can add your own which you could use to either compute UVs or use to look up data in a texture to effectively achieve your desired result.
For example let's say you have 20x10 cubes. You could do something like
attribute vec4 position; // 36 cube positions
attribute vec2 uv; // 36 cube UVs
attribute float instanceId; // 1 per instance
uniform float cubesAcross; // set to 20
uniform float cubesDown; // set to 10
varying v_uv;
void main()
float cubeX = mod(instanceId, cubesAcross);
float cubeY = floor(instanceId / cubesAcross);
v_vu = (vec2(cubeX, cubeY) + uv) / vec2(cubesAcross, cubesDown);
gl_Position = ...whatever you were doing for position before...
example:
"use strict";
function main()
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext)
return alert('need ANGLE_instanced_arrays');
twgl.addExtensionsToContext(gl);
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
attribute mat4 matrix;
attribute float instanceId;
uniform float cubesAcross; // set to 20
uniform float cubesDown; // set to 10
varying vec2 v_texcoord;
void main()
gl_Position = matrix * position;
float cubeX = mod(instanceId, cubesAcross);
float cubeY = floor(instanceId / cubesAcross);
v_texcoord = (vec2(cubeX, cubeY) + texcoord) / vec2(cubesAcross, cubesDown);
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
void main()
gl_FragColor = vec4(v_texcoord, 0, 1);
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const cubesAcross = 20;
const cubesDown = 10;
const numCubes = cubesAcross * cubesDown;
// matrix per instance
const matrixData = new Float32Array(16 * numCubes);
const matrices = [];
const instanceIds = new Float32Array(numCubes);
for (let i = 0; i < numCubes; ++i)
instanceIds[i] = i;
// make a typedarray view for each matrix
matrices.push(matrixData.subarray(i * 16, (i + 1) * 16));
const arrays =
position: [
1, 1, -1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, 1, 1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
1, 1, 1,
-1, 1, 1,
-1, -1, 1,
1, -1, 1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
],
texcoord: [
1, 0,
0, 0,
0, 1,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1,
0, 1,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1,
1, 0,
0, 0,
1, 1,
0, 1,
0, 0,
1, 0,
1, 0,
0, 0,
0, 1,
1, 1,
],
indices: [
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23,
],
instanceId: numComponents: 1, data: instanceIds, divisor: 1 ,
matrix: numComponents: 16, data: matrices, divisor: 1 ,
;
// create buffers, upload data (gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
function render(time) gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 24, 76];
const target = [18, 10, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
// update the instance for each matrix
const spacing = 2.5 + Math.sin(time) * .5;
let i = 0;
for (let y = 0; y < cubesDown; ++y)
for (let x = 0; x < cubesAcross; ++x)
const matrix = matrices[i++];
m4.translate(viewProjection, [
x * spacing,
y * spacing,
0,
], matrix);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer, ext.vertexAttribDivisorANGLE
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo,
cubesAcross,
cubesDown,
);
// upload instance matrices to buffer
gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.matrix.buffer);
gl.bufferData(gl.ARRAY_BUFFER, matrixData, gl.DYNAMIC_DRAW);
ext.drawElementsInstancedANGLE(
gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0, numCubes);
requestAnimationFrame(render);
requestAnimationFrame(render);
main();
body
margin: 0;
canvas
width: 100vw;
height: 100vh;
display: block;
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
If you actually want unique per vertex per instance data then put it in a texture and pass in a vertexId as well. Then use both vertexId and instanceId to compute a texture coordinate
attribute float instanceId;
attribute float vertexId;
uniform sampler2D dataTexture;
uniform vec2 dataTextureSize;
varying vec2 v_texcoord;
void main() {
// each row is for an instance, each texel
// per vertex. Of course if you want more data
// per vertex then multiply vertexId by the number of
// vec4s of data you need. If you need more instances
// then compute a more complex offset based off instanceId
vec2 uv = (vec2(vertexId, instanceId) + .5) / dataTextureSize;
vec4 data = texture2D(dataTexture, uv);
v_texcoord = data.xy;
"use strict";
function main()
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext1 = gl.getExtension('OES_texture_float');
if (!ext1)
return alert('need OES_texture_float');
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext)
return alert('need ANGLE_instanced_arrays');
twgl.addExtensionsToContext(gl);
const vs = `
attribute vec4 position;
attribute mat4 matrix;
attribute float instanceId;
attribute float vertexId;
uniform sampler2D dataTexture;
uniform vec2 dataTextureSize;
varying vec2 v_texcoord;
void main()
gl_Position = matrix * position;
// each row is for an instance, each texel
// per vertex. Of course if you want more data
// per vertex then multiply vertexId by the number of
// vec4s of data you need. If you need more instances
// then compute a more complex offset based off instanceId
vec2 uv = (vec2(vertexId, instanceId) + .5) / dataTextureSize;
vec4 data = texture2D(dataTexture, uv);
v_texcoord = data.xy;
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
void main()
gl_FragColor = vec4(v_texcoord, 0, 1);
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const cubesAcross = 20;
const cubesDown = 10;
const numCubes = cubesAcross * cubesDown;
// matrix per instance
const matrixData = new Float32Array(16 * numCubes);
const matrices = [];
const instanceIds = new Float32Array(numCubes);
for (let i = 0; i < numCubes; ++i)
instanceIds[i] = i;
// make a typedarray view for each matrix
matrices.push(matrixData.subarray(i * 16, (i + 1) * 16));
const arrays =
position: [
1, 1, -1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, 1, 1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
1, 1, 1,
-1, 1, 1,
-1, -1, 1,
1, -1, 1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
],
vertexId:
numComponents: 1,
data: [
0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15,
16, 17, 18, 19,
20, 21, 21, 23,
],
,
indices: [
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23,
],
instanceId: numComponents: 1, data: instanceIds, divisor: 1 ,
matrix: numComponents: 16, data: matrices, divisor: 1 ,
;
// create buffers, upload data (gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
// put UV data in texture
const uvs = [];
for (let y = 0; y < cubesDown; ++y)
const v0 = (y ) / cubesDown;
const v1 = (y + 1) / cubesDown;
for (let x = 0; x < cubesAcross; ++x)
const u0 = (x ) / cubesAcross;
const u1 = (x + 1) / cubesAcross;
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
const texWidth = 24; // width = 24 vertices * 1 texel per
const texHeight = numCubes; // height = numInstances
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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.texImage2D(
gl.TEXTURE_2D,
0, // level
gl.RGBA,
texWidth,
texHeight,
0, // border
gl.RGBA,
gl.FLOAT,
new Float32Array(uvs));
function render(time) gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 24, 76];
const target = [18, 10, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
// update the instance for each matrix
const spacing = 2.5 + Math.sin(time) * .5;
let i = 0;
for (let y = 0; y < cubesDown; ++y)
for (let x = 0; x < cubesAcross; ++x)
const matrix = matrices[i++];
m4.translate(viewProjection, [
x * spacing,
y * spacing,
0,
], matrix);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer, ext.vertexAttribDivisorANGLE
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo,
dataTexture: tex,
dataTextureSize: [texWidth, texHeight],
);
// upload instance matrices to buffer
gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.matrix.buffer);
gl.bufferData(gl.ARRAY_BUFFER, matrixData, gl.DYNAMIC_DRAW);
ext.drawElementsInstancedANGLE(
gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0, numCubes);
requestAnimationFrame(render);
requestAnimationFrame(render);
main();
body
margin: 0;
canvas
width: 100vw;
height: 100vh;
display: block;
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
It depends on what you mean by
is it possible to pass per-vertex data for each instance of the geometry that gets rendered?
If you mean via attributes then the answer is no. You can get data per vertex or data per instance but not data per vertex per instance.
But you also get 2 extra inputs, gl_VertexID
and gl_InstanceID
(in WebGL2) or you can add your own which you could use to either compute UVs or use to look up data in a texture to effectively achieve your desired result.
For example let's say you have 20x10 cubes. You could do something like
attribute vec4 position; // 36 cube positions
attribute vec2 uv; // 36 cube UVs
attribute float instanceId; // 1 per instance
uniform float cubesAcross; // set to 20
uniform float cubesDown; // set to 10
varying v_uv;
void main()
float cubeX = mod(instanceId, cubesAcross);
float cubeY = floor(instanceId / cubesAcross);
v_vu = (vec2(cubeX, cubeY) + uv) / vec2(cubesAcross, cubesDown);
gl_Position = ...whatever you were doing for position before...
example:
"use strict";
function main()
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext)
return alert('need ANGLE_instanced_arrays');
twgl.addExtensionsToContext(gl);
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
attribute mat4 matrix;
attribute float instanceId;
uniform float cubesAcross; // set to 20
uniform float cubesDown; // set to 10
varying vec2 v_texcoord;
void main()
gl_Position = matrix * position;
float cubeX = mod(instanceId, cubesAcross);
float cubeY = floor(instanceId / cubesAcross);
v_texcoord = (vec2(cubeX, cubeY) + texcoord) / vec2(cubesAcross, cubesDown);
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
void main()
gl_FragColor = vec4(v_texcoord, 0, 1);
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const cubesAcross = 20;
const cubesDown = 10;
const numCubes = cubesAcross * cubesDown;
// matrix per instance
const matrixData = new Float32Array(16 * numCubes);
const matrices = [];
const instanceIds = new Float32Array(numCubes);
for (let i = 0; i < numCubes; ++i)
instanceIds[i] = i;
// make a typedarray view for each matrix
matrices.push(matrixData.subarray(i * 16, (i + 1) * 16));
const arrays =
position: [
1, 1, -1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, 1, 1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
1, 1, 1,
-1, 1, 1,
-1, -1, 1,
1, -1, 1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
],
texcoord: [
1, 0,
0, 0,
0, 1,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1,
0, 1,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1,
1, 0,
0, 0,
1, 1,
0, 1,
0, 0,
1, 0,
1, 0,
0, 0,
0, 1,
1, 1,
],
indices: [
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23,
],
instanceId: numComponents: 1, data: instanceIds, divisor: 1 ,
matrix: numComponents: 16, data: matrices, divisor: 1 ,
;
// create buffers, upload data (gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
function render(time) gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 24, 76];
const target = [18, 10, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
// update the instance for each matrix
const spacing = 2.5 + Math.sin(time) * .5;
let i = 0;
for (let y = 0; y < cubesDown; ++y)
for (let x = 0; x < cubesAcross; ++x)
const matrix = matrices[i++];
m4.translate(viewProjection, [
x * spacing,
y * spacing,
0,
], matrix);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer, ext.vertexAttribDivisorANGLE
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo,
cubesAcross,
cubesDown,
);
// upload instance matrices to buffer
gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.matrix.buffer);
gl.bufferData(gl.ARRAY_BUFFER, matrixData, gl.DYNAMIC_DRAW);
ext.drawElementsInstancedANGLE(
gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0, numCubes);
requestAnimationFrame(render);
requestAnimationFrame(render);
main();
body
margin: 0;
canvas
width: 100vw;
height: 100vh;
display: block;
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
If you actually want unique per vertex per instance data then put it in a texture and pass in a vertexId as well. Then use both vertexId and instanceId to compute a texture coordinate
attribute float instanceId;
attribute float vertexId;
uniform sampler2D dataTexture;
uniform vec2 dataTextureSize;
varying vec2 v_texcoord;
void main() {
// each row is for an instance, each texel
// per vertex. Of course if you want more data
// per vertex then multiply vertexId by the number of
// vec4s of data you need. If you need more instances
// then compute a more complex offset based off instanceId
vec2 uv = (vec2(vertexId, instanceId) + .5) / dataTextureSize;
vec4 data = texture2D(dataTexture, uv);
v_texcoord = data.xy;
"use strict";
function main()
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext1 = gl.getExtension('OES_texture_float');
if (!ext1)
return alert('need OES_texture_float');
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext)
return alert('need ANGLE_instanced_arrays');
twgl.addExtensionsToContext(gl);
const vs = `
attribute vec4 position;
attribute mat4 matrix;
attribute float instanceId;
attribute float vertexId;
uniform sampler2D dataTexture;
uniform vec2 dataTextureSize;
varying vec2 v_texcoord;
void main()
gl_Position = matrix * position;
// each row is for an instance, each texel
// per vertex. Of course if you want more data
// per vertex then multiply vertexId by the number of
// vec4s of data you need. If you need more instances
// then compute a more complex offset based off instanceId
vec2 uv = (vec2(vertexId, instanceId) + .5) / dataTextureSize;
vec4 data = texture2D(dataTexture, uv);
v_texcoord = data.xy;
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
void main()
gl_FragColor = vec4(v_texcoord, 0, 1);
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const cubesAcross = 20;
const cubesDown = 10;
const numCubes = cubesAcross * cubesDown;
// matrix per instance
const matrixData = new Float32Array(16 * numCubes);
const matrices = [];
const instanceIds = new Float32Array(numCubes);
for (let i = 0; i < numCubes; ++i)
instanceIds[i] = i;
// make a typedarray view for each matrix
matrices.push(matrixData.subarray(i * 16, (i + 1) * 16));
const arrays =
position: [
1, 1, -1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, 1, 1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
1, 1, 1,
-1, 1, 1,
-1, -1, 1,
1, -1, 1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
],
vertexId:
numComponents: 1,
data: [
0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15,
16, 17, 18, 19,
20, 21, 21, 23,
],
,
indices: [
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23,
],
instanceId: numComponents: 1, data: instanceIds, divisor: 1 ,
matrix: numComponents: 16, data: matrices, divisor: 1 ,
;
// create buffers, upload data (gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
// put UV data in texture
const uvs = [];
for (let y = 0; y < cubesDown; ++y)
const v0 = (y ) / cubesDown;
const v1 = (y + 1) / cubesDown;
for (let x = 0; x < cubesAcross; ++x)
const u0 = (x ) / cubesAcross;
const u1 = (x + 1) / cubesAcross;
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
const texWidth = 24; // width = 24 vertices * 1 texel per
const texHeight = numCubes; // height = numInstances
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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.texImage2D(
gl.TEXTURE_2D,
0, // level
gl.RGBA,
texWidth,
texHeight,
0, // border
gl.RGBA,
gl.FLOAT,
new Float32Array(uvs));
function render(time) gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 24, 76];
const target = [18, 10, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
// update the instance for each matrix
const spacing = 2.5 + Math.sin(time) * .5;
let i = 0;
for (let y = 0; y < cubesDown; ++y)
for (let x = 0; x < cubesAcross; ++x)
const matrix = matrices[i++];
m4.translate(viewProjection, [
x * spacing,
y * spacing,
0,
], matrix);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer, ext.vertexAttribDivisorANGLE
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo,
dataTexture: tex,
dataTextureSize: [texWidth, texHeight],
);
// upload instance matrices to buffer
gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.matrix.buffer);
gl.bufferData(gl.ARRAY_BUFFER, matrixData, gl.DYNAMIC_DRAW);
ext.drawElementsInstancedANGLE(
gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0, numCubes);
requestAnimationFrame(render);
requestAnimationFrame(render);
main();
body
margin: 0;
canvas
width: 100vw;
height: 100vh;
display: block;
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
"use strict";
function main()
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext)
return alert('need ANGLE_instanced_arrays');
twgl.addExtensionsToContext(gl);
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
attribute mat4 matrix;
attribute float instanceId;
uniform float cubesAcross; // set to 20
uniform float cubesDown; // set to 10
varying vec2 v_texcoord;
void main()
gl_Position = matrix * position;
float cubeX = mod(instanceId, cubesAcross);
float cubeY = floor(instanceId / cubesAcross);
v_texcoord = (vec2(cubeX, cubeY) + texcoord) / vec2(cubesAcross, cubesDown);
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
void main()
gl_FragColor = vec4(v_texcoord, 0, 1);
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const cubesAcross = 20;
const cubesDown = 10;
const numCubes = cubesAcross * cubesDown;
// matrix per instance
const matrixData = new Float32Array(16 * numCubes);
const matrices = [];
const instanceIds = new Float32Array(numCubes);
for (let i = 0; i < numCubes; ++i)
instanceIds[i] = i;
// make a typedarray view for each matrix
matrices.push(matrixData.subarray(i * 16, (i + 1) * 16));
const arrays =
position: [
1, 1, -1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, 1, 1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
1, 1, 1,
-1, 1, 1,
-1, -1, 1,
1, -1, 1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
],
texcoord: [
1, 0,
0, 0,
0, 1,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1,
0, 1,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1,
1, 0,
0, 0,
1, 1,
0, 1,
0, 0,
1, 0,
1, 0,
0, 0,
0, 1,
1, 1,
],
indices: [
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23,
],
instanceId: numComponents: 1, data: instanceIds, divisor: 1 ,
matrix: numComponents: 16, data: matrices, divisor: 1 ,
;
// create buffers, upload data (gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
function render(time) gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 24, 76];
const target = [18, 10, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
// update the instance for each matrix
const spacing = 2.5 + Math.sin(time) * .5;
let i = 0;
for (let y = 0; y < cubesDown; ++y)
for (let x = 0; x < cubesAcross; ++x)
const matrix = matrices[i++];
m4.translate(viewProjection, [
x * spacing,
y * spacing,
0,
], matrix);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer, ext.vertexAttribDivisorANGLE
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo,
cubesAcross,
cubesDown,
);
// upload instance matrices to buffer
gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.matrix.buffer);
gl.bufferData(gl.ARRAY_BUFFER, matrixData, gl.DYNAMIC_DRAW);
ext.drawElementsInstancedANGLE(
gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0, numCubes);
requestAnimationFrame(render);
requestAnimationFrame(render);
main();
body
margin: 0;
canvas
width: 100vw;
height: 100vh;
display: block;
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
"use strict";
function main()
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext)
return alert('need ANGLE_instanced_arrays');
twgl.addExtensionsToContext(gl);
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
attribute mat4 matrix;
attribute float instanceId;
uniform float cubesAcross; // set to 20
uniform float cubesDown; // set to 10
varying vec2 v_texcoord;
void main()
gl_Position = matrix * position;
float cubeX = mod(instanceId, cubesAcross);
float cubeY = floor(instanceId / cubesAcross);
v_texcoord = (vec2(cubeX, cubeY) + texcoord) / vec2(cubesAcross, cubesDown);
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
void main()
gl_FragColor = vec4(v_texcoord, 0, 1);
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const cubesAcross = 20;
const cubesDown = 10;
const numCubes = cubesAcross * cubesDown;
// matrix per instance
const matrixData = new Float32Array(16 * numCubes);
const matrices = [];
const instanceIds = new Float32Array(numCubes);
for (let i = 0; i < numCubes; ++i)
instanceIds[i] = i;
// make a typedarray view for each matrix
matrices.push(matrixData.subarray(i * 16, (i + 1) * 16));
const arrays =
position: [
1, 1, -1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, 1, 1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
1, 1, 1,
-1, 1, 1,
-1, -1, 1,
1, -1, 1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
],
texcoord: [
1, 0,
0, 0,
0, 1,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1,
0, 1,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1,
1, 0,
0, 0,
1, 1,
0, 1,
0, 0,
1, 0,
1, 0,
0, 0,
0, 1,
1, 1,
],
indices: [
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23,
],
instanceId: numComponents: 1, data: instanceIds, divisor: 1 ,
matrix: numComponents: 16, data: matrices, divisor: 1 ,
;
// create buffers, upload data (gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
function render(time) gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 24, 76];
const target = [18, 10, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
// update the instance for each matrix
const spacing = 2.5 + Math.sin(time) * .5;
let i = 0;
for (let y = 0; y < cubesDown; ++y)
for (let x = 0; x < cubesAcross; ++x)
const matrix = matrices[i++];
m4.translate(viewProjection, [
x * spacing,
y * spacing,
0,
], matrix);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer, ext.vertexAttribDivisorANGLE
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo,
cubesAcross,
cubesDown,
);
// upload instance matrices to buffer
gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.matrix.buffer);
gl.bufferData(gl.ARRAY_BUFFER, matrixData, gl.DYNAMIC_DRAW);
ext.drawElementsInstancedANGLE(
gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0, numCubes);
requestAnimationFrame(render);
requestAnimationFrame(render);
main();
body
margin: 0;
canvas
width: 100vw;
height: 100vh;
display: block;
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
"use strict";
function main()
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext1 = gl.getExtension('OES_texture_float');
if (!ext1)
return alert('need OES_texture_float');
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext)
return alert('need ANGLE_instanced_arrays');
twgl.addExtensionsToContext(gl);
const vs = `
attribute vec4 position;
attribute mat4 matrix;
attribute float instanceId;
attribute float vertexId;
uniform sampler2D dataTexture;
uniform vec2 dataTextureSize;
varying vec2 v_texcoord;
void main()
gl_Position = matrix * position;
// each row is for an instance, each texel
// per vertex. Of course if you want more data
// per vertex then multiply vertexId by the number of
// vec4s of data you need. If you need more instances
// then compute a more complex offset based off instanceId
vec2 uv = (vec2(vertexId, instanceId) + .5) / dataTextureSize;
vec4 data = texture2D(dataTexture, uv);
v_texcoord = data.xy;
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
void main()
gl_FragColor = vec4(v_texcoord, 0, 1);
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const cubesAcross = 20;
const cubesDown = 10;
const numCubes = cubesAcross * cubesDown;
// matrix per instance
const matrixData = new Float32Array(16 * numCubes);
const matrices = [];
const instanceIds = new Float32Array(numCubes);
for (let i = 0; i < numCubes; ++i)
instanceIds[i] = i;
// make a typedarray view for each matrix
matrices.push(matrixData.subarray(i * 16, (i + 1) * 16));
const arrays =
position: [
1, 1, -1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, 1, 1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
1, 1, 1,
-1, 1, 1,
-1, -1, 1,
1, -1, 1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
],
vertexId:
numComponents: 1,
data: [
0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15,
16, 17, 18, 19,
20, 21, 21, 23,
],
,
indices: [
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23,
],
instanceId: numComponents: 1, data: instanceIds, divisor: 1 ,
matrix: numComponents: 16, data: matrices, divisor: 1 ,
;
// create buffers, upload data (gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
// put UV data in texture
const uvs = [];
for (let y = 0; y < cubesDown; ++y)
const v0 = (y ) / cubesDown;
const v1 = (y + 1) / cubesDown;
for (let x = 0; x < cubesAcross; ++x)
const u0 = (x ) / cubesAcross;
const u1 = (x + 1) / cubesAcross;
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
const texWidth = 24; // width = 24 vertices * 1 texel per
const texHeight = numCubes; // height = numInstances
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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.texImage2D(
gl.TEXTURE_2D,
0, // level
gl.RGBA,
texWidth,
texHeight,
0, // border
gl.RGBA,
gl.FLOAT,
new Float32Array(uvs));
function render(time) gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 24, 76];
const target = [18, 10, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
// update the instance for each matrix
const spacing = 2.5 + Math.sin(time) * .5;
let i = 0;
for (let y = 0; y < cubesDown; ++y)
for (let x = 0; x < cubesAcross; ++x)
const matrix = matrices[i++];
m4.translate(viewProjection, [
x * spacing,
y * spacing,
0,
], matrix);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer, ext.vertexAttribDivisorANGLE
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo,
dataTexture: tex,
dataTextureSize: [texWidth, texHeight],
);
// upload instance matrices to buffer
gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.matrix.buffer);
gl.bufferData(gl.ARRAY_BUFFER, matrixData, gl.DYNAMIC_DRAW);
ext.drawElementsInstancedANGLE(
gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0, numCubes);
requestAnimationFrame(render);
requestAnimationFrame(render);
main();
body
margin: 0;
canvas
width: 100vw;
height: 100vh;
display: block;
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
"use strict";
function main()
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext1 = gl.getExtension('OES_texture_float');
if (!ext1)
return alert('need OES_texture_float');
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext)
return alert('need ANGLE_instanced_arrays');
twgl.addExtensionsToContext(gl);
const vs = `
attribute vec4 position;
attribute mat4 matrix;
attribute float instanceId;
attribute float vertexId;
uniform sampler2D dataTexture;
uniform vec2 dataTextureSize;
varying vec2 v_texcoord;
void main()
gl_Position = matrix * position;
// each row is for an instance, each texel
// per vertex. Of course if you want more data
// per vertex then multiply vertexId by the number of
// vec4s of data you need. If you need more instances
// then compute a more complex offset based off instanceId
vec2 uv = (vec2(vertexId, instanceId) + .5) / dataTextureSize;
vec4 data = texture2D(dataTexture, uv);
v_texcoord = data.xy;
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
void main()
gl_FragColor = vec4(v_texcoord, 0, 1);
`;
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const cubesAcross = 20;
const cubesDown = 10;
const numCubes = cubesAcross * cubesDown;
// matrix per instance
const matrixData = new Float32Array(16 * numCubes);
const matrices = [];
const instanceIds = new Float32Array(numCubes);
for (let i = 0; i < numCubes; ++i)
instanceIds[i] = i;
// make a typedarray view for each matrix
matrices.push(matrixData.subarray(i * 16, (i + 1) * 16));
const arrays =
position: [
1, 1, -1,
1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, 1, 1,
-1, 1, -1,
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
1, 1, 1,
-1, 1, 1,
-1, -1, 1,
1, -1, 1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
-1, -1, -1,
],
vertexId:
numComponents: 1,
data: [
0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15,
16, 17, 18, 19,
20, 21, 21, 23,
],
,
indices: [
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11,
12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19,
20, 21, 22, 20, 22, 23,
],
instanceId: numComponents: 1, data: instanceIds, divisor: 1 ,
matrix: numComponents: 16, data: matrices, divisor: 1 ,
;
// create buffers, upload data (gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
// put UV data in texture
const uvs = [];
for (let y = 0; y < cubesDown; ++y)
const v0 = (y ) / cubesDown;
const v1 = (y + 1) / cubesDown;
for (let x = 0; x < cubesAcross; ++x)
const u0 = (x ) / cubesAcross;
const u1 = (x + 1) / cubesAcross;
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
uvs.push(u0, v0, 0, 0, u1, v0, 0, 0, u0, v1, 0, 0, u1, v1, 0, 0);
const texWidth = 24; // width = 24 vertices * 1 texel per
const texHeight = numCubes; // height = numInstances
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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.texImage2D(
gl.TEXTURE_2D,
0, // level
gl.RGBA,
texWidth,
texHeight,
0, // border
gl.RGBA,
gl.FLOAT,
new Float32Array(uvs));
function render(time) gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 24, 76];
const target = [18, 10, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
// update the instance for each matrix
const spacing = 2.5 + Math.sin(time) * .5;
let i = 0;
for (let y = 0; y < cubesDown; ++y)
for (let x = 0; x < cubesAcross; ++x)
const matrix = matrices[i++];
m4.translate(viewProjection, [
x * spacing,
y * spacing,
0,
], matrix);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer, ext.vertexAttribDivisorANGLE
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo,
dataTexture: tex,
dataTextureSize: [texWidth, texHeight],
);
// upload instance matrices to buffer
gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.matrix.buffer);
gl.bufferData(gl.ARRAY_BUFFER, matrixData, gl.DYNAMIC_DRAW);
ext.drawElementsInstancedANGLE(
gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0, numCubes);
requestAnimationFrame(render);
requestAnimationFrame(render);
main();
body
margin: 0;
canvas
width: 100vw;
height: 100vh;
display: block;
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
edited Mar 24 at 16:31
answered Mar 24 at 5:43
gmangman
50.8k17119214
50.8k17119214
Thanks for the help and many thanks for the detailed sample! Figured it would have to end up something along these lines if I didn't just re-render things.
– sortofsleepy
Mar 24 at 22:51
add a comment |
Thanks for the help and many thanks for the detailed sample! Figured it would have to end up something along these lines if I didn't just re-render things.
– sortofsleepy
Mar 24 at 22:51
Thanks for the help and many thanks for the detailed sample! Figured it would have to end up something along these lines if I didn't just re-render things.
– sortofsleepy
Mar 24 at 22:51
Thanks for the help and many thanks for the detailed sample! Figured it would have to end up something along these lines if I didn't just re-render things.
– sortofsleepy
Mar 24 at 22:51
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55319323%2fin-webgl-when-instancing-geometry-is-it-possible-to-pass-per-vertex-attribute%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
"The geometry I'm rendering has 4 vertices." OK, let me stop you right there. Don't do instancing just to render quads. Just repeat the position data; you'll almost certainly be better off, performance-wise.
– Nicol Bolas
Mar 24 at 6:04
Agree with what Nicol said. It's faster to repeat the data than to use instancing so the less vertices per instance the better it is to just repeat the data.
– gman
Mar 24 at 15:34
ah got it - thanks for the tip Nicol.
– sortofsleepy
Mar 24 at 22:53