Я хочу использовать пиксельные координаты в моем шейдере
С https://webgl2fundamentals.org/webgl/lessons/webgl-image-processing.html
WebGL2 добавляет возможность читать текстуру, используя также пиксельные координаты. Какой путь лучше, зависит от вас. Я чувствую, что чаще использовать текстурные координаты, чем пиксельные.
Нигде это не упомянуто, кроме как передать форму с размерами текстуры в пикселях и вычислить оттуда, есть ли способ получить доступ к этим координатам пикселей без вычисления, как это предполагается здесь?
1 ответ
Вы можете читать отдельные пиксели / тексели из текстуры в WebGL2 с помощью texelFetch
vec4 color = texelFetch(someUniformSampler, ivec2(pixelX, pixelY), intMipLevel);
Например, вычислить средний цвет текстуры, читая каждый пиксель
const vs = `#version 300 es
void main() {
gl_Position = vec4(0, 0, 0, 1);
gl_PointSize = 1.0;
}
`;
const fs = `#version 300 es
precision highp float;
uniform sampler2D tex;
out vec4 outColor;
void main() {
int level = 0;
ivec2 size = textureSize(tex, level);
vec4 color = vec4(0);
for (int y = 0; y < size.y; ++y) {
for (int x = 0; x < size.x; ++x) {
color += texelFetch(tex, ivec2(x, y), level);
}
}
outColor = color / float(size.x * size.y);
}
`;
function main() {
const gl = document.createElement('canvas').getContext('webgl2');
if (!gl) {
return alert('need webgl2');
}
const prg = twgl.createProgram(gl, [vs, fs]);
gl.canvas.width = 1;
gl.canvas.height = 1;
gl.viewport(0, 0, 1, 1);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
// so we don't need mips
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
// so we can pass a non multiple of 4 bytes
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
const values = new Uint8Array([10, 255, 13, 70, 56, 45, 89]);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, values.length, 1, 0, gl.RED, gl.UNSIGNED_BYTE, values);
gl.useProgram(prg);
gl.drawArrays(gl.POINTS, 0, 1);
const gpuAverage = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, gpuAverage);
const jsAverage = values.reduce((s, v) => s + v) / values.length;
console.log('gpuAverage:', gpuAverage[0]);
console.log('jsAverage:', jsAverage);
}
main();
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
Примечания: так как холст RGBA8 может получить только целочисленный результат. Может измениться на некоторый формат с плавающей точкой, но это усложнит пример, который не о рендеринге. texelFetch
,
Конечно, просто изменив данные с R8 на RGBA8, мы можем сделать 4 массива как один раз, если мы чередуем значения
const vs = `#version 300 es
void main() {
gl_Position = vec4(0, 0, 0, 1);
gl_PointSize = 1.0;
}
`;
const fs = `#version 300 es
precision highp float;
uniform sampler2D tex;
out vec4 outColor;
void main() {
int level = 0;
ivec2 size = textureSize(tex, level);
vec4 color = vec4(0);
for (int y = 0; y < size.y; ++y) {
for (int x = 0; x < size.x; ++x) {
color += texelFetch(tex, ivec2(x, y), level);
}
}
outColor = color / float(size.x * size.y);
}
`;
function main() {
const gl = document.createElement('canvas').getContext('webgl2');
if (!gl) {
return alert('need webgl2');
}
const prg = twgl.createProgram(gl, [vs, fs]);
gl.canvas.width = 1;
gl.canvas.height = 1;
gl.viewport(0, 0, 1, 1);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
// so we don't need mips
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
// so we can pass a non multiple of 4 bytes
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
const values = new Uint8Array([
10, 100, 200, 1,
12, 150, 231, 9,
50, 129, 290, 3,
45, 141, 300, 2,
12, 123, 212, 4,
]);
const width = 1;
const height = values.length / 4;
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, values);
gl.useProgram(prg);
gl.drawArrays(gl.POINTS, 0, 1);
const gpuAverages = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, gpuAverages);
let jsAverages = [0, 0, 0, 0];
for (let i = 0; i < height; ++i) {
for (let j = 0; j < 4; ++j) {
jsAverages[j] += values[i * 4 + j];
}
}
jsAverages = jsAverages.map(v => v / height);
console.log('gpuAverage:', gpuAverages.join(', '));
console.log('jsAverage:', jsAverages.join(', '));
}
main();
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
Чтобы сделать больше, нужно найти способ упорядочить данные и использовать вход для фрагментного шейдера, чтобы выяснить, где находятся данные. Например, мы снова чередуем данные, 5 массивов, поэтому данные идут 0,1,2,3,4,0,1,2,3,4,0,1,2,3,4.
Вернемся к R8 и сделаем 5 отдельных массивов. Нам нужно нарисовать 5 пикселей. Мы можем сказать, какой пиксель рисуется, посмотрев на gl_FragCoord
, Мы можем использовать это, чтобы сместить, на какие пиксели мы смотрим и сколько пропустить.
const vs = `#version 300 es
void main() {
gl_Position = vec4(0, 0, 0, 1);
gl_PointSize = 100.0;
}
`;
const fs = `#version 300 es
precision highp float;
uniform sampler2D tex;
uniform int numArrays;
out vec4 outColor;
void main() {
int level = 0;
int start = int(gl_FragCoord.x);
ivec2 size = textureSize(tex, level);
vec4 color = vec4(0);
for (int y = 0; y < size.y; ++y) {
for (int x = start; x < size.x; x += numArrays) {
color += texelFetch(tex, ivec2(x, y), level);
}
}
outColor = color / float(size.x / numArrays * size.y);
}
`;
function main() {
const gl = document.createElement('canvas').getContext('webgl2');
if (!gl) {
return alert('need webgl2');
}
const prg = twgl.createProgram(gl, [vs, fs]);
const numArraysLoc = gl.getUniformLocation(prg, "numArrays");
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
// so we don't need mips
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
// so we can pass a non multiple of 4 bytes
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
const numArrays = 5;
const values = new Uint8Array([
10, 100, 200, 1, 70,
12, 150, 231, 9, 71,
50, 129, 290, 3, 90,
45, 141, 300, 2, 80,
12, 123, 212, 4, 75,
]);
const width = values.length;
const height = 1;
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, width, height, 0, gl.RED, gl.UNSIGNED_BYTE, values);
gl.canvas.width = numArrays;
gl.canvas.height = 1;
gl.viewport(0, 0, numArrays, 1);
gl.useProgram(prg);
gl.uniform1i(numArraysLoc, numArrays);
gl.drawArrays(gl.POINTS, 0, 1);
const gpuData = new Uint8Array(4 * numArrays);
gl.readPixels(0, 0, numArrays, 1, gl.RGBA, gl.UNSIGNED_BYTE, gpuData);
const gpuAverages = [];
for (let i = 0; i < numArrays; ++i) {
gpuAverages.push(gpuData[i * 4]); // because we're only using the RED channel
}
const jsAverages = (new Array(numArrays)).fill(0);
const numValues = (width / numArrays) * height;
for (let i = 0; i < width / numArrays; ++i) {
for (let j = 0; j < numArrays; ++j) {
jsAverages[j] += values[i * numArrays + j] / numValues;
}
}
console.log('gpuAverage:', gpuAverages.join(', '));
console.log('jsAverage:', jsAverages.join(', '));
}
main();
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>