在WebGL中-实例化几何体时,是否可以为每个实例传递每个顶点的属性信息?

时间:2019-03-23 23:27:56

标签: glsl webgl

我正在尝试通过具有实例化几何体的原始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

是否有可能像第二个步骤那样使事情正常进行?如果不是这样,那很好,但是我认为应该问。

1 个答案:

答案 0 :(得分:0)

这取决于您的意思

  

是否可以为要渲染的几何的每个实例传递每个顶点的数据?

如果通过属性来表示,那么答案是否定的。您可以获取每个顶点的数据或每个实例的数据,但不能获取每个实例的每个顶点的数据。

但是您还会获得2个额外的输入,gl_VertexIDgl_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>