原帖地址 http://ogldev.atspace.co.uk/www/tutorial19/tutorial19.html
最初我们计算环境光的时候,唯一影响光照的就是光的强度,接着在漫反射光计算时,我们引入了光源的方向以及物体顶点法线的概念,在本篇教程中,我们学习如何计算高光,我们会再次引入一个新的参数视点位置,因为高光会随着视点的移动而改变位置。在一些角度,高光看起来会更亮。金属物体通常都有高光的效果。
下面的高光计算中会引入视点位置,首先看一下下面的光照图:
这儿有5个参数需要我们注意:
- 'I' 是照亮物体表面的入射光
- 'N' 是物体表面法向
- 'R' 是入射光在照射到物体表面后的发射光,反射光和入射光是沿着法线对称的。
- 'V' 是物体表面上的点指向视点的向量。
- 'α' 'R' 和 'V'两个向量的夹角
角度'α'为0的时候,R和V重合,此时高光的强度最大,当观察者视线逐渐离开的时候,α逐渐变大,高光效果会逐渐变小。基于这个依据,我们将用点积操作来求得α的cosine值,这将作为我们计算高光时的因子,当α大于90度时候,cosine值为负值,此时没有高光效果。
为了计算α,我们需要两个向量 'R' 和 'V'。 'V'向量可以用摄像机(视点)位置减光照作用的顶点位置得到,注意这2个位置应该都是位于世界坐标系。对于摄像机,我们需要在shader中传入它的世界坐标系位置,而高光的计算我们放在片元shader中,将对每个片元的世界坐标系位置(由插值而来)计算高光。
下面我们看看如何根据入射光向量I计算光线反射向量 'R':
我们知道,向量并没有起始点,唯一决定向量的是方向和长度,所以,如上图所示,入射向量I可以看作“拷贝”到-N的方向的I,这时有I+V=R,(注意这儿的V并不是视点到顶点的向量,而是计算R的向量,也称作V有点混淆...),我们知道V/2 = N*(-N*I)【就是说V向量的和N向量一个方向,它的长度等于(-N*I)】,所有我们有了以下的公式:
在GLSL中,有个内置的函数'reflect'就是用来计算反射向量的,在下面shader代码中,我们就用了该函数。
我们来看下最终的高光函数:
它等于光源的颜色乘以物体表面的颜色,再乘以材质高光强度('M'),接着再乘以反射光线和摄像机指向物体的向量夹角的cosin值的p次方。如果物体没有高光效果,比如木头,则M为0,而p则被称为高光指数,它通常也是作为物体的材质属性,它的大小将决定高光区域边缘的显示效果,下面的图是p为1时候的高光效果:
而下面这幅图则是p为32时候的效果:
主要代码:
lighting_technique.h
class LightingTechnique : public Technique
{
public:
...
void SetEyeWorldPos(const Vector3f& EyeWorldPos);
void SetMatSpecularIntensity(float Intensity);
void SetMatSpecularPower(float Power);
private:
...
GLuint m_eyeWorldPosLocation;
GLuint m_matSpecularIntensityLocation;
GLuint m_matSpecularPowerLocation;
我们在LightingTechnique类中增加了3个新的属性:眼的位置,高光强度和高光指数,这些都在光源中设置的,但是这样的设置并不太好,比如场景中的不同物质,可能高光效果是一样的,通常我们会把高光强度和高光指数当作材质属性,后面的教程中,我们会看到,这些属性会被当作顶点属性。
lighting_technique.cpp
out vec3 WorldPos0;
void main()
{
gl_Position = gWVP * vec4(Position, 1.0);
TexCoord0 = TexCoord;
Normal0 = (gWorld * vec4(Normal, 0.0)).xyz;
WorldPos0 = (gWorld * vec4(Position, 1.0)).xyz;
}
顶点shader增加了一行代码,用来得到顶点世界坐标系中的位置,以便在片元shader中计算高光时候使用。
in vec3 WorldPos0;
.
.
.
uniform vec3 gEyeWorldPos;
uniform float gMatSpecularIntensity;
uniform float gSpecularPower;
void main()
{
vec4 AmbientColor = vec4(gDirectionalLight.Color, 1.0f) * gDirectionalLight.AmbientIntensity;
vec3 LightDirection = -gDirectionalLight.Direction;
vec3 Normal = normalize(Normal0);
float DiffuseFactor = dot(Normal, LightDirection);
vec4 DiffuseColor = vec4(0, 0, 0, 0);
vec4 SpecularColor = vec4(0, 0, 0, 0);
if (DiffuseFactor > 0) {
DiffuseColor = vec4(gDirectionalLight.Color, 1.0f) *
gDirectionalLight.DiffuseIntensity *
DiffuseFactor;
vec3 VertexToEye = normalize(gEyeWorldPos - WorldPos0);
vec3 LightReflect = normalize(reflect(gDirectionalLight.Direction, Normal));
float SpecularFactor = dot(VertexToEye, LightReflect);
SpecularFactor = pow(SpecularFactor, gSpecularPower);
if (SpecularFactor > 0) {
SpecularColor = vec4(gDirectionalLight.Color, 1.0f) * gMatSpecularIntensity * SpecularFactor;
}
}
FragColor = texture2D(gSampler, TexCoord0.xy) * (AmbientColor + DiffuseColor + SpecularColor);
}
在片元shader中,我们增加了三个uniform变量,它们保存了眼的位置,高光强度以及高光指数,用来计算高光。环境光和前面教程中计算的方法一样,漫反射光和高光则被初始化为0,当入射光和物体表面夹角小于90度时,分别计算漫反射光和高光。计算高光时候,我们归一化了光线的方向向量和摄像机到像素的向量,最后根据公式得到高光产生的颜色。输出像素颜色时候,我们把纹理采样的颜色和光照的颜色进行混合调制操作,得到最终的颜色。
tutorial19.cpp
m_pEffect->SetEyeWorldPos(m_pGameCamera->GetPos());
m_pEffect->SetMatSpecularIntensity(1.0f);
m_pEffect->SetMatSpecularPower(32);
在render循环冲,我们设置摄像机位置,高光强度和高光指数。
下面是程序运行后的效果,我们可以选择物体观察高光的效果: