如何在帧缓冲区中为一层 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 模拟选项
使用变换反馈。
在这种情况下,您传入属性并在缓冲区中生成数据。实际上你有 in 属性和 out 属性,通常你只有 运行 顶点着色器。换句话说,你的变化,你的顶点着色器的输出,被写入一个缓冲区。所以你至少有 2 组缓冲区,currentState 和 nextState,你的顶点着色器从 currentState 读取属性并将它们写入 nextState
有一个通过变换反馈写入缓冲区的示例 here 尽管该示例仅在开始时使用变换反馈来填充缓冲区一次。
使用附加到帧缓冲区的纹理
在这种情况下,类似地,您有 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 值传递到我们的顶点着色器如何帮助您定义 POINTS
、LINES
或 TRIANGLES
?
您似乎在尝试使用 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>
我有一个 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 模拟选项
使用变换反馈。
在这种情况下,您传入属性并在缓冲区中生成数据。实际上你有 in 属性和 out 属性,通常你只有 运行 顶点着色器。换句话说,你的变化,你的顶点着色器的输出,被写入一个缓冲区。所以你至少有 2 组缓冲区,currentState 和 nextState,你的顶点着色器从 currentState 读取属性并将它们写入 nextState
有一个通过变换反馈写入缓冲区的示例 here 尽管该示例仅在开始时使用变换反馈来填充缓冲区一次。
使用附加到帧缓冲区的纹理
在这种情况下,类似地,您有 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 值传递到我们的顶点着色器如何帮助您定义 POINTS
、LINES
或 TRIANGLES
?
您似乎在尝试使用 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>