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 }
這樣其實就可以看到效果了,還可以通過屬性面板控制光源的參數。
但是手動輸入數值很不爽啊,沒有一點點隨心所欲的順暢。于是后續的折騰就開始了。讓我來畫一個真正的太陽,然后通過鼠標拖動太陽,實時更新光源的位置(方向),看到本文開始的效果。
太陽(光源)
畫太陽
其實太陽模型早就做過了,本質上就是利用一個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坐標變換)
總結
整完收工。
文章列表