WebGL 模板,如何使用 2D 精灵的透明度作为遮罩?

WebGL Stencils, How to use a 2D sprite's transparency as a mask?

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);
        }


我正在尝试让一些东西像这样工作 哪里弄到sprite的透明度,然后在没有透明度的地方画一个sprite,谢谢

不清楚为什么要为此使用模板。通常你会 setup blending and use the transparency to blend

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

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 个句点。

我的意思不是说 "don't use the stencil" 而是说有时最好,有时不是。只有您知道您的情况应该选择哪种解决方案。