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:
@@ -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);
|
||||||
}
|
}
|
||||||
|
@@ -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去创建它,但是其他库可能没默认创建模板库,所以一定要查看你使用的库的文档。
|
||||||
|
|
||||||
下面是一个模板缓冲的简单例子:
|
下面是一个模板缓冲的简单例子:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
模板缓冲先清空模板缓冲中设置的零,然后开启矩形一。场景中的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)
|
||||||
|
|
||||||
和深度测试一样,我们也有几个不同控制权,决定何时模板测试通过或失败以及它怎样影响模板缓冲。一共有两种方程可供我们使用去配置模板测试:glStencilFunc和glStencilOp。
|
和深度测试一样,我们也有几个不同控制权,决定何时模板测试通过或失败以及它怎样影响模板缓冲。一共有两种函数可供我们使用去配置模板测试:`glStencilFunc`和`glStencilOp`。
|
||||||
|
|
||||||
glStencilFunc(GLenum func, GLint ref, GLuint mask)有三个参数:
|
`void glStencilFunc(GLenum func, GLint ref, GLuint mask)`函数有三个参数:
|
||||||
|
|
||||||
* func:设置模板测试方程。这个测试方程应用到已经储存的模板值和glStencilFunc的ref值上,可用的选项是:GL_NEVER、GL_LEQUAL、GL_GREATER、GL_GEQUAL、GL_EQUAL、GL_NOTEQUAL、GL_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)引用值1,fragment就能通过测试被绘制了,否则就会被丢弃。
|
它会告诉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) ,所以任何测试的任何结果,模板缓冲都会保留它的值。默认行为不会更新模板缓冲,所以如果你想写入模板缓冲的话,你必须像任意选项指定至少一个不同的动作。
|
||||||
|
|
||||||
使用glStencilFunc和glStencilOp,我们就可以指定在什么时候以及我们打算怎么样去更新模板缓冲了,我们也可以指定何时让测试通过或不通过。什么时候fragment会被抛弃。
|
使用`glStencilFunc`和`glStencilOp`,我们就可以指定在什么时候以及我们打算怎么样去更新模板缓冲了,我们也可以指定何时让测试通过或不通过。什么时候片段会被抛弃。
|
||||||
|
|
||||||
###物体轮廓
|
## 物体轮廓
|
||||||
|
|
||||||
看了前面的部分你未必能理解模板测试是如何工作的,所以我们会展示一个模板测试可以实现的一个特别的和有用的功能,叫做物体轮廓(object outlining)。
|
看了前面的部分你未必能理解模板测试是如何工作的,所以我们会展示一个用模板测试实现的一个特别的和有用的功能,叫做物体轮廓(object outlining)。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
物体轮廓就像它的名字所描述的那样,它能够给每个(或一个)物体创建一个有颜色的边。在策略游戏中当你打算选择一个单位的时候它特别有用。给物体加上轮廓的步骤如下:
|
物体轮廓就像它的名字所描述的那样,它能够给每个(或一个)物体创建一个有颜色的边。在策略游戏中当你打算选择一个单位的时候它特别有用。给物体加上轮廓的步骤如下:
|
||||||
|
|
||||||
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://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工具箱中给我们提供了另一种好用工具。
|
||||||
|
@@ -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);
|
|||||||
运行程序你将看到:
|
运行程序你将看到:
|
||||||

|

|
||||||
|
|
||||||
出现这种情况是因为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。现在我们来看看效果:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -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设置,但是源和目标因子可以让我们自由设置。我们来看一个简单的例子:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
@@ -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%以上的了。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
这的确是个好主意,但是有个问题需要我们去解决:我们如何知道某个面在观察者的视野中不会出现呢?如果我们去想象任何封闭的形状,它每个面有两面。一面面向用户,另一面背对用户。假如我们只渲染面向观察者的面会怎样?
|
这的确是个好主意,但是有个问题需要我们去解决:我们如何知道某个面在观察者的视野中不会出现呢?如果我们去想象任何封闭的形状,它每个面有两面。一面面向用户,另一面背对用户。假如我们只渲染面向观察者的面会怎样?
|
||||||
|
|
||||||
这正是面剔除所做的(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)。
|
||||||
|
@@ -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.
|
||||||
|
@@ -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
|
||||||
|
@@ -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。
|
||||||
|
@@ -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()
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
我们现在可以计算出两个完全不同的像素着色器结果,每个显示在窗口的一端。这对于测试不同的光照技术很有好处。
|
我们现在可以计算出两个完全不同的片段着色器结果,每个显示在窗口的一端。这对于测试不同的光照技术很有好处。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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或less,OpenGL会假定你只写入比当前的像素深度值的深度值大或小的。
|
如果把深度条件定义为greater或less,OpenGL会假定你只写入比当前的像素深度值的深度值大或小的。
|
||||||
|
|
||||||
下面是一个在像素着色器里增加深度值的例子,不过仍可开启前置深度测试:
|
下面是一个在片段着色器里增加深度值的例子,不过仍可开启前置深度测试:
|
||||||
|
|
||||||
```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 layout(uniform块布局)。
|
现在你可能会奇怪layout(std140)是什么意思。它的意思是说当前定义的uniform block为它的内容使用特定的内存布局;这个声明实际上是设置uniform block layout(uniform块布局)。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### uniform block layout(uniform块布局)
|
#### uniform block layout(uniform块布局)
|
||||||
|
|
||||||
@@ -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);
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
通过改变模型矩阵,每个立方体都移动到窗口的一边,由于像素着色器不同,物体的颜色也不同。这是一个相对简单的场景,我们可以使用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缓冲对象。
|
||||||
|
@@ -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,结果是为我们绘制的每个点生成一个绿房子:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -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)。
|
||||||
|
@@ -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++)
|
|||||||
|
|
||||||
有些机器渲染十万可能会有点吃力,所以尝试修改这个数量知道你能获得可以接受的帧率。
|
有些机器渲染十万可能会有点吃力,所以尝试修改这个数量知道你能获得可以接受的帧率。
|
||||||
|
|
||||||
就像你所看到的,在合适的条件下,实例渲染对于你的显卡来说和普通渲染有很大不同。处于这个理由,实例渲染通常用来渲染草、草丛、粒子以及像这样的场景,基本上来讲只要场景中有很多重复物体,使用实例渲染都会获得好处。
|
就像你所看到的,在合适的条件下,实例渲染对于你的显卡来说和普通渲染有很大不同。处于这个理由,实例渲染通常用来渲染草、草丛、粒子以及像这样的场景,基本上来讲只要场景中有很多重复物体,使用实例渲染都会获得好处。
|
||||||
|
@@ -20,11 +20,11 @@
|
|||||||
|
|
||||||
为了理解什么是多采样,以及它是如何解决走样的问题的,我们先要更深入了解一个OpenGL像素器的工作方式。
|
为了理解什么是多采样,以及它是如何解决走样的问题的,我们先要更深入了解一个OpenGL像素器的工作方式。
|
||||||
|
|
||||||
像素器是是你的最终的经处理的顶点和像素着色器之间的所有算法和处理的集合。像素器将属于一个基本图形的所有顶点转化为一系列fragment。顶点坐标理论上可以含有任何坐标,但fragment却不是这样,这是因为它们与你的窗口的解析度有关。几乎永远都不会有顶点坐标和fragment的一对一映射,所以像素器必须以某种方式决定每个特定顶点最终结束于哪个fragment/屏幕坐标上。
|
像素器是是你的最终的经处理的顶点和片段着色器之间的所有算法和处理的集合。像素器将属于一个基本图形的所有顶点转化为一系列fragment。顶点坐标理论上可以含有任何坐标,但fragment却不是这样,这是因为它们与你的窗口的解析度有关。几乎永远都不会有顶点坐标和fragment的一对一映射,所以像素器必须以某种方式决定每个特定顶点最终结束于哪个fragment/屏幕坐标上。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
这里我们看到一个屏幕像素网格,每个像素中心包含一个采样点(sample point),它被用来决定一个像素是否被三角形所覆盖。红色的采样点如果被三角形覆盖,那么就会为这个被覆盖像(屏幕)素生成一个fragment。即使三角形覆盖了部分屏幕像素,但是采样点没被覆盖,这个像素仍然不会任何像素着色器影响到。
|
这里我们看到一个屏幕像素网格,每个像素中心包含一个采样点(sample point),它被用来决定一个像素是否被三角形所覆盖。红色的采样点如果被三角形覆盖,那么就会为这个被覆盖像(屏幕)素生成一个fragment。即使三角形覆盖了部分屏幕像素,但是采样点没被覆盖,这个像素仍然不会任何片段着色器影响到。
|
||||||
|
|
||||||
你可能已经明白走样的原因来自何处了。三角形渲染后的版本最后在你的屏幕上是这样的:
|
你可能已经明白走样的原因来自何处了。三角形渲染后的版本最后在你的屏幕上是这样的:
|
||||||
|
|
||||||
@@ -36,21 +36,21 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
左侧的图显示了我们普通决定一个三角形的覆盖范围的方式。这个像素并不会运行一个像素着色器(这就仍保持空白),因为它的采样点没有被三角形所覆盖。右边的图展示了多采样的版本,每个像素包含4个采样点。这里我们可以看到只有2个采样点被三角形覆盖。
|
左侧的图显示了我们普通决定一个三角形的覆盖范围的方式。这个像素并不会运行一个片段着色器(这就仍保持空白),因为它的采样点没有被三角形所覆盖。右边的图展示了多采样的版本,每个像素包含4个采样点。这里我们可以看到只有2个采样点被三角形覆盖。
|
||||||
|
|
||||||
!!! Important
|
!!! Important
|
||||||
|
|
||||||
采样点的数量是任意的,更多的采样点能带来更精确的覆盖率。
|
采样点的数量是任意的,更多的采样点能带来更精确的覆盖率。
|
||||||
|
|
||||||
多采样开始变得有趣了。2个子样本被三角覆盖,下一步是决定这个像素的颜色。我们原来猜测,我们会为每个被覆盖的子样本运行像素着色器,然后对每个像素的子样本的颜色进行平均化。例子的那种情况,我们在插值的顶点数据的每个子样本上运行像素着色器,然后将这些采样点的最终颜色储存起来。幸好,它不是这么运作的,因为这等于说我们必须运行更多的像素着色器,会明显降低性能。
|
多采样开始变得有趣了。2个子样本被三角覆盖,下一步是决定这个像素的颜色。我们原来猜测,我们会为每个被覆盖的子样本运行片段着色器,然后对每个像素的子样本的颜色进行平均化。例子的那种情况,我们在插值的顶点数据的每个子样本上运行片段着色器,然后将这些采样点的最终颜色储存起来。幸好,它不是这么运作的,因为这等于说我们必须运行更多的片段着色器,会明显降低性能。
|
||||||
|
|
||||||
MSAA的真正工作方式是,每个像素只运行一次像素着色器,无论多少子样本被三角形所覆盖。像素着色器运行着插值到像素中心的顶点数据,最后颜色被储存近每个被覆盖的子样本中,每个像素的所有颜色接着将平均化,每个像素最终有了一个唯一颜色。在前面的图片中4个样本中只有2个被覆盖,像素的颜色将以三角形的颜色进行平均化,颜色同时也被储存到其他2个采样点,最后生成的是一种浅蓝色。
|
MSAA的真正工作方式是,每个像素只运行一次片段着色器,无论多少子样本被三角形所覆盖。片段着色器运行着插值到像素中心的顶点数据,最后颜色被储存近每个被覆盖的子样本中,每个像素的所有颜色接着将平均化,每个像素最终有了一个唯一颜色。在前面的图片中4个样本中只有2个被覆盖,像素的颜色将以三角形的颜色进行平均化,颜色同时也被储存到其他2个采样点,最后生成的是一种浅蓝色。
|
||||||
|
|
||||||
结果是,颜色缓冲中所有基本图形的边都生成了更加平滑的样式。让我们看看当再次决定前面的三角形覆盖范围时多样本看起来是这样的:
|
结果是,颜色缓冲中所有基本图形的边都生成了更加平滑的样式。让我们看看当再次决定前面的三角形覆盖范围时多样本看起来是这样的:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
这里每个像素包含着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
|
||||||
```
|
```
|
||||||
|
|
||||||
我们不会深究自定义反走样技术的创建细节,但是会给你自己去实现它提供一些提示。
|
我们不会深究自定义反走样技术的创建细节,但是会给你自己去实现它提供一些提示。
|
||||||
|
Reference in New Issue
Block a user