\\ Home : Articoli : Stampa
Approfondimenti sulle Mesh
Di RobyDx (del 27/07/2007 @ 16:01:19, in DirectX9, linkato 2527 volte)

La struttura più utilizzata da tutti per il rendering è la mesh, ma pochissime persone sanno esattamente come è strutturata una mesh e come crearla in assenza di fileX. Una mesh è per definizione un oggetto 3D, una figura composta da un insieme di triangoli, eventualmente raggrupati in insiemi che si differenziano per materiale (texture e colori).

Una mesh è composta dalle seguenti componenti:

  • Vertici
  • Indici
  • Adiacenze
  • Attributi

Vertici

I vertici sono la parte più importante perchè contengono le informazioni per il rendering. Un vertice dalla versione 8 di directX può essere personalizzato a piacimento eccetto per un punto: la posizione. Il vertice deve sempre contenere informazioni sulla posizione che esso assume nello spazio. Il resto è a carico del programmatore che può specificare quali informazioni includere.

  • Depth : informazioni sulla profondità
  • Sample : per i vertex shader 3.0 permette di stabilire quale sample usare per il displacement mapping (richiede supporto hardware)
  • Fog  :informazioni sulla nebbia
  • PositionTransformed: posizione in pixel del vertice
  • TessellateFactor: questo fattore viene utilizzato per le high order primitive
  • BiNormal :questo vettore è perpendicolare alla tangente
  • Tangent: questo vettore è tangente alla poligono ed ormai è usatissimo per tutte gli effetti hardware con normal map
  • TextureCoordinate: da 0 a 7 sono le coordinate per le texture
  • BlendIndices: indici per lo skin tramite indici
  • BlendWeight: pesi per lo skinning
  • Color: colore del vertice
  • PointSize : dimensione del vertice (solo per i sistemi a particelle o point sprite)
  • Normal : scomposizione della normale alla faccia nel punto. Viene utilizzata per il calcolo della luce e di moltissime altre cose
  • Position : posizione del vertice in unità

Impostando il vertice a piacimento è possibile dare informazioni al device sul rendering degli oggetti (soprattutto quando si utilizzano le funzioni base di illuminazione di directX). Tutto questo diventa parzialmente inutile se si usano i vertex shader visto che saremo noi a decidere come utilizzare i valori restituiti.

La lettura come potete vedere nel tutorial sull'accesso alla memoria per i vertici. Innanzitutto dovete dichiarare la struttura più consona

Public Structure Vertex
    Public p As Vector3
    Public n As Vector3
    Public tu, tv As Single
End Structure

Dichiarare un array di queste strutture e fare un lock

Dim vertici as Vertex()

vertici=CType(vertexBuffer.Lock(0, GetType(Vertex), 0, m.NumberVertices),Vertex())

vertexBuffer.Unlock();

tra il lock e l'unlock l'array è direttamente connesso al buffer. Quindi qualsiasi modifica all'array modifica la mesh. Il vertexbuffer si prende dalla mesh, basta fare (punto)vertexbuffer. L'importante è che tutto coincida facendo appositi cast. Giù l'istruzione richiede un tipo e tramite l'istruzione getType si può trasformare una struttura in un tipo. L'istruzione CType converte il valore restituito (un System.Array) nell'array di strutture da noi scelte. La cosa più importante è che la struttura coincida perfettamente. Per farli coincidere è spesso necessario effettuare un clone

mesh.Clone(MeshFlags.Managed, elementi, device)

Usando l'element declaration si converte il vertice della mesh nel formato desiderato. Quest'ultimo argomento è spiegato con chiarezza nel tutorial sui vertexshader. Consiglio di utilizzare i vertexelement per la clonazione visto che danno maggiore sicurezza nella correttezza. Ragionate bene sulla struttura perchè nella maggior parte dei casi i problemi nascono da strutture vertici e dichiarazioni vertici non perfettamente coincidenti. Per maggiore precisione consiglio anche di estrarre i dati dell'array (dovete copiare i dati in un secondo array visto che quello con cui effettuate il lock è connesso alla mesh) e di ricopiare i dati nella mesh dopo la clonazione.

Indici

Un cubo ha 6 faccie quadrate, riducibili a 12 triangoli. Tuttavia i vertici sono solamente 8. I 12 triangoli contenenti 36 vertici hanno di per se dei duplicati infatti. Per evitare che ogni vertice compaia in memoria molte volte si usano gli indici. Gli indici sono l'ordine in cui vengono presi i vertici per formare la figura. Sono ovviamente come numero dei multipli di 3 dato che devono sempre formare triangoli. Gli indici sono rappresentati come un array di interi a 16 o 32bit.Ogni valore contiene l'indice nell'array di vertici usato per il triangolo. Ad esempio 0-1-2-5-7-1 sono 2 triangoli, il primo con i vertici di indici 0,1 e 2 ed il secondo con quelli di indice 5,7 e 1. Di solito sono sufficienti gli short che permettono con metà della memoria di contare fino a 32768 vertici ma nel caso le mesh siano maggiori è necessario usare gli int a 32 bit (oltre 4 miliardi di vertici, irraggiungibili da qualsiasi hardware). Anche gli indici richiedono un lock

Dim indici As Short()
indici = mesh.indexBuffer.Lock(0, GetType(Short), 0, m.NumberFaces * 3)

essendo tipi primitivi non richiedono il cast come per i vertici (attenzione in C# invece si).

Le figure comunque non hanno mai il numero esatto dei vertici della figura. Questo perchè i vertici oltre alla posizione hanno normali, texture ed altro. Quindi gli 8 teorici possono essere di più. In definitiva però non dovrebbe mai esistere un vertice con i dati perfettamente coincidenti ad un altro.

Adiacenze

Le adiacenze sono le informazioni sui triangoli vicini ad ogni triangolo. Ogni triangolo infatti può avere fino a 3 triangoli che condividono uno spigolo con lui. Questo array di interi ha dimensione esattamente 3 volte il numero delle faccie. Ogni 3 valori scandisce una faccia indicando i 3 vicini. Ad esempio 2,5,4,1,3,-1 significa che il primo triangolo ha come vicini i triangoli 2,4 e 5 mentre il secondo solo 1 e 3. Il valore -1 indca che su quello spigolo non ci sono altri vicini. Questo valore è fondamentale per l'ottimizzazione e per le progressive mesh. Ottenere le adiacenze è molto semplice

Dim adia As Integer()
ReDim adia(mesh.NumberFaces * 3)
mesh.GenerateAdjacency(0.001F, adia)

adia ora contiene le adiacenze. La costante che ho messo serve per specificare quanto devono essere distanti i vertici prima di essere considerati coincidenti.

Attributi

Una mesh può essere divisa in più subset. Ognuna ad esempio potrebbe avere una texture diversa e quindi si rende necessario fermare il rendering per cambiarla. Gli attributi si trovano in più forme. La prima è l'attributerange, ossia una tabella che indica per ogni subset il suo numero d'ordine, il numero di vertici e di indici presenti e l'indice degli array da dove partire a contare. Questa informazione è molto importante quando andiamo a spezzare la mesh o a leggere i valori negli array vertex e index. In memoria però l'attribute buffer è un array di interi che, per ogni faccia, specifica il numero di subset di appartenenza. Quindi ad esempio 0,0,0,0,1,2,3,3 dirà che il primo subset è composto dai primi 4 triangoli, il secondo ed il terzo da 1 ed il 4 da 2.

Per prendere gli attributeRange

mesh.GetAttributeTable

Questa restituirò un array di attributeRange, ognuno corrispondente ad una subset. Potete usare anche SetAttributeTable per inserirli. Per bloccare veramente gli attribute però dovete usare il solito lock

Dim attributi As Integer() = mesh.LockAttributeBufferArray(LockFlags.None)
mesh.UnlockAttributeBuffer(attributi)

Funziona come per il lock ma alla fine dovete passargli i valori se volete aggiornarli.

Creazione di una mesh da zero

Con queste informazioni potete modificare una mesh. Ora creiamone una da zero.

Dim m as New Mesh(numeroFacce,numeroVertici,MeshFlags.Managed ,elementi,device)

Le mesh flags sono state già spiegate nei precedenti tutorial. Ricordate questa comunque da aggiungere

MeshFlags.Use32Bit

Nel caso di mesh con almeno 32000 vertici. Elementi è l'array di vertexElement corrispondente.

Fatto questo tramite lock inserite indici e vertici. Generate anche le adiacenze e se volete spezzare la mesh inserite gli attributebuffer. Se volete potete usare anche l'istruzione computeNormal per generare in automatico le normali. ComputeTangent invece permette di generare le tangenti e le binormali in formati di vertici che le hanno (controllate anche che il numero di usage coincida con quelli del vertexdeclaration usato).  La mesh è creata, ora potete usarla o salvarla in un file.

mesh.Save("filename",adia,materiali,XFileFormat.Binary)

materiali è un array di extendedMaterial..Questo array conterrà il nome della texture ed i valori per il materiale. Con quanto scritto in questo tutorial potete già iniziare a creare il vostro editor 3D per i vostri giochi e gestire meglio i vostri vertici.  Per fare in modo che la vostra mesh sia perfetta e ottimale usate anche

mesh.Optimize(MeshFlags.OptimizeAttributeSort). Questa restituisce una mesh ottimizzata. Gli attributi da passare sono

OptimizeDeviceIndependent ottimizzazione per vecchi hardware.
OptimizeDoNotSplit : i vertici appartenenti a più subset non vengono divisi.
OptimizeIgnoreVerts: non ottimizzare i vertici
OptimizeStripeReorder :riorganizza la mesh per rendere i triangoli consecutivi. Accellera il rendering
OptimizeVertexCache :riorganizza la mesh per ottimizzare la cache dei vertici
OptimizeAttributeSort :ottimizza la mesh in modo da limitare i cambi causati dall'attribute buffer (uno stesso attribute potrebbe essere sparso nella mesh)
OptimizeCompact: elimina facce e vertici non usati.

Volete sapere se avete fatto un buon lavoro?

mesh.Validate(adia,errori)

errori è una stringa che vi avverte se ci sono errori nella mesh. In questo modo la mesh viene validata ed è pronta per il rendering o il salvataggio.