shader快速複習,鞏固知識加強感覺。今天的內容是per pixel lighting(逐畫素光照)。——zwqxin.com
拋開光線跟蹤和輻射度演算法,現在的實時渲染主要用的是gouraud模型和phong模型,粗俗地說,就是乙個是頂點級別的乙個是畫素級別的,對應per vertex lighting和per pixel lighting。細節點說,就是乙個是對頂點插值,乙個是對法線插值。具體的區別網路上到處可見辨析。我真正「接觸」per pixel lighting,是在初學glsl的時候。
本文**於 zwqxin (
在shader中,我們需要自己計算光照。這最初覺得應該算是麻煩活了,要知道,固定管道中只是簡單的api啊,可是那畢竟只是per vertex lighting,在shader裡寫光照模型最好的就是可以方便地實現per pixel lighting。例如我的shadow map demo就用了,不過只計算散射光而已。
如上所言,per pixel lighting最重要的是法線插值這步,接下來得十分留意場景傳入的法線在shader裡的「流向」;同時不得不「提防」的還有半向量halfvector的「流向」。
經典的opengl光照模型是如下這樣的:
但是這裡先別理會自發光emission,不理會全域性環境光,也不理會光的衰減等等。關注最重心的phong模型,有:
pixel color = ambient_color + diffuse_color + specular_color
頂點shader要做的僅僅是把「力所能及」的東西做好。計算正確的光源向量,法線向量和半向量。半向量是「光向量與視線向量的『半』」,等會再多解釋。
uniform vec3 lightpos;
uniform vec4 eyepos;
varying vec3 lightdir;
varying vec3 halfvec;
varying vec3 norm;
void
main(
void)
在畫素shader裡,首先是把從頂點shader插值而來的向量重新單位化。原因是插值過程中這些向量會把「大小」也插值了,而我們必須保證這些向量的單位化,從而保證不破壞光照模型。光源向量也可以不重新插值(如果它是方向光而不是點光源的話——取決於應用)。
lightdir = normalize(lightdir);
norm = normalize(norm);
halfvec = normalize(halfvec);
三種光的公式:
其中,如果能保證各向量的正確單位化的話,有:
cos(θ) = dot(light_vec , normal_vec) ;
cos(α) = dot(reflect_vec , eye_vec) ;
reflect_vec的求解比較麻煩(主要是reflect這個函式有點耗gpu了),因此按blin模型,可以這樣:
dot(reflect_vec , eye_vec) = dot(halfvec , normal_vec)
這裡的半向量 halfvec = eyevec - lightvec(input),注意圖中的都是入射光向量lightvec(input) (= gl_vertex - lightpos) ,但我們shader裡是用lightvec(= lightpos - gl_vertex) ,因此halfvec的計算應該是 halfvec = eyevec + lightvec,即vertex shader裡那樣。
簡化一下,我shader裡就預先把材質顏色和光源顏色合在一起傳入,即ambient, diffuse, specular。另外設定三種光所佔最終顏色的百分比(這個重要,因為即使是最終顏色,也不過是個0.0~1.0的顏色值,三種光的結果各自就是個0.0~1.0的顏色值,它們要求直接加合的話肯定得超過1.0,因此需要給予權值再加~)。
uniform vec4 ambient, diffuse, specular;
float
amb = 0.3;
float
diff = 0.4;
float
spec = 0.3;
最後就是這樣了:
float
diffusefract = max( dot(lightdir,norm) , 0.0);
float
specularfract = max( dot(halfvec,norm) , 0.0);
specularfract = pow(specularfract, shiness);
gl_fragcolor = vec4(amb*ambient.xyz + diff * diffuse.xyz * diffusefract
+ spec * specular.xyz * specularfract ,1.0);
恩.....考慮到cos(θ) 、cos(α)小於0的時候說明該物體部分不接受光照,直接取0.0(不產生光照顏色)就行。啊,對了,既然該部分不接受光照,那該部分何必還繼續半向量呀取指數呀的計算?浪費。於是,最後的畫素shader如下:
uniform
float
shiness;
uniform vec4 ambient, diffuse, specular;
float
amb = 0.3;
float
diff = 0.4;
float
spec = 0.3;
varying vec3 lightdir;
varying vec3 halfvec;
varying vec3 norm;
void
main(
void
)gl_fragcolor = vec4(amb*ambient.xyz
+ diff * diffuse.xyz * diffusefract
+ spec * specular.xyz * specularfract ,1.0);}
效果檢視(rendermonkey):
(改變模型,顏色,增加鏡面光權值,減小shiness後:)
如果打算直接從opengl應用中取值,那注意下面這個內建的gl_lightsourceparameters結構,把相應的變數替換就可以了,例如上面的ambient, diffuse, specular等等,可以不用,而以gl_lightsource[0].ambient*gl_frontmaterial].ambient表示; 另外halfvector甚至不用自己算。但這樣做之前可記得在opengl實現裡要指定gllight***這類函式喔。
獲取應用中設定的光源特性,gl_lightsource[i]對應第i號光源
struct gl_lightsourceparameters ;
uniform gl_lightsourceparameters gl_lightsource[gl_maxlights];
獲取應用中設定的全域性環境光
struct gl_lightmodelparameters ;
uniform gl_lightmodelparameters gl_lightmodel;
獲取應用中設定的材質
struct gl_materialparameters ;
uniform gl_materialparameters gl_frontmaterial;
uniform gl_materialparameters gl_backmaterial;
XNA Shader 逐頂點和逐畫素光照
所謂逐頂點光照,簡單地說就是在vetext shader中計算光照顏色,該過程將為每個頂點計算一次光照顏色,然後在通過頂點在多邊形所覆蓋的區域對畫素顏色進行線形插值。現實中,光照值取決於光線角度,表面法線,和觀察點 對於鏡面高光來說 具體實現時的shader 如下 相關全域性變數 shared fl...
GLSL 逐畫素的光照
逐畫素的方向光 directional light per pixel 這一節將把前面的shader 改為逐畫素計算的方向光。我們需要將工作按照兩個shader拆分,以確定哪些是需要逐畫素操作的。首先看看每個頂點接收到的資訊 法線 半向量 光源方向 我們需要將法線變換到視點空間然後歸一化。我們還需要...
GLSL教程 (七)逐畫素的光照
from 逐畫素的方向光 directional light per pixel 這一節將把前面的shader 改為逐畫素計算的方向光。我們需要將工作按照兩個shader拆分,以確定哪些是需要逐畫素操作的。首先看看每個頂點接收到的資訊 法線 半向量 光源方向 我們需要將法線變換到視點空間然後歸一化。...