如何在 WebGL 1.0 中将纹理用作屏幕大小的位图?像素坐标映射到 gl_FragCoord?
How to use a texture as a screen-sized bitmap in WebGL 1.0? Pixel coordinates mapped to gl_FragCoord?
这是之前 的后续内容,我在其中询问了如何在 WebGL 1.0 中将许多四边形和变化的线条组合在一起。我正在尝试一种替代方法,我想执行以下操作:
使用整个 window 大小的纹理。颜色值将充当深度索引。使用 gl_FragCoord.xy
在每个片段处对纹理进行索引。根据我的理解,我不需要任何属性或 UV 坐标。我只希望 0, 0 位于左上角,右下角位于 (width, height)。 (这可能是一个 XY 问题,但我仍然想至少尝试这种方法以获得学习收益。)
我的动机:
以防万一这是一个 XY 问题,一个切线来澄清我希望做的事情:
我需要用线交错四边形层。我希望应用 Bresenham 算法在纹理上绘制线条,我使用颜色来标记线条在特定像素坐标处的图层。这样,我可以为四边形分配索引并与烘焙到纹理中的线索引进行比较。如果某个片段的线条索引较大,则应绘制线条颜色。否则四边形在顶部(也许一次做几帧太慢了...... Bresenham 为每一行写入纹理,将数据重新馈送到 GPU。WebGL 2.0 可能有一些额外的深度缓冲区有助于解决此问题的功能,但我必须使用 1.0。)。
问题:
...是我的尝试产生了奇怪的结果。 (注意:我在视频剪辑的动态纹理上使用 MDN 的 tutorial 作为参考)。
为了查看坐标是否正确,我将颜色设置为 4 组随机值 (RGBA),其中第 4 组的不透明度始终为 255。
我看到的是我的整个形状都是一种颜色,这表明纹理实际上是从 0 到 1,并且每个 gl_FragCoord.xy 选择一个 "pixel" 处的一种颜色(或者我认为是一个像素)。这可能是错误的,但目前我没有其他解释。到目前为止的搜索没有显示将纹理用于基本位图的示例。
我将 post 一些我的代码片段以备不时之需。我正在使用来自 gl-matrix 的正交投影矩阵。
纹理创建:
function createScreenDepthTexture(gl, width, height, scale) { // scale is 1 for now
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// I think that I need to flip y since 0, 0 is in the bottom-left corner by default
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
const level = 0;
const internalFormat = gl.RGBA;
const w = width;
const h = height;
const border = 0;
const srcFormat = gl.RGBA;
const srcType = gl.UNSIGNED_BYTE;
const size = width * height * scale * scale * Uint8Array.BYTES_PER_ELEMENT * 4;
const src = new Uint8Array(size);
// from MDN
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; //The maximum is inclusive and the minimum is inclusive
}
for (let i = 0; i < size; i += 4) {
// random colors, but I see only one color in the end
src[i ] = getRandomIntInclusive(0, 255),
src[i + 1] = getRandomIntInclusive(0, 255),
src[i + 2] = getRandomIntInclusive(0, 255),
src[i + 3] = 255;
}
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, w * scale, h * scale, border, srcFormat, srcType, src);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
return {texture : texture, src : src, w : width * scale, h : w * scale};
}
纹理设置:
...
const textureRecord = createScreenDepthTexture(G.gl, 640, 480, SCALE);
// G is a wrapper containing the gl context and other data
G.gl.activeTexture(gl.TEXTURE0);
G.gl.bindTexture(gl.TEXTURE_2D, textureRecord.texture);
G.gl.uniform1i(G.gl.getUniformLocation(G.program, "u_sampler"), 0);
...
VERTEX AND FRAGMENT SHADERS(本次测试中未使用的几个变量,因此我排除了它们):
// 注意我的 JavaScript 投影矩阵是:
mat4.ortho(matProjection, 0.0, G.canvas.width, G.canvas.height, 0.0, -1000.0, 1000.0);
// VERTEX
precision highp float;
attribute vec3 a_position;
uniform mat4 u_matrix;
void main() {
v_color = u_color;
gl_Position = u_matrix * vec4(a_position, 1.0);
}
// FRAGMENT
precision highp float;
uniform sampler2D u_sampler;
void main() {
// I thought that this would set the color of this specific pixel
// to be the color of the texture at this specific coordinate,
// but I think that the coordinate systems are off if I see only one color,
// is the sampler set to 0-1? How would I change this correctly?
gl_FragColor = texture2D(u_sampler, gl_FragCoord.xy);
}
我还缺少什么?另外,请随时告诉我这个实验是否真的出于性能方面的考虑绝对不是可行的方法。我会重复地连续几帧重写纹理。提前谢谢你。
// I thought that this would set the color of this specific pixel
// to be the color of the texture at this specific coordinate,
// but I think that the coordinate systems are off if I see only one color,
// is the sampler set to 0-1? How would I change this correctly?
gl_FragColor = texture2D(u_sampler, gl_FragCoord.xy);
是的texture2D
采用归一化纹理坐标,在WebGL 1中无法直接访问texel中的纹理space(在WebGL 2中你会使用texelFetch),所以你需要通过将 gl_FragCoord 除以纹理的宽度和高度(在本例中为 screen/window)来计算归一化纹理坐标 (UV)。
正在回答您评论中的问题:
could there be rounding errors
并非如此,因为所有数学运算都是 32 位浮点数,对此具有足够的精度,您可能希望通过将归一化坐标偏移半个纹素来确保对纹素中心进行采样。
if I were ever to move to WebGL 2, how would this be done there?
使用 texelFetch
而不是 texture2d
any thoughts on the efficiency of this method?
好吧,我认为它会很慢,如果我理解正确的话,流程将是这样的:
10 draw quad with shader reading from your screen texture
20 read back the result
30 update the texture
40 goto 10
如果是这样的话,它就是性能的毒药......
所以是的,我会尝试使线条起作用,或者在其上绘制带有线条像素着色器的条带(使用硬件 z 缓冲区、像素着色器来建立漂亮的、恒定宽度的线条)。
这是之前
使用整个 window 大小的纹理。颜色值将充当深度索引。使用 gl_FragCoord.xy
在每个片段处对纹理进行索引。根据我的理解,我不需要任何属性或 UV 坐标。我只希望 0, 0 位于左上角,右下角位于 (width, height)。 (这可能是一个 XY 问题,但我仍然想至少尝试这种方法以获得学习收益。)
我的动机: 以防万一这是一个 XY 问题,一个切线来澄清我希望做的事情: 我需要用线交错四边形层。我希望应用 Bresenham 算法在纹理上绘制线条,我使用颜色来标记线条在特定像素坐标处的图层。这样,我可以为四边形分配索引并与烘焙到纹理中的线索引进行比较。如果某个片段的线条索引较大,则应绘制线条颜色。否则四边形在顶部(也许一次做几帧太慢了...... Bresenham 为每一行写入纹理,将数据重新馈送到 GPU。WebGL 2.0 可能有一些额外的深度缓冲区有助于解决此问题的功能,但我必须使用 1.0。)。
问题: ...是我的尝试产生了奇怪的结果。 (注意:我在视频剪辑的动态纹理上使用 MDN 的 tutorial 作为参考)。
为了查看坐标是否正确,我将颜色设置为 4 组随机值 (RGBA),其中第 4 组的不透明度始终为 255。 我看到的是我的整个形状都是一种颜色,这表明纹理实际上是从 0 到 1,并且每个 gl_FragCoord.xy 选择一个 "pixel" 处的一种颜色(或者我认为是一个像素)。这可能是错误的,但目前我没有其他解释。到目前为止的搜索没有显示将纹理用于基本位图的示例。
我将 post 一些我的代码片段以备不时之需。我正在使用来自 gl-matrix 的正交投影矩阵。
纹理创建:
function createScreenDepthTexture(gl, width, height, scale) { // scale is 1 for now
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// I think that I need to flip y since 0, 0 is in the bottom-left corner by default
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
const level = 0;
const internalFormat = gl.RGBA;
const w = width;
const h = height;
const border = 0;
const srcFormat = gl.RGBA;
const srcType = gl.UNSIGNED_BYTE;
const size = width * height * scale * scale * Uint8Array.BYTES_PER_ELEMENT * 4;
const src = new Uint8Array(size);
// from MDN
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; //The maximum is inclusive and the minimum is inclusive
}
for (let i = 0; i < size; i += 4) {
// random colors, but I see only one color in the end
src[i ] = getRandomIntInclusive(0, 255),
src[i + 1] = getRandomIntInclusive(0, 255),
src[i + 2] = getRandomIntInclusive(0, 255),
src[i + 3] = 255;
}
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, w * scale, h * scale, border, srcFormat, srcType, src);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
return {texture : texture, src : src, w : width * scale, h : w * scale};
}
纹理设置:
...
const textureRecord = createScreenDepthTexture(G.gl, 640, 480, SCALE);
// G is a wrapper containing the gl context and other data
G.gl.activeTexture(gl.TEXTURE0);
G.gl.bindTexture(gl.TEXTURE_2D, textureRecord.texture);
G.gl.uniform1i(G.gl.getUniformLocation(G.program, "u_sampler"), 0);
...
VERTEX AND FRAGMENT SHADERS(本次测试中未使用的几个变量,因此我排除了它们):
// 注意我的 JavaScript 投影矩阵是:
mat4.ortho(matProjection, 0.0, G.canvas.width, G.canvas.height, 0.0, -1000.0, 1000.0);
// VERTEX
precision highp float;
attribute vec3 a_position;
uniform mat4 u_matrix;
void main() {
v_color = u_color;
gl_Position = u_matrix * vec4(a_position, 1.0);
}
// FRAGMENT
precision highp float;
uniform sampler2D u_sampler;
void main() {
// I thought that this would set the color of this specific pixel
// to be the color of the texture at this specific coordinate,
// but I think that the coordinate systems are off if I see only one color,
// is the sampler set to 0-1? How would I change this correctly?
gl_FragColor = texture2D(u_sampler, gl_FragCoord.xy);
}
我还缺少什么?另外,请随时告诉我这个实验是否真的出于性能方面的考虑绝对不是可行的方法。我会重复地连续几帧重写纹理。提前谢谢你。
// I thought that this would set the color of this specific pixel
// to be the color of the texture at this specific coordinate,
// but I think that the coordinate systems are off if I see only one color,
// is the sampler set to 0-1? How would I change this correctly?
gl_FragColor = texture2D(u_sampler, gl_FragCoord.xy);
是的texture2D
采用归一化纹理坐标,在WebGL 1中无法直接访问texel中的纹理space(在WebGL 2中你会使用texelFetch),所以你需要通过将 gl_FragCoord 除以纹理的宽度和高度(在本例中为 screen/window)来计算归一化纹理坐标 (UV)。
正在回答您评论中的问题:
could there be rounding errors
并非如此,因为所有数学运算都是 32 位浮点数,对此具有足够的精度,您可能希望通过将归一化坐标偏移半个纹素来确保对纹素中心进行采样。
if I were ever to move to WebGL 2, how would this be done there?
使用 texelFetch
而不是 texture2d
any thoughts on the efficiency of this method?
好吧,我认为它会很慢,如果我理解正确的话,流程将是这样的:
10 draw quad with shader reading from your screen texture
20 read back the result
30 update the texture
40 goto 10
如果是这样的话,它就是性能的毒药...... 所以是的,我会尝试使线条起作用,或者在其上绘制带有线条像素着色器的条带(使用硬件 z 缓冲区、像素着色器来建立漂亮的、恒定宽度的线条)。