15. diel - 3D bludisko v XNA - Hardvérové instancování druhýkrát
Vitajte po dvadsiatej piatej. V tomto diele si dokončíme rozrobenú
komponent. Nebudem teda zdržovať, ideme na to. Kópie si budeme chcieť
skladovať v Listu
, ktorý nám uľahčí manipuláciu s nimi. Aby
to bolo možné urobiť, budeme potrebovať urobiť triedu generickú. Chceme,
aby sa dali vkladať len a len inštancie jedného typu vertexu. Nestačí teda
len používať rozhranie. Z triedy urobíme generickú takto:
public class InstancedModel3D<T> : Component where T : struct, IvertexType
Všetky vertexy musia implementovať rozhranie IVertexType
a
zároveň sú štruktúrami. Teraz si už môžeme vytvoriť List:
protected List<T> PrimitivesList;
A v konstruktoru ho vytvoriť:
PrimitivesList = new List<T>(MaxCount);
Pridáme taky metódy pre pridanie a odobratie kópie, nerobia nič moc špecifického, proste manipulujú s dátami v liste:
public virtual void AddPrimitive(T obj){ PrimitivesList.Add(obj); } public virtual void RemovePrimitive(T obj){ PrimitivesList.Remove(obj); }
Tak síce urobíme potrebné zmeny, ale na grafická karta o nich zatiaľ
nevie. Nie je teda nič ľahšieho, než vytvoriť metódu Apply
,
ktorá údaje aktualizuje. Tento prístup som zvolil, aby sa minimalizoval
počet komunikácia s grafickou kartou. Zavoláme si metódu len vtedy, keď
budeme mať všetky zmeny hotové. Vyzerá nasledovne:
public virtual void Apply(){ if (Primitives == null && PrimitivesList.Count > 0){ Primitives = new DynamicVertexBuffer(Parent.Engine.GraphicsDevice, PrimitivesList[0].VertexDeclaration, MaxCount, BufferUsage.None); } Primitives.SetData<T>(PrimitivesList.ToArray(), 0, PrimitivesList.Count); Count = PrimitivesList.Count; }
Najprv si skontrolujeme, či je náš dynamický buffer vytvorený a pokiaľ nie je a máme čo do neho pridávať, tak ho vytvoríme. Potom už do neho len nastavíme dáta. Naozaj nič moc zložitého.
V úvode som naznačil, že nebudeme môcť používať vstavanú triedu
BasicEffect
, ale budeme si musieť napísať shader vlastné.
Keďže sme na túto tému zatiaľ nenarazili, tak tu pridám súbor s hotovým
shaderov. Bude stačiť ho len do projektu vložiť. Pridáme si premenné:
protected string effectName; protected Effect Effect;
V konstruktoru si odovzdáme poslednú parameter a to názov používaného efektu, celý konštruktor teda bude vyzerať nasledovne:
public InstancedModel3D(string name,int max,string effect){ ModelName = name; MaxCount = max; effectName = effect; PrimitivesList = new List<T>(MaxCount); }
A v metóde Load
ho rovnako ako všetko ostatné nahráme:
Effect = Parent.Engine.Content.Load<Effect>(effectName);
A teraz si dodáme metódu pre vykresľovanie. Je to posledná čriepok
skladačky a vlastne dosť možno ten najdôležitejší. Prepíšeme si teda
metódu Draw
:
public override void Draw(Matrix View, Matrix Projection, Vector3 CameraPosition)
Prvé, čo musíme urobiť, je skontrolovať, či je vôbec čo vykresľovať:
if (Primitives == null || Count == 0) return;
Ďalej musíme nastaviť spoločné parametre efektu. Nie je to ale tak
pekné ako u BasicEffectu
, ale čo narobíme:
Effect.Parameters["View"].SetValue(View); Effect.Parameters["Projection"].SetValue(Projection); Effect.Parameters["Texture"].SetValue(Texture);
Pošleme zmeny do grafickej karty:
Effect.CurrentTechnique.Passes[0].Apply();
Teraz potrebujeme nastaviť naše buffer ako aktívny. U indexov nie je žiadny problém:
Parent.Engine.GraphicsDevice.Indices = Indicies;
Ale nastavenie vertexov už nie je intuitívne. Použijeme pomocnú
štruktúru VertexBufferBinding
, ktorá obsahuje ďalšie parametre
ako je offset. A aby nedochádzalo
k vytváraniu neporiadku pretože táto metóda sa volá až 60x za sekundu,
tak si vytvoríme privátne pomocné pole:
private readonly VertexBufferBinding[] binding = new VertexBufferBinding[2];
V metóde Load
pridáme ako nultý prvok buffer s vertexy:
binding[0] = new VertexBufferBinding(Verticles);
A do metódy Apply
pri vytváraní buffera pre kópie pridáme
ďalší prvok do tohto poľa:
binding[1] = new VertexBufferBinding(Primitives,0,1);
Teraz už môžeme toto pole využiť a nastaviť oba buffer:
Parent.Engine.GraphicsDevice.SetVertexBuffers(binding);
A konečne sa dostávame k poslednému kroku a to je samotný príkaz vykresli:
Parent.Engine.GraphicsDevice.DrawInstancedPrimitives(PrimitiveType.TriangleList,0,0, Verticles.VertexCount,0,Indicies.IndexCount/3,Count);
Pozrime sa na parametre. Prvý nám rovnako ako vždy hovorí, že budeme kresliť trojuholníky. Potom nasledujú dve nuly značiace offset, ďalší parameter je počet vertexov, ktoré budeme vykresľovať. Ďalšie nula tentokrát offset pre indexy, potom je tam počet trojuholníkov ktoré vykresľujú (áno, každý má 3 vrcholy, preto delíme tromi) a na záver počet kópií.
Uff. Dúfam, že som na nič nezabudol. Teraz si skúsime komponent použiť.
Najprv si musíme vytvoriť vlastné vertex, v ktorom budeme mať unikátne
parametre pre každú kópiu. Ja som si túto triedu pomenoval
InstanceDataVertex
, ale poznáte to, na mene predsa nezáleží.
Pridajte si teda túto a alebo podobne pomenovanú triedu do bludiska. Z triedy
urobte štruktúru, tiež by mali byť verejné a navyše musí implementovať
rozhranie IVertexType
:
public struct InstanceDataVertex: IvertexType{
Kliknite na meno rozhraní a v zozname vyberte
implement interface explicitly
. Pre každú kópiu budeme ukladať
jej maticu World
a taky farbu. Pridáme si teda obe premenné:
public Matrix World; public Color Color;
Dajte si pozor na poradie, musí byť presne také, aké som uviedol, inak to nebude fungovať. V konstruktoru si ich priradíme:
public InstanceDataVertex(Matrix world,Color color){
World = world;
Color = color;
}
Aby bola štruktúra kompletná, potrebujeme takzvanú deklaráciu vertexu. Čo to presne je a ako to funguje si necháme na neskôr, až sa dostaneme ku grafickej karte. Ale ak by niekto túžil po poznaní, odkazujem vás na tento pekný, hoci anglický článok. Takže zatiaľ ako ovečky opisujte:
public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration( new VertexElement(0, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 3), new VertexElement(sizeof(float) * 4,VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate,4), new VertexElement(sizeof(float) * 8,VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate,5), new VertexElement(sizeof(float) * 12,VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate,6), //barva new VertexElement(sizeof(float) * 16,VertexElementFormat.Color, VertexElementUsage.Color,0) );
A vo vygenerovanom Getter si deklaráciu vrátime:
return InstanceDataVertex.VertexDeclaration;
Optimálne by mala naša štruktúra obsahovať aj metódu, ktorá vráti veľkosť v bajtoch, tak pridáme aj ju:
public static readonly int SizeInBytes = sizeof(float)*(16 + 4);
A tá je ak dobre počítam takáto. Štruktúru mám hotovú. Teraz sa
presunieme do triedy s mapou. Budeme chcieť instanciovat pre začiatok iba
podlahy, pre ostatné objekty ktoré hrajú úlohu v kolíziách si budeme
musieť napísať komponent novú. Konkrétne do metódy Nacti
, kde
si pod volanie metódy Promaz
pridáme:
InstancedModel3D<InstanceDataVertex> podlahy = new InstancedModel3D<InstanceDataVertex>("podlaha", 50, "instancedeffect");
Kde podlaha
je názov modelu našej podlahy, 50 maximálny
počet kópií (toto číslo je potrebné vhodne zvoliť podľa veľkosti
bludisko) a instancedeffect
je meno shader, pomocou ktorého budeme
vykresľovať. Do switche si do vetvy pre nulu pridáme:
podlahy.AddPrimitive(new InstanceDataVertex(Utility.CreateWorld(new Vector3(i * 20 + 10, 0, (j-1) * 20 + 10),Matrix.Identity,new Vector3(1.34f)),Color.White));
A naopak zakomentujeme vytváranie komponenty podlahy. Posledné, čo musíme urobiť, je komponent pridať do enginu. To vykonáme v metóde úplne dole:
Parent.AddComponent(podlahy);
A zavoláme metódu Apply
:
podlahy.Apply();
Hotovo. Ešte si do projektu pridajte súbor sa shaderov. Nájdete ho v archíve dole pod článkom. Zatiaľ to bude taká temná nepriehľadná krabička, ale nie je všetkým dňom koniec. Docela si aj myslím, že máme jeden z automatických systémov, ktoré sa ľahko používajú. Pre malé bludisko síce nemá moc veľký význam, ale akonáhle sa bludisko rozrastie, už je rozdiel veľmi citeľný. Pre dnešok je to teda všetko, opäť ako vždy sa budem tešiť na otázky, nápady a otázky dole pod článkom. A čo bude nabudúce? Doriešime kolízie.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 146x (1.78 MB)
Aplikácia je vrátane zdrojových kódov v jazyku C#