opengl 教程(19) 高光

原帖地址 http://ogldev.atspace.co.uk/www/tutorial19/tutorial19.html

      最初我们计算环境光的时候,唯一影响光照的就是光的强度,接着在漫反射光计算时,我们引入了光源的方向以及物体顶点法线的概念,在本篇教程中,我们学习如何计算高光,我们会再次引入一个新的参数视点位置,因为高光会随着视点的移动而改变位置。在一些角度,高光看起来会更亮。金属物体通常都有高光的效果。

      下面的高光计算中会引入视点位置,首先看一下下面的光照图:

specular_light

这儿有5个参数需要我们注意:

  • 'I' 是照亮物体表面的入射光
  • 'N' 是物体表面法向
  • 'R' 是入射光在照射到物体表面后的发射光,反射光和入射光是沿着法线对称的。
  • 'V' 是物体表面上的点指向视点的向量。
  • 'α' 'R' 和 'V'两个向量的夹角

       角度'α'为0的时候,R和V重合,此时高光的强度最大,当观察者视线逐渐离开的时候,α逐渐变大,高光效果会逐渐变小。基于这个依据,我们将用点积操作来求得α的cosine值,这将作为我们计算高光时的因子,当α大于90度时候,cosine值为负值,此时没有高光效果。

      为了计算α,我们需要两个向量 'R' 和 'V'。 'V'向量可以用摄像机(视点)位置减光照作用的顶点位置得到,注意这2个位置应该都是位于世界坐标系。对于摄像机,我们需要在shader中传入它的世界坐标系位置,而高光的计算我们放在片元shader中,将对每个片元的世界坐标系位置(由插值而来)计算高光。 

     下面我们看看如何根据入射光向量I计算光线反射向量 'R':

reflected_light

      我们知道,向量并没有起始点,唯一决定向量的是方向和长度,所以,如上图所示,入射向量I可以看作“拷贝”到-N的方向的I,这时有I+V=R,(注意这儿的V并不是视点到顶点的向量,而是计算R的向量,也称作V有点混淆...),我们知道V/2 = N*(-N*I)【就是说V向量的和N向量一个方向,它的长度等于(-N*I)】,所有我们有了以下的公式:

reflect

     在GLSL中,有个内置的函数'reflect'就是用来计算反射向量的,在下面shader代码中,我们就用了该函数。

我们来看下最终的高光函数:

specular1

      它等于光源的颜色乘以物体表面的颜色,再乘以材质高光强度('M'),接着再乘以反射光线和摄像机指向物体的向量夹角的cosin值的p次方。如果物体没有高光效果,比如木头,则M为0,而p则被称为高光指数,它通常也是作为物体的材质属性,它的大小将决定高光区域边缘的显示效果,下面的图是p为1时候的高光效果:

shininess_1

而下面这幅图则是p为32时候的效果:

shininess_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循环冲,我们设置摄像机位置,高光强度和高光指数。

下面是程序运行后的效果,我们可以选择物体观察高光的效果:

clipboard

posted on 2013-01-26 08:21  迈克老狼2012  阅读(2547)  评论(0编辑  收藏  举报

导航