1
0
mirror of https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git synced 2025-08-23 12:45:29 +08:00

校对 04/02

This commit is contained in:
Geequlim
2015-08-01 16:07:05 +08:00
parent 91eb0bacfd
commit caff344dcc
11 changed files with 336 additions and 325 deletions

View File

@@ -1,10 +1,14 @@
##模板测试Stencil testing
# 模板测试(Stencil testing)
本文作者JoeyDeVries由Django翻译自http://learnopengl.com
原文 | [Stencil testing](http://learnopengl.com/#!Advanced-OpenGL/Stencil-testing)
---|---
作者 | JoeyDeVries
翻译 | [Django](http://bullteacher.com/)
校对 | [Geequlim](http://geequlim.com)
像素着色器处理完fragment以后模板测试stencil test就开始执行了,和深度测试一样,它有能力丢弃fragment。仍然保留下来的fragment进入深度测试阶段,深度测试可能丢弃更多的fragment。模板测试基于另一个缓冲,这个缓冲叫做模板缓冲stencil buffer,我们被允许在渲染时更新它来获取有意思的效果。
片段着色器处理完片段之后,**模板测试(stencil test)** 就开始执行了,和深度测试一样,它能丢弃一些片段。仍然保留下来的片段进入深度测试阶段,深度测试可能丢弃更多。模板测试基于另一个缓冲,这个缓冲叫做**模板缓冲(stencil buffer)**,我们被允许在渲染时更新它来获取有意思的效果。
模板缓冲中一个模板值stencil value通常含有8位大小因此每个像素/fragment总共有256不同的模板值译注8位就是1字节大小因此和char的容量一样是256个不同值。这样我们就能将这些模板值设置为我们链接的然后当一个特定fragment有一个特定的模板值,我们就可以丢弃或保留fragment了。
模板缓冲中模板值stencil value通常是8位的因此每个片段像素共有256不同的模板值译注8位就是1字节大小因此和char的容量一样是256个不同值。这样我们就能将这些模板值设置为我们链接的然后在模板测试时根据这个模板值,我们就可以决定丢弃或保留了。
!!! Important
@@ -14,18 +18,18 @@
![image description](http://learnopengl.com/img/advanced/stencil_buffer.png)
模板缓冲先清空模板缓冲设置的零然后开启矩形一。场景中的fragment值渲染模板值包含1的那些fragment(其他的都被丢弃)。
模板缓冲先清空模板缓冲设置所有片段的模板值为0然后开启矩形片段用1填充。场景中的模板值1的那些片段才会被渲染(其他的都被丢弃)。
无论我们在渲染哪里的fragment,模板缓冲操作都允许我们把模板缓冲设置为一个特定值。改变模板缓冲的内容实际上就是对模板缓冲进行写入。在同一次(或接下来的)渲染迭代我们可以读取这些值来决定丢弃还是保留这些fragment。当使用模板缓冲的时候,你可以随心所欲,但是需要遵守下面的原则:
无论我们在渲染哪里的片段,模板缓冲操作都允许我们把模板缓冲设置为一个特定值。改变模板缓冲的内容实际上就是对模板缓冲进行写入。在同一次(或接下来的)渲染迭代我们可以读取这些值来决定丢弃还是保留这些片段。当使用模板缓冲的时候,你可以随心所欲,但是需要遵守下面的原则:
* 开启模板缓冲写入。
* 渲染物体,更新模板缓冲。
* 关闭模板缓冲写入。
* 渲染(其他)物体,这次基于模板缓冲内容丢弃特定fragment
* 渲染(其他)物体,这次基于模板缓冲内容丢弃特定片段
使用模板缓冲我们可以基于场景中已经绘制的fragment,来决定是否丢弃特定的fragment
使用模板缓冲我们可以基于场景中已经绘制的片段,来决定是否丢弃特定的片段
你可以开启GL_STENCIL_TEST来开启模板测试。接着所有渲染函数调用都会以这样或那样的方式影响到模板缓冲。
你可以开启`GL_STENCIL_TEST`来开启模板测试。接着所有渲染函数调用都会以这样或那样的方式影响到模板缓冲。
```c
glEnable(GL_STENCIL_TEST);
@@ -36,24 +40,30 @@ glEnable(GL_STENCIL_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
```
同时和深度测试的glDepthMask函数一样模板缓冲也有一个相似函数。glStencilMask允许我们给模板值设置一个位遮罩bitmask它与模板值进行与and运算决定缓冲是否可写。默认设置的位遮罩都是1这样就不会影响输出但是如果我们设置为0x00所有写入深度缓冲最后都是0。这和深度缓冲的glDepthMask(GL_FALSE)一样
同时,和深度测试的`glDepthMask`函数一样,模板缓冲也有一个相似函数。`glStencilMask`允许我们给模板值设置一个**位遮罩bitmask**,它与模板值进行按位and运算决定缓冲是否可写。默认设置的位遮罩都是1这样就不会影响输出但是如果我们设置为0x00所有写入深度缓冲最后都是0。这和深度缓冲的`glDepthMask(GL_FALSE)`很类似
```c++
glStencilMask(0xFF); // Each bit is written to the stencil buffer as is
glStencilMask(0x00); // Each bit ends up as 0 in the stencil buffer (disabling writes)
// 0xFF == 0b11111111
//此时,模板值与它进行按位与运算结果是模板值,模板缓冲可写
glStencilMask(0xFF);
// 0x00 == 0b00000000 == 0
//此时模板值与它进行按位与运算结果是0模板缓冲不可写
glStencilMask(0x00);
```
大多数情况你的模板遮罩stencil mask写为0x00或0xFF就行但是最好知道有一个选项可以自定义位遮罩。
###模板方程stencil functions
## 模板函数stencil functions
和深度测试一样,我们也有几个不同控制权,决定何时模板测试通过或失败以及它怎样影响模板缓冲。一共有两种方程可供我们使用去配置模板测试glStencilFuncglStencilOp。
和深度测试一样,我们也有几个不同控制权,决定何时模板测试通过或失败以及它怎样影响模板缓冲。一共有两种函数可供我们使用去配置模板测试:`glStencilFunc`和`glStencilOp`
glStencilFunc(GLenum func, GLint ref, GLuint mask)有三个参数:
`void glStencilFunc(GLenum func, GLint ref, GLuint mask)`函数有三个参数:
* func设置模板测试方程。这个测试方程应用到已经储存的模板值和glStencilFuncref值上可用的选项是GL_NEVERGL_LEQUALGL_GREATERGL_GEQUALGL_EQUALGL_NOTEQUALGL_ALWAYS。它们的语义和深度缓冲的方程相似。
* ref指定模板测试的引用值。模板缓冲的内容会与这个值对比。
* mask指定一个遮罩在模板测试对比引用值和储存的模板值前对它们进行与and操作初始设置为1。
* **func**:设置模板测试操作。这个测试操作应用到已经储存的模板值和`glStencilFunc`的`ref`值上,可用的选项是:`GL_NEVER`、`GL_LEQUAL`、`GL_GREATER`、`GL_GEQUAL`、`GL_EQUAL`、`GL_NOTEQUAL`、`GL_ALWAYS`。它们的语义和深度缓冲的相似。
* **ref**:指定模板测试的引用值。模板缓冲的内容会与这个值对比。
* **mask**:指定一个遮罩,在模板测试对比引用值和储存的模板值前,对它们进行按位and操作初始设置为1。
在上面简单模板的例子里,方程应该设置为:
@@ -61,52 +71,52 @@ glStencilFunc(GLenum func, GLint ref, GLuint mask)有三个参数:
glStencilFunc(GL_EQUAL, 1, 0xFF)
```
它会告诉OpenGL无论何时一个fragment模板值等于GL_EQUAL引用值1fragment就能通过测试被绘制了,否则就会被丢弃。
它会告诉OpenGL无论何时一个片段模板值等于(`GL_EQUAL`)引用值`1`,片段就能通过测试被绘制了,否则就会被丢弃。
但是glStencilFunc只描述了OpenGL对模板缓冲做什么而不是我们如何更新缓冲。这就需要glStencilOp登场了。
但是`glStencilFunc`只描述了OpenGL对模板缓冲做什么而不是描述我们如何更新缓冲。这就需要`glStencilOp`登场了。
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)包含三个选项,我们可以指定每个选项的动作:
`void glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)`函数包含三个选项,我们可以指定每个选项的动作:
* sfail如果模板测试失败将采取的动作。
* dpfail如果模板测试通过,但是深度测试失败时采取的动作。
* dppass如果深度测试和模板测试都通过,将采取的动作。
* **sfail** 如果模板测试失败将采取的动作。
* **dpfail** 如果模板测试通过,但是深度测试失败时采取的动作。
* **dppass** 如果深度测试和模板测试都通过,将采取的动作。
每个选项都可以使用下列任何一个动作。
操作 | 描述
---|---
GL_KEEP |The currently stored stencil value is kept.
GL_ZERO |The stencil value is set to 0.
GL_REPLACE| The stencil value is replaced with the reference value set with glStencilFunc.
GL_INCR |The stencil value is increased by 1 if it is lower than the maximum value.
GL_INCR_WRAP| Same as GL_INCR, but wraps it back to 0 as soon as the maximum value is exceeded.
GL_DECR |The stencil value is decreased by 1 if it is higher than the minimum value.
GL_DECR_WRAP| Same as GL_DECR, but wraps it to the maximum value if it ends up lower than 0.
---|---
GL_KEEP | 保持现有的模板值
GL_ZERO | 将模板值置为0
GL_REPLACE | 将模板值设置为用`glStencilFunc`函数设置的**ref**值
GL_INCR | 如果模板值不是最大值就将模板值+1
GL_INCR_WRAP| 与`GL_INCR`一样将模板值+1如果模板值已经是最大值则设为0
GL_DECR | 如果模板值不是最小值就将模板值-1
GL_DECR_WRAP| 与`GL_DECR`一样将模板值-1如果模板值已经是最小值则设为最大值
GL_INVERT | Bitwise inverts the current stencil buffer value.
glStencilOp函数默认设置为 (GL_KEEP, GL_KEEP, GL_KEEP) ,所以任何测试的任何结果,模板缓冲都会保留它的值。默认行为不会更新模板缓冲,所以如果你想写入模板缓冲的话,你必须像任意选项指定至少一个不同的动作。
`glStencilOp`函数默认设置为 (GL_KEEP, GL_KEEP, GL_KEEP) ,所以任何测试的任何结果,模板缓冲都会保留它的值。默认行为不会更新模板缓冲,所以如果你想写入模板缓冲的话,你必须像任意选项指定至少一个不同的动作。
使用glStencilFuncglStencilOp我们就可以指定在什么时候以及我们打算怎么样去更新模板缓冲了我们也可以指定何时让测试通过或不通过。什么时候fragment会被抛弃。
使用`glStencilFunc`和`glStencilOp`,我们就可以指定在什么时候以及我们打算怎么样去更新模板缓冲了,我们也可以指定何时让测试通过或不通过。什么时候片段会被抛弃。
###物体轮廓
## 物体轮廓
看了前面的部分你未必能理解模板测试是如何工作的,所以我们会展示一个模板测试可以实现的一个特别的和有用的功能叫做物体轮廓object outlining
看了前面的部分你未必能理解模板测试是如何工作的,所以我们会展示一个模板测试实现的一个特别的和有用的功能叫做物体轮廓object outlining
![](http://learnopengl.com/img/advanced/stencil_object_outlining.png)
物体轮廓就像它的名字所描述的那样,它能够给每个(或一个)物体创建一个有颜色的边。在策略游戏中当你打算选择一个单位的时候它特别有用。给物体加上轮廓的步骤如下:
1. 在绘制(有轮廓的)物体前把模板方程设置为GL_ALWAYS用1更新物体将被渲染的fragment
2. 渲染物体。
1. 在绘制物体前,把模板方程设置为`GL_ALWAYS`用1更新物体将被渲染的片段
2. 渲染物体,写入模板缓冲
3. 关闭模板写入和深度测试。
4. 每个物体放大一点点。
5. 使用一个不同的像素着色器输出一个纯颜色。
6. 再次绘制物体,但只是当它们的fragment的模板值不为1时才进行。
5. 使用一个不同的片段着色器用来输出一个纯颜色。
6. 再次绘制物体,但只是当它们的片段的模板值不为1时才进行。
7. 开启模板写入和深度测试。
这个过程将每个物体的fragment模板缓冲设置为1当我们绘制边框的时候我们基本上绘制的是放大版本的物体通过测试的地方,放大的版本绘制后物体就会有一个边。我们基本会使用模板缓冲丢弃所有的不是原来物体的fragment的放大的版本内容。
这个过程将每个物体的片段模板缓冲设置为1当我们绘制边框的时候我们基本上绘制的是放大版本的物体通过测试的地方,放大的版本绘制后物体就会有一个边。我们基本会使用模板缓冲丢弃所有的不是原来物体的片段的放大的版本内容。
我们先来创建一个非常基本的像素着色器它输出一个边框颜色。我们简单地设置一个固定的颜色值把这个着色器命名为shaderSingleColor
我们先来创建一个非常基本的片段着色器它输出一个边框颜色。我们简单地设置一个固定的颜色值把这个着色器命名为shaderSingleColor
```c++
void main()
@@ -115,39 +125,39 @@ void main()
}
```
我们只打算给两个箱子加上边框,所以我们不会对地面做什么。这样我们要先绘制地面,然后再绘制两个箱子(同时写入模板缓冲),接着我们绘制放大的箱子(同时丢弃前面已经绘制的箱子的那部分fragment)。
我们只打算给两个箱子加上边框,所以我们不会对地面做什么。这样我们要先绘制地面,然后再绘制两个箱子(同时写入模板缓冲),接着我们绘制放大的箱子(同时丢弃前面已经绘制的箱子的那部分片段)。
我们先开启模板测试,把将设置为无论何时、任何测试通过或失败时才采取动作:
我们先开启模板测试,设置模板、深度测试通过或失败时才采取动作:
```c++
glEnable(GL_DEPTH_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
```
如果任何测试失败我们都什么也不做,我们简单地保持深度缓冲中当前所储存着的值。如果模板测试和深度测试都成功了,我们就将储存着的模板值替换为1我们要用glStencilFunc来做这件事。
如果任何测试失败我们都什么也不做,我们简单地保持深度缓冲中当前所储存着的值。如果模板测试和深度测试都成功了,我们就将储存着的模板值替换为`1`,我们要用`glStencilFunc`来做这件事。
我们清空模板缓冲为0为箱子的所有绘制的fragment的模板缓冲更新为1
我们清空模板缓冲为0为箱子的所有绘制的片段的模板缓冲更新为1
```c++
glStencilFunc(GL_ALWAYS, 1, 0xFF); // All fragments should update the stencil buffer
glStencilMask(0xFF); // Enable writing to the stencil buffer
glStencilFunc(GL_ALWAYS, 1, 0xFF); //所有片段都要写入模板缓冲
glStencilMask(0xFF); // 设置模板缓冲为可写状态
normalShader.Use();
DrawTwoContainers();
```
使用GL_ALWAYS模板测试方程,我们确保箱子的每个fragment用模板值1更新模板缓冲。因为fragment总会通过模板测试,在我们绘制它们的地方,模板缓冲用引用值更新。
使用`GL_ALWAYS`模板测试函数,我们确保箱子的每个片段用模板值1更新模板缓冲。因为片段总会通过模板测试,在我们绘制它们的地方,模板缓冲用引用值更新。
现在箱子绘制之处模板缓冲更新为1了我们将要绘制放大的箱子但是这次关闭模板缓冲的写入
```c++
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00); // Disable writing to the stencil buffer
glStencilMask(0x00); // 禁止修改模板缓冲
glDisable(GL_DEPTH_TEST);
shaderSingleColor.Use();
DrawTwoScaledUpContainers();
```
我们把模板方程设置为GL_NOTEQUAL它保证我们只箱子上不等于1的部分这样只绘制前面绘制的箱子外围的那部分。注意我们也要关闭深度测试这样放大的的箱子也就是边框才不会被地面覆盖。
我们把模板方程设置为`GL_NOTEQUAL`它保证我们只箱子上不等于1的部分这样只绘制前面绘制的箱子外围的那部分。注意我们也要关闭深度测试这样放大的的箱子也就是边框才不会被地面覆盖。
做完之后还要保证再次开启深度缓冲。
@@ -159,7 +169,7 @@ glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glStencilMask(0x00); // Make sure we don't update the stencil buffer while drawing the floor
glStencilMask(0x00); // 绘制地板时确保关闭模板缓冲的写入
normalShader.Use();
DrawFloor()
@@ -179,9 +189,10 @@ glEnable(GL_DEPTH_TEST);
理解这段代码后面的模板测试的思路并不难以理解。如果还不明白尝试再仔细阅读上面的部分,尝试理解每个函数的作用,现在你已经看到了它的使用方法的例子。
这个边框的算法的结果在深度测试教程的那个场景中,看起来像这样:
![](http://bullteacher.com/wp-content/uploads/2015/06/stencil_scene_outlined.png)
在这里查看源码和着色器,看看完整的物体边框算法是怎样的。
在这里[查看源码](http://learnopengl.com/code_viewer.php?code=advanced/stencil_testing)和[着色器](http://learnopengl.com/code_viewer.php?code=advanced/depth_testing_func_shaders),看看完整的物体边框算法是怎样的。
!!! Important

View File

@@ -36,7 +36,7 @@ unsigned char * image = SOIL_load_image(path, &width, &height, 0, SOIL_LOAD_RGBA
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);
```
保证你在像素着色器中获取了纹理的所有4个颜色元素而不仅仅是RGB元素
保证你在片段着色器中获取了纹理的所有4个颜色元素而不仅仅是RGB元素
```c++
void main()
@@ -79,7 +79,7 @@ glBindVertexArray(0);
运行程序你将看到:
![image description](http://learnopengl.com/img/advanced/blending_no_discard.png)
出现这种情况是因为OpenGL默认是不知道如何处理alpha值的不知道何时丢弃它们。我们不得不手动做这件事。幸运的是这很简单感谢着色器吧。GLSL为我们提供了discard命令它保证了fragment不会被进一步处理这样就不会进入颜色缓冲。有了这个命令我们就可以在像素着色器中检查一个fragment是否有在一定的阈限下的alpha值如果有那么丢弃这个fragment就好像它从来都没被处理过一样
出现这种情况是因为OpenGL默认是不知道如何处理alpha值的不知道何时丢弃它们。我们不得不手动做这件事。幸运的是这很简单感谢着色器吧。GLSL为我们提供了discard命令它保证了fragment不会被进一步处理这样就不会进入颜色缓冲。有了这个命令我们就可以在片段着色器中检查一个fragment是否有在一定的阈限下的alpha值如果有那么丢弃这个fragment就好像它从来都没被处理过一样
```c++
#version 330 core
@@ -98,7 +98,7 @@ void main()
}
```
在这儿我们检查被采样纹理颜色包含着一个低于0.1这个阈限的alpha值如果有就丢弃fragment。这个像素着色器能够保证我们只渲染哪些不是完全透明的fragment。现在我们来看看效果
在这儿我们检查被采样纹理颜色包含着一个低于0.1这个阈限的alpha值如果有就丢弃fragment。这个片段着色器能够保证我们只渲染哪些不是完全透明的fragment。现在我们来看看效果
![image description](http://learnopengl.com/img/advanced/blending_discard.png)
@@ -131,7 +131,7 @@ C¯result = C¯source Fsource + C¯destination Fdestination
* Fsource源因子。设置了对源颜色的alpha值影响。
* Fdestination目标因子。设置了对目标颜色的alpha影响。
像素着色器运行和所有的测试就通过了以后混合方程才能自由执行fragment的颜色输出当前它在颜色缓冲中前面的fragment的颜色在当前fragment之前储存。源和目标颜色会自动被OpenGL设置但是源和目标因子可以让我们自由设置。我们来看一个简单的例子
片段着色器运行和所有的测试就通过了以后混合方程才能自由执行fragment的颜色输出当前它在颜色缓冲中前面的fragment的颜色在当前fragment之前储存。源和目标颜色会自动被OpenGL设置但是源和目标因子可以让我们自由设置。我们来看一个简单的例子
![](http://learnopengl.com/img/advanced/blending_equation.png)
@@ -204,7 +204,7 @@ glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
```
由于我们开启了混合就不需要丢弃fragment了所以我们把像素着色器设置为原来的那个版本:
由于我们开启了混合就不需要丢弃fragment了所以我们把片段着色器设置为原来的那个版本:
```c++
#version 330 core

View File

@@ -2,7 +2,7 @@
本文作者JoeyDeVries由Django翻译自http://learnopengl.com
尝试在头脑中想象一下有一个3D立方体你从任何一个方向去看他你最多可以一次看到多少个面。如果你的想象力不是过于丰富你最终最多能数出来的面是3个。你可以从一个立方体的任意位置和方向上去看它但是你永远不能看到多于3个面。所以我们为何还要去绘制那三个不会显示出来的3个面呢。如果我们可以以某种方式丢弃它们我们会提高像素着色器50%的性能!
尝试在头脑中想象一下有一个3D立方体你从任何一个方向去看他你最多可以一次看到多少个面。如果你的想象力不是过于丰富你最终最多能数出来的面是3个。你可以从一个立方体的任意位置和方向上去看它但是你永远不能看到多于3个面。所以我们为何还要去绘制那三个不会显示出来的3个面呢。如果我们可以以某种方式丢弃它们我们会提高片段着色器50%的性能!
!!! Important
@@ -12,7 +12,7 @@
这的确是个好主意,但是有个问题需要我们去解决:我们如何知道某个面在观察者的视野中不会出现呢?如果我们去想象任何封闭的形状,它每个面有两面。一面面向用户,另一面背对用户。假如我们只渲染面向观察者的面会怎样?
这正是面剔除所做的face culling。OpenGL检查所有正面朝向front facing观察者的面并渲染它们而丢弃所有背面朝向back facing的面这样就节约了我们很多像素着色器的命令它们很昂贵。我们必须告诉OpenGL我们使用的哪个面是正面哪个面是反面。OpenGL使用一种聪明的手段解决这个问题——分析顶点数据的链接顺序winding order
这正是面剔除所做的face culling。OpenGL检查所有正面朝向front facing观察者的面并渲染它们而丢弃所有背面朝向back facing的面这样就节约了我们很多片段着色器的命令它们很昂贵。我们必须告诉OpenGL我们使用的哪个面是正面哪个面是反面。OpenGL使用一种聪明的手段解决这个问题——分析顶点数据的链接顺序winding order

View File

@@ -224,7 +224,7 @@ void main()
}
```
没有花哨的地方。像素着色器更简洁,因为我们做的唯一一件事是从纹理采样:
没有花哨的地方。片段着色器更简洁,因为我们做的唯一一件事是从纹理采样:
```c++
#version 330 core
@@ -272,7 +272,7 @@ glBindVertexArray(0);
你可以从这里得到应用的源码。
然而这有什么好处呢?好处就是我们现在可以自由的获取已经渲染场景中的任何像素,然后把它当作一个纹理图像了,我们可以在像素着色器中创建一些有意思的效果。所有这些有意思的效果统称为后处理特效。
然而这有什么好处呢?好处就是我们现在可以自由的获取已经渲染场景中的任何像素,然后把它当作一个纹理图像了,我们可以在片段着色器中创建一些有意思的效果。所有这些有意思的效果统称为后处理特效。
###后处理
@@ -281,7 +281,7 @@ glBindVertexArray(0);
###反相
我们已经取得了渲染输出的每个颜色,所以在像素着色器里返回这些颜色的反色并不难。我们得到屏幕纹理的颜色然后用1.0减去它:
我们已经取得了渲染输出的每个颜色,所以在片段着色器里返回这些颜色的反色并不难。我们得到屏幕纹理的颜色然后用1.0减去它:
```c++
void main()
@@ -335,7 +335,7 @@ kernel是一个长得有点像一个小矩阵的数值数组它中间的值
你在网上能找到的kernel的例子大多数都是所有值加起来等于1如果加起来不等于1就意味着这个纹理值比原来更大或者更小了。
kernel对于后处理来说非常管用因为用起来简单。网上能找到有很多实例为了能用上kernel我们还得改改像素着色器。这里假设每个kernel都是3×3实际上大多数都是3×3
kernel对于后处理来说非常管用因为用起来简单。网上能找到有很多实例为了能用上kernel我们还得改改片段着色器。这里假设每个kernel都是3×3实际上大多数都是3×3
```c++
const float offset = 1.0 / 300;
@@ -373,7 +373,7 @@ void main()
}
```
像素着色器中我们先为每个四周的纹理坐标创建一个9个vec2偏移量的数组。偏移量是一个简单的常数你可以设置为自己喜欢的。接着我们定义kernel这里应该是一个锐化kernel它通过一种有趣的方式从所有周边的像素采样对每个颜色值进行锐化。最后在采样的时候我们把每个偏移量加到当前纹理坐标上然后用加在一起的kernel的值乘以这些纹理值。
片段着色器中我们先为每个四周的纹理坐标创建一个9个vec2偏移量的数组。偏移量是一个简单的常数你可以设置为自己喜欢的。接着我们定义kernel这里应该是一个锐化kernel它通过一种有趣的方式从所有周边的像素采样对每个颜色值进行锐化。最后在采样的时候我们把每个偏移量加到当前纹理坐标上然后用加在一起的kernel的值乘以这些纹理值。
这个锐化的kernel看起来像这样

View File

@@ -71,7 +71,7 @@ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
```
像素着色器中我们也必须使用一个不同的采样器——samplerCube用它来从texture函数中采样但是这次使用的是一个vec3方向向量取代vec2。下面是一个像素着色器使用了cubemap的例子
片段着色器中我们也必须使用一个不同的采样器——samplerCube用它来从texture函数中采样但是这次使用的是一个vec3方向向量取代vec2。下面是一个片段着色器使用了cubemap的例子
```c++
@@ -182,7 +182,7 @@ void main()
}
```
注意,顶点着色器有意思的地方在于我们把输入的位置向量作为输出给像素着色器的纹理坐标。像素着色器就会把它们作为输入去采样samplerCube
注意,顶点着色器有意思的地方在于我们把输入的位置向量作为输出给片段着色器的纹理坐标。片段着色器就会把它们作为输入去采样samplerCube
```c++
#version 330 core
@@ -197,7 +197,7 @@ void main()
}
```
像素着色器比较明了。我们把顶点属性中的位置向量作为纹理的方向向量使用它们从cubemap采样纹理值。渲染天空盒现在很简单我们有了一个cubemap纹理我们简单绑定cubemap纹理天空盒就自动地用天空盒的cubemap填充了。为了绘制天空盒我们将把它作为场景中第一个绘制的物体并且关闭深度写入。这样天空盒才能成为所有其他物体的背景来绘制出来。
片段着色器比较明了。我们把顶点属性中的位置向量作为纹理的方向向量使用它们从cubemap采样纹理值。渲染天空盒现在很简单我们有了一个cubemap纹理我们简单绑定cubemap纹理天空盒就自动地用天空盒的cubemap填充了。为了绘制天空盒我们将把它作为场景中第一个绘制的物体并且关闭深度写入。这样天空盒才能成为所有其他物体的背景来绘制出来。
```c++
@@ -228,9 +228,9 @@ glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));
#### 优化
现在我们在渲染场景中的其他物体之前渲染了天空盒。这么做没错,但是不怎么高效。如果我们先渲染了天空盒,那么我们就是在为每一个屏幕上的像素运行像素着色器即使天空盒只有部分在显示着fragment可以使用前置深度测试early depth testing简单地被丢弃这样就节省了我们宝贵的带宽。
现在我们在渲染场景中的其他物体之前渲染了天空盒。这么做没错,但是不怎么高效。如果我们先渲染了天空盒,那么我们就是在为每一个屏幕上的像素运行片段着色器即使天空盒只有部分在显示着fragment可以使用前置深度测试early depth testing简单地被丢弃这样就节省了我们宝贵的带宽。
所以最后渲染天空盒就能够给我们带来轻微的性能提升。采用这种方式深度缓冲被全部物体的深度值完全填充所以我们只需要渲染通过前置深度测试的那部分天空的fragment就行了而且能显著减少像素着色器的调用。问题是天空盒是个1×1×1的立方体极有可能会渲染失败因为极有可能通不过深度测试。简单地不用深度测试渲染它也不是解决方案这是因为天空盒会在之后覆盖所有的场景中其他物体。我们需要耍个花招让深度缓冲相信天空盒的深度缓冲有着最大深度值1.0,如此只要有个物体存在深度测试就会失败,看似物体就在它前面了。
所以最后渲染天空盒就能够给我们带来轻微的性能提升。采用这种方式深度缓冲被全部物体的深度值完全填充所以我们只需要渲染通过前置深度测试的那部分天空的fragment就行了而且能显著减少片段着色器的调用。问题是天空盒是个1×1×1的立方体极有可能会渲染失败因为极有可能通不过深度测试。简单地不用深度测试渲染它也不是解决方案这是因为天空盒会在之后覆盖所有的场景中其他物体。我们需要耍个花招让深度缓冲相信天空盒的深度缓冲有着最大深度值1.0,如此只要有个物体存在深度测试就会失败,看似物体就在它前面了。
在坐标系教程中我们说过透视除法perspective division是在顶点着色器运行之后执行的把gl_Position的xyz坐标除以w元素。我们从深度测试教程了解到除法结果的z元素等于顶点的深度值。利用这个信息我们可以把输出位置的z元素设置为它的w元素这样就会导致z元素等于1.0了因为当透视除法应用后它的z元素转换为w/w = 1.0
@@ -263,7 +263,7 @@ void main()
我们基于观察方向向量I和物体的法线向量N计算出反射向量R。我们可以使用GLSL的内建函数reflect来计算这个反射向量。最后向量R作为一个方向向量对cubemap进行索引/采样,返回一个环境的颜色值。最后的效果看起来就像物体反射了天空盒。
因为我们在场景中已经设置了一个天空盒,创建反射就不难了。我们改变一下箱子使用的那个像素着色器,给箱子一个反射属性:
因为我们在场景中已经设置了一个天空盒,创建反射就不难了。我们改变一下箱子使用的那个片段着色器,给箱子一个反射属性:
```c++
#version 330 core
@@ -304,7 +304,7 @@ void main()
}
```
我们用了法线向量所以我们打算使用一个法线矩阵normal matrix变换它们。Position输出的向量是一个世界空间位置向量。顶点着色器输出的Position用来在像素着色器计算观察方向向量。
我们用了法线向量所以我们打算使用一个法线矩阵normal matrix变换它们。Position输出的向量是一个世界空间位置向量。顶点着色器输出的Position用来在片段着色器计算观察方向向量。
因为我们使用法线你还得更新顶点数据更新属性指针。还要确保设置cameraPos的uniform。
@@ -353,7 +353,7 @@ glBindVertexArray(0);
我们使用这些折射指数来计算光线通过两个材质的比率。在我们的例子中,光线/视线从空气进入玻璃如果我们假设箱子是玻璃做的所以比率是1.001.52 = 0.658。
我们已经绑定了cubemap提供了定点数据设置了摄像机位置的uniform。现在只需要改变像素着色器:
我们已经绑定了cubemap提供了定点数据设置了摄像机位置的uniform。现在只需要改变片段着色器:
```c++
void main()

View File

@@ -8,7 +8,7 @@
### GLSL的内建变量
着色器是很小的如果我们需要从当前着色器以外的别的资源里的数据那么我们就不得不穿给它。我们学过了使用顶点属性、uniform和采样器可以实现这个目标。GLSL有几个以gl_为前缀的变量使我们有一个额外的手段来获取和写入数据。我们已经看到了两个gl_Position和gl_FragCoord前一个是顶点着色器的输出向量后一个是像素着色器的变量。
着色器是很小的如果我们需要从当前着色器以外的别的资源里的数据那么我们就不得不穿给它。我们学过了使用顶点属性、uniform和采样器可以实现这个目标。GLSL有几个以gl_为前缀的变量使我们有一个额外的手段来获取和写入数据。我们已经看到了两个gl_Position和gl_FragCoord前一个是顶点着色器的输出向量后一个是片段着色器的变量。
我们会讨论几个有趣的GLSL内建变量并向你解释为什么它们对我们来说很有好处。注意我们不会讨论到GLSL中所有的内建变量因此如果你想看到所有的内建变量还是最好去看[OpenGL的wiki](http://www.opengl.org/wiki/Built-in_Variable_(GLSL)。
@@ -52,9 +52,9 @@ gl_VertexID是个整型变量它储存着我们绘制的当前顶点的ID。
尽管目前看似没用,但是我们最好知道我们能获取这样的信息。
#### 像素着色器的变量
#### 片段着色器的变量
像素着色器中也有一些有趣的变量。GLSL给我们提供了两个有意思的输入变量它们是gl_FragCoord和gl_FrontFacing。
片段着色器中也有一些有趣的变量。GLSL给我们提供了两个有意思的输入变量它们是gl_FragCoord和gl_FrontFacing。
##### gl_FragCoord
@@ -62,7 +62,7 @@ gl_VertexID是个整型变量它储存着我们绘制的当前顶点的ID。
gl_FragCoord的x和y元素是这个fragment窗口空间坐标window-space coordinate。它们的起始处是窗口的左下角。如果我们的窗口是800×600的那么一个fragment的窗口空间坐标x的范围就在0到800之间y在0到600之间。
我们可以使用像素着色器基于fragment的窗口坐标计算出一个不同的颜色。gl_FragCoord变量的一个常用的方式是与一个不同的fragment计算出来的视频输出进行对比通常在技术演示中常见。比如我们可以把屏幕分为两个部分窗口的左侧渲染一个输出窗口的右边渲染另一个输出。下面是一个基于fragment的窗口坐标的位置的不同输出不同的颜色的像素着色器:
我们可以使用片段着色器基于fragment的窗口坐标计算出一个不同的颜色。gl_FragCoord变量的一个常用的方式是与一个不同的fragment计算出来的视频输出进行对比通常在技术演示中常见。比如我们可以把屏幕分为两个部分窗口的左侧渲染一个输出窗口的右边渲染另一个输出。下面是一个基于fragment的窗口坐标的位置的不同输出不同的颜色的片段着色器:
```c++
@@ -79,13 +79,13 @@ void main()
![](http://bullteacher.com/wp-content/uploads/2015/06/advanced_glsl_fragcoord.png)
我们现在可以计算出两个完全不同的像素着色器结果,每个显示在窗口的一端。这对于测试不同的光照技术很有好处。
我们现在可以计算出两个完全不同的片段着色器结果,每个显示在窗口的一端。这对于测试不同的光照技术很有好处。
gl_FrontFacing
像素着色器另一个有意思的输入变量是gl_FrontFacing变量。在面剔除教程中我们提到过OpenGL可以根据顶点绘制顺序弄清楚一个面是正面还是背面。如果我们不适用面剔除那么gl_FrontFacing变量能告诉我们当前fragment是一个正面的一部分还是背面的一部分。然后我们可以决定做一些事情比如为正面计算出不同的颜色。
片段着色器另一个有意思的输入变量是gl_FrontFacing变量。在面剔除教程中我们提到过OpenGL可以根据顶点绘制顺序弄清楚一个面是正面还是背面。如果我们不适用面剔除那么gl_FrontFacing变量能告诉我们当前fragment是一个正面的一部分还是背面的一部分。然后我们可以决定做一些事情比如为正面计算出不同的颜色。
gl_FrontFacing变量是一个布尔值如果fragment是正面的一部分那么就是true否则就是false。这样我们可以创建一个立方体里面和外面使用不同的纹理
@@ -124,9 +124,9 @@ gl_FragDepth = 0.0f; // This fragment now has a depth value of 0.0f
如果着色器中没有像gl_FragDepth变量写入什么它就会自动采用gl_FragCoord.z的值。
我们自己设置深度值有一个显著缺点,因为只要我们在像素着色器中对gl_FragDepth写入什么OpenGL就会关闭所有的前置深度测试。它被关闭的原因是在我们运行像素着色器之前OpenGL搞不清像素的深度值因为像素着色器可能会完全改变这个深度值。
我们自己设置深度值有一个显著缺点,因为只要我们在片段着色器中对gl_FragDepth写入什么OpenGL就会关闭所有的前置深度测试。它被关闭的原因是在我们运行片段着色器之前OpenGL搞不清像素的深度值因为片段着色器可能会完全改变这个深度值。
你也需要考虑到gl_FragDepth写入所带来的性能的下降。然而从OpenGL4.2起,我们仍然可以对二者进行一定的调和,这需要在像素着色器的顶部使用深度条件depth condition来重新声明gl_FragDepth
你也需要考虑到gl_FragDepth写入所带来的性能的下降。然而从OpenGL4.2起,我们仍然可以对二者进行一定的调和,这需要在片段着色器的顶部使用深度条件depth condition来重新声明gl_FragDepth
```c++
layout (depth_<condition>) out float gl_FragDepth;
@@ -143,7 +143,7 @@ unchanged |如果写入gl_FragDepth, 你就会写gl_FragCoord.z
如果把深度条件定义为greater或lessOpenGL会假定你只写入比当前的像素深度值的深度值大或小的。
下面是一个在像素着色器里增加深度值的例子,不过仍可开启前置深度测试:
下面是一个在片段着色器里增加深度值的例子,不过仍可开启前置深度测试:
```c++
#version 330 core
@@ -163,7 +163,7 @@ void main()
### Interface blocks接口块
到目前位置,每次我们打算从顶点向像素着色器发送数据,我们都会声明一个相互匹配的输入/输入变量。从一个着色器向另一个着色器发送数据,一次将它们声明好是最简单的方式,但是随着应用变得越来越大,你也许会打算发送的不仅仅是变量,最好还可以包括数组和结构体。
到目前位置,每次我们打算从顶点向片段着色器发送数据,我们都会声明一个相互匹配的输入/输入变量。从一个着色器向另一个着色器发送数据,一次将它们声明好是最简单的方式,但是随着应用变得越来越大,你也许会打算发送的不仅仅是变量,最好还可以包括数组和结构体。
为了帮助我们组织这些变量GLSL为我们提供了一些叫做interface blocks的东西好让我们能够组织这些变量。声明interface block和声明struct有点像不同之处是它现在基于块block使用in和out关键字来声明最后它将成为一个输入或输出块block
@@ -190,7 +190,7 @@ void main()
这次我们声明一个叫做vs_out的interface block它把我们需要发送给下个阶段着色器的所有输出变量组合起来。虽然这是一个微不足道的例子但是你可以想象一下它的确能够帮助我们组织着色器的输入和输出。当我们希望把着色器的输入和输出组织成数组的时候它就变得很有用我们会在下节几何着色器geometry中见到。
然后,我们还需要在下一个着色器——像素着色器中声明一个输入interface block。块名block name应该是一样的但是实例名可以是任意的。
然后,我们还需要在下一个着色器——片段着色器中声明一个输入interface block。块名block name应该是一样的但是实例名可以是任意的。
```c++
#version 330 core
@@ -388,7 +388,7 @@ void main()
}
```
这儿没什么特别的除了我们现在使用了一个带有std140布局的uniform block。我们在例程中将显示4个立方体每个立方体都使用一个不同的着色器程序。4个着色器程序使用同样的顶点着色器但是它们将使用各自的像素着色器,每个像素着色器输出一个单色。
这儿没什么特别的除了我们现在使用了一个带有std140布局的uniform block。我们在例程中将显示4个立方体每个立方体都使用一个不同的着色器程序。4个着色器程序使用同样的顶点着色器但是它们将使用各自的片段着色器,每个片段着色器输出一个单色。
首先我们把顶点着色器的uniform block设置为绑定点0。注意我们必须为每个着色器做这件事。
@@ -457,7 +457,7 @@ glBindVertexArray(0);
![](http://learnopengl.com/img/advanced/advanced_glsl_uniform_buffer_objects.png)
通过改变模型矩阵,每个立方体都移动到窗口的一边,由于像素着色器不同物体的颜色也不同。这是一个相对简单的场景我们可以使用uniform缓冲对象但是任何大型渲染程序有成百上千的活动着色程序彼时uniform缓冲对象就会闪闪发光了。
通过改变模型矩阵,每个立方体都移动到窗口的一边,由于片段着色器不同物体的颜色也不同。这是一个相对简单的场景我们可以使用uniform缓冲对象但是任何大型渲染程序有成百上千的活动着色程序彼时uniform缓冲对象就会闪闪发光了。
你可以[在这里获得例程的完整源码](http://www.learnopengl.com/code_viewer.php?code=advanced/advanced_glsl_uniform_buffer_objects)。

View File

@@ -2,7 +2,7 @@
本文作者JoeyDeVries由Django翻译自http://learnopengl.com
在顶点和像素着色器张志坚有一个可选的着色器阶段叫做几何着色器geometry shader。几何着色器以一个或多个表示为一个单独基本图形primitive的顶点作为输入比如可以是一个点或者三角形。几何着色器在将这些顶点发送到下一个着色阶段之前可以将这些顶点转变为它认为合适的内容。几何着色器有意思的地方在于它可以把一个或多个顶点转变为完全不同的基本图形primitive从而生成比原来多得多的顶点。
在顶点和片段着色器张志坚有一个可选的着色器阶段叫做几何着色器geometry shader。几何着色器以一个或多个表示为一个单独基本图形primitive的顶点作为输入比如可以是一个点或者三角形。几何着色器在将这些顶点发送到下一个着色阶段之前可以将这些顶点转变为它认为合适的内容。几何着色器有意思的地方在于它可以把一个或多个顶点转变为完全不同的基本图形primitive从而生成比原来多得多的顶点。
我们直接用一个例子深入了解一下:
@@ -120,7 +120,7 @@ void main()
}
```
我们会简单地为所有点输出绿色,我们直接在像素着色器里进行硬编码:
我们会简单地为所有点输出绿色,我们直接在片段着色器里进行硬编码:
```c++
#version 330 core
@@ -163,7 +163,7 @@ void main() {
现在这个几何着色器应该很容易理解了。它简单地将它接收到的输入的无修改的顶点位置发射出去然后生成一个point基本图形。
一个几何着色器需要像顶点和像素着色器一样被编译和链接但是这次我们将使用GL_GEOMETRY_SHADER作为着色器的类型来创建这个着色器
一个几何着色器需要像顶点和片段着色器一样被编译和链接但是这次我们将使用GL_GEOMETRY_SHADER作为着色器的类型来创建这个着色器
```c++
geometryShader = glCreateShader(GL_GEOMETRY_SHADER);
@@ -174,7 +174,7 @@ glAttachShader(program, geometryShader);
glLinkProgram(program);
```
编译着色器的代码和顶点、像素着色器的基本一样。要记得检查编译和链接错误!
编译着色器的代码和顶点、片段着色器的基本一样。要记得检查编译和链接错误!
如果你现在编译和运行,就会看到和下面相似的结果:
@@ -225,7 +225,7 @@ void main()
```
这个几何着色器生成5个顶点每个顶点是点point的位置加上一个偏移量来组成一个大triangle strip。接着最后的基本图形被像素化像素着色器处理整个triangle strip结果是为我们绘制的每个点生成一个绿房子
这个几何着色器生成5个顶点每个顶点是点point的位置加上一个偏移量来组成一个大triangle strip。接着最后的基本图形被像素化片段着色器处理整个triangle strip结果是为我们绘制的每个点生成一个绿房子
![](http://learnopengl.com/img/advanced/geometry_shader_houses.png)
@@ -285,7 +285,7 @@ in VS_OUT {
out vec3 fColor;
```
因为像素着色器只需要一个已进行了插值的颜色传送多个颜色没有意义。fColor向量这样就不是一个数组而是一个单一的向量。当发射一个顶点时为了它的像素着色器运行每个顶点都会储存最后在fColor中储存的值。对于这些房子来说我们可以在第一个顶点被发射对整个房子上色前只使用来自顶点着色器的颜色填充fColor一次
因为片段着色器只需要一个已进行了插值的颜色传送多个颜色没有意义。fColor向量这样就不是一个数组而是一个单一的向量。当发射一个顶点时为了它的片段着色器运行每个顶点都会储存最后在fColor中储存的值。对于这些房子来说我们可以在第一个顶点被发射对整个房子上色前只使用来自顶点着色器的颜色填充fColor一次
```c++
fColor = gs_in[0].color; // gs_in[0] since there's only one input vertex
@@ -482,7 +482,7 @@ void main()
到现在为止像这样的几何着色器的内容就不言自明了。需要注意的是我们我们把法线向量乘以一个MAGNITUDE向量来限制显示出的法线向量的大小否则它们就太大了
由于把法线显示出来通常用于调试的目的,我们可以在像素着色器的帮助下把它们显示为单色的线(如果你愿意也可以更炫一点)。
由于把法线显示出来通常用于调试的目的,我们可以在片段着色器的帮助下把它们显示为单色的线(如果你愿意也可以更炫一点)。
```c++
#version 330 core

View File

@@ -44,7 +44,7 @@ GLfloat quadVertices[] = {
};
```
像素着色器接收从顶点着色器发送来的颜色向量,设置为它的颜色输出,从而为四边形上色:
片段着色器接收从顶点着色器发送来的颜色向量,设置为它的颜色输出,从而为四边形上色:
```c++
#version 330 core

View File

@@ -20,11 +20,11 @@
为了理解什么是多采样以及它是如何解决走样的问题的我们先要更深入了解一个OpenGL像素器的工作方式。
像素器是是你的最终的经处理的顶点和像素着色器之间的所有算法和处理的集合。像素器将属于一个基本图形的所有顶点转化为一系列fragment。顶点坐标理论上可以含有任何坐标但fragment却不是这样这是因为它们与你的窗口的解析度有关。几乎永远都不会有顶点坐标和fragment的一对一映射所以像素器必须以某种方式决定每个特定顶点最终结束于哪个fragment/屏幕坐标上。
像素器是是你的最终的经处理的顶点和片段着色器之间的所有算法和处理的集合。像素器将属于一个基本图形的所有顶点转化为一系列fragment。顶点坐标理论上可以含有任何坐标但fragment却不是这样这是因为它们与你的窗口的解析度有关。几乎永远都不会有顶点坐标和fragment的一对一映射所以像素器必须以某种方式决定每个特定顶点最终结束于哪个fragment/屏幕坐标上。
![](http://learnopengl.com/img/advanced/anti_aliasing_rasterization.png)
这里我们看到一个屏幕像素网格每个像素中心包含一个采样点sample point它被用来决定一个像素是否被三角形所覆盖。红色的采样点如果被三角形覆盖那么就会为这个被覆盖像屏幕素生成一个fragment。即使三角形覆盖了部分屏幕像素但是采样点没被覆盖这个像素仍然不会任何像素着色器影响到。
这里我们看到一个屏幕像素网格每个像素中心包含一个采样点sample point它被用来决定一个像素是否被三角形所覆盖。红色的采样点如果被三角形覆盖那么就会为这个被覆盖像屏幕素生成一个fragment。即使三角形覆盖了部分屏幕像素但是采样点没被覆盖这个像素仍然不会任何片段着色器影响到。
你可能已经明白走样的原因来自何处了。三角形渲染后的版本最后在你的屏幕上是这样的:
@@ -36,21 +36,21 @@
![](http://learnopengl.com/img/advanced/anti_aliasing_sample_points.png)
左侧的图显示了我们普通决定一个三角形的覆盖范围的方式。这个像素并不会运行一个像素着色器这就仍保持空白因为它的采样点没有被三角形所覆盖。右边的图展示了多采样的版本每个像素包含4个采样点。这里我们可以看到只有2个采样点被三角形覆盖。
左侧的图显示了我们普通决定一个三角形的覆盖范围的方式。这个像素并不会运行一个片段着色器这就仍保持空白因为它的采样点没有被三角形所覆盖。右边的图展示了多采样的版本每个像素包含4个采样点。这里我们可以看到只有2个采样点被三角形覆盖。
!!! Important
采样点的数量是任意的,更多的采样点能带来更精确的覆盖率。
多采样开始变得有趣了。2个子样本被三角覆盖下一步是决定这个像素的颜色。我们原来猜测我们会为每个被覆盖的子样本运行像素着色器,然后对每个像素的子样本的颜色进行平均化。例子的那种情况,我们在插值的顶点数据的每个子样本上运行像素着色器,然后将这些采样点的最终颜色储存起来。幸好,它不是这么运作的,因为这等于说我们必须运行更多的像素着色器,会明显降低性能。
多采样开始变得有趣了。2个子样本被三角覆盖下一步是决定这个像素的颜色。我们原来猜测我们会为每个被覆盖的子样本运行片段着色器,然后对每个像素的子样本的颜色进行平均化。例子的那种情况,我们在插值的顶点数据的每个子样本上运行片段着色器,然后将这些采样点的最终颜色储存起来。幸好,它不是这么运作的,因为这等于说我们必须运行更多的片段着色器,会明显降低性能。
MSAA的真正工作方式是每个像素只运行一次像素着色器,无论多少子样本被三角形所覆盖。像素着色器运行着插值到像素中心的顶点数据最后颜色被储存近每个被覆盖的子样本中每个像素的所有颜色接着将平均化每个像素最终有了一个唯一颜色。在前面的图片中4个样本中只有2个被覆盖像素的颜色将以三角形的颜色进行平均化颜色同时也被储存到其他2个采样点最后生成的是一种浅蓝色。
MSAA的真正工作方式是每个像素只运行一次片段着色器,无论多少子样本被三角形所覆盖。片段着色器运行着插值到像素中心的顶点数据最后颜色被储存近每个被覆盖的子样本中每个像素的所有颜色接着将平均化每个像素最终有了一个唯一颜色。在前面的图片中4个样本中只有2个被覆盖像素的颜色将以三角形的颜色进行平均化颜色同时也被储存到其他2个采样点最后生成的是一种浅蓝色。
结果是,颜色缓冲中所有基本图形的边都生成了更加平滑的样式。让我们看看当再次决定前面的三角形覆盖范围时多样本看起来是这样的:
![](http://learnopengl.com/img/advanced/anti_aliasing_rasterization_samples.png)
这里每个像素包含着4个子样本不相关的已被隐藏蓝色的子样本是被三角形覆盖了的灰色的没有被覆盖。三角形内部区域中的所有像素都会运行一次像素着色器它输出的颜色被储存到所有4个子样本中。三角形的边缘并不是所有的子样本都会被覆盖所以像素着色器的结果仅储存在部分子样本中。根据被覆盖子样本的数量,最终的像素颜色由三角形颜色和其他子样本所储存的颜色所决定。
这里每个像素包含着4个子样本不相关的已被隐藏蓝色的子样本是被三角形覆盖了的灰色的没有被覆盖。三角形内部区域中的所有像素都会运行一次片段着色器它输出的颜色被储存到所有4个子样本中。三角形的边缘并不是所有的子样本都会被覆盖所以片段着色器的结果仅储存在部分子样本中。根据被覆盖子样本的数量,最终的像素颜色由三角形颜色和其他子样本所储存的颜色所决定。
大致上来说,如果更多的采样点被覆盖,那么像素的颜色就会更接近于三角形。如果我们用早期使用的三角形的颜色填充像素,我们会获得这样的结果:
@@ -142,7 +142,7 @@ glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT,
你可以[在这里找到源代码](http://learnopengl.com/code_viewer.php?code=advanced/anti_aliasing_framebuffers)。
但是如果我们打算使用一个多采样帧缓冲的纹理结果来做这件事,就像后处理一样会怎样?我们不能在像素着色器中直接使用多采样纹理。我们可以做的事情是把多缓冲位块传送(Blit)到另一个带有非多采样纹理附件的FBO中。之后我们使用这个普通的颜色附件纹理进行后处理通过多采样来对一个图像渲染进行后处理效率很高。这意味着我们必须生成一个新的FBO它仅作为一个将多采样缓冲还原为一个我们可以在像素着色器中使用的普通2D纹理中介。伪代码是这样的
但是如果我们打算使用一个多采样帧缓冲的纹理结果来做这件事,就像后处理一样会怎样?我们不能在片段着色器中直接使用多采样纹理。我们可以做的事情是把多缓冲位块传送(Blit)到另一个带有非多采样纹理附件的FBO中。之后我们使用这个普通的颜色附件纹理进行后处理通过多采样来对一个图像渲染进行后处理效率很高。这意味着我们必须生成一个新的FBO它仅作为一个将多采样缓冲还原为一个我们可以在片段着色器中使用的普通2D纹理中介。伪代码是这样的
```c++
GLuint msFBO = CreateFBOWithMultiSampledAttachments();