在 WebGL 中 - 实例化几何体时,是否可以为每个实例传递每个顶点的属性信息?
In WebGL - when instancing geometry, is it possible to pass per-vertex attribute information for each instance?
我正在尝试通过原始 WebGL the following three.js example 使用实例化几何重新创建;不过,在测试之后,看起来我可能只需要像示例中那样绘制多个网格,但我想我会先在这里要求仔细检查。
现在的问题本质上是 - 是否可以为渲染的每个几何体实例传递逐顶点数据?
希望这是有道理的,但如果没有 -
- 我正在渲染的几何体有 4 个顶点。
- 对于几何体的每个实例 - 我需要为每个几何体实例的每个顶点分配 uv 坐标
- 似乎设置步幅或偏移量似乎不起作用。
基本上现在发生的事情是给定看起来像
的当前数据
[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
而不是像这样在块中读取和分配数据
// 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
数据似乎是这样读取的
[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 possible to pass per-vertex data for each instance of the geometry that gets rendered?
如果您的意思是通过属性,那么答案是否定的。您可以获取每个顶点的数据或每个实例的数据,但不能获取每个实例的每个顶点的数据。
但是您还可以获得 2 个额外的输入,gl_VertexID
和 gl_InstanceID
(在 WebGL2 中),或者您可以添加自己的输入,用于计算 UV 或用于查找数据纹理有效地达到您想要的结果。
例如,假设您有 20x10 个立方体。你可以做类似
的事情
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...
}
示例:
"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) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clear(gl.COLOR_BUFFER_BIT | 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>
如果你真的想要每个实例数据的每个顶点都是唯一的,那么把它放在一个纹理中并传入一个 vertexId。然后同时使用 vertexId 和 instanceId 来计算纹理坐标
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) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clear(gl.COLOR_BUFFER_BIT | 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>
另见 this answer
我正在尝试通过原始 WebGL the following three.js example 使用实例化几何重新创建;不过,在测试之后,看起来我可能只需要像示例中那样绘制多个网格,但我想我会先在这里要求仔细检查。
现在的问题本质上是 - 是否可以为渲染的每个几何体实例传递逐顶点数据?
希望这是有道理的,但如果没有 -
- 我正在渲染的几何体有 4 个顶点。
- 对于几何体的每个实例 - 我需要为每个几何体实例的每个顶点分配 uv 坐标
- 似乎设置步幅或偏移量似乎不起作用。
基本上现在发生的事情是给定看起来像
的当前数据[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
而不是像这样在块中读取和分配数据
// 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
数据似乎是这样读取的
[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 possible to pass per-vertex data for each instance of the geometry that gets rendered?
如果您的意思是通过属性,那么答案是否定的。您可以获取每个顶点的数据或每个实例的数据,但不能获取每个实例的每个顶点的数据。
但是您还可以获得 2 个额外的输入,gl_VertexID
和 gl_InstanceID
(在 WebGL2 中),或者您可以添加自己的输入,用于计算 UV 或用于查找数据纹理有效地达到您想要的结果。
例如,假设您有 20x10 个立方体。你可以做类似
的事情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...
}
示例:
"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) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clear(gl.COLOR_BUFFER_BIT | 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>
如果你真的想要每个实例数据的每个顶点都是唯一的,那么把它放在一个纹理中并传入一个 vertexId。然后同时使用 vertexId 和 instanceId 来计算纹理坐标
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) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clear(gl.COLOR_BUFFER_BIT | 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>
另见 this answer