在 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>