mirror of
https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git
synced 2025-08-23 04:35:28 +08:00
@@ -88,9 +88,9 @@ glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
// 1. first render to depth cubemap
|
||||
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
ConfigureShaderAndMatrices();
|
||||
RenderScene();
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
ConfigureShaderAndMatrices();
|
||||
RenderScene();
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
// 2. then render scene as normal with shadow mapping (using depth cubemap)
|
||||
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
|
||||
@@ -329,7 +329,7 @@ float ShadowCalculation(vec3 fragPos)
|
||||
|
||||
在这里,我们得到了fragment的位置与光的位置之间的不同的向量,使用这个向量作为一个方向向量去对立方体贴图进行采样。方向向量不需要是单位向量,所以无需对它进行标准化。最后的closestDepth是光源和它最接近的可见fragment之间的标准化的深度值。
|
||||
|
||||
closestDepth值现在在0到1的范围内了,所以我们先将其转换会0到far_plane的范围,这需要把他乘以far_plane:
|
||||
closestDepth值现在在0到1的范围内了,所以我们先将其转换回0到far_plane的范围,这需要把他乘以far_plane:
|
||||
|
||||
```c++
|
||||
closestDepth *= far_plane;
|
||||
@@ -428,7 +428,7 @@ shadow /= (samples * samples * samples);
|
||||
|
||||
然而,samples设置为4.0,每个fragment我们会得到总共64个样本,这太多了!
|
||||
|
||||
大多数这些样本都是多余的,它们在原始方向向量近处采样,不如在采样方向向量的垂直方向进行采样更有意义。可是,没有(简单的)方式能够指出哪一个子方向是多余的,这就难了。有个技巧可以使用,用一个偏移量方向数组,它们差不多都是分开的,每一个指向完全不同的方向,剔除彼此接近的那些子方向。下面就是一个有着20个偏移方向的数组:
|
||||
大多数这些采样都是多余的,与其在原始方向向量附近处采样,不如在采样方向向量的垂直方向进行采样更有意义。可是,没有(简单的)方式能够指出哪一个子方向是多余的,这就难了。有个技巧可以使用,用一个偏移量方向数组,它们差不多都是分开的,每一个指向完全不同的方向,剔除彼此接近的那些子方向。下面就是一个有着20个偏移方向的数组:
|
||||
|
||||
```c++
|
||||
vec3 sampleOffsetDirections[20] = vec3[]
|
||||
|
@@ -84,7 +84,7 @@ void main()
|
||||
|
||||
## 切线空间
|
||||
|
||||
法线贴图中的法线向量在切线空间中,法线永远指着正z方向。切线空间是位于三角形表面之上的空间:法线相对于单个三角形的本地参考框架。它就像法线贴图向量的本地空间;它们都被定义为指向正z方向,无论最终变换到什么方向。使用一个特定的矩阵我们就能将本地/切线空寂中的法线向量转成世界或视图坐标,使它们转向到最终的贴图表面的方向。
|
||||
法线贴图中的法线向量定义在切线空间中,在切线空间中,法线永远指着正z方向。切线空间是位于三角形表面之上的空间:法线相对于单个三角形的本地参考框架。它就像法线贴图向量的本地空间;它们都被定义为指向正z方向,无论最终变换到什么方向。使用一个特定的矩阵我们就能将本地/切线空间中的法线向量转成世界或视图空间下,使它们转向到最终的贴图表面的方向。
|
||||
|
||||
我们可以说,上个部分那个朝向正y的法线贴图错误的贴到了表面上。法线贴图被定义在切线空间中,所以一种解决问题的方式是计算出一种矩阵,把法线从切线空间变换到一个不同的空间,这样它们就能和表面法线方向对齐了:法线向量都会指向正y方向。切线空间的一大好处是我们可以为任何类型的表面计算出一个这样的矩阵,由此我们可以把切线空间的z方向和表面的法线方向对齐。
|
||||
|
||||
@@ -239,7 +239,7 @@ void main()
|
||||
2. 我们也可以使用TBN矩阵的逆矩阵,这个矩阵可以把世界坐标空间的向量转换到切线坐标空间。因此我们使用这个矩阵左乘其他光照变量,把他们转换到切线空间,这样法线和其他光照变量再一次在一个坐标系中了。
|
||||
|
||||
|
||||
**我们来看看第一种情况。**我们从法线贴图重采样得来的法线向量,是以切线空间表达的,尽管其他光照向量是以世界空间表达的。把TBN传给像素着色器,我们就能将采样得来的切线空间的法线乘以这个TBN矩阵,将法线向量变换到和其他光照向量一样的参考空间中。这种方式随后所有光照计算都可以简单的理解。
|
||||
**我们来看看第一种情况。**我们从法线贴图采样得来的法线向量,是在切线空间表示的,尽管其他光照向量都是在世界空间表示的。把TBN传给像素着色器,我们就能将采样得来的切线空间的法线乘以这个TBN矩阵,将法线向量变换到和其他光照向量一样的参考空间中。这种方式随后所有光照计算都可以简单的理解。
|
||||
|
||||
把TBN矩阵发给像素着色器很简单:
|
||||
|
||||
|
@@ -135,7 +135,7 @@ void main()
|
||||
}
|
||||
```
|
||||
|
||||
我们定义了一个叫做ParallaxMapping的函数,它把fragment的纹理坐标作和切线空间中的fragment到观察者的方向向量为输入。这个函数返回经位移的纹理坐标。然后我们使用这些经位移的纹理坐标进行diffuse和法线贴图的采样。最后fragment的diffuse颜色和法线向量就正确的对应于表面的经位移的位置上了。
|
||||
我们定义了一个叫做ParallaxMapping的函数,它把fragment的纹理坐标和切线空间中的fragment到观察者的方向向量为输入。这个函数返回经位移的纹理坐标。然后我们使用这些经位移的纹理坐标进行diffuse和法线贴图的采样。最后fragment的diffuse颜色和法线向量就正确的对应于表面的经位移的位置上了。
|
||||
|
||||
我们来看看ParallaxMapping函数的内部:
|
||||
|
||||
@@ -148,11 +148,11 @@ vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
|
||||
}
|
||||
```
|
||||
|
||||
这个相对简单的函数是我们所讨论过的内容的直接表述。我们用本来的纹理坐标texCoords从高度贴图中来采样出当前fragment高度\(\color{green}{H(A)}\)。然后计算出\(\color{brown}{\bar{P}}\),x和y元素在切线空间中,viewDir向量除以它的z元素,用fragment的高度对它进行缩放。我们同时引入额一个height_scale的uniform,来进行一些额外的控制,因为视差效果如果没有一个缩放参数通常会过于强烈。然后我们用\(\color{brown}{\bar{P}}\)减去纹理坐标来获得最终的经过位移纹理坐标。
|
||||
这个相对简单的函数是我们所讨论过的内容的直接表述。我们用本来的纹理坐标texCoords从高度贴图中来采样,得到当前fragment的高度\(\color{green}{H(A)}\)。然后计算出\(\color{brown}{\bar{P}}\),x和y元素在切线空间中,viewDir向量除以它的z元素,用fragment的高度对它进行缩放。我们同时引入额一个height_scale的uniform,来进行一些额外的控制,因为视差效果如果没有一个缩放参数通常会过于强烈。然后我们用\(\color{brown}{\bar{P}}\)减去纹理坐标来获得最终的经过位移纹理坐标。
|
||||
|
||||
有一个地方需要注意,就是viewDir.xy除以viewDir.z那里。因为viewDir向量是经过了标准化的,viewDir.z会在0.0到1.0之间的某处。当viewDir大致平行于表面时,它的z元素接近于0.0,除法会返回比viewDir垂直于表面的时候更大的\(\color{brown}{\bar{P}}\)向量。所以基本上我们增加了\(\color{brown}{\bar{P}}\)的大小,当以一个角度朝向一个表面相比朝向顶部时它对纹理坐标会进行更大程度的缩放;这回在角上获得更大的真实度。
|
||||
有一个地方需要注意,就是viewDir.xy除以viewDir.z那里。因为viewDir向量是经过了标准化的,viewDir.z会在0.0到1.0之间的某处。当viewDir大致平行于表面时,它的z元素接近于0.0,除法会返回比viewDir垂直于表面的时候更大的\(\color{brown}{\bar{P}}\)向量。所以,从本质上,相比正朝向表面,当带有角度地看向平面时,我们会更大程度地缩放\(\color{brown}{\bar{P}}\)的大小,从而增加纹理坐标的偏移;这样做在视角上会获得更大的真实度。
|
||||
|
||||
有些人更喜欢在等式中不使用viewDir.z,因为普通的视差贴图会在角上产生不想要的结果;这个技术叫做有偏移量限制的视差贴图(Parallax Mapping with Offset Limiting)。选择哪一个技术是个人偏好问题,但我倾向于普通的视差贴图。
|
||||
有些人更喜欢不在等式中使用viewDir.z,因为普通的视差贴图会在角度上产生不尽如人意的结果;这个技术叫做有偏移量限制的视差贴图(Parallax Mapping with Offset Limiting)。选择哪一个技术是个人偏好问题,但我倾向于普通的视差贴图。
|
||||
|
||||
最后的纹理坐标随后被用来进行采样(diffuse和法线)贴图,下图所展示的位移效果中height_scale等于1:
|
||||
|
||||
@@ -182,7 +182,9 @@ if(texCoords.x > 1.0 || texCoords.y > 1.0 || texCoords.x < 0.0 || texCoords.y <
|
||||
|
||||
## 陡峭视差映射
|
||||
|
||||
陡峭视差映射(Steep Parallax Mapping)是视差映射的扩展,原则是一样的,但不是使用一个样本而是多个样本来确定向量\(\color{brown}{\bar{P}}\)到\(\color{blue}B\)。它能得到更好的结果,它将总深度范围分布到同一个深度/高度的多个层中。从每个层中我们沿着\(\color{brown}{\bar{P}}\)方向移动采样纹理坐标,直到我们找到了一个采样得到的低于当前层的深度值的深度值。看看下面的图片:
|
||||
陡峭视差映射(Steep Parallax Mapping)是视差映射的扩展,原则是一样的,但不是使用一个样本而是多个样本来确定向量\(\color{brown}{\bar{P}}\)到\(\color{blue}B\)。即使在陡峭的高度变化的情况下,它也能得到更好的结果,原因在于该技术通过增加采样的数量提高了精确性。
|
||||
|
||||
陡峭视差映射的基本思想是将总深度范围划分为同一个深度/高度的多个层。从每个层中我们沿着\(\color{brown}{\bar{P}}\)方向移动采样纹理坐标,直到我们找到一个采样低于当前层的深度值。看看下面的图片:
|
||||
|
||||

|
||||
|
||||
|
@@ -98,7 +98,7 @@ void main()
|
||||
|
||||
色调映射(Tone Mapping)是一个损失很小的转换浮点颜色值至我们所需的LDR[0.0, 1.0]范围内的过程,通常会伴有特定的风格的色平衡(Stylistic Color Balance)。
|
||||
|
||||
最简单的色调映射算法是Reinhard色调映射,它涉及到分散整个HDR颜色值到LDR颜色值上,所有的值都有对应。Reinhard色调映射算法平均得将所有亮度值分散到LDR上。我们将Reinhard色调映射应用到之前的片段着色器上,并且为了更好的测量加上一个Gamma校正过滤(包括SRGB纹理的使用):
|
||||
最简单的色调映射算法是Reinhard色调映射,它涉及到分散整个HDR颜色值到LDR颜色值上,所有的值都有对应。Reinhard色调映射算法平均地将所有亮度值分散到LDR上。我们将Reinhard色调映射应用到之前的片段着色器上,并且为了更好的测量加上一个Gamma校正过滤(包括SRGB纹理的使用):
|
||||
|
||||
```c++
|
||||
void main()
|
||||
@@ -119,7 +119,7 @@ void main()
|
||||
|
||||

|
||||
|
||||
现在你可以看到在隧道的尽头木头纹理变得可见了。用了这个非常简单地色调映射算法,我们可以合适的看到存在浮点帧缓冲中整个范围的HDR值,给我们对于无损场景光照精确的控制。
|
||||
现在你可以看到在隧道的尽头木头纹理变得可见了。用了这个非常简单地色调映射算法,我们可以合适的看到存在浮点帧缓冲中整个范围的HDR值,使我们能在不丢失细节的前提下,对场景光照有精确的控制。
|
||||
|
||||
另一个有趣的色调映射应用是曝光(Exposure)参数的使用。你可能还记得之前我们在介绍里讲到的,HDR图片包含在不同曝光等级的细节。如果我们有一个场景要展现日夜交替,我们当然会在白天使用低曝光,在夜间使用高曝光,就像人眼调节方式一样。有了这个曝光参数,我们可以去设置可以同时在白天和夜晚不同光照条件工作的光照参数,我们只需要调整曝光参数就行了。
|
||||
|
||||
@@ -152,9 +152,9 @@ void main()
|
||||
|
||||
### HDR拓展
|
||||
|
||||
在这里展示的两个色调映射算法仅仅是大量(更先进)的色调映射算法中的一小部分,这些算法各有长短.一些色调映射算法倾向于特定的某种颜色/强度,也有一些算法同时显示低于高曝光颜色从而能够显示更加多彩和精细的图像。也有一些技巧被称作自动曝光调整(Automatic Exposure Adjustment)或者叫人眼适应(Eye Adaptation)技术,它能够检测前一帧场景的亮度并且缓慢调整曝光参数模仿人眼使得场景在黑暗区域逐渐变亮或者在明亮区域逐渐变暗,
|
||||
在这里展示的两个色调映射算法仅仅是大量(更先进)的色调映射算法中的一小部分,这些算法各有长短。一些色调映射算法倾向于特定的某种颜色/强度,也有一些算法同时显示低高曝光颜色从而能够显示更加多彩和精细的图像。也有一些技巧被称作自动曝光调整(Automatic Exposure Adjustment)或者叫人眼适应(Eye Adaptation)技术,它能够检测前一帧场景的亮度并且缓慢调整曝光参数模仿人眼使得场景在黑暗区域逐渐变亮或者在明亮区域逐渐变暗。
|
||||
|
||||
HDR渲染的真正优点在庞大和复杂的场景中应用复杂光照算法会被显示出来,但是出于教学目的创建这样复杂的演示场景是很困难的,这个教程用的场景是很小的,而且缺乏细节。但是如此简单的演示也是能够显示出HDR渲染的一些优点:在明亮和黑暗区域无细节损失,因为它们可以由色调映射重新获取;多个光照的叠加不会导致亮度被约束的区域;光照可以被设定为他们原来的亮度而不是被LDR值限定。而且,HDR渲染也使一些有趣的效果更加可行和真实; 其中一个效果叫做泛光(Bloom),我们将在下一节讨论他。
|
||||
HDR渲染的真正优点在庞大和复杂的场景中应用复杂光照算法会被显示出来,但是出于教学目的创建这样复杂的演示场景是很困难的,这个教程用的场景是很小的,而且缺乏细节。但是如此简单的演示也是能够显示出HDR渲染的一些优点:在明亮和黑暗区域无细节损失,因为它们可以通过色调映射重新获得;多个光照的叠加不会导致亮度被截断的区域的出现,光照可以被设定为它们原来的亮度值而不是被LDR值限制。而且,HDR渲染也使一些有趣的效果更加可行和真实; 其中一个效果叫做泛光(Bloom),我们将在下一节讨论。
|
||||
|
||||
## 附加资源
|
||||
|
||||
|
@@ -10,7 +10,7 @@
|
||||
|
||||
本节暂未进行完全的重写,错误可能会很多。如果可能的话,请对照原文进行阅读。如果有报告本节的错误,将会延迟至重写之后进行处理。
|
||||
|
||||
明亮的光源和区域经常很难向观察者表达出来,因为监视器的亮度范围是有限的。一种区分明亮光源的方式是使它们在监视器上发出光芒,光源的的光芒向四周发散。这样观察者就会产生光源或亮区的确是强光区。(译注:这个问题的提出简单来说是为了解决这样的问题:例如有一张在阳光下的白纸,白纸在监视器上显示出是出白色,而前方的太阳也是纯白色的,所以基本上白纸和太阳就是一样的了,给太阳加一个光晕,这样太阳看起来似乎就比白纸更亮了)
|
||||
明亮的光源和区域经常很难向观察者表达出来,因为监视器的亮度范围是有限的。一种区分明亮光源的方式是使它们在监视器上发出光芒,光源的光芒向四周发散。这样观察者就会产生光源或亮区的确是强光区。(译注:这个问题的提出简单来说是为了解决这样的问题:例如有一张在阳光下的白纸,白纸在监视器上显示出是出白色,而前方的太阳也是纯白色的,所以基本上白纸和太阳就是一样的了,给太阳加一个光晕,这样太阳看起来似乎就比白纸更亮了)
|
||||
|
||||
光晕效果可以使用一个后处理特效泛光来实现。泛光使所有明亮区域产生光晕效果。下面是一个使用了和没有使用光晕的对比(图片生成自虚幻引擎):
|
||||
|
||||
@@ -30,15 +30,15 @@ Bloom和HDR结合使用效果很好。常见的一个误解是HDR和泛光是一
|
||||
|
||||

|
||||
|
||||
我们将这个超过一定亮度阈限的纹理进行模糊。泛光效果的强度很大程度上被模糊过滤器的范围和强度所决定。
|
||||
我们将这个超过一定亮度阈限的纹理进行模糊。泛光效果的强度很大程度上是由被模糊过滤器的范围和强度所决定。
|
||||
|
||||

|
||||
|
||||
最终的被模糊化的纹理就是我们用来获得发出光晕效果的东西。这个已模糊的纹理要添加到原来的HDR场景纹理的上部。因为模糊过滤器的应用明亮区域发出光晕,所以明亮区域在长和宽上都有所扩展。
|
||||
最终的被模糊化的纹理就是我们用来获得发出光晕效果的东西。这个已模糊的纹理要添加到原来的HDR场景纹理之上。因为模糊过滤器的应用明亮区域发出光晕,所以明亮区域在长和宽上都有所扩展。
|
||||
|
||||

|
||||
|
||||
泛光本身并不是个复杂的技术,但很难获得正确的效果。它的品质很大程度上取决于所用的模糊过滤器的质量和类型。简单的改改模糊过滤器就会极大的改变泛光效果的品质。
|
||||
泛光本身并不是个复杂的技术,但很难获得正确的效果。它的品质很大程度上取决于所用的模糊过滤器的质量和类型。简单地改改模糊过滤器就会极大的改变泛光效果的品质。
|
||||
|
||||
下面这几步就是泛光后处理特效的过程,它总结了实现泛光所需的步骤。
|
||||
|
||||
@@ -48,7 +48,7 @@ Bloom和HDR结合使用效果很好。常见的一个误解是HDR和泛光是一
|
||||
|
||||
## 提取亮色
|
||||
|
||||
第一步我们要从渲染出来的场景中提取两张图片。我们可以渲染场景两次,每次使用一个不同的不同的着色器渲染到不同的帧缓冲中,但我们可以使用一个叫做MRT(Multiple Render Targets多渲染目标)的小技巧,这样我们就能定义多个像素着色器了;有了它我们还能够在一个单独渲染处理中提取头两个图片。在像素着色器的输出前,我们指定一个布局location标识符,这样我们便可控制一个像素着色器写入到哪个颜色缓冲:
|
||||
第一步我们要从渲染出来的场景中提取两张图片。我们可以渲染场景两次,每次使用一个不同的着色器渲染到不同的帧缓冲中,但我们可以使用一个叫做MRT(Multiple Render Targets,多渲染目标)的小技巧,这样我们就能指定多个像素着色器输出;有了它我们还能够在一个单独渲染处理中提取头两个图片。在像素着色器的输出前,我们指定一个布局location标识符,这样我们便可控制一个像素着色器写入到哪个颜色缓冲:
|
||||
|
||||
```c++
|
||||
layout (location = 0) out vec4 FragColor;
|
||||
@@ -88,7 +88,7 @@ GLuint attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
|
||||
glDrawBuffers(2, attachments);
|
||||
```
|
||||
|
||||
当渲染到这个帧缓冲中的时候,一个着色器使用一个布局location修饰符,那么fragment就会用相应的颜色缓冲就会被用来渲染。这很棒,因为这样省去了我们为提取明亮区域的额外渲染步骤,因为我们现在可以直接从将被渲染的fragment提取出它们:
|
||||
当渲染到这个帧缓冲的时候,一个着色器使用一个布局location修饰符,fragment就会写入对应的颜色缓冲。这很棒,因为这样省去了我们为提取明亮区域的额外渲染步骤,因为我们现在可以直接从将被渲染的fragment提取出它们:
|
||||
|
||||
```c++
|
||||
#version 330 core
|
||||
|
Reference in New Issue
Block a user