XNA 4.0 Phong Shading 實作 shader implement
接下來就是進行實作的部分,時做的部分這邊我只說明實作可能會想問的問題,詳細內容各位可以下載原始的程式碼。
(本範例程式碼,包含使用基本的basicEffect特效將模型顯示)
模型的部分,這邊我是使用OBJ檔案格式,在XNA Creater club上面有一個範例,裡面是使用自定的content pipeline來將obj檔案資訊分析出來,這邊我們就直接運用它即可。
在renderer.cs的程式碼中,裡面有一個函數use_BasicEffect程式碼如下
1: public void use_BasicEffect()
2: {
3: for (int i = 0; i < scene_model.Length; i++)
4: {
5: foreach (ModelMesh mesh in scene_model[i].Meshes)
6: {
7: foreach (BasicEffect effect in mesh.Effects)
8: {
9: effect.EnableDefaultLighting();
10: effect.World = Matrix.Identity;
11: effect.View = camera.View;
12: effect.Projection = camera.Projection;
13: }
14: mesh.Draw();
15: }
16: }
17: }
上面程式碼有使用camera.cs這些相關的資源可以在右邊的地方可以找到。(component.rar)
首先,我們先來看shader code的部分,你可以在content的資料夾按下右鍵"增加新的項目",會出現下圖的圖式
選擇Effect file,然後給定一個你所指定的名稱。
確定以後,他會幫你自動產生一個可以執行的shader code如下所示
1: float4x4 World;
2: float4x4 View;
3: float4x4 Projection;
4:
5: // TODO: add effect parameters here.
6:
7: struct VertexShaderInput
8: {
9: float4 Position : POSITION0;
10:
11: // TODO: add input channels such as texture
12: // coordinates and vertex colors here.
13: };
14:
15: struct VertexShaderOutput
16: {
17: float4 Position : POSITION0;
18:
19: // TODO: add vertex shader outputs such as colors and texture
20: // coordinates here. These values will automatically be interpolated
21: // over the triangle, and provided as input to your pixel shader.
22: };
23:
24: VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
25: {
26: VertexShaderOutput output;
27:
28: float4 worldPosition = mul(input.Position, World);
29: float4 viewPosition = mul(worldPosition, View);
30: output.Position = mul(viewPosition, Projection);
31:
32: // TODO: add your vertex shader code here.
33:
34: return output;
35: }
36:
37: float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
38: {
39: // TODO: add your pixel shader code here.
40:
41: return float4(1, 0, 0, 1);
42: }
43:
44: technique Technique1
45: {
46: pass Pass1
47: {
48: // TODO: set renderstates here.
49:
50: VertexShader = compile vs_2_0 VertexShaderFunction();
51: PixelShader = compile ps_2_0 PixelShaderFunction();
52: }
53: }
float4x4的型態代表的是一個 4 by 4的陣列,也就是我們在XNA中用的matrix。這邊宣告了三個,分別是World、View、Projection。代表的意義如下:
World: 世界矩陣,用來做縮放平移旋轉等操作,另一個簡單的說法是將模型從自己的空間(object space)轉換到世界座標(world coordinate)。通常我們會在XNA的code中使用Matrix.Identity。不過可以根據情況做模型的縮放平移等操作。
View: 這個矩陣主要做的事情就是,將觀察者的位置(view position)轉換到原點。
Projection: 將投影的大小調整到[-1, 1]之間。
接下來是Struct的部分,這邊的語法都跟C很像,所以懂C的話應該不會太困難看懂才是
struct VertexShaderInput
{
float4 Position : POSITION0;
// TODO: add input channels such as texture
// coordinates and vertex colors here.
};
這邊是指你宣告了一個結構VertexShaderInput,然後這個結構中包含了一個float4的資訊,而它所指定的型態就是POSITION。float4是變數型態,Position是指變數名稱,分號後面指的是語意(給HLSL知道說現在是甚麼東西需要處理)。語意的部分有蠻多項目的,詳情可以參考MSDN,不過常用的通常是POSITION、NORMAL、TEXCOORD。
函數的部分跟C很像結構通常是:
傳回的變數型態 函數名稱(傳入的變數型態 變數名稱)
{
}
最後面的一個看起來有點怪的東西,這邊需要特別說明
technique Technique1
{
pass Pass1
{
// TODO: set renderstates here.
VertexShader = compile vs_2_0 VertexShaderFunction();
PixelShader = compile ps_2_0 PixelShaderFunction();
}
}
technique指的是說現在你在XNA,如果你要指定使用哪個技術。
像是effect.Technique["Technique1"];
這樣就是說我現在要使用Technique1的shader技術,然後在Technique1裡面,又可能包含很多的Pass,所以我們也可以指定說我們現在要用哪個pass。EX: effect.Technique["Technique"].Pass["Pass1"].Apply();
在pass1中裡面有兩行,有兩個關鍵字VertexShader和PixelShader這兩個名稱是不可以變動的!!分別是說現在編譯的shader code各自的函數是分別送給VertexShader和PixelShader處理。
後面的compile的部分,就是說現在是使用第幾版的shader model,目前最新的shader model是到5.0,不過XNA只能支援到3.a所以這部分大家要稍微注意一下。
簡介完上面的shader code以後,我們現在要寫phong shading話,我們先看看phong shading的計算公式,如下:
由上面公式知道,我們需要傳入光的位置、攝影機的位置,和模型的材質資訊等等。至於頂點和normal這些我們可以讓pipeline幫我們送入就好了。
所以我們在shader code的上方需要加入以下的程式碼:
// TODO: add effect parameters here.
float3 light_position;
float3 light_diffuse;
float3 light_specular;
float4 kd;
float3 ks;
float ns;
float3 camera_position;
然後,在VertexShader的input中,我們需要將結構稍微做些調整,因為我們需要把normal送進來。
struct VertexShaderInput
{
float4 Position : POSITION0;
// TODO: add input channels such as texture
// coordinates and vertex colors here.
float3 Normal : NORMAL0;
};
在output的地方也要調整,至於output過後,接下來就是交給pipeline去做內插了,而我們希望頂點跟法向量都可以被內差。所以輸出的vertex shader結構也要做些調整。
struct VertexShaderOutput
{
float4 Position : POSITION0;
// TODO: add vertex shader outputs such as colors and texture
// coordinates here. These values will automatically be interpolated
// over the triangle, and provided as input to your pixel shader.
float4 Pos : TEXCOORD0;
float3 Nor : TEXCOORD1;
};
這邊輸出的部分語意是使用TEXCOORD而非使用COLOR,因為用COLOR內差會有問題,然後後面的數字分別是說輸出的channel,記得數字不可以一樣!
接下來,在pixel shader部分就是進行phong lighting的計算。
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
// TODO: add your pixel shader code here.
float3 value = 0;
float3 Light_Direction = normalize(light_position - input.Pos);
float NdotL = saturate(dot(normalize(input.Nor), Light_Direction));
if(NdotL>0)
{
value += light_diffuse*kd*NdotL;
float3 E = normalize(camera_position - input.Pos);
float3 R = reflect(-Light_Direction, normalize(input.Nor));
// blinn
float3 H = normalize(Light_Direction+E);
float specular = pow(saturate(dot(E,normalize(R))),ns);
// blinn
//float specular = pow(saturate(dot(input.Nor,H)),ns);
value+= light_specular*ks*specular;
}
return float4(value, 1);
}
這邊程式碼我就不多做說明,因為主要就是計算上述所說的公式…
可能需要注意的就是這邊所指的向量方向,這邊所說的向量方向,全部都是"往外"發射的向量,像是E這個向量來說,就是"眼睛-頂點做標"。
接下來,在XNA的程式碼我們就需要一個Effect的特效變數,並且用content pipeline將他載入近來。
完成以後,在繪製模型的地方我們就希望能夠套入Phong shading的效果。
我們需要先傳入一些變數到shader去
1: phong_effect.Parameters["World"].SetValue(Matrix.Identity);
2: phong_effect.Parameters["View"].SetValue(camera.View);
3: phong_effect.Parameters["Projection"].SetValue(camera.Projection);
4: phong_effect.Parameters["camera_position"].SetValue(camera.Position);
5: phong_effect.Parameters["light_position"].SetValue(light_position);
6: phong_effect.Parameters["light_diffuse"].SetValue(light_diffuse);
7: phong_effect.Parameters["light_specular"].SetValue(light_specular);
Parameters裡面的""變數名稱,必須跟Shader code是相互對應的!
像是KD KS NS這些變數,我們必須要從模型裡面獲得才行,所以我們在繪製模型的時候在抓取資訊。
1: foreach (Model model in scene_model)
2: {
3: foreach (ModelMesh mesh in model.Meshes)
4: {
5: foreach (ModelMeshPart meshPart in mesh.MeshParts)
6: {
7: phong_effect.Parameters["kd"].SetValue(meshPart.Effect.Parameters["DiffuseColor"].GetValueVector4());
8: phong_effect.Parameters["ks"].SetValue(meshPart.Effect.Parameters["SpecularColor"].GetValueVector3());
9: phong_effect.Parameters["ns"].SetValue(meshPart.Effect.Parameters["SpecularPower"].GetValueSingle());
10:
11: phong_effect.Techniques[0].Passes[0].Apply();
12: GraphicsDevice.Indices = meshPart.IndexBuffer;
13: GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer);
14: GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList,meshPart.VertexOffset, 0, meshPart.NumVertices, meshPart.StartIndex, meshPart.PrimitiveCount);
15: }
16: mesh.Draw();
17: }
18: }
接下來執行以後就可以得到下面圖片的結果
可以看到黃色的BOX有specular的效果
詳細內容還是建議各位可以參考一下範例程式碼
下載
SHADER這邊就是要多多練習,需要習慣一下會比較容易上手。
對於shader有問題大家也可以一起討論看看~~