Managed DirectX 第四章

 

更多渲染技術

  翻譯:clayman  

   在討論過了基礎渲染方法之後,我們應該把注意力放到一些能提高性能,並且讓場景看起來更好的渲染技術上來:

 

 

 

渲染各種圖元類型

     至今爲止,我們只渲染過一種類型的圖元,稱爲三角形集合。實際上,我們可以繪製很多種不同類型的圖元,下邊的列表描述了這些圖原類型:

     PointList――這是一個自我描述的圖元類型,它把數據作爲一系列離散的點來繪製。不能使用這種類型繪製indexed primitives

     LineList——把每一對點作爲單獨的直線來繪製。使用時至少需要有兩個頂點。

     LineStrip——把頂點繪製爲一條折線。至少需要兩個頂點。

     TrangleList——這就是我們一直在使用的類型。每三個頂點被繪製爲一個單獨的三角形。通過當前的剔除模式來決定如何進行背面剔除。

     TrangleStrip——三角形帶是一系列相連的三角形,每兩個相鄰的三角形共享兩個頂點。剔除模式會自動翻轉所有偶數個三角形(flipped on all even-numbered triangles),因爲相鄰的三角形共享兩個頂點,他們會被翻到反方向。這也是複雜的3D對象使用的最多的圖元類型。

     TrangleFan——與三角形帶相似,不過所有的三角形都共享一個頂點。

     可以使用同樣的數據來繪製任意類型,任意數量的圖元。Direct3D會根據給定的圖元類型來繪圖。寫一點來嘛來繪製一下這幾種圖元吧。    

     修改我們創建頂點緩衝時的代碼。因爲不需要移動頂點,可以把SetupCamera裏的world transform刪除了,同樣所有引用到angle成員的代碼也可以刪除了。添加一下代碼:

     private const int NumberItems 12

     12雖然是隨便挑選的數字,但也有一定的原因。太多的頂點會讓屏幕太擁擠,同時,頂點的數量要同時能被23整除。這樣無論那種圖元都能都被正確的渲染。接下來修改創建頂點緩衝的代碼:

     vb=new VertexBuffer(typeof(CustomVertex.PositionColored), NumberItems, device, Usage.Dynamic | Usage.WriteOnly, CustomVertex.PositionColored.Format,Pool.Default);

     CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[NumberItems];

for(int i=0;i<NumberItems;i++)

     {

         float xPos = (float)(Rnd.NextDouble()*5.0f) - (float)(Rnd.NextDouble()*5.0f);

         ······(詳見源碼)

         verts[i].SetPosition(new Vector3(xPos,yPos,zPos));

         verts[i].Color = RandomColor.ToArgb();

     }

     這裏沒有什麼特別的地方,我們修改了頂點緩衝大小來保存足夠多的頂點。接下來,修改了創建頂點的方法,用一種隨機的方式來填充頂點。你可以在源碼中找到關於RndRandomColor的聲明。

     現在需要修改繪圖方法了。不停的滾動顯示幾種類型的圖原,可以簡單的展示出他們之間的聯繫。我們每兩秒鐘顯示一種類型。可以根據開機時到現在爲止的相對時間(in ticks)來計時。添加一下兩個成員變量的聲明:

     private bool needRecreate = false;

    private static readonly int ImitialTickCount  = System.Environment.TickCount;

 

 

 

     第一個布爾變量控制着在每個“週期”開始的時候重新創建頂點緩衝。這樣,就不必每次都顯示同樣的頂點。用一下代碼代替簡單的DrawPrimitives方法:

     (見源碼中帶有switch的部分)

     這基本上是一段可以自我解釋的代碼。根據一個週期中的不同時刻,調用DrawPrimitives來繪製相應的圖原。注意,由於圖原類型的不同,相同數量的頂點能繪製的圖原數也是不同的。運行程序,將按照PointListLinelistLineStripTragleList,TangleStrip的順序顯示圖原。如果你覺得顯示PointList時“點”太小看不清楚,可以通過調整render state把它稍稍放大一點:

     device.RenderStare.PointSize = 3.0f;

 

 

使用索引緩衝(Index Buffer

     還記得我們創建盒子時的帶碼嗎,我們一共創建了36個頂點。實際上,我們只使用了8個不同的頂點而已,即正方形的8個頂點。在這樣的小程序裏把相同的頂點儲存許多次並不會出什麼大問題。但在需要儲存大量數據的大得多的程序裏,減少數據的重複來節約空間就顯得很重要了。很幸運,Direct3D裏一種成爲索引緩衝的機制能讓同一個圖原共享他的頂點數據。

     就像他的名字暗示的那樣,索引緩衝就是一塊保存了頂點數據索引的緩衝。緩衝中的索引爲32位或16位的整數。比如,你使用索引016來繪製一個三角形時,會通過索引映射到相應的頂點來渲染圖像。使用索引來修改一下繪製盒子的代碼吧,首先修改創建頂點的方法:

     vb=new VertexBuffer(typeof(CustomVertex.PositionColored), 8, device, Usage.Dynamic | Usage.WriteOnly, CustomVertex.PositionColored.Format,Pool.Default);

    CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[8];

    verts[0] = new CustomVertex.PositionColored(-1.0f, 1.0f, 1.0f, Color.Red.ToArgb());

    ·····(見源碼OnVertexBufferCreate方法)

 

 

 

    如你所見,我們戲劇性的減少了頂點的數量,僅儲存正方形的8個頂點。既然已經有了頂點,那36個繪製盒子的索引應該是什麼樣子呢?看一下先前的程序,依照36個頂點的順序,列出適當的索引:

     private static readonly short[] indices =

    {

        0,1,2,  //front face

        1,3,2,  //front face

·····

    }

    爲了便於閱讀,索引分爲3個一行,表示一個特點的三角形。第一個三角形使用頂點012第二個使用132;以此類推。僅僅有索引列表是不夠的,還需要創建索引緩衝:

     private IndexBuffer ib null

這個對象就是儲存並且讓Direct3D訪問索引的地方。它與創建頂點緩衝的方法也很相似。接下來初始化對象,填充數據:

ib = new VertexBuffer(typeof(short),indices.Length,device,Usage.WriteOnly,Pool.Default);

    ib.Created += new EventHandler(ib_Created);

OnIndexBufferCreate(ib,null);

 

 

 

private void ib_Created(object sender, EventArgs e)

    {

        IndexBuffer buffer = (IndexBuffer)sender;

        buffer.SetData(indices,0,LockFlags.None);

}

 

 

 

除了參數的約束條件以外,索引緩衝的構造器簡直就是一個模子裏出來的。與前面提到的一樣,只能使用16位或32位的整數作爲索引。我們訂閱了事件處理程序,並且在程序第一次運行時手動調用他。最後爲索引緩衝填充了數據。

現在,需要修改渲染圖像的代碼來使用這個數據了。如果你還記得,我們以前使用了一個叫“SetStreamSource”的方法來告訴DirectX渲染的時候使用哪一快頂點緩衝。同樣,對於索引緩衝來說也有這樣一種機制,不過它僅僅只是一個屬性而已,因爲同一時間只可能使用一種類型的索引緩衝。在SetStreamSource之後,設置如下屬性:

device.Indices = ib;

 

 

 

這下Direct3D知道頂點緩衝的存在了,接下來修改繪圖代碼。目前,我們的繪圖方法嘗試從頂點緩衝繪製12個圖原,可是這必然不會成功,因爲現在頂點緩衝裏只有8個頂點了。添加DrawBox方法:

private void DrawBox(float yaw,float pitch,float roll,float x,float y,float z)

    {

        angle += 0.01f;

        device.Transform.World = Matrix.RotationYawPitchRoll(yaw,pitch,roll) * Matrix.Translation(x,y,z);

        device.DrawIndexedPrimitives(PrimitiveType.TriangleList,0,0,8,0,indices.Length /3);

}

 

 

 

這裏,我們把DrawPrimitives改爲了DrawIndexedPrimitives。來看看這個方法的原型吧:

public void DrawIndexedPrimitives(PrimitiveType primitiveType,int baseVertex ,int minVertexIndex,int numVertices, int startIndex, int primCount);

 

 

 

第一個參數和上一個方法的一樣,表示要繪製的圖原類型。參數baseVertex表示從索引緩衝起點到要使用的第一個頂點索引的偏移量。MinVertexIndex是這幾個頂點中最小的頂點索引值。很顯然,numVertices指的就是所要使用的頂點數量。startIndex表示從數組中的哪一個位置開始讀取頂點。最後一個參數則是要繪製的圖原數量。

現在通過索引緩衝中的8個頂點,就可以繪製出了構成立方體的12個圖原了。接下來用DrawBox方法代替原來的DrawPrimitives方法。

DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 2.0f, angle / (float)Math.PI / 4.0f, 0.0f, 0.0f, 0.0f);

(略,詳見源碼)

 

 

 

再次運行程序,可以看到顏色非常鮮豔的盒子在旋轉。我們的每一個頂點都有不同的顏色,因此,真實的反映了使用索引緩衝共享頂點的缺點。當多個圖原共享頂點的時候,所有的頂點數據都是共享的,包括顏色,法線數據等等。當決定是否共享頂點時,必須確定共享數據不會帶來燈光或顏色上的錯誤(因爲燈光的計算依賴於法線)。可以看到立方體每個面的顏色都是由頂點顏色插值計算出來的。

 

 

 

使用深度緩衝(Using Depth Buffer

     深度緩衝(depth buffer)(也就是通常所說的z-bufferw-buffer)是Direct3D在渲染時儲存“深度”(“depth一般指方向爲從屏幕指向觀察者的z軸的窗口座標)。深度信息用於在光柵化時決定象素之間的替代關係(注:度通常用視點到物體的距離來度量,這樣帶有較大深度值的象素就會被帶有較小深度值的象素替代,即遠處的物體被近處的物體遮擋住了)。至今爲止,我們的程序都沒有使用過深度緩衝,所以光柵化時沒有象素被遮擋住。除此之外,我們甚至還沒有會相互重疊的象素,那麼,現在來繪製一些會與已有的立方體重疊的的立方體吧。

在已有的DrawBox方法調用後添加如下代碼:

DrawBox(angle / (float)Math.PI,angle / (float)Math.PI*2.0f, angle / (float)Math.PI / 4.0f,0.0f,(float)Math.Cos(angle),(float)Math.Sin(angle));

···(略)

 

 

 

     我們在添加了三個旋轉的立方體到原來中間一排的立方體上。運行程序,可以看到重疊的立方體,卻不能分清兩個立方體重疊部分的邊界,看起來不過是一塊普通的斑點而已。這就需要通過深度緩衝來處理了。

     添加深度緩衝實在是一個簡單的任務。記得我們傳遞給device構造函數的presentation parameters參數嗎?well,這將是我們添加深度緩衝的地方。創建一個包含深度緩衝的device,需要用到兩個新的參數:

     public Mircosoft.DirectX.Direct3D.DepthFormat AutoDepthStencilFormat [ get, set ]

     public bool EnableAutoDepthStencil [get,set]

    

     EnableAutoDepthStencil設置爲true就可以爲device打開深度緩衝,使用DepthFormat來指定AutoDepthStencilFormat成員。DepthFormat枚舉中,可使用的值列在下表中:

 

 

 

D16           A 16-bit z-buffer bit depth.

D32           A 32-bit z-buffer bit depth.

D16Lockable   A 16-bit z-buffer bit depth that is lockable.

D32Flockable  A lockable format where depth value is represented by a standard IEEE floating point number.

D15S1         A 16-bit z-buffer bit depth using 15 bits for depth channel, with the last bit used for the stencil channel (stencil channels will be discussed later).

D24S8         A 32-bit z-buffer bit depth using 24 bits for depth channel, with the remaining 8 bits used for the stencil channel.

D24X8         A 32-bit z-buffer bit depth using 24 bits for depth channel, with the remaining 8 bits ignored.

D24X4S4       A 32-bit z-buffer bit depth using 24 bits for depth channel, with 4 bits used for the stencil channel, and the remaining 4 bits ignored.

D24FS8        A non-lockable format that contains 24 points of depth (as a floating point) and 8 bits for the stencil channel.

 

 

 

     深度緩衝越大,能儲存的深度數據也越多,但這是以犧牲性能爲代價的。除非你確定需要使用很大的深度緩衝,否則使用最小的值就可以了。大部分現代的圖形卡都支持最小16bit的深度緩衝,so,添加代碼:

     presentParams.AutoDepthStencilFormat = DepthFormat.D16;

    presentParams.SwapEffect = SwapEffect.Discard;

 

 

 

Perfect,現在device獲得了深度緩衝。來看看有什麼不同吧,運行程序。哇,結果並不是我們期盼的那樣,程序被破壞了。這些立方體發生了什麼?爲什麼加入了深度緩衝之後導致渲染被破壞了呢。呵呵,原因是深度緩衝從來沒有被“cleared”,所以它一直處於一種不正確的狀態。應該在clear device的同時clear深度緩衝,修改代碼如下

device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.CornflowerBlue, 1.0f, 0);

 

 

 

Ok,一切正常了,休息一下來欣賞我們的作品吧^_^

需要源碼可以到這個地方去下http://bbs.gameres.com/showthread.asp?threadid=24673

發佈了78 篇原創文章 · 獲贊 0 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章