文章出處

CSharpGL(13)用GLSL實現點光源(point light)和平行光源(directional light)的漫反射(diffuse reflection)

 

2016-08-13

 

由于CSharpGL一直在更新,現在這個教程已經不適用最新的代碼了。CSharpGL源碼中包含10多個獨立的Demo,更適合入門參考。

 

為了盡可能提升渲染效率,CSharpGL是面向Shader的,因此稍有難度。

 

 

 

光源

如何用GLSL實現點光源和平行光源等各類光源的效果?這個問題我查找資料、思考了很久,今天終于解決了一部分。

漫反射(diffuse reflection)是粗糙表面的反射效果。理論上的粗糙表面,對各個方向的反射效果都完全相同。本篇就分別實現點光源(point light)和平行光源(directional light)照射到粗糙表面時產生的漫反射效果。

下載

這個示例是CSharpGL的一部分,CSharpGL已在GitHub開源,歡迎對OpenGL有興趣的同學加入(https://github.com/bitzhuwei/CSharpGL

點光源(point light)

講GLSL當然要從shader開始說起。

Vertex shader

 1 #version 150 core
 2 
 3 in vec3 in_Position;
 4 in vec3 in_Normal;
 5 out vec4 pass_Position;
 6 out vec4 pass_Color;
 7 uniform mat4 modelMatrix;
 8 uniform mat4 viewMatrix;
 9 uniform mat4 projectionMatrix;
10 uniform vec3 lightPosition;
11 uniform vec3 lightColor;
12 uniform vec3 globalAmbient;
13 uniform float Kd;
14 
15 void main(void)
16 {
17     gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(in_Position, 1.0f);
18     vec3 worldPos = (viewMatrix * modelMatrix * vec4(in_Position, 1.0f)).xyz;
19     vec3 N = (transpose(inverse(viewMatrix * modelMatrix)) * vec4(in_Normal, 1.0f)).xyz;
20     N = normalize(N);
21     // light's direction
22     vec3 L = (viewMatrix * vec4(lightPosition, 1.0f)).xyz - worldPos;// point light
23     L = normalize(L);
24     // diffuse color from directional light
25     vec3 diffuseColor = Kd * lightColor * max(dot(N, L), 0);
26     // ambient color
27     vec3 ambientColor = Kd * globalAmbient;
28     pass_Color.xyz = diffuseColor + ambientColor;
29     pass_Color.w = 1;
30 }

首先, gl_Position 的計算是不用解釋了。

要計算漫反射下的點光源照射物體的效果,需要知道物體的頂點的法線(normal)、頂點位置到光源的方向(light direction),兩者夾角越小,那么光照越強。 max(dot(N, L), 0) 就是在計算光照強度。

為了避免全黑,我們加個環境光(ambient light)。環境光,就是那些綜合起來的光照因素,不好準確計算,就簡單地用一個 vec3 globalAmbient; 來描述了。

重要知識點

MVP的含義(projection * view * model)

那三個矩陣變換里,每個都有各自的意義。其中,modelMatrix是在物體坐標系旋轉縮放平移模型。就好比父母在家里打扮自己的寶寶。ViewMatrix是把物體放到世界坐標系下,讓攝像機為原點(0,0,0),來觀察模型。就好比把各家各戶的寶寶放到幼兒園拍合影。ProjectionMatrix就是寶寶們的合影照片。

所以, (viewMatrix * modelMatrix * vec4(in_Position, 1.0f)).xyz 就是物體的頂點在世界坐標系的位置。也就是在場景中的位置。(不是在3dmax里的位置)

法線(normal)

但是,不能以此類推世界坐標系里的法線(normal)。法線從3dmax中的值變換到場景中后,它的值應該是 (transpose(inverse(viewMatrix * modelMatrix)) * vec4(in_Normal, 1.0f)).xyz (就是要求逆然后轉置),而不是 (viewMatrix * modelMatrix * vec4(in_Normal, 1.0f)).xyz 。具體原因可參考這里(http://blog.csdn.net/racehorse/article/details/6664775)。或者

Normals are funny.  They're vec3's, since you don't want perspective on normals.   And they don't actually scale quite right--a 45 degree surface with a 45 degree normal, scaled by glScalef(1,0.1,1), drops the surface down to near 0 degrees, but actually tilts the normal *up*, in the opposite direction from the surface, to near 90 degrees.

Mathematically, if between two points a and b on the surface, dot(n,b-a)==0, then after applying a matrix M to the points, you want the normal to still be perpendicular.  The question is, what matrix N do you have to apply to the normal to make this happen?  In other words, find N such that
    dot( N * n , M * a - M * b) == 0

We can solve this by noting that dot product can be expresed as matrix multiplication--dot(x,y) = transpose(x) * y, where we treat an ordinary column-vector as a little matrix, and flip it horizontally.  So
   transpose(N * n) * (M*a - M*b) == 0         (as above, but write using transpose and matrix multiplication)
   transpose(N * n) * M * (a-b) == 0              (collect both copies of M)
   transpose(n) * transpose(N) * M * (a-b) == 0    (transpose-of-product is product-of-transposes in opposite order)

OK.  This is really similar to our assumption that the original normal was perpendicular to the surface--that dot(n,b-a) == transpose(n) * (a-b) == 0.  In fact, the only difference is the new matrices wedged in the middle.  If we pick N to make the term in the middle the identity, then our new normal will be perpendicular to the surface too:
    transpose(N) * M == I   (the identity matrix)
This is the definition for matrix inverses, so the "normal matrix" N = transpose(inverse(M)).

If you look up the GLSL definition for "gl_NormalMatrix", it's defined as "the transpose of the inverse of the gl_ModelViewMatrix".  Now you know why!
normal

 

光源的位置

由于模型受到viewMatrix的影響,所以攝像機的改變也會改變模型的頂點位置,而點光源的位置如果不變,就會出現不同的光照結果。這不合實際。所以點光源也要按viewMatrix做變換。

Fragment shader

極其簡單。

1 #version 150 core
2 
3 in vec4 pass_Color;
4 out vec4 out_Color;
5 
6 void main(void)
7 {
8     out_Color = pass_Color;
9 }

 

平行光源(directional light)

評選光源與之類似。至于為何在計算平行光源的方向時要用 (transpose(inverse(viewMatrix * modelMatrix)) * vec4(in_Normal, 1.0f)).xyz 這種復雜的步驟,原理在上文的法線(normal)中科院找到。(提示:都是為了讓攝像機對模型和對光源方向產生同樣的變換,從而使得攝像機的移動不會改變光照效果。)

 1 #version 150 core
 2 
 3 in vec3 in_Position;
 4 in vec3 in_Normal;
 5 out vec4 pass_Position;
 6 out vec4 pass_Color;
 7 uniform mat4 modelMatrix;
 8 uniform mat4 viewMatrix;
 9 uniform mat4 projectionMatrix;
10 uniform vec3 lightPosition;
11 uniform vec3 lightColor;
12 uniform vec3 globalAmbient;
13 uniform float Kd;
14 
15 void main(void)
16 {
17     gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(in_Position, 1.0f);
18     vec3 worldPos = (viewMatrix * modelMatrix * vec4(in_Position, 1.0f)).xyz;
19     vec3 N = (transpose(inverse(viewMatrix * modelMatrix)) * vec4(in_Normal, 1.0f)).xyz;
20     N = normalize(N);
21     // light's direction
22     vec3 L = (transpose(inverse(viewMatrix)) * vec4(lightPosition, 1.0f)).xyz;// directional light
23     L = normalize(L);
24     // diffuse color from directional light
25     vec3 diffuseColor = Kd * lightColor * max(dot(N, L), 0);
26     // ambient color
27     vec3 ambientColor = Kd * globalAmbient;
28     pass_Color.xyz = diffuseColor + ambientColor;
29     pass_Color.w = 1;
30 }

Fragment shader與上文的相同。

試驗

在一個場景中,一個點光源照射到物體上。如果物體旋轉,那么光照效果會改變。如果光源位置改變,那么光照效果會改變。但是,如果攝像機改變位置,卻不會改變漫反射的效果。(漫反射,反射到各個角度的光效是相同的,所以與攝像機位置無關。)

您可以下載此示例(https://github.com/bitzhuwei/CSharpGL)試驗,鼠標左鍵旋轉模型,光效會改變。鼠標右鍵旋轉攝像機,光效是不變的。

 

總結

今后將繼續整合一些鏡面反射等類型的光照效果,為更多更強的shader效果做準備。

不得不說線性代數在計算機3D效果方面的應用徹底證明了它的強大。

學OpenGL有2年了,從NEHE到SharpGL,從《3D Math Primer for Graphics and Game Development》到《OpenGL Programming Guide》,算是對OpenGL有了初級的認識。最近我糾集整理了SharpGL,GLM,SharpFont等開源庫,想做一個更好用的純C#版OpenGL。歡迎對OpenGL有興趣的同學加入(https://github.com/bitzhuwei/CSharpGL


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()