首页 > 解决方案 > WebGL Stencils,如何使用 2D 精灵的透明度作为蒙版?

问题描述

if (statuseffect) {
           // Clearing the stencil buffer
           gl.clearStencil(0);
           gl.clear(gl.STENCIL_BUFFER_BIT);
       

           gl.stencilFunc(gl.ALWAYS, 1, 1);
           gl.stencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE);
       

            gl.colorMask(false, false, false, false); 
            
           gl.enable(gl.STENCIL_TEST);

           // Renders the mask through gl.drawArrays L111
           drawImage(statuseffectmask.texture, lerp(-725, 675, this.Transtion_Value), 280, 128 * 4, 32 * 4)
       
           // Telling the stencil now to draw/keep only pixels that equals 1 - which we set earlier
           gl.stencilFunc(gl.EQUAL, 1, 1);
           gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
           
           // enabling back the color buffer
           gl.colorMask(true, true, true, true);
      
       
           drawImage(statuseffect.texture, lerp(-725, 675, this.Transtion_Value), 280, 128 * 4, 32 * 4)

  
           gl.disable(gl.STENCIL_TEST);
        }


我试图让一些东西像这样工作在此处输入图像描述 它在哪里获得精灵的透明度,然后在没有透明度的区域绘制一个精灵,谢谢。

标签: javascriptwebgl

解决方案


目前尚不清楚您为什么要为此使用模板。通常你只需设置混合并使用透明度来混合

如果您真的想使用模板,则需要制作一个着色器,discard如果透明度(alpha)小于某个值,则调用该着色器,以便仅在精灵不透明的情况下设置模板

precision highp float;

varying vec2 v_texcoord;

uniform sampler2D u_texture;
uniform float u_alphaTest;

void main() {
  vec4 color = texture2D(u_texture, v_texcoord);
  if (color.a < u_alphaTest) {
    discard;  // don't draw this pixel
  }
  gl_FragColor = color;
}

但问题是,它已经在不使用模板的情况下透明地绘制纹理。

const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');

const vs = `
attribute vec4 position;
attribute vec2 texcoord;

uniform mat4 u_matrix;

varying vec2 v_texcoord;
void main() {
  gl_Position = u_matrix * position;
  v_texcoord = texcoord;
}
`;

const fs = `
precision highp float;

varying vec2 v_texcoord;

uniform sampler2D u_texture;
uniform float u_alphaTest;

void main() {
  vec4 color = texture2D(u_texture, v_texcoord);
  if (color.a < u_alphaTest) {
    discard;  // don't draw this pixel
  }
  gl_FragColor = color;
}
`;

// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

// make buffers for positions and texcoords for a plane
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);

const texture = makeSpriteTexture(gl, '', 128);

function render(time) {
  time *= 0.001;  // convert to seconds
  
  gl.useProgram(programInfo.program);
  
  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
  const mat = m4.ortho(-150, 150, 75, -75, -1, 1);
  m4.translate(mat, [Math.cos(time) * 20, Math.sin(time) * 20, 0], mat);
  m4.scale(mat, [64, 64, 1], mat);
  
  // calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
  twgl.setUniformsAndBindTextures(programInfo, {
      u_texture: texture,
      u_alphaTest: 0.5,
      u_matrix: mat,
  });

  // calls gl.drawArrays or gl.drawElements
  twgl.drawBufferInfo(gl, bufferInfo);

  requestAnimationFrame(render);
}
requestAnimationFrame(render);

// just so we don't have to download an image
// we'll make our own using a canvas and the 2D API
function makeSpriteTexture(gl, str, size) {
  const canvas = document.createElement('canvas');
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext('2d');
  ctx.font = `${size * 3 / 4}px sans-serif`;
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillText(str, size / 2, size / 2);
  
  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  gl.texImage2D(
      gl.TEXTURE_2D,
      0, // mip level
      gl.RGBA,  // internal format
      gl.RGBA,  // format
      gl.UNSIGNED_BYTE, // type,
      canvas);
  // let's assume we used power of 2 dimensions
  gl.generateMipmap(gl.TEXTURE_2D);
  return tex;
}
canvas { 
 background: url(https://i.imgur.com/v38pV.jpg) no-repeat center center;
  background-size: cover; 
}
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>

否则,如果您现在真的想使用模板,因为代码正在丢弃一些像素,它应该可以工作并且您的代码是正确的。请注意,下面的代码不会清除模板,因为它默认为每帧都被清除

const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl', {stencil: true});

const vs = `
attribute vec4 position;
attribute vec2 texcoord;

uniform mat4 u_matrix;

varying vec2 v_texcoord;
void main() {
  gl_Position = u_matrix * position;
  v_texcoord = texcoord;
}
`;

const fs = `
precision highp float;

varying vec2 v_texcoord;

uniform sampler2D u_texture;
uniform float u_alphaTest;

void main() {
  vec4 color = texture2D(u_texture, v_texcoord);
  if (color.a < u_alphaTest) {
    discard;  // don't draw this pixel
  }
  gl_FragColor = color;
}
`;

// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

// make buffers for positions and texcoords for a plane
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);

const starTexture = makeSpriteTexture(gl, '✱', 128);
const bugTexture = makeSpriteTexture(gl, '', 128);

function render(time) {
  time *= 0.001;  // convert to seconds
  
  gl.useProgram(programInfo.program);
  
  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
  const mat = m4.ortho(-150, 150, 75, -75, -1, 1);
  m4.translate(mat, [Math.cos(time) * 20, 0, 0], mat);
  m4.scale(mat, [64, 64, 1], mat);

  gl.enable(gl.STENCIL_TEST);
  // set the stencil to 1 everwhere we draw a pixel
  gl.stencilFunc(gl.ALWAYS, 1, 0xFF);
  gl.stencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE);
  // don't render pixels
  gl.colorMask(false, false, false, false);

  // calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
  twgl.setUniformsAndBindTextures(programInfo, {
      u_texture: starTexture,
      u_alphaTest: 0.5,
      u_matrix: mat,
  });

  // calls gl.drawArrays or gl.drawElements
  twgl.drawBufferInfo(gl, bufferInfo);
  
  // only draw if the stencil = 1
  gl.stencilFunc(gl.EQUAL, 1, 0xFF);
  gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
  
  // render pixels
  gl.colorMask(true, true, true, true);

  m4.ortho(-150, 150, 75, -75, -1, 1, mat);
  m4.translate(mat, [0, Math.cos(time * 1.1) * 20, 0], mat);
  m4.scale(mat, [64, 64, 1], mat);

  // calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
  twgl.setUniformsAndBindTextures(programInfo, {
      u_texture: bugTexture,
      u_alphaTest: 0,  // draw all pixels (but stencil will prevent some)
      u_matrix: mat,
  });

  // calls gl.drawArrays or gl.drawElements
  twgl.drawBufferInfo(gl, bufferInfo);

  requestAnimationFrame(render);
}
requestAnimationFrame(render);

// just so we don't have to download an image
// we'll make our own using a canvas and the 2D API
function makeSpriteTexture(gl, str, size) {
  const canvas = document.createElement('canvas');
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext('2d');
  ctx.font = `${size * 3 / 4}px sans-serif`;
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillText(str, size / 2, size / 2);
  
  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  gl.texImage2D(
      gl.TEXTURE_2D,
      0, // mip level
      gl.RGBA,  // internal format
      gl.RGBA,  // format
      gl.UNSIGNED_BYTE, // type,
      canvas);
  // let's assume we used power of 2 dimensions
  gl.generateMipmap(gl.TEXTURE_2D);
  return tex;
}
canvas { 
 background: url(https://i.imgur.com/v38pV.jpg) no-repeat center center;
  background-size: cover; 
}
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>

让我还指出,使用 alpha 混合可能会更好,将两个纹理传递到单个着色器并传递另一个矩阵或其他制服以应用一个纹理的 alpha 或另一个。这将更加灵活,因为您可以混合 0 到 1 的所有值,而与模板一样,您只能屏蔽 0 或 1 个句点。

我的意思不是说“不要使用模板”,而是说有时它是最好的,有时不是。只有您可以知道您的情况选择哪种解决方案。


推荐阅读