如何比较 JavaScript / WebGL2 中的 2 个纹理?
How to compare 2 textures in JavaScript / WebGL2?
我正在为图像处理算法编写片段着色器。着色器将在循环中的两个帧缓冲区之间 运行 多次(乒乓球)。在某些时候,当输入和输出纹理相同时,我需要停止循环。
我打算做的是Canny边缘检测算法的最后一步,"hysterezis edge tracking"。我想制作实时 GPU/WebGL2 版本的 Canny 算法并将其上传到网站。
最后一步如下:
给定包含 "strong" 边缘像素 (1.0) 和 "weak" 边缘像素 (0.5)
的双阈值图像
找到所有与强像素相连的弱像素链并标记它们"strong"
保留所有 "strong" 像素并丢弃所有剩余的 "weak" 像素。
这可以在片段着色器中实现 运行循环多次。如果在其 8 像素邻域中至少有一个强像素,则当前 "weak" 像素被标记为 "strong"。在每次迭代中,我们应该有更多的强像素和更少的弱像素。最后,应该只保留孤立的弱像素链。这是片段着色器成为直通着色器的点,应该检测到以停止循环。
2019 年 9 月更新:我在此处 http://www.oldrinb.info/dip/canny/ 上传了 GPU Canny 边缘检测器。它适用于支持 WebGL2 的浏览器,以及支持 WebGL1 和 'WEBGL_draw_buffers' 扩展的浏览器。我很快也会把源代码放到github。
我不是 100% 确定你在问什么。您要求在 CPU 上进行比较。您可以通过将纹理附加到帧缓冲区然后调用 gl.readPixels
来读取纹理的内容。然后您可以比较所有像素。注意:并非所有纹理格式都可以附加到帧缓冲区,但假设您使用的是可以的格式。您已经为 ping-ponging 将纹理附加到帧缓冲区,那么您还想要什么?
就像我在 GPU 的评论中写的那样,你可以编写一个着色器来比较 2 个纹理
#version 300 es
precision highp float;
uniform sampler2D tex1;
uniform sampler2D tex2;
out vec4 outColor;
void main() {
ivec2 size = textureSize(tex1, 0); // size of mip 0
float len = 0.0;
for (int y = 0; y < size.y; ++y) {
for (int x = 0; x < size.x; ++x) {
vec4 color1 = texelFetch(tex1, ivec2(x, y), 0);
vec4 color2 = texelFetch(tex2, ivec2(x, y), 0);
vec4 diff = color1 - color2;
len = length(diff);
if (len > 0.0) break;
}
if (len > 0.0) break;
}
outColor = mix(vec4(0), vec4(1), step(len, 0.0));
}
现在只需绘制 1 个像素并使用 readPixels 读取它。如果它为 0,则纹理相同。如果不是,它们是不同的。
代码假定纹理大小相同,但当然,如果它们大小不同,那么我们已经知道它们不可能相同。
// make 3 canvaes as sources for textures
const canvases = ['A', 'B', 'B'].map((msg) => {
const canvas = document.createElement('canvas');
canvas.width = 128;
canvas.height = 128;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 128, 128);
ctx.font = '80px monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'yellow';
ctx.fillText(msg, 64, 64);
document.body.appendChild(canvas);
return canvas;
});
const gl = document.createElement('canvas').getContext('webgl2');
if (!gl) { alert('need webgl2'); }
const vs = `#version 300 es
void main() {
gl_PointSize = 1.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
const fs = `#version 300 es
precision highp float;
uniform sampler2D tex1;
uniform sampler2D tex2;
out vec4 outColor;
void main() {
ivec2 size = textureSize(tex1, 0); // size of mip 0
float len = 0.0;
for (int y = 0; y < size.y; ++y) {
for (int x = 0; x < size.x; ++x) {
vec4 color1 = texelFetch(tex1, ivec2(x, y), 0);
vec4 color2 = texelFetch(tex2, ivec2(x, y), 0);
vec4 diff = color1 - color2;
len = length(diff);
if (len > 0.0) break;
}
if (len > 0.0) break;
}
outColor = mix(vec4(0), vec4(1), step(len, 0.0));
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const textures = canvases.map((canvas) => {
// gl.createTexture, gl.bindTexture, gl.texImage, etc.
return twgl.createTexture(gl, {src: canvas});
});
compareTextures(0, 1);
compareTextures(1, 2);
function compareTextures(ndx1, ndx2) {
gl.useProgram(programInfo.program);
// gl.activeTexture, gl.bindTexture, gl.uniform
twgl.setUniforms(programInfo, {
tex1: textures[ndx1],
tex2: textures[ndx2],
});
// draw the bottom right pixel
gl.viewport(0, 0, 1, 1);
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
// read the pixel
const result = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, result);
console.log('textures', ndx1, 'and', ndx2, 'are', result[0] ? 'the same' : 'not the same');
}
canvas { padding: 5px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
您还可以使用遮挡查询。优点是它们可能不会像 readPixels 那样阻塞 GPU。缺点是您无法在同一个 JavaScript 事件中检查它们,因此它们可能不符合您的需求
// make 3 canvaes as sources for textures
const canvases = ['A', 'B', 'B'].map((msg) => {
const canvas = document.createElement('canvas');
canvas.width = 128;
canvas.height = 128;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 128, 128);
ctx.font = '80px monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'yellow';
ctx.fillText(msg, 64, 64);
document.body.appendChild(canvas);
return canvas;
});
const gl = document.createElement('canvas').getContext('webgl2');
if (!gl) { alert('need webgl2'); }
const vs = `#version 300 es
void main() {
gl_PointSize = 1.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
const fs = `#version 300 es
precision highp float;
uniform sampler2D tex1;
uniform sampler2D tex2;
out vec4 outColor;
void main() {
ivec2 size = textureSize(tex1, 0); // size of mip 0
float len = 0.0;
for (int y = 0; y < size.y; ++y) {
for (int x = 0; x < size.x; ++x) {
vec4 color1 = texelFetch(tex1, ivec2(x, y), 0);
vec4 color2 = texelFetch(tex2, ivec2(x, y), 0);
vec4 diff = color1 - color2;
len = length(diff);
if (len > 0.0) break;
}
if (len > 0.0) break;
}
if (len > 0.0) {
discard;
}
outColor = vec4(1);
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const textures = canvases.map((canvas) => {
// gl.createTexture, gl.bindTexture, gl.texImage, etc.
return twgl.createTexture(gl, {src: canvas});
});
function wait(ms = 0) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function test() {
await compareTextures(0, 1);
await compareTextures(1, 2);
}
test();
async function compareTextures(ndx1, ndx2) {
gl.clear(gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
gl.useProgram(programInfo.program);
// gl.activeTexture, gl.bindTexture, gl.uniform
twgl.setUniforms(programInfo, {
tex1: textures[ndx1],
tex2: textures[ndx2],
});
// draw the bottom right pixel
gl.viewport(0, 0, 1, 1);
const query = gl.createQuery();
gl.beginQuery(gl.ANY_SAMPLES_PASSED, query);
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
gl.endQuery(gl.ANY_SAMPLES_PASSED);
gl.flush();
let ready = false;
while(!ready) {
await wait();
ready = gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
}
const same = gl.getQueryParameter(query, gl.QUERY_RESULT);
console.log('textures', ndx1, 'and', ndx2, 'are', same ? 'the same' : 'not the same');
}
canvas { padding: 5px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
我正在为图像处理算法编写片段着色器。着色器将在循环中的两个帧缓冲区之间 运行 多次(乒乓球)。在某些时候,当输入和输出纹理相同时,我需要停止循环。
我打算做的是Canny边缘检测算法的最后一步,"hysterezis edge tracking"。我想制作实时 GPU/WebGL2 版本的 Canny 算法并将其上传到网站。
最后一步如下:
给定包含 "strong" 边缘像素 (1.0) 和 "weak" 边缘像素 (0.5)
找到所有与强像素相连的弱像素链并标记它们"strong"
保留所有 "strong" 像素并丢弃所有剩余的 "weak" 像素。
这可以在片段着色器中实现 运行循环多次。如果在其 8 像素邻域中至少有一个强像素,则当前 "weak" 像素被标记为 "strong"。在每次迭代中,我们应该有更多的强像素和更少的弱像素。最后,应该只保留孤立的弱像素链。这是片段着色器成为直通着色器的点,应该检测到以停止循环。
2019 年 9 月更新:我在此处 http://www.oldrinb.info/dip/canny/ 上传了 GPU Canny 边缘检测器。它适用于支持 WebGL2 的浏览器,以及支持 WebGL1 和 'WEBGL_draw_buffers' 扩展的浏览器。我很快也会把源代码放到github。
我不是 100% 确定你在问什么。您要求在 CPU 上进行比较。您可以通过将纹理附加到帧缓冲区然后调用 gl.readPixels
来读取纹理的内容。然后您可以比较所有像素。注意:并非所有纹理格式都可以附加到帧缓冲区,但假设您使用的是可以的格式。您已经为 ping-ponging 将纹理附加到帧缓冲区,那么您还想要什么?
就像我在 GPU 的评论中写的那样,你可以编写一个着色器来比较 2 个纹理
#version 300 es
precision highp float;
uniform sampler2D tex1;
uniform sampler2D tex2;
out vec4 outColor;
void main() {
ivec2 size = textureSize(tex1, 0); // size of mip 0
float len = 0.0;
for (int y = 0; y < size.y; ++y) {
for (int x = 0; x < size.x; ++x) {
vec4 color1 = texelFetch(tex1, ivec2(x, y), 0);
vec4 color2 = texelFetch(tex2, ivec2(x, y), 0);
vec4 diff = color1 - color2;
len = length(diff);
if (len > 0.0) break;
}
if (len > 0.0) break;
}
outColor = mix(vec4(0), vec4(1), step(len, 0.0));
}
现在只需绘制 1 个像素并使用 readPixels 读取它。如果它为 0,则纹理相同。如果不是,它们是不同的。
代码假定纹理大小相同,但当然,如果它们大小不同,那么我们已经知道它们不可能相同。
// make 3 canvaes as sources for textures
const canvases = ['A', 'B', 'B'].map((msg) => {
const canvas = document.createElement('canvas');
canvas.width = 128;
canvas.height = 128;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 128, 128);
ctx.font = '80px monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'yellow';
ctx.fillText(msg, 64, 64);
document.body.appendChild(canvas);
return canvas;
});
const gl = document.createElement('canvas').getContext('webgl2');
if (!gl) { alert('need webgl2'); }
const vs = `#version 300 es
void main() {
gl_PointSize = 1.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
const fs = `#version 300 es
precision highp float;
uniform sampler2D tex1;
uniform sampler2D tex2;
out vec4 outColor;
void main() {
ivec2 size = textureSize(tex1, 0); // size of mip 0
float len = 0.0;
for (int y = 0; y < size.y; ++y) {
for (int x = 0; x < size.x; ++x) {
vec4 color1 = texelFetch(tex1, ivec2(x, y), 0);
vec4 color2 = texelFetch(tex2, ivec2(x, y), 0);
vec4 diff = color1 - color2;
len = length(diff);
if (len > 0.0) break;
}
if (len > 0.0) break;
}
outColor = mix(vec4(0), vec4(1), step(len, 0.0));
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const textures = canvases.map((canvas) => {
// gl.createTexture, gl.bindTexture, gl.texImage, etc.
return twgl.createTexture(gl, {src: canvas});
});
compareTextures(0, 1);
compareTextures(1, 2);
function compareTextures(ndx1, ndx2) {
gl.useProgram(programInfo.program);
// gl.activeTexture, gl.bindTexture, gl.uniform
twgl.setUniforms(programInfo, {
tex1: textures[ndx1],
tex2: textures[ndx2],
});
// draw the bottom right pixel
gl.viewport(0, 0, 1, 1);
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
// read the pixel
const result = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, result);
console.log('textures', ndx1, 'and', ndx2, 'are', result[0] ? 'the same' : 'not the same');
}
canvas { padding: 5px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
您还可以使用遮挡查询。优点是它们可能不会像 readPixels 那样阻塞 GPU。缺点是您无法在同一个 JavaScript 事件中检查它们,因此它们可能不符合您的需求
// make 3 canvaes as sources for textures
const canvases = ['A', 'B', 'B'].map((msg) => {
const canvas = document.createElement('canvas');
canvas.width = 128;
canvas.height = 128;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 128, 128);
ctx.font = '80px monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'yellow';
ctx.fillText(msg, 64, 64);
document.body.appendChild(canvas);
return canvas;
});
const gl = document.createElement('canvas').getContext('webgl2');
if (!gl) { alert('need webgl2'); }
const vs = `#version 300 es
void main() {
gl_PointSize = 1.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
const fs = `#version 300 es
precision highp float;
uniform sampler2D tex1;
uniform sampler2D tex2;
out vec4 outColor;
void main() {
ivec2 size = textureSize(tex1, 0); // size of mip 0
float len = 0.0;
for (int y = 0; y < size.y; ++y) {
for (int x = 0; x < size.x; ++x) {
vec4 color1 = texelFetch(tex1, ivec2(x, y), 0);
vec4 color2 = texelFetch(tex2, ivec2(x, y), 0);
vec4 diff = color1 - color2;
len = length(diff);
if (len > 0.0) break;
}
if (len > 0.0) break;
}
if (len > 0.0) {
discard;
}
outColor = vec4(1);
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const textures = canvases.map((canvas) => {
// gl.createTexture, gl.bindTexture, gl.texImage, etc.
return twgl.createTexture(gl, {src: canvas});
});
function wait(ms = 0) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function test() {
await compareTextures(0, 1);
await compareTextures(1, 2);
}
test();
async function compareTextures(ndx1, ndx2) {
gl.clear(gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
gl.useProgram(programInfo.program);
// gl.activeTexture, gl.bindTexture, gl.uniform
twgl.setUniforms(programInfo, {
tex1: textures[ndx1],
tex2: textures[ndx2],
});
// draw the bottom right pixel
gl.viewport(0, 0, 1, 1);
const query = gl.createQuery();
gl.beginQuery(gl.ANY_SAMPLES_PASSED, query);
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
gl.endQuery(gl.ANY_SAMPLES_PASSED);
gl.flush();
let ready = false;
while(!ready) {
await wait();
ready = gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
}
const same = gl.getQueryParameter(query, gl.QUERY_RESULT);
console.log('textures', ndx1, 'and', ndx2, 'are', same ? 'the same' : 'not the same');
}
canvas { padding: 5px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>