
這是大學時期的畢業作品,使用Microsoft XNA(3.1)堆出來的玩意。
繼續閱讀 Here sky前天,在噗浪上隨便搜尋,突然發現到之前有興趣的「台灣微軟校園巡迴講座」有一場是在端午連假的今天,地點是在台灣微軟的公司裡。
這麼好的時間點,又正好找不到特別的事情要做,約一約Masaki就去聽了。
台灣微軟的公司是再市政府站附近的國泰大樓裡面,警衛櫃台兼任報到處,我們大約在開始前15分鐘到場,順序為67和68。
進到會議室,放眼望去幾乎都是年紀相仿的學生,我們挑了中後方的位置。
講座開始時,座位差不多都滿了,演講者是一位淡江大學資工系四年級的學生。
稍微介紹一下XNA的來由和架構後,開始程式碼範例。
首先介紹的是2D貼圖的function,也就是sprite.Draw()的各種多載。
演講者是預設台下的聽眾是有程式基礎以及基本圖學概念下去講的,至於講解得清不清楚,就使用過的我來說,也無從分辨= =||
講起來和學校的程式課一樣…枯燥。
Masaki聽一聽已經快睡著,有些聽眾也尿遁逃離,其實真不怪他,程式課都這樣的,哈哈!
講了大約一個小時後,演講者提議先休息個一下再繼續。
喔~怎麼可能放棄這種機會!
Masaki用強烈的眼光表示他要迴避,雖然我還有點興趣聽,但還沒到放棄司機的程度XDD
所以接下來的講座就沒聽,感覺得出來算是一種推廣用的入門教學講座,或許下半段會有有趣的東西吧!
字數上限…
所以追加下篇!
名稱 | 顏色 | 名稱 | 顏色 |
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 | |
|
|
嗯…剩下五天了!
暫時讓腦袋休息…就順便弄出色表(被毆)
以下是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 |
完全出乎我意料的,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;
嗯~看起來似乎正確了!要來幾層都沒問題啦!
本來想寫得簡單一些,沒想到還是打了這大串
就此暫時打住,剩下的留到下篇補完吧!
另外,如果有錯誤的地方麻煩告知一下,小弟手殘眼殘腦殘也不是五天六天的事情了
如果有試著用XNA內建的shader(也就是BasicEffect),而且也將方向光(Direct Light)打開的話。
或許你可能會發現某些部分的面,會完全沒受到光,看起來就像一塊瘡疤!
當然,可以用高超的打光技巧掩飾這個問題,可惜我沒有
最近在改寫「KiloWatt Animation」的預設shader,因為它只有單一方向光以及不太適當的fog計算方式。
本來想要找個很棒的shader來用,可惜和HLSL不夠熟,完全不知從何下手…
最後決定把BasicEffect(以下簡稱BE)的寫法移植過來,我比較習慣他的fog,以及有三個方向光。
撞了無數次牆壁後,終於移植完成,成功的將光線和fog放進去。
但是,之前使用BE方向光的問題就同樣地出現了。
經過這次移植,也對HLSL有更進一步的了解,就試著解決這個問題。
先來看看原始的光線會造成的狀況:
右圖可以很明顯地看到"瘡疤",當反射程度越高越容易發現。
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
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裡面的參數就對了。
不知道為啥有時候按空白鍵就會當掉…這篇打了三次,整個就很沒力(暈)
所以更詳細的部分就略過了
還是看他們的文章卡實在,哈哈!
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.M21 = oriMat.M01; mat.M31 = oriMat.M02; mat.M41 = pos.X; 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.M10 = oriMat.M12; mat.M20 = oriMat.M13; return mat; |
如果有錯(希望沒有),麻煩提醒一下=..=
「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; } |
目前推測應該就是Contents的問題,或許XNA是在繪製每個東西的時候都會比照辦理吧!(炸)
相關連結:
「[2.0] SetRenderTarget causes a purple background.」
「RenderTargetUsage.PreserveContents on Xbox」
「Purple Sprite Halo」
「Screen Manager」
畢業製作的第二次審查終於過去了(有沒有被當是另一回事XDD)
這一兩個禮拜真是充實到不行
這幾天來整理一下手邊的資料…喔~MHP2G也沒閒著呢!(?)
先來兩篇挺有趣的文章~
「Why purple?」,如果你發現透明混合(Alpha Blend)有出現暗紫色,那麼你應該會有興趣看看這篇!
「WHAT'S THE MEANING OF "XNA"?」,對於官方一直強調XNA並無特殊意義,這位作者有個瘋狂的想法。