在 WebGL 中采样整数纹理 returns 奇怪的值
Sampling integer texture in WebGL returns weird values
我正在尝试通过在片段着色器中应用 window leveling 在 WebGL2 中从 16 位数组缓冲区渲染灰度图像。我生成的纹理如下:
let typedArray = new Int16Array(data);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.R16I,
w, h,
0,
gl.RED_INTEGER,
gl.SHORT,
typedArray);
并尝试使用来自以下片段着色器的数据:
let fragmentShaderSource = `#version 300 es
precision highp float;
precision highp int;
precision highp isampler2D;
// our texture
uniform isampler2D u_image;
uniform highp float u_windowWidth;
uniform highp float u_windowCenter;
in vec2 v_texCoord;
out vec4 outColor;
void main() {
highp float f = float(texture(u_image, v_texCoord).r);
f = (f - (u_windowCenter - 0.5)) / max(u_windowWidth - 1.0, 1.0) + 0.5;
f = min(max(f, 0.0), 1.0);
outColor = vec4(vec3(f), 1.0);
}
`;
但这只会呈现黑屏。实际上,经过一些调试,我发现 texture(u_image, v_texCoord)
在所有像素的 rgb
中的值为零,而 a
(alpha)字段的值非常大(2^29 ~ 2^30)。我试过在着色器中更改精度,但结果是一样的。
为了缩小问题范围,我尝试了一种不同的方法,将 16 位整数拆分为 gl.RGBA4
,其中每个 RGBA 通道包含 4 位:
let typedArray = new Uint16Array(data);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA4,
w, h,
0,
gl.RGBA,
gl.UNSIGNED_SHORT_4_4_4_4,
typedArray);
并在片段着色器中将 RGBA 值组合回 16 位整数。
let fragmentShaderSource = `#version 300 es
precision highp float;
precision highp int;
precision highp sampler2D;
// our texture
uniform sampler2D u_image;
uniform highp float u_windowWidth;
uniform highp float u_windowCenter;
in vec2 v_texCoord;
out vec4 outColor;
void main() {
highp vec4 rgba_map = texture(u_image, v_texCoord);
// Combining rgba4 back into int16
highp f = rgba_map.r * 65536.0 + rgba_map.g * 4096.0 + rgba_map.b * 256.0 + rgba_map.a * 16.0;
// signed value
if (f > 32768.0) {
f = 65536.0 - f;
}
f = (f - (u_windowCenter - 0.5)) / max(u_windowWidth - 1.0, 1.0) + 0.5;
f = min(max(f, 0.0), 1.0);
outColor = vec4(vec3(f), 1.0);
}
`;
并且此版本很好地渲染了预期的图像,尽管由于转换导致结果有点嘈杂。我也试过其他一些格式,float类型的还好,integer类型的格式都不行。所以我认为程序的其他部分都很好。我想知道我的程序有什么问题。
您还没有真正发布足够的代码来进行调试,所以让我们做一些有用的东西吧。
function main() {
const gl = document.querySelector('canvas').getContext('webgl2');
if (!gl) {
return alert('need WebGL2');
}
const vs = `#version 300 es
void main() {
gl_PointSize = 300.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
const fs = `#version 300 es
precision highp float;
precision highp int;
precision highp isampler2D;
// our texture
uniform isampler2D u_image;
out vec4 color;
void main() {
ivec4 intColor = texture(u_image, gl_PointCoord.xy);
color = vec4(vec3(intColor.rrr) / 10000.0, 1);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(
gl.TEXTURE_2D,
0, // mip level
gl.R16I, // internal format
10, // width
1, // height
0, // border
gl.RED_INTEGER, // source format
gl.SHORT, // source type
new Int16Array([
1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000
]));
// can't filter integer textures
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.useProgram(program);
// no need to set any attributes or
// uniforms as we're not using attributes
// and uniforms default to zero so will use
// texture unit zero
gl.drawArrays(gl.POINTS, 0, 1);
console.log('max point size:', gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE)[1]);
}
main();
canvas {
border: 1px solid black;
background: red;
}
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
应该是这样的
但如果您的 GPU 最大点大小 < 300,可能会有红色边框
一些想法
您检查 JavaScript 控制台是否有错误?
您是否关闭了纹理过滤?
整数纹理无法过滤
你的纹理宽度是偶数吗?
如果不是,你可能需要设置 gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1)
尽管我希望你在这里得到一个错误,除非你的 Int16Array
大于 width * height
我正在尝试通过在片段着色器中应用 window leveling 在 WebGL2 中从 16 位数组缓冲区渲染灰度图像。我生成的纹理如下:
let typedArray = new Int16Array(data);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.R16I,
w, h,
0,
gl.RED_INTEGER,
gl.SHORT,
typedArray);
并尝试使用来自以下片段着色器的数据:
let fragmentShaderSource = `#version 300 es
precision highp float;
precision highp int;
precision highp isampler2D;
// our texture
uniform isampler2D u_image;
uniform highp float u_windowWidth;
uniform highp float u_windowCenter;
in vec2 v_texCoord;
out vec4 outColor;
void main() {
highp float f = float(texture(u_image, v_texCoord).r);
f = (f - (u_windowCenter - 0.5)) / max(u_windowWidth - 1.0, 1.0) + 0.5;
f = min(max(f, 0.0), 1.0);
outColor = vec4(vec3(f), 1.0);
}
`;
但这只会呈现黑屏。实际上,经过一些调试,我发现 texture(u_image, v_texCoord)
在所有像素的 rgb
中的值为零,而 a
(alpha)字段的值非常大(2^29 ~ 2^30)。我试过在着色器中更改精度,但结果是一样的。
为了缩小问题范围,我尝试了一种不同的方法,将 16 位整数拆分为 gl.RGBA4
,其中每个 RGBA 通道包含 4 位:
let typedArray = new Uint16Array(data);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA4,
w, h,
0,
gl.RGBA,
gl.UNSIGNED_SHORT_4_4_4_4,
typedArray);
并在片段着色器中将 RGBA 值组合回 16 位整数。
let fragmentShaderSource = `#version 300 es
precision highp float;
precision highp int;
precision highp sampler2D;
// our texture
uniform sampler2D u_image;
uniform highp float u_windowWidth;
uniform highp float u_windowCenter;
in vec2 v_texCoord;
out vec4 outColor;
void main() {
highp vec4 rgba_map = texture(u_image, v_texCoord);
// Combining rgba4 back into int16
highp f = rgba_map.r * 65536.0 + rgba_map.g * 4096.0 + rgba_map.b * 256.0 + rgba_map.a * 16.0;
// signed value
if (f > 32768.0) {
f = 65536.0 - f;
}
f = (f - (u_windowCenter - 0.5)) / max(u_windowWidth - 1.0, 1.0) + 0.5;
f = min(max(f, 0.0), 1.0);
outColor = vec4(vec3(f), 1.0);
}
`;
并且此版本很好地渲染了预期的图像,尽管由于转换导致结果有点嘈杂。我也试过其他一些格式,float类型的还好,integer类型的格式都不行。所以我认为程序的其他部分都很好。我想知道我的程序有什么问题。
您还没有真正发布足够的代码来进行调试,所以让我们做一些有用的东西吧。
function main() {
const gl = document.querySelector('canvas').getContext('webgl2');
if (!gl) {
return alert('need WebGL2');
}
const vs = `#version 300 es
void main() {
gl_PointSize = 300.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
const fs = `#version 300 es
precision highp float;
precision highp int;
precision highp isampler2D;
// our texture
uniform isampler2D u_image;
out vec4 color;
void main() {
ivec4 intColor = texture(u_image, gl_PointCoord.xy);
color = vec4(vec3(intColor.rrr) / 10000.0, 1);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(
gl.TEXTURE_2D,
0, // mip level
gl.R16I, // internal format
10, // width
1, // height
0, // border
gl.RED_INTEGER, // source format
gl.SHORT, // source type
new Int16Array([
1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000
]));
// can't filter integer textures
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.useProgram(program);
// no need to set any attributes or
// uniforms as we're not using attributes
// and uniforms default to zero so will use
// texture unit zero
gl.drawArrays(gl.POINTS, 0, 1);
console.log('max point size:', gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE)[1]);
}
main();
canvas {
border: 1px solid black;
background: red;
}
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
应该是这样的
但如果您的 GPU 最大点大小 < 300,可能会有红色边框
一些想法
您检查 JavaScript 控制台是否有错误?
您是否关闭了纹理过滤?
整数纹理无法过滤
你的纹理宽度是偶数吗?
如果不是,你可能需要设置
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1)
尽管我希望你在这里得到一个错误,除非你的Int16Array
大于 width * height