首页 > 解决方案 > 如何在帧缓冲区中为 3DTexture 的一层渲染单个像素?

问题描述

我有一个 4x4x4 3DTexture,我正在初始化并正确显示它以着色我的 4x4x4 顶点网格(请参阅带有一个白色像素 - 0,0,0 的附加红色网格)。

在此处输入图像描述

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

在此处输入图像描述

当我只使用 gl.COLOR_ATTACHMENT0 做一层时,相同的 4 个像素显示为 1 层正确更改,而其他 3 层保持原始颜色不变。当我将 gl.viewport(0, 0, size, size) (在此示例中为 size = 4)更改为整个屏幕之类的其他内容,或者大小不超过 4 时,会写入不同的像素,但不会超过 4 . 我的目标是精确地单独指定每一层的所有 16 个像素。我现在使用颜色作为学习经验,但纹理实际上是用于物理模拟的每个顶点的位置和速度信息。我假设(错误的假设?)有 64 个点/顶点,我正在运行顶点着色器和片段着色器各 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);
}
`;

标签: framebufferwebgl23d-texture

解决方案


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

在 WebGL2 中有 2 个用于 GPU 模拟的选项

  1. 使用变换反馈。

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

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

  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,那么变换反馈就更容易了。如果需要每个粒子能够查看其他粒子的状态,换句话说,您需要随机访问所有数据,那么您唯一的选择是将数据存储在纹理中。

至于职位,我不明白你的代码。位置定义了一个基元,要么POINTS,,LINES要么TRIANGLES将整数 X,Y 值传递到我们的顶点着色器中如何帮助您定义POINTSLINES或者TRIANGLES

看起来您正在尝试使用POINTS在这种情况下您需要设置gl_PointSize为要绘制的点的大小(1.0),并且您需要将这些位置转换为剪辑空间

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

resolution纹理的大小在哪里。

但是这样做会很慢。最好只绘制一个全尺寸(-1 到 +1)的剪辑空间四边形。对于目标中的每个像素,都会调用片段着色器。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>


推荐阅读