14. diel - 3D bludisko v XNA - Hardvérové instancování prvýkrát
Vitajte po dvadsiatej štvrtej. Ako som naznačil posledne, nie som vôbec spokojný s rýchlosťou vykresľovania. Nie je to síce ten pravý ukazovateľ, za ktorým by bolo fajn sa naháňať, ale pre veľké bludisko to problém je celkom veľký. Zoberme si také bludisko s veľkosťou 30x30 polí. To je tvorené z celkom 900-tich modelov. Našťastie, ako už vieme, XNA model nahrá iba raz a my potom dostávame iba odkazy na jednu inštanciu. Z minulého dielu tiež vieme, že dáta sú uložené v grafickej karte v podobe vertex a index bufferu. Zakaždým, keď chceme vykresľovať celé bludisko, opakujeme niekoľko krokov:
- Vymastíme celú obrazovku
- Pre každý model, ktorý vykresľuje, vykonáme: - Nastavíme spoločné
parametre efektu (matica view, projection)
- Nastavíme jedinečné vlastnosti objektu, treba farba u podláh a alebo pozície modelu (matica world)
- Nastavíme efekt ako aktívny
- Nastavíme index a vertex buffer modelu ako práve používaný (toto je súčasťou metódy Draw v triede modelu)
- Vykeslíme model (opäť súčasťou metódy Draw)
- Vrátime nastavenia farby pri podlahe späť
To sa hodí najmä na tie miesta, kde je veľa opakujúcich sa modelov. A to je presne náš prípad. Vykresľujú spústy múrov a spústy podláh, preto som dal prednosť tejto technike. Všetkému potom predchádza príprava, kde si vytvoríme jeden nový buffer, v ktorom skladujeme rozdielne parametre skupiny objektov ktoré nainstancujeme. V našom prípade to bude matice World a farba. Proces vykresľovanie potom vyzerá nasledovne:
- Príprava: Vytvoríme si buffer s jedinečnými informáciami o instancovaných objektoch (vykonávame len raz)
- Vymastíme celú obrazovku (tohto kroku sa nedá zbaviť)
- Nastavíme ručne index a vertex buffer instancovaného modelu
- Nastavíme ručne druhý vertex buffer, ktorý obsahuje rozdielne vlastnosti
- Nastavíme spoločné efektu vlastnosti (matica view a projection a trebárs i textúru)
- Nastavíme efekt ako aktívny
- Pre všetkých instancované objekty vykonávame - vykresľujú objekty pomocou informácií v druhom vertex bufferu
Budeme ich potrebovať pre osvetľovanie a taky pre postprocesorové efekty, ale to opäť predbiehajú. Celý článok je založený na tomto návode: http://sciencefact.co.uk/...ng-in-xna-4/
Dosť bolo rozprávanie, ideme na vec. Napadli ma dve možnosti, ako to
napísať tak, aby sa to hodilo do enginu. Môžeme urobiť špeciálne herné
okno, kde zakaždým keď pridáme model tak sa správne priradí do skupiny a
bude sa instanciovat. A alebo môžeme vytvoriť nový komponent. Nakoniec som
zvolil komponent. Síce to nebude tak prívetivé na používanie, ale zas to
bude ladiť s okolím. Do enginu, do zložky s komponentmi si pridáme novú
triedu, ja som ju nazval InstancedModel3D
. Urobíme jej verejnú a
opravíme menný priestor. Ako východiskový triedu použijeme triedu
Component
. Pridáme si premennú s menom súboru s modelom:
public string ModelName{ get; protected set; }
A tiež premennú, kde si uložíme naložené model:
protected Model Model;
V konstruktoru odovzdáme meno modelu, ktorý budeme instanciovat:
public InstancedModel3D(string name){ ModelName = name; }
Prepíšeme si metódu Load
od predka, stále nič neznámeho, a
v nej načítame model:
protected override void Load(){ Model = Parent.Engine.Content.Load<Model>(ModelName); }
Bohužiaľ nie je možné k instanciování používať priamo triedu Model, budeme z nej teda potrebovať dostať vertexy a indexy. Pridáme si pár nových vlastností do našej triedy:
protected VertexBuffer Verticles; protected IndexBuffer Indicies;
Vertex buffer sa bude hodiť určite, do neho si uložíme vrcholy nášho
modelu. A aby bolo navrhované pospájať, pridáme aj buffer s indexmi. Obaja
si ich v metóde Load
naplníme, ale najprv musíme mať čím. Do
metódy Load
si pridáme dva pomocné listy, do ktorých budeme
vertexy a indexy ukladať:
List<VertexPositionNormalTexture> vert = new List<VertexPositionNormalTexture>(); List<ushort> ind = new List<ushort>();
U indexov použijeme typ ushort
, teda
unsigned short
, čo je 16-bitové celé číslo bez znamienka.
Vertexy si uložíme is normálou, môže sa nám neskôr pri tvorbe
pokročilých efektov hodiť. Ďalej si tiež nakopírujeme lokálnej
transformácie modelu do známeho poľa:
Matrix[] transforms=new Matrix[Model.Bones.Count];
Model.CopyAbsoluteBoneTransformsTo(transforms);
Máme všetko pripravené, môžeme ísť na extrakciu dát. Budeme musieť prejsť všetky súčasti modelu a dáta z nich dostať:
foreach (ModelMesh mesh in Model.Meshes){ foreach (ModelMeshPart part in mesh.MeshParts){ //dalsi kod sem } }
Model, ako už viete, sa člení na jednotlivé navzájom hierarchicky usporiadané časti, ktorým hovoríme meshe. Navyše sa každá mesh skladá z niekoľkých častí, aj keď zvyčajne to je len jedna. Ale pri zložitejších modeloch to nemusí platiť, preto musíme prejsť foreach cyklom úplne všetky. Vytvoríme si opäť dve pomocné polia, do ktorých presunieme obsah z bufferov:
VertexPositionNormalTexture[] partVerts = new VertexPositionNormalTexture[part.VertexBuffer.VertexCount];
a
ushort[] partIndices = new ushort[part.IndexBuffer.IndexCount];
Veľkosti polí si berieme z oboch bufferov. Ako prvý si naplníme vertexy:
part.VertexBuffer.GetData(partVerts);
A práve získané vertexy si musíme ešte poupraviť. Teraz ich máme, povedal by som, v surovej podobe. Musíme na ne aplikovať transformácie, ktoré sme si dopredu uložili. Je to rovnaký proces, ktorý robíme, keď model vykresľujú, takže nič moc nové pod slnkom:
for (int i = 0; i < partVerts.Length; i++){ partVerts[i].Position = Vector3.Transform(partVerts[i].Position, transforms[mesh.ParentBone.Index]); }
Potom nám už zostáva ich len uložiť do pomocného listu:
vert.AddRange(partVerts);
Obdobne naložíme s indexmi, len je nie je potrebné nijako upravovať, takže je len vyzdvihneme a uložíme do listu:
part.IndexBuffer.GetData(partIndices); ind.AddRange(partIndices);
A je to, najčernejšie práca je prakticky hotová. Už len stačí vytvoriť zo získaných dát nové buffer:
Verticles = new VertexBuffer(Parent.Engine.GraphicsDevice, VertexPositionNormalTexture.VertexDeclaration,vert.Count, BufferUsage.WriteOnly); Verticles.SetData<VertexPositionNormalTexture>(vert.ToArray()); Indicies = new IndexBuffer(Parent.Engine.GraphicsDevice, IndexElementSize.SixteenBits, ind.Count, BufferUsage.WriteOnly); Indicies.SetData<ushort>(ind.ToArray());
Obaja buffer zostanú po celú dobu nezmenené. Preto nie je potrebné ich mať ako dynamické. Ešte si z modelu vytiahneme textúru, ktorú používa, a tú si uložíme niekam bokom:
protected Texture2D Texture; Texture = ((BasicEffect)Model.Meshes[0].Effects[0]).Texture;
Áno má to celé háčik, komplexnejšie modely sa nám takto jednoducho ošéfovat nepodarí. Modely, ktoré používajú viac ako jednu textúru takto nenainstanciujeme. Ale pre jednoduchosť to takto ponecháme.
Gratulujem, máme za sebou asi to najhoršie. Teraz už len tie krajšie veci. Ako je v úvode spomenuté, potrebujeme viac ako jeden buffer s vertexy. Je potrebné ešte jeden ďalší, kde budeme mať odlišné vlastnosti. A keďže sa predpokladá, že ich budeme programovo meniť, tak ich budeme ukladať v dynamickom bufferu:
protected DynamicVertexBuffer Primitives;
Bude potreba taky určiť maximálny počet kópií. Preto si pridáme:
public int MaxCount{ get; protected set; }
A tiež aktuálny počet kópií:
public int Count{ get; protected set; }
Do konstruktoru k menu modelu pridáme maximálny počet kópií:
public InstancedModel3D(string name,int max){ ModelName = name; MaxCount = max; }
Tu sa pre dnešné diel zastavíme. Triedu si dokončíme nabudúce. Zatiaľ očakávam otázky, nápady sťažnosti a priania no však veľmi dobre viete kde - v komentároch.