Трафареты WebGL, Как использовать прозрачность 2D-спрайта в качестве маски? - PullRequest
1 голос
/ 10 марта 2020
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);
        }


Я пытаюсь заставить что-то работать так: enter image description here Где он получает прозрачность спрайта, а затем dr aws спрайт в областях, где нет прозрачности, спасибо вы.

1 Ответ

1 голос
/ 10 марта 2020

Непонятно, почему вы хотите использовать трафарет для этого. Обычно вы просто настраиваете смешивание и используете прозрачность для смешивания .

Если вы действительно хотите использовать трафарет, вам нужно создать шейдер, который вызывает 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>

Позвольте мне также отметить, что это, вероятно, также лучше сделать, используя альфа-смешивание, передавая обе текстуры в один шейдер и передавая в другой матрице или другие формы, чтобы применить одну текстуру альфа к другой. Это было бы более гибко, так как вы могли бы смешивать все значения от 0 до 1, где, как и с трафаретом, вы можете маскировать только 0 или 1 период.

Моя точка зрения не говорит "не используйте трафарет ", а скорее, что бывают времена, когда это лучше, и времена, когда это не так. Только вы можете узнать, какое решение выбрать.

...