WebGL - 使用网格作为背景图像的蒙版

时间:2017-02-10 11:06:49

标签: html5 background webgl mask mesh

我使用WebGL解决了以下问题:

想象一下相机前面的网格。网格实际上并没有阴影,而是它的"剪影"作为一个整体用于揭示背景图像作为某种面具或模板,如果你愿意。所以例如你会得到一个带有白色背景的输出图像和一个轮廓形状,其中纹理/图像是该位置的背景。

Image that explains it better maybe(我想要使用的网格明显比球体更复杂)

达到这种效果的最佳方法是什么?我想到了投影映射,以便从摄像机的角度将背景纹理投影到网格上。或者可能是一种模板缓冲区?从我所读到的内容来看,它的支持在这一点上并不高。也许有一种更简单的方法来解决我错过的这个问题?

2 个答案:

答案 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

将画布的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;
}

您仍然需要调整纹理坐标以尊重屏幕/纹理宽高比,比例等。