【GLSL教程】(六)逐頂點的光照

vampirem發表於2013-09-17
引言
在OpenGL中有三種型別的光:方向光(directional)、點光(point)、聚光(spotlight)。本教程將從方向光講起,首先我們將使用GLSL來模仿OpenGL中的光。
我們將向shader中逐漸新增環境光、散射光和高光效果。

後面的教程中我們將使用逐畫素光照以獲得更好的效果。

接下來我們將實現逐畫素的點光和聚光。這些內容與方向光很相近,大部分程式碼都是通用的。

在卡通著色的教程中我們接觸過在GLSL中如何訪問OpenGL狀態中關於光源的部分,這些資料描述了每個光源的引數。
  1. struct gl_LightSourceParameters  
  2. {  
  3.     vec4 ambient;  
  4.     vec4 diffuse;  
  5.     vec4 specular;  
  6.     vec4 position;  
  7.     vec4 halfVector;  
  8.     vec3 spotDirection;  
  9.     float spotExponent;  
  10.     float spotCutoff; // (range: [0.0,90.0], 180.0)   
  11.     float spotCosCutoff; // (range: [1.0,0.0],-1.0)   
  12.     float constantAttenuation;  
  13.     float linearAttenuation;  
  14.     float quadraticAttenuation;  
  15. };  
  16.   
  17. uniform gl_LightSourceParameters gl_LightSource[gl_MaxLights];  
  18. struct gl_LightModelParameters  
  19. {  
  20.     vec4 ambient;  
  21. };  
  22. uniform gl_LightModelParameters gl_LightModel;  
struct gl_LightSourceParameters
{
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec4 position;
    vec4 halfVector;
    vec3 spotDirection;
    float spotExponent;
    float spotCutoff; // (range: [0.0,90.0], 180.0)
    float spotCosCutoff; // (range: [1.0,0.0],-1.0)
    float constantAttenuation;
    float linearAttenuation;
    float quadraticAttenuation;
};

uniform gl_LightSourceParameters gl_LightSource[gl_MaxLights];
struct gl_LightModelParameters
{
    vec4 ambient;
};
uniform gl_LightModelParameters gl_LightModel;
在GLSL中也同樣可以訪問材質引數:
  1. struct gl_MaterialParameters  
  2. {  
  3.     vec4 emission;  
  4.     vec4 ambient;  
  5.     vec4 diffuse;  
  6.     vec4 specular;  
  7.     float shininess;  
  8. };  
  9.   
  10. uniform gl_MaterialParameters gl_FrontMaterial;  
  11. uniform gl_MaterialParameters gl_BackMaterial;  
struct gl_MaterialParameters
{
    vec4 emission;
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    float shininess;
};

uniform gl_MaterialParameters gl_FrontMaterial;
uniform gl_MaterialParameters gl_BackMaterial;
在OpenGL程式中,這些引數中的大部分,不論屬於光源還是材質,用起來都是相似的。我們將使用這些引數實現自己的方向光。

方向光I
本節的公式來自《OpenGL程式設計指南》中“和光照有關的數學知識”這一章。
我們從散射光開始討論。在OpenGL中假定,不管觀察者的角度如何,得到的散射光強度總是相同的。散射光的強度與光源中散射光成分以及材質中散射光反射係數相關,此外也和入射光角度與物體表面法線的夾角相關。

OpenGL用下面的公式計算散射光成分:

I是反射光的強度,Ld是光源的散射成分(gl_LightSource[0].diffuse),Md是材質的散射係數(gl_FrontMaterial.diffuse)。
這個公式就是Lambert漫反射模型。Lambert餘弦定律描述了平面散射光的亮度,正比於平面法線與入射光線夾角的餘弦,這一理論提出已經超過200年了。
在頂點shader中要實現這個公式,需要用到光源引數中的方向、散射成分強度,還要用到材質中的散射成分值。因此使用此shader時,在OpenGL中需要像在平時一樣設定好光源。注意:由於沒有使用固定功能流水線,所以不需要對光源呼叫glEnable。
要計算餘弦值,首先要確保光線方向向量(gl_LightSource[0].position)與法線向量都是歸一化的,然後就可以使用點積得到餘弦值。注意:對方向光,OpenGL中儲存的方向是從頂點指向光源,與上面圖中畫的相反。
OpenGL將光源的方向儲存在視點空間座標系內,因此我們需要把法線也變換到視點空間。完成這個變換可以用預先定義的一致變數gl_NormalMatrix。這個矩陣是模型檢視變換矩陣的左上3×3子矩陣的逆矩陣的轉置。
以下就是上述內容的頂點shader程式碼:
  1. void main()  
  2. {  
  3.     vec3 normal, lightDir;  
  4.     vec4 diffuse;  
  5.     float NdotL;  
  6.   
  7.     /* first transform the normal into eye space and normalize the result */  
  8.     normal = normalize(gl_NormalMatrix * gl_Normal);  
  9.     /* now normalize the light's direction. Note that according to the 
  10.     OpenGL specification, the light is stored in eye space. Also since 
  11.     we're talking about a directional light, the position field is actually 
  12.     direction */  
  13.     lightDir = normalize(vec3(gl_LightSource[0].position));  
  14.     /* compute the cos of the angle between the normal and lights direction. 
  15.     The light is directional so the direction is constant for every vertex. 
  16.     Since these two are normalized the cosine is the dot product. We also 
  17.     need to clamp the result to the [0,1] range. */  
  18.     NdotL = max(dot(normal, lightDir), 0.0);  
  19.     /* Compute the diffuse term */  
  20.     diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;  
  21.     gl_FrontColor =  NdotL * diffuse;  
  22.   
  23.     gl_Position = ftransform();  
  24. }  
void main()
{
    vec3 normal, lightDir;
    vec4 diffuse;
    float NdotL;

    /* first transform the normal into eye space and normalize the result */
    normal = normalize(gl_NormalMatrix * gl_Normal);
    /* now normalize the light's direction. Note that according to the
    OpenGL specification, the light is stored in eye space. Also since
    we're talking about a directional light, the position field is actually
    direction */
    lightDir = normalize(vec3(gl_LightSource[0].position));
    /* compute the cos of the angle between the normal and lights direction.
    The light is directional so the direction is constant for every vertex.
    Since these two are normalized the cosine is the dot product. We also
    need to clamp the result to the [0,1] range. */
    NdotL = max(dot(normal, lightDir), 0.0);
    /* Compute the diffuse term */
    diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
    gl_FrontColor =  NdotL * diffuse;

    gl_Position = ftransform();
}
在片斷shader中要做的就是使用易變變數gl_Color設定顏色。
  1. void main()  
  2. {  
  3.     gl_FragColor = gl_Color;  
  4. }  
void main()
{
    gl_FragColor = gl_Color;
}
下圖顯示了應用此shader的茶壺效果。注意茶壺的底部非常黑,這是因為還沒有使用環境光的緣故。

加入環境光非常容易,只需要使用一個全域性的環境光引數以及光源的環境光引數即可,公式如下所示:

前面的頂點shader中需要加入幾條語句完成環境光的計算:
  1. void main()  
  2. {  
  3.     vec3 normal, lightDir;  
  4.     vec4 diffuse, ambient, globalAmbient;  
  5.     float NdotL;  
  6.   
  7.     normal = normalize(gl_NormalMatrix * gl_Normal);  
  8.     lightDir = normalize(vec3(gl_LightSource[0].position));  
  9.     NdotL = max(dot(normal, lightDir), 0.0);  
  10.     diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;  
  11.     /* Compute the ambient and globalAmbient terms */  
  12.     ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;  
  13.     globalAmbient = gl_FrontMaterial.ambient * gl_LightModel.ambient;  
  14.     gl_FrontColor =  NdotL * diffuse + globalAmbient + ambient;  
  15.   
  16.     gl_Position = ftransform();  
  17. }  
void main()
{
    vec3 normal, lightDir;
    vec4 diffuse, ambient, globalAmbient;
    float NdotL;

    normal = normalize(gl_NormalMatrix * gl_Normal);
    lightDir = normalize(vec3(gl_LightSource[0].position));
    NdotL = max(dot(normal, lightDir), 0.0);
    diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
    /* Compute the ambient and globalAmbient terms */
    ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
    globalAmbient = gl_FrontMaterial.ambient * gl_LightModel.ambient;
    gl_FrontColor =  NdotL * diffuse + globalAmbient + ambient;

    gl_Position = ftransform();
}
下圖顯示了最終效果。加入環境光後整個畫面都變亮了,不過相對於應用了反射光效果的全域性光照模型(global illumination model),這種計算環境光的方式只能算廉價的解決方案。


方向光II
下面介紹OpenGL方向光中的鏡面反射部分。我們使用稱為Blin-Phong模型的光照模型,這是Phong模型的簡化版。在這之前,我們有必要先看看Phong模型,以便於更好地理解Blin-Phong模型。
在Phong模型中,鏡面反射成分和反射光線與視線夾角的餘弦相關,如下圖:

L表示入射光,N表示法線,Eye表示從頂點指向觀察點的視線,R是L經鏡面反射後的結果,鏡面反射成分與α角的餘弦相關。
如果視線正好和反射光重合,我們將接收到最大的反射強度。當視線與反射光相分離時,反射強度將隨之下降,下降速率可以由一個稱為shininess的因子控制,shininess的值越大,下降速率越快。也就是說,shininess越大的話,鏡面反射產生的亮點就越小。在OpenGL中這個值的範圍是0到128。

計算反射光向量的公式:

OpenGL中使用Phong模型計算鏡面反射成分的公式:

式中指數s就是shininess因子,Ls是光源中鏡面反射強度,Ms是材質中的鏡面反射係數。
Blinn提出了一種簡化的模型,也就是Blinn-Phong模型。它基於半向量(half-vector),也就是方向處在觀察向量以及光線向量之間的一個向量:

現在可以利用半向量和法線之間夾角的餘弦來計算鏡面反射成分。OpenGL所使用的Blinn-Phong模型計算鏡面反射的公式如下:

這個方法與顯示卡的固定流水線中使用的方法相同。因為我們要模擬OpenGL中的方向光,所以在shader中也使用此公式。幸運的是:OpenGL會幫我們算半向量,我們只需要使用下面的程式碼:
  1. /* compute the specular term if NdotL is  larger than zero */  
  2. if (NdotL > 0.0)  
  3. {  
  4.     // normalize the half-vector, and then compute the   
  5.     // cosine (dot product) with the normal   
  6.     NdotHV = max(dot(normal, gl_LightSource[0].halfVector.xyz),0.0);  
  7.     specular = gl_FrontMaterial.specular * gl_LightSource[0].specular *  
  8.             pow(NdotHV,gl_FrontMaterial.shininess);  
  9. }  
/* compute the specular term if NdotL is  larger than zero */
if (NdotL > 0.0)
{
    // normalize the half-vector, and then compute the
    // cosine (dot product) with the normal
    NdotHV = max(dot(normal, gl_LightSource[0].halfVector.xyz),0.0);
    specular = gl_FrontMaterial.specular * gl_LightSource[0].specular *
            pow(NdotHV,gl_FrontMaterial.shininess);
}
完整的Shader Designer工程下載:
http://lighthouse3d.com/wptest/wp-content/uploads/2011/03/ogldirsd.zip

相關文章