我使用WebGL解决了以下问题:
想象一下相机前面的网格。网格实际上并没有阴影,而是它的"剪影"作为一个整体用于揭示背景图像作为某种面具或模板,如果你愿意。所以例如你会得到一个带有白色背景的输出图像和一个轮廓形状,其中纹理/图像是该位置的背景。
(我想要使用的网格明显比球体更复杂)
达到这种效果的最佳方法是什么?我想到了投影映射,以便从摄像机的角度将背景纹理投影到网格上。或者可能是一种模板缓冲区?从我所读到的内容来看,它的支持在这一点上并不高。也许有一种更简单的方法来解决我错过的这个问题?
答案 0 :(得分:3)
有很多方法可以做到这一点,最适合你的是你自己。
将网格绘制到模板缓冲区中,然后使用模板测试集绘制图像,使其仅绘制网格绘制的位置
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背景设置为图像。将画布清除为某种颜色,用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将网格绘制到纹理,以在纹理中生成轮廓。将该纹理用作另一个着色器的输入作为遮罩
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混合)。这意味着你可以根据需要获得抗锯齿效果。您可以屏蔽每种颜色的单独数量。您可以轻松地在两个图像等之间进行混合。您可以在最终传递中添加其他效果,如displacement maps等。
以下是以灰色阴影渲染立方体并使用结果混合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>
一般来说,你可以使用纹理蒙版实现更多效果,但这实际上取决于你的目标。
答案 1 :(得分:0)
这两种方法都可行。
&#39;投影&#39;一个可能更有效率和直接。它只需一次通过。您只需要在顶点着色器中用顶点的屏幕坐标替换经典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;
}
您仍然需要调整纹理坐标以尊重屏幕/纹理宽高比,比例等。