XNA講座

前天,在噗浪上隨便搜尋,突然發現到之前有興趣的「台灣微軟校園巡迴講座」有一場是在端午連假的今天,地點是在台灣微軟的公司裡。
這麼好的時間點,又正好找不到特別的事情要做,約一約Masaki就去聽了。
台灣微軟的公司是再市政府站附近的國泰大樓裡面,警衛櫃台兼任報到處,我們大約在開始前15分鐘到場,順序為67和68。
進到會議室,放眼望去幾乎都是年紀相仿的學生,我們挑了中後方的位置。
講座開始時,座位差不多都滿了,演講者是一位淡江大學資工系四年級的學生。
稍微介紹一下XNA的來由和架構後,開始程式碼範例。
首先介紹的是2D貼圖的function,也就是sprite.Draw()的各種多載。
演講者是預設台下的聽眾是有程式基礎以及基本圖學概念下去講的,至於講解得清不清楚,就使用過的我來說,也無從分辨= =||
講起來和學校的程式課一樣…枯燥。
Masaki聽一聽已經快睡著,有些聽眾也尿遁逃離,其實真不怪他,程式課都這樣的,哈哈!
講了大約一個小時後,演講者提議先休息個一下再繼續。
喔~怎麼可能放棄這種機會!
Masaki用強烈的眼光表示他要迴避,雖然我還有點興趣聽,但還沒到放棄司機的程度XDD
所以接下來的講座就沒聽,感覺得出來算是一種推廣用的入門教學講座,或許下半段會有有趣的東西吧!

(繼續閱讀…)

XNA – Color固定色表(續)

字數上限…
所以追加下篇!

 名稱  顏色  名稱  顏色
LightSkyBlue
Peru
LightSlateGray
Pink
LightSteelBlue
Plum
LightYellow
PowderBlue
Lime
Purple
LimeGreen
Red
Linen
RosyBrown
Magenta
RoyalBlue
Maroon
SaddleBrown
MediumAquamarine
Salmon
MediumBlue
SandyBrown
MediumOrchid
SeaGreen
MediumPurple
SeaShell
MediumSeaGreen
Sienna
MediumSlateBlue
Silver
MediumSpringGreen
SkyBlue
MediumTurquoise
SlateBlue
MediumVioletRed
SlateGray
MidnightBlue
Snow
MintCream
SpringGreen
MistyRose
SteelBlue
Moccasin
Tan
NavajoWhite
Teal
Navy
Thistle
OldLace
Tomato
Olive
TransparentBlack (透明)
OliveDrab
TransparentWhite (透明)
Orange
Turquoise
OrangeRed
Violet
Orchid
Wheat
PaleGoldenrod
White
PaleGreen
WhiteSmoke
PaleTurquoise
Yellow
PaleVioletRed
YellowGreen
PapayaWhip


PeachPuff



–End

XNA – Color固定色表

嗯…剩下五天了!
暫時讓腦袋休息…就順便弄出色表(被毆)
以下是XNA裡Color的色表,有需要可以看看XDD

 名稱  顏色  名稱  顏色
AliceBlue DarkTurquoise
AntiqueWhite DarkViolet
Aqua DeepPink
Aquamarine DeepSkyBlue
Azure DimGray
Beige DodgerBlue
Bisque Firebrick
Black FloralWhite
BlanchedAlmond ForestGreen
Blue Fuchsia
 BlueViolet   Gainsboro
 Brown   GhostWhite
 BurlyWood   Gold
 CadetBlue   Goldenrod
 Chartreuse   Gray
 Chocolate   Green
 Coral   GreenYellow
 CornflowerBlue   Honeydew
 Cornsilk   HotPink
 Crimson   IndianRed
 Cyan   Indigo
 DarkBlue   Ivory
 DarkCyan   Khaki
 DarkGoldenrod   Lavender
 DarkGray   LavenderBlush
 DarkGreen   LawnGreen
 DarkKhaki   LemonChiffon
 DarkMagenta   LightBlue
 DarkOliveGreen   LightCoral
 DarkOrange   LightCyan
 DarkOrchid   LightGoldenrodYellow
DarkRed LightGray
DarkSalmon LightGreen
DarkSeaGreen LightPink
DarkSlateBlue LightSalmon
DarkSlateGray LightSeaGreen


–Next

XNA – RenderTarget & AlphaBlend (上)

完全出乎我意料的,AlphaBlend真是有夠麻煩的…
終於,找到原因(應該),也順利解決了問題(似乎)!
嘛~結果是我要的就好了咩XDDD

AlphaBlend會出問題的原因在於「算法」和「認知」的差異。
對,或許你腦中的混和方程式其實是錯的,書上寫的甚至也是錯誤的
(也許)
如果不想被我誤導,請先瀏覽一下參考文章:
Drawing problems with RenderTarget2D
SpriteBatch::DrawString() into RenderTarget2D odd effect
Premultiplied alpha
Alpha Blending (Part 1)
Alpha Blending, Part 2
Alpha Blending, Part 3




先來看看預設的計算公式:

FinalColor = (SourceColor * SourceBlend) + (DestinationColor * DestinationBlend);

其中:
FinalColor – 最終顏色。
SourceColor – 來源顏色。
DestinationColor – 目標顏色。
SourceBlend – 混合時,來源的混合方式。預設為「SourceAlpha」,也就是來源顏色的透明值。
DestinationBlend – 混合時,目標的混合方式。預設為「InverseSourceAlpha」,也就是來源顏色透明值的反差。

也就是…

FinalColor.rgb = (SourceColor.rgb* SourceColor.a) + (DestinationColor.rgb * (1 – SourceColor.a));

最終結果的顏色 = 來源提供的量(依照透明值) + 目標提供的量(依照透明值)。
看似合情合理,卻和我們直覺的混合有所不同,舉個簡單的例子。
假設我們將一顆純紅且半透明(1, 0, 0, 0.5)的顏色,畫到一個純黑且完全透明(0, 0, 0, 0)的顏色上。
那麼,直覺上來說,結果應該就是來源的顏色rgb(0.5, 0, 0)吧?
接著來看看實際的計算過程:

FinalColor = (SourceColor* SourceColor.a) + (DestinationColor * (1 – SourceColor.a));
FinalColor = ((1, 0, 0) * 0.5) + ((0, 0, 0) * (1 – 0.5));
FinalColor = ((1, 0, 0) * 0.5) + ((0, 0, 0) * (0.5));
FinalColor = (0.5, 0, 0) + (0, 0, 0);
FinalColor = (0.5, 0, 0);

到這裡,毫無破綻,確實符合裡想的結果。
所以,當你直接把圖片畫在Backbuffer上是完全看不出異樣的。



現在,我們把上面的顏色畫在某個畫布(RenderTarget)上,我們在將畫布畫到BackBuffer。
首先,先把上半部沒有明確列出來的Alpha計算補完。
在「SeparateAlphaBlendEnabled」開啟之前(預設為關閉),Alpha的混合是參照rgb的混合方式。
也就是依照
SourceAlpha和InverseSourceAlpha計算,先來看看預設的計算結果:

FinalColor.a = (SourceColor.a * SourceColor.a) + (DestinationColor.a* (1 – SourceColor.a));
FinalColor.a = (0.5 * 0.5) + (0 * (1 – 0.5));
FinalColor.a = (0.5 * 0.5) + (0 * 0.5);
FinalColor.a = (0.25) + (0);
FinalColor.a = 0.25;

很顯然的,這個透明值是錯誤的,也就是一切錯誤的根源…
後續畫到BackBuffer的步驟就省略了,其
結果應該不難想像。
既然如此,我想我們需要將SeparateAlphaBlendEnabled打開,這是為了讓透明值獨立計算。
分離後的透明值計算公式如下:

FinalColor.a = (SourceColor.a * AlphaSourceBlend) + (DestinationColor.a * AlphaDestinationBlend);

其中:
AlphaSourceBlend – 來源透明質的混合方式。預設為One。
AlphaDestinationBlend – 目標透明質的混合方式。預設為Zero。

OK,來走一次計算過程吧!

FinalColor.a = (SourceColor.a * AlphaSourceBlend) + (DestinationColor.a * AlphaDestinationBlend);
FinalColor.a = (SourceColor.a * One) + (DestinationColor.a * Zero);
FinalColor.a = (0.5 * 1) + (0 * 0);
FinalColor.a = (0.5) + (0);
FinalColor.a = 0.5;

Great!!正確的結果。
那麼,我們在經過一層呢?這次畫在純藍的些許透明(0, 0, 1, 0.8)的顏色上~

FinalColor.a = (0.5 * 1) + (0.8 * 0);

…?目標的透明值呢?它天生是個M,就該拋棄它嗎?
而且,如果我們選擇拋棄它,顯然是個極大的錯誤!
那麼,是要讓兩個透明值相加?還是相乘呢?
哪一種方式全看個人需求,但是請看看這篇的參考文章,就跟著用相乘吧XDDD
將AlphaSourceBlend設為One,
AlphaDestinationBlend設為InverseSourceAlpha。

再走一次計算流程吧!

FinalColor.a = (SourceColor.a * One) + (DestinationColor.a * InverseSourceAlpha);
FinalColor.a = (0.5 * 1) + (0.8 * (1 – 0.5));
FinalColor.a = (0.5 * 1) + (0.8 * 0.5);
FinalColor.a = (0.5) + (0.4);
FinalColor.a = 0.9;

嗯~看起來似乎正確了!要來幾層都沒問題啦!



本來想寫得簡單一些,沒想到還是打了這大串
就此暫時打住,剩下的留到下篇補完吧!
另外,如果有錯誤的地方麻煩告知一下,小弟手殘眼殘腦殘也不是五天六天的事情了

–Next

XNA – BasicEffect的光線

如果有試著用XNA內建的shader(也就是BasicEffect),而且也將方向光(Direct Light)打開的話。
或許你可能會發現某些部分的面,會完全沒受到光,看起來就像一塊瘡疤!
當然,可以用高超的打光技巧掩飾這個問題,可惜我沒有

最近在改寫「KiloWatt Animation」的預設shader,因為它只有單一方向光以及不太適當的fog計算方式。
本來想要找個很棒的shader來用,可惜和HLSL不夠熟,完全不知從何下手…
最後決定把BasicEffect(以下簡稱BE)的寫法移植過來,我比較習慣他的fog,以及有三個方向光。

撞了無數次牆壁後,終於移植完成,成功的將光線和fog放進去。
但是,之前使用BE方向光的問題就同樣地出現了。
經過這次移植,也對HLSL有更進一步的了解,就試著解決這個問題。

先來看看原始的光線會造成的狀況:

左圖和右圖差別在於光線的反射光(SpecularColor)和物件的反射強度(SpecularPower)

右圖可以很明顯地看到"瘡疤",當反射程度越高越容易發現。

 float3 L = -DirLightXDirection;
 float3 H = normalize(E + L);
 float dt = max(0,dot(L,N));
 result.Diffuse += DirLightXDiffuseColor * dt;
 if (dt != 0)
     result.Specular += DirLightXSpecularColor * pow(max(0,dot(H,N)), SpecularPower);

這是BE裡面計算光線的方法「ComputePerPixelLights(…)」裡的光線算法。
我推測這一切的問題來源在於「dt = max(0,dot(L,N));」。
dt影響到後面「result.Diffuse += DirLightXDiffuseColor * dt;」
,它將dot值小於0的一律比照辦理。
所以才會產生交界(以0為界線)的地方會有明顯的區分,而非漸進式的遞減。
至於解法,應該有千百種吧…
我只用一個簡單的想法「背光的就將之變暗就是,但是只會變暗到一定程度」。
這是基於他原本忽略背光的方式而改的,至於變暗的限制,只是單純避免暗過頭,會全黑掉低

 float3 L = -DirLightXDirection;
 float3 H = normalize(E + L);
 float dt = dot(L,N);
 if(dt < 0)
     dt *= 0.1;
 result.Diffuse += DirLightXDiffuseColor * dt;
 if (dt != 0)
     result.Specular += DirLightXSpecularColor * pow(max(0,dot(H,N)), SpecularPower);


這樣就可以避免瘡疤的出現了~很簡單吧!
前面也說過,這只是純粹消除瘡疤。
如果有華麗的解決方法,麻煩偷偷告訴我XDD

–End

XNA – Billboard

Billboard,通常翻譯為「廣告牌」或「告示牌」。
在多數的3D遊戲中,它一直都是不可或缺的技術之一。
技術需求低(如果沒有把數學環老師的話),效能需求低,效果卻非常好!

Billboard的原理很簡單,就是讓一個3D物件(通常是個Plane)永遠面對鏡頭。
處理物件的方是主要分為兩種:「丟給CPU算」「丟給GPU算」。
分辨的方式很簡單,一個在render前就先把物件轉好;而後者則是將資訊傳入,在VertexShader裡面處理。
效能何者較優?應該是後者…吧?

處理的過程也各有不同,可參考底下連結:
Billboards
3D Billboard Particles Tutorial VI
葵花朝太阳的BILLBOARD算法
Billboard(广告牌)实现的逐步推导,扫盲!
3.11 Billboarding:在3D世界绘制2D图像使它们总是面向相机」※感謝楊漱玉青」提供。

第一個連結,也就是Club上範例的,作者腦袋不知道在想啥!
身為教學,在shader加入風吹草動就算了,還多加一道ContentPipeline增加複雜度!
如果是進階教學是很好,但是還沒有先來個簡單又單純的啊!!
咳~回歸正題。

接著稍微解說一下第二篇。

首先,全域變數:

// HLSL
half4x4 world : World;                          // 世界轉換矩陣
half4x4 vp : ViewProjection;                  // 攝影機的 View * Projection 矩陣
texture particleTexture;                       // 要繪製的紋理圖
// XNA
shader.Parameters[“world"]
shader.Parameters[“vp"]
shader.Parameters[“particleTexture"]

頂點資料:

struct VertexIn
{
half4 Position : POSITION0; // 頂點座標
half2 TextureCoords : TEXCOORD0;  // UV座標
half4 Color : COLOR0;                       // 頂點顏色
half4 Data : POSITION1;                  // 其他資料。這裡X=縮放,Y=Alpha。
};
public struct BillboardParticleElement
{
Vector3 position;
Vector2 textureCoordinate;
Color color;
Vector4 data;
// …下略…
}

Update和Draw就直接看Code吧~
基本上就是控制BillboardParticleElement裡面的參數就對了。


不知道為啥有時候按空白鍵就會當掉…這篇打了三次,整個就很沒力(暈)
所以更詳細的部分就略過了
還是看他們的文章卡實在,哈哈!

–End

XNA – ODE’s matrix to XNA’s matrix

ODE弄了老半天,終於在前陣子開始"動"了!
當然,是我沒有仔細掃過wiki,不然蠻多問題在裡面
有解答說
但是,旋轉問題馬上就出現了!

找了又找,估了又估,最後回到wiki裡面。
# 12.3.2 Can I use ODE with DirectX?
裡面可以很明顯的知道,ODE和DirectX是屬於同種類的矩陣,GREAT!!
我就很老實的套用他提供的轉換方法。
(在這時間點,我以為DX和XNA是同屬的…鄞老,我不小心又忘記你的提醒了 囧)

疑?怎麼總覺得不對勁?
雖然臨時使用頂點緩衝區繪製當作Debug用,可是大小不對就算了,轉得亂七八糟,整個就錯的吧!
就這樣從一個多月前開始修修整整,到了今天才正式確定。
好吧~自製的Debug工具的確有很大的Bug
今天終於可以靜下心來慢慢調整測試,才修正到目前應該正確的結果。

我是使用Tao Framework包裝的ODE。

Ode.Matrix3 To Matrix:

 public static Matrix ConvertToMatrix(Ode.dMatrix3 oriMat, Vector3 pos)
 {
     // Xx Yx Zx Ox
     // Xy Yy Zy Oy
     // Xz Yz Zz Oz
     // 0  0  0  1
     //
     //    -TO-
     //
     // Xx Xy Xz 0
     // Yx Yy Yz 0
     // Zx Zy Zz 0
     // Ox Oy Oz 1

     Matrix mat = new Matrix();
     mat.M11 = oriMat.M00;
     mat.M12 = oriMat.M10;
     mat.M13 = oriMat.M20;
     mat.M14 = 0;

     mat.M21 = oriMat.M01;
     mat.M22 = oriMat.M11;
     mat.M23 = oriMat.M21;
     mat.M24 = 0;

     mat.M31 = oriMat.M02;
     mat.M32 = oriMat.M12;
     mat.M33 = oriMat.M22;
     mat.M34 = 0;

     mat.M41 = pos.X;
     mat.M42 = pos.Y;
     mat.M43 = pos.Z;
     mat.M44 = 1;

     return mat;
 }

Matrix To Ode.Matrix3:

 public static Ode.dMatrix3 ConvertToOdeMatrix3(Matrix oriMat)
 {
     // Xx Xy Xz 0
     // Yx Yy Yz 0
     // Zx Zy Zz 0
     // Ox Oy Oz 1
     //
     //    -TO-
     //
     // Xx Yx Zx Ox
     // Xy Yy Zy Oy
     // Xz Yz Zz Oz
     // 0  0  0  1
     Ode.dMatrix3 mat = new Ode.dMatrix3();

     mat.M00 = oriMat.M11;
     mat.M01 = oriMat.M21;
     mat.M02 = oriMat.M31;
     mat.M03 = 0;

     mat.M10 = oriMat.M12;
     mat.M11 = oriMat.M22;
     mat.M12 = oriMat.M32;
     mat.M13 = 0;

     mat.M20 = oriMat.M13;
     mat.M21 = oriMat.M23;
     mat.M22 = oriMat.M33;
     mat.M23 = 0;

     return mat;
 }

如果有錯(希望沒有),麻煩提醒一下=..=

–End

XNA – RenderTarget

Render Targets」,簡而言之就是畫布。
其中,一直都在使用的Backbuffer就是其中一個RenderTarget。
對於組合較於複雜的畫面,這是一個重要且必要的技術…
最簡單的範例就是「視窗」,每個視窗內都有或多或少的物件要繪製,對整個視窗做特效也就只需要動到該視窗專用的RenderTarget即可。
再者就是很常見的多Viewport也就一定要用啦~!
進階的就像是即時產生紋理圖之類的。

使用RenderTarget的基本流程:

 // 保留原本的Target
 RenderTarget2D oriTarget = GraphicsDevice.GetRenderTarget(0) as RenderTarget2D;
 GraphicsDevice.SetRenderTarget(0, MyTarget);
 GraphicsDevice.Clear(Color.White);
 // …繪製…
 // 設回原來的Target
 GraphicsDevice.SetRenderTarget(0, oriSpace);



關於使用
RenderTarget時,如果有牽涉到AlphaBlend,會產生莫名其妙的紫色的問題,目前還沒找到真正的解法。
目前可以應急的方式為:
「截取GraphicsDeviceManager的OnPreparingDeviceSettings事件,將RenderTargetUsage設為PreserveContents

{
 // …
 GraphicDeviceManager.PreparingDeviceSettings += OnPreDevSet;
 // …
}
private void OnPreDevSet(object sender, PreparingDeviceSettingsEventArgs e)
 {
  e.GraphicsDeviceInformation.PresentationParameters.RenderTargetUsage = RenderTargetUsage.PreserveContents;
 }

關於RenderTargetUsage可參考「Rendertarget changes in XNA Game Studio 2.0」。
XNA中的RenderTarget」裡面提到,PreserveContents在PC上的效能會比DiscardContents來得好。
這樣的話,如果只需要在PC上執行,就直接使用
PreserveContents吧!
但如果在X360上呢?這真是個好問題,希望有解法出現。

目前推測應該就是Contents的問題,或許XNA是在繪製每個東西的時候都會比照辦理吧!(炸)

相關連結:
[2.0] SetRenderTarget causes a purple background.
RenderTargetUsage.PreserveContents on Xbox
Purple Sprite Halo
Screen Manager

–End

XNA – 兩個為什麼?

畢業製作的第二次審查終於過去了(有沒有被當是另一回事XDD)
這一兩個禮拜真是充實到不行
這幾天來整理一下手邊的資料…喔~MHP2G也沒閒著呢!(?)

先來兩篇挺有趣的文章~

Why purple?」,如果你發現透明混合(Alpha Blend)有出現暗紫色,那麼你應該會有興趣看看這篇!
WHAT'S THE MEANING OF "XNA"?」,對於官方一直強調XNA並無特殊意義,這位作者有個瘋狂的想法。

–End