【问题标题】:WebGL2 -- How to store and retrieve 3D texture data needed by 3D grid of vertices to calculate new vertex positionsWebGL2——如何存储和检索 3D 顶点网格所需的 3D 纹理数据以计算新的顶点位置
【发布时间】:2019-09-12 20:54:07
【问题描述】:

3D 物理模拟需要访问着色器中相邻顶点的位置和属性来计算顶点的新位置。 2D 版本有效,但在将解决方案移植到 3D 时遇到问题。翻转两个 3D 纹理似乎是正确的,为一个纹理输入一组 x、y 和 z 坐标,并获取包含相邻点的位置-速度-加速度数据的 vec4s,用于计算每个顶点的新位置和速度。 2D 版本使用带有帧缓冲区的 1 个绘图调用将所有生成的 gl_FragColors 保存到 sampler2D。我想使用帧缓冲区对 sampler3D 做同样的事情。但它看起来像在 3D 中使用帧缓冲区,我需要在第二个 3D 纹理的时候写一个 + 层,直到所有层都被保存。我对将顶点网格映射到纹理的相对 x、y、z 坐标以及如何将其单独保存到图层感到困惑。在 2D 版本中,写入帧缓冲区的 gl_FragColor 直接映射到画布的 2D x-y 坐标系,每个像素都是一个顶点。但我不明白如何确保将包含 3D 顶点位置速度数据的 gl_FragColor 写入纹理,以便它保持正确映射到 3D 顶点。

这适用于片段着色器中的 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 上找到了这个答案: Render to 3D texture webgl2 它演示了在帧缓冲区中一次写入多个图层,但我没有看到如何将其与片段着色器等同起来,从一个绘图调用,自动运行 1,000,000 次(100 x 100 x 100 ...(长度 x 宽度x height)),每次使用位置-速度-加速度数据填充 sampler3D 中的正确像素,然后我可以将其用于下一次迭代。

我还没有结果。我希望以编程方式制作第一个 sampler3D,用它生成新的顶点数据,保存在第二个 sampler3D 中,然后切换纹理并重复。

【问题讨论】:

    标签: textures shader framebuffer webgl2


    【解决方案1】:

    WebGL 是基于目标的。这意味着它对要写入目标的每个结果执行 1 次操作。您可以设置的唯一类型的目的地是 2D 平面中的点(像素的正方形)、线和三角形。这意味着写入 3D 纹理将需要分别处理每个平面。充其量你可以通过将多个附件设置到帧缓冲区来分别在 N 为 4 到 8 的情况下执行 N 个平面,直至允许的最大附件数

    所以我假设您了解如何一次渲染到 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 层。我们只为v_texCoord 传入一个vec2,并根据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 查询纹理大小。它根据纹理类型返回ivec2ivec3

    您也可以使用texelFetch 代替texturetexelFetch 采用整数纹理像素坐标和 mip 级别,例如 vec4 color = texelFetch(some3DTexture, ivec3(12, 23, 45), 0); 从 mip 级别 0 获取 x = 12、y = 23、z = 45 处的纹理像素。这意味着您不需要计算如果您发现使用像素而不是规范化纹理坐标更容易,那么您的代码中就有“onePixel”。

    【讨论】:

    • 答案,加上代码 sn-p,加上关于 texelFetch 的建议似乎解决了我的问题,消除了我的困惑,并简化了我的代码。
    • 如果不是@gman,我所有的网络图形都会由蜡笔和贴在我电脑屏幕上的便签组成。
    猜你喜欢
    • 1970-01-01
    • 2015-07-17
    • 2021-06-02
    • 1970-01-01
    • 2012-07-31
    • 2011-08-15
    • 2011-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多