現代OpenGL+Qt學習筆記之七:Phong光照及在GLSL中使用函數
主要內容
在現代OpenGL+Qt學習筆記之六:繪製可旋轉、帶光照效果的三維物體中介紹了一個最簡單的漫射光的原理及在OpenGL中的實現。本文將重點介紹一個更加經典的光照模型——Phong光照模型,也叫ADS(Ambient, Diffuse, Specular)光照模型。即在渲染物體時,不僅僅只考慮物體的漫反射(Diffuse),還考慮環境光(Ambient)和鏡面反射(Specular)。同時還會介紹如何在GLSL中使用函數,示例程序將使用GLSL函數實現Phong光照。
Phong光照
Phong光照模型中,光強的計算是環境光強、漫反射光強和鏡面反射光強的和。其中環境光照射到物體表面上的光強相同,且在各個方向上的反射光強也相同。因爲環境光和入射方向和反射方向都無關,其光強(
漫反射光強的原理和計算方法在現代OpenGL+Qt學習筆記之六:繪製可旋轉、帶光照效果的三維物體中已經有詳細介紹,這裏僅給出它的計算公式:
鏡面反射光能讓物體表面產生高亮效果。鏡面反射光的入射角和反射角相等,入射方向和反射方向關於法向對稱(不考慮指向),如圖所示:
其中r是對應入射方向(-s)的反射方向,n是法向量,r可以通過下式計算得到:
和漫反射的情況不同,計算鏡面反射的光強還需要考慮觀察點的位置。即計算鏡面反射光強需要如下4個(單位)向量:反射點到光源的方向s;反射方向r;反射點到觀察點的方向v;法向量n。如下是各向量的示意圖:
結合生活經驗我們知道,當v和r方向重合時,反射光是最強的,而到v與r不重合,在夾角增大的過程中,反射光的光強會迅速衰減。該特點可以用v和r之間的餘弦值刻畫,但實際的衰減速度會比餘弦值更高,因此在餘弦值上增加了一個指數(
其中
將所有的反射光強相加,即得到了最終的光照模型:
在介紹完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