現代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)

  鏡面反射光能讓物體表面產生高亮效果。鏡面反射光的入射角和反射角相等,入射方向和反射方向關於法向對稱(不考慮指向),如圖所示:

鏡面反射

其中r是對應入射方向(-s)的反射方向,n是法向量,r可以通過下式計算得到:
r=s+2(sn)n

  和漫反射的情況不同,計算鏡面反射的光強還需要考慮觀察點的位置。即計算鏡面反射光強需要如下4個(單位)向量:反射點到光源的方向s;反射方向r;反射點到觀察點的方向v;法向量n。如下是各向量的示意圖:

反射模型向量

  結合生活經驗我們知道,當v和r方向重合時,反射光是最強的,而到v與r不重合,在夾角增大的過程中,反射光的光強會迅速衰減。該特點可以用v和r之間的餘弦值刻畫,但實際的衰減速度會比餘弦值更高,因此在餘弦值上增加了一個指數(f )。

Is=LsKs(rv)f

  其中LsKs 是和鏡面反射相關的入射光強和反射係數,當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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章