我可以在 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。如果它们的大小或类型不同,或者混合到同一个缓冲区中,它们都需要不同的数学运算。