В WebGL - при создании экземпляра геометрии можно ли передавать информацию об атрибутах каждой вершины для каждого экземпляра? - PullRequest
1 голос
/ 24 марта 2019

Я пытаюсь воссоздать через raw WebGL следующий пример three.js с экземплярами геометрии; хотя после тестирования мне кажется, что мне просто нужно нарисовать несколько сеток, как в примере, но я подумал, что сначала попрошу здесь проверить еще раз.

Вопрос теперь в сущности - возможно ли передавать данные по каждой вершине для каждого экземпляра геометрии, которая визуализируется?

Надеюсь, это имеет смысл, но если нет -

  • Геометрия, которую я рендерил, имеет 4 вершины.
  • Для каждого экземпляра геометрии - мне нужно назначить ув-координаты каждой вершине каждого экземпляра геометрии
  • кажется, что установка шага или смещения не работает.

В основном то, что происходит сейчас, дано с текущими данными, которые выглядят примерно так:

[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 голосов
/ 24 марта 2019

Это зависит от того, что вы подразумеваете под

возможно ли передавать данные на каждую вершину для каждого экземпляра геометрии, которая отображается?

Если вы имеете в виду через атрибуты, то ответ - нет. Вы можете получить данные для каждой вершины или данные для каждого экземпляра, но не данные для каждой вершины для экземпляра.

Но вы также получаете 2 дополнительных входа, gl_VertexID и gl_InstanceID (в WebGL2), или вы можете добавить свой собственный, который вы могли бы использовать для вычисления UV или для поиска данных в текстуре для эффективного достижения желаемого результат.

Например, допустим, у вас есть кубики размером 20х10. Вы могли бы сделать что-то вроде

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>
...