WebGL2 - Как хранить и извлекать данные 3D-текстуры, необходимые для 3D-сетки вершин, для расчета новых позиций вершин - PullRequest
2 голосов
/ 23 апреля 2019

Для моделирования трехмерной физики необходим доступ к позициям и атрибутам соседних вершин в шейдере, чтобы вычислить новую позицию вершины. 2D версия работает, но у меня возникли проблемы с переносом решения в 3D. Flip-Flopping двух трехмерных текстур кажется правильным, вводя наборы координат x, y и z для одной текстуры, и получая vec4s, который содержит данные о положении-скорости-ускорении соседних точек, чтобы использовать их для вычисления новых положений и скоростей для каждой вершины. 2D версия использует 1 вызов отрисовки с кадровым буфером, чтобы сохранить все сгенерированные gl_FragColors в sampler2D. Я хочу использовать кадровый буфер, чтобы сделать то же самое с sampler3D. Но похоже на использование кадрового буфера в 3D, мне нужно написать один + слой за время 2-й 3D-текстуры, пока все слои не будут сохранены. Я запутался в отображении сетки вершин в относительные координаты x, y, z текстуры и в том, как сохранить это в слоях по отдельности. В 2D-версии gl_FragColor, записанный в кадровый буфер, отображается непосредственно в 2D-систему координат x-y холста, причем каждый пиксель является вершиной. но я не понимаю, как убедиться, что gl_FragColor, который содержит данные о скорости положения для трехмерной вершины, записывается в текстуру таким образом, чтобы он правильно отображал трехмерные вершины.

Это работает для 2D в фрагментном шейдере:

vec2 onePixel = vec2(1.0, 1.0)/u_textureSize;
vec4 currentState = texture2D(u_image, v_texCoord);
float fTotal = 0.0;
for (int i=-1;i<=1;i+=2){
    for (int j=-1;j<=1;j+=2){
        if (i == 0 && j == 0) continue;
        vec2 neighborCoord = v_texCoord + vec2(onePixel.x*float(i), onePixel.y*float(j));

        vec4 neighborState;
        if (neighborCoord.x < 0.0 || neighborCoord.y < 0.0 || neighborCoord.x >= 1.0 || neighborCoord.y >= 1.0){
            neighborState = vec4(0.0,0.0,0.0,1.0);
        } else {
            neighborState = texture2D(u_image, neighborCoord);
        }

        float deltaP =  neighborState.r - currentState.r;
        float deltaV = neighborState.g - currentState.g;

        fTotal += u_kSpring*deltaP + u_dSpring*deltaV;
    }
}

float acceleration = fTotal/u_mass;
float velocity = acceleration*u_dt + currentState.g;
float position = velocity*u_dt + currentState.r;
gl_FragColor = vec4(position,velocity,acceleration,1);

Это то, что я пытался сделать в 3D во фрагментном шейдере: #version 300 es

vec3 onePixel = vec3(1.0, 1.0, 1.0)/u_textureSize;
vec4 currentState = texture(u_image, v_texCoord);
float fTotal = 0.0;
for (int i=-1; i<=1; i++){
    for (int j=-1; j<=1; j++){
        for (int k=-1; k<=1; k++){
           if (i == 0 && j == 0 && k == 0) continue;
           vec3 neighborCoord = v_texCoord + vec3(onePixel.x*float(i), onePixel.y*float(j), onePixel.z*float(k));
           vec4 neighborState;

           if (neighborCoord.x < 0.0 || neighborCoord.y < 0.0 || neighborCoord.z < 0.0 || neighborCoord.x >= 1.0 || neighborCoord.y >= 1.0 || neighborCoord.z >= 1.0){
               neighborState = vec4(0.0,0.0,0.0,1.0);
           } else {
               neighborState = texture(u_image, neighborCoord);
           }
           float deltaP =  neighborState.r - currentState.r;  //Distance from neighbor
           float springDeltaLength =  (deltaP - u_springOrigLength[counter]);

           //Add the force on our point of interest from the current neighbor point.  We'll be adding up to 26 of these together.
           fTotal += u_kSpring[counter]*springDeltaLength;
        }
    }
}

float acceleration = fTotal/u_mass;
float velocity = acceleration*u_dt + currentState.g;
float position = velocity*u_dt + currentState.r;
gl_FragColor = vec4(position,velocity,acceleration,1);

После того, как я написал это, я продолжал читать и обнаружил, что кадровый буфер не получает доступ ко всем слоям sampler3D для записи одновременно. Мне нужно как-то обрабатывать 1 - 4 слоя за раз. Я не уверен в том, как это сделать, а также в том, что gl_FragColor переходит к нужному пикселю на правом слое.

Я нашел этот ответ на SO: Визуализация в 3D текстуру webgl2 Он демонстрирует запись в несколько слоев одновременно в кадровом буфере, но я не вижу, как приравнять это к фрагментному шейдеру из одного вызова отрисовки, который автоматически запускается 1 000 000 раз (100 x 100 x 100 ... (длина x ширина) x height)), каждый раз заполняя правый пиксель в sampler3D данными об ускорении положения и скорости, которые затем я могу использовать для следующей итерации.

У меня пока нет результатов. Я надеюсь сделать первый sampler3D программно, использовать его для генерации новых данных вершин, которые сохраняются во втором sampler3D, а затем переключать текстуры и повторять.

1 Ответ

1 голос
/ 24 апреля 2019

WebGL зависит от пункта назначения.Это означает, что он выполняет 1 операцию для каждого результата, который он хочет записать в место назначения.Единственными видами назначений, которые вы можете установить, являются точки (квадраты пикселей), линии и треугольники в 2D-плоскости.Это означает, что для записи в 3D-текстуру потребуется обрабатывать каждую плоскость отдельно.В лучшем случае вы можете выполнить N плоскостей отдельно, где N равно 4–8, настроив несколько вложений в буфер кадров до максимально допустимых вложений

Так что я предполагаю, что вы понимаете, как выполнить рендеринг до 100слои 1 за один раз.Во время инициализации либо создайте 100 кадровых буферов и прикрепите разные слои к каждому.ИЛИ, во время рендеринга обновите один кадровый буфер с другим вложением.Зная, сколько проверки происходит, я бы выбрал создание 100 кадровых буферов

Так что

const framebuffers = [];
for (let layer = 0; layer < numLayers; ++layer) {
  const fb = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
  gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture, 
    0, layer);
  framebuffers.push(fb);
}

теперь во время рендеринга для каждого слоя

framebuffers.forEach((fb, layer) => {
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
  // pass in the layer number to the shader it can use for calculations
  gl.uniform1f(layerLocation, layer);
  ....
  gl.drawXXX(...);
});

WebGL1 не поддерживает 3Dтекстур, поэтому мы знаем, что вы используете WebGL2, так как вы упомянули об использовании sampler3D.

В WebGL2 вы обычно используете #version 300 es в верхней части ваших шейдеров, чтобы показать, что вы хотите использовать более современный GLSL ES 3.00.

Для рисования на нескольких слоях необходимо сначала выяснить, сколько слоев вы хотите визуализировать.WebGL2 поддерживает минимум 4 одновременно, поэтому мы можем просто предположить 4 слоя.Для этого вы должны прикрепить 4 слоя к каждому фреймбуферу

const layersPerFramebuffer = 4;
const framebuffers = [];
for (let baseLayer = 0; baseLayer < numLayers; baseLayer += layersPerFramebuffer) {
  const fb = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
  for (let layer = 0; layer < layersPerFramebuffer; ++layer) {
    gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + layer, texture, 0, baseLayer + layer);
  }
  framebuffers.push(fb);
}

В шейдерах GLSL ES 3.0 не используется gl_FragCoord, они используют определяемый пользователем вывод, поэтому мы объявляем вывод массива

out vec4 ourOutput[4];

, а затем используйте это так же, как вы ранее использовали gl_FragColor, за исключением добавления индекса.Ниже мы обрабатываем 4 слоя.Мы только передаем vec2 для v_texCoord и вычисляем третью координату на основе baseLayerTexCoord, что мы передаем при каждом вызове отрисовки.

varying vec2 v_texCoord;
uniform float baseLayerTexCoord;

vec4 results[4];
vec3 onePixel = vec3(1.0, 1.0, 1.0)/u_textureSize;
const int numLayers = 4;
for (int layer = 0; layer < numLayers; ++layer) {
    vec3 baseTexCoord = vec3(v_texCoord, baseLayerTexCoord + onePixel * float(layer));
    vec4 currentState = texture(u_image, baseTexCoord);
    float fTotal = 0.0;
    for (int i=-1; i<=1; i++){
        for (int j=-1; j<=1; j++){
            for (int k=-1; k<=1; k++){
               if (i == 0 && j == 0 && k == 0) continue;
               vec3 neighborCoord = baseTexCoord + vec3(onePixel.x*float(i), onePixel.y*float(j), onePixel.z*float(k));
               vec4 neighborState;

               if (neighborCoord.x < 0.0 || neighborCoord.y < 0.0 || neighborCoord.z < 0.0 || neighborCoord.x >= 1.0 || neighborCoord.y >= 1.0 || neighborCoord.z >= 1.0){
                   neighborState = vec4(0.0,0.0,0.0,1.0);
               } else {
                   neighborState = texture(u_image, neighborCoord);
               }
               float deltaP =  neighborState.r - currentState.r;  //Distance from neighbor
               float springDeltaLength =  (deltaP - u_springOrigLength[counter]);

               //Add the force on our point of interest from the current neighbor point.  We'll be adding up to 26 of these together.
               fTotal += u_kSpring[counter]*springDeltaLength;
            }
        }
    }

    float acceleration = fTotal/u_mass;
    float velocity = acceleration*u_dt + currentState.g;
    float position = velocity*u_dt + currentState.r;
    results[layer] = vec4(position,velocity,acceleration,1);
}
ourOutput[0] = results[0];
ourOutput[1] = results[1];
ourOutput[2] = results[2];
ourOutput[3] = results[3];

Последнее, что нам нужно сделать, - это вызватьgl.drawBuffers, чтобы сообщить WebGL2, где хранить результаты.Поскольку мы делаем 4 слоя за раз, мы будем использовать

gl.drawBuffers([
  gl.COLOR_ATTACHMENT0,
  gl.COLOR_ATTACHMENT1,
  gl.COLOR_ATTACHMENT2,
  gl.COLOR_ATTACHMENT3,
]);
framebuffers.forEach((fb, ndx) => {
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
  gl.uniform1f(baseLayerTexCoordLocation, (ndx * layersPerFramebuffer + 0.5) / numLayers);
  ....
  gl.drawXXX(...);
});

Пример:

function main() {
  const gl = document.querySelector('canvas').getContext('webgl2');
  if (!gl) {
    return alert('need webgl2');
  }
  const ext = gl.getExtension('EXT_color_buffer_float');
  if (!ext) {
    return alert('need EXT_color_buffer_float');
  }
  
  const vs = `#version 300 es
  in vec4 position;
  out vec2 v_texCoord;
  void main() {
    gl_Position = position;
    // position will be a quad -1 to +1 so we
    // can use that for our texcoords
    v_texCoord = position.xy * 0.5 + 0.5;
  }
  `;
  
  const fs = `#version 300 es
precision highp float;
in vec2 v_texCoord;
uniform float baseLayerTexCoord;
uniform highp sampler3D u_image;
uniform mat3 u_kernel[3];

out vec4 ourOutput[4];

void main() {
  vec3 textureSize = vec3(textureSize(u_image, 0));
  vec3 onePixel = vec3(1.0, 1.0, 1.0)/textureSize;
  const int numLayers = 4;
  vec4 results[4];
  for (int layer = 0; layer < numLayers; ++layer) {
      vec3 baseTexCoord = vec3(v_texCoord, baseLayerTexCoord + onePixel * float(layer));
      float fTotal = 0.0;
      vec4 color;
      for (int i=-1; i<=1; i++){
          for (int j=-1; j<=1; j++){
              for (int k=-1; k<=1; k++){
                 vec3 neighborCoord = baseTexCoord + vec3(onePixel.x*float(i), onePixel.y*float(j), onePixel.z*float(k));
                 color += u_kernel[k + 1][j + 1][i + 1] * texture(u_image, neighborCoord);
              }
          }
      }

      results[layer] = color;
  }
  ourOutput[0] = results[0];
  ourOutput[1] = results[1];
  ourOutput[2] = results[2];
  ourOutput[3] = results[3];
}
  `;
  const vs2 = `#version 300 es
  uniform vec4 position;
  uniform float size;
  void main() {
    gl_Position = position;
    gl_PointSize = size;
  }
  `;
  const fs2 = `#version 300 es
  precision highp float;
  uniform highp sampler3D u_image;
  uniform float slice;
  out vec4 outColor;
  void main() {
    outColor = texture(u_image, vec3(gl_PointCoord.xy, slice));
  }
  `;
  
  const computeProgramInfo = twgl.createProgramInfo(gl, [vs, fs]);
  const drawProgramInfo = twgl.createProgramInfo(gl, [vs2, fs2]);
  
  const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
    position: {
      numComponents: 2,
      data: [
        -1, -1,
         1, -1,
        -1,  1,
        -1,  1,
         1, -1,
         1,  1,
      ],
    },
  });

  function create3DTexture(gl, size) {
    const tex = gl.createTexture();
    const data = new Float32Array(size * size * size * 4);
    for (let i = 0; i < data.length; i += 4) {
      data[i + 0] = i % 100 / 100;
      data[i + 1] = i % 10000 / 10000;
      data[i + 2] = i % 100000 / 100000;
      data[i + 3] = 1;
    }
    gl.bindTexture(gl.TEXTURE_3D, tex);
    gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA32F, size, size, size, 0, gl.RGBA, gl.FLOAT, data);

    gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    return tex;
  }

  const size = 100;
  let inTex = create3DTexture(gl, size);
  let outTex = create3DTexture(gl, size);
  const numLayers = size;
  const layersPerFramebuffer = 4;
  
  function makeFramebufferSet(gl, tex) {
    const framebuffers = [];
    for (let baseLayer = 0; baseLayer < numLayers; baseLayer += layersPerFramebuffer) {
      const fb = gl.createFramebuffer();
      gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
      for (let layer = 0; layer < layersPerFramebuffer; ++layer) {
        gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + layer, tex, 0, baseLayer + layer);
      }
      framebuffers.push(fb);
    }
    return framebuffers;
  };
  
  let inFramebuffers = makeFramebufferSet(gl, inTex);
  let outFramebuffers = makeFramebufferSet(gl, outTex);

  function render() {
    gl.viewport(0, 0, size, size);
    gl.useProgram(computeProgramInfo.program);
    twgl.setBuffersAndAttributes(gl, computeProgramInfo, bufferInfo);

    outFramebuffers.forEach((fb, ndx) => {
      gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
      gl.drawBuffers([
        gl.COLOR_ATTACHMENT0,
        gl.COLOR_ATTACHMENT1,
        gl.COLOR_ATTACHMENT2,
        gl.COLOR_ATTACHMENT3,
      ]);

      const baseLayerTexCoord = (ndx * layersPerFramebuffer + 0.5) / numLayers;
      twgl.setUniforms(computeProgramInfo, {
        baseLayerTexCoord,
        u_kernel: [
          0, 0, 0,
          0, 0, 0,
          0, 0, 0,

          0, 0, 1,
          0, 0, 0,
          0, 0, 0,

          0, 0, 0,
          0, 0, 0,
          0, 0, 0,
        ],
        u_image: inTex,      
      });

      gl.drawArrays(gl.TRIANGLES, 0, 6);
    });

    {
      const t = inFramebuffers;
      inFramebuffers = outFramebuffers;
      outFramebuffers = t;
    }

    {
      const t = inTex;
      inTex = outTex;
      outTex = t;
    }

    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.drawBuffers([gl.BACK]);
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    gl.useProgram(drawProgramInfo.program);

    const slices = 10.0;
    const sliceSize = 25.0
    for (let slice = 0; slice < slices; ++slice) {
      const sliceZTexCoord = (slice / slices * size + 0.5) / size;
      twgl.setUniforms(drawProgramInfo, {
        position: [
          ((slice * (sliceSize + 1) + sliceSize * .5) / gl.canvas.width * 2) - 1,
          0,
          0,
          1,
        ],
        slice: sliceZTexCoord,
        size: sliceSize,
      });
      gl.drawArrays(gl.POINTS, 0, 1);
    }
    
    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}

main();


function glEnumToString(gl, v) {
  const hits = [];
  for (const key in gl) {
    if (gl[key] === v) {
      hits.push(key);
    }
  }
  return hits.length ? hits.join(' | ') : `0x${v.toString(16)}`;
}
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

Некоторые другие вещи, на которые следует обратить внимание: В GLSL ES 3.00 вам не нужно передавать размер текстуры, так как вы можете запросить размер текстуры с помощью функции textureSize.Возвращает ivec2 или ivec3 в зависимости от типа текстуры.

Вы также можете использовать texelFetch вместо texture.texelFetch принимает целочисленную координату текселя и уровень mip, например, vec4 color = texelFetch(some3DTexture, ivec3(12, 23, 45), 0); получает тексель при x = 12, y = 23, z = 45 от уровня mip 0. Это означает, что вам не нужно делать математику о'onePixel` у вас есть в коде, если вам легче работать с пикселями вместо нормализованных координат текстуры.

...