1
0
mirror of https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git synced 2025-08-23 04:35:28 +08:00

Rewrite 04-04

This commit is contained in:
Meow J
2017-07-04 13:39:26 +08:00
parent 38eb703343
commit a090730cdf
2 changed files with 54 additions and 42 deletions

View File

@@ -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个顶点设定一个环绕顺序。
![](../img/04/04/faceculling_windingorder.png)
正如你所看到的那样我们先定义了顶点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计算的所以当顶点着色器已经运行后。顶点就能够在观察者的观察点被看到
定义顶点顺序的时候,你应该想象对应的三角形是面向你的,所以你定义的三角形从正面看去应该是逆时针的。这样定义顶点很棒的一点是,实际的环绕顺序是在光栅化阶段进行的,也就是顶点着色器运行后。这些顶点就是从**观察者视角**所见的了
我们指定了它们以后,观察者面对的所有三角形顶点的连接顺序都是正确的,但是现在渲染的立方体另一面的三角形顶点的连接顺序被反转。最终,我们所面的三角形被视为正面朝向的三角形,后部的三角形被视为背面朝向的三角形。下图展示了这个效果:
观察者所面向的所有三角形顶点就是我们所指定的正确环绕顺序了,而立方体另一面的三角形顶点则是以相反的环绕顺序所渲染的。这样的结果就是,我们所面的三角形将会是正向三角形,而背面的三角形则是背向三角形。下面这张图显示了这个效果:
![](../img/04/04/faceculling_frontback.png)
在顶点数据中我们将两个三角形都以逆时针顺序定义正面的三角形是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);
```
最后的结果只有背面被渲染了:
这样的结果只有背面被渲染了:
![](../img/04/04/faceculling_reverse.png)
注意你可以使用默认逆时针顺序剔除正面,来创建相同的效果:
注意你可以使用默认逆时针环绕顺序,但剔除正面,来达到相同的效果:
```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)