webgl2 方法解析: CULL_FACE

226 阅读3分钟

在 WebGL 中,gl.CULL_FACE 用于控制面剔除(Face Culling)。

1. 什么是面剔除(Face Culling)

面剔除是一种图形优化技术,用于在渲染过程中丢弃某些多边形面。在 3D 模型中,每个面都有一个“正面”和一个“背面”。面剔除可以根据指定的规则(如判断面的朝向)来决定是否渲染某个面。例如:

  • 如果一个面的法线方向指向视点(即背面朝向视点),那么这个面可以被剔除,从而减少渲染负担。
  • 这种技术可以显著提高渲染效率,尤其是在处理复杂的 3D 场景时。

2. 如何启用和禁用面剔除

在 WebGL 中,可以通过以下方法启用或禁用面剔除:

// 启用面剔除
gl.enable(gl.CULL_FACE);
​
// 禁用面剔除
gl.disable(gl.CULL_FACE);

3. 设置剔除的面

默认情况下,启用面剔除后,WebGL 会剔除背面(gl.BACK)。你可以通过 gl.cullFace 方法指定要剔除的面:

  • gl.BACK:剔除背面(默认值)。
  • gl.FRONT:剔除正面。
  • gl.FRONT_AND_BACK:剔除正面和背面。

例如:

// 剔除背面
gl.cullFace(gl.BACK);
​
// 剔除正面
gl.cullFace(gl.FRONT);
​
// 剔除正面和背面
gl.cullFace(gl.FRONT_AND_BACK);

4. 如何定义正面和背面

在 WebGL 中,正面和背面的定义是通过顶点的绕序(Winding Order)来确定的:

  • 顺时针绕序(Clockwise) :如果顶点按照顺时针方向绕序,则该面被认为是背面。
  • 逆时针绕序(Counter-Clockwise) :如果顶点按照逆时针方向绕序,则该面被认为是正面。

默认情况下,WebGL 认为逆时针绕序的面是正面。你可以通过 gl.frontFace 方法修改正面的定义:

// 设置正面为逆时针绕序(默认值)
gl.frontFace(gl.CCW);
​
// 设置正面为顺时针绕序
gl.frontFace(gl.CW);

5. 实际应用

面剔除在以下场景中非常有用:

  • 封闭物体的渲染:对于完全封闭的物体(如立方体),内部的面永远不会被看到,因此可以剔除背面,减少渲染工作量。
  • 优化性能:通过剔除不必要的面,可以减少 GPU 的工作量,提高渲染效率。

6.示例代码

以下是一个简单的示例,可以直接运行:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebGL2 Cull Face Example</title>
    <style>
        body { margin: 0; overflow: hidden; }
        canvas { display: block; width: 800px; height: 600px; }
    </style>
</head>
<body>
    <canvas id="glCanvas"></canvas>
    <button onclick="main(true, false, false)">打开/关闭剔除</button>
    <button onclick="main(false, true, false)">剔除背面/正面</button>
    <button onclick="main(false, false, true)">方向修改</button>
    <script>
        // 顶点着色器代码
        const vsSource = `#version 300 es
        in vec4 aPosition;
        void main() {
            gl_Position = aPosition;
        }`;
​
        // 片段着色器代码
        const fsSource = `#version 300 es
        precision highp float;
        out vec4 fragColor;
        void main() {
            fragColor = vec4(1.0, 0.5, 0.2, 1.0); // 橙色
        }`;
​
        function main(isEneble, switchMode, isSwitchDirection) {
          const canvas = document.getElementById('glCanvas');
          const gl = canvas.getContext('webgl2');
          
          if (!gl) {
            alert('WebGL2 not supported in your browser');
            return;
          }
​
          // 调整canvas实际尺寸以匹配显示尺寸
          canvas.width = 800;
          canvas.height = 600;
          gl.viewport(0, 0, canvas.width, canvas.height);
​
          // 创建着色器程序
          const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource);
          const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
          const program = createProgram(gl, vertexShader, fragmentShader);
          gl.useProgram(program);
​
          // 获取属性位置
          const positionAttributeLocation = gl.getAttribLocation(program, "aPosition");
          
          // 创建一个包含两个三角形的矩形,其中一个顺时针,一个逆时针
          const positions = new Float32Array([
            // 第一个三角形(逆时针 - 正面)
            -0.5, -0.5,
            0.5, -0.5,
            0.5,  0.5,
              
            // 第二个三角形(顺时针 - 背面)
            -0.5, -0.5,
            -0.5,  0.5,
            0.5,  0.5,
          ]);
​
          // 创建并绑定顶点缓冲区
          const positionBuffer = gl.createBuffer();
          gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
          gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
​
          // 启用顶点属性
          gl.enableVertexAttribArray(positionAttributeLocation);
          gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
​
          // 启用面剔除
          if (isEneble) {
            enableCullFace(gl);
          }
​
          if (switchMode) {
            gl.enable(gl.CULL_FACE);
            switchCullState(gl);
          }
​
          if (isSwitchDirection) {
            gl.enable(gl.CULL_FACE);
            switchDirection(gl);
          }
​
          // 设置清除颜色并清除画布
          gl.clearColor(0.1, 0.1, 0.1, 1.0);
          gl.clear(gl.COLOR_BUFFER_BIT);
​
          // 绘制两个三角形
          gl.drawArrays(gl.TRIANGLES, 0, 6);
        }
​
        function createShader(gl, type, source) {
            const shader = gl.createShader(type);
            gl.shaderSource(shader, source);
            gl.compileShader(shader);
            
            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                console.error('Shader compilation error:', gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
                return null;
            }
            
            return shader;
        }
​
        function createProgram(gl, vertexShader, fragmentShader) {
            const program = gl.createProgram();
            gl.attachShader(program, vertexShader);
            gl.attachShader(program, fragmentShader);
            gl.linkProgram(program);
            
            if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
                console.error('Program linking error:', gl.getProgramInfoLog(program));
                gl.deleteProgram(program);
                return null;
            }
            
            return program;
        }
​
        // 切换剔除模式
        function switchCullState(gl) {
          // 获取当前的剔除模式
          const currentCullMode = gl.getParameter(gl.CULL_FACE_MODE);
​
          if (currentCullMode === gl.BACK) {
            gl.cullFace(gl.FRONT);
          } else if (currentCullMode === gl.FRONT) {
            gl.cullFace(gl.BACK);
          }
        }
​
        // 切换方向
        function switchDirection(gl) {
          const direction = gl.getParameter(gl.FRONT_FACE);
​
          if (direction === gl.CCW) {
            // 设置正面为顺时针绕序
            gl.frontFace(gl.CW);
          } else if (direction === gl.CW) {
            // 设置正面为逆时针绕序(默认值)
            gl.frontFace(gl.CCW);
          }
        }
​
        // 打开关闭剔除
        function enableCullFace(gl) {
          const isCull = gl.getParameter(gl.CULL_FACE);
​
          if (isCull === true) {
            gl.disable(gl.CULL_FACE);
          } else {
            gl.enable(gl.CULL_FACE);
          }
        }
​
        main();
​
    </script>
</body>
</html>