mirror of
https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git
synced 2025-08-22 20:25:28 +08:00
Rewrite 04-04
This commit is contained in:
@@ -3,89 +3,88 @@
|
||||
原文 | [Face culling](http://learnopengl.com/#!Advanced-OpenGL/Face-culling)
|
||||
---|---
|
||||
作者 | JoeyDeVries
|
||||
翻译 | [Django](http://bullteacher.com/)
|
||||
校对 | [Geequlim](http://geequlim.com)
|
||||
翻译 | Meow J
|
||||
校对 | 暂未校对
|
||||
|
||||
尝试在头脑中想象一下有一个3D立方体,你从任何一个方向去看它,最多可以同时看到多少个面。如果你的想象力不是过于丰富,你最终最多能数出来的面是3个。你可以从一个立方体的任意位置和方向上去看它,但是你永远不能看到多于3个面。所以我们为何还要去绘制那三个不会显示出来的3个面呢。如果我们可以以某种方式丢弃它们,我们会提高片段着色器超过50%的性能!
|
||||
尝试在脑子中想象一个3D立方体,数数你从任意方向最多能同时看到几个面。如果你的想象力不是过于丰富了,你应该能得出最大的面数是3。你可以从任意位置和任意方向看向这个球体,但你永远不能看到3个以上的面。所以我们为什么要浪费时间绘制我们不能看见的那3个面呢?如果我们能够以某种方式丢弃这几个看不见的面,我们能省下超过50%的片段着色器执行数!
|
||||
|
||||
!!! Important
|
||||
|
||||
我们所说的是超过50%而不是50%,因为从一个角度只有2个或1个面能够被看到。这种情况下我们就能够提高50%以上性能了。
|
||||
我说的是**超过**50%而不是50%,因为从特定角度来看的话只能看见2个甚至是1个面。在这种情况下,我们就能省下超过50%了。
|
||||
|
||||
这是一个很好的主意,但我们仍有一个问题需要解决:我们如何知道一个物体的某一个面不能从观察者视角看到呢?
|
||||
如果我们想象任何一个闭合形状,它的每一个面都有两侧,每一侧要么**面向**用户,要么背对用户。如果我们能够只绘制**面向**观察者的面呢?
|
||||
|
||||
这的确是个好主意,但是有个问题需要解决:我们如何知道某个面在观察者的视野中不会出现呢?如果我们去想象任何封闭的几何平面,它们都有两面,一面面向用户,另一面背对用户。假如我们只渲染面向观察者的面会怎样?
|
||||
这正是<def>面剔除</def>(Face Culling)所做的。OpenGL能够检查所有<def>面向</def>(Front Facing)观察者的面,并渲染它们,而丢弃那些<def>背向</def>(Back Facing)的面,节省我们很多的片段着色器调用(它们的开销很大!)。但我们仍要告诉OpenGL哪些面是正向面(Front Face),哪些面是背向面(Back Face)。OpenGL使用了一个很聪明的技巧,分析顶点数据的<def>环绕顺序</def>(Winding Order)。
|
||||
|
||||
这正是**面剔除**(Face culling)所要做的。OpenGL允许检查所有正面朝向(Front facing)观察者的面,并渲染它们,而丢弃所有背面朝向(Back facing)的面,这样就节约了我们很多片段着色器的命令(它们很昂贵!)。我们必须告诉OpenGL我们使用的哪个面是正面,哪个面是反面。OpenGL使用一种聪明的手段解决这个问题——分析顶点数据的连接顺序(Winding order)。
|
||||
## 环绕顺序
|
||||
|
||||
|
||||
## 顶点连接顺序
|
||||
|
||||
当我们定义一系列的三角顶点时,我们会把它们定义为一个特定的连接顺序(Winding Order),它们可能是**顺时针**的或**逆时针**的。每个三角形由3个顶点组成,我们从三角形的中间去看,从而把这三个顶点指定一个连接顺序。
|
||||
当我们定义一组三角形顶点时,我们会以特定的环绕顺序来定义它们,可能是<def>顺时针</def>(Clockwise)的,也可能是<def>逆时针</def>(Counter-clockwise)的。每个三角形由3个顶点所组成,我们会从三角形中间来看,为这3个顶点设定一个环绕顺序。
|
||||
|
||||

|
||||
|
||||
正如你所看到的那样,我们先定义了顶点1,接着我们定义顶点2或3,这个不同的选择决定了这个三角形的连接顺序。下面的代码展示出这点:
|
||||
可以看到,我们首先定义了顶点1,之后我们可以选择定义顶点2或者顶点3,这个选择将定义了这个三角形的环绕顺序。下面的代码展示了这点:
|
||||
|
||||
```c++
|
||||
GLfloat vertices[] = {
|
||||
//顺时针
|
||||
vertices[0], // vertex 1
|
||||
vertices[1], // vertex 2
|
||||
vertices[2], // vertex 3
|
||||
float vertices[] = {
|
||||
// 顺时针
|
||||
vertices[0], // 顶点1
|
||||
vertices[1], // 顶点2
|
||||
vertices[2], // 顶点3
|
||||
// 逆时针
|
||||
vertices[0], // vertex 1
|
||||
vertices[2], // vertex 3
|
||||
vertices[1] // vertex 2
|
||||
vertices[0], // 顶点1
|
||||
vertices[2], // 顶点3
|
||||
vertices[1] // 顶点2
|
||||
};
|
||||
```
|
||||
|
||||
每三个顶点都形成了一个包含着连接顺序的基本三角形。OpenGL使用这个信息在渲染你的基本图形的时候决定这个三角形是三角形的正面还是三角形的背面。默认情况下,**逆时针**的顶点连接顺序被定义为三角形的**正面**。
|
||||
每组组成三角形图元的三个顶点就包含了一个环绕顺序。OpenGL在渲染图元的时候将使用这个信息来决定一个三角形是一个<def>正向</def>三角形还是<def>背向</def>三角形。默认情况下,逆时针顶点所定义的三角形将会被处理为正向三角形。
|
||||
|
||||
当定义你的顶点顺序时,你如果定义能够看到的一个三角形,那它一定是正面朝向的,所以你定义的三角形应该是逆时针的,就像你直接面向这个三角形。把所有的顶点指定成这样是件炫酷的事,实际的顶点连接顺序是在**光栅化**阶段(Rasterization stage)计算的,所以当顶点着色器已经运行后。顶点就能够在观察者的观察点被看到。
|
||||
当你定义顶点顺序的时候,你应该想象对应的三角形是面向你的,所以你定义的三角形从正面看去应该是逆时针的。这样定义顶点很棒的一点是,实际的环绕顺序是在光栅化阶段进行的,也就是顶点着色器运行之后。这些顶点就是从**观察者视角**所见的了。
|
||||
|
||||
我们指定了它们以后,观察者面对的所有的三角形的顶点的连接顺序都是正确的,但是现在渲染的立方体另一面的三角形的顶点的连接顺序被反转。最终,我们所面对的三角形被视为正面朝向的三角形,后部的三角形被视为背面朝向的三角形。下图展示了这个效果:
|
||||
观察者所面向的所有三角形顶点就是我们所指定的正确环绕顺序了,而立方体另一面的三角形顶点则是以相反的环绕顺序所渲染的。这样的结果就是,我们所面向的三角形将会是正向三角形,而背面的三角形则是背向三角形。下面这张图显示了这个效果:
|
||||
|
||||

|
||||
|
||||
在顶点数据中,我们将两个三角形都以逆时针顺序定义(正面的三角形是1、2、3,背面的三角形也是1、2、3(如果我们从正面看这个三角形的话))。然而,如果从观察者当前视角使用1、2、3的顺序来绘制的话,从观察者的方向来看,背面的三角形将会是以顺时针顺序渲染的。虽然背面的三角形是以逆时针定义的,它现在是以顺时针顺序渲染的了。这正是我们想要<def>剔除</def>(Cull,丢弃)的不可见面了!
|
||||
|
||||
在顶点数据中,我们定义的是两个逆时针顺序的三角形。然而,从观察者的方面看,后面的三角形是顺时针的,如果我们仍以1、2、3的顺序以观察者当面的视野看的话。即使我们以逆时针顺序定义后面的三角形,它现在还是变为顺时针。它正是我们打算剔除(丢弃)的不可见的面!
|
||||
|
||||
|
||||
|
||||
## 面剔除
|
||||
|
||||
在教程的开头,我们说过OpenGL可以丢弃背面朝向的三角形。现在我们知道了如何设置顶点的连接顺序,我们可以开始使用OpenGL默认关闭的面剔除选项了。
|
||||
在本节的开头我们就说过,OpenGL能够丢弃那些渲染为背向三角形的三角形图元。既然已经知道如何设置顶点的环绕顺序了,我们就可以使用OpenGL的<def>面剔除</def>选项了,它默认是禁用状态的。
|
||||
|
||||
记住我们上一节所使用的立方体的定点数据不是以逆时针顺序定义的。所以我更新了顶点数据,好去反应为一个逆时针链接顺序,你可以[从这里复制它](http://learnopengl.com/code_viewer.php?code=advanced/faceculling_vertexdata)。把所有三角的顶点都定义为逆时针是一个很好的习惯。
|
||||
在之前教程中使用的立方体顶点数据并不是按照逆时针环绕顺序定义的,所以我更新了顶点数据,来反映逆时针的环绕顺序,你可以从[这里](https://learnopengl.com/code_viewer.php?code=advanced/faceculling_vertexdata)复制它们。尝试想象这些顶点,确认在每个三角形中它们都是以逆时针定义的,这是一个很好的习惯。
|
||||
|
||||
开启OpenGL的`GL_CULL_FACE`选项就能开启面剔除功能:
|
||||
要想启用面剔除,我们只需要启用OpenGL的<var>GL_CULL_FACE</var>选项:
|
||||
|
||||
```c++
|
||||
glEnable(GL_CULL_FACE);
|
||||
```
|
||||
|
||||
从这儿以后,所有的不是正面朝向的面都会被丢弃(尝试飞入立方体看看,里面什么面都看不见了)。目前,在渲染片段上我们节约了超过50%的性能,但记住这只对像立方体这样的封闭形状有效。当我们绘制上个教程中那个草的时候,我们必须关闭面剔除,这是因为它的前、后面都必须是可见的。
|
||||
从这一句代码之后,所有背向面都将被丢弃(尝试飞进立方体内部,看看所有的内面是不是都被丢弃了)。目前我们在渲染片段的时候能够节省50%以上的性能,但注意这只对像立方体这样的封闭形状有效。当我们想要绘制[上一节](03 Blending.md)中的草时,我们必须要再次禁用面剔除,因为它们的正向面和背向面都应该是可见的。
|
||||
|
||||
OpenGL允许我们改变剔除面的类型。要是我们剔除正面而不是背面会怎样?我们可以调用`glCullFace`来做这件事:
|
||||
OpenGL允许我们改变需要剔除的面的类型。如果我们只想剔除正向面而不是背向面会怎么样?我们可以调用<fun>glCullFace</fun>来定义这一行为:
|
||||
|
||||
```c++
|
||||
glCullFace(GL_BACK);
|
||||
glCullFace(GL_FRONT);
|
||||
```
|
||||
|
||||
`glCullFace`函数有三个可用的选项:
|
||||
<fun>glCullFace</fun>函数有三个可用的选项:
|
||||
|
||||
* GL_BACK:只剔除背面。
|
||||
* GL_FRONT:只剔除正面。
|
||||
* GL_FRONT_AND_BACK:剔除背面和正面。
|
||||
- `GL_BACK`:只剔除背向面。
|
||||
- `GL_FRONT`:只剔除正向面。
|
||||
- `GL_FRONT_AND_BACK`:剔除正向面和背向面。
|
||||
|
||||
`glCullFace`的初始值是`GL_BACK`。另外,我们还可以告诉OpenGL使用顺时针而不是逆时针来表示正面,这通过glFrontFace来设置:
|
||||
<fun>glCullFace</fun>的初始值是<var>GL_BACK</var>。除了需要剔除的面之外,我们也可以通过调用<fun>glFrontFace</fun>,告诉OpenGL我们希望将顺时针的面(而不是逆时针的面)定义为正向面:
|
||||
|
||||
```c++
|
||||
glFrontFace(GL_CCW);
|
||||
```
|
||||
|
||||
默认值是`GL_CCW`,它代表逆时针,`GL_CW`代表顺时针顺序。
|
||||
默认值是<var>GL_CCW</var>,它代表的是逆时针的环绕顺序,另一个选项是<var>GL_CW</var>,它(显然)代表的是顺时针顺序。
|
||||
|
||||
我们可以做个小实验,告诉OpenGL现在顺时针代表正面:
|
||||
我们可以来做一个实验,告诉OpenGL现在顺时针顺序代表的是正向面:
|
||||
|
||||
```c++
|
||||
glEnable(GL_CULL_FACE);
|
||||
@@ -93,19 +92,19 @@ glCullFace(GL_BACK);
|
||||
glFrontFace(GL_CW);
|
||||
```
|
||||
|
||||
最后的结果只有背面被渲染了:
|
||||
这样的结果是只有背向面被渲染了:
|
||||
|
||||

|
||||
|
||||
要注意,你可以使用默认逆时针顺序剔除正面,来创建相同的效果:
|
||||
注意你可以仍使用默认的逆时针环绕顺序,但剔除正向面,来达到相同的效果:
|
||||
|
||||
```c
|
||||
```c++
|
||||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(GL_FRONT);
|
||||
```
|
||||
|
||||
正如你所看到的那样,面剔除是OpenGL提高效率的一个强大工具,它使应用节省运算。你必须跟踪下来哪个物体可以使用面剔除,哪些不能。
|
||||
可以看到,面剔除是一个提高OpenGL程序性能的很棒的工具。但你需要记住哪些物体能够从面剔除中获益,而哪些物体不应该被剔除。
|
||||
|
||||
## 练习
|
||||
|
||||
你可以自己重新定义一个顺时针的顶点顺序,然后用顺时针作为正面把它渲染出来吗:[解决方案](http://learnopengl.com/code_viewer.php?code=advanced/faceculling-exercise1)。
|
||||
- 你能够重新定义顶点数据,将每个三角形设置为顺时针顺序,并将顺时针的三角形设置为正向面,仍将场景渲染出来吗?:[参考解答](https://learnopengl.com/code_viewer.php?code=advanced/faceculling-exercise1)
|
13
glossary.md
13
glossary.md
@@ -317,6 +317,19 @@
|
||||
- Reverse Iterator:反向迭代器
|
||||
- Order Independent Transparency:次序无关透明度
|
||||
|
||||
## 04-04
|
||||
|
||||
- Side(面):侧
|
||||
- Face Culling:面剔除
|
||||
- Front Facing:面向
|
||||
- Back Facing:背向
|
||||
- Front Face:正向面
|
||||
- Back Face:背向面
|
||||
- Winding Order:环绕顺序
|
||||
- Front-facing Triangle:正向三角形
|
||||
- Back-facing Triangle:背向三角形
|
||||
- Cull:剔除
|
||||
|
||||
## 06-01
|
||||
|
||||
- Debugging:调试
|
||||
|
Reference in New Issue
Block a user