在 WebGL 中用 1d 纹理替换统一数组时数据损坏
Data corruption when replacing uniform array with 1d texture in WebGL
我正在 WebGL2 中对大型 4D 输入数组进行一些 GPGPU 处理。最初,我只是将输入数组展平并将其作为统一的整数数组传入,使用 GLSL 中的自定义访问器函数将 4D 坐标转换为数组索引,如下所示:
const int SIZE = 5; // The largest dimension that works; if I can switch to textures, this will be passed in as a uniform value.
const int SIZE2 = SIZE*SIZE;
const int SIZE3 = SIZE*SIZE2;
const int SIZE4 = SIZE*SIZE3;
uniform int u_map[SIZE4];
int get_cell(vec4 m){
ivec4 i = ivec4(mod(m,float(SIZE)));
return u_map[i.x*SIZE3+i.y*SIZE2+i.z*SIZE+i.w];
}
在JavaScript这边,压平后的数据传入如下:
const map_loc = gl.getUniformLocation(program, "u_map");
gl.uniform1iv(map_loc, data);
其中 data
是一个包含 8 位值的 Int32Array
(因为这是映射到 GLSL 整数所需要的)。
可行,但它严重限制了我可以使用的输入大小。将维度大小提高到 6 会导致使用 1296 个统一槽,而只有 1024 个可用于此数据和其他控制输入。
所以,我想改用纹理来保存大量数据。所以,我将 JS 代码更新为:
const tex = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, data.length, 1, 0, gl.RED, gl.UNSIGNED_BYTE, data);
其中 data
已被重新打包为 Uint8Array
,它应该用于在 GLSL 的纹理采样器对象中提供单通道 r
值。 GLSL代码更新如下:
uniform sampler2D u_map;
int get_cell(vec4 m){
ivec4 i = ivec4(mod(m,float(SIZE)));
float r = texelFetch(u_map, ivec2(i.x*SIZE3+i.y*SIZE2+i.z*SIZE+i.w, 0), 0).r;
return int(r);
}
这应该从底层缓冲区中获取单个有效通道值,就像我们使用数组时一样。
然而,在做了这个替换之后,我得到了垃圾。从 get_cell
返回的值似乎都是 1 或 0——其中 none 甚至可靠地对应于原始数据缓冲区中实际存在的 1 或 0 值。
我做错了什么?
如果纹理的内部格式是gl.R8
,那么在texelFetch
查找纹理时返回的值在[0.0, 1.0]范围内。
如果您想投注 [0, 255]:
范围内的数值,您必须将数值乘以 255.0
int get_cell(vec4 m){
ivec4 i = ivec4(mod(m,float(SIZE)));
float r = texelFetch(u_map, ivec2(i.x*SIZE3+i.y*SIZE2+i.z*SIZE+i.w, 0), 0).r;
return int(r * 255.0);
}
注意,R8
、RGB8
和RGBA8
等格式是无符号归一化定点格式。假定颜色值是 [0.0, 1.0] 范围内的浮点值,其中最小可能值 0 转换为 0.0,最大可能值 (2^8-1 == 255) 转换为 1.0。
您使用了 R8
格式,这是一种规范化的浮点格式,其值从 0.0 到 1.1。如果您想要 int
格式,请考虑使用 R32I
纹理内部格式,以 gl.RED_INTEGER
格式提供纹理数据,键入 gl.INT
并使用 Int32Array
,更改您的采样器到 isampler2D
并使用 int r = textureFetch(....).r
读取数据。
如果你想要一个整数结果,你还需要为结果制作一个整数纹理并将其附加到帧缓冲区以渲染整数结果。
我正在 WebGL2 中对大型 4D 输入数组进行一些 GPGPU 处理。最初,我只是将输入数组展平并将其作为统一的整数数组传入,使用 GLSL 中的自定义访问器函数将 4D 坐标转换为数组索引,如下所示:
const int SIZE = 5; // The largest dimension that works; if I can switch to textures, this will be passed in as a uniform value.
const int SIZE2 = SIZE*SIZE;
const int SIZE3 = SIZE*SIZE2;
const int SIZE4 = SIZE*SIZE3;
uniform int u_map[SIZE4];
int get_cell(vec4 m){
ivec4 i = ivec4(mod(m,float(SIZE)));
return u_map[i.x*SIZE3+i.y*SIZE2+i.z*SIZE+i.w];
}
在JavaScript这边,压平后的数据传入如下:
const map_loc = gl.getUniformLocation(program, "u_map");
gl.uniform1iv(map_loc, data);
其中 data
是一个包含 8 位值的 Int32Array
(因为这是映射到 GLSL 整数所需要的)。
可行,但它严重限制了我可以使用的输入大小。将维度大小提高到 6 会导致使用 1296 个统一槽,而只有 1024 个可用于此数据和其他控制输入。
所以,我想改用纹理来保存大量数据。所以,我将 JS 代码更新为:
const tex = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, data.length, 1, 0, gl.RED, gl.UNSIGNED_BYTE, data);
其中 data
已被重新打包为 Uint8Array
,它应该用于在 GLSL 的纹理采样器对象中提供单通道 r
值。 GLSL代码更新如下:
uniform sampler2D u_map;
int get_cell(vec4 m){
ivec4 i = ivec4(mod(m,float(SIZE)));
float r = texelFetch(u_map, ivec2(i.x*SIZE3+i.y*SIZE2+i.z*SIZE+i.w, 0), 0).r;
return int(r);
}
这应该从底层缓冲区中获取单个有效通道值,就像我们使用数组时一样。
然而,在做了这个替换之后,我得到了垃圾。从 get_cell
返回的值似乎都是 1 或 0——其中 none 甚至可靠地对应于原始数据缓冲区中实际存在的 1 或 0 值。
我做错了什么?
如果纹理的内部格式是gl.R8
,那么在texelFetch
查找纹理时返回的值在[0.0, 1.0]范围内。
如果您想投注 [0, 255]:
范围内的数值,您必须将数值乘以 255.0int get_cell(vec4 m){
ivec4 i = ivec4(mod(m,float(SIZE)));
float r = texelFetch(u_map, ivec2(i.x*SIZE3+i.y*SIZE2+i.z*SIZE+i.w, 0), 0).r;
return int(r * 255.0);
}
注意,R8
、RGB8
和RGBA8
等格式是无符号归一化定点格式。假定颜色值是 [0.0, 1.0] 范围内的浮点值,其中最小可能值 0 转换为 0.0,最大可能值 (2^8-1 == 255) 转换为 1.0。
您使用了 R8
格式,这是一种规范化的浮点格式,其值从 0.0 到 1.1。如果您想要 int
格式,请考虑使用 R32I
纹理内部格式,以 gl.RED_INTEGER
格式提供纹理数据,键入 gl.INT
并使用 Int32Array
,更改您的采样器到 isampler2D
并使用 int r = textureFetch(....).r
读取数据。
如果你想要一个整数结果,你还需要为结果制作一个整数纹理并将其附加到帧缓冲区以渲染整数结果。