Per Pixel Lighting 逐畫素光照

2021-06-07 08:33:14 字數 3681 閱讀 6165

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拆分,以確定哪些是需要逐畫素操作的。首先看看每個頂點接收到的資訊 法線 半向量 光源方向 我們需要將法線變換到視點空間然後歸一化。...