Vianoce v ITnetwork sú tu! Dobí si teraz kredity a získaj až 80 % extra kreditov na e-learningové kurzy ZADARMO. Zisti viac.
Hľadáme nové posily do ITnetwork tímu. Pozri sa na voľné pozície a pridaj sa k najagilnejšej firme na trhu - Viac informácií.

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#

 

Predchádzajúci článok
3D bludisko v XNA - Hardvérové instancování prvýkrát
Všetky články v sekcii
3D bludisko v XNA
Preskočiť článok
(neodporúčame)
3D bludisko v XNA - Kolízia štvrtýkrát a naozaj nie naposledy
Článok pre vás napísal vodacek
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Vodáček dělá že umí C#, naplno se již pět let angažuje v projektu ŽvB. Nyní studuje na FEI Upa informatiku, ikdyž si připadá spíš na ekonomice. Není mu také cizí PHP a SQL. Naopak cizí mu je Java a Python.
Aktivity