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高)),每次使用位置 - 速度 - 加速度数据填充sampler3D中的右像素,然后我可以触发它以用于下一次迭代。
我还没有结果。我希望以编程方式制作第一个sampler3D,用它来生成新的顶点数据,保存在第二个sampler3D中,然后切换纹理并重复。
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
的第3个坐标,这是我们在每个绘图调用中传递的。
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);
从mip级别0获得x = 12,y = 23,z = 45的纹素。这意味着你不需要对'onePixel`进行数学计算如果您发现使用像素而不是标准化纹理坐标更容易使用代码。