现代OpenGL+Qt学习笔记之七:Phong光照及在GLSL中使用函数

现代OpenGL+Qt学习笔记之七:Phong光照及在GLSL中使用函数

主要内容

  在现代OpenGL+Qt学习笔记之六:绘制可旋转、带光照效果的三维物体中介绍了一个最简单的漫射光的原理及在OpenGL中的实现。本文将重点介绍一个更加经典的光照模型——Phong光照模型,也叫ADS(Ambient, Diffuse, Specular)光照模型。即在渲染物体时,不仅仅只考虑物体的漫反射(Diffuse),还考虑环境光(Ambient)和镜面反射(Specular)。同时还会介绍如何在GLSL中使用函数,示例程序将使用GLSL函数实现Phong光照。

Phong光照

  Phong光照模型中,光强的计算是环境光强、漫反射光强和镜面反射光强的和。其中环境光照射到物体表面上的光强相同,且在各个方向上的反射光强也相同。因为环境光和入射方向和反射方向都无关,其光强( Ia 的计算只需要计算入射光强( La )和反射系数( Ka )的乘积即可。

Ia=LaKa

  漫反射光强的原理和计算方法在现代OpenGL+Qt学习笔记之六:绘制可旋转、带光照效果的三维物体中已经有详细介绍,这里仅给出它的计算公式:

Id=LdKd(sn)

  镜面反射光能让物体表面产生高亮效果。镜面反射光的入射角和反射角相等,入射方向和反射方向关于法向对称(不考虑指向),如图所示:

现代OpenGL+Qt学习笔记之七:Phong光照及在GLSL中使用函数_第1张图片

其中r是对应入射方向(-s)的反射方向,n是法向量,r可以通过下式计算得到:
r=s+2(sn)n

  和漫反射的情况不同,计算镜面反射的光强还需要考虑观察点的位置。即计算镜面反射光强需要如下4个(单位)向量:反射点到光源的方向s;反射方向r;反射点到观察点的方向v;法向量n。如下是各向量的示意图:

现代OpenGL+Qt学习笔记之七:Phong光照及在GLSL中使用函数_第2张图片

  结合生活经验我们知道,当v和r方向重合时,反射光是最强的,而到v与r不重合,在夹角增大的过程中,反射光的光强会迅速衰减。该特点可以用v和r之间的余弦值刻画,但实际的衰减速度会比余弦值更高,因此在余弦值上增加了一个指数( f )。

Is=LsKs(rv)f

  其中 Ls Ks 是和镜面反射相关的入射光强和反射系数,当 f 增大时,其反射光强随着r和v之间的夹角的增大而降低的速度更快。通常 f 的取值在1到200之前, f 越大,物体表面形成的镜面高光区域越小。

  将所有的反射光强相加,即得到了最终的光照模型:

I=Ia+Id+Is=LaKa+LdKd(sn)+LsKs(rv)f

  在介绍完GLSL中的函数之后,会通过一个程序介绍该模型在GLSL中使如何实现的。

在GLSL中使用函数

  当GLSL程序较大时,肯定得要将代码分成几个函数。下面是一个没有使用函数的例子:

// Structure declaration, to use as sample
struct Light
{
    vec3 position;
    vec3 diffuseColor;
    vec3 attenuation;
};

// Shader entry point, just like in C, but no input params
void main()
{
    vec3 myPosition = vec3(1.0, 0.0, 0.0);
    // Let's create and initialize some ligths
    Light light1 = Light(vec3(10.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), vec3(1.0, 2.0, 3.0));
    Light light2 = Light(vec3(0.0, 10.0, 0.0), vec3(1.0, 0.0, 0.0) ,vec3(1.0, 2.0, 3.0));
    Light light3 = Light(vec3(0.0, 0.0, 10.0), vec3(1.0, 0.0, 0.0) ,vec3(1.0, 2.0, 3.0));
    // Calculate simplified light contribution and add to final color
    vec3 finalColor = vec3(0.0, 0.0, 0.0);
    //distance is a GLSL built-in function
    float distance1 = distance(myPosition, light1.position);
    float attenuation1 = 1.0 / (light1.attenuation[0] + light1.attenuation[1] * distance1 + light1.attenuation[2] * distance1 * distance1);
    finalColor += light1.diffuseColor * light1.attenuation;

    // Let's calculate the same, for light2
    float distance2 = distance(myPosition, light2.position);
    float attenuation2 = 1.0 / (light2.attenuation[0] + light2.attenuation[1] * distance2 + light2.attenuation[2] * distance2 * distance2);
    finalColor += light2.diffuseColor * light2.attenuation;

    // Light 3
    float distance3 = distance(myPosition, light3.position);
    float attenuation3 = 1.0 / (light3.attenuation[0] + light3.attenuation[1] * distance3 + light3.attenuation[2] * distance3 * distance3);
    finalColor += light3.diffuseColor * light3.attenuation;

    // Now finalColor stores our desired color
}

  将其改成用函数的方式实现:

struct Light
{
    vec3 position;
    vec3 diffuseColor;
    vec3 attenuation;
};

vec3 CalculateContribution(const in Light light, const in vec3
position)
{
    float distance = distance(position, light.position);
    float attenuation = 1.0 / (light.attenuation[0] + light.attenuation[1] * distance + light.attenuation[2] * distance * distance);

    return light.diffuseColor * light.attenuation;
}
// Shader entry point, just like in C, but no input params
void main()
{
    vec3 myPosition = vec3(1.0, 0.0, 0.0);
    Light light1 = Light(vec3(10.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0) ,vec3(1.0, 2.0, 3.0));
    Light light2 = Light(vec3(0.0, 10.0, 0.0), vec3(1.0, 0.0, 0.0) ,vec3(1.0, 2.0, 3.0));
    Light light3 = Light(vec3(0.0, 0.0, 10.0), vec3(1.0, 0.0, 0.0) ,vec3(1.0, 2.0, 3.0));
    // Calculate light1
    vec3 finalColor = CalculateContribution(light1, myPosition);
    // Calculate light2
    finalColor += CalculateContribution(light2, myPosition);
    // Calculate light3
    finalColor += CalculateContribution(light3, myPosition);
}

  这样的代码相比于之前,更具有可读性,且易于调试、差错和修改。

  在GLSL中,函数必须在其使用前声明,因此需要将函数的声明放在调用点之前。如上面的例子将函数声明和定义在了调用它的main函数之前,也可以通过先定义函数原型,然后在调用点之后定义函数主体。

  GLSL函数和C/C++形式相同,可以不带返回值void,也可以带返回值,如这里的vec3。

  对于上面的例子的函数原型:

vec3 CalculateContribution(const in Light light, const in vec3 position);

有两个输入变量,const关键字说明该变量的值不会在函数内部被改变;关键字in指定无论在函数内部对这个变量进行了怎样的操作,变量在函数调用结束后仍保持不变;关键字out是指变量仅仅会复制出函数,而不会复制进函数,即就算这个变量在输入前是有值得,但是其值不会被输入到调用的函数中;关键字inout指变量会被函数输入和输出,类似于C++中的引用和指针,其值会被赋值到函数中,也能被函数改变,然后输出。

out变量在输入函数前没有初始化

  const关键字和out或inout一起是没有意义的,因此不要使用这种组合。

  默认关键字的限定符是in,对于输入类型的参数,其值被输入到函数中,但是在其中发生改变,不会影响到其原始变量的值。

示例程序

  下面使用函数实现上面介绍的Phong光照模型,程序在现代OpenGL+Qt学习笔记之六:绘制可旋转、带光照效果的三维物体中的程序基础上增改。

  首先是两个新的着色器function.frag:

#version 430

in vec3 LightIntensity;

layout( location = 0 ) out vec4 FragColor;

void main() {
    FragColor = vec4(LightIntensity, 1.0);
}

和之前的片元着色器功能完全相同,接下来是相应的用函数实现的Phong光照模型的顶点着色器function.vert:

#version 430

layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexNormal;

out vec3 LightIntensity;

struct LightInfo {
  vec4 Position; // Light position in eye coords.
  vec3 La;       // Ambient light intensity
  vec3 Ld;       // Diffuse light intensity
  vec3 Ls;       // Specular light intensity
};
uniform LightInfo Light;

struct MaterialInfo {
  vec3 Ka;            // Ambient reflectivity
  vec3 Kd;            // Diffuse reflectivity
  vec3 Ks;            // Specular reflectivity
  float Shininess;    // Specular shininess factor
};
uniform MaterialInfo Material;

uniform mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;
uniform mat4 ProjectionMatrix;
uniform mat4 MVP;

void getEyeSpace( out vec3 norm, out vec4 position )
{
    norm = normalize( NormalMatrix * VertexNormal);
    position = ModelViewMatrix * vec4(VertexPosition,1.0);
}

vec3 phongModel( vec4 position, vec3 norm )
{
    vec3 s = normalize(vec3(Light.Position - position));
    vec3 v = normalize(-position.xyz);
    vec3 r = reflect( -s, norm );
    vec3 ambient = Light.La * Material.Ka;
    float sDotN = max( dot(s,norm), 0.0 );
    vec3 diffuse = Light.Ld * Material.Kd * sDotN;
    vec3 spec = vec3(0.0);
    if( sDotN > 0.0 )
        spec = Light.Ls * Material.Ks *
               pow( max( dot(r,v), 0.0 ), Material.Shininess );

    return ambient + diffuse + spec;
}

void main()
{
    vec3 eyeNorm;
    vec4 eyePosition;

    // Get the position and normal in eye space
    getEyeSpace(eyeNorm, eyePosition);

    // Evaluate the lighting equation.
    LightIntensity = phongModel( eyePosition, eyeNorm );

    gl_Position = MVP * vec4(VertexPosition,1.0);
}

  接下来,主程序需要做一些简单改动,所有改动在initializeGL()函数中进行,首先将上述两个着色器添加到主程序的资源文件中,然后修改主程序中药用到的着色器源码文件的路径:

    // vertex shader
    QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);
    vshader->compileSourceFile(":/shader/function.vert");
    // fragment shader
    QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);
    fshader->compileSourceFile(":/shader/function.frag");

  最后要再主程序中,依次为各个和光照及材质相关的系数赋值,将

    // uniform light/material property
    program->setUniformValue("Kd", QVector3D(0.9f, 0.5f, 0.3f));
    program->setUniformValue("Ld", QVector3D(1.0f, 1.0f, 1.0f));
    program->setUniformValue("LightPosition", view * QVector4D(0.0f,0.0f,5.0f,1.0f) );

改为

    // uniform light/material property
    QVector4D worldLight = QVector4D(5.0f,5.0f,2.0f,1.0f);
    program->setUniformValue("Material.Kd", 0.9f, 0.5f, 0.3f);
    program->setUniformValue("Light.Ld", 1.0f, 1.0f, 1.0f);
    program->setUniformValue("Light.Position", view * worldLight );
    program->setUniformValue("Material.Ka", 0.9f, 0.5f, 0.3f);
    program->setUniformValue("Light.La", 0.4f, 0.4f, 0.4f);
    program->setUniformValue("Material.Ks", 0.8f, 0.8f, 0.8f);
    program->setUniformValue("Light.Ls", 1.0f, 1.0f, 1.0f);
    program->setUniformValue("Material.Shininess", 100.0f);

  程序运行结果如下:

小结

  本文主要介绍了ADS光照模型以及在GLSL中使用函数的一些知识,并通过一个程序演示GLSL函数的使用和ADS光照模型的实现。后续还会采用这种形式,整个几个知识点一起介绍和创建演示程序。

源码地址:http://download.csdn.net/download/chaojiwudixiaofeixia/9994280

你可能感兴趣的:(OpenGL)