如何在帧缓冲区中为一层 3DTexture 渲染单个像素?

How to render individual pixels for one layer of a 3DTexture in a framebuffer?

我有一个 4x4x4 3DTexture,我正在对其进行初始化并正确显示,以便为我的 4x4x4 顶点网格着色(参见附带的带有一个白色像素的红色网格 - 0,0,0)。

然而,当我在帧缓冲区中渲染 4 层时(使用 gl.COLOR_ATTACHMENT0 --> gl.COLOR_ATTACHMENT3 一次渲染所有 4 层时,一层上的 16 个像素中只有 4 个是由我的片段着色器成功渲染(变为绿色)。

当我只做一层时,使用 gl.COLOR_ATTACHMENT0,第 1 层正确显示相同的 4 个像素,而其他 3 层保持原始颜色不变。当我将 gl.viewport(0, 0, size, size)(在本例中为 size = 4)更改为整个屏幕或不同于 4 的其他尺寸时,将写入不同的像素,但不会更多大于 4。我的目标是精确地单独指定每一层的所有 16 个像素。我现在使用颜色,作为一种学习经验,但纹理实际上是用于物理模拟的每个顶点的位置和速度信息。我假设(错误的假设?)64 points/vertices,我 运行 顶点着色器和片段着色器各 64 次,每次调用着色一个像素。

我已经从着色器中删除了除重要代码之外的所有代码。我没有改变 javascript。我怀疑我的问题是错误地初始化和传递了顶点位置数组。

//Set x,y position coordinates to be used to extract data from one plane of our data cube
//remember, z we handle as a 1 layer of our cube which is composed of a stack of x-y planes. 
const oneLayerVertices = new Float32Array(size * size * 2);
count = 0;  
for (var j = 0; j < (size); j++) {
    for (var i = 0; i < (size); i++) {
        oneLayerVertices[count] = i;
        count++;

        oneLayerVertices[count] = j;
        count++;

        //oneLayerVertices[count] = 0;
        //count++;

        //oneLayerVertices[count] = 0;
        //count++;

    }
}

const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
   position: {
      numComponents: 2,
      data: oneLayerVertices,
   },
});

然后我使用 bufferInfo 如下:

gl.useProgram(computeProgramInfo.program);
   twgl.setBuffersAndAttributes(gl, computeProgramInfo, bufferInfo);

   gl.viewport(0, 0, size, size); //remember size = 4

   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 * numLayersPerFramebuffer);
      console.log("My baseLayerTexCoord is "+baseLayerTexCoord);
      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_position: inPos,      
         u_velocity: inVel,      
         loopCounter: loopCounter,   

         numLayersPerFramebuffer: numLayersPerFramebuffer
      });
      gl.drawArrays(gl.POINTS, 0, (16));
   });

顶点着色器: calc_vertex:

const compute_vs = `#version 300 es
  precision highp float;
  in vec4 position;
  void main() {
    gl_Position = position;
  }
`;

片段着色器: calc_fragment:

const compute_fs = `#version 300 es
precision highp float;

out vec4 ourOutput[4];

void main() {
   ourOutput[0] = vec4(0,1,0,1);
   ourOutput[1] = vec4(0,1,0,1);
   ourOutput[2] = vec4(0,1,0,1);
   ourOutput[3] = vec4(0,1,0,1);
}
`;

我不确定你想做什么以及你认为这些职位会做什么。

在 WebGL2 中您有 2 个 GPU 模拟选项

  1. 使用变换反馈。

    在这种情况下,您传入属性并在缓冲区中生成数据。实际上你有 in 属性和 out 属性,通常你只有 运行 顶点着色器。换句话说,你的变化,你的顶点着色器的输出,被写入一个缓冲区。所以你至少有 2 组缓冲区,currentState 和 nextState,你的顶点着色器从 currentState 读取属性并将它们写入 nextState

    有一个通过变换反馈写入缓冲区的示例 here 尽管该示例仅在开始时使用变换反馈来填充缓冲区一次。

  2. 使用附加到帧缓冲区的纹理

    在这种情况下,类似地,您有 2 个纹理,currentState 和 nextState,您将 nextState 设置为渲染目标并从 currentState 读取以生成下一个状态。

    困难在于您只能通过在顶点着色器中输出图元来渲染到纹理。如果 currentState 和 nextState 是 2D 纹理那是微不足道的。只需从顶点着色器输出一个 -1.0 到 +1.0 的四边形,nextState 中的所有像素都将被渲染到。

    如果你使用的是 3D 纹理,那么同样的事情,除了你一次只能渲染到 4 个图层(好吧,gl.getParameter(gl.MAX_DRAW_BUFFERS))。所以你必须做类似

    的事情
    for(let layer = 0; layer < numLayers; layer += 4) {
       // setup framebuffer to use these 4 layers
       gl.drawXXX(...) // draw to 4 layers)
    }
    

    或更好

    // at init time
    const fbs = [];
    for(let layer = 0; layer < numLayers; layer += 4) {
       fbs.push(createFramebufferForThese4Layers(layer);
    }
    
    // at draw time
    fbs.forEach((fb, ndx) => {;
       gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
       gl.drawXXX(...) // draw to 4 layers)
    });
    

    我猜多个绘图调用比一个绘图调用慢,所以另一种解决方案是将 2D 纹理视为 3D 数组并适当地计算纹理坐标。

不知道哪个更好。如果您正在模拟粒子并且它们只需要查看它们自己的 currentState,那么变换反馈就更容易了。如果需要每个粒子能够查看其他粒子的状态,换句话说,您需要随机访问所有数据,那么您唯一的选择就是将数据存储在纹理中。

至于位置我不明白你的代码。位置定义基元,POINTSLINESTRIANGLES 那么将整数 X、Y 值传递到我们的顶点着色器如何帮助您定义 POINTSLINESTRIANGLES?

您似乎在尝试使用 POINTS,在这种情况下,您需要将 gl_PointSize 设置为您要绘制的点的大小 (1.0),并且需要将它们转换位置到剪辑 space

gl_Position = vec4((position.xy + 0.5) / resolution, 0, 1);

其中 resolution 是纹理的大小。

但是这样做会很慢。最好只画一个全尺寸(-1 到 +1)剪辑 space 四边形。对于目标中的每个像素,将调用片段着色器。 gl_FragCoord.xy 将是当前正在渲染的像素的中心位置,因此左下角的第一个像素 gl_FragCoord.xy 将是 (0.5, 0.5)。右边的像素将是 (1.5, 0.5)。右边的像素将是 (2.5, 0.5)。您可以使用该值来计算如何访问 currentState。假设 1x1 映射最简单的方法是

int n = numberOfLayerThatsAttachedToCOLOR_ATTACHMENT0;
vec4 currentStateValueForLayerN = texelFetch(
    currentStateTexture, ivec3(gl_FragCoord.xy, n + 0), 0);
vec4 currentStateValueForLayerNPlus1 = texelFetch(
    currentStateTexture, ivec3(gl_FragCoord.xy, n + 1), 0);
vec4 currentStateValueForLayerNPlus2 = texelFetch(
    currentStateTexture, ivec3(gl_FragCoord.xy, n + 2), 0);
...

vec4 nextStateForLayerN = computeNextStateFromCurrentState(currentStateValueForLayerN);
vec4 nextStateForLayerNPlus1 = computeNextStateFromCurrentState(currentStateValueForLayerNPlus1);
vec4 nextStateForLayerNPlus2 = computeNextStateFromCurrentState(currentStateValueForLayerNPlus2);
...

outColor[0] = nextStateForLayerN;
outColor[1] = nextStateForLayerNPlus1;
outColor[2] = nextStateForLayerNPlus1;
...

我不知道你是否需要这个,但只是为了测试这里有一个简单的例子,它为 4x4x4 纹理的每个像素渲染不同的颜色,然后显示它们。

const pointVS = `
#version 300 es

uniform int size;
uniform highp sampler3D tex;
out vec4 v_color;

void main() {
  int x = gl_VertexID % size;
  int y = (gl_VertexID / size) % size;
  int z = gl_VertexID / (size * size);
  
  v_color = texelFetch(tex, ivec3(x, y, z), 0);
  
  gl_PointSize = 8.0;
  
  vec3 normPos = vec3(x, y, z) / float(size); 
  gl_Position = vec4(
     mix(-0.9, 0.6, normPos.x) + mix(0.0,  0.3, normPos.y),
     mix(-0.6, 0.9, normPos.z) + mix(0.0, -0.3, normPos.y),
     0,
     1);
}
`;

const pointFS = `
#version 300 es
precision highp float;

in vec4 v_color;
out vec4 outColor;

void main() {
  outColor = v_color;
}
`;

const rtVS = `
#version 300 es
in vec4 position;
void main() {
  gl_Position = position;
}
`;

const rtFS = `
#version 300 es
precision highp float;

uniform vec2 resolution;
out vec4 outColor[4];

void main() {
  vec2 xy = gl_FragCoord.xy / resolution;
  outColor[0] = vec4(1, 0, xy.x, 1);
  outColor[1] = vec4(0.5, xy.yx, 1);
  outColor[2] = vec4(xy, 0, 1);
  outColor[3] = vec4(1, vec2(1) - xy, 1);
}
`;

function main() {
  const gl = document.querySelector('canvas').getContext('webgl2');
  if (!gl) {
    return alert('need webgl2');
  }
  
  const pointProgramInfo = twgl.createProgramInfo(gl, [pointVS, pointFS]);
  const rtProgramInfo = twgl.createProgramInfo(gl, [rtVS, rtFS]);
  
  const size = 4;
  const numPoints = size * size * size;
  const tex = twgl.createTexture(gl, {
    target: gl.TEXTURE_3D,
    width: size,
    height: size,
    depth: size,
  });
  
  const clipspaceFullSizeQuadBufferInfo = twgl.createBufferInfoFromArrays(gl, {
    position: {
      data: [
        -1, -1,
         1, -1,
        -1,  1,
        
        -1,  1,
         1, -1,
         1,  1,
      ],
      numComponents: 2,
    },
  });
  
  const fb = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
  for (let i = 0; i < 4; ++i) {
    gl.framebufferTextureLayer(
        gl.FRAMEBUFFER,
        gl.COLOR_ATTACHMENT0 + i,
        tex,
        0, // mip level
        i, // layer
    );
  }
  
  gl.drawBuffers([
     gl.COLOR_ATTACHMENT0,
     gl.COLOR_ATTACHMENT1,
     gl.COLOR_ATTACHMENT2,
     gl.COLOR_ATTACHMENT3,
  ]);

  gl.viewport(0, 0, size, size);
  gl.useProgram(rtProgramInfo.program);
  twgl.setBuffersAndAttributes(
      gl,
      rtProgramInfo,
      clipspaceFullSizeQuadBufferInfo);
  twgl.setUniforms(rtProgramInfo, {
    resolution: [size, size],
  });
  twgl.drawBufferInfo(gl, clipspaceFullSizeQuadBufferInfo);
  
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  gl.drawBuffers([
     gl.BACK,
  ]);
  
  gl.useProgram(pointProgramInfo.program);
  twgl.setUniforms(pointProgramInfo, {
    tex,
    size,
  });
  gl.drawArrays(gl.POINTS, 0, numPoints);
}
main();
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>