WebGL stencil 模板测试与模板缓冲区

1,921 阅读4分钟

image.png

  • 常用业务需求:
    • 绘制物体的轮廓
    • 实现镜面效果
    • 实现阴影效果(Shadow Volume)
    • 删除重叠的部分,防止出现z-fighting的问题
    • 只需要绘制部分我们所需要的, 其他部分不考虑
  • 模板缓冲区是一个buffer,可以通过gl.clear/gl.clearStencil 执行/设置初始值
  • Stencil buffer为每个fragment提供8位的存储空间,即可以存储256个不同的数值,但是如果要实现一个简单的模板剪裁效果,其实1位(0和1)就够用了
  • gl.stencilFunc 用来控制stencil的测试方式,即怎样才算通过测试
//Func 参数
const GLenum NEVER                          = 0x0200;
const GLenum LESS                           = 0x0201;
const GLenum EQUAL                          = 0x0202; //等于 
const GLenum LEQUAL                         = 0x0203;
const GLenum GREATER                        = 0x0204;
const GLenum NOTEQUAL                       = 0x0205;
const GLenum GEQUAL                         = 0x0206; //
const GLenum ALWAYS                         = 0x0207; //一般都是第一个设置
void stencilFunc(GLenum func, //上面
                 GLint ref, //用来做stencil测试的参考值。
                 GLuint mask); //指定操作掩码,在测试的时候会先将ref与mask进行与运算,
               //再将ref与buffer中的值进行与运算,最后将两个结果进行比较,比较的方法由func参数所指定。
  • stencilFuncSeparate函数不常用
// 可分开设置前面或背面的模板测试函数,及其引用值。
void stencilFuncSeparate(GLenum face, //gl.FRONT,
                         GLenum func,  //上面
                         GLint ref,    //用来做stencil测试的参考值。
                         GLuint mask); 
  • gl.stencilOp 用来指定通过测试和未通过测试时要怎么处理。
//fail zfail zpass
const GLenum GL_ZERO  ; //设置为0。
const GLenum KEEP  = 0x1E00; //保持当前值不变。
const GLenum REPLACE  = 0x1E01; //设置stencil buffer的值为ref,该ref是通过stencilFunc指定的。
const GLenum INCR     = 0x1E02; //递增当前stencil buffer的值。
                               //如果超过buffer允许的最大值则保持为该值。
const GLenum INCR_WRAP  = 0x8507; //递增当前stencil buffer的值。
                       //如果递增前buffer的值已经是允许的最大值,则设置buffer值为0,wrap的含义即如此
const GLenum DECR   = 0x1E03; //递减当前stencil buffer的值。如果小于0,则设置buffer值为0。
const GLenum INVERT  = 0x150A; //递减当前stencil buffer的值。
                               //如果递减前buffer的值为0,则设置buffer值为允许的最大值。
const GLenum DECR_WRAP  = 0x8508; //进行按位取反操作。
  • 默认情况下glStencilOp是设置为(GL_KEEP, GL_KEEP, GL_KEEP)的,所以不论任何测试的结果是如何,模板缓冲都会保留它的值。
void stencilOp(GLenum fail, //指定当stencil测试未通过时的行为
               GLenum zfail, //指定当stencil测试通过但是depth测试未通过时的行为
               GLenum zpass); //指定当stencil测试通过且depth测试也通过时的行为,
               //或者当stencil测试通过且当前没有depth buffer或者depth测试被关闭时的行为
  • 这个不常用
// 可分开设置,在前面和背面的模板缓冲区上执行的操作。
void stencilOpSeparate(GLenum face,  //gl.front
                       GLenum fail,  //上面
                       GLenum zfail, //上面
                       GLenum zpass); //上面
  • glStencilMask(0xFF); // 每一位写入模板缓冲时都保持原样
  • glStencilMask(0x00); // 每一位在写入模板缓冲时都会变成0(禁用写入)
  • 大部分情况下都只会使用0x00或者0xFF作为模板掩码(Stencil Mask)
// mask来控制写入stencil buffer的有效位
void stencilMask(GLuint mask);
// 可分开启用或禁用,前面和背面的模板测试掩码。
void stencilMaskSeparate(GLenum face, GLuint mask);
  • 代码讲解

image.png

//清除着色缓冲与深度缓冲
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

//清除模板缓存
gl.clear(gl.STENCIL_BUFFER_BIT);
//关闭深度检测
gl.disable(gl.DEPTH_TEST);
//允许模板测试
gl.enable(gl.STENCIL_TEST);

//设置模板测试参数
gl.stencilFunc(gl.ALWAYS, 1, 1);
//设置模板测试后的操作
gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
ms.pushMatrix();
ms.scale(0.3, 0.3, 0.3);
//绘制反射面地板
rectdb.drawSelf(ms, texMap["db"]);
ms.popMatrix();

//设置模板测试参数
gl.stencilFunc(gl.EQUAL, 1, 1);
//绘制镜像体
drawmirror();
//禁用模板测试 这里关闭
gl.disable(gl.STENCIL_TEST);
//开启混合
gl.enable(gl.BLEND);
//设置混合因子
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
ms.pushMatrix();
ms.scale(0.3, 0.3, 0.3);
//绘制半透明反射面地板
rectdb.drawSelf(ms, texMap["tm"]);
ms.popMatrix();
//开启深度检测
gl.enable(gl.DEPTH_TEST);
//关闭混合
gl.disable(gl.BLEND);

//绘制实际物体
drawball();
  • 代码讲解 image.png
  • 代码讲解
    • 案例1
    • 这里面有三个设置
    • function getGLContext() {
        var glContextNames = ["webgl", "experimental-webgl"];
        for (var i = 0; i < glContextNames.length; i++) {
          try {
            gl = canvas.getContext(glContextNames[i], {
              stencil: true
            }); //注意这个
          } catch (e) {}
          if (gl) {
            gl.clearColor(74 / 255, 115 / 255, 94 / 255, 1.0);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); //clear
            gl.viewport(0, 0, canvas.width, canvas.height); //viewport
            gl.enable(gl.STENCIL_TEST); //允许
            break;
          }
        }
      }
      
  • stencilFunc stencilOp stencilMask(0xff) stencilFunc stencilMask(0x00)
  •   // Always pass test
      gl.stencilFunc(gl.ALWAYS, 1, 0xff); //1与0xFF进行与运算
      gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE); //替代的方式
      //gl.REPLACE表示通过测试时用stencilFunc的ref的值来进行替代,
      //结果就是绘制这个模板三角形的时候stencil buffer中三角形的有效区域对应的值都是1。
      gl.stencilMask(0xff); //模板允许写入
      gl.clear(gl.STENCIL_BUFFER_BIT);
      // No need to display the triangle
      gl.colorMask(0, 0, 0, 0);
      gl.drawArrays(gl.TRIANGLES, 0, maskVertex.length / 3); //长方形里 全是1 其他全为0 这个长方形是a为0,所以不会展示出来
    
    
    
      gl.useProgram(program);
      gl.stencilFunc(gl.EQUAL, 1, 0xff); //等于1 才能显示
      gl.stencilMask(0x00); //模板禁止写入
      gl.colorMask(1, 1, 1, 1);
      // draw the clipped triangle
      var color = [1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1];
      var colorBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(color), gl.STATIC_DRAW);
    
      var aColorPosition = gl.getAttribLocation(program, "aColor");
      gl.vertexAttribPointer(aColorPosition, 4, gl.FLOAT, false, 0, 0);
      gl.enableVertexAttribArray(aColorPosition);
    
      var vertexBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
      gl.bufferData(
        gl.ARRAY_BUFFER,
        new Float32Array(vertex),
        gl.STATIC_DRAW
      );
    
      var aVertexPosition = gl.getAttribLocation(program, "aPos");
      gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0);
      gl.enableVertexAttribArray(aVertexPosition);
      gl.clear(gl.COLOR_BUFFER_BIT);
      gl.drawArrays(gl.TRIANGLES, 0, vertex.length / 3);