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

@@ -52,8 +52,8 @@ glDepthFunc(GL_LESS);
运算符|描述 运算符|描述
----------|------------------ ----------|------------------
GL_ALWAYS |永远通过测试 GL_ALWAYS |永远通过测试
GL_NEVER |永远不通过测试 GL_NEVER |永远不通过测试
GL_LESS |在片段深度值小于缓冲区的深度时通过测试 GL_LESS |在片段深度值小于缓冲区的深度时通过测试
GL_EQUAL |在片段深度值等于缓冲区的深度时通过测试 GL_EQUAL |在片段深度值等于缓冲区的深度时通过测试
GL_LEQUAL |在片段深度值小于等于缓冲区的深度时通过测试 GL_LEQUAL |在片段深度值小于等于缓冲区的深度时通过测试
@@ -153,14 +153,14 @@ out vec4 color;
float LinearizeDepth(float depth) float LinearizeDepth(float depth)
{ {
float near = 0.1; float near = 0.1;
float far = 100.0; float far = 100.0;
float z = depth * 2.0 - 1.0; // Back to NDC float z = depth * 2.0 - 1.0; // Back to NDC
return (2.0 * near) / (far + near - z * (far - near)); return (2.0 * near) / (far + near - z * (far - near));
} }
void main() void main()
{ {
float depth = LinearizeDepth(gl_FragCoord.z); float depth = LinearizeDepth(gl_FragCoord.z);
color = vec4(vec3(depth), 1.0f); color = vec4(vec3(depth), 1.0f);
} }

View File

@@ -1,31 +1,35 @@
##模板测试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 !!! Important
每个窗口库都需要为你设置模板缓冲。GLFW自动做了这件事所以你不必告诉GLFW去创建它但是其他库可能没默认创建模板库所以一定要查看你使用的库的文档。 每个窗口库都需要为你设置模板缓冲。GLFW自动做了这件事所以你不必告诉GLFW去创建它但是其他库可能没默认创建模板库所以一定要查看你使用的库的文档。
下面是一个模板缓冲的简单例子: 下面是一个模板缓冲的简单例子:
![image description](http://learnopengl.com/img/advanced/stencil_buffer.png) ![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 ```c
glEnable(GL_STENCIL_TEST); 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); 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++ ```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 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。它们的语义和深度缓冲的方程相似。 * **func**:设置模板测试操作。这个测试操作应用到已经储存的模板值和`glStencilFunc`的`ref`值上,可用的选项是:`GL_NEVER`、`GL_LEQUAL`、`GL_GREATER`、`GL_GEQUAL`、`GL_EQUAL`、`GL_NOTEQUAL`、`GL_ALWAYS`。它们的语义和深度缓冲的相似。
* ref指定模板测试的引用值。模板缓冲的内容会与这个值对比。 * **ref**:指定模板测试的引用值。模板缓冲的内容会与这个值对比。
* mask指定一个遮罩在模板测试对比引用值和储存的模板值前对它们进行与and操作初始设置为1。 * **mask**:指定一个遮罩,在模板测试对比引用值和储存的模板值前,对它们进行按位and操作初始设置为1。
在上面简单模板的例子里,方程应该设置为: 在上面简单模板的例子里,方程应该设置为:
@@ -61,52 +71,52 @@ glStencilFunc(GLenum func, GLint ref, GLuint mask)有三个参数:
glStencilFunc(GL_EQUAL, 1, 0xFF) 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如果模板测试失败将采取的动作。 * **sfail** 如果模板测试失败将采取的动作。
* dpfail如果模板测试通过,但是深度测试失败时采取的动作。 * **dpfail** 如果模板测试通过,但是深度测试失败时采取的动作。
* dppass如果深度测试和模板测试都通过,将采取的动作。 * **dppass** 如果深度测试和模板测试都通过,将采取的动作。
每个选项都可以使用下列任何一个动作。 每个选项都可以使用下列任何一个动作。
操作 | 描述 操作 | 描述
---|--- ---|---
GL_KEEP |The currently stored stencil value is kept. GL_KEEP | 保持现有的模板值
GL_ZERO |The stencil value is set to 0. GL_ZERO | 将模板值置为0
GL_REPLACE| The stencil value is replaced with the reference value set with glStencilFunc. GL_REPLACE | 将模板值设置为用`glStencilFunc`函数设置的**ref**值
GL_INCR |The stencil value is increased by 1 if it is lower than the maximum value. GL_INCR | 如果模板值不是最大值就将模板值+1
GL_INCR_WRAP| Same as GL_INCR, but wraps it back to 0 as soon as the maximum value is exceeded. GL_INCR_WRAP| 与`GL_INCR`一样将模板值+1如果模板值已经是最大值则设为0
GL_DECR |The stencil value is decreased by 1 if it is higher than the minimum value. GL_DECR | 如果模板值不是最小值就将模板值-1
GL_DECR_WRAP| Same as GL_DECR, but wraps it to the maximum value if it ends up lower than 0. GL_DECR_WRAP| 与`GL_DECR`一样将模板值-1如果模板值已经是最小值则设为最大值
GL_INVERT | Bitwise inverts the current stencil buffer value. 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) ![](http://learnopengl.com/img/advanced/stencil_object_outlining.png)
物体轮廓就像它的名字所描述的那样,它能够给每个(或一个)物体创建一个有颜色的边。在策略游戏中当你打算选择一个单位的时候它特别有用。给物体加上轮廓的步骤如下: 物体轮廓就像它的名字所描述的那样,它能够给每个(或一个)物体创建一个有颜色的边。在策略游戏中当你打算选择一个单位的时候它特别有用。给物体加上轮廓的步骤如下:
1. 在绘制(有轮廓的)物体前把模板方程设置为GL_ALWAYS用1更新物体将被渲染的fragment 1. 在绘制物体前,把模板方程设置为`GL_ALWAYS`用1更新物体将被渲染的片段
2. 渲染物体。 2. 渲染物体,写入模板缓冲
3. 关闭模板写入和深度测试。 3. 关闭模板写入和深度测试。
4. 每个物体放大一点点。 4. 每个物体放大一点点。
5. 使用一个不同的像素着色器输出一个纯颜色。 5. 使用一个不同的片段着色器用来输出一个纯颜色。
6. 再次绘制物体,但只是当它们的fragment的模板值不为1时才进行。 6. 再次绘制物体,但只是当它们的片段的模板值不为1时才进行。
7. 开启模板写入和深度测试。 7. 开启模板写入和深度测试。
这个过程将每个物体的fragment模板缓冲设置为1当我们绘制边框的时候我们基本上绘制的是放大版本的物体通过测试的地方,放大的版本绘制后物体就会有一个边。我们基本会使用模板缓冲丢弃所有的不是原来物体的fragment的放大的版本内容。 这个过程将每个物体的片段模板缓冲设置为1当我们绘制边框的时候我们基本上绘制的是放大版本的物体通过测试的地方,放大的版本绘制后物体就会有一个边。我们基本会使用模板缓冲丢弃所有的不是原来物体的片段的放大的版本内容。
我们先来创建一个非常基本的像素着色器它输出一个边框颜色。我们简单地设置一个固定的颜色值把这个着色器命名为shaderSingleColor 我们先来创建一个非常基本的片段着色器它输出一个边框颜色。我们简单地设置一个固定的颜色值把这个着色器命名为shaderSingleColor
```c++ ```c++
void main() void main()
@@ -115,39 +125,39 @@ void main()
} }
``` ```
我们只打算给两个箱子加上边框,所以我们不会对地面做什么。这样我们要先绘制地面,然后再绘制两个箱子(同时写入模板缓冲),接着我们绘制放大的箱子(同时丢弃前面已经绘制的箱子的那部分fragment)。 我们只打算给两个箱子加上边框,所以我们不会对地面做什么。这样我们要先绘制地面,然后再绘制两个箱子(同时写入模板缓冲),接着我们绘制放大的箱子(同时丢弃前面已经绘制的箱子的那部分片段)。
我们先开启模板测试,把将设置为无论何时、任何测试通过或失败时才采取动作: 我们先开启模板测试,设置模板、深度测试通过或失败时才采取动作:
```c++ ```c++
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
``` ```
如果任何测试失败我们都什么也不做,我们简单地保持深度缓冲中当前所储存着的值。如果模板测试和深度测试都成功了,我们就将储存着的模板值替换为1我们要用glStencilFunc来做这件事。 如果任何测试失败我们都什么也不做,我们简单地保持深度缓冲中当前所储存着的值。如果模板测试和深度测试都成功了,我们就将储存着的模板值替换为`1`,我们要用`glStencilFunc`来做这件事。
我们清空模板缓冲为0为箱子的所有绘制的fragment的模板缓冲更新为1 我们清空模板缓冲为0为箱子的所有绘制的片段的模板缓冲更新为1
```c++ ```c++
glStencilFunc(GL_ALWAYS, 1, 0xFF); // All fragments should update the stencil buffer glStencilFunc(GL_ALWAYS, 1, 0xFF); //所有片段都要写入模板缓冲
glStencilMask(0xFF); // Enable writing to the stencil buffer glStencilMask(0xFF); // 设置模板缓冲为可写状态
normalShader.Use(); normalShader.Use();
DrawTwoContainers(); DrawTwoContainers();
``` ```
使用GL_ALWAYS模板测试方程,我们确保箱子的每个fragment用模板值1更新模板缓冲。因为fragment总会通过模板测试,在我们绘制它们的地方,模板缓冲用引用值更新。 使用`GL_ALWAYS`模板测试函数,我们确保箱子的每个片段用模板值1更新模板缓冲。因为片段总会通过模板测试,在我们绘制它们的地方,模板缓冲用引用值更新。
现在箱子绘制之处模板缓冲更新为1了我们将要绘制放大的箱子但是这次关闭模板缓冲的写入 现在箱子绘制之处模板缓冲更新为1了我们将要绘制放大的箱子但是这次关闭模板缓冲的写入
```c++ ```c++
glStencilFunc(GL_NOTEQUAL, 1, 0xFF); glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00); // Disable writing to the stencil buffer glStencilMask(0x00); // 禁止修改模板缓冲
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
shaderSingleColor.Use(); shaderSingleColor.Use();
DrawTwoScaledUpContainers(); DrawTwoScaledUpContainers();
``` ```
我们把模板方程设置为GL_NOTEQUAL它保证我们只箱子上不等于1的部分这样只绘制前面绘制的箱子外围的那部分。注意我们也要关闭深度测试这样放大的的箱子也就是边框才不会被地面覆盖。 我们把模板方程设置为`GL_NOTEQUAL`它保证我们只箱子上不等于1的部分这样只绘制前面绘制的箱子外围的那部分。注意我们也要关闭深度测试这样放大的的箱子也就是边框才不会被地面覆盖。
做完之后还要保证再次开启深度缓冲。 做完之后还要保证再次开启深度缓冲。
@@ -156,21 +166,21 @@ DrawTwoScaledUpContainers();
```c++ ```c++
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 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(); normalShader.Use();
DrawFloor() DrawFloor()
glStencilFunc(GL_ALWAYS, 1, 0xFF); glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF); glStencilMask(0xFF);
DrawTwoContainers(); DrawTwoContainers();
glStencilFunc(GL_NOTEQUAL, 1, 0xFF); glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00); glStencilMask(0x00);
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
shaderSingleColor.Use(); shaderSingleColor.Use();
DrawTwoScaledUpContainers(); DrawTwoScaledUpContainers();
glStencilMask(0xFF); glStencilMask(0xFF);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
@@ -179,9 +189,10 @@ glEnable(GL_DEPTH_TEST);
理解这段代码后面的模板测试的思路并不难以理解。如果还不明白尝试再仔细阅读上面的部分,尝试理解每个函数的作用,现在你已经看到了它的使用方法的例子。 理解这段代码后面的模板测试的思路并不难以理解。如果还不明白尝试再仔细阅读上面的部分,尝试理解每个函数的作用,现在你已经看到了它的使用方法的例子。
这个边框的算法的结果在深度测试教程的那个场景中,看起来像这样: 这个边框的算法的结果在深度测试教程的那个场景中,看起来像这样:
![](http://bullteacher.com/wp-content/uploads/2015/06/stencil_scene_outlined.png) ![](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 !!! Important
@@ -189,4 +200,4 @@ glEnable(GL_DEPTH_TEST);
你目前看到的物体边框算法在一些游戏中显示备选物体想象策略游戏非常常用这样的算法可以在一个模型类中轻易实现。你可以简单地在模型类设置一个布尔类型的标识来决定是否绘制边框。如果你想要更多的创造性你可以使用后处理post-processing过滤比如高斯模糊来使边框看起来更自然。 你目前看到的物体边框算法在一些游戏中显示备选物体想象策略游戏非常常用这样的算法可以在一个模型类中轻易实现。你可以简单地在模型类设置一个布尔类型的标识来决定是否绘制边框。如果你想要更多的创造性你可以使用后处理post-processing过滤比如高斯模糊来使边框看起来更自然。
除了物体边框以外模板测试还有很多其他的应用目的比如在后视镜中绘制纹理这样它会很好的适合镜子的形状比如使用一种叫做shadow volumes的模板缓冲技术渲染实时阴影。模板缓冲在我们的已扩展的OpenGL工具箱中给我们提供了另一种好用工具。 除了物体边框以外模板测试还有很多其他的应用目的比如在后视镜中绘制纹理这样它会很好的适合镜子的形状比如使用一种叫做shadow volumes的模板缓冲技术渲染实时阴影。模板缓冲在我们的已扩展的OpenGL工具箱中给我们提供了另一种好用工具。

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); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);
``` ```
保证你在像素着色器中获取了纹理的所有4个颜色元素而不仅仅是RGB元素 保证你在片段着色器中获取了纹理的所有4个颜色元素而不仅仅是RGB元素
```c++ ```c++
void main() void main()
@@ -66,10 +66,10 @@ vegetation.push_back(glm::vec3( 0.5f, 0.0f, -0.6f));
```c++ ```c++
glBindVertexArray(vegetationVAO); glBindVertexArray(vegetationVAO);
glBindTexture(GL_TEXTURE_2D, grassTexture); glBindTexture(GL_TEXTURE_2D, grassTexture);
for(GLuint i = 0; i < vegetation.size(); i++) for(GLuint i = 0; i < vegetation.size(); i++)
{ {
model = glm::mat4(); model = glm::mat4();
model = glm::translate(model, vegetation[i]); model = glm::translate(model, vegetation[i]);
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glDrawArrays(GL_TRIANGLES, 0, 6); glDrawArrays(GL_TRIANGLES, 0, 6);
} }
@@ -79,18 +79,18 @@ glBindVertexArray(0);
运行程序你将看到: 运行程序你将看到:
![image description](http://learnopengl.com/img/advanced/blending_no_discard.png) ![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++ ```c++
#version 330 core #version 330 core
in vec2 TexCoords; in vec2 TexCoords;
out vec4 color; out vec4 color;
uniform sampler2D texture1; uniform sampler2D texture1;
void main() void main()
{ {
vec4 texColor = texture(texture1, TexCoords); vec4 texColor = texture(texture1, TexCoords);
if(texColor.a < 0.1) if(texColor.a < 0.1)
discard; discard;
@@ -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) ![image description](http://learnopengl.com/img/advanced/blending_discard.png)
@@ -109,7 +109,7 @@ void main()
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
你可以[在这里得到源码](http://learnopengl.com/code_viewer.php?code=advanced/blending_discard)。 你可以[在这里得到源码](http://learnopengl.com/code_viewer.php?code=advanced/blending_discard)。
###混合 ###混合
@@ -131,7 +131,7 @@ C¯result = C¯source Fsource + C¯destination Fdestination
* Fsource源因子。设置了对源颜色的alpha值影响。 * Fsource源因子。设置了对源颜色的alpha值影响。
* Fdestination目标因子。设置了对目标颜色的alpha影响。 * Fdestination目标因子。设置了对目标颜色的alpha影响。
像素着色器运行和所有的测试就通过了以后混合方程才能自由执行fragment的颜色输出当前它在颜色缓冲中前面的fragment的颜色在当前fragment之前储存。源和目标颜色会自动被OpenGL设置但是源和目标因子可以让我们自由设置。我们来看一个简单的例子 片段着色器运行和所有的测试就通过了以后混合方程才能自由执行fragment的颜色输出当前它在颜色缓冲中前面的fragment的颜色在当前fragment之前储存。源和目标颜色会自动被OpenGL设置但是源和目标因子可以让我们自由设置。我们来看一个简单的例子
![](http://learnopengl.com/img/advanced/blending_equation.png) ![](http://learnopengl.com/img/advanced/blending_equation.png)
@@ -204,18 +204,18 @@ glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
``` ```
由于我们开启了混合就不需要丢弃fragment了所以我们把像素着色器设置为原来的那个版本: 由于我们开启了混合就不需要丢弃fragment了所以我们把片段着色器设置为原来的那个版本:
```c++ ```c++
#version 330 core #version 330 core
in vec2 TexCoords; in vec2 TexCoords;
out vec4 color; out vec4 color;
uniform sampler2D texture1; uniform sampler2D texture1;
void main() void main()
{ {
color = texture(texture1, TexCoords); color = texture(texture1, TexCoords);
} }
``` ```
@@ -233,7 +233,7 @@ void main()
!!! Important !!! Important
对于全透明物体,比如草叶,我们选择简单的丢弃透明像素而不是混合,这样就减少了我们的头疼问题(没有深度测试问题)。 对于全透明物体,比如草叶,我们选择简单的丢弃透明像素而不是混合,这样就减少了我们的头疼问题(没有深度测试问题)。
####别打乱顺序 ####别打乱顺序
要让混合在多物体上有效,我们必须先绘制最远的物体,最后绘制最近的物体。普通的无混合物体仍然可以使用深度缓冲正常绘制,所以不必给它们排序。我们一定要保证它们在透明物体前绘制好。当无透明度物体和透明物体一起绘制的时候,通常要遵循以下原则: 要让混合在多物体上有效,我们必须先绘制最远的物体,最后绘制最近的物体。普通的无混合物体仍然可以使用深度缓冲正常绘制,所以不必给它们排序。我们一定要保证它们在透明物体前绘制好。当无透明度物体和透明物体一起绘制的时候,通常要遵循以下原则:
@@ -257,10 +257,10 @@ for (GLuint i = 0; i < windows.size(); i++) // windows contains all window posit
随后当渲染的时候我们逆序获取到每个map的值从远到近然后以正确的绘制相应的窗子 随后当渲染的时候我们逆序获取到每个map的值从远到近然后以正确的绘制相应的窗子
```c++ ```c++
for(std::map<float,glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it) for(std::map<float,glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it)
{ {
model = glm::mat4(); model = glm::mat4();
model = glm::translate(model, it->second); model = glm::translate(model, it->second);
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glDrawArrays(GL_TRIANGLES, 0, 6); glDrawArrays(GL_TRIANGLES, 0, 6);
} }

View File

@@ -2,19 +2,19 @@
本文作者JoeyDeVries由Django翻译自http://learnopengl.com 本文作者JoeyDeVries由Django翻译自http://learnopengl.com
尝试在头脑中想象一下有一个3D立方体你从任何一个方向去看他你最多可以一次看到多少个面。如果你的想象力不是过于丰富你最终最多能数出来的面是3个。你可以从一个立方体的任意位置和方向上去看它但是你永远不能看到多于3个面。所以我们为何还要去绘制那三个不会显示出来的3个面呢。如果我们可以以某种方式丢弃它们我们会提高像素着色器50%的性能! 尝试在头脑中想象一下有一个3D立方体你从任何一个方向去看他你最多可以一次看到多少个面。如果你的想象力不是过于丰富你最终最多能数出来的面是3个。你可以从一个立方体的任意位置和方向上去看它但是你永远不能看到多于3个面。所以我们为何还要去绘制那三个不会显示出来的3个面呢。如果我们可以以某种方式丢弃它们我们会提高片段着色器50%的性能!
!!! Important !!! Important
我们所说的是大于50%而不是50%因为从一个角度只有2个或1个面能够被看到。这种情况下我们就能够节约50%以上的了。 我们所说的是大于50%而不是50%因为从一个角度只有2个或1个面能够被看到。这种情况下我们就能够节约50%以上的了。
![](http://learnopengl.com/img/advanced/faceculling_windingorder.png) ![](http://learnopengl.com/img/advanced/faceculling_windingorder.png)
这的确是个好主意,但是有个问题需要我们去解决:我们如何知道某个面在观察者的视野中不会出现呢?如果我们去想象任何封闭的形状,它每个面有两面。一面面向用户,另一面背对用户。假如我们只渲染面向观察者的面会怎样? 这的确是个好主意,但是有个问题需要我们去解决:我们如何知道某个面在观察者的视野中不会出现呢?如果我们去想象任何封闭的形状,它每个面有两面。一面面向用户,另一面背对用户。假如我们只渲染面向观察者的面会怎样?
这正是面剔除所做的face culling。OpenGL检查所有正面朝向front facing观察者的面并渲染它们而丢弃所有背面朝向back facing的面这样就节约了我们很多像素着色器的命令它们很昂贵。我们必须告诉OpenGL我们使用的哪个面是正面哪个面是反面。OpenGL使用一种聪明的手段解决这个问题——分析顶点数据的链接顺序winding order 这正是面剔除所做的face culling。OpenGL检查所有正面朝向front facing观察者的面并渲染它们而丢弃所有背面朝向back facing的面这样就节约了我们很多片段着色器的命令它们很昂贵。我们必须告诉OpenGL我们使用的哪个面是正面哪个面是反面。OpenGL使用一种聪明的手段解决这个问题——分析顶点数据的链接顺序winding order
25.1 顶点链接顺序winding order 25.1 顶点链接顺序winding order
@@ -45,7 +45,7 @@ GLfloat vertices[] = {
在顶点数据中我们定义的是两个逆时针顺序的前面的三角是1、2、3后面剪的也是1、2、3如果我们从这个三角的前面去观察。然而从观察者的方面看后面的三角形是顺时针的如果我们仍以1、2、3的顺序以观察者当面的视野看的话。即使我们以逆时针顺序定义后面的三角形它现在还是变为顺时针。它正是我们打算剔除丢弃的不可见的面 在顶点数据中我们定义的是两个逆时针顺序的前面的三角是1、2、3后面剪的也是1、2、3如果我们从这个三角的前面去观察。然而从观察者的方面看后面的三角形是顺时针的如果我们仍以1、2、3的顺序以观察者当面的视野看的话。即使我们以逆时针顺序定义后面的三角形它现在还是变为顺时针。它正是我们打算剔除丢弃的不可见的面
###面剔除 ###面剔除
@@ -100,4 +100,4 @@ glCullFace(GL_FRONT);
###练习 ###练习
你可以自己重新定义一个顺时针的顶点顺序,然后用顺时针作为正面把它渲染出来吗:[解决方案](http://learnopengl.com/code_viewer.php?code=advanced/faceculling-exercise1)。 你可以自己重新定义一个顺时针的顶点顺序,然后用顺时针作为正面把它渲染出来吗:[解决方案](http://learnopengl.com/code_viewer.php?code=advanced/faceculling-exercise1)。

View File

@@ -8,7 +8,7 @@
你不会立刻理解应用程序的帧缓的含义,但是把你的场景渲染到一个不同的帧缓冲中,可以使我们能够在场景中创建镜子这样的效果,或者做出一些炫酷的后处理特效。首先我们会讨论它们是如何工作的,然后我们将利用它们来实现一些炫酷的后处理效果。 你不会立刻理解应用程序的帧缓的含义,但是把你的场景渲染到一个不同的帧缓冲中,可以使我们能够在场景中创建镜子这样的效果,或者做出一些炫酷的后处理特效。首先我们会讨论它们是如何工作的,然后我们将利用它们来实现一些炫酷的后处理效果。
###创建一个帧缓冲 ###创建一个帧缓冲
@@ -42,7 +42,7 @@ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
// Execute victory dance // Execute victory dance
``` ```
后续所有渲染操作将渲染到当前绑定的帧缓冲的附加缓冲中由于我们的帧缓冲不是默认的帧缓冲渲染命令对窗口的视频输出不会产生任何影响。出于这个原因它被称为离屏渲染off-screen rendering就是渲染到一个另外的缓冲中。为了让所有的渲染操作对主窗口产生影响我们必须通过绑定为0来使默认帧缓冲激活 后续所有渲染操作将渲染到当前绑定的帧缓冲的附加缓冲中由于我们的帧缓冲不是默认的帧缓冲渲染命令对窗口的视频输出不会产生任何影响。出于这个原因它被称为离屏渲染off-screen rendering就是渲染到一个另外的缓冲中。为了让所有的渲染操作对主窗口产生影响我们必须通过绑定为0来使默认帧缓冲激活
```c++ ```c++
@@ -67,9 +67,9 @@ glDeleteFramebuffers(1, &fbo);
GLuint texture; GLuint texture;
glGenTextures(1, &texture); glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture); glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
``` ```
@@ -96,10 +96,10 @@ glFramebufferTexture2D函数有以下参数
```c++ ```c++
glTexImage2D( glTexImage2D(
GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0, GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0,
GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL
); );
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0);
``` ```
渲染缓冲对象附件Renderbuffer object attachments 渲染缓冲对象附件Renderbuffer object attachments
@@ -164,7 +164,7 @@ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NU
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
// Attach it to currently bound framebuffer object // Attach it to currently bound framebuffer object
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0);
``` ```
@@ -176,7 +176,7 @@ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texC
```c++ ```c++
GLuint rbo; GLuint rbo;
glGenRenderbuffers(1, &rbo); glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo); glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
glBindRenderbuffer(GL_RENDERBUFFER, 0); glBindRenderbuffer(GL_RENDERBUFFER, 0);
``` ```
@@ -214,27 +214,27 @@ glBindFramebuffer(GL_FRAMEBUFFER, 0);
#version 330 core #version 330 core
layout (location = 0) in vec2 position; layout (location = 0) in vec2 position;
layout (location = 1) in vec2 texCoords; layout (location = 1) in vec2 texCoords;
out vec2 TexCoords; out vec2 TexCoords;
void main() void main()
{ {
gl_Position = vec4(position.x, position.y, 0.0f, 1.0f); gl_Position = vec4(position.x, position.y, 0.0f, 1.0f);
TexCoords = texCoords; TexCoords = texCoords;
} }
``` ```
没有花哨的地方。像素着色器更简洁,因为我们做的唯一一件事是从纹理采样: 没有花哨的地方。片段着色器更简洁,因为我们做的唯一一件事是从纹理采样:
```c++ ```c++
#version 330 core #version 330 core
in vec2 TexCoords; in vec2 TexCoords;
out vec4 color; out vec4 color;
uniform sampler2D screenTexture; uniform sampler2D screenTexture;
void main() void main()
{ {
color = texture(screenTexture, TexCoords); color = texture(screenTexture, TexCoords);
} }
``` ```
@@ -247,13 +247,13 @@ glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // We're not using stencil buffer now glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // We're not using stencil buffer now
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
DrawScene(); DrawScene();
// Second pass // Second pass
glBindFramebuffer(GL_FRAMEBUFFER, 0); // back to default glBindFramebuffer(GL_FRAMEBUFFER, 0); // back to default
glClearColor(1.0f, 1.0f, 1.0f, 1.0f); glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
screenShader.Use(); screenShader.Use();
glBindVertexArray(quadVAO); glBindVertexArray(quadVAO);
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
@@ -272,16 +272,16 @@ glBindVertexArray(0);
你可以从这里得到应用的源码。 你可以从这里得到应用的源码。
然而这有什么好处呢?好处就是我们现在可以自由的获取已经渲染场景中的任何像素,然后把它当作一个纹理图像了,我们可以在像素着色器中创建一些有意思的效果。所有这些有意思的效果统称为后处理特效。 然而这有什么好处呢?好处就是我们现在可以自由的获取已经渲染场景中的任何像素,然后把它当作一个纹理图像了,我们可以在片段着色器中创建一些有意思的效果。所有这些有意思的效果统称为后处理特效。
###后处理 ###后处理
现在,整个场景渲染到了一个单独的纹理上,我们可以创建一些有趣的效果,只要简单操纵纹理数据就能做到。这部分,我们会向你展示一些流行的后处理特效,以及怎样添加一些创造性去创建出你自己的特效。 现在,整个场景渲染到了一个单独的纹理上,我们可以创建一些有趣的效果,只要简单操纵纹理数据就能做到。这部分,我们会向你展示一些流行的后处理特效,以及怎样添加一些创造性去创建出你自己的特效。
###反相 ###反相
我们已经取得了渲染输出的每个颜色,所以在像素着色器里返回这些颜色的反色并不难。我们得到屏幕纹理的颜色然后用1.0减去它: 我们已经取得了渲染输出的每个颜色,所以在片段着色器里返回这些颜色的反色并不难。我们得到屏幕纹理的颜色然后用1.0减去它:
```c++ ```c++
void main() void main()
@@ -334,12 +334,12 @@ kernel是一个长得有点像一个小矩阵的数值数组它中间的值
!!! Important !!! Important
你在网上能找到的kernel的例子大多数都是所有值加起来等于1如果加起来不等于1就意味着这个纹理值比原来更大或者更小了。 你在网上能找到的kernel的例子大多数都是所有值加起来等于1如果加起来不等于1就意味着这个纹理值比原来更大或者更小了。
kernel对于后处理来说非常管用因为用起来简单。网上能找到有很多实例为了能用上kernel我们还得改改像素着色器。这里假设每个kernel都是3×3实际上大多数都是3×3 kernel对于后处理来说非常管用因为用起来简单。网上能找到有很多实例为了能用上kernel我们还得改改片段着色器。这里假设每个kernel都是3×3实际上大多数都是3×3
```c++ ```c++
const float offset = 1.0 / 300; const float offset = 1.0 / 300;
void main() void main()
{ {
vec2 offsets[9] = vec2[]( vec2 offsets[9] = vec2[](
@@ -351,15 +351,15 @@ void main()
vec2(offset, 0.0f), // center-right vec2(offset, 0.0f), // center-right
vec2(-offset, -offset), // bottom-left vec2(-offset, -offset), // bottom-left
vec2(0.0f, -offset), // bottom-center vec2(0.0f, -offset), // bottom-center
vec2(offset, -offset) // bottom-right vec2(offset, -offset) // bottom-right
); );
float kernel[9] = float[]( float kernel[9] = float[](
-1, -1, -1, -1, -1, -1,
-1, 9, -1, -1, 9, -1,
-1, -1, -1 -1, -1, -1
); );
vec3 sampleTex[9]; vec3 sampleTex[9];
for(int i = 0; i < 9; i++) for(int i = 0; i < 9; i++)
{ {
@@ -368,12 +368,12 @@ void main()
vec3 col; vec3 col;
for(int i = 0; i < 9; i++) for(int i = 0; i < 9; i++)
col += sampleTex[i] * kernel[i]; col += sampleTex[i] * kernel[i];
color = vec4(col, 1.0); color = vec4(col, 1.0);
} }
``` ```
像素着色器中我们先为每个四周的纹理坐标创建一个9个vec2偏移量的数组。偏移量是一个简单的常数你可以设置为自己喜欢的。接着我们定义kernel这里应该是一个锐化kernel它通过一种有趣的方式从所有周边的像素采样对每个颜色值进行锐化。最后在采样的时候我们把每个偏移量加到当前纹理坐标上然后用加在一起的kernel的值乘以这些纹理值。 片段着色器中我们先为每个四周的纹理坐标创建一个9个vec2偏移量的数组。偏移量是一个简单的常数你可以设置为自己喜欢的。接着我们定义kernel这里应该是一个锐化kernel它通过一种有趣的方式从所有周边的像素采样对每个颜色值进行锐化。最后在采样的时候我们把每个偏移量加到当前纹理坐标上然后用加在一起的kernel的值乘以这些纹理值。
这个锐化的kernel看起来像这样 这个锐化的kernel看起来像这样
@@ -420,4 +420,4 @@ It probably does not come as a surprise that kernels like this are used as image
###Exercises ###Exercises
Can you use framebuffers to create a rear-view mirror? For this you'll have to draw your scene twice: one with the camera rotated 180 degrees and the other as normal. Try to create a small quad on the top of your screen to apply the mirror texture on: solution and visual result. Can you use framebuffers to create a rear-view mirror? For this you'll have to draw your scene twice: one with the camera rotated 180 degrees and the other as normal. Try to create a small quad on the top of your screen to apply the mirror texture on: solution and visual result.
Play around with the kernel values and create your own interesting post-processing effects. Try searching the internet as well for other interesting kernels. Play around with the kernel values and create your own interesting post-processing effects. Try searching the internet as well for other interesting kernels.

View File

@@ -50,7 +50,7 @@ for(GLuint i = 0; i < textures_faces.size(); i++)
{ {
image = SOIL_load_image(textures_faces[i], &width, &height, 0, SOIL_LOAD_RGB); image = SOIL_load_image(textures_faces[i], &width, &height, 0, SOIL_LOAD_RGB);
glTexImage2D( glTexImage2D(
GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image
); );
} }
@@ -71,22 +71,22 @@ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
``` ```
像素着色器中我们也必须使用一个不同的采样器——samplerCube用它来从texture函数中采样但是这次使用的是一个vec3方向向量取代vec2。下面是一个像素着色器使用了cubemap的例子 片段着色器中我们也必须使用一个不同的采样器——samplerCube用它来从texture函数中采样但是这次使用的是一个vec3方向向量取代vec2。下面是一个片段着色器使用了cubemap的例子
```c++ ```c++
in vec3 textureDir; // Direction vector representing a 3D texture coordinate in vec3 textureDir; // Direction vector representing a 3D texture coordinate
uniform samplerCube cubemap; // Cubemap texture sampler uniform samplerCube cubemap; // Cubemap texture sampler
void main() void main()
{ {
color = texture(cubemap, textureDir); color = texture(cubemap, textureDir);
} }
``` ```
看起来不错但是何必这么做呢因为恰巧使用cubemap可以简单的实现很多有意思的技术。其中之一便是天空盒skybox 看起来不错但是何必这么做呢因为恰巧使用cubemap可以简单的实现很多有意思的技术。其中之一便是天空盒skybox
### 天空盒(Skybox) ### 天空盒(Skybox)
@@ -118,10 +118,10 @@ GLuint loadCubemap(vector<const GLchar*> faces)
GLuint textureID; GLuint textureID;
glGenTextures(1, &textureID); glGenTextures(1, &textureID);
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
int width,height; int width,height;
unsigned char* image; unsigned char* image;
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
for(GLuint i = 0; i < faces.size(); i++) for(GLuint i = 0; i < faces.size(); i++)
{ {
@@ -137,7 +137,7 @@ GLuint loadCubemap(vector<const GLchar*> faces)
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0); glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
return textureID; return textureID;
} }
``` ```
@@ -159,7 +159,7 @@ GLuint cubemapTexture = loadCubemap(faces);
现在我们已经用cubemapTexture作为id把天空盒加载为cubemap。我们现在可以把它绑定到一个立方体来替换不完美的clear color在前面的所有教程中这个东西做背景已经很久了。 现在我们已经用cubemapTexture作为id把天空盒加载为cubemap。我们现在可以把它绑定到一个立方体来替换不完美的clear color在前面的所有教程中这个东西做背景已经很久了。
#### 天空盒的显示 #### 天空盒的显示
@@ -171,10 +171,10 @@ cubemap用于给3D立方体帖上纹理可以用立方体的位置作为纹
#version 330 core #version 330 core
layout (location = 0) in vec3 position; layout (location = 0) in vec3 position;
out vec3 TexCoords; out vec3 TexCoords;
uniform mat4 projection; uniform mat4 projection;
uniform mat4 view; uniform mat4 view;
void main() void main()
{ {
gl_Position = projection * view * vec4(position, 1.0); gl_Position = projection * view * vec4(position, 1.0);
@@ -182,22 +182,22 @@ void main()
} }
``` ```
注意,顶点着色器有意思的地方在于我们把输入的位置向量作为输出给像素着色器的纹理坐标。像素着色器就会把它们作为输入去采样samplerCube 注意,顶点着色器有意思的地方在于我们把输入的位置向量作为输出给片段着色器的纹理坐标。片段着色器就会把它们作为输入去采样samplerCube
```c++ ```c++
#version 330 core #version 330 core
in vec3 TexCoords; in vec3 TexCoords;
out vec4 color; out vec4 color;
uniform samplerCube skybox; uniform samplerCube skybox;
void main() void main()
{ {
color = texture(skybox, TexCoords); color = texture(skybox, TexCoords);
} }
``` ```
像素着色器比较明了。我们把顶点属性中的位置向量作为纹理的方向向量使用它们从cubemap采样纹理值。渲染天空盒现在很简单我们有了一个cubemap纹理我们简单绑定cubemap纹理天空盒就自动地用天空盒的cubemap填充了。为了绘制天空盒我们将把它作为场景中第一个绘制的物体并且关闭深度写入。这样天空盒才能成为所有其他物体的背景来绘制出来。 片段着色器比较明了。我们把顶点属性中的位置向量作为纹理的方向向量使用它们从cubemap采样纹理值。渲染天空盒现在很简单我们有了一个cubemap纹理我们简单绑定cubemap纹理天空盒就自动地用天空盒的cubemap填充了。为了绘制天空盒我们将把它作为场景中第一个绘制的物体并且关闭深度写入。这样天空盒才能成为所有其他物体的背景来绘制出来。
```c++ ```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 在坐标系教程中我们说过透视除法perspective division是在顶点着色器运行之后执行的把gl_Position的xyz坐标除以w元素。我们从深度测试教程了解到除法结果的z元素等于顶点的深度值。利用这个信息我们可以把输出位置的z元素设置为它的w元素这样就会导致z元素等于1.0了因为当透视除法应用后它的z元素转换为w/w = 1.0
@@ -263,19 +263,19 @@ void main()
我们基于观察方向向量I和物体的法线向量N计算出反射向量R。我们可以使用GLSL的内建函数reflect来计算这个反射向量。最后向量R作为一个方向向量对cubemap进行索引/采样,返回一个环境的颜色值。最后的效果看起来就像物体反射了天空盒。 我们基于观察方向向量I和物体的法线向量N计算出反射向量R。我们可以使用GLSL的内建函数reflect来计算这个反射向量。最后向量R作为一个方向向量对cubemap进行索引/采样,返回一个环境的颜色值。最后的效果看起来就像物体反射了天空盒。
因为我们在场景中已经设置了一个天空盒,创建反射就不难了。我们改变一下箱子使用的那个像素着色器,给箱子一个反射属性: 因为我们在场景中已经设置了一个天空盒,创建反射就不难了。我们改变一下箱子使用的那个片段着色器,给箱子一个反射属性:
```c++ ```c++
#version 330 core #version 330 core
in vec3 Normal; in vec3 Normal;
in vec3 Position; in vec3 Position;
out vec4 color; out vec4 color;
uniform vec3 cameraPos; uniform vec3 cameraPos;
uniform samplerCube skybox; uniform samplerCube skybox;
void main() void main()
{ {
vec3 I = normalize(Position - cameraPos); vec3 I = normalize(Position - cameraPos);
vec3 R = reflect(I, normalize(Normal)); vec3 R = reflect(I, normalize(Normal));
color = texture(skybox, R); color = texture(skybox, R);
@@ -288,14 +288,14 @@ void main()
#version 330 core #version 330 core
layout (location = 0) in vec3 position; layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal; layout (location = 1) in vec3 normal;
out vec3 Normal; out vec3 Normal;
out vec3 Position; out vec3 Position;
uniform mat4 model; uniform mat4 model;
uniform mat4 view; uniform mat4 view;
uniform mat4 projection; uniform mat4 projection;
void main() void main()
{ {
gl_Position = projection * view * model * vec4(position, 1.0f); gl_Position = projection * view * model * vec4(position, 1.0f);
@@ -304,7 +304,7 @@ void main()
} }
``` ```
我们用了法线向量所以我们打算使用一个法线矩阵normal matrix变换它们。Position输出的向量是一个世界空间位置向量。顶点着色器输出的Position用来在像素着色器计算观察方向向量。 我们用了法线向量所以我们打算使用一个法线矩阵normal matrix变换它们。Position输出的向量是一个世界空间位置向量。顶点着色器输出的Position用来在片段着色器计算观察方向向量。
因为我们使用法线你还得更新顶点数据更新属性指针。还要确保设置cameraPos的uniform。 因为我们使用法线你还得更新顶点数据更新属性指针。还要确保设置cameraPos的uniform。
@@ -312,7 +312,7 @@ void main()
```c++ ```c++
glBindVertexArray(cubeVAO); glBindVertexArray(cubeVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture); glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture);
glDrawArrays(GL_TRIANGLES, 0, 36); glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0); glBindVertexArray(0);
``` ```
@@ -353,11 +353,11 @@ glBindVertexArray(0);
我们使用这些折射指数来计算光线通过两个材质的比率。在我们的例子中,光线/视线从空气进入玻璃如果我们假设箱子是玻璃做的所以比率是1.001.52 = 0.658。 我们使用这些折射指数来计算光线通过两个材质的比率。在我们的例子中,光线/视线从空气进入玻璃如果我们假设箱子是玻璃做的所以比率是1.001.52 = 0.658。
我们已经绑定了cubemap提供了定点数据设置了摄像机位置的uniform。现在只需要改变像素着色器: 我们已经绑定了cubemap提供了定点数据设置了摄像机位置的uniform。现在只需要改变片段着色器:
```c++ ```c++
void main() void main()
{ {
float ratio = 1.00 / 1.52; float ratio = 1.00 / 1.52;
vec3 I = normalize(Position - cameraPos); vec3 I = normalize(Position - cameraPos);
vec3 R = refract(I, normalize(Normal), ratio); vec3 R = refract(I, normalize(Normal), ratio);
@@ -369,7 +369,7 @@ void main()
```c++ ```c++
void main() void main()
{ {
float ratio = 1.00 / 1.52; float ratio = 1.00 / 1.52;
vec3 I = normalize(Position - cameraPos); vec3 I = normalize(Position - cameraPos);
vec3 R = refract(I, normalize(Normal), ratio); vec3 R = refract(I, normalize(Normal), ratio);
@@ -387,8 +387,8 @@ void main()
它看起效果很好但是有一个劣势使用环境贴图我们必须为每个物体渲染场景6次这需要非常大的开销。现代应用尝试尽量使用天空盒子凡可能预编译cubemap就创建少量动态环境贴图。动态环境映射是个非常棒的技术要想在不降低执行效率的情况下实现它就需要很多巧妙的技巧。 它看起效果很好但是有一个劣势使用环境贴图我们必须为每个物体渲染场景6次这需要非常大的开销。现代应用尝试尽量使用天空盒子凡可能预编译cubemap就创建少量动态环境贴图。动态环境映射是个非常棒的技术要想在不降低执行效率的情况下实现它就需要很多巧妙的技巧。
### 练习 ### 练习
//TODO //TODO

View File

@@ -98,4 +98,4 @@ glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData)); glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));
``` ```
有了这些额外的关于如何操纵缓冲的知识我们已经可以以更有意思的方式来使用它们了。当你对OpenGL更熟悉这些新缓冲方法就变得更有用。下个教程中我们会讨论unform缓冲对象彼时我们会充分利用glBufferSubData。 有了这些额外的关于如何操纵缓冲的知识我们已经可以以更有意思的方式来使用它们了。当你对OpenGL更熟悉这些新缓冲方法就变得更有用。下个教程中我们会讨论unform缓冲对象彼时我们会充分利用glBufferSubData。

View File

@@ -8,7 +8,7 @@
### GLSL的内建变量 ### 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)。 我们会讨论几个有趣的GLSL内建变量并向你解释为什么它们对我们来说很有好处。注意我们不会讨论到GLSL中所有的内建变量因此如果你想看到所有的内建变量还是最好去看[OpenGL的wiki](http://www.opengl.org/wiki/Built-in_Variable_(GLSL)。
@@ -33,8 +33,8 @@ glEnable(GL_PROGRAM_POINT_SIZE);
```c++ ```c++
void main() void main()
{ {
gl_Position = projection * view * model * vec4(position, 1.0f); gl_Position = projection * view * model * vec4(position, 1.0f);
gl_PointSize = gl_Position.z; gl_PointSize = gl_Position.z;
} }
``` ```
@@ -52,9 +52,9 @@ gl_VertexID是个整型变量它储存着我们绘制的当前顶点的ID。
尽管目前看似没用,但是我们最好知道我们能获取这样的信息。 尽管目前看似没用,但是我们最好知道我们能获取这样的信息。
#### 像素着色器的变量 #### 片段着色器的变量
像素着色器中也有一些有趣的变量。GLSL给我们提供了两个有意思的输入变量它们是gl_FragCoord和gl_FrontFacing。 片段着色器中也有一些有趣的变量。GLSL给我们提供了两个有意思的输入变量它们是gl_FragCoord和gl_FrontFacing。
##### gl_FragCoord ##### gl_FragCoord
@@ -62,16 +62,16 @@ gl_VertexID是个整型变量它储存着我们绘制的当前顶点的ID。
gl_FragCoord的x和y元素是这个fragment窗口空间坐标window-space coordinate。它们的起始处是窗口的左下角。如果我们的窗口是800×600的那么一个fragment的窗口空间坐标x的范围就在0到800之间y在0到600之间。 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++ ```c++
void main() void main()
{ {
if(gl_FragCoord.x < 400) if(gl_FragCoord.x < 400)
color = vec4(1.0f, 0.0f, 0.0f, 1.0f); color = vec4(1.0f, 0.0f, 0.0f, 1.0f);
else else
color = vec4(0.0f, 1.0f, 0.0f, 1.0f); color = vec4(0.0f, 1.0f, 0.0f, 1.0f);
} }
``` ```
@@ -79,13 +79,13 @@ void main()
![](http://bullteacher.com/wp-content/uploads/2015/06/advanced_glsl_fragcoord.png) ![](http://bullteacher.com/wp-content/uploads/2015/06/advanced_glsl_fragcoord.png)
我们现在可以计算出两个完全不同的像素着色器结果,每个显示在窗口的一端。这对于测试不同的光照技术很有好处。 我们现在可以计算出两个完全不同的片段着色器结果,每个显示在窗口的一端。这对于测试不同的光照技术很有好处。
gl_FrontFacing gl_FrontFacing
像素着色器另一个有意思的输入变量是gl_FrontFacing变量。在面剔除教程中我们提到过OpenGL可以根据顶点绘制顺序弄清楚一个面是正面还是背面。如果我们不适用面剔除那么gl_FrontFacing变量能告诉我们当前fragment是一个正面的一部分还是背面的一部分。然后我们可以决定做一些事情比如为正面计算出不同的颜色。 片段着色器另一个有意思的输入变量是gl_FrontFacing变量。在面剔除教程中我们提到过OpenGL可以根据顶点绘制顺序弄清楚一个面是正面还是背面。如果我们不适用面剔除那么gl_FrontFacing变量能告诉我们当前fragment是一个正面的一部分还是背面的一部分。然后我们可以决定做一些事情比如为正面计算出不同的颜色。
gl_FrontFacing变量是一个布尔值如果fragment是正面的一部分那么就是true否则就是false。这样我们可以创建一个立方体里面和外面使用不同的纹理 gl_FrontFacing变量是一个布尔值如果fragment是正面的一部分那么就是true否则就是false。这样我们可以创建一个立方体里面和外面使用不同的纹理
@@ -93,12 +93,12 @@ gl_FrontFacing变量是一个布尔值如果fragment是正面的一部分那
#version 330 core #version 330 core
out vec4 color; out vec4 color;
in vec2 TexCoords; in vec2 TexCoords;
uniform sampler2D frontTexture; uniform sampler2D frontTexture;
uniform sampler2D backTexture; uniform sampler2D backTexture;
void main() void main()
{ {
if(gl_FrontFacing) if(gl_FrontFacing)
color = texture(frontTexture, TexCoords); color = texture(frontTexture, TexCoords);
else else
@@ -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变量写入什么它就会自动采用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++ ```c++
layout (depth_<condition>) out float gl_FragDepth; layout (depth_<condition>) out float gl_FragDepth;
@@ -143,15 +143,15 @@ unchanged |如果写入gl_FragDepth, 你就会写gl_FragCoord.z
如果把深度条件定义为greater或lessOpenGL会假定你只写入比当前的像素深度值的深度值大或小的。 如果把深度条件定义为greater或lessOpenGL会假定你只写入比当前的像素深度值的深度值大或小的。
下面是一个在像素着色器里增加深度值的例子,不过仍可开启前置深度测试: 下面是一个在片段着色器里增加深度值的例子,不过仍可开启前置深度测试:
```c++ ```c++
#version 330 core #version 330 core
layout (depth_greater) out float gl_FragDepth; layout (depth_greater) out float gl_FragDepth;
out vec4 color; out vec4 color;
void main() void main()
{ {
color = vec4(1.0f); color = vec4(1.0f);
gl_FragDepth = gl_FragCoord.z + 0.1f; gl_FragDepth = gl_FragCoord.z + 0.1f;
} }
@@ -159,11 +159,11 @@ void main()
一定要记住这个功能只在OpenGL4.2以上版本才有。 一定要记住这个功能只在OpenGL4.2以上版本才有。
### Interface blocks接口块 ### Interface blocks接口块
到目前位置,每次我们打算从顶点向像素着色器发送数据,我们都会声明一个相互匹配的输入/输入变量。从一个着色器向另一个着色器发送数据,一次将它们声明好是最简单的方式,但是随着应用变得越来越大,你也许会打算发送的不仅仅是变量,最好还可以包括数组和结构体。 到目前位置,每次我们打算从顶点向片段着色器发送数据,我们都会声明一个相互匹配的输入/输入变量。从一个着色器向另一个着色器发送数据,一次将它们声明好是最简单的方式,但是随着应用变得越来越大,你也许会打算发送的不仅仅是变量,最好还可以包括数组和结构体。
为了帮助我们组织这些变量GLSL为我们提供了一些叫做interface blocks的东西好让我们能够组织这些变量。声明interface block和声明struct有点像不同之处是它现在基于块block使用in和out关键字来声明最后它将成为一个输入或输出块block 为了帮助我们组织这些变量GLSL为我们提供了一些叫做interface blocks的东西好让我们能够组织这些变量。声明interface block和声明struct有点像不同之处是它现在基于块block使用in和out关键字来声明最后它将成为一个输入或输出块block
@@ -171,41 +171,41 @@ void main()
#version 330 core #version 330 core
layout (location = 0) in vec3 position; layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texCoords; layout (location = 1) in vec2 texCoords;
uniform mat4 model; uniform mat4 model;
uniform mat4 view; uniform mat4 view;
uniform mat4 projection; uniform mat4 projection;
out VS_OUT out VS_OUT
{ {
vec2 TexCoords; vec2 TexCoords;
} vs_out; } vs_out;
void main() void main()
{ {
gl_Position = projection * view * model * vec4(position, 1.0f); gl_Position = projection * view * model * vec4(position, 1.0f);
vs_out.TexCoords = texCoords; vs_out.TexCoords = texCoords;
} }
``` ```
这次我们声明一个叫做vs_out的interface block它把我们需要发送给下个阶段着色器的所有输出变量组合起来。虽然这是一个微不足道的例子但是你可以想象一下它的确能够帮助我们组织着色器的输入和输出。当我们希望把着色器的输入和输出组织成数组的时候它就变得很有用我们会在下节几何着色器geometry中见到。 这次我们声明一个叫做vs_out的interface block它把我们需要发送给下个阶段着色器的所有输出变量组合起来。虽然这是一个微不足道的例子但是你可以想象一下它的确能够帮助我们组织着色器的输入和输出。当我们希望把着色器的输入和输出组织成数组的时候它就变得很有用我们会在下节几何着色器geometry中见到。
然后,我们还需要在下一个着色器——像素着色器中声明一个输入interface block。块名block name应该是一样的但是实例名可以是任意的。 然后,我们还需要在下一个着色器——片段着色器中声明一个输入interface block。块名block name应该是一样的但是实例名可以是任意的。
```c++ ```c++
#version 330 core #version 330 core
out vec4 color; out vec4 color;
in VS_OUT in VS_OUT
{ {
vec2 TexCoords; vec2 TexCoords;
} fs_in; } fs_in;
uniform sampler2D texture; uniform sampler2D texture;
void main() void main()
{ {
color = texture(texture, fs_in.TexCoords); color = texture(texture, fs_in.TexCoords);
} }
``` ```
@@ -222,15 +222,15 @@ OpenGL为我们提供了一个叫做uniform缓冲对象的工具使我们能
```c++ ```c++
#version 330 core #version 330 core
layout (location = 0) in vec3 position; layout (location = 0) in vec3 position;
layout (std140) uniform Matrices layout (std140) uniform Matrices
{ {
mat4 projection; mat4 projection;
mat4 view; mat4 view;
}; };
uniform mat4 model; uniform mat4 model;
void main() void main()
{ {
gl_Position = projection * view * model * vec4(position, 1.0); gl_Position = projection * view * model * vec4(position, 1.0);
@@ -243,7 +243,7 @@ void main()
现在你可能会奇怪layout(std140)是什么意思。它的意思是说当前定义的uniform block为它的内容使用特定的内存布局这个声明实际上是设置uniform block layoutuniform块布局 现在你可能会奇怪layout(std140)是什么意思。它的意思是说当前定义的uniform block为它的内容使用特定的内存布局这个声明实际上是设置uniform block layoutuniform块布局
#### uniform block layoutuniform块布局 #### uniform block layoutuniform块布局
@@ -287,7 +287,7 @@ Struct | Equal to the computed size of its elements according to the previous ru
layout (std140) uniform ExampleBlock layout (std140) uniform ExampleBlock
{ {
// // base alignment // aligned offset // // base alignment // aligned offset
float value; // 4 // 0 float value; // 4 // 0
vec3 vector; // 16 // 16 (must be multiple of 16 so 4->16) vec3 vector; // 16 // 16 (must be multiple of 16 so 4->16)
mat4 matrix; // 16 // 32 (column 0) mat4 matrix; // 16 // 32 (column 0)
// 16 // 48 (column 1) // 16 // 48 (column 1)
@@ -330,7 +330,7 @@ glBindBuffer(GL_UNIFORM_BUFFER, 0);
我们调用glUniformBlockBinding函数来把uniform block设置到一个特定的绑定点上。函数的第一个参数是一个程序对象接着是一个uniform block索引uniform block index和打算链接的绑定点。uniform block索引是一个着色器中定义的uniform block的索引位置可以调用glGetUniformBlockIndex来获取这个值这个函数接收一个程序对象和uniform block的名字。我们可以从图表设置Lights这个uniform block链接到绑定点2 我们调用glUniformBlockBinding函数来把uniform block设置到一个特定的绑定点上。函数的第一个参数是一个程序对象接着是一个uniform block索引uniform block index和打算链接的绑定点。uniform block索引是一个着色器中定义的uniform block的索引位置可以调用glGetUniformBlockIndex来获取这个值这个函数接收一个程序对象和uniform block的名字。我们可以从图表设置Lights这个uniform block链接到绑定点2
```c++ ```c++
GLuint lights_index = glGetUniformBlockIndex(shaderA.Program, "Lights"); GLuint lights_index = glGetUniformBlockIndex(shaderA.Program, "Lights");
glUniformBlockBinding(shaderA.Program, lights_index, 2); glUniformBlockBinding(shaderA.Program, lights_index, 2);
``` ```
@@ -348,7 +348,7 @@ layout(std140, binding = 2) uniform Lights { ... };
然后我们还需要把uniform缓冲对象绑定到同样的绑定点上这个可以使用glBindBufferBase或glBindBufferRange来完成。 然后我们还需要把uniform缓冲对象绑定到同样的绑定点上这个可以使用glBindBufferBase或glBindBufferRange来完成。
```c++ ```c++
glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboExampleBlock); glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboExampleBlock);
// or // or
glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 150); glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 150);
``` ```
@@ -360,7 +360,7 @@ glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 150);
```c++ ```c++
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock); glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
GLint b = true; // bools in GLSL are represented as 4 bytes, so we store it in an integer GLint b = true; // bools in GLSL are represented as 4 bytes, so we store it in an integer
glBufferSubData(GL_UNIFORM_BUFFER, 142, 4, &b); glBufferSubData(GL_UNIFORM_BUFFER, 142, 4, &b);
glBindBuffer(GL_UNIFORM_BUFFER, 0); glBindBuffer(GL_UNIFORM_BUFFER, 0);
``` ```
@@ -374,21 +374,21 @@ glBindBuffer(GL_UNIFORM_BUFFER, 0);
```c++ ```c++
#version 330 core #version 330 core
layout (location = 0) in vec3 position; layout (location = 0) in vec3 position;
layout (std140) uniform Matrices layout (std140) uniform Matrices
{ {
mat4 projection; mat4 projection;
mat4 view; mat4 view;
}; };
uniform mat4 model; uniform mat4 model;
void main() void main()
{ {
gl_Position = projection * view * model * vec4(position, 1.0); gl_Position = projection * view * model * vec4(position, 1.0);
} }
``` ```
这儿没什么特别的除了我们现在使用了一个带有std140布局的uniform block。我们在例程中将显示4个立方体每个立方体都使用一个不同的着色器程序。4个着色器程序使用同样的顶点着色器但是它们将使用各自的像素着色器,每个像素着色器输出一个单色。 这儿没什么特别的除了我们现在使用了一个带有std140布局的uniform block。我们在例程中将显示4个立方体每个立方体都使用一个不同的着色器程序。4个着色器程序使用同样的顶点着色器但是它们将使用各自的片段着色器,每个片段着色器输出一个单色。
首先我们把顶点着色器的uniform block设置为绑定点0。注意我们必须为每个着色器做这件事。 首先我们把顶点着色器的uniform block设置为绑定点0。注意我们必须为每个着色器做这件事。
@@ -397,7 +397,7 @@ GLuint uniformBlockIndexRed = glGetUniformBlockIndex(shaderRed.Program, "Matrice
GLuint uniformBlockIndexGreen = glGetUniformBlockIndex(shaderGreen.Program, "Matrices"); GLuint uniformBlockIndexGreen = glGetUniformBlockIndex(shaderGreen.Program, "Matrices");
GLuint uniformBlockIndexBlue = glGetUniformBlockIndex(shaderBlue.Program, "Matrices"); GLuint uniformBlockIndexBlue = glGetUniformBlockIndex(shaderBlue.Program, "Matrices");
GLuint uniformBlockIndexYellow = glGetUniformBlockIndex(shaderYellow.Program, "Matrices"); GLuint uniformBlockIndexYellow = glGetUniformBlockIndex(shaderYellow.Program, "Matrices");
glUniformBlockBinding(shaderRed.Program, uniformBlockIndexRed, 0); glUniformBlockBinding(shaderRed.Program, uniformBlockIndexRed, 0);
glUniformBlockBinding(shaderGreen.Program, uniformBlockIndexGreen, 0); glUniformBlockBinding(shaderGreen.Program, uniformBlockIndexGreen, 0);
glUniformBlockBinding(shaderBlue.Program, uniformBlockIndexBlue, 0); glUniformBlockBinding(shaderBlue.Program, uniformBlockIndexBlue, 0);
@@ -409,11 +409,11 @@ glUniformBlockBinding(shaderYellow.Program, uniformBlockIndexYellow, 0);
```c++ ```c++
GLuint uboMatrices GLuint uboMatrices
glGenBuffers(1, &uboMatrices); glGenBuffers(1, &uboMatrices);
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices); glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_STATIC_DRAW); glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0); glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindBufferRange(GL_UNIFORM_BUFFER, 0, uboMatrices, 0, 2 * sizeof(glm::mat4)); glBindBufferRange(GL_UNIFORM_BUFFER, 0, uboMatrices, 0, 2 * sizeof(glm::mat4));
``` ```
@@ -431,7 +431,7 @@ glBindBuffer(GL_UNIFORM_BUFFER, 0);
这里我们用投影矩阵储存了uniform缓冲的前半部分。在我们在每次渲染迭代绘制物体前我们用视图矩阵更新缓冲的第二个部分 这里我们用投影矩阵储存了uniform缓冲的前半部分。在我们在每次渲染迭代绘制物体前我们用视图矩阵更新缓冲的第二个部分
```c++ ```c++
glm::mat4 view = camera.GetViewMatrix(); glm::mat4 view = camera.GetViewMatrix();
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices); glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData( glBufferSubData(
GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view)); GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view));
@@ -446,7 +446,7 @@ shaderRed.Use();
glm::mat4 model; glm::mat4 model;
model = glm::translate(model, glm::vec3(-0.75f, 0.75f, 0.0f)); // Move top-left model = glm::translate(model, glm::vec3(-0.75f, 0.75f, 0.0f)); // Move top-left
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glDrawArrays(GL_TRIANGLES, 0, 36); glDrawArrays(GL_TRIANGLES, 0, 36);
// ... Draw Green Cube // ... Draw Green Cube
// ... Draw Blue Cube // ... Draw Blue Cube
// ... Draw Yellow Cube // ... Draw Yellow Cube
@@ -457,8 +457,8 @@ glBindVertexArray(0);
![](http://learnopengl.com/img/advanced/advanced_glsl_uniform_buffer_objects.png) ![](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)。 你可以[在这里获得例程的完整源码](http://www.learnopengl.com/code_viewer.php?code=advanced/advanced_glsl_uniform_buffer_objects)。
uniform缓冲对象比单独的uniform有很多好处。第一一次设置多个uniform比一次设置一个速度快。第二如果你打算改变一个横跨多个着色器的uniform在uniform缓冲中只需更改一次。最后一个好处可能不是很明显使用uniform缓冲对象你可以在着色器中使用更多的uniform。OpenGL有一个对可使用uniform数据的数量的限制可以用GL_MAX_VERTEX_UNIFORM_COMPONENTS来获取。当使用uniform缓冲对象中这个限制的阈限会更高。所以无论何时你达到了uniform的最大使用数量比如做谷歌动画的时候你可以使用uniform缓冲对象。 uniform缓冲对象比单独的uniform有很多好处。第一一次设置多个uniform比一次设置一个速度快。第二如果你打算改变一个横跨多个着色器的uniform在uniform缓冲中只需更改一次。最后一个好处可能不是很明显使用uniform缓冲对象你可以在着色器中使用更多的uniform。OpenGL有一个对可使用uniform数据的数量的限制可以用GL_MAX_VERTEX_UNIFORM_COMPONENTS来获取。当使用uniform缓冲对象中这个限制的阈限会更高。所以无论何时你达到了uniform的最大使用数量比如做谷歌动画的时候你可以使用uniform缓冲对象。

View File

@@ -2,7 +2,7 @@
本文作者JoeyDeVries由Django翻译自http://learnopengl.com 本文作者JoeyDeVries由Django翻译自http://learnopengl.com
在顶点和像素着色器张志坚有一个可选的着色器阶段叫做几何着色器geometry shader。几何着色器以一个或多个表示为一个单独基本图形primitive的顶点作为输入比如可以是一个点或者三角形。几何着色器在将这些顶点发送到下一个着色阶段之前可以将这些顶点转变为它认为合适的内容。几何着色器有意思的地方在于它可以把一个或多个顶点转变为完全不同的基本图形primitive从而生成比原来多得多的顶点。 在顶点和片段着色器张志坚有一个可选的着色器阶段叫做几何着色器geometry shader。几何着色器以一个或多个表示为一个单独基本图形primitive的顶点作为输入比如可以是一个点或者三角形。几何着色器在将这些顶点发送到下一个着色阶段之前可以将这些顶点转变为它认为合适的内容。几何着色器有意思的地方在于它可以把一个或多个顶点转变为完全不同的基本图形primitive从而生成比原来多得多的顶点。
我们直接用一个例子深入了解一下: 我们直接用一个例子深入了解一下:
@@ -10,14 +10,14 @@
#version 330 core #version 330 core
layout (points) in; layout (points) in;
layout (line_strip, max_vertices = 2) out; layout (line_strip, max_vertices = 2) out;
void main() { void main() {
gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0); gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0);
EmitVertex(); EmitVertex();
gl_Position = gl_in[0].gl_Position + vec4(0.1, 0.0, 0.0, 0.0); gl_Position = gl_in[0].gl_Position + vec4(0.1, 0.0, 0.0, 0.0);
EmitVertex(); EmitVertex();
EndPrimitive(); EndPrimitive();
} }
``` ```
@@ -60,7 +60,7 @@ in gl_Vertex
float gl_PointSize; float gl_PointSize;
float gl_ClipDistance[]; float gl_ClipDistance[];
} gl_in[]; } gl_in[];
``` ```
这里它被声明为一个interface block前面的教程已经讨论过它包含几个有意思的变量其中最有意思的是gl_Position它包含着和我们设置的顶点着色器的输出相似的向量。 这里它被声明为一个interface block前面的教程已经讨论过它包含几个有意思的变量其中最有意思的是gl_Position它包含着和我们设置的顶点着色器的输出相似的向量。
@@ -70,13 +70,13 @@ in gl_Vertex
使用从前一个顶点着色阶段的顶点数据我们就可以开始生成新的数据了这是通过2个几何着色器函数EmitVertex和EndPrimitive来完成的。几何着色器需要你去生成/输出至少一个你定义为输出的基本图形。在我们的例子里我们打算至少生成一个线条line strip基本图形。 使用从前一个顶点着色阶段的顶点数据我们就可以开始生成新的数据了这是通过2个几何着色器函数EmitVertex和EndPrimitive来完成的。几何着色器需要你去生成/输出至少一个你定义为输出的基本图形。在我们的例子里我们打算至少生成一个线条line strip基本图形。
```c++ ```c++
void main() { void main() {
gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0); gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0);
EmitVertex(); EmitVertex();
gl_Position = gl_in[0].gl_Position + vec4(0.1, 0.0, 0.0, 0.0); gl_Position = gl_in[0].gl_Position + vec4(0.1, 0.0, 0.0, 0.0);
EmitVertex(); EmitVertex();
EndPrimitive(); EndPrimitive();
} }
``` ```
@@ -113,22 +113,22 @@ GLfloat points[] = {
```c++ ```c++
#version 330 core #version 330 core
layout (location = 0) in vec2 position; layout (location = 0) in vec2 position;
void main() void main()
{ {
gl_Position = vec4(position.x, position.y, 0.0f, 1.0f); gl_Position = vec4(position.x, position.y, 0.0f, 1.0f);
} }
``` ```
我们会简单地为所有点输出绿色,我们直接在像素着色器里进行硬编码: 我们会简单地为所有点输出绿色,我们直接在片段着色器里进行硬编码:
```c++ ```c++
#version 330 core #version 330 core
out vec4 color; out vec4 color;
void main() void main()
{ {
color = vec4(0.0f, 1.0f, 0.0f, 1.0f); color = vec4(0.0f, 1.0f, 0.0f, 1.0f);
} }
``` ```
@@ -153,9 +153,9 @@ glBindVertexArray(0);
#version 330 core #version 330 core
layout (points) in; layout (points) in;
layout (points, max_vertices = 1) out; layout (points, max_vertices = 1) out;
void main() { void main() {
gl_Position = gl_in[0].gl_Position; gl_Position = gl_in[0].gl_Position;
EmitVertex(); EmitVertex();
EndPrimitive(); EndPrimitive();
} }
@@ -163,7 +163,7 @@ void main() {
现在这个几何着色器应该很容易理解了。它简单地将它接收到的输入的无修改的顶点位置发射出去然后生成一个point基本图形。 现在这个几何着色器应该很容易理解了。它简单地将它接收到的输入的无修改的顶点位置发射出去然后生成一个point基本图形。
一个几何着色器需要像顶点和像素着色器一样被编译和链接但是这次我们将使用GL_GEOMETRY_SHADER作为着色器的类型来创建这个着色器 一个几何着色器需要像顶点和片段着色器一样被编译和链接但是这次我们将使用GL_GEOMETRY_SHADER作为着色器的类型来创建这个着色器
```c++ ```c++
geometryShader = glCreateShader(GL_GEOMETRY_SHADER); geometryShader = glCreateShader(GL_GEOMETRY_SHADER);
@@ -174,7 +174,7 @@ glAttachShader(program, geometryShader);
glLinkProgram(program); glLinkProgram(program);
``` ```
编译着色器的代码和顶点、像素着色器的基本一样。要记得检查编译和链接错误! 编译着色器的代码和顶点、片段着色器的基本一样。要记得检查编译和链接错误!
如果你现在编译和运行,就会看到和下面相似的结果: 如果你现在编译和运行,就会看到和下面相似的结果:
@@ -182,7 +182,7 @@ glLinkProgram(program);
它和没用几何着色器一样!我承认有点无聊,但是事实上,我们仍能绘制证明几何着色器工作了的点,所以现在是时候来做点更有意思的事了! 它和没用几何着色器一样!我承认有点无聊,但是事实上,我们仍能绘制证明几何着色器工作了的点,所以现在是时候来做点更有意思的事了!
### 创建几个房子 ### 创建几个房子
@@ -202,11 +202,11 @@ glLinkProgram(program);
#version 330 core #version 330 core
layout (points) in; layout (points) in;
layout (triangle_strip, max_vertices = 5) out; layout (triangle_strip, max_vertices = 5) out;
void build_house(vec4 position) void build_house(vec4 position)
{ {
gl_Position = position + vec4(-0.2f, -0.2f, 0.0f, 0.0f); // 1:bottom-left gl_Position = position + vec4(-0.2f, -0.2f, 0.0f, 0.0f); // 1:bottom-left
EmitVertex(); EmitVertex();
gl_Position = position + vec4( 0.2f, -0.2f, 0.0f, 0.0f); // 2:bottom-right gl_Position = position + vec4( 0.2f, -0.2f, 0.0f, 0.0f); // 2:bottom-right
EmitVertex(); EmitVertex();
gl_Position = position + vec4(-0.2f, 0.2f, 0.0f, 0.0f); // 3:top-left gl_Position = position + vec4(-0.2f, 0.2f, 0.0f, 0.0f); // 3:top-left
@@ -217,15 +217,15 @@ void build_house(vec4 position)
EmitVertex(); EmitVertex();
EndPrimitive(); EndPrimitive();
} }
void main() void main()
{ {
build_house(gl_in[0].gl_Position); build_house(gl_in[0].gl_Position);
} }
``` ```
这个几何着色器生成5个顶点每个顶点是点point的位置加上一个偏移量来组成一个大triangle strip。接着最后的基本图形被像素化像素着色器处理整个triangle strip结果是为我们绘制的每个点生成一个绿房子 这个几何着色器生成5个顶点每个顶点是点point的位置加上一个偏移量来组成一个大triangle strip。接着最后的基本图形被像素化片段着色器处理整个triangle strip结果是为我们绘制的每个点生成一个绿房子
![](http://learnopengl.com/img/advanced/geometry_shader_houses.png) ![](http://learnopengl.com/img/advanced/geometry_shader_houses.png)
@@ -240,7 +240,7 @@ GLfloat points[] = {
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // Bottom-right 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // Bottom-right
-0.5f, -0.5f, 1.0f, 1.0f, 0.0f // Bottom-left -0.5f, -0.5f, 1.0f, 1.0f, 0.0f // Bottom-left
}; };
``` ```
然后我们更新顶点着色器使用一个interface block来像几何着色器发送颜色属性 然后我们更新顶点着色器使用一个interface block来像几何着色器发送颜色属性
@@ -248,14 +248,14 @@ GLfloat points[] = {
#version 330 core #version 330 core
layout (location = 0) in vec2 position; layout (location = 0) in vec2 position;
layout (location = 1) in vec3 color; layout (location = 1) in vec3 color;
out VS_OUT { out VS_OUT {
vec3 color; vec3 color;
} vs_out; } vs_out;
void main() void main()
{ {
gl_Position = vec4(position.x, position.y, 0.0f, 1.0f); gl_Position = vec4(position.x, position.y, 0.0f, 1.0f);
vs_out.color = color; vs_out.color = color;
} }
``` ```
@@ -273,24 +273,24 @@ in VS_OUT {
!!! Important !!! Important
我们不是必须使用interface block来把数据发送到几何着色器中。我们还可以这么写 我们不是必须使用interface block来把数据发送到几何着色器中。我们还可以这么写
in vec3 vColor[]; in vec3 vColor[];
如果顶点着色器发送的颜色向量是out vec3 vColor那么interface block就会在比如几何着色器这样的着色器中更轻松地完成工作。事实上几何着色器的输入可以非常大把它们组成一个大的interface block数组会更有意义。 如果顶点着色器发送的颜色向量是out vec3 vColor那么interface block就会在比如几何着色器这样的着色器中更轻松地完成工作。事实上几何着色器的输入可以非常大把它们组成一个大的interface block数组会更有意义。
然后我们还要为下一个像素着色阶段僧名一个输出颜色向量: 然后我们还要为下一个像素着色阶段僧名一个输出颜色向量:
```c++ ```c++
out vec3 fColor; out vec3 fColor;
``` ```
因为像素着色器只需要一个已进行了插值的颜色传送多个颜色没有意义。fColor向量这样就不是一个数组而是一个单一的向量。当发射一个顶点时为了它的像素着色器运行每个顶点都会储存最后在fColor中储存的值。对于这些房子来说我们可以在第一个顶点被发射对整个房子上色前只使用来自顶点着色器的颜色填充fColor一次 因为片段着色器只需要一个已进行了插值的颜色传送多个颜色没有意义。fColor向量这样就不是一个数组而是一个单一的向量。当发射一个顶点时为了它的片段着色器运行每个顶点都会储存最后在fColor中储存的值。对于这些房子来说我们可以在第一个顶点被发射对整个房子上色前只使用来自顶点着色器的颜色填充fColor一次
```c++ ```c++
fColor = gs_in[0].color; // gs_in[0] since there's only one input vertex fColor = gs_in[0].color; // gs_in[0] since there's only one input vertex
gl_Position = position + vec4(-0.2f, -0.2f, 0.0f, 0.0f); // 1:bottom-left gl_Position = position + vec4(-0.2f, -0.2f, 0.0f, 0.0f); // 1:bottom-left
EmitVertex(); EmitVertex();
gl_Position = position + vec4( 0.2f, -0.2f, 0.0f, 0.0f); // 2:bottom-right gl_Position = position + vec4( 0.2f, -0.2f, 0.0f, 0.0f); // 2:bottom-right
EmitVertex(); EmitVertex();
gl_Position = position + vec4(-0.2f, 0.2f, 0.0f, 0.0f); // 3:top-left gl_Position = position + vec4(-0.2f, 0.2f, 0.0f, 0.0f); // 3:top-left
@@ -309,9 +309,9 @@ EndPrimitive();
为了好玩儿,我们还可以假装这是在冬天,给最后一个顶点一个自己的白色,就像在屋顶上落了一些雪。 为了好玩儿,我们还可以假装这是在冬天,给最后一个顶点一个自己的白色,就像在屋顶上落了一些雪。
```c++ ```c++
fColor = gs_in[0].color; fColor = gs_in[0].color;
gl_Position = position + vec4(-0.2f, -0.2f, 0.0f, 0.0f); // 1:bottom-left gl_Position = position + vec4(-0.2f, -0.2f, 0.0f, 0.0f); // 1:bottom-left
EmitVertex(); EmitVertex();
gl_Position = position + vec4( 0.2f, -0.2f, 0.0f, 0.0f); // 2:bottom-right gl_Position = position + vec4( 0.2f, -0.2f, 0.0f, 0.0f); // 2:bottom-right
EmitVertex(); EmitVertex();
gl_Position = position + vec4(-0.2f, 0.2f, 0.0f, 0.0f); // 3:top-left gl_Position = position + vec4(-0.2f, 0.2f, 0.0f, 0.0f); // 3:top-left
@@ -352,7 +352,7 @@ vec3 GetNormal()
vec3 b = vec3(gl_in[2].gl_Position) - vec3(gl_in[1].gl_Position); vec3 b = vec3(gl_in[2].gl_Position) - vec3(gl_in[1].gl_Position);
return normalize(cross(a, b)); return normalize(cross(a, b));
} }
``` ```
这里我们使用减法获取了两个向量a和b它们平行于三角形的表面。两个向量相减得到一个两个向量的差值由于所有3个点都在三角形平面上任何向量相减都会得到一个平行于平面的向量。一定要注意如果我们调换了a和b的叉乘顺序我们得到的法线向量就会使反的顺序很重要 这里我们使用减法获取了两个向量a和b它们平行于三角形的表面。两个向量相减得到一个两个向量的差值由于所有3个点都在三角形平面上任何向量相减都会得到一个平行于平面的向量。一定要注意如果我们调换了a和b的叉乘顺序我们得到的法线向量就会使反的顺序很重要
@@ -362,7 +362,7 @@ vec3 GetNormal()
vec4 explode(vec4 position, vec3 normal) vec4 explode(vec4 position, vec3 normal)
{ {
float magnitude = 2.0f; float magnitude = 2.0f;
vec3 direction = normal * ((sin(time) + 1.0f) / 2.0f) * magnitude; vec3 direction = normal * ((sin(time) + 1.0f) / 2.0f) * magnitude;
return position + vec4(direction, 0.0f); return position + vec4(direction, 0.0f);
} }
``` ```
@@ -375,22 +375,22 @@ vec4 explode(vec4 position, vec3 normal)
#version 330 core #version 330 core
layout (triangles) in; layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out; layout (triangle_strip, max_vertices = 3) out;
in VS_OUT { in VS_OUT {
vec2 texCoords; vec2 texCoords;
} gs_in[]; } gs_in[];
out vec2 TexCoords; out vec2 TexCoords;
uniform float time; uniform float time;
vec4 explode(vec4 position, vec3 normal) { ... } vec4 explode(vec4 position, vec3 normal) { ... }
vec3 GetNormal() { ... } vec3 GetNormal() { ... }
void main() { void main() {
vec3 normal = GetNormal(); vec3 normal = GetNormal();
gl_Position = explode(gl_in[0].gl_Position, normal); gl_Position = explode(gl_in[0].gl_Position, normal);
TexCoords = gs_in[0].texCoords; TexCoords = gs_in[0].texCoords;
EmitVertex(); EmitVertex();
@@ -409,7 +409,7 @@ void main() {
也不要忘记在OpenGL代码中设置time变量 也不要忘记在OpenGL代码中设置time变量
```c++ ```c++
glUniform1f(glGetUniformLocation(shader.Program, "time"), glfwGetTime()); glUniform1f(glGetUniformLocation(shader.Program, "time"), glfwGetTime());
``` ```
最后的结果是一个随着时间持续不断地爆炸的3D模型不断爆炸不断回到正常状态。尽管没什么大用处它却向你展示出很多几何着色器的高级用法。你可以用完整的源码和着色器对比一下你自己的。 最后的结果是一个随着时间持续不断地爆炸的3D模型不断爆炸不断回到正常状态。尽管没什么大用处它却向你展示出很多几何着色器的高级用法。你可以用完整的源码和着色器对比一下你自己的。
@@ -433,18 +433,18 @@ DrawScene();
#version 330 core #version 330 core
layout (location = 0) in vec3 position; layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal; layout (location = 1) in vec3 normal;
out VS_OUT { out VS_OUT {
vec3 normal; vec3 normal;
} vs_out; } vs_out;
uniform mat4 projection; uniform mat4 projection;
uniform mat4 view; uniform mat4 view;
uniform mat4 model; uniform mat4 model;
void main() void main()
{ {
gl_Position = projection * view * model * vec4(position, 1.0f); gl_Position = projection * view * model * vec4(position, 1.0f);
mat3 normalMatrix = mat3(transpose(inverse(view * model))); mat3 normalMatrix = mat3(transpose(inverse(view * model)));
vs_out.normal = normalize(vec3(projection * vec4(normalMatrix * normal, 1.0))); vs_out.normal = normalize(vec3(projection * vec4(normalMatrix * normal, 1.0)));
} }
@@ -456,13 +456,13 @@ void main()
#version 330 core #version 330 core
layout (triangles) in; layout (triangles) in;
layout (line_strip, max_vertices = 6) out; layout (line_strip, max_vertices = 6) out;
in VS_OUT { in VS_OUT {
vec3 normal; vec3 normal;
} gs_in[]; } gs_in[];
const float MAGNITUDE = 0.4f; const float MAGNITUDE = 0.4f;
void GenerateLine(int index) void GenerateLine(int index)
{ {
gl_Position = gl_in[index].gl_Position; gl_Position = gl_in[index].gl_Position;
@@ -471,7 +471,7 @@ void GenerateLine(int index)
EmitVertex(); EmitVertex();
EndPrimitive(); EndPrimitive();
} }
void main() void main()
{ {
GenerateLine(0); // First vertex normal GenerateLine(0); // First vertex normal
@@ -482,12 +482,12 @@ void main()
到现在为止像这样的几何着色器的内容就不言自明了。需要注意的是我们我们把法线向量乘以一个MAGNITUDE向量来限制显示出的法线向量的大小否则它们就太大了 到现在为止像这样的几何着色器的内容就不言自明了。需要注意的是我们我们把法线向量乘以一个MAGNITUDE向量来限制显示出的法线向量的大小否则它们就太大了
由于把法线显示出来通常用于调试的目的,我们可以在像素着色器的帮助下把它们显示为单色的线(如果你愿意也可以更炫一点)。 由于把法线显示出来通常用于调试的目的,我们可以在片段着色器的帮助下把它们显示为单色的线(如果你愿意也可以更炫一点)。
```c++ ```c++
#version 330 core #version 330 core
out vec4 color; out vec4 color;
void main() void main()
{ {
color = vec4(1.0f, 1.0f, 0.0f, 1.0f); color = vec4(1.0f, 1.0f, 0.0f, 1.0f);
@@ -500,4 +500,4 @@ void main()
除了我们的纳米服现在看起来有点像一个带着隔热手套的全身多毛的家伙外,它给了我们一种非常有效的检查一个模型的法线向量是否有错误的方式。你可以想象下这样的几何着色器也经常能被用在给物体添加毛发上。 除了我们的纳米服现在看起来有点像一个带着隔热手套的全身多毛的家伙外,它给了我们一种非常有效的检查一个模型的法线向量是否有错误的方式。你可以想象下这样的几何着色器也经常能被用在给物体添加毛发上。
你可以从这里找到[源码](http://learnopengl.com/code_viewer.php?code=advanced/geometry_shader_normals)和可显示法线的[着色器](http://learnopengl.com/code_viewer.php?code=advanced/geometry_shader_normals_shaders)。 你可以从这里找到[源码](http://learnopengl.com/code_viewer.php?code=advanced/geometry_shader_normals)和可显示法线的[着色器](http://learnopengl.com/code_viewer.php?code=advanced/geometry_shader_normals_shaders)。

View File

@@ -13,7 +13,7 @@ for(GLuint i = 0; i < amount_of_models_to_draw; i++)
glDrawArrays(GL_TRIANGLES, 0, amount_of_vertices); glDrawArrays(GL_TRIANGLES, 0, amount_of_vertices);
} }
``` ```
像这样绘制出你模型的其他实例多次绘制之后很快将达到一个瓶颈。和渲染真实的顶点相比告诉GPU使用像glDrawArrays或glDrawElements这样的函数去渲染你的顶点数据会明显降低执行效率这是因为OpenGL比在它可以绘制你的顶点数据之前必须做一些不必要的准备工作比如告诉GPU从哪个缓冲读取数据以及在哪里找到顶点属性所有这些都会是CPU到GPU的总线变慢。所以即使渲染顶点超快给你的GPU下达这样的渲染命令却未必。 像这样绘制出你模型的其他实例多次绘制之后很快将达到一个瓶颈。和渲染真实的顶点相比告诉GPU使用像glDrawArrays或glDrawElements这样的函数去渲染你的顶点数据会明显降低执行效率这是因为OpenGL比在它可以绘制你的顶点数据之前必须做一些不必要的准备工作比如告诉GPU从哪个缓冲读取数据以及在哪里找到顶点属性所有这些都会是CPU到GPU的总线变慢。所以即使渲染顶点超快给你的GPU下达这样的渲染命令却未必。
@@ -37,20 +37,20 @@ GLfloat quadVertices[] = {
-0.05f, 0.05f, 1.0f, 0.0f, 0.0f, -0.05f, 0.05f, 1.0f, 0.0f, 0.0f,
0.05f, -0.05f, 0.0f, 1.0f, 0.0f, 0.05f, -0.05f, 0.0f, 1.0f, 0.0f,
-0.05f, -0.05f, 0.0f, 0.0f, 1.0f, -0.05f, -0.05f, 0.0f, 0.0f, 1.0f,
-0.05f, 0.05f, 1.0f, 0.0f, 0.0f, -0.05f, 0.05f, 1.0f, 0.0f, 0.0f,
0.05f, -0.05f, 0.0f, 1.0f, 0.0f, 0.05f, -0.05f, 0.0f, 1.0f, 0.0f,
0.05f, 0.05f, 0.0f, 1.0f, 1.0f 0.05f, 0.05f, 0.0f, 1.0f, 1.0f
}; };
``` ```
像素着色器接收从顶点着色器发送来的颜色向量,设置为它的颜色输出,从而为四边形上色: 片段着色器接收从顶点着色器发送来的颜色向量,设置为它的颜色输出,从而为四边形上色:
```c++ ```c++
#version 330 core #version 330 core
in vec3 fColor; in vec3 fColor;
out vec4 color; out vec4 color;
void main() void main()
{ {
color = vec4(fColor, 1.0f); color = vec4(fColor, 1.0f);
@@ -63,11 +63,11 @@ void main()
#version 330 core #version 330 core
layout (location = 0) in vec2 position; layout (location = 0) in vec2 position;
layout (location = 1) in vec3 color; layout (location = 1) in vec3 color;
out vec3 fColor; out vec3 fColor;
uniform vec2 offsets[100]; uniform vec2 offsets[100];
void main() void main()
{ {
vec2 offset = offsets[gl_InstanceID]; vec2 offset = offsets[gl_InstanceID];
@@ -95,7 +95,7 @@ for(GLint y = -10; y < 10; y += 2)
} }
} }
``` ```
这里我们创建100个平移向量它包含着10×10格子所有位置。除了生成translations数组外我们还需要把数据发送到顶点着色器的uniform数组 这里我们创建100个平移向量它包含着10×10格子所有位置。除了生成translations数组外我们还需要把数据发送到顶点着色器的uniform数组
```c++ ```c++
@@ -104,8 +104,8 @@ for(GLuint i = 0; i < 100; i++)
{ {
stringstream ss; stringstream ss;
string index; string index;
ss << i; ss << i;
index = ss.str(); index = ss.str();
GLint location = glGetUniformLocation(shader.Program, ("offsets[" + index + "]").c_str()) GLint location = glGetUniformLocation(shader.Program, ("offsets[" + index + "]").c_str())
glUniform2f(location, translations[i].x, translations[i].y); glUniform2f(location, translations[i].x, translations[i].y);
} }
@@ -136,15 +136,15 @@ glDrawArraysInstanced的参数和glDrawArrays一样除了最后一个参数
layout (location = 0) in vec2 position; layout (location = 0) in vec2 position;
layout (location = 1) in vec3 color; layout (location = 1) in vec3 color;
layout (location = 2) in vec2 offset; layout (location = 2) in vec2 offset;
out vec3 fColor; out vec3 fColor;
void main() void main()
{ {
gl_Position = vec4(position + offset, 0.0f, 1.0f); gl_Position = vec4(position + offset, 0.0f, 1.0f);
fColor = color; fColor = color;
} }
``` ```
我们不再使用gl_InstanceID可以直接用offset属性不用先在一个大uniform数组里进行索引。 我们不再使用gl_InstanceID可以直接用offset属性不用先在一个大uniform数组里进行索引。
@@ -164,7 +164,7 @@ glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(2); glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO); glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (GLvoid*)0); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (GLvoid*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribDivisor(2, 1); glVertexAttribDivisor(2, 1);
``` ```
@@ -207,7 +207,7 @@ void main()
GLuint amount = 1000; GLuint amount = 1000;
glm::mat4* modelMatrices; glm::mat4* modelMatrices;
modelMatrices = new glm::mat4[amount]; modelMatrices = new glm::mat4[amount];
srand(glfwGetTime()); // initialize random seed srand(glfwGetTime()); // initialize random seed
GLfloat radius = 50.0; GLfloat radius = 50.0;
GLfloat offset = 2.5f; GLfloat offset = 2.5f;
for(GLuint i = 0; i < amount; i++) for(GLuint i = 0; i < amount; i++)
@@ -221,10 +221,10 @@ for(GLuint i = 0; i < amount; i++)
GLfloat y = displacement * 0.4f; // y value has smaller displacement GLfloat y = displacement * 0.4f; // y value has smaller displacement
displacement = (rand() % (GLint)(2 * offset * 100)) / 100.0f - offset; displacement = (rand() % (GLint)(2 * offset * 100)) / 100.0f - offset;
GLfloat z = cos(angle) * radius + displacement; GLfloat z = cos(angle) * radius + displacement;
model = glm::translate(model, glm::vec3(x, y, z)); model = glm::translate(model, glm::vec3(x, y, z));
// 2. Scale: Scale between 0.05 and 0.25f // 2. Scale: Scale between 0.05 and 0.25f
GLfloat scale = (rand() % 20) / 100.0f + 0.05; GLfloat scale = (rand() % 20) / 100.0f + 0.05;
model = glm::scale(model, glm::vec3(scale)); model = glm::scale(model, glm::vec3(scale));
// 3. Rotation: add random rotation around a (semi)randomly picked rotation axis vector // 3. Rotation: add random rotation around a (semi)randomly picked rotation axis vector
GLfloat rotAngle = (rand() % 360); GLfloat rotAngle = (rand() % 360);
model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f)); model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f));
@@ -245,7 +245,7 @@ model = glm::translate(model, glm::vec3(0.0f, -5.0f, 0.0f));
model = glm::scale(model, glm::vec3(4.0f, 4.0f, 4.0f)); model = glm::scale(model, glm::vec3(4.0f, 4.0f, 4.0f));
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
planet.Draw(shader); planet.Draw(shader);
// Draw Asteroid circle // Draw Asteroid circle
for(GLuint i = 0; i < amount; i++) for(GLuint i = 0; i < amount; i++)
{ {
@@ -271,15 +271,15 @@ for(GLuint i = 0; i < amount; i++)
layout (location = 0) in vec3 position; layout (location = 0) in vec3 position;
layout (location = 2) in vec2 texCoords; layout (location = 2) in vec2 texCoords;
layout (location = 3) in mat4 instanceMatrix; layout (location = 3) in mat4 instanceMatrix;
out vec2 TexCoords; out vec2 TexCoords;
uniform mat4 projection; uniform mat4 projection;
uniform mat4 view; uniform mat4 view;
void main() void main()
{ {
gl_Position = projection * view * instanceMatrix * vec4(position, 1.0f); gl_Position = projection * view * instanceMatrix * vec4(position, 1.0f);
TexCoords = texCoords; TexCoords = texCoords;
} }
``` ```
@@ -300,20 +300,20 @@ for(GLuint i = 0; i < rock.meshes.size(); i++)
glBufferData(GL_ARRAY_BUFFER, amount * sizeof(glm::mat4), &modelMatrices[0], GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, amount * sizeof(glm::mat4), &modelMatrices[0], GL_STATIC_DRAW);
// Vertex Attributes // Vertex Attributes
GLsizei vec4Size = sizeof(glm::vec4); GLsizei vec4Size = sizeof(glm::vec4);
glEnableVertexAttribArray(3); glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (GLvoid*)0); glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (GLvoid*)0);
glEnableVertexAttribArray(4); glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (GLvoid*)(vec4Size)); glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (GLvoid*)(vec4Size));
glEnableVertexAttribArray(5); glEnableVertexAttribArray(5);
glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (GLvoid*)(2 * vec4Size)); glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (GLvoid*)(2 * vec4Size));
glEnableVertexAttribArray(6); glEnableVertexAttribArray(6);
glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (GLvoid*)(3 * vec4Size)); glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (GLvoid*)(3 * vec4Size));
glVertexAttribDivisor(3, 1); glVertexAttribDivisor(3, 1);
glVertexAttribDivisor(4, 1); glVertexAttribDivisor(4, 1);
glVertexAttribDivisor(5, 1); glVertexAttribDivisor(5, 1);
glVertexAttribDivisor(6, 1); glVertexAttribDivisor(6, 1);
glBindVertexArray(0); glBindVertexArray(0);
} }
``` ```
@@ -345,4 +345,4 @@ for(GLuint i = 0; i < rock.meshes.size(); i++)
有些机器渲染十万可能会有点吃力,所以尝试修改这个数量知道你能获得可以接受的帧率。 有些机器渲染十万可能会有点吃力,所以尝试修改这个数量知道你能获得可以接受的帧率。
就像你所看到的,在合适的条件下,实例渲染对于你的显卡来说和普通渲染有很大不同。处于这个理由,实例渲染通常用来渲染草、草丛、粒子以及像这样的场景,基本上来讲只要场景中有很多重复物体,使用实例渲染都会获得好处。 就像你所看到的,在合适的条件下,实例渲染对于你的显卡来说和普通渲染有很大不同。处于这个理由,实例渲染通常用来渲染草、草丛、粒子以及像这样的场景,基本上来讲只要场景中有很多重复物体,使用实例渲染都会获得好处。

View File

@@ -20,11 +20,11 @@
为了理解什么是多采样以及它是如何解决走样的问题的我们先要更深入了解一个OpenGL像素器的工作方式。 为了理解什么是多采样以及它是如何解决走样的问题的我们先要更深入了解一个OpenGL像素器的工作方式。
像素器是是你的最终的经处理的顶点和像素着色器之间的所有算法和处理的集合。像素器将属于一个基本图形的所有顶点转化为一系列fragment。顶点坐标理论上可以含有任何坐标但fragment却不是这样这是因为它们与你的窗口的解析度有关。几乎永远都不会有顶点坐标和fragment的一对一映射所以像素器必须以某种方式决定每个特定顶点最终结束于哪个fragment/屏幕坐标上。 像素器是是你的最终的经处理的顶点和片段着色器之间的所有算法和处理的集合。像素器将属于一个基本图形的所有顶点转化为一系列fragment。顶点坐标理论上可以含有任何坐标但fragment却不是这样这是因为它们与你的窗口的解析度有关。几乎永远都不会有顶点坐标和fragment的一对一映射所以像素器必须以某种方式决定每个特定顶点最终结束于哪个fragment/屏幕坐标上。
![](http://learnopengl.com/img/advanced/anti_aliasing_rasterization.png) ![](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) ![](http://learnopengl.com/img/advanced/anti_aliasing_sample_points.png)
左侧的图显示了我们普通决定一个三角形的覆盖范围的方式。这个像素并不会运行一个像素着色器这就仍保持空白因为它的采样点没有被三角形所覆盖。右边的图展示了多采样的版本每个像素包含4个采样点。这里我们可以看到只有2个采样点被三角形覆盖。 左侧的图显示了我们普通决定一个三角形的覆盖范围的方式。这个像素并不会运行一个片段着色器这就仍保持空白因为它的采样点没有被三角形所覆盖。右边的图展示了多采样的版本每个像素包含4个采样点。这里我们可以看到只有2个采样点被三角形覆盖。
!!! Important !!! Important
采样点的数量是任意的,更多的采样点能带来更精确的覆盖率。 采样点的数量是任意的,更多的采样点能带来更精确的覆盖率。
多采样开始变得有趣了。2个子样本被三角覆盖下一步是决定这个像素的颜色。我们原来猜测我们会为每个被覆盖的子样本运行像素着色器,然后对每个像素的子样本的颜色进行平均化。例子的那种情况,我们在插值的顶点数据的每个子样本上运行像素着色器,然后将这些采样点的最终颜色储存起来。幸好,它不是这么运作的,因为这等于说我们必须运行更多的像素着色器,会明显降低性能。 多采样开始变得有趣了。2个子样本被三角覆盖下一步是决定这个像素的颜色。我们原来猜测我们会为每个被覆盖的子样本运行片段着色器,然后对每个像素的子样本的颜色进行平均化。例子的那种情况,我们在插值的顶点数据的每个子样本上运行片段着色器,然后将这些采样点的最终颜色储存起来。幸好,它不是这么运作的,因为这等于说我们必须运行更多的片段着色器,会明显降低性能。
MSAA的真正工作方式是每个像素只运行一次像素着色器,无论多少子样本被三角形所覆盖。像素着色器运行着插值到像素中心的顶点数据最后颜色被储存近每个被覆盖的子样本中每个像素的所有颜色接着将平均化每个像素最终有了一个唯一颜色。在前面的图片中4个样本中只有2个被覆盖像素的颜色将以三角形的颜色进行平均化颜色同时也被储存到其他2个采样点最后生成的是一种浅蓝色。 MSAA的真正工作方式是每个像素只运行一次片段着色器,无论多少子样本被三角形所覆盖。片段着色器运行着插值到像素中心的顶点数据最后颜色被储存近每个被覆盖的子样本中每个像素的所有颜色接着将平均化每个像素最终有了一个唯一颜色。在前面的图片中4个样本中只有2个被覆盖像素的颜色将以三角形的颜色进行平均化颜色同时也被储存到其他2个采样点最后生成的是一种浅蓝色。
结果是,颜色缓冲中所有基本图形的边都生成了更加平滑的样式。让我们看看当再次决定前面的三角形覆盖范围时多样本看起来是这样的: 结果是,颜色缓冲中所有基本图形的边都生成了更加平滑的样式。让我们看看当再次决定前面的三角形覆盖范围时多样本看起来是这样的:
![](http://learnopengl.com/img/advanced/anti_aliasing_rasterization_samples.png) ![](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)。 你可以[在这里找到源代码](http://learnopengl.com/code_viewer.php?code=advanced/anti_aliasing_framebuffers)。
但是如果我们打算使用一个多采样帧缓冲的纹理结果来做这件事,就像后处理一样会怎样?我们不能在像素着色器中直接使用多采样纹理。我们可以做的事情是把多缓冲位块传送(Blit)到另一个带有非多采样纹理附件的FBO中。之后我们使用这个普通的颜色附件纹理进行后处理通过多采样来对一个图像渲染进行后处理效率很高。这意味着我们必须生成一个新的FBO它仅作为一个将多采样缓冲还原为一个我们可以在像素着色器中使用的普通2D纹理中介。伪代码是这样的 但是如果我们打算使用一个多采样帧缓冲的纹理结果来做这件事,就像后处理一样会怎样?我们不能在片段着色器中直接使用多采样纹理。我们可以做的事情是把多缓冲位块传送(Blit)到另一个带有非多采样纹理附件的FBO中。之后我们使用这个普通的颜色附件纹理进行后处理通过多采样来对一个图像渲染进行后处理效率很高。这意味着我们必须生成一个新的FBO它仅作为一个将多采样缓冲还原为一个我们可以在片段着色器中使用的普通2D纹理中介。伪代码是这样的
```c++ ```c++
GLuint msFBO = CreateFBOWithMultiSampledAttachments(); GLuint msFBO = CreateFBOWithMultiSampledAttachments();
@@ -153,7 +153,7 @@ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, scre
while(!glfwWindowShouldClose(window)) while(!glfwWindowShouldClose(window))
{ {
... ...
glBindFramebuffer(msFBO); glBindFramebuffer(msFBO);
ClearFrameBuffer(); ClearFrameBuffer();
DrawScene(); DrawScene();
@@ -166,8 +166,8 @@ while(!glfwWindowShouldClose(window))
ClearFramebuffer(); ClearFramebuffer();
glBindTexture(GL_TEXTURE_2D, screenTexture); glBindTexture(GL_TEXTURE_2D, screenTexture);
DrawPostProcessingQuad(); DrawPostProcessingQuad();
... ...
} }
``` ```
@@ -178,9 +178,9 @@ while(!glfwWindowShouldClose(window))
你可以[在这里找到所有MSAA版本的后处理源码](http://learnopengl.com/code_viewer.php?code=advanced/anti_aliasing_post_processing)。 你可以[在这里找到所有MSAA版本的后处理源码](http://learnopengl.com/code_viewer.php?code=advanced/anti_aliasing_post_processing)。
!!! Important !!! Important
因为屏幕纹理重新变回了只有一个采样点的普通纹理有些后处理过滤器比如边检测edge-detection将会再次导致锯齿边问题。为了修正此问题之后你应该对纹理进行模糊处理或者创建你自己的反走样算法。 因为屏幕纹理重新变回了只有一个采样点的普通纹理有些后处理过滤器比如边检测edge-detection将会再次导致锯齿边问题。为了修正此问题之后你应该对纹理进行模糊处理或者创建你自己的反走样算法。
当我们希望将多采样和离屏渲染结合起来时我们需要自己负责一些细节。所有细节都是值得付出这些额外努力的因为多采样可以明显提升场景视频输出的质量。要注意开启多采样会明显降低性能样本越多越明显。本文写作时MSAA4样本很常用。 当我们希望将多采样和离屏渲染结合起来时我们需要自己负责一些细节。所有细节都是值得付出这些额外努力的因为多采样可以明显提升场景视频输出的质量。要注意开启多采样会明显降低性能样本越多越明显。本文写作时MSAA4样本很常用。
#### 自定义反走样算法 #### 自定义反走样算法
@@ -199,4 +199,4 @@ uniform sampler2DMS screenTextureMS;
vec4 colorSample = texelFetch(screenTextureMS, TexCoords, 3); // 4th subsample vec4 colorSample = texelFetch(screenTextureMS, TexCoords, 3); // 4th subsample
``` ```
我们不会深究自定义反走样技术的创建细节,但是会给你自己去实现它提供一些提示。 我们不会深究自定义反走样技术的创建细节,但是会给你自己去实现它提供一些提示。