mirror of
https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git
synced 2025-08-23 04:35:28 +08:00
校对光照部分完成
This commit is contained in:
@@ -1,29 +1,31 @@
|
||||
本文作者JoeyDeVries,由[Django](http://bullteacher.com/16-light-casters.html)翻译自[http://learnopengl.com](http://www.learnopengl.com/#!Lighting/Light-casters)
|
||||
# 投光物
|
||||
|
||||
#投光物
|
||||
原文 | [Light casters](http://www.learnopengl.com/#!Lighting/Light-casters)
|
||||
---|---
|
||||
作者 | JoeyDeVries
|
||||
翻译 | [Django](http://bullteacher.com/)
|
||||
校对 | [Geequlim](http://geequlim.com)
|
||||
|
||||
我们目前使用的所有光照都来自于一个单独的光源,这是空间中的一个点。它的效果不错,但是在真实世界,我们有多种类型的光,它们每个表现都不同。一个光源把光投射到物体上,叫做投光。这个教程里我们讨论几种不同的投光类型。学习模拟不同的光源是你未来丰富你的场景的另一个工具。
|
||||
|
||||
我们首先讨论定向光(directional light),接着是作为之前学到知识的扩展的点光(point light),最后我们讨论聚光灯(spot light)。下面的教程我们会把这几种不同的光类型整合到一个场景中。
|
||||
我们首先讨论定向光(directional light),接着是作为之前学到知识的扩展的点光(point light),最后我们讨论聚光灯(Spotlight)。下面的教程我们会把这几种不同的光类型整合到一个场景中。
|
||||
|
||||
## 定向光(Directional Light)
|
||||
|
||||
当一个光源很远的时候,来自光源的每条光线接近于平行。这看起来就像所有的光线来自于同一个方向,无论物体和观察者在哪儿。当一个光源被设置为无限远时,它被称为定向光(也被成为平行光),因为所有的光线都有着同一个方向;它会独立于光源的位置。
|
||||
|
||||
## 定向光
|
||||
|
||||
当一个光源很远的时候,来自光源的每条光线接近于平行。这看起来就像所有的光线来自于同一个方向,无论物体和观察者在哪儿。当一个光源被设置为无限远时,它被称为定向光,因为所有的光线都有着同一个方向;它会独立于光源的位置。
|
||||
|
||||
我们知道的定向光源的一个好例子是,太阳。太阳和我们不是无限远,但它太远了,在计算光照的时候,我们感觉它就像无限远。在下面的图片里,来自于太阳的所有的光线都被定义为平行光:
|
||||
我们知道的定向光源的一个好例子是,太阳。太阳和我们不是无限远,但它也足够远了,在计算光照的时候,我们感觉它就像无限远。在下面的图片里,来自于太阳的所有的光线都被定义为平行光:
|
||||
|
||||

|
||||
|
||||
因为所有的光线都是平行的,对于场景中的每个物体光的方向都保持一致,物体和光源的位置保持怎样的关系都无所谓。由于光的方向向量保持一致,光照计算会和场景中的其他物体相似。
|
||||
|
||||
我们可以通过定义一个光的方向向量,来模拟这样一个定向光,而不是使用光的位置向量。着色器计算保持大致相同的要求,这次我们直接使用光的方向向量来代替用lightDir向量和position向量的计算:
|
||||
我们可以通过定义一个光的方向向量,来模拟这样一个定向光,而不是使用光的位置向量。着色器计算保持大致相同的要求,这次我们直接使用光的方向向量来代替用`lightDir`向量和`position`向量的计算:
|
||||
|
||||
```c++
|
||||
struct Light
|
||||
{
|
||||
// vec3 position; // No longer necessery when using
|
||||
directional lights.
|
||||
// vec3 position; // 现在不在需要光源位置了,因为它是无限远的
|
||||
vec3 direction;
|
||||
vec3 ambient;
|
||||
vec3 diffuse;
|
||||
@@ -36,11 +38,13 @@ void main()
|
||||
...
|
||||
}
|
||||
```
|
||||
注意,我们首先忽略light.direction向量。目前我们使用的光照计算需要光的方向作为一个来自片段朝向的光源的方向,但是人们通常更习惯定义一个定向光作为一个全局方向,它从光源发出。所以我们必须忽略全局光的方向向量来改变它的方向;它现在是一个方向向量指向光源。同时,确保对向量进行归一处理,因为假定输入的向量就是一个单位向量是不明智的。
|
||||
|
||||
作为结果的lightDir向量被使用在diffuse和specular计算之前。
|
||||
注意,我们首先忽略light.direction向量。目前我们使用的光照计算需要光的方向作为一个来自片段朝向的光源的方向,但是人们通常更习惯定义一个定向光作为一个全局方向,它从光源发出。所以我们必须忽略全局光的方向向量来改变它的方向;它现在是一个方向向量指向光源。同时,确保对向量进行标准化处理,因为假定输入的向量就是一个单位向量是不明智的。
|
||||
|
||||
作为结果的`lightDir`向量被使用在`diffuse`和`specular`计算之前。
|
||||
|
||||
为了清晰地强调一个定向光对所有物体都有同样的影响,我们再次访问[坐标系教程](http://learnopengl-cn.readthedocs.org/zh/latest/01%20Getting%20started/08%20Coordinate%20Systems/)结尾部分的箱子场景。例子里我们先定义10个不同的箱子位置,为每个箱子生成不同的模型矩阵,每个模型矩阵包含相应的本地到世界变换:
|
||||
|
||||
为了清晰地强调一个定向光对所有物体都有同样的影响,我们再次访问坐标系教程结尾部分的箱子场景。例子里我们先定义10个不同的箱子位置,为每个箱子生成不同的模型矩阵,每个模型矩阵包含相应的本地到世界变换:
|
||||
```c++
|
||||
for(GLuint i = 0; i < 10; i++)
|
||||
{
|
||||
@@ -52,45 +56,51 @@ for(GLuint i = 0; i < 10; i++)
|
||||
glDrawArrays(GL_TRIANGLES, 0, 36);
|
||||
}
|
||||
```
|
||||
|
||||
同时,不要忘记定义光源的方向(注意,我们把方向定义为:从光源处发出的方向;在下面,你可以快速看到光的方向的指向):
|
||||
|
||||
```c++
|
||||
GLint lightDirPos = glGetUniformLocation(lightingShader.Program, "light.direction");
|
||||
glUniform3f(lightDirPos, -0.2f, -1.0f, -0.3f);
|
||||
```
|
||||
我们已经把光的位置和方向向量传递为vec3,但是有些人去想更喜欢把所有的向量设置为vec4.当定义位置向量为vec4的时候,把w元素设置为1.0非常重要,这样平移和投影才会合理的被应用。然而,当定义一个方向向量为vec4时,我们并不想让平移发挥作用(因为它们除了代表方向,其他什么也不是)所以我们把w元素设置为0.0。
|
||||
|
||||
方向向量被表示为:vec4(0.2f, 1.0f, 0.3f, 0.0f)。这可以作为简单检查光的类型的方法:你可以检查w元素是否等于1.0,查看我们现在所拥有的光的位置向量,w是否等于0.0,我们有一个光的方向向量,所以根据那个调整计算方法:
|
||||
```c++
|
||||
if(lightVector.w == 0.0) // Note: be careful for floating point errors
|
||||
// Do directional light calculations
|
||||
!!! Important
|
||||
|
||||
我们已经把光的位置和方向向量传递为vec3,但是有些人去想更喜欢把所有的向量设置为vec4.当定义位置向量为vec4的时候,把w元素设置为1.0非常重要,这样平移和投影才会合理的被应用。然而,当定义一个方向向量为vec4时,我们并不想让平移发挥作用(因为它们除了代表方向,其他什么也不是)所以我们把w元素设置为0.0。
|
||||
|
||||
else if(lightVector.w == 1.0)
|
||||
// Do light calculations using the light’s position (like last tutorial)
|
||||
```
|
||||
有趣的事实:这就是旧OpenGL(固定函数式)决定一个光源是一个定向光还是位置光源,更具这个修改它的光照。
|
||||
如果你现在编译应用,飞跃场景,它看起来像有一个太阳一样的光源,把光抛到物体身上。你可以看到diffuse和specular元素都反射了,就像天空上有一个光源吗?看起来就像这样:
|
||||
方向向量被表示为:vec4(0.2f, 1.0f, 0.3f, 0.0f)。这可以作为简单检查光的类型的方法:你可以检查w元素是否等于1.0,查看我们现在所拥有的光的位置向量,w是否等于0.0,我们有一个光的方向向量,所以根据那个调整计算方法:
|
||||
|
||||
```c++
|
||||
if(lightVector.w == 0.0) // 请留意浮点数错误
|
||||
// 执行定向光照计算
|
||||
|
||||
else if(lightVector.w == 1.0)
|
||||
// 像上一个教程一样执行顶点光照计算
|
||||
```
|
||||
|
||||
有趣的事实:这就是旧OpenGL(固定函数式)决定一个光源是一个定向光还是位置光源,更具这个修改它的光照。
|
||||
|
||||
如果你现在编译应用,飞跃场景,它看起来像有一个太阳一样的光源,把光抛到物体身上。你可以看到`diffuse`和`specular`元素都对该光源进行反射了,就像天空上有一个光源吗?看起来就像这样:
|
||||
|
||||

|
||||
|
||||
你可以在这里获得应用的所有代码,这里是顶点和片段着色器代码。
|
||||
你可以在这里获得[应用的所有代码](http://learnopengl.com/code_viewer.php?code=lighting/light_casters_directional),这里是[顶点](http://learnopengl.com/code_viewer.php?code=lighting/lighting_maps&type=vertex)和[片段](http://learnopengl.com/code_viewer.php?code=lighting/light_casters_directional&type=fragment)着色器代码。
|
||||
|
||||
|
||||
|
||||
##16.2 点光
|
||||
## 定点光(Point Light)
|
||||
|
||||
定向光作为全局光可以照亮整个场景,这非常棒,但是另一方面除了定向光,我们通常也需要几个点光,在场景里发亮。点光是一个在时间里有位置的光源,它向所有方向发光,光线随距离增加逐渐变暗。想象灯泡和火炬作为投光物,它们可以扮演点光的角色。
|
||||
定向光作为全局光可以照亮整个场景,这非常棒,但是另一方面除了定向光,我们通常也需要几个定点光,在场景里发亮。点光是一个在时间里有位置的光源,它向所有方向发光,光线随距离增加逐渐变暗。想象灯泡和火炬作为投光物,它们可以扮演点光的角色。
|
||||
|
||||

|
||||
|
||||
之前的教程我们已经使用了(最简单的)点光。我们有一个有位置的光源,它从自身的位置向所有方向发出光线。然而,这个我们定义的光源所模拟光线的从不会衰减,这使得它看起来光源亮度极强。在大多数3D模拟中,我们喜欢模拟一个之恩那个照亮一个周围确定区域光源,它不会照亮整个场景。
|
||||
之前的教程我们已经使用了(最简单的)点光。我们有一个有位置的光源,它从自身的位置向所有方向发出光线。然而,这个我们定义的光源所模拟光线的从不会衰减,这使得它看起来光源亮度极强。在大多数3D模拟中,我们喜欢模拟一个能照亮一个周围确定区域光源,但它不会照亮整个场景。
|
||||
|
||||
如果你把10个箱子添加到之前教程的光照场景中,你会注意到黑暗中的每个箱子都会有同样的亮度,就像箱子在光照的前面;没有公式定义光的距离衰减。我们想让黑暗中与光源比较近的箱子被轻微地照亮。
|
||||
|
||||
### 衰减(Attenuation)
|
||||
|
||||
|
||||
##16.3 衰减
|
||||
|
||||
随着光线穿越更远的距离相应地减少亮度,通常被称为衰减。一种随着距离减少亮度的方式是使用线性等式。这样的一个随着距离减少亮度的线性方程,可以使远处的物体更暗。然而,这样的线性方程效果会有点假。在真实世界,通常光在近处时非常亮,但是一个光源的亮度,开始的时候减少的非常快,之后随着距离的增加,减少的速度会慢下来。我们需要一种不同的方程来减少光的亮度。
|
||||
随着光线穿越更远的距离相应地减少亮度,通常被称为衰减(Attenuation)。一种随着距离减少亮度的方式是使用线性等式。这样的一个随着距离减少亮度的线性方程,可以使远处的物体更暗。然而,这样的线性方程效果会有点假。在真实世界,通常光在近处时非常亮,但是一个光源的亮度,开始的时候减少的非常快,之后随着距离的增加,减少的速度会慢下来。我们需要一种不同的方程来减少光的亮度。
|
||||
|
||||
幸运的是一些聪明人已经早就把它想到了。下面的方程把一个片段的光的亮度除以一个已经计算出来的衰减值,这个值根据光源的远近得到:
|
||||
|
||||
@@ -109,7 +119,7 @@ else if(lightVector.w == 1.0)
|
||||
|
||||
|
||||
|
||||
###16.3.1 选择正确的值
|
||||
#### 选择正确的值
|
||||
|
||||
但是,我们把这三个项设置为什么值呢?正确的值的设置由很多因素决定:环境、你希望光所覆盖的距离范围、光的类型等。大多数场合,这是经验的问题,也要适度调整。下面的表格展示一些各项的值,它们模拟现实(某种类型的)光源,覆盖特定的半径(距离)。第一蓝定义一个光的距离,它覆盖所给定的项。这些值是大多数光的良好开始,它是来自Ogre3D的维基的礼物:
|
||||
|
||||
@@ -130,9 +140,10 @@ Distance|Constant|Linear|Quadratic
|
||||
|
||||
就像你所看到的,常数项一直都是1.0。一次项为了覆盖更远的距离通常很小,二次项就更小了。尝试用这些值进行实验,看看它们在你的实现中各自的效果。我们的环境中,32到100的距离对大多数光通常就足够了。
|
||||
|
||||
###16.3.2 实现衰减
|
||||
#### 实现衰减
|
||||
|
||||
为了实现衰减,在着色器中我们会需要三个额外数值:也就是公式的常量、一次项和二次项。最好把它们储存在之前定义的Light结构体中。要注意的是我们计算`lightDir`,就是在前面的教程中我们所做的,不是像之前的定向光的那部分。
|
||||
|
||||
为了实现衰减,在着色器中我们会需要三个额外数值:也就是公式的常量、一次项和二次项。最好把它们储存在之前定义的Light结构体中。要注意的是我们计算lightDir,就是在前面的教程中我们所做的,不是像之前的定向光的那部分。
|
||||
```c++
|
||||
struct Light
|
||||
{
|
||||
@@ -145,60 +156,68 @@ struct Light
|
||||
float quadratic;
|
||||
};
|
||||
```
|
||||
|
||||
然后,我们在OpenGL中设置这些项:我们希望光覆盖50的距离,所以我们会使用上面的表格中合适的常数项、一次项和二次项:
|
||||
|
||||
```c++
|
||||
glUniform1f(glGetUniformLocation(lightingShader.Program, "light.constant"), 1.0f);
|
||||
glUniform1f(glGetUniformLocation(lightingShader.Program, "light.linear"), 0.09);
|
||||
glUniform1f(glGetUniformLocation(lightingShader.Program, "light.quadratic"), 0.032);
|
||||
|
||||
```
|
||||
在片段着色器中实现衰减很直接:我们根据公式简单的计算衰减值,在乘以ambient、diffuse和specular元素。
|
||||
|
||||
我们需要光源的距离提供给公式;记得我们能够计算向量的长度吗?我们可以通过获取片段和光源之间的不同向量把向量的长度结果作为距离项。我们可以使用GLSL的内建length函数做这件事:
|
||||
在片段着色器中实现衰减很直接:我们根据公式简单的计算衰减值,在乘以`ambient`、`diffuse`和`specular`元素。
|
||||
|
||||
我们需要光源的距离提供给公式;记得我们能够计算向量的长度吗?我们可以通过获取片段和光源之间的不同向量把向量的长度结果作为距离项。我们可以使用GLSL的内建`length`函数做这件事:
|
||||
|
||||
```c++
|
||||
float distance = length(light.position - Position);
|
||||
float attenuation = 1.0f / (light.constant + light.linear*distance +light.quadratic*(distance*distance));
|
||||
```
|
||||
然后,我们在光照计算中,通过把衰减值乘以ambient、diffuse和specular颜色,包含这个衰减值。
|
||||
|
||||
我们可以可以把ambient元素留着不变,这样amient光照就不会随着距离减少,但是如果我们使用多余1个的光源,所有的ambient元素会开始叠加,因此这种情况,我们希望ambient光照也衰减。简单的调试出对于你的环境来说最好的效果。
|
||||
然后,我们在光照计算中,通过把衰减值乘以`ambient`、`diffuse`和`specular`颜色,包含这个衰减值。
|
||||
|
||||
!!! Important
|
||||
|
||||
我们可以可以把`ambient`元素留着不变,这样`amient`光照就不会随着距离减少,但是如果我们使用多余1个的光源,所有的`ambient`元素会开始叠加,因此这种情况,我们希望`ambient`光照也衰减。简单的调试出对于你的环境来说最好的效果。
|
||||
|
||||
```c++
|
||||
ambient *= attenuation;
|
||||
diffuse *= attenuation;
|
||||
specular *= attenuation;
|
||||
```
|
||||
|
||||
如果你运行应用后获得这样的效果:
|
||||
|
||||
light_casters_point_light
|
||||

|
||||
|
||||
你可以看到现在只有最近处的箱子的前面被照得最亮。后面的箱子一点都没被照亮,因为它们距离光源太远了。你可以在这里找到应用源码和片段的代码。
|
||||
你可以看到现在只有最近处的箱子的前面被照得最亮。后面的箱子一点都没被照亮,因为它们距离光源太远了。你可以在这里找到[应用源码](http://learnopengl.com/code_viewer.php?code=lighting/light_casters_point)和[片段着色器](http://learnopengl.com/code_viewer.php?code=lighting/light_casters_point&type=fragment)的代码。
|
||||
|
||||
电光就是一个可配的置位置和衰减值应用到光照计算中。还有另一种类型光可用于我们照明库当中。
|
||||
定点光就是一个可配的置位置和衰减值应用到光照计算中。还有另一种类型光可用于我们照明库当中。
|
||||
|
||||
|
||||
## 聚光灯(Spotlight)
|
||||
|
||||
##16.4 聚光灯
|
||||
|
||||
我们要讨论的最后一种类型光是聚光灯。聚光灯是一种位于环境中某处的光源,它不是向所有方向照射,而是只朝某个方向照射。结果是只有一个聚光灯照射方向的确定半径内的物体才会被照亮,其他的都保持黑暗。聚光灯的好例子是路灯或手电筒。
|
||||
我们要讨论的最后一种类型光是聚光灯(Spotlight)。聚光灯是一种位于环境中某处的光源,它不是向所有方向照射,而是只朝某个方向照射。结果是只有一个聚光灯照射方向的确定半径内的物体才会被照亮,其他的都保持黑暗。聚光灯的好例子是路灯或手电筒。
|
||||
|
||||
OpenGL中的聚光灯用世界空间位置,一个方向和一个指定了聚光灯半径的切光角来表示。我们计算的每个片段,如果片段在聚光灯的切光方向之间(就是在圆锥体内),我们就会把片段照亮。下面的图可以让你明白聚光灯是如何工作的:
|
||||
|
||||

|
||||
|
||||
* LightDir:从片段指向光源的向量。
|
||||
* SpotDir:聚光灯所指向的方向。
|
||||
* Phiφ:定义聚光灯半径的切光角。每个落在这个角度之外的,聚光灯都不会照亮。
|
||||
* Thetaθ:LightDir向量和SpotDir向量之间的角度。θ值应该比φ值小,这样才会在聚光灯内。
|
||||
* `LightDir`:从片段指向光源的向量。
|
||||
* `SpotDir`:聚光灯所指向的方向。
|
||||
* `Phiφ`:定义聚光灯半径的切光角。每个落在这个角度之外的,聚光灯都不会照亮。
|
||||
* `Thetaθ`:`LightDir`向量和`SpotDir向`量之间的角度。θ值应该比φ值小,这样才会在聚光灯内。
|
||||
|
||||
所以我们大致要做的是,计算LightDir向量和SpotDir向量的点乘(返回两个单位向量的点乘,还记得吗?),然后在和遮光角φ对比。现在你应该明白聚光灯是我们下面将创建的手电筒的范例。
|
||||
所以我们大致要做的是,计算`LightDir`向量和`SpotDir`向量的点乘(返回两个单位向量的点乘,还记得吗?),然后在和遮光角φ对比。现在你应该明白聚光灯是我们下面将创建的手电筒的范例。
|
||||
|
||||
|
||||
|
||||
##16.5 手电筒
|
||||
### 手电筒
|
||||
|
||||
手电筒是一个坐落在观察者位置的聚光灯,通常瞄准玩家透视图的前面。基本上说,一个手电筒是一个普通的聚光灯,但是根据玩家的位置和方向持续的更新它的位置和方向。
|
||||
|
||||
所以我们需要为片段着色器提供的值,是聚光灯的位置向量(来计算光的方向坐标),聚光灯的方向向量和遮光角。我们可以把这些值储存在Light结构体中:
|
||||
所以我们需要为片段着色器提供的值,是聚光灯的位置向量(来计算光的方向坐标),聚光灯的方向向量和遮光角。我们可以把这些值储存在`Light`结构体中:
|
||||
|
||||
```c++
|
||||
struct Light
|
||||
{
|
||||
@@ -208,42 +227,48 @@ struct Light
|
||||
...
|
||||
};
|
||||
```
|
||||
|
||||
下面我们把这些适当的值传给着色器:
|
||||
|
||||
```c++
|
||||
glUniform3f(lightPosLoc, camera.Position.x, camera.Position.y, camera.Position.z);
|
||||
glUniform3f(lightSpotdirLoc, camera.Front.x, camera.Front.y, camera.Front.z);
|
||||
glUniform1f(lightSpotCutOffLoc, glm::cos(glm::radians(12.5f)));
|
||||
```
|
||||
你可以看到,我们为遮光角设置一个角度,但是我们根据一个角度计算了余弦值,把这个余弦结果传给了片段着色器。这么做的原因是在片段着色器中,我们计算LightDir和SpotDir向量的点乘,而点乘返回一个余弦值,不是一个角度,所以我们不能直接把一个角度和余弦值对比。为了获得这个角度,我们必须计算点乘结果的反余弦,这个操作开销是很大的。所以为了节约一些性能,我们先计算给定切光角的余弦值,然后把结果传递给片段着色器。由于每个角度都被表示为余弦了,我们可以直接对比它们,而不用进行任何开销高昂的操作。
|
||||
|
||||
你可以看到,我们为遮光角设置一个角度,但是我们根据一个角度计算了余弦值,把这个余弦结果传给了片段着色器。这么做的原因是在片段着色器中,我们计算`LightDir`和`SpotDir`向量的点乘,而点乘返回一个余弦值,不是一个角度,所以我们不能直接把一个角度和余弦值对比。为了获得这个角度,我们必须计算点乘结果的反余弦,这个操作开销是很大的。所以为了节约一些性能,我们先计算给定切光角的余弦值,然后把结果传递给片段着色器。由于每个角度都被表示为余弦了,我们可以直接对比它们,而不用进行任何开销高昂的操作。
|
||||
|
||||
现在剩下要做的是计算θ值,用它和φ值对比,以决定我们是否在或不在聚光灯的内部:
|
||||
|
||||
```c++
|
||||
float theta = dot(lightDir, normalize(-light.direction));
|
||||
if(theta > light.cutOff)
|
||||
{
|
||||
// Do lighting calculations
|
||||
// 执行光照计算
|
||||
}
|
||||
else // else, use ambient light so scene isn’t completely dark outside the spotlight.
|
||||
else // 否则使用环境光,使得场景不至于完全黑暗
|
||||
color = vec4(light.ambient*vec3(texture(material.diffuse,TexCoords)), 1.0f);
|
||||
```
|
||||
我们首先计算lightDir和负方向向量的点乘(它负的是因为我们想要向量指向光源,而不是从光源作为指向出发点。译注:前面的specular教程中作者却用了相反的表示方法,这里读者可以选择喜欢的表达方式)。确保对所有相关向量进行了归一处理。
|
||||
|
||||
你可能奇怪为什么if条件中使用>符号而不是<符号。为了在聚光灯以内,θ不是应该比光的遮光值更小吗?这没错,但是不要忘了,角度值是以余弦值来表示的,一个0度的角表示为1.0的余弦值,当一个角是90度的时候被表示为0.0的余弦值,你可以在这里看到:
|
||||
我们首先计算`lightDir`和负方向向量的点乘(它负的是因为我们想要向量指向光源,而不是从光源作为指向出发点。译注:前面的`specular`教程中作者却用了相反的表示方法,这里读者可以选择喜欢的表达方式)。确保对所有相关向量进行了标准化处理。
|
||||
|
||||

|
||||
!!! Important
|
||||
|
||||
你可能奇怪为什么if条件中使用>符号而不是<符号。为了在聚光灯以内,θ不是应该比光的遮光值更小吗?这没错,但是不要忘了,角度值是以余弦值来表示的,一个0度的角表示为1.0的余弦值,当一个角是90度的时候被表示为0.0的余弦值,你可以在这里看到:
|
||||
|
||||

|
||||
|
||||
现在你可以看到,余弦越是接近1.0,角度就越小。这就解释了为什么θ需要比切光值更大了。切光值当前被设置为12.5的余弦,它等于0.9978,所以θ的余弦值在0.9979和1.0之间,片段会在聚光灯内,被照亮。
|
||||
|
||||
现在你可以看到,余弦越是接近1.0,角度就越小。这就解释了为什么θ需要比切光值更大了。切光值当前被设置为12.5的余弦,它等于0.9978,所以θ的余弦值在0.9979和1.0之间,片段会在聚光灯内,被照亮。
|
||||
运行应用,在聚光灯内的片段才会被照亮。这看起来像这样:
|
||||
|
||||
`[](http://www.learnopengl.com/img/lighting/light_casters_spotlight_hard.png)
|
||||

|
||||
|
||||
你可以在这里获得全部源码和片段着色器的源码。
|
||||
你可以在这里获得[全部源码](http://learnopengl.com/code_viewer.php?code=lighting/light_casters_spotlight_hard)和[片段着色器的源码](http://learnopengl.com/code_viewer.php?code=lighting/light_casters_spotlight_hard&type=fragment)。
|
||||
|
||||
它看起来仍然有点假,大部分原因是聚光灯有了一个硬边。片段着色器一旦到达了聚光灯的圆锥边缘,它就立刻黑了下来,却没有任何平滑减弱的过度。一个真实的聚光灯的光会在它的边界处平滑减弱的。
|
||||
它看起来仍然有点假,原因是聚光灯有了一个硬边。片段着色器一旦到达了聚光灯的圆锥边缘,它就立刻黑了下来,却没有任何平滑减弱的过度。一个真实的聚光灯的光会在它的边界处平滑减弱的。
|
||||
|
||||
|
||||
|
||||
##16.6 平滑/软化边缘
|
||||
## 平滑/软化边缘
|
||||
|
||||
为创建聚光灯的平滑边,我们希望去模拟的聚光灯有一个内圆锥和外圆锥。我们可以把内圆锥设置为前面部分定义的圆锥,我们希望外圆锥从内边到外边逐步的变暗。
|
||||
|
||||
@@ -265,9 +290,11 @@ color = vec4(light.ambient*vec3(texture(material.diffuse,TexCoords)), 1.0f);
|
||||
0.83|34|0.91|25|0.82|35|0.91 - 0.82 = 0.09|0.83 - 0.82 / 0.09 = 0.11
|
||||
0.64|50|0.91|25|0.82|35|0.91 - 0.82 = 0.09|0.64 - 0.82 / 0.09 = -2.0
|
||||
0.966|15|0.9978|12.5|0.953|17.5|0.966 - 0.953 = 0.0448|0.966 - 0.953 / 0.0448 = 0.29
|
||||
|
||||
就像你看到的那样我们基本是根据θ在外余弦和内余弦之间插值。如果你仍然不明白怎么继续,不要担心。你可以简单的使用这个公式计算,当你更加老道和明白的时候再来看。
|
||||
|
||||
由于我们现在有了一个亮度值,当在聚光灯外的时候是个负的,当在内部圆锥以内大于1。如果我们适当地把这个值固定,我们在片段着色器中就再不需要if-else了,我们可以简单地用计算出的亮度值乘以光的元素:
|
||||
|
||||
```c++
|
||||
float theta = dot(lightDir, normalize(-light.direction));
|
||||
float epsilon = light.cutOff - light.outerCutOff;
|
||||
@@ -278,12 +305,13 @@ light.diffuse* = intensity;
|
||||
specular* = intensity;
|
||||
...
|
||||
```
|
||||
注意,我们使用了clamp函数,它把第一个参数固定在0.0和1.0之间。这保证了亮度值不会超出[0, 1]以外。
|
||||
|
||||
确定你把outerCutOff值添加到了Light结构体,并在应用中设置了它的uniform值。对于下面的图片,内部遮光角12.5f,外部遮光角是17.5f:
|
||||
注意,我们使用了`clamp`函数,它把第一个参数固定在0.0和1.0之间。这保证了亮度值不会超出[0, 1]以外。
|
||||
|
||||
确定你把`outerCutOff`值添加到了`Light`结构体,并在应用中设置了它的uniform值。对于下面的图片,内部遮光角`12.5f`,外部遮光角是`17.5f`:
|
||||
|
||||

|
||||
|
||||
看起来好多了。仔细看看内部和外部遮光角,尝试创建一个符合你求的聚光灯。可以在这里找到应用源码,以及片段的源代码。
|
||||
|
||||
这样的一个手电筒/聚光灯类型的灯光非常适合恐怖游戏,结合定向和点光,环境会真的开始被照亮了。下一个教程,我们会结合所有我们目前讨论了的光和技巧。
|
||||
这样的一个手电筒/聚光灯类型的灯光非常适合恐怖游戏,结合定向和点光,环境会真的开始被照亮了。[下一个教程](http://learnopengl-cn.readthedocs.org/zh/latest/02%20Lighting/06%20Multiple%20lights/),我们会结合所有我们目前讨论了的光和技巧。
|
||||
|
@@ -1,10 +1,14 @@
|
||||
##多光源(Multiple lights)
|
||||
# 多光源(Multiple lights)
|
||||
|
||||
本文作者JoeyDeVries,由Geequlim翻译自[http://learnopengl.com](http://learnopengl.com/#!Lighting/Multiple-lights)
|
||||
原文 | [Multiple lights](http://learnopengl.com/#!Lighting/Multiple-lights)
|
||||
---|---
|
||||
作者 | JoeyDeVries
|
||||
翻译 | [Geequlim](http://geequlim.com)
|
||||
校对 | [Geequlim](http://geequlim.com)
|
||||
|
||||
我们在前面的教程中已经学习了许多关于OpenGL 光照的知识,其中包括冯氏照明(Phong shading)、光照材质(Materials)、光照图(Lighting maps)以及各种投光物。本教程将结合上述所学的知识,创建一个包含六个光源的场景。我们将模拟一个类似阳光的平行光(Directional light)和4个定点光(Point lights)以及一个手电筒(Flashlight).
|
||||
我们在前面的教程中已经学习了许多关于OpenGL 光照的知识,其中包括冯氏照明模型(Phong shading)、光照材质(Materials)、光照图(Lighting maps)以及各种投光物(Light casters)。本教程将结合上述所学的知识,创建一个包含六个光源的场景。我们将模拟一个类似阳光的平行光(Directional light)和4个定点光(Point lights)以及一个手电筒(Flashlight).
|
||||
|
||||
要在场景中使用多光源我们需要封装一些GLSL函数用来计算光照。如果我们对每个光源都去些一遍光照计算的代码,这将是一件令人恶心的事情,并且这些放在main函数中的代码将难以理解,所以我们将一些操作封装为函数。
|
||||
要在场景中使用多光源我们需要封装一些GLSL函数用来计算光照。如果我们对每个光源都去写一遍光照计算的代码,这将是一件令人恶心的事情,并且这些放在main函数中的代码将难以理解,所以我们将一些操作封装为函数。
|
||||
|
||||
GLSL中的函数与C语言的非常相似,它需要一个函数名、一个返回值类型。并且在调用前必须提前声明。接下来我们将为下面的每一种光照来写一个函数。
|
||||
|
||||
@@ -31,7 +35,7 @@ void main()
|
||||
|
||||
即使对每一种光源的运算实现不同,但此算法的结构一般是与上述出入不大的。我们将定义几个用于计算各个光源的函数,并将这些函数的结算结果(返回颜色)添加到输出颜色向量中。例如,靠近被照射物体的光源计算结果将返回比远离背照射物体的光源更明亮的颜色。
|
||||
|
||||
###平行光(Directional light)
|
||||
## 平行光(Directional light)
|
||||
|
||||
我们要在片段着色器中定义一个函数用来计算平行光在对应的照射点上的光照颜色,这个函数需要几个参数并返回一个计算平行光照结果的颜色。
|
||||
|
||||
@@ -47,7 +51,8 @@ struct DirLight {
|
||||
};
|
||||
uniform DirLight dirLight;
|
||||
```
|
||||
之后我们可以将 *dirLight*这个uniform变量作为下面这个函数原型的参数。
|
||||
|
||||
之后我们可以将`dirLight`这个uniform变量作为下面这个函数原型的参数。
|
||||
|
||||
```c++
|
||||
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
|
||||
@@ -57,7 +62,7 @@ vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
|
||||
|
||||
和C/C++一样,我们调用一个函数的前提是这个函数在调用前已经被声明过(此例中我们是在main函数中调用)。通常情况下我们都将函数定义在main函数之后,为了能在main函数中调用这些函数,我们就必须在main函数之前声明这些函数的原型,这就和我们写C语言是一样的。
|
||||
|
||||
你已经知道,这个函数需要一个**DirLight**和两个其他的向量作为参数来计算光照。如果你看过之前的教程的话,你会觉得下面的函数定义得一点也不意外:
|
||||
你已经知道,这个函数需要一个`DirLight`和两个其他的向量作为参数来计算光照。如果你看过之前的教程的话,你会觉得下面的函数定义得一点也不意外:
|
||||
|
||||
```c++
|
||||
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
|
||||
@@ -78,7 +83,7 @@ vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
|
||||
|
||||
我们从之前的教程中复制了代码,并用两个向量来作为函数参数来计算出平行光的光照颜色向量,该结果是一个由该平行光的环境反射、漫反射和镜面反射的各个分量组成的一个向量。
|
||||
|
||||
###定点光(Point light)
|
||||
## 定点光(Point light)
|
||||
|
||||
和计算平行光一样,我们同样需要定义一个函数用于计算定点光照。同样的,我们定义一个包含定点光源所需属性的结构体:
|
||||
|
||||
@@ -98,7 +103,7 @@ struct PointLight {
|
||||
uniform PointLight pointLights[NR_POINT_LIGHTS];
|
||||
```
|
||||
|
||||
如你所见,我们在GLSL中使用预处理器指令来定义定点光源的数目。之后我们使用这个**NR_POINT_LIGHTS**常量来创建一个**PointLight**结构体的数组。和C语言一样,GLSL也是用一对中括号来创建数组的。现在我们有了4个**PointLight**结构体对象了。
|
||||
如你所见,我们在GLSL中使用预处理器指令来定义定点光源的数目。之后我们使用这个`NR_POINT_LIGHTS`常量来创建一个`PointLight`结构体的数组。和C语言一样,GLSL也是用一对中括号来创建数组的。现在我们有了4个`PointLight`结构体对象了。
|
||||
|
||||
!!! Important
|
||||
|
||||
@@ -106,10 +111,9 @@ uniform PointLight pointLights[NR_POINT_LIGHTS];
|
||||
|
||||
```c++
|
||||
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
|
||||
|
||||
```
|
||||
|
||||
这个函数将所有用得到的数据作为它的参数并使用一个vec3作为它的返回值类表示一个顶点光的结算结果。我们再一次聪明地从之前的教程中复制代码来把它定义成下面的样子:
|
||||
这个函数将所有用得到的数据作为它的参数并使用一个`vec3`作为它的返回值类表示一个顶点光的结算结果。我们再一次聪明地从之前的教程中复制代码来把它定义成下面的样子:
|
||||
|
||||
```c++
|
||||
// 计算定点光在确定位置的光照颜色
|
||||
@@ -138,7 +142,7 @@ vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
|
||||
|
||||
有了这个函数我们就可以在main函数中调用它来代替写很多个计算点光源的代码了。通过循环调用此函数就能实现同样的效果,当然代码更简洁。
|
||||
|
||||
###把它们放到一起
|
||||
## 把它们放到一起
|
||||
|
||||
我们现在定义了用于计算平行光和定点光的函数,现在我们把这些代码放到一起,写入文开始的一般结构中:
|
||||
|
||||
@@ -161,10 +165,9 @@ void main()
|
||||
}
|
||||
```
|
||||
|
||||
每一个光源的运算结果都添加到了输出颜色上,输出颜色包含了此场景中的所有光源的影响。
|
||||
如果你想实现手电筒的光照效果,同样的把计算结果添加到输出颜色上。我在这里就把CalcSpotLight的实现留作个读者们的练习吧。
|
||||
每一个光源的运算结果都添加到了输出颜色上,输出颜色包含了此场景中的所有光源的影响。如果你想实现手电筒的光照效果,同样的把计算结果添加到输出颜色上。我在这里就把`CalcSpotLight`的实现留作个读者们的练习吧。
|
||||
|
||||
设置平行光结构体的uniform值和之前所讲过的方式没什么两样,但是你可能想知道如何设置场景中**PointLight**结构体的uniforms变量数组。我们之前并未讨论过如何做这件事。
|
||||
设置平行光结构体的uniform值和之前所讲过的方式没什么两样,但是你可能想知道如何设置场景中`PointLight`结构体的uniforms变量数组。我们之前并未讨论过如何做这件事。
|
||||
|
||||
庆幸的是,这并不是什么难题。设置uniform变量数组和设置单个uniform变量值是相似的,只需要用一个合适的下标就能够检索到数组中我们想要的uniform变量了。
|
||||
|
||||
@@ -172,11 +175,9 @@ void main()
|
||||
glUniform1f(glGetUniformLocation(lightingShader.Program, "pointLights[0].constant"), 1.0f);
|
||||
```
|
||||
|
||||
这样我们检索到pointLights数组中的第一个 PointLight 结构体元素,同时也可以获取到该结构体中的各个属性变量。不幸的是这一位置我们还需要手动
|
||||
对这个四个光源的每一个属性都进行设置,这样手动设置这28个uniform变量是相当乏味的工作。你可以尝试去定义个光源类来为你设置这些uniform属性来减少你的工作,
|
||||
但这依旧不能改变去设置每个uniform属性的事实。
|
||||
这样我们检索到`pointLights`数组中的第一个`PointLight`结构体元素,同时也可以获取到该结构体中的各个属性变量。不幸的是这一位置我们还需要手动对这个四个光源的每一个属性都进行设置,这样手动设置这28个uniform变量是相当乏味的工作。你可以尝试去定义个光源类来为你设置这些uniform属性来减少你的工作,但这依旧不能改变去设置每个uniform属性的事实。
|
||||
|
||||
别忘了,我们还需要为每个光源设置它们的位置。这里,我们定义一个glm::vec3类的数组来包含这些点光源的坐标:
|
||||
别忘了,我们还需要为每个光源设置它们的位置。这里,我们定义一个`glm::vec3`类的数组来包含这些点光源的坐标:
|
||||
|
||||
```c++
|
||||
glm::vec3 pointLightPositions[] = {
|
||||
@@ -201,7 +202,7 @@ glm::vec3 pointLightPositions[] = {
|
||||
|
||||
相信你现在已经对OpenGL的光照有很好的理解了。有了这些知识我们便可以创建丰富有趣的环境和氛围了。快试试改变所有的属性的值来创建你的光照环境吧!
|
||||
|
||||
###练习
|
||||
## 练习
|
||||
|
||||
* 创建一个表示手电筒光的结构体Spotlight并实现CalcSpotLight(...)函数:[解决方案](http://learnopengl.com/code_viewer.php?code=lighting/multiple_lights-exercise1)
|
||||
* 你能通过调节不同的光照属性来重新创建一个不同的氛围吗?[解决方案](http://learnopengl.com/code_viewer.php?code=lighting/multiple_lights-exercise2)
|
@@ -1,12 +1,22 @@
|
||||
本文作者JoeyDeVries,由Meow J翻译自[http://learnopengl.com](http://learnopengl.com/#!Lighting/Review)
|
||||
|
||||
# 复习
|
||||
|
||||
恭喜您已经学到了这个地方!不知道你有没有注意到,总体来说我们在学习光照教程的时候学习的除了一些细节(像访问uniform数组)并不是OpenGL本身. 到现在的所有教程都是关于用一些技巧和公式来操作着色器从而达到真实的光照效果. 这同样向你展示了着色器的威力. 着色器是非常灵活的,你也亲眼见证了仅仅使用一些3D向量和可配置的变量就能够创造出惊人的图形这一点.
|
||||
原文 | [Review](http://learnopengl.com/#!Lighting/Review)
|
||||
---|---
|
||||
作者 | JoeyDeVries
|
||||
翻译 | Meow J
|
||||
校对 | [Geequlim](http://geequlim.com)
|
||||
|
||||
在你学过的最后几个教程中,你学习了有关颜色,Phong氏光照模型(包括环境,漫反射,镜面反射光照),对象材质,可配置的光照属性,漫反射和镜面反射贴图,不同种类的光,并且学习了怎样将所有所学知识融合到单独的一个程序里面. 记得去实验不同的光照,材质颜色,光照属性,并且试着使用创造力创建自己的环境.
|
||||
恭喜您已经学到了这个地方!不知道你有没有注意到,总体来说我们在学习光照教程的时候学习的除了一些细节(像访问uniform数组)并不是OpenGL本身。
|
||||
|
||||
在下一个教程当中,我们将加入更高级的形状到我们的场景中,这些形状将会在我们讨论过的光照模型中非常好看.
|
||||
到现在的所有教程都是关于用一些技巧和公式来操作着色器从而达到真实的光照效果。 这同样向你展示了着色器的威力。
|
||||
|
||||
着色器是非常灵活的,你也亲眼见证了仅仅使用一些3D向量和可配置的变量就能够创造出惊人的图形这一点。
|
||||
|
||||
在你学过的最后几个教程中,你学习了有关颜色,冯氏光照模型(包括环境,漫反射,镜面反射光照),对象材质,可配置的光照属性,漫反射和镜面反射贴图,不同种类的光,并且学习了怎样将所有所学知识融合到单独的一个程序里面。
|
||||
|
||||
记得去实验不同的光照,材质颜色,光照属性,并且试着使用创造力创建自己的环境。
|
||||
|
||||
在[下一个教程](http://learnopengl-cn.readthedocs.org/zh/latest/03%20Model%20Loading/01%20Assimp/)当中,我们将加入更高级的形状到我们的场景中,这些形状将会在我们讨论过的光照模型中非常好看。
|
||||
|
||||
词汇表
|
||||
--------
|
||||
|
Reference in New Issue
Block a user