在片段着色器中接收非规范化输出纹理坐标
Receiving denormalized output texture coordinates in Frag shader
更新
请参阅下面我的问题末尾的基本原理
使用 WebGL2
我可以通过其非规范化坐标访问纹素(抱歉,这不是正确的行话)。这意味着我不必像在 texture2D()
中那样将它们缩小到 0-1。
然而,片段着色器的输入仍然是规范化值中的 vec2/3
。
有没有办法在顶点和片段着色器中声明 in/out 变量,这样我就不必缩放坐标了?
顶点着色器中的某处:
...
out vec2 TextureCoordinates;
片段着色器中的某处:
...
in vec2 TextureCoordinates;
我希望 TextureCoordinates
为 ivec2
并且已经缩放。
这个问题和我关于 webgl 的所有其他问题都与使用 WebGL 的一般计算有关。我们正在尝试使用 WebGL 进行张量(多维矩阵)运算。
我们以几种方式将数据映射到纹理。我们遵循的最简单的方法是——假设我们可以将我们的数据作为一个平面数组来访问——沿着纹理的宽度布置它,并沿着纹理的高度向上布置,直到我们完成。
由于我们的思维、逻辑和计算都基于 tensor/matrix 索引——在片段着色器内部——我们必须将 X-Y 纹理坐标映射回 to/from 到索引。这里的中间步骤是计算纹素给定位置的偏移量。然后根据该偏移量,我们可以根据其步幅计算矩阵索引。
在 webgl 1 中为非常大的纹理计算偏移量似乎比使用整数坐标的 webgl2 花费的时间长得多。见下文:
WebGL 1 偏移计算
int coordsToOffset(vec2 coords, int width, int height) {
float s = coords.s * float(width);
float t = coords.t * float(height);
int offset = int(t) * width + int(s);
return offset;
}
vec2 offsetToCoords(int offset, int width, int height) {
int t = offset / width;
int s = offset - t*width;
vec2 coords = (vec2(s,t) + vec2(0.5,0.5)) / vec2(width, height);
return coords;
}
存在int坐标的WebGL 2偏移计算
int coordsToOffset(ivec2 coords, int width) {
return coords.t * width + coords.s;
}
ivec2 offsetToCoords(int offset, int width) {
int t = offset / width;
int s = offset - t*width;
return ivec2(s,t);
}
很明显,对于一系列大型纹理操作,我们仅在 offset/coords 计算中就节省了数十万次操作。
不清楚你为什么要做你想做的事。最好问 "I'm trying to draw an image/implement post processing glow/do ray tracing/... and to do that I want to use un-normalized texture coordinates because " 这样的问题,然后我们可以告诉您您的解决方案是否有效以及如何解决。
在任何情况下,都支持传递 int
或 unsigned int
或 ivec2/3/4
或 uvec2/3/4
作为变量,但 不支持插值 .您必须将它们声明为 flat
.
不过,您可以将未规范化的值作为 float
或 vec2/3/4
传递,并在片段着色器中转换为 int
、ivec2/3/4
。
另一个问题是您将无法使用 texelFetch
进行采样,该函数采用纹素坐标而不是归一化纹理坐标。它只是 returns 单个像素的精确值。它不像普通的 texture
函数那样支持过滤。
示例:
function main() {
const gl = document.querySelector('canvas').getContext('webgl2');
if (!gl) {
return alert("need webgl2");
}
const vs = `
#version 300 es
in vec4 position;
in ivec2 texelcoord;
out vec2 v_texcoord;
void main() {
v_texcoord = vec2(texelcoord);
gl_Position = position;
}
`;
const fs = `
#version 300 es
precision mediump float;
in vec2 v_texcoord;
out vec4 outColor;
uniform sampler2D tex;
void main() {
outColor = texelFetch(tex, ivec2(v_texcoord), 0);
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// create buffers via gl.createBuffer, gl.bindBuffer, gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: {
numComponents: 2,
data: [
-.5, -.5,
.5, -.5,
0, .5,
],
},
texelcoord: {
numComponents: 2,
data: new Int32Array([
0, 0,
15, 0,
8, 15,
]),
}
});
// make a 16x16 texture
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = 16;
ctx.canvas.height = 16;
for (let i = 23; i > 0; --i) {
ctx.fillStyle = `hsl(${i / 23 * 360 | 0}, 100%, ${i % 2 ? 25 : 75}%)`;
ctx.beginPath();
ctx.arc(8, 15, i, 0, Math.PI * 2, false);
ctx.fill();
}
const tex = twgl.createTexture(gl, { src: ctx.canvas });
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// no need to set uniforms since they default to 0
// and only one texture which is already on texture unit 0
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
main();
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
所以在回答你更新的问题时,你还不清楚你想做什么。为什么要将变量传递给片段着色器?你不能在片段着色器本身中做任何你想做的数学吗?
示例:
uniform sampler2D tex;
out float result;
// some all the values in the texture
vec4 sum4 = vec4(0);
ivec2 texDim = textureSize(tex, 0);
for (int y = 0; y < texDim.y; ++y) {
for (int x = 0; x < texDim.x; ++x) {
sum4 += texelFetch(tex, ivec2(x, y), 0);
}
}
result = sum4.x + sum4.y + sum4.z + sum4.w;
示例 2
uniform isampler2D indices;
uniform sampler2D data;
out float result;
// some only values in data pointed to by indices
vec4 sum4 = vec4(0);
ivec2 texDim = textureSize(indices, 0);
for (int y = 0; y < texDim.y; ++y) {
for (int x = 0; x < texDim.x; ++x) {
ivec2 index = texelFetch(indices, ivec2(x, y), 0).xy;
sum4 += texelFetch(tex, index, 0);
}
}
result = sum4.x + sum4.y + sum4.z + sum4.w;
请注意,我也不是 GPGPU 方面的专家,但我预感上面的代码不是最快的方法,因为我相信并行化是基于输出发生的。上面的代码只有 1 个输出,所以没有并行化?更改起来很容易,因此它将块 ID、图块 ID、区域 ID 作为输入并仅计算该区域的总和。然后你会用每个块的总和写出一个更大的纹理,最后对块总和求和。
此外,依赖和不均匀的纹理读取是一个已知的性能问题。第一个示例按顺序读取纹理。这是缓存友好的。第二个示例以随机顺序(由索引指定)读取纹理,这对缓存不友好。
更新
请参阅下面我的问题末尾的基本原理
使用 WebGL2
我可以通过其非规范化坐标访问纹素(抱歉,这不是正确的行话)。这意味着我不必像在 texture2D()
中那样将它们缩小到 0-1。
然而,片段着色器的输入仍然是规范化值中的 vec2/3
。
有没有办法在顶点和片段着色器中声明 in/out 变量,这样我就不必缩放坐标了?
顶点着色器中的某处:
...
out vec2 TextureCoordinates;
片段着色器中的某处:
...
in vec2 TextureCoordinates;
我希望 TextureCoordinates
为 ivec2
并且已经缩放。
这个问题和我关于 webgl 的所有其他问题都与使用 WebGL 的一般计算有关。我们正在尝试使用 WebGL 进行张量(多维矩阵)运算。
我们以几种方式将数据映射到纹理。我们遵循的最简单的方法是——假设我们可以将我们的数据作为一个平面数组来访问——沿着纹理的宽度布置它,并沿着纹理的高度向上布置,直到我们完成。
由于我们的思维、逻辑和计算都基于 tensor/matrix 索引——在片段着色器内部——我们必须将 X-Y 纹理坐标映射回 to/from 到索引。这里的中间步骤是计算纹素给定位置的偏移量。然后根据该偏移量,我们可以根据其步幅计算矩阵索引。
在 webgl 1 中为非常大的纹理计算偏移量似乎比使用整数坐标的 webgl2 花费的时间长得多。见下文:
WebGL 1 偏移计算
int coordsToOffset(vec2 coords, int width, int height) {
float s = coords.s * float(width);
float t = coords.t * float(height);
int offset = int(t) * width + int(s);
return offset;
}
vec2 offsetToCoords(int offset, int width, int height) {
int t = offset / width;
int s = offset - t*width;
vec2 coords = (vec2(s,t) + vec2(0.5,0.5)) / vec2(width, height);
return coords;
}
存在int坐标的WebGL 2偏移计算
int coordsToOffset(ivec2 coords, int width) {
return coords.t * width + coords.s;
}
ivec2 offsetToCoords(int offset, int width) {
int t = offset / width;
int s = offset - t*width;
return ivec2(s,t);
}
很明显,对于一系列大型纹理操作,我们仅在 offset/coords 计算中就节省了数十万次操作。
不清楚你为什么要做你想做的事。最好问 "I'm trying to draw an image/implement post processing glow/do ray tracing/... and to do that I want to use un-normalized texture coordinates because " 这样的问题,然后我们可以告诉您您的解决方案是否有效以及如何解决。
在任何情况下,都支持传递 int
或 unsigned int
或 ivec2/3/4
或 uvec2/3/4
作为变量,但 不支持插值 .您必须将它们声明为 flat
.
不过,您可以将未规范化的值作为 float
或 vec2/3/4
传递,并在片段着色器中转换为 int
、ivec2/3/4
。
另一个问题是您将无法使用 texelFetch
进行采样,该函数采用纹素坐标而不是归一化纹理坐标。它只是 returns 单个像素的精确值。它不像普通的 texture
函数那样支持过滤。
示例:
function main() {
const gl = document.querySelector('canvas').getContext('webgl2');
if (!gl) {
return alert("need webgl2");
}
const vs = `
#version 300 es
in vec4 position;
in ivec2 texelcoord;
out vec2 v_texcoord;
void main() {
v_texcoord = vec2(texelcoord);
gl_Position = position;
}
`;
const fs = `
#version 300 es
precision mediump float;
in vec2 v_texcoord;
out vec4 outColor;
uniform sampler2D tex;
void main() {
outColor = texelFetch(tex, ivec2(v_texcoord), 0);
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// create buffers via gl.createBuffer, gl.bindBuffer, gl.bufferData)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: {
numComponents: 2,
data: [
-.5, -.5,
.5, -.5,
0, .5,
],
},
texelcoord: {
numComponents: 2,
data: new Int32Array([
0, 0,
15, 0,
8, 15,
]),
}
});
// make a 16x16 texture
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = 16;
ctx.canvas.height = 16;
for (let i = 23; i > 0; --i) {
ctx.fillStyle = `hsl(${i / 23 * 360 | 0}, 100%, ${i % 2 ? 25 : 75}%)`;
ctx.beginPath();
ctx.arc(8, 15, i, 0, Math.PI * 2, false);
ctx.fill();
}
const tex = twgl.createTexture(gl, { src: ctx.canvas });
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// no need to set uniforms since they default to 0
// and only one texture which is already on texture unit 0
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
main();
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
所以在回答你更新的问题时,你还不清楚你想做什么。为什么要将变量传递给片段着色器?你不能在片段着色器本身中做任何你想做的数学吗?
示例:
uniform sampler2D tex;
out float result;
// some all the values in the texture
vec4 sum4 = vec4(0);
ivec2 texDim = textureSize(tex, 0);
for (int y = 0; y < texDim.y; ++y) {
for (int x = 0; x < texDim.x; ++x) {
sum4 += texelFetch(tex, ivec2(x, y), 0);
}
}
result = sum4.x + sum4.y + sum4.z + sum4.w;
示例 2
uniform isampler2D indices;
uniform sampler2D data;
out float result;
// some only values in data pointed to by indices
vec4 sum4 = vec4(0);
ivec2 texDim = textureSize(indices, 0);
for (int y = 0; y < texDim.y; ++y) {
for (int x = 0; x < texDim.x; ++x) {
ivec2 index = texelFetch(indices, ivec2(x, y), 0).xy;
sum4 += texelFetch(tex, index, 0);
}
}
result = sum4.x + sum4.y + sum4.z + sum4.w;
请注意,我也不是 GPGPU 方面的专家,但我预感上面的代码不是最快的方法,因为我相信并行化是基于输出发生的。上面的代码只有 1 个输出,所以没有并行化?更改起来很容易,因此它将块 ID、图块 ID、区域 ID 作为输入并仅计算该区域的总和。然后你会用每个块的总和写出一个更大的纹理,最后对块总和求和。
此外,依赖和不均匀的纹理读取是一个已知的性能问题。第一个示例按顺序读取纹理。这是缓存友好的。第二个示例以随机顺序(由索引指定)读取纹理,这对缓存不友好。