文章出處

CSharpGL(39)GLSL光照示例:鼠標拖動太陽(光源)觀察平行光的漫反射和鏡面反射效果

開始

一圖抵千言。首先來看鼠標拖動太陽(光源)的情形。

然后是鼠標拖拽旋轉模型的情形。

然后我們移動攝像機來從不同的角度看看。

現在太陽(光源)跑到比較遠的位置去了,我們再移動它試試看。

本文就介紹平行光下是如何實現漫反射和鏡面反射的。

本文shader核心部分來自紅寶書第八版。

光照

只需記住一點,用GLSL實現光照效果時,都是根據頂點的位置、法線方向、光源位置(方向)、攝像機位置等等這些數據,根據物理學的反射規則計算出來的。當然,為了兼顧效果和效率,可能會對物理規則做一些簡化處理。

Vertex shader

先看vertex shader。除了傳遞頂點的位置、顏色外,這里新增了傳遞法線(normal)的代碼。頂點的變換可以通過mvp矩陣完成,而法線的變換則有所不同(說來話長,這里不要深究),所以單獨提供一個normalMatrix供法線使用。

 1 #version 330 core
 2 
 3 uniform mat4 mvpMatrix;
 4 uniform mat3 normalMatrix;// normal matrix is transpose(inverse(model matrix))
 5 
 6 in vec3 inPosition;
 7 in vec3 inColor;
 8 in vec3 inNormal;
 9 
10 out vec3 passNormal;
11 out vec3 passColor;
12 
13 void main()
14 {
15     passNormal = normalize(normalMatrix * inNormal);
16     passColor = inColor;
17     gl_Position = mvpMatrix * vec4(inPosition, 1.0);
18 }

Fragment shader

這里是計算光照的地方。詳情見注釋。

 1 #version 330 core
 2 
 3 uniform vec3 ambientLight;// 環境光
 4 uniform vec3 directionalLightColor;
 5 uniform vec3 directionalLightDirection;
 6 uniform vec3 halfVector;
 7 uniform float shininess;
 8 uniform float strength;
 9 
10 in vec3 passNormal;
11 in vec3 passColor;
12 
13 out vec4 outColor;
14 
15 void main()
16 {
17     // 根據光源方向與法線方向的夾角計算此處的漫反射的強度
18     float diffuse = max(0.0, dot(passNormal, directionalLightDirection));
19     // 計算此處的鏡面反射的強度
20     float specular = max(0.0, dot(passNormal, halfVector));
21     
22     if (diffuse == 0.0) { specular = 0.0; }// 若光源沒有照射到此處,自然也不應該有鏡面反射效果
23     else { specular = pow(specular, shininess); }// 指數式的劇烈變化,就是產生鏡面高光的原理
24     
25     vec3 scatteredLight = ambientLight + directionalLightColor * diffuse;// 漫反射光+環境光
26     vec3 reflectedLight = directionalLightColor * specular * strength;// 鏡面反射光
27 
28     vec3 rgb = min(passColor * scatteredLight + reflectedLight, vec3(1.0));// 最后的顏色
29     outColor = vec4(rgb, 1.0);// 搞定
30

渲染器

shader做好了,下面寫一個渲染器。關于渲染器的詳細介紹可參看(CSharpGL(34)以從零編寫一個KleinBottle渲染器為例學習如何使用CSharpGL)由于制作光照效果需要模型自帶法線值,而我手里的模型只有這個Teapot是有法線值的,又僅僅是個例子,就寫死了用Teapot了。

 1     class DirectonalLightRenderer : PickableRenderer
 2     {
 3         public vec3 AmbientLightColor { get; set; }
 4         public vec3 DirectionalLightDirection { get; set; }
 5         public vec3 DirectionalLightColor { get; set; }
 6         //public vec3 HalfVector { get; set; }
 7         public float Shininess { get; set; }
 8         public float Strength { get; set; }
 9 
10         public static DirectonalLightRenderer Create()
11         {
12             var model = new Teapot();
13             var shaderCodes = new ShaderCode[2];
14             shaderCodes[0] = new ShaderCode(File.ReadAllText(@"shaders\DirectionalLight.vert"), ShaderType.VertexShader);
15             shaderCodes[1] = new ShaderCode(File.ReadAllText(@"shaders\DirectionalLight.frag"), ShaderType.FragmentShader);
16             var map = new AttributeMap();
17             map.Add("inPosition", Teapot.strPosition);
18             map.Add("inColor", Teapot.strColor);
19             map.Add("inNormal", Teapot.strNormal);
20 
21             var renderer = new DirectonalLightRenderer(model, shaderCodes, map, Teapot.strPosition);
22             renderer.ModelSize = model.Size;
23             return renderer;
24         }
25 
26         private DirectonalLightRenderer(IBufferable model, ShaderCode[] shaderCodes,
27             AttributeMap attributeMap, string positionNameInIBufferable,
28             params GLState[] switches)
29             : base(model, shaderCodes, attributeMap, positionNameInIBufferable, switches)
30         {
31             this.AmbientLightColor = new vec3(0.2f);
32             this.DirectionalLightDirection = new vec3(1);
33             this.DirectionalLightColor = new vec3(1);
34             //this.HalfVector = new vec3(1);
35             this.Shininess = 10.0f;
36             this.Strength = 1.0f;
37         }
38 
39         protected override void DoRender(RenderEventArgs arg)
40         {
41             this.SetUniform("ambientLight", this.AmbientLightColor);
42             this.SetUniform("directionalLightColor", this.DirectionalLightColor);
43             this.SetUniform("directionalLightDirection", this.DirectionalLightDirection.normalize());
44             this.SetUniform("halfVector", this.DirectionalLightDirection.normalize());
45             //this.SetUniform("halfVector", this.HalfVector.normalize());
46             this.SetUniform("shininess", this.Shininess);
47             this.SetUniform("strength", this.Strength);
48 
49             mat4 projection = arg.Camera.GetProjectionMatrix();
50             mat4 view = arg.Camera.GetViewMatrix();
51             mat4 model = this.GetModelMatrix().Value;
52             this.SetUniform("mvpMatrix", projection * view * model);
53             this.SetUniform("normalMatrix", glm.transpose(glm.inverse(model)).to_mat3());
54 
55             base.DoRender(arg);
56         }
57     }
DirectonalLightRenderer

這樣其實就可以看到效果了,還可以通過屬性面板控制光源的參數。

但是手動輸入數值很不爽啊,沒有一點點隨心所欲的順暢。于是后續的折騰就開始了。讓我來畫一個真正的太陽,然后通過鼠標拖動太陽,實時更新光源的位置(方向),看到本文開始的效果。

太陽(光源)

畫太陽

其實太陽模型早就做過了,本質上就是利用一個noise方法模擬太陽表面的活動。外圍輻射效果什么的,我先拉倒吧。

拖動太陽

把太陽放在那里很容易,如何用鼠標移動呢?

原理在(CSharpGL(20)用unProject和Project實現鼠標拖拽圖元)已經整理出來了。只不過當時是單獨修改模型內部的頂點位置,而現在需要整體移動模型,即修改模型的(RendererBase.WorldPosition)屬性。

我的思路如下:假設有一個點在原點position = new vec3(0,0,0),我們像之前一樣計算它在平移之后的位置newPosition,這是模型本身的變化,然后只需分別通過RendererBase.GetModelMatrix()的變換,就變成了在World Space里的變化,這個差別就是模型的位移。代碼如下。

 1         void IMouseHandler.canvas_MouseMove(object sender, MouseEventArgs e)
 2         {
 3             if (mouseDownFlag && ((e.Button & this.lastBindingMouseButtons) != MouseButtons.None))
 4             {
 5                 Point location = new Point(e.X, this.canvas.ClientRectangle.Height - e.Y - 1);
 6                 Point differenceOnScreen = new Point(location.X - this._lastPosition.X, location.Y - this._lastPosition.Y);
 7                 mat4 model = this.renderer.GetModelMatrix().Value;
 8                 mat4 view = this.camera.GetViewMatrix();
 9                 mat4 projection = this.camera.GetProjectionMatrix();
10                 vec4 viewport;
11                 {
12                     int[] result = OpenGL.GetViewport();
13                     viewport = new vec4(result[0], result[1], result[2], result[3]);
14                 }
15                 var position = new vec3(0.0f);// imangine we have a point at (0, 0, 0).
16                 vec3 windowPos = glm.project(position, view * model, projection, viewport);
17                 var newWindowPos = new vec3(windowPos.x + differenceOnScreen.X, windowPos.y + differenceOnScreen.Y, windowPos.z);
18                 vec3 newPosition = glm.unProject(newWindowPos, view * model, projection, viewport);
19                 var worldPosition = new vec3(model * new vec4(position, 1.0f));
20                 var newWorldPosition = new vec3(model * new vec4(newPosition, 1.0f));
21                 this.renderer.WorldPosition += newWorldPosition - worldPosition;
22 
23                 this._lastPosition = location;
24             }
25         }

這樣說似乎也不能徹底解釋清楚,因為還需要先理解OpenGL里坐標變換的問題,這個問題可以參看(CSharpGL(27)講講清楚OpenGL坐標變換

總結

整完收工。

 


文章列表


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

    IT工程師數位筆記本

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