mirror of
https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git
synced 2025-08-23 04:35:28 +08:00
Fix all the titles
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
## 阴影映射(Shadow Mapping)
|
||||
|
||||
本文作者JoeyDeVries,由Django翻译自[http://learnopengl.com](http://learnopengl.com)
|
||||
# 阴影映射
|
||||
|
||||
原文 | [Shadow Mapping](http://learnopengl.com/#!Advanced-Lighting/Shadows/Shadow-Mapping)
|
||||
---|---
|
||||
@@ -20,9 +18,9 @@
|
||||
|
||||
视频游戏中较多使用的一种技术是阴影贴图(shadow mapping),效果不错,而且相对容易实现。阴影贴图并不难以理解,性能也不会太低,而且非常容易扩展成更高级的算法(比如 [Omnidirectional Shadow Maps](http://learnopengl.com/#!Advanced-Lighting/Shadows/Point-Shadows)和 [Cascaded Shadow Maps](http://learnopengl.com/#!Advanced-Lighting/Shadows/CSM))。
|
||||
|
||||
### 阴影映射
|
||||
## 阴影映射
|
||||
|
||||
阴影映射背后的思路非常简单:我们以光的位置为视角进行渲染,我们能看到的东西都将被点亮,看不见的一定是在阴影之中了。假设有一个地板,在光源和它之间有一个大盒子。由于光源处向光线方向看去,可以看到这个盒子,但看不到地板的一部分,这部分就应该在阴影中了。
|
||||
阴影映射(Shadow Mapping)背后的思路非常简单:我们以光的位置为视角进行渲染,我们能看到的东西都将被点亮,看不见的一定是在阴影之中了。假设有一个地板,在光源和它之间有一个大盒子。由于光源处向光线方向看去,可以看到这个盒子,但看不到地板的一部分,这部分就应该在阴影中了。
|
||||
|
||||

|
||||
|
||||
@@ -44,9 +42,9 @@
|
||||
|
||||
深度映射由两个步骤组成:首先,我们渲染深度贴图,然后我们像往常一样渲染场景,使用生成的深度贴图来计算片元是否在阴影之中。听起来有点复杂,但随着我们一步一步地讲解这个技术,就能理解了。
|
||||
|
||||
### 深度贴图(depth map)
|
||||
## 深度贴图
|
||||
|
||||
第一步我们需要生成一张深度贴图。深度贴图是从光的透视图里渲染的深度纹理,用它计算阴影。因为我们需要将场景的渲染结果储存到一个纹理中,我们将再次需要帧缓冲。
|
||||
第一步我们需要生成一张深度贴图(Depth Map)。深度贴图是从光的透视图里渲染的深度纹理,用它计算阴影。因为我们需要将场景的渲染结果储存到一个纹理中,我们将再次需要帧缓冲。
|
||||
|
||||
首先,我们要为渲染的深度贴图创建一个帧缓冲对象:
|
||||
|
||||
@@ -105,9 +103,9 @@ RenderScene();
|
||||
|
||||
这段代码隐去了一些细节,但它表达了阴影映射的基本思路。这里一定要记得调用glViewport。因为阴影贴图经常和我们原来渲染的场景(通常是窗口解析度)有着不同的解析度,我们需要改变视口(viewport)的参数以适应阴影贴图的尺寸。如果我们忘了更新视口参数,最后的深度贴图要么太小要么就不完整。
|
||||
|
||||
### 光源空间的变换(light spacce transform)
|
||||
### 光源空间的变换
|
||||
|
||||
前面那段代码中一个不清楚的函数是COnfigureShaderAndMatrices。它是用来在第二个步骤确保为每个物体设置了合适的投影和视图矩阵,以及相关的模型矩阵。然而,第一个步骤中,我们从光的位置的视野下使用了不同的投影和视图矩阵来渲染的场景。
|
||||
前面那段代码中一个不清楚的函数是`ConfigureShaderAndMatrices`。它是用来在第二个步骤确保为每个物体设置了合适的投影和视图矩阵,以及相关的模型矩阵。然而,第一个步骤中,我们从光的位置的视野下使用了不同的投影和视图矩阵来渲染的场景。
|
||||
|
||||
因为我们使用的是一个所有光线都平行的定向光。出于这个原因,我们将为光源使用正交投影矩阵,透视图将没有任何变形:
|
||||
|
||||
@@ -132,9 +130,9 @@ glm::mat4 lightSpaceMatrix = lightProjection * lightView;
|
||||
|
||||
这个lightSpaceMatrix正是前面我们称为\(T\)的那个变换矩阵。有了lightSpaceMatrix只要给shader提供光空间的投影和视图矩阵,我们就能像往常那样渲染场景了。然而,我们只关心深度值,并非所有片元计算都在我们的着色器中进行。为了提升性能,我们将使用一个与之不同但更为简单的着色器来渲染出深度贴图。
|
||||
|
||||
### 渲染出深度贴图
|
||||
### 渲染至深度贴图
|
||||
|
||||
当我们以光的透视图进行场景渲染的时候,我们会用一个比较简单的着色器,这个着色器除了把顶点变换到光空间以外,不会做得更多了。这个简单的着色器叫做simpleDepthShader,就是使用下面的这个着色器:
|
||||
当我们以光的透视图进行场景渲染的时候,我们会用一个比较简单的着色器,这个着色器除了把顶点变换到光空间以外,不会做得更多了。这个简单的着色器叫做`simpleDepthShader`,就是使用下面的这个着色器:
|
||||
|
||||
```c++
|
||||
#version 330 core
|
||||
@@ -203,7 +201,7 @@ void main()
|
||||
|
||||
你可以在[这里](http://learnopengl.com/code_viewer.php?code=advanced-lighting/shadow_mapping_depth_map)获得把场景渲染成深度贴图的源码。
|
||||
|
||||
### 渲染阴影
|
||||
## 渲染阴影
|
||||
|
||||
正确地生成深度贴图以后我们就可以开始生成阴影了。这段代码在像素着色器中执行,用来检验一个片元是否在阴影之中,不过我们在顶点着色器中进行光空间的变换:
|
||||
|
||||
@@ -359,17 +357,17 @@ float ShadowCalculation(vec4 fragPosLightSpace)
|
||||
|
||||
如果你做对了,你会看到地板和上有立方体的阴影。你可以从这里找到demo程序的[源码](http://learnopengl.com/code_viewer.php?code=advanced-lighting/shadow_mapping_shadows)。
|
||||
|
||||
### 改进阴影贴图
|
||||
## 改进阴影贴图
|
||||
|
||||
我们试图让阴影映射工作,但是你也看到了,阴影映射还是有点不真实,我们修复它才能获得更好的效果,这是下面的部分所关注的焦点。
|
||||
|
||||
#### 阴影失真(shadow acne)
|
||||
### 阴影失真
|
||||
|
||||
前面的图片中明显有不对的地方。放大看会发现明显的线条样式:
|
||||
|
||||

|
||||
|
||||
我们可以看到地板四边形渲染出很大一块交替黑线。这种阴影贴图的不真实感叫做阴影失真,下图解释了成因:
|
||||
我们可以看到地板四边形渲染出很大一块交替黑线。这种阴影贴图的不真实感叫做**阴影失真(Shadow Acne)**,下图解释了成因:
|
||||
|
||||

|
||||
|
||||
@@ -400,13 +398,13 @@ float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
|
||||
|
||||
选用正确的偏移数值,在不同的场景中需要一些像这样的轻微调校,但大多情况下,实际上就是增加偏移量直到所有失真都被移除的问题。
|
||||
|
||||
#### 悬浮
|
||||
### 悬浮
|
||||
|
||||
使用阴影偏移的一个缺点是你对物体的实际深度应用了平移。偏移有可能足够大,以至于可以看出阴影相对实际物体位置的偏移,你可以从下图看到这个现象(这是一个夸张的偏移值):
|
||||
|
||||

|
||||
|
||||
这个阴影失真叫做Peter panning,因为物体看起来轻轻悬浮在表面之上(译注Peter Pan就是童话彼得潘,而panning有平移、悬浮之意,而且彼得潘是个会飞的男孩…)。我们可以使用一个叫技巧解决大部分的Peter panning问题:当渲染深度贴图时候使用正面剔除(front face culling)你也许记得在面剔除教程中OpenGL默认是背面剔除。我们要告诉OpenGL我们要剔除正面。
|
||||
这个阴影失真叫做悬浮(Peter Panning),因为物体看起来轻轻悬浮在表面之上(译注Peter Pan就是童话彼得潘,而panning有平移、悬浮之意,而且彼得潘是个会飞的男孩…)。我们可以使用一个叫技巧解决大部分的Peter panning问题:当渲染深度贴图时候使用正面剔除(front face culling)你也许记得在面剔除教程中OpenGL默认是背面剔除。我们要告诉OpenGL我们要剔除正面。
|
||||
|
||||
因为我们只需要深度贴图的深度值,对于实体物体无论我们用它们的正面还是背面都没问题。使用背面深度不会有错误,因为阴影在物体内部有错误我们也看不见。
|
||||
|
||||
@@ -424,7 +422,7 @@ glCullFace(GL_BACK); // 不要忘记设回原先的culling face
|
||||
|
||||
另一个要考虑到的地方是接近阴影的物体仍然会出现不正确的效果。必须考虑到何时使用正面剔除对物体才有意义。不过使用普通的偏移值通常就能避免peter panning。
|
||||
|
||||
#### 采样超出
|
||||
### 采样过多
|
||||
|
||||
无论你喜不喜欢还有一个视觉差异,就是光的视锥不可见的区域一律被认为是处于阴影中,不管它真的处于阴影之中。出现这个状况是因为超出光的视锥的投影坐标比1.0大,这样采样的深度纹理就会超出他默认的0到1的范围。根据纹理环绕方式,我们将会得到不正确的深度结果,它不是基于真实的来自光源的深度值。
|
||||
|
||||
@@ -468,7 +466,7 @@ float ShadowCalculation(vec4 fragPosLightSpace)
|
||||
|
||||
这些结果意味着,只有在深度贴图范围以内的被投影的fragment坐标才有阴影,所以任何超出范围的都将会没有阴影。由于在游戏中通常这只发生在远处,就会比我们之前的那个明显的黑色区域效果更真实。
|
||||
|
||||
#### PCF
|
||||
## PCF
|
||||
|
||||
阴影现在已经附着到场景中了,不过这仍不是我们想要的。如果你放大看阴影,阴影映射对解析度的依赖很快变得很明显。
|
||||
|
||||
@@ -509,10 +507,9 @@ shadow /= 9.0;
|
||||
实际上PCF还有更多的内容,以及很多技术要点需要考虑以提升柔和阴影的效果,但处于本章内容长度考虑,我们将留在以后讨论。
|
||||
|
||||
|
||||
|
||||
### 正交 vs 投影
|
||||
|
||||
在渲染深度贴图的时候,正交和投影矩阵之间有所不同。正交投影矩阵并不会将场景用透视图进行变形,所有视线/光线都是平行的,这使它对于定向光来说是个很好的投影矩阵。然而透视投影矩阵,会将所有顶点根据透视关系进行变形,结果因此而不同。下图展示了两种投影方式所产生的不同阴影区域:
|
||||
在渲染深度贴图的时候,正交(Orthographic)和投影(Projection)矩阵之间有所不同。正交投影矩阵并不会将场景用透视图进行变形,所有视线/光线都是平行的,这使它对于定向光来说是个很好的投影矩阵。然而透视投影矩阵,会将所有顶点根据透视关系进行变形,结果因此而不同。下图展示了两种投影方式所产生的不同阴影区域:
|
||||
|
||||

|
||||
|
||||
@@ -545,14 +542,9 @@ void main()
|
||||
|
||||
这个深度值与我们见到的用正交投影的很相似。需要注意的是,这个只适用于调试;正交或投影矩阵的深度检查仍然保持原样,因为相关的深度并没有改变。
|
||||
|
||||
### 附加资源
|
||||
## 附加资源
|
||||
|
||||
[Tutorial 16 : Shadow](http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping/)
|
||||
|
||||
[mapping:opengl-tutorial.org](http://ogldev.atspace.co.uk/www/tutorial23/tutorial23.html) 提供的类似的阴影映射教程,里面有一些额外的解释。
|
||||
|
||||
[Shadow Mapping – Part 1:ogldev](http://ogldev.atspace.co.uk/www/tutorial23/tutorial23.html)提供的另一个阴影映射教程。
|
||||
|
||||
[How Shadow Mapping Works](https://www.youtube.com/watch?v=EsccgeUpdsM):的一个第三方YouTube视频教程,里面解释了阴影映射及其实现。
|
||||
|
||||
[Common Techniques to Improve Shadow Depth Maps](https://msdn.microsoft.com/en-us/library/windows/desktop/ee416324%28v=vs.85%29.aspx):微软的一篇好文章,其中理出了很多提升阴影贴图质量的技术。
|
||||
- [Tutorial 16 : Shadow mapping](http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping/):提供的类似的阴影映射教程,里面有一些额外的解释。
|
||||
- [Shadow Mapping – Part 1:ogldev](http://ogldev.atspace.co.uk/www/tutorial23/tutorial23.html):提供的另一个阴影映射教程。
|
||||
- [How Shadow Mapping Works](https://www.youtube.com/watch?v=EsccgeUpdsM):的一个第三方YouTube视频教程,里面解释了阴影映射及其实现。
|
||||
- [Common Techniques to Improve Shadow Depth Maps](https://msdn.microsoft.com/en-us/library/windows/desktop/ee416324%28v=vs.85%29.aspx):微软的一篇好文章,其中理出了很多提升阴影贴图质量的技术。
|
@@ -1,6 +1,10 @@
|
||||
本文作者JoeyDeVries,由Django翻译自[http://learnopengl.com](http://learnopengl.com)
|
||||
# 点光源阴影
|
||||
|
||||
## 点光源阴影(Shadow Mapping)
|
||||
原文 | [Point Shadows](http://learnopengl.com/#!Advanced-Lighting/Shadows/Point-Shadows)
|
||||
---|---
|
||||
作者 | JoeyDeVries
|
||||
翻译 | [Django](http://bullteacher.com/)
|
||||
校对 | 暂无
|
||||
|
||||
上个教程我们学到了如何使用阴影映射技术创建动态阴影。效果不错,但它只适合定向光,因为阴影只是在单一定向光源下生成的。所以它也叫定向阴影映射,深度(阴影)贴图生成自定向光的视角。
|
||||
|
||||
@@ -13,15 +17,15 @@
|
||||
本节代码基于前面的阴影映射教程,所以如果你对传统阴影映射不熟悉,还是建议先读一读阴影映射教程。
|
||||
算法和定向阴影映射差不多:我们从光的透视图生成一个深度贴图,基于当前fragment位置来对深度贴图采样,然后用储存的深度值和每个fragment进行对比,看看它是否在阴影中。定向阴影映射和万向阴影映射的主要不同在于深度贴图的使用上。
|
||||
|
||||
对于深度贴图,我们需要从一个点光源的所有渲染场景,普通2D深度贴图不能工作;如果我们使用cubemap会怎样?因为cubemap可以储存6个面的环境数据,它可以将整个场景渲染到cubemap的每个面上,把它们当作点光源四周的深度值来采样。
|
||||
对于深度贴图,我们需要从一个点光源的所有渲染场景,普通2D深度贴图不能工作;如果我们使用立方体贴图会怎样?因为立方体贴图可以储存6个面的环境数据,它可以将整个场景渲染到立方体贴图的每个面上,把它们当作点光源四周的深度值来采样。
|
||||
|
||||

|
||||
|
||||
生成后的深度cubemap被传递到光照像素着色器,它会用一个方向向量来采样cubemap,从而得到当前的fragment的深度(从光的透视图)。大部分复杂的事情已经在阴影映射教程中讨论过了。算法只是在深度cubemap生成上稍微复杂一点。
|
||||
生成后的深度立方体贴图被传递到光照像素着色器,它会用一个方向向量来采样立方体贴图,从而得到当前的fragment的深度(从光的透视图)。大部分复杂的事情已经在阴影映射教程中讨论过了。算法只是在深度立方体贴图生成上稍微复杂一点。
|
||||
|
||||
#### 生成深度cubemap
|
||||
## 生成深度立方体贴图
|
||||
|
||||
为创建一个光周围的深度值的cubemap,我们必须渲染场景6次:每次一个面。显然渲染场景6次需要6个不同的视图矩阵,每次把一个不同的cubemap面附加到帧缓冲对象上。这看起来是这样的:
|
||||
为创建一个光周围的深度值的立方体贴图,我们必须渲染场景6次:每次一个面。显然渲染场景6次需要6个不同的视图矩阵,每次把一个不同的立方体贴图面附加到帧缓冲对象上。这看起来是这样的:
|
||||
|
||||
```c++
|
||||
for(int i = 0; i < 6; i++)
|
||||
@@ -33,16 +37,16 @@ for(int i = 0; i < 6; i++)
|
||||
}
|
||||
```
|
||||
|
||||
这会很耗费性能因为一个深度贴图下需要进行很多渲染调用。这个教程中我们将转而使用另外的一个小技巧来做这件事,几何着色器允许我们使用一次渲染过程来建立深度cubemap。
|
||||
这会很耗费性能因为一个深度贴图下需要进行很多渲染调用。这个教程中我们将转而使用另外的一个小技巧来做这件事,几何着色器允许我们使用一次渲染过程来建立深度立方体贴图。
|
||||
|
||||
首先,我们需要创建一个cubemap:
|
||||
首先,我们需要创建一个立方体贴图:
|
||||
|
||||
```c++
|
||||
GLuint depthCubemap;
|
||||
glGenTextures(1, &depthCubemap);
|
||||
```
|
||||
|
||||
然后生成cubemap的每个面,将它们作为2D深度值纹理图像:
|
||||
然后生成立方体贴图的每个面,将它们作为2D深度值纹理图像:
|
||||
|
||||
```c++
|
||||
const GLuint SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;
|
||||
@@ -62,7 +66,7 @@ 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);
|
||||
```
|
||||
|
||||
正常情况下,我们把cubemap纹理的一个面附加到帧缓冲对象上,渲染场景6次,每次将帧缓冲的深度缓冲目标改成不同cubemap面。由于我们将使用一个几何着色器,它允许我们把所有面在一个过程渲染,我们可以使用glFramebufferTexture直接把cubemap附加成帧缓冲的深度附件:
|
||||
正常情况下,我们把立方体贴图纹理的一个面附加到帧缓冲对象上,渲染场景6次,每次将帧缓冲的深度缓冲目标改成不同立方体贴图面。由于我们将使用一个几何着色器,它允许我们把所有面在一个过程渲染,我们可以使用glFramebufferTexture直接把立方体贴图附加成帧缓冲的深度附件:
|
||||
|
||||
```c++
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
|
||||
@@ -72,9 +76,9 @@ glReadBuffer(GL_NONE);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
```
|
||||
|
||||
还要记得调用glDrawBuffer和glReadBuffer:当生成一个深度cubemap时我们只关心深度值,所以我们必须显式告诉OpenGL这个帧缓冲对象不会渲染到一个颜色缓冲里。
|
||||
还要记得调用glDrawBuffer和glReadBuffer:当生成一个深度立方体贴图时我们只关心深度值,所以我们必须显式告诉OpenGL这个帧缓冲对象不会渲染到一个颜色缓冲里。
|
||||
|
||||
万向阴影贴图有两个渲染阶段:首先我们生成深度贴图,然后我们正常使用深度贴图渲染,在场景中创建阴影。帧缓冲对象和cubemap的处理看起是这样的:
|
||||
万向阴影贴图有两个渲染阶段:首先我们生成深度贴图,然后我们正常使用深度贴图渲染,在场景中创建阴影。帧缓冲对象和立方体贴图的处理看起是这样的:
|
||||
|
||||
```c++
|
||||
// 1. first render to depth cubemap
|
||||
@@ -92,11 +96,11 @@ glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubemap);
|
||||
RenderScene();
|
||||
```
|
||||
|
||||
这个过程和默认的阴影映射一样,尽管这次我们渲染和使用的是一个cubemap深度纹理,而不是2D深度纹理。在我们实际开始从光的视角的所有方向渲染场景之前,我们先得计算出合适的变换矩阵。
|
||||
这个过程和默认的阴影映射一样,尽管这次我们渲染和使用的是一个立方体贴图深度纹理,而不是2D深度纹理。在我们实际开始从光的视角的所有方向渲染场景之前,我们先得计算出合适的变换矩阵。
|
||||
|
||||
### 光空间的变换
|
||||
|
||||
设置了帧缓冲和cubemap,我们需要一些方法来讲场景的所有几何体变换到6个光的方向中相应的光空间。与阴影映射教程类似,我们将需要一个光空间的变换矩阵T,但是这次是每个面都有一个。
|
||||
设置了帧缓冲和立方体贴图,我们需要一些方法来讲场景的所有几何体变换到6个光的方向中相应的光空间。与阴影映射教程类似,我们将需要一个光空间的变换矩阵T,但是这次是每个面都有一个。
|
||||
|
||||
每个光空间的变换矩阵包含了投影和视图矩阵。对于投影矩阵来说,我们将使用一个透视投影矩阵;光源代表一个空间中的点,所以透视投影矩阵更有意义。每个光空间变换矩阵使用同样的投影矩阵:
|
||||
|
||||
@@ -107,9 +111,9 @@ GLfloat far = 25.0f;
|
||||
glm::mat4 shadowProj = glm::perspective(90.0f, aspect, near, far);
|
||||
```
|
||||
|
||||
非常重要的一点是,这里glm::perspective的视野参数,设置为90度。90度我们才能保证视野足够大到可以合适地填满cubemap的一个面,cubemap的所有面都能与其他面在边缘对齐。
|
||||
非常重要的一点是,这里glm::perspective的视野参数,设置为90度。90度我们才能保证视野足够大到可以合适地填满立方体贴图的一个面,立方体贴图的所有面都能与其他面在边缘对齐。
|
||||
|
||||
因为投影矩阵在每个方向上并不会改变,我们可以在6个变换矩阵中重复使用。我们要为每个方向提供一个不同的视图矩阵。用glm::lookAt创建6个观察方向,每个都按顺序注视着cubemap的的一个方向:右、左、上、下、近、远:
|
||||
因为投影矩阵在每个方向上并不会改变,我们可以在6个变换矩阵中重复使用。我们要为每个方向提供一个不同的视图矩阵。用glm::lookAt创建6个观察方向,每个都按顺序注视着立方体贴图的的一个方向:右、左、上、下、近、远:
|
||||
|
||||
```c++
|
||||
std::vector<glm::mat4> shadowTransforms;
|
||||
@@ -127,15 +131,13 @@ shadowTransforms.push_back(shadowProj *
|
||||
glm::lookAt(lightPos, lightPos + glm::vec3(0.0,0.0,-1.0), glm::vec3(0.0,-1.0,0.0));
|
||||
```
|
||||
|
||||
这里我们创建了6个视图矩阵,把它们乘以投影矩阵,来得到6个不同的光空间变换矩阵。glm::lookAt的target参数是它注视的cubemap的面的一个方向。
|
||||
这里我们创建了6个视图矩阵,把它们乘以投影矩阵,来得到6个不同的光空间变换矩阵。glm::lookAt的target参数是它注视的立方体贴图的面的一个方向。
|
||||
|
||||
这些变换矩阵发送到着色器渲染到cubemap里。
|
||||
|
||||
|
||||
这些变换矩阵发送到着色器渲染到立方体贴图里。
|
||||
|
||||
### 深度着色器
|
||||
|
||||
为了把值渲染到深度cubemap,我们将需要3个着色器:顶点和像素着色器,以及一个它们之间的几何着色器。
|
||||
为了把值渲染到深度立方体贴图,我们将需要3个着色器:顶点和像素着色器,以及一个它们之间的几何着色器。
|
||||
|
||||
几何着色器是负责将所有世界空间的顶点变换到6个不同的光空间的着色器。因此顶点着色器简单地将顶点变换到世界空间,然后直接发送到几何着色器:
|
||||
|
||||
@@ -153,7 +155,7 @@ void main()
|
||||
|
||||
紧接着几何着色器以3个三角形的顶点作为输入,它还有一个光空间变换矩阵的uniform数组。几何着色器接下来会负责将顶点变换到光空间;这里它开始变得有趣了。
|
||||
|
||||
几何着色器有一个内建变量叫做gl_Layer,它指定发散出基本图形送到cubemap的哪个面。当不管它时,几何着色器就会像往常一样把它的基本图形发送到输送管道的下一阶段,但当我们更新这个变量就能控制每个基本图形将渲染到cubemap的哪一个面。当然这只有当我们有了一个附加到激活的帧缓冲的cubemap纹理才有效:
|
||||
几何着色器有一个内建变量叫做gl_Layer,它指定发散出基本图形送到立方体贴图的哪个面。当不管它时,几何着色器就会像往常一样把它的基本图形发送到输送管道的下一阶段,但当我们更新这个变量就能控制每个基本图形将渲染到立方体贴图的哪一个面。当然这只有当我们有了一个附加到激活的帧缓冲的立方体贴图纹理才有效:
|
||||
|
||||
```c++
|
||||
#version 330 core
|
||||
@@ -180,7 +182,7 @@ void main()
|
||||
}
|
||||
```
|
||||
|
||||
几何着色器相对简单。我们输入一个三角形,输出总共6个三角形(6*3顶点,所以总共18个顶点)。在main函数中,我们遍历cubemap的6个面,我们每个面指定为一个输出面,把这个面的interger(整数)存到gl_Layer。然后,我们通过把面的光空间变换矩阵乘以FragPos,将每个世界空间顶点变换到相关的光空间,生成每个三角形。注意,我们还要将最后的FragPos变量发送给像素着色器,我们需要计算一个深度值。
|
||||
几何着色器相对简单。我们输入一个三角形,输出总共6个三角形(6*3顶点,所以总共18个顶点)。在main函数中,我们遍历立方体贴图的6个面,我们每个面指定为一个输出面,把这个面的interger(整数)存到gl_Layer。然后,我们通过把面的光空间变换矩阵乘以FragPos,将每个世界空间顶点变换到相关的光空间,生成每个三角形。注意,我们还要将最后的FragPos变量发送给像素着色器,我们需要计算一个深度值。
|
||||
|
||||
上个教程,我们使用的是一个空的像素着色器,让OpenGL配置深度贴图的深度值。这次我们将计算自己的深度,这个深度就是每个fragment位置和光源位置之间的线性距离。计算自己的深度值使得之后的阴影计算更加直观。
|
||||
|
||||
@@ -206,11 +208,11 @@ void main()
|
||||
|
||||
像素着色器将来自几何着色器的FragPos、光的位置向量和视锥的远平面值作为输入。这里我们把fragment和光源之间的距离,映射到0到1的范围,把它写入为fragment的深度值。
|
||||
|
||||
使用这些着色器渲染场景,cubemap附加的帧缓冲对象激活以后,你会得到一个完全填充的深度cubemap,以便于进行第二阶段的阴影计算。
|
||||
使用这些着色器渲染场景,立方体贴图附加的帧缓冲对象激活以后,你会得到一个完全填充的深度立方体贴图,以便于进行第二阶段的阴影计算。
|
||||
|
||||
### 万向阴影贴图
|
||||
## 万向阴影贴图
|
||||
|
||||
所有事情都做好了,是时候来渲染万向阴影了。这个过程和定向阴影映射教程相似,尽管这次我们绑定的深度贴图是一个cubemap,而不是2D纹理,并且将光的投影的远平面发送给了着色器。
|
||||
所有事情都做好了,是时候来渲染万向阴影(Omnidirectional Shadow)了。这个过程和定向阴影映射教程相似,尽管这次我们绑定的深度贴图是一个立方体贴图,而不是2D纹理,并且将光的投影的远平面发送给了着色器。
|
||||
|
||||
```c++
|
||||
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
|
||||
@@ -309,9 +311,9 @@ void main()
|
||||
|
||||
有一些细微的不同:光照代码一样,但我们现在有了一个uniform变量samplerCube,shadowCalculation函数用fragment的位置作为它的参数,取代了光空间的fragment位置。我们现在还要引入光的视锥的远平面值,后面我们会需要它。像素着色器的最后,我们计算出阴影元素,当fragment在阴影中时它是1.0,不在阴影中时是0.0。我们使用计算出来的阴影元素去影响光照的diffuse和specular元素。
|
||||
|
||||
在ShadowCalculation函数中有很多不同之处,现在是从cubemap中进行采样,不再使用2D纹理了。我们来一步一步的讨论一下的它的内容。
|
||||
在ShadowCalculation函数中有很多不同之处,现在是从立方体贴图中进行采样,不再使用2D纹理了。我们来一步一步的讨论一下的它的内容。
|
||||
|
||||
我们需要做的第一件事是获取cubemap的森都。你可能已经从教程的cubemap部分想到,我们已经将深度储存为fragment和光位置之间的距离了;我们这里采用相似的处理方式:
|
||||
我们需要做的第一件事是获取立方体贴图的森都。你可能已经从教程的立方体贴图部分想到,我们已经将深度储存为fragment和光位置之间的距离了;我们这里采用相似的处理方式:
|
||||
|
||||
```c++
|
||||
float ShadowCalculation(vec3 fragPos)
|
||||
@@ -321,7 +323,7 @@ float ShadowCalculation(vec3 fragPos)
|
||||
}
|
||||
```
|
||||
|
||||
在这里,我们得到了fragment的位置与光的位置之间的不同的向量,使用这个向量作为一个方向向量去对cubemap进行采样。方向向量不需要是单位向量,所以无需对它进行标准化。最后的closestDepth是光源和它最接近的可见fragment之间的标准化的深度值。
|
||||
在这里,我们得到了fragment的位置与光的位置之间的不同的向量,使用这个向量作为一个方向向量去对立方体贴图进行采样。方向向量不需要是单位向量,所以无需对它进行标准化。最后的closestDepth是光源和它最接近的可见fragment之间的标准化的深度值。
|
||||
|
||||
closestDepth值现在在0到1的范围内了,所以我们先将其转换会0到far_plane的范围,这需要把他乘以far_plane:
|
||||
|
||||
@@ -329,7 +331,7 @@ closestDepth值现在在0到1的范围内了,所以我们先将其转换会0
|
||||
closestDepth *= far_plane;
|
||||
```
|
||||
|
||||
下一步我们获取当前fragment和光源之间的深度值,我们可以简单的使用fragToLight的长度来获取它,这取决于我们如何计算cubemap中的深度值:
|
||||
下一步我们获取当前fragment和光源之间的深度值,我们可以简单的使用fragToLight的长度来获取它,这取决于我们如何计算立方体贴图中的深度值:
|
||||
|
||||
```c++
|
||||
float currentDepth = length(fragToLight);
|
||||
@@ -371,7 +373,7 @@ float ShadowCalculation(vec3 fragPos)
|
||||
|
||||
你可以从这里找到这个[demo的源码](http://www.learnopengl.com/code_viewer.php?code=advanced-lighting/point_shadows)、[顶点](http://www.learnopengl.com/code_viewer.php?code=advanced-lighting/point_shadows&type=vertex)和[片段](http://www.learnopengl.com/code_viewer.php?code=advanced-lighting/point_shadows&type=fragment)着色器。
|
||||
|
||||
#### 把cubemap深度缓冲显示出来
|
||||
### 显示立方体贴图深度缓冲
|
||||
|
||||
如果你想我一样第一次并没有做对,那么就要进行调试排错,将深度贴图显示出来以检查其是否正确。因为我们不再用2D深度贴图纹理,深度贴图的显示不会那么显而易见。
|
||||
|
||||
@@ -385,9 +387,9 @@ FragColor = vec4(vec3(closestDepth / far_plane), 1.0);
|
||||
|
||||

|
||||
|
||||
你可能也注意到了带阴影部分在墙外。如果看起来和这个差不多,你就知道深度cubemap生成的没错。否则你可能做错了什么,也许是closestDepth仍然还在0到far_plane的范围。
|
||||
你可能也注意到了带阴影部分在墙外。如果看起来和这个差不多,你就知道深度立方体贴图生成的没错。否则你可能做错了什么,也许是closestDepth仍然还在0到far_plane的范围。
|
||||
|
||||
#### PCF
|
||||
## PCF
|
||||
|
||||
由于万向阴影贴图基于传统阴影映射的原则,它便也继承了由解析度产生的非真实感。如果你放大就会看到锯齿边了。PCF或称Percentage-closer filtering允许我们通过对fragment位置周围过滤多个样本,并对结果平均化。
|
||||
|
||||
@@ -435,7 +437,7 @@ vec3 sampleOffsetDirections[20] = vec3[]
|
||||
);
|
||||
```
|
||||
|
||||
然后我们把PCF算法与从sampleOffsetDirections得到的样本数量进行适配,使用它们从cubemap里采样。这么做的好处是与之前的PCF算法相比,我们需要的样本数量变少了。
|
||||
然后我们把PCF算法与从sampleOffsetDirections得到的样本数量进行适配,使用它们从立方体贴图里采样。这么做的好处是与之前的PCF算法相比,我们需要的样本数量变少了。
|
||||
|
||||
```c++
|
||||
float shadow = 0.0;
|
||||
@@ -453,7 +455,7 @@ for(int i = 0; i < samples; ++i)
|
||||
shadow /= float(samples);
|
||||
```
|
||||
|
||||
这里我们把一个偏移量添加到指定的diskRadius中,它在fragToLight方向向量周围从cubemap里采样。
|
||||
这里我们把一个偏移量添加到指定的diskRadius中,它在fragToLight方向向量周围从立方体贴图里采样。
|
||||
|
||||
另一个在这里可以应用的有意思的技巧是,我们可以基于观察者里一个fragment的距离来改变diskRadius;这样我们就能根据观察者的距离来增加偏移半径了,当距离更远的时候阴影更柔和,更近了就更锐利。
|
||||
|
||||
@@ -470,12 +472,8 @@ PCF算法的结果如果没有变得更好,也是非常不错的,这是柔
|
||||
|
||||
我还要提醒一下使用几何着色器来生成深度贴图不会一定比每个面渲染场景6次更快。使用几何着色器有它自己的性能局限,在第一个阶段使用它可能获得更好的性能表现。这取决于环境的类型,以及特定的显卡驱动等等,所以如果你很关心性能,就要确保对两种方法有大致了解,然后选择对你场景来说更高效的那个。我个人还是喜欢使用几何着色器来进行阴影映射,原因很简单,因为它们使用起来更简单。
|
||||
|
||||
|
||||
## 附加资源
|
||||
|
||||
### 附加资源
|
||||
|
||||
[Shadow Mapping for point light sources in OpenGL](http://www.sunandblackcat.com/tipFullView.php?l=eng&topicid=36):sunandblackcat的万向阴影映射教程。
|
||||
|
||||
[Multipass Shadow Mapping With Point Lights](http://ogldev.atspace.co.uk/www/tutorial43/tutorial43.html):ogldev的万向阴影映射教程。
|
||||
|
||||
[Omni-directional Shadows](http://www.cg.tuwien.ac.at/~husky/RTR/OmnidirShadows-whyCaps.pdf):Peter Houska的关于万向阴影映射的一组很好的ppt。
|
||||
- [Shadow Mapping for point light sources in OpenGL](http://www.sunandblackcat.com/tipFullView.php?l=eng&topicid=36):sunandblackcat的万向阴影映射教程。
|
||||
- [Multipass Shadow Mapping With Point Lights](http://ogldev.atspace.co.uk/www/tutorial43/tutorial43.html):ogldev的万向阴影映射教程。
|
||||
- [Omni-directional Shadows](http://www.cg.tuwien.ac.at/~husky/RTR/OmnidirShadows-whyCaps.pdf):Peter Houska的关于万向阴影映射的一组很好的ppt。
|
@@ -1,6 +1,6 @@
|
||||
# CSM
|
||||
|
||||
# 未完成
|
||||
**未完成**
|
||||
|
||||
这篇教程暂时还没有完成,您可以经常来刷新看看是否有更新的进展。
|
||||
|
||||
|
Reference in New Issue
Block a user