WebGL – 使用网格作为背景图像的遮罩
WebGL – Use mesh as mask for background image
我有以下问题需要用 WebGL 解决:
想象一下在镜头前有一个网格。网格实际上并没有被着色,但它的 "silhouette" 作为一个整体用于显示背景图像作为某种蒙版或模板,如果你愿意的话。所以例如你会得到一个带有白色背景的输出图像和一个填充有 texture/image 作为该位置背景的剪影形状。
(我要用的网格明显比球体复杂)
实现这种效果的最佳方法是什么?我考虑过从相机的角度将背景纹理投影到网格上的投影映射。或者模板缓冲区是可行的方法吗?据我所知,目前对它的支持度还不是很高。也许还有一种更简单的方法可以解决我错过的这个问题?
有很多方法可以做到这一点,哪种方法最适合您取决于您。
使用模板缓冲区
将网格绘制到模板缓冲区中,然后使用模板测试集绘制图像,这样它就只在绘制网格的地方绘制
var geoVS = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
var geoFS = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1); // doesn't matter. We're only using the stencil
}
`;
var imgVS = `
attribute vec4 position;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = position.xy * .5 + .5; // only works if position is -1 <-> +1 quad
}
`;
var imgFS = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, v_texcoord);
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl", {stencil: true});
const geoPrgInfo = twgl.createProgramInfo(gl, [geoVS, geoFS]);
const imgPrgInfo = twgl.createProgramInfo(gl, [imgVS, imgFS]);
const geoBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const tex = twgl.createTexture(gl, {
src: "https://farm9.staticflickr.com/8873/18598400202_3af67ef38f_z_d.jpg",
crossOrigin: "",
flipY: true,
});
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
var fov = Math.PI * .25;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.1;
var zFar = 10;
var mat = m4.perspective(fov, aspect, zNear, zFar);
mat = m4.translate(mat, [0, 0, -3]);
mat = m4.rotateX(mat, time * 0.81);
mat = m4.rotateZ(mat, time * 0.77);
// draw geometry to generate stencil
gl.useProgram(geoPrgInfo.program);
twgl.setBuffersAndAttributes(gl, geoPrgInfo, geoBufferInfo);
twgl.setUniforms(geoPrgInfo, {
matrix: mat,
});
// write 1 to stencil
gl.enable(gl.STENCIL_TEST);
gl.stencilFunc(gl.ALWAYS, 1, 0xFF);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
gl.drawElements(gl.TRIANGLES, geoBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
// draw image where stencil is set
gl.useProgram(imgPrgInfo.program);
twgl.setBuffersAndAttributes(gl, imgPrgInfo, quadBufferInfo);
twgl.setUniforms(imgPrgInfo, {
tex: tex,
});
gl.stencilFunc(gl.EQUAL, 1, 0xFF);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
gl.drawElements(gl.TRIANGLES, quadBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
使用深度缓冲区
将网格绘制到深度缓冲区中,然后使用设置的深度函数绘制图像,这样它就只在绘制网格的地方绘制。
var geoVS = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
var geoFS = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1); // doesn't matter. We're only using the stencil
}
`;
var imgVS = `
attribute vec4 position;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = position.xy * .5 + .5; // only works if position is -1 <-> +1 quad
}
`;
var imgFS = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, v_texcoord);
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl", {stencil: true});
const geoPrgInfo = twgl.createProgramInfo(gl, [geoVS, geoFS]);
const imgPrgInfo = twgl.createProgramInfo(gl, [imgVS, imgFS]);
const geoBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const tex = twgl.createTexture(gl, {
src: "https://farm9.staticflickr.com/8873/18598400202_3af67ef38f_z_d.jpg",
crossOrigin: "",
flipY: true,
});
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearDepth(0); // clear depth to 0 (normally it's 1)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
var fov = Math.PI * .25;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.1;
var zFar = 10;
var mat = m4.perspective(fov, aspect, zNear, zFar);
mat = m4.translate(mat, [0, 0, -3]);
mat = m4.rotateX(mat, time * 0.81);
mat = m4.rotateZ(mat, time * 0.77);
// draw geometry to generate depth
gl.useProgram(geoPrgInfo.program);
twgl.setBuffersAndAttributes(gl, geoPrgInfo, geoBufferInfo);
twgl.setUniforms(geoPrgInfo, {
matrix: mat,
});
gl.depthFunc(gl.ALWAYS); // we only care about silhouette
gl.drawElements(gl.TRIANGLES, geoBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
// draw image where depth is set
gl.useProgram(imgPrgInfo.program);
twgl.setBuffersAndAttributes(gl, imgPrgInfo, quadBufferInfo);
twgl.setUniforms(imgPrgInfo, {
tex: tex,
});
gl.depthFunc(gl.LESS);
// this quad is drawn at z = 0 which is in the middle Z wize. Should probably
// make it 1 so it's in the back but it's working as is so too lazy to
// change
gl.drawElements(gl.TRIANGLES, quadBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
使用CSS
将 canvas 的 CSS 背景设置为图像。将 canvas 清除为某种颜色,用 0,0,0,0 绘制网格以切出一个洞。
var geoVS = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
var geoFS = `
precision mediump float;
void main() {
gl_FragColor = vec4(0);
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl", {stencil: true});
const geoPrgInfo = twgl.createProgramInfo(gl, [geoVS, geoFS]);
const geoBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(1, 1, 1, 1); // clear to white
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var fov = Math.PI * .25;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.1;
var zFar = 10;
var mat = m4.perspective(fov, aspect, zNear, zFar);
mat = m4.translate(mat, [0, 0, -3]);
mat = m4.rotateX(mat, time * 0.81);
mat = m4.rotateZ(mat, time * 0.77);
// draw in 0,0,0,0 to cut a whole in the canvas to the HTML/CSS
// defined background
gl.useProgram(geoPrgInfo.program);
twgl.setBuffersAndAttributes(gl, geoPrgInfo, geoBufferInfo);
twgl.setUniforms(geoPrgInfo, {
matrix: mat,
});
gl.drawElements(gl.TRIANGLES, geoBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block;
background-image: url(https://farm9.staticflickr.com/8873/18598400202_3af67ef38f_z_d.jpg);
background-size: 100% 100%;
}
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
生成纹理遮罩
通过framebuffer将mesh绘制到texture中,在texture中生成剪影。使用该纹理作为另一个着色器的输入作为遮罩
var geoVS = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
var geoFS = `
precision mediump float;
void main() {
gl_FragColor = vec4(1);
}
`;
var imgVS = `
attribute vec4 position;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = position.xy * .5 + .5; // only works if position is -1 <-> +1 quad
}
`;
var imgFS = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D colorTex;
uniform sampler2D maskTex;
void main() {
vec4 color = texture2D(colorTex, v_texcoord);
vec4 mask = texture2D(maskTex, v_texcoord);
gl_FragColor = color * mask;
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl", {stencil: true});
const geoPrgInfo = twgl.createProgramInfo(gl, [geoVS, geoFS]);
const imgPrgInfo = twgl.createProgramInfo(gl, [imgVS, imgFS]);
const geoBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const tex = twgl.createTexture(gl, {
src: "https://farm9.staticflickr.com/8873/18598400202_3af67ef38f_z_d.jpg",
crossOrigin: "",
flipY: true,
});
// with no options creates a framebuffer with an RGBA8 texture
// and depth buffer
const fbi = twgl.createFramebufferInfo(gl);
function render(time) {
time *= 0.001;
if (twgl.resizeCanvasToDisplaySize(gl.canvas)) {
// with no argument will resize to the canvas size
twgl.resizeFramebufferInfo(gl, fbi);
}
// calls gl.bindFramebuffer and gl.viewport
twgl.bindFramebufferInfo(gl, fbi);
// first draw the geometry to the texture
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var fov = Math.PI * .25;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.1;
var zFar = 10;
var mat = m4.perspective(fov, aspect, zNear, zFar);
mat = m4.translate(mat, [0, 0, -3]);
mat = m4.rotateX(mat, time * 0.81);
mat = m4.rotateZ(mat, time * 0.77);
gl.useProgram(geoPrgInfo.program);
twgl.setBuffersAndAttributes(gl, geoPrgInfo, geoBufferInfo);
twgl.setUniforms(geoPrgInfo, {
matrix: mat,
});
gl.drawElements(gl.TRIANGLES, geoBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
// the texture now is black (0,0,0,0) where there's nothing and (1,1,1,1)
// where are geometry was drawn
// calls gl.bindFramebuffer and gl.viewport
twgl.bindFramebufferInfo(gl, null);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
// draw image using our texture as a mask
gl.useProgram(imgPrgInfo.program);
twgl.setBuffersAndAttributes(gl, imgPrgInfo, quadBufferInfo);
twgl.setUniforms(imgPrgInfo, {
colorTex: tex,
maskTex: fbi.attachments[0],
});
gl.drawElements(gl.TRIANGLES, quadBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
我个人可能会使用最后一个,因为它更灵活。您可以使用任何技术来生成掩码。蒙版将具有级别(因为您可以将其设置为 0.5 以获得 50/50 的混合)。这意味着如果你愿意,你可以得到一个抗锯齿。您可以屏蔽每种颜色的单独数量。您可以轻松地混合 2 个图像等。您可以将 等其他效果添加到最终通道。
这是一个以灰色阴影渲染立方体并使用结果混合 2 个图像的示例。
var geoVS = `
attribute vec4 position;
attribute vec3 normal;
uniform mat4 matrix;
varying vec3 v_normal;
void main() {
gl_Position = matrix * position;
v_normal = (matrix * vec4(normal, 0)).xyz;
}
`;
var geoFS = `
precision mediump float;
uniform vec3 u_lightDir;
varying vec3 v_normal;
void main() {
gl_FragColor = vec4(dot(normalize(v_normal), u_lightDir) * .5 + .5);
}
`;
var imgVS = `
attribute vec4 position;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = position.xy * .5 + .5; // only works if position is -1 <-> +1 quad
}
`;
var imgFS = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D color1Tex;
uniform sampler2D color2Tex;
uniform sampler2D maskTex;
void main() {
// it probably doesn't make sense to use the same
// texcoords for all 3 textures but I'm lazy
vec4 color1 = texture2D(color1Tex, v_texcoord);
vec4 color2 = texture2D(color2Tex, v_texcoord);
vec4 mask = texture2D(maskTex, v_texcoord);
gl_FragColor = mix(color1, color2, mask);
}
`;
const m4 = twgl.m4;
const v3 = twgl.v3;
const gl = document.querySelector("canvas").getContext("webgl", {stencil: true});
const geoPrgInfo = twgl.createProgramInfo(gl, [geoVS, geoFS]);
const imgPrgInfo = twgl.createProgramInfo(gl, [imgVS, imgFS]);
const geoBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const textures = twgl.createTextures(gl, {
tex1: {
src: "https://farm9.staticflickr.com/8873/18598400202_3af67ef38f_z_d.jpg",
crossOrigin: "",
flipY: true,
},
tex2: {
src: "https://farm1.staticflickr.com/339/18414821420_e3d0a8ec5f_z_d.jpg",
crossOrigin: "",
flipY: true,
},
});
// with no options creates a framebuffer with an RGBA8 texture
// and depth buffer
const fbi = twgl.createFramebufferInfo(gl);
function render(time) {
time *= 0.001;
if (twgl.resizeCanvasToDisplaySize(gl.canvas)) {
// with no argument will resize to the canvas size
twgl.resizeFramebufferInfo(gl, fbi);
}
// calls gl.bindFramebuffer and gl.viewport
twgl.bindFramebufferInfo(gl, fbi);
// first draw the geometry to the texture
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
var fov = Math.PI * .25;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.1;
var zFar = 10;
var mat = m4.perspective(fov, aspect, zNear, zFar);
mat = m4.translate(mat, [0, 0, -3]);
mat = m4.rotateX(mat, time * 0.81);
mat = m4.rotateZ(mat, time * 0.77);
gl.useProgram(geoPrgInfo.program);
twgl.setBuffersAndAttributes(gl, geoPrgInfo, geoBufferInfo);
twgl.setUniforms(geoPrgInfo, {
matrix: mat,
u_lightDir: v3.normalize([1, 2, 3]),
});
gl.drawElements(gl.TRIANGLES, geoBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
// the texture now is black (0,0,0,0) where there's nothing and (1,1,1,1)
// where are geometry was drawn
// calls gl.bindFramebuffer and gl.viewport
twgl.bindFramebufferInfo(gl, null);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
// draw image using our texture as a mask
gl.useProgram(imgPrgInfo.program);
twgl.setBuffersAndAttributes(gl, imgPrgInfo, quadBufferInfo);
twgl.setUniforms(imgPrgInfo, {
color1Tex: textures.tex1,
color2Tex: textures.tex2,
maskTex: fbi.attachments[0],
});
gl.drawElements(gl.TRIANGLES, quadBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
一般来说,您可以使用纹理蒙版实现更多效果,但这实际上取决于您的目标。
两种方法都可以。
'projection' 可能更高效、更直接。它只需一次通过即可。您只需要在顶点着色器中用顶点的屏幕坐标替换经典的 UV 坐标。
varying vec2 vTexCoord;
void main( void ){
// whatever how gl_Position is compute
gl_Position = uMVP * vec4(aPosition, 1.0);
// vTexCoord = aTexCoord;
// replace the standard UVs by the vertex screen position
vTexCoord = .5 * ( gl_Position.xy / gl_Position.w ) + .5;
}
您仍然需要调整这些纹理坐标以符合 screen/texture 宽高比、比例等
我有以下问题需要用 WebGL 解决:
想象一下在镜头前有一个网格。网格实际上并没有被着色,但它的 "silhouette" 作为一个整体用于显示背景图像作为某种蒙版或模板,如果你愿意的话。所以例如你会得到一个带有白色背景的输出图像和一个填充有 texture/image 作为该位置背景的剪影形状。
实现这种效果的最佳方法是什么?我考虑过从相机的角度将背景纹理投影到网格上的投影映射。或者模板缓冲区是可行的方法吗?据我所知,目前对它的支持度还不是很高。也许还有一种更简单的方法可以解决我错过的这个问题?
有很多方法可以做到这一点,哪种方法最适合您取决于您。
使用模板缓冲区
将网格绘制到模板缓冲区中,然后使用模板测试集绘制图像,这样它就只在绘制网格的地方绘制
var geoVS = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
var geoFS = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1); // doesn't matter. We're only using the stencil
}
`;
var imgVS = `
attribute vec4 position;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = position.xy * .5 + .5; // only works if position is -1 <-> +1 quad
}
`;
var imgFS = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, v_texcoord);
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl", {stencil: true});
const geoPrgInfo = twgl.createProgramInfo(gl, [geoVS, geoFS]);
const imgPrgInfo = twgl.createProgramInfo(gl, [imgVS, imgFS]);
const geoBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const tex = twgl.createTexture(gl, {
src: "https://farm9.staticflickr.com/8873/18598400202_3af67ef38f_z_d.jpg",
crossOrigin: "",
flipY: true,
});
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
var fov = Math.PI * .25;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.1;
var zFar = 10;
var mat = m4.perspective(fov, aspect, zNear, zFar);
mat = m4.translate(mat, [0, 0, -3]);
mat = m4.rotateX(mat, time * 0.81);
mat = m4.rotateZ(mat, time * 0.77);
// draw geometry to generate stencil
gl.useProgram(geoPrgInfo.program);
twgl.setBuffersAndAttributes(gl, geoPrgInfo, geoBufferInfo);
twgl.setUniforms(geoPrgInfo, {
matrix: mat,
});
// write 1 to stencil
gl.enable(gl.STENCIL_TEST);
gl.stencilFunc(gl.ALWAYS, 1, 0xFF);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
gl.drawElements(gl.TRIANGLES, geoBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
// draw image where stencil is set
gl.useProgram(imgPrgInfo.program);
twgl.setBuffersAndAttributes(gl, imgPrgInfo, quadBufferInfo);
twgl.setUniforms(imgPrgInfo, {
tex: tex,
});
gl.stencilFunc(gl.EQUAL, 1, 0xFF);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
gl.drawElements(gl.TRIANGLES, quadBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
使用深度缓冲区
将网格绘制到深度缓冲区中,然后使用设置的深度函数绘制图像,这样它就只在绘制网格的地方绘制。
var geoVS = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
var geoFS = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1); // doesn't matter. We're only using the stencil
}
`;
var imgVS = `
attribute vec4 position;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = position.xy * .5 + .5; // only works if position is -1 <-> +1 quad
}
`;
var imgFS = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, v_texcoord);
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl", {stencil: true});
const geoPrgInfo = twgl.createProgramInfo(gl, [geoVS, geoFS]);
const imgPrgInfo = twgl.createProgramInfo(gl, [imgVS, imgFS]);
const geoBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const tex = twgl.createTexture(gl, {
src: "https://farm9.staticflickr.com/8873/18598400202_3af67ef38f_z_d.jpg",
crossOrigin: "",
flipY: true,
});
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearDepth(0); // clear depth to 0 (normally it's 1)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
var fov = Math.PI * .25;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.1;
var zFar = 10;
var mat = m4.perspective(fov, aspect, zNear, zFar);
mat = m4.translate(mat, [0, 0, -3]);
mat = m4.rotateX(mat, time * 0.81);
mat = m4.rotateZ(mat, time * 0.77);
// draw geometry to generate depth
gl.useProgram(geoPrgInfo.program);
twgl.setBuffersAndAttributes(gl, geoPrgInfo, geoBufferInfo);
twgl.setUniforms(geoPrgInfo, {
matrix: mat,
});
gl.depthFunc(gl.ALWAYS); // we only care about silhouette
gl.drawElements(gl.TRIANGLES, geoBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
// draw image where depth is set
gl.useProgram(imgPrgInfo.program);
twgl.setBuffersAndAttributes(gl, imgPrgInfo, quadBufferInfo);
twgl.setUniforms(imgPrgInfo, {
tex: tex,
});
gl.depthFunc(gl.LESS);
// this quad is drawn at z = 0 which is in the middle Z wize. Should probably
// make it 1 so it's in the back but it's working as is so too lazy to
// change
gl.drawElements(gl.TRIANGLES, quadBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
使用CSS
将 canvas 的 CSS 背景设置为图像。将 canvas 清除为某种颜色,用 0,0,0,0 绘制网格以切出一个洞。
var geoVS = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
var geoFS = `
precision mediump float;
void main() {
gl_FragColor = vec4(0);
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl", {stencil: true});
const geoPrgInfo = twgl.createProgramInfo(gl, [geoVS, geoFS]);
const geoBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(1, 1, 1, 1); // clear to white
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var fov = Math.PI * .25;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.1;
var zFar = 10;
var mat = m4.perspective(fov, aspect, zNear, zFar);
mat = m4.translate(mat, [0, 0, -3]);
mat = m4.rotateX(mat, time * 0.81);
mat = m4.rotateZ(mat, time * 0.77);
// draw in 0,0,0,0 to cut a whole in the canvas to the HTML/CSS
// defined background
gl.useProgram(geoPrgInfo.program);
twgl.setBuffersAndAttributes(gl, geoPrgInfo, geoBufferInfo);
twgl.setUniforms(geoPrgInfo, {
matrix: mat,
});
gl.drawElements(gl.TRIANGLES, geoBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block;
background-image: url(https://farm9.staticflickr.com/8873/18598400202_3af67ef38f_z_d.jpg);
background-size: 100% 100%;
}
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
生成纹理遮罩
通过framebuffer将mesh绘制到texture中,在texture中生成剪影。使用该纹理作为另一个着色器的输入作为遮罩
var geoVS = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
var geoFS = `
precision mediump float;
void main() {
gl_FragColor = vec4(1);
}
`;
var imgVS = `
attribute vec4 position;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = position.xy * .5 + .5; // only works if position is -1 <-> +1 quad
}
`;
var imgFS = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D colorTex;
uniform sampler2D maskTex;
void main() {
vec4 color = texture2D(colorTex, v_texcoord);
vec4 mask = texture2D(maskTex, v_texcoord);
gl_FragColor = color * mask;
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl", {stencil: true});
const geoPrgInfo = twgl.createProgramInfo(gl, [geoVS, geoFS]);
const imgPrgInfo = twgl.createProgramInfo(gl, [imgVS, imgFS]);
const geoBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const tex = twgl.createTexture(gl, {
src: "https://farm9.staticflickr.com/8873/18598400202_3af67ef38f_z_d.jpg",
crossOrigin: "",
flipY: true,
});
// with no options creates a framebuffer with an RGBA8 texture
// and depth buffer
const fbi = twgl.createFramebufferInfo(gl);
function render(time) {
time *= 0.001;
if (twgl.resizeCanvasToDisplaySize(gl.canvas)) {
// with no argument will resize to the canvas size
twgl.resizeFramebufferInfo(gl, fbi);
}
// calls gl.bindFramebuffer and gl.viewport
twgl.bindFramebufferInfo(gl, fbi);
// first draw the geometry to the texture
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var fov = Math.PI * .25;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.1;
var zFar = 10;
var mat = m4.perspective(fov, aspect, zNear, zFar);
mat = m4.translate(mat, [0, 0, -3]);
mat = m4.rotateX(mat, time * 0.81);
mat = m4.rotateZ(mat, time * 0.77);
gl.useProgram(geoPrgInfo.program);
twgl.setBuffersAndAttributes(gl, geoPrgInfo, geoBufferInfo);
twgl.setUniforms(geoPrgInfo, {
matrix: mat,
});
gl.drawElements(gl.TRIANGLES, geoBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
// the texture now is black (0,0,0,0) where there's nothing and (1,1,1,1)
// where are geometry was drawn
// calls gl.bindFramebuffer and gl.viewport
twgl.bindFramebufferInfo(gl, null);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
// draw image using our texture as a mask
gl.useProgram(imgPrgInfo.program);
twgl.setBuffersAndAttributes(gl, imgPrgInfo, quadBufferInfo);
twgl.setUniforms(imgPrgInfo, {
colorTex: tex,
maskTex: fbi.attachments[0],
});
gl.drawElements(gl.TRIANGLES, quadBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
我个人可能会使用最后一个,因为它更灵活。您可以使用任何技术来生成掩码。蒙版将具有级别(因为您可以将其设置为 0.5 以获得 50/50 的混合)。这意味着如果你愿意,你可以得到一个抗锯齿。您可以屏蔽每种颜色的单独数量。您可以轻松地混合 2 个图像等。您可以将
这是一个以灰色阴影渲染立方体并使用结果混合 2 个图像的示例。
var geoVS = `
attribute vec4 position;
attribute vec3 normal;
uniform mat4 matrix;
varying vec3 v_normal;
void main() {
gl_Position = matrix * position;
v_normal = (matrix * vec4(normal, 0)).xyz;
}
`;
var geoFS = `
precision mediump float;
uniform vec3 u_lightDir;
varying vec3 v_normal;
void main() {
gl_FragColor = vec4(dot(normalize(v_normal), u_lightDir) * .5 + .5);
}
`;
var imgVS = `
attribute vec4 position;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = position.xy * .5 + .5; // only works if position is -1 <-> +1 quad
}
`;
var imgFS = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D color1Tex;
uniform sampler2D color2Tex;
uniform sampler2D maskTex;
void main() {
// it probably doesn't make sense to use the same
// texcoords for all 3 textures but I'm lazy
vec4 color1 = texture2D(color1Tex, v_texcoord);
vec4 color2 = texture2D(color2Tex, v_texcoord);
vec4 mask = texture2D(maskTex, v_texcoord);
gl_FragColor = mix(color1, color2, mask);
}
`;
const m4 = twgl.m4;
const v3 = twgl.v3;
const gl = document.querySelector("canvas").getContext("webgl", {stencil: true});
const geoPrgInfo = twgl.createProgramInfo(gl, [geoVS, geoFS]);
const imgPrgInfo = twgl.createProgramInfo(gl, [imgVS, imgFS]);
const geoBufferInfo = twgl.primitives.createCubeBufferInfo(gl, 1);
const quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const textures = twgl.createTextures(gl, {
tex1: {
src: "https://farm9.staticflickr.com/8873/18598400202_3af67ef38f_z_d.jpg",
crossOrigin: "",
flipY: true,
},
tex2: {
src: "https://farm1.staticflickr.com/339/18414821420_e3d0a8ec5f_z_d.jpg",
crossOrigin: "",
flipY: true,
},
});
// with no options creates a framebuffer with an RGBA8 texture
// and depth buffer
const fbi = twgl.createFramebufferInfo(gl);
function render(time) {
time *= 0.001;
if (twgl.resizeCanvasToDisplaySize(gl.canvas)) {
// with no argument will resize to the canvas size
twgl.resizeFramebufferInfo(gl, fbi);
}
// calls gl.bindFramebuffer and gl.viewport
twgl.bindFramebufferInfo(gl, fbi);
// first draw the geometry to the texture
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
var fov = Math.PI * .25;
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 0.1;
var zFar = 10;
var mat = m4.perspective(fov, aspect, zNear, zFar);
mat = m4.translate(mat, [0, 0, -3]);
mat = m4.rotateX(mat, time * 0.81);
mat = m4.rotateZ(mat, time * 0.77);
gl.useProgram(geoPrgInfo.program);
twgl.setBuffersAndAttributes(gl, geoPrgInfo, geoBufferInfo);
twgl.setUniforms(geoPrgInfo, {
matrix: mat,
u_lightDir: v3.normalize([1, 2, 3]),
});
gl.drawElements(gl.TRIANGLES, geoBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
// the texture now is black (0,0,0,0) where there's nothing and (1,1,1,1)
// where are geometry was drawn
// calls gl.bindFramebuffer and gl.viewport
twgl.bindFramebufferInfo(gl, null);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
// draw image using our texture as a mask
gl.useProgram(imgPrgInfo.program);
twgl.setBuffersAndAttributes(gl, imgPrgInfo, quadBufferInfo);
twgl.setUniforms(imgPrgInfo, {
color1Tex: textures.tex1,
color2Tex: textures.tex2,
maskTex: fbi.attachments[0],
});
gl.drawElements(gl.TRIANGLES, quadBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas></canvas>
一般来说,您可以使用纹理蒙版实现更多效果,但这实际上取决于您的目标。
两种方法都可以。
'projection' 可能更高效、更直接。它只需一次通过即可。您只需要在顶点着色器中用顶点的屏幕坐标替换经典的 UV 坐标。
varying vec2 vTexCoord;
void main( void ){
// whatever how gl_Position is compute
gl_Position = uMVP * vec4(aPosition, 1.0);
// vTexCoord = aTexCoord;
// replace the standard UVs by the vertex screen position
vTexCoord = .5 * ( gl_Position.xy / gl_Position.w ) + .5;
}
您仍然需要调整这些纹理坐标以符合 screen/texture 宽高比、比例等