我可以在 drawArraysInstancedANGLE 中使用多个纹理吗?
can I use multiple texture with drawArraysInstancedANGLE?
我尝试在屏幕上绘制多个图标并使用 drawArraysInstancedANGLE 方法。
我尝试像这样使用多个纹理,但有些图标绘制不同的几何图形,我找不到那样绘制的东西。
我使用一个大图标贴图纹理并用这个函数填充图标顶点坐标数组:
FillIconTextCoordBuffer(data, mapW, mapH, i) {
const ULiconW = data.x / mapW
const ULiconH = data.y / mapH
const LRiconW = (data.x + data.width) / mapW
const LRiconH = (data.y + data.height) / mapH
const { gl } = this.FGlobe
this.IconMapTexCoordArr[i] = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, this.IconMapTexCoordArr[i])
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
ULiconW, ULiconH,
LRiconW, LRiconH,
LRiconW, ULiconH,
LRiconW, LRiconH,
ULiconW, ULiconH,
ULiconW, LRiconH]), gl.STATIC_DRAW)
}
然后我的绘图函数是这样的:
gl.uniform1f(P2DRotationForLayer, icon.rotDeg)
gl.uniform2fv(P2DScaleLocForLayer, icon.__size)
gl.uniform4fv(P2DOpacityLocForLayer, __iconColor)
ext.vertexAttribDivisorANGLE(P2DoffsetForLayer, 1) // This makes it instanced!
gl.bindBuffer(gl.ARRAY_BUFFER, this.IconMapVertCoordArr)
gl.enableVertexAttribArray(P2DvertexPosForLayer)
gl.vertexAttribPointer(P2DvertexPosForLayer, 3, gl.FLOAT, false, 0, 0)
gl.bindBuffer(gl.ARRAY_BUFFER, this.IconCoordBuff)
gl.enableVertexAttribArray(P2DoffsetForLayer)
gl.vertexAttribPointer(P2DoffsetForLayer, 2, gl.FLOAT, false, 0, 0)
gl.bindTexture(gl.TEXTURE_2D, IconMap[icon.mapIndex].texture)
gl.disable(gl.BLEND)
for (var j = this.StartCountArr.length; j--;) {
this.DrawInstances(this.StartCountArr[j].start, this.StartCountArr[j].count, j)
}
ext.vertexAttribDivisorANGLE(P2DoffsetForLayer, 0)
我的 DrawInstances 函数是这样的:
DrawInstances(start, count, j) {
const {
gl, ext,
P2DtextCoordLocForLayer,
} = this.FGlobe
gl.bindBuffer(gl.ARRAY_BUFFER, this.IconMapTexCoordArr[j])
gl.enableVertexAttribArray(P2DtextCoordLocForLayer)
gl.vertexAttribPointer(P2DtextCoordLocForLayer, 2, gl.FLOAT, false, 0, 0)
gl.bindBuffer(gl.ARRAY_BUFFER, null)
ext.drawArraysInstancedANGLE(gl.TRIANGLES, start, 6, count)
}
实际上有些图标画对了我看到了 2 个不同的图标,但还有一种看起来像这样:
|\
| \
| \
| /
| /
|/
我的图标只有下面两个三角形,我没有像上面那样设置任何形状,
______
|\ |
| \ |
| \ |
| \|
------
这是一个使用实例化绘图从精灵 sheet 绘制多个精灵的示例。
请注意,如果是我,我会为每个实例使用一个矩阵 like this example 但我认为在此处使用偏移和缩放代码会更简单。
const gl = document.querySelector('canvas').getContext('webgl');
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext) alert('need ANGLE_instanced_arrays');
const vs = `
attribute vec2 position;
attribute vec2 uv;
attribute vec2 offset; // instanced
attribute vec2 scale; // instanced
attribute vec2 uvOffset; // instanced
attribute vec2 uvScale; // instanced
uniform mat4 matrix;
varying vec2 v_uv;
void main() {
gl_Position = matrix * vec4(position * scale + offset, 0, 1);
v_uv = uv * uvScale + uvOffset;
}
`;
const fs = `
precision highp float;
varying vec2 v_uv;
uniform sampler2D spriteAtlas;
void main() {
gl_FragColor = texture2D(spriteAtlas, v_uv);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const uvLoc = gl.getAttribLocation(program, 'uv');
const offsetLoc = gl.getAttribLocation(program, 'offset');
const scaleLoc = gl.getAttribLocation(program, 'scale');
const uvOffsetLoc = gl.getAttribLocation(program, 'uvOffset');
const uvScaleLoc = gl.getAttribLocation(program, 'uvScale');
const matrixLoc = gl.getUniformLocation(program, 'matrix');
// setup quad positions and uv
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 0,
1, 0,
0, 1,
0, 1,
1, 0,
1, 1,
]), gl.STATIC_DRAW);
const uvBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 0,
1, 0,
0, 1,
0, 1,
1, 0,
1, 1,
]), gl.STATIC_DRAW);
// create typed array for instanced data
const maxSprites = 1000;
const offsets = new Float32Array(maxSprites * 2);
const scales = new Float32Array(maxSprites * 2);
const uvOffsets = new Float32Array(maxSprites * 2);
const uvScales = new Float32Array(maxSprites * 2);
// create buffers fo instanced data
const offsetBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
gl.bufferData(gl.ARRAY_BUFFER, offsets.byteLength, gl.DYNAMIC_DRAW);
const scaleBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, scaleBuffer);
gl.bufferData(gl.ARRAY_BUFFER, scales.byteLength, gl.DYNAMIC_DRAW);
const uvOffsetBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, uvOffsetBuffer);
gl.bufferData(gl.ARRAY_BUFFER, uvOffsets.byteLength, gl.DYNAMIC_DRAW);
const uvScaleBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, uvScaleBuffer);
gl.bufferData(gl.ARRAY_BUFFER, uvScales.byteLength, gl.DYNAMIC_DRAW);
let spriteNdx = 0;
function addSprite(
spriteAtlasWidth, spriteAtlasHeight,
srcX, srcY, srcWidth, srcHeight,
dstX, dstY, dstWidth, dstHeight) {
const off0 = spriteNdx * 2;
const off1 = off0 + 1;
offsets[off0] = dstX;
offsets[off1] = dstY;
scales[off0] = dstWidth;
scales[off1] = dstHeight;
uvOffsets[off0] = srcX / spriteAtlasWidth;
uvOffsets[off1] = srcY / spriteAtlasHeight;
uvScales[off0] = srcWidth / spriteAtlasWidth;
uvScales[off1] = srcHeight / spriteAtlasHeight;
++spriteNdx;
}
const sprites = [
{msg: 'A', x: 0, y: 0, w: 64, h: 32, bg: 'red', fg: 'yellow'},
{msg: 'B', x: 64, y: 0, w: 64, h: 32, bg: 'blue', fg: 'white' },
{msg: 'C', x: 0, y: 32, w: 40, h: 32, bg: 'green', fg: 'pink' },
{msg: 'D', x: 40, y: 32, w: 48, h: 32, bg: 'purple', fg: 'cyan' },
{msg: 'F', x: 88, y: 32, w: 40, h: 32, bg: 'black', fg: 'red' },
];
// make 5 sprites in an atlas
const spriteAtlasWidth = 128;
const spriteAtlasHeight = 64;
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = spriteAtlasWidth;
ctx.canvas.height = spriteAtlasHeight;
for (const spr of sprites) {
ctx.fillStyle = spr.bg;
ctx.fillRect(spr.x, spr.y, spr.w, spr.h);
ctx.strokeStyle = spr.fg;
ctx.strokeRect(spr.x + .5, spr.y + .5, spr.w - 1, spr.h - 1);
ctx.fillStyle = spr.fg;
ctx.font = 'bold 26px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(spr.msg, spr.x + spr.w / 2, spr.y + spr.h / 2);
}
// show the atlas
document.body.appendChild(ctx.canvas);
// copy the atlas to a texture
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, ctx.canvas);
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);
function render(time) {
time *= 0.001; // convert to seconds
spriteNdx = 0;
const numSprites = 10;
for (let i = 0; i < numSprites; ++i) {
const sp = sprites[i % sprites.length];
addSprite(
spriteAtlasWidth, spriteAtlasHeight,
sp.x, sp.y, sp.w, sp.h,
Math.sin(time + i * 15) * gl.canvas.width / 2 + gl.canvas.width / 2,
Math.cos(time + i * 17) * gl.canvas.height / 2 + gl.canvas.height / 2,
sp.w, sp.h,
);
}
// copy the latest sprite instance data
// to their respective buffers and setup
// the attributes.
// NOTE: for the attributes it would be better
// to use a vertex array
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer);
gl.enableVertexAttribArray(uvLoc);
gl.vertexAttribPointer(uvLoc, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, offsets);
gl.enableVertexAttribArray(offsetLoc);
gl.vertexAttribPointer(offsetLoc, 2, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(offsetLoc, 1);
gl.bindBuffer(gl.ARRAY_BUFFER, scaleBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, scales);
gl.enableVertexAttribArray(scaleLoc);
gl.vertexAttribPointer(scaleLoc, 2, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(scaleLoc, 1);
gl.bindBuffer(gl.ARRAY_BUFFER, uvOffsetBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, uvOffsets);
gl.enableVertexAttribArray(uvOffsetLoc);
gl.vertexAttribPointer(uvOffsetLoc, 2, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(uvOffsetLoc, 1);
gl.bindBuffer(gl.ARRAY_BUFFER, uvScaleBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, uvScales);
gl.enableVertexAttribArray(uvScaleLoc);
gl.vertexAttribPointer(uvScaleLoc, 2, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(uvScaleLoc, 1);
gl.useProgram(program);
// pass in a projection matrix that
// converts to pixel space so the top
// left corner is 0,0 and the bottom right corner
// is canvas.width, canvas.height
//
// if you had a 3d math library this would be something like
// m4.ortho(0, gl.canvas.width, gl.canvas.height, 0, -1, 1);
gl.uniformMatrix4fv(matrixLoc, false, [
2 / gl.canvas.width, 0, 0, 0,
0, -2 / gl.canvas.height, 0, 0,
0, 0, 1, 0,
-1, 1, 0, 1,
]);
// note as there as only 1 texture and
// uniforms default to 0 we don't need to
// bind the texture to setup a uniform
// as the defaults happen to work.
ext.drawArraysInstancedANGLE(
gl.TRIANGLES,
0,
6, // verts per instance
spriteNdx, // num instances
);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; margin: 5px; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
请注意,我没有跳过实例,但如果您想跳过实例,则需要为每个实例属性设置传递给 gl.vertexAttribPointer
的偏移量。例如在上面的代码中,如果你想绘制实例 7 到 29,它将是
const numInstancesToSkip = 7;
const numInstancesToDraw = 29 - 7 + 1;
const size = 2; // vec2
const sizeOfFloat = 4;
const offset = numInstancesToSkip * sizeOfFloat * size;
gl.bindBuffer(offsetBuffer);
gl.vertexAttribPointer(offsetLoc, size, gl.FLOAT, false, 0, offset);
gl.bindBuffer(scaleBuffer);
gl.vertexAttribPointer(scaleLoc, size, gl.FLOAT, false, 0, offset);
gl.bindBuffer(uvOffsetBuffer);
gl.vertexAttribPointer(uvOffsetLoc, size, gl.FLOAT, false, 0, offset);
gl.bindBuffer(uvScaleBuffer);
gl.vertexAttribPointer(uvScaleLoc, size, gl.FLOAT, false, 0, offset);
并且绘制将是
ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, mumInstancesToDraw);
请注意,上面的 offset
对于每个属性都是相同的,因为所有属性都具有相同的大小 (2) 和相同的类型 (gl.FLOAT),并且它们都位于单独的缓冲区中,因此它们基本偏移量均为 0。如果它们的大小或类型不同,或者混合到同一个缓冲区中,它们都需要不同的数学运算。
我尝试在屏幕上绘制多个图标并使用 drawArraysInstancedANGLE 方法。 我尝试像这样使用多个纹理,但有些图标绘制不同的几何图形,我找不到那样绘制的东西。
我使用一个大图标贴图纹理并用这个函数填充图标顶点坐标数组:
FillIconTextCoordBuffer(data, mapW, mapH, i) {
const ULiconW = data.x / mapW
const ULiconH = data.y / mapH
const LRiconW = (data.x + data.width) / mapW
const LRiconH = (data.y + data.height) / mapH
const { gl } = this.FGlobe
this.IconMapTexCoordArr[i] = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, this.IconMapTexCoordArr[i])
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
ULiconW, ULiconH,
LRiconW, LRiconH,
LRiconW, ULiconH,
LRiconW, LRiconH,
ULiconW, ULiconH,
ULiconW, LRiconH]), gl.STATIC_DRAW)
}
然后我的绘图函数是这样的:
gl.uniform1f(P2DRotationForLayer, icon.rotDeg)
gl.uniform2fv(P2DScaleLocForLayer, icon.__size)
gl.uniform4fv(P2DOpacityLocForLayer, __iconColor)
ext.vertexAttribDivisorANGLE(P2DoffsetForLayer, 1) // This makes it instanced!
gl.bindBuffer(gl.ARRAY_BUFFER, this.IconMapVertCoordArr)
gl.enableVertexAttribArray(P2DvertexPosForLayer)
gl.vertexAttribPointer(P2DvertexPosForLayer, 3, gl.FLOAT, false, 0, 0)
gl.bindBuffer(gl.ARRAY_BUFFER, this.IconCoordBuff)
gl.enableVertexAttribArray(P2DoffsetForLayer)
gl.vertexAttribPointer(P2DoffsetForLayer, 2, gl.FLOAT, false, 0, 0)
gl.bindTexture(gl.TEXTURE_2D, IconMap[icon.mapIndex].texture)
gl.disable(gl.BLEND)
for (var j = this.StartCountArr.length; j--;) {
this.DrawInstances(this.StartCountArr[j].start, this.StartCountArr[j].count, j)
}
ext.vertexAttribDivisorANGLE(P2DoffsetForLayer, 0)
我的 DrawInstances 函数是这样的:
DrawInstances(start, count, j) {
const {
gl, ext,
P2DtextCoordLocForLayer,
} = this.FGlobe
gl.bindBuffer(gl.ARRAY_BUFFER, this.IconMapTexCoordArr[j])
gl.enableVertexAttribArray(P2DtextCoordLocForLayer)
gl.vertexAttribPointer(P2DtextCoordLocForLayer, 2, gl.FLOAT, false, 0, 0)
gl.bindBuffer(gl.ARRAY_BUFFER, null)
ext.drawArraysInstancedANGLE(gl.TRIANGLES, start, 6, count)
}
实际上有些图标画对了我看到了 2 个不同的图标,但还有一种看起来像这样:
|\
| \
| \
| /
| /
|/
我的图标只有下面两个三角形,我没有像上面那样设置任何形状,
______
|\ |
| \ |
| \ |
| \|
------
这是一个使用实例化绘图从精灵 sheet 绘制多个精灵的示例。
请注意,如果是我,我会为每个实例使用一个矩阵 like this example 但我认为在此处使用偏移和缩放代码会更简单。
const gl = document.querySelector('canvas').getContext('webgl');
const ext = gl.getExtension('ANGLE_instanced_arrays');
if (!ext) alert('need ANGLE_instanced_arrays');
const vs = `
attribute vec2 position;
attribute vec2 uv;
attribute vec2 offset; // instanced
attribute vec2 scale; // instanced
attribute vec2 uvOffset; // instanced
attribute vec2 uvScale; // instanced
uniform mat4 matrix;
varying vec2 v_uv;
void main() {
gl_Position = matrix * vec4(position * scale + offset, 0, 1);
v_uv = uv * uvScale + uvOffset;
}
`;
const fs = `
precision highp float;
varying vec2 v_uv;
uniform sampler2D spriteAtlas;
void main() {
gl_FragColor = texture2D(spriteAtlas, v_uv);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const uvLoc = gl.getAttribLocation(program, 'uv');
const offsetLoc = gl.getAttribLocation(program, 'offset');
const scaleLoc = gl.getAttribLocation(program, 'scale');
const uvOffsetLoc = gl.getAttribLocation(program, 'uvOffset');
const uvScaleLoc = gl.getAttribLocation(program, 'uvScale');
const matrixLoc = gl.getUniformLocation(program, 'matrix');
// setup quad positions and uv
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 0,
1, 0,
0, 1,
0, 1,
1, 0,
1, 1,
]), gl.STATIC_DRAW);
const uvBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 0,
1, 0,
0, 1,
0, 1,
1, 0,
1, 1,
]), gl.STATIC_DRAW);
// create typed array for instanced data
const maxSprites = 1000;
const offsets = new Float32Array(maxSprites * 2);
const scales = new Float32Array(maxSprites * 2);
const uvOffsets = new Float32Array(maxSprites * 2);
const uvScales = new Float32Array(maxSprites * 2);
// create buffers fo instanced data
const offsetBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
gl.bufferData(gl.ARRAY_BUFFER, offsets.byteLength, gl.DYNAMIC_DRAW);
const scaleBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, scaleBuffer);
gl.bufferData(gl.ARRAY_BUFFER, scales.byteLength, gl.DYNAMIC_DRAW);
const uvOffsetBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, uvOffsetBuffer);
gl.bufferData(gl.ARRAY_BUFFER, uvOffsets.byteLength, gl.DYNAMIC_DRAW);
const uvScaleBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, uvScaleBuffer);
gl.bufferData(gl.ARRAY_BUFFER, uvScales.byteLength, gl.DYNAMIC_DRAW);
let spriteNdx = 0;
function addSprite(
spriteAtlasWidth, spriteAtlasHeight,
srcX, srcY, srcWidth, srcHeight,
dstX, dstY, dstWidth, dstHeight) {
const off0 = spriteNdx * 2;
const off1 = off0 + 1;
offsets[off0] = dstX;
offsets[off1] = dstY;
scales[off0] = dstWidth;
scales[off1] = dstHeight;
uvOffsets[off0] = srcX / spriteAtlasWidth;
uvOffsets[off1] = srcY / spriteAtlasHeight;
uvScales[off0] = srcWidth / spriteAtlasWidth;
uvScales[off1] = srcHeight / spriteAtlasHeight;
++spriteNdx;
}
const sprites = [
{msg: 'A', x: 0, y: 0, w: 64, h: 32, bg: 'red', fg: 'yellow'},
{msg: 'B', x: 64, y: 0, w: 64, h: 32, bg: 'blue', fg: 'white' },
{msg: 'C', x: 0, y: 32, w: 40, h: 32, bg: 'green', fg: 'pink' },
{msg: 'D', x: 40, y: 32, w: 48, h: 32, bg: 'purple', fg: 'cyan' },
{msg: 'F', x: 88, y: 32, w: 40, h: 32, bg: 'black', fg: 'red' },
];
// make 5 sprites in an atlas
const spriteAtlasWidth = 128;
const spriteAtlasHeight = 64;
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = spriteAtlasWidth;
ctx.canvas.height = spriteAtlasHeight;
for (const spr of sprites) {
ctx.fillStyle = spr.bg;
ctx.fillRect(spr.x, spr.y, spr.w, spr.h);
ctx.strokeStyle = spr.fg;
ctx.strokeRect(spr.x + .5, spr.y + .5, spr.w - 1, spr.h - 1);
ctx.fillStyle = spr.fg;
ctx.font = 'bold 26px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(spr.msg, spr.x + spr.w / 2, spr.y + spr.h / 2);
}
// show the atlas
document.body.appendChild(ctx.canvas);
// copy the atlas to a texture
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, ctx.canvas);
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);
function render(time) {
time *= 0.001; // convert to seconds
spriteNdx = 0;
const numSprites = 10;
for (let i = 0; i < numSprites; ++i) {
const sp = sprites[i % sprites.length];
addSprite(
spriteAtlasWidth, spriteAtlasHeight,
sp.x, sp.y, sp.w, sp.h,
Math.sin(time + i * 15) * gl.canvas.width / 2 + gl.canvas.width / 2,
Math.cos(time + i * 17) * gl.canvas.height / 2 + gl.canvas.height / 2,
sp.w, sp.h,
);
}
// copy the latest sprite instance data
// to their respective buffers and setup
// the attributes.
// NOTE: for the attributes it would be better
// to use a vertex array
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer);
gl.enableVertexAttribArray(uvLoc);
gl.vertexAttribPointer(uvLoc, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, offsets);
gl.enableVertexAttribArray(offsetLoc);
gl.vertexAttribPointer(offsetLoc, 2, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(offsetLoc, 1);
gl.bindBuffer(gl.ARRAY_BUFFER, scaleBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, scales);
gl.enableVertexAttribArray(scaleLoc);
gl.vertexAttribPointer(scaleLoc, 2, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(scaleLoc, 1);
gl.bindBuffer(gl.ARRAY_BUFFER, uvOffsetBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, uvOffsets);
gl.enableVertexAttribArray(uvOffsetLoc);
gl.vertexAttribPointer(uvOffsetLoc, 2, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(uvOffsetLoc, 1);
gl.bindBuffer(gl.ARRAY_BUFFER, uvScaleBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, uvScales);
gl.enableVertexAttribArray(uvScaleLoc);
gl.vertexAttribPointer(uvScaleLoc, 2, gl.FLOAT, false, 0, 0);
ext.vertexAttribDivisorANGLE(uvScaleLoc, 1);
gl.useProgram(program);
// pass in a projection matrix that
// converts to pixel space so the top
// left corner is 0,0 and the bottom right corner
// is canvas.width, canvas.height
//
// if you had a 3d math library this would be something like
// m4.ortho(0, gl.canvas.width, gl.canvas.height, 0, -1, 1);
gl.uniformMatrix4fv(matrixLoc, false, [
2 / gl.canvas.width, 0, 0, 0,
0, -2 / gl.canvas.height, 0, 0,
0, 0, 1, 0,
-1, 1, 0, 1,
]);
// note as there as only 1 texture and
// uniforms default to 0 we don't need to
// bind the texture to setup a uniform
// as the defaults happen to work.
ext.drawArraysInstancedANGLE(
gl.TRIANGLES,
0,
6, // verts per instance
spriteNdx, // num instances
);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; margin: 5px; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
请注意,我没有跳过实例,但如果您想跳过实例,则需要为每个实例属性设置传递给 gl.vertexAttribPointer
的偏移量。例如在上面的代码中,如果你想绘制实例 7 到 29,它将是
const numInstancesToSkip = 7;
const numInstancesToDraw = 29 - 7 + 1;
const size = 2; // vec2
const sizeOfFloat = 4;
const offset = numInstancesToSkip * sizeOfFloat * size;
gl.bindBuffer(offsetBuffer);
gl.vertexAttribPointer(offsetLoc, size, gl.FLOAT, false, 0, offset);
gl.bindBuffer(scaleBuffer);
gl.vertexAttribPointer(scaleLoc, size, gl.FLOAT, false, 0, offset);
gl.bindBuffer(uvOffsetBuffer);
gl.vertexAttribPointer(uvOffsetLoc, size, gl.FLOAT, false, 0, offset);
gl.bindBuffer(uvScaleBuffer);
gl.vertexAttribPointer(uvScaleLoc, size, gl.FLOAT, false, 0, offset);
并且绘制将是
ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, mumInstancesToDraw);
请注意,上面的 offset
对于每个属性都是相同的,因为所有属性都具有相同的大小 (2) 和相同的类型 (gl.FLOAT),并且它们都位于单独的缓冲区中,因此它们基本偏移量均为 0。如果它们的大小或类型不同,或者混合到同一个缓冲区中,它们都需要不同的数学运算。