B2. Triangoli e matrici
Il progetto iniziale è un semplicissimo sorgente, quasi vuoto. Qui potete scaricare il progetto dal quale inizieremo.
In principio fu il triangolo
Come forse saprete, ogni modello tridimensionale, anche quello che all'apparenza sembra liscio e curvo, è costituito da un gran numero di
triangoli, ossia la figura geometrica più semplice in assoluto. Proprio per questa sua natura particolare, il triangolo viene chiamato
primitiva: ogni cosa che andremo a disegnare sarà costituita da triangoli. Dato che ogni primitiva ha tre lati e tre vertici,
saranno necessari almeno tre punti per determinare un triangolo: tali punto sono chiamati, banalmente, vertici. Un vertice non indica
soltanto una posizione nello spazio 3D, ma il più delle volte porta con sé altre informazioni molto utili. Nel caso più
semplice, queste informazioni rappresentano il colore del vertice. Disegnando in questo modo, il triangolo finale sarà composto da
tre diversi colori, che si mischiano gli uni con gli altri all'interno della sua superficie.
Ecco un piccolo esempio:
Fate correre il tutto et voilà! Emh... un bellissimo schermo azzurro. E il triangolo dove è finito?Imports Microsoft.Xna.FrameworkImports Microsoft.Xna.Framework.InputImports Microsoft.Xna.Framework.GraphicsPublic Class GameInherits Microsoft.Xna.Framework.Game 'Definiamo questa variabile in modo da non dover 'scrivere troppo in seguito. Contiene il percorso 'della cartella del programma Private AppPathAs String = _My .Application.Info.DirectoryPathPrivate GraphicsAs GraphicsDeviceManager 'Questo oggetto rappresenta l'effetto che useremo per 'disegnare il triangolo Private ShaderAs Effect 'Questo array contiene tutti i vertici necessari a disegnare 'un triangolo. Bisogna notare che il tipo usato ci dice 'quali informazioni possiamo includere in ogni elemento 'dell'array. In questo caso la struttura VertexPositionColor 'indica che ogni vertice (vertex) avrà una posizione '(position) e un colore (color): Private Vertices(2)As VertexPositionColor 'Questa variabile è molto importante, perchè comunica 'alla GPU quali sono i dati in ingresso, qual è la loro 'funzione e quanta memoria utilizzare Private VDeclarationAs VertexDeclaration 'Dichiariamo questa funzione subito. In poche parole, serve 'per caricare uno shader da un file *.fx Private Function GetEffect(ByVal FileNameAs String )As EffectDim CompEffectAs CompiledEffect = _ Effect.CompileEffectFromFile(FileName, _Nothing ,Nothing , _ CompilerOptions.None, _ TargetPlatform.Windows)Return New Effect(Me .GraphicsDevice, _ CompEffect.GetEffectCode, _ CompilerOptions.None,Nothing )End Function 'Questa procedura imposta i campi di ogni vertice Private Sub SetVertices() 'Primo vertice, rosso, in posizione (0,0,0) Vertices(0).Position =New Vector3(0, 0, 0) Vertices(0).Color = Color.Red 'Secondo vertice, giallo, in posizione (1,0,-1) 'Ricordate che y indica lo spostamento alto-basso 'e z avanti-indietro. -1 significa che il vertice 'è leggermente spostato in avanti rispetto 'all'asse x Vertices(1).Position =New Vector3(1, 0, -1) Vertices(1).Color = Color.Yellow 'Terzo vertice, blu, in posizione (0,0,-1) Vertices(2).Position =New Vector3(0, 0, -1) Vertices(2).Color = Color.Blue 'Inizializza l'oggetto VertexDeclaration. In questo 'caso esso dice alla GPU che i dati in input sono 'di tipo VertexPositionColor. Vedremo cosa significhi 'il campo VertexElements in seguito VDeclaration =New VertexDeclaration(Me .GraphicsDevice, _ VertexPositionColor.VertexElements)End Sub Sub New ()Me .Graphics =New GraphicsDeviceManager(Me )Me .Content.RootDirectory = "content"End Sub Protected Overrides Sub Initialize()MyBase .Initialize()End Sub Protected Overrides Sub LoadContent() 'Carica l'effetto dalla cartella del programma Shader = GetEffect(AppPath & "\SimpleShader.fx") 'Carica i vertici SetVertices()MyBase .LoadContent()End Sub Protected Overrides Sub UnloadContent()MyBase .UnloadContent()End Sub Protected Overrides Sub Update(ByVal GameTimeAs GameTime)MyBase .Update(GameTime)End Sub Protected Overrides Sub Draw(ByVal gameTimeAs GameTime)Me .Graphics.GraphicsDevice.Clear(Color.CornflowerBlue) 'Imposta la tecnica da usare. Niente di speciale per 'ora. La tecnica "Colored" semplicemente prende la 'posizione e il colore di un vertice e li disegna 'sullo schermo Shader.CurrentTechnique = Shader.Techniques("Colored")'Questa impostare serve per visualizzare il triangolo. Infatti, 'per risparmiare memoria, la GPU visualizza solo i triangoli 'rivolti verso la telecamera (ossia i cui vertici sono 'definiti in senso orario). 'Dato che non abbiamo controllato se i vertici sono 'definiti in questo modo, diciamo semplicemente di disegnare 'tutti i triangoli esistenti, tanto ce ne sarà solo uno. 'Ricordatevi dell'opzione CullMode, può tornare utile 'per scovare difetti nel posizionamento Me .GraphicsDevice.RenderState.CullMode = CullMode.None 'Inizia l'effetto Shader.Begin() 'Altra cosa importante. Ogni tecnica può essere 'divisa in più parti, da applicare in successione. 'Anche se noi useremo sempre una sola parte (passo), 'usiamo un ciclo For per essicurare la massima 'compatibilità For Each PassAs EffectPassIn Shader.CurrentTechnique.Passes 'Inizia il passo Pass.Begin()With Me .GraphicsDevice 'Passa la dichiarazione dei vertici al device grafico .VertexDeclaration = VDeclaration 'Disegna la primitiva sullo schermo. Il primo parametro 'verrà analizzato in seguito. Il secondo parametro 'specifica l'array da cui attingere i dati dei vertici. 'Il terzo indica l'offset da cui iniziare (0, perchè 'iniziamo dal primo vertice). Il quarto indica quanti 'triangoli disegnare. Dato che un triangolo è 'definito da tre vertici, il numero di triangoli sarà '1/3 di quello dei vertici. .DrawUserPrimitives(PrimitiveType.TriangleList, _ Vertices, 0, Vertices.Length / 3)End With 'Termina il passo Pass.End()Next 'Finisce l'effetto Shader.End()MyBase .Draw(gameTime)End Sub End Class
Usare la telecamera
Ah, ecco dov'era l'errore! Non abbiamo specificato nessun punto di vista, quindi è come se non stessimo guardando. Per specificare
tutte le impostazioni relative alla telecamera e molto altro ancora si usano le matrici.
In queste circostanze, una matrice non è solo un array a due dimensioni, come potrete aver pensato, ma si tratta di un vero e proprio
strumento matematico, a dir la verità molto potente. Non è essenziale conoscere il funzionamento intrinseco delle matrici
per quanto riguarda l'algebra: basti sapere che una matrice rappresenta, grossomodo, una tabella contenente valori numerici, sulla quale
possono essere eseguite operazioni matematiche. Se ce ne sarà bisogno, specificherò io stesso le proprietà che si devono
conoscere, tuttavia nessuno vieta di approfondire l'argomento.Come dicevo, la telecamera viene direzionata e governata da matrici, in genere due: la matrice di vista (View) e quella di proiezione (Projection). La prima specifica la posizione dell'osservatore (in questo caso, l'utente) e il punto verso il quale l'osservatore sta guardando; la seconda, invece, ci dice quanta parte di "mondo" possiamo vedere, quanto lontano e quanto vicino possiamo mettere a fuoco gli oggetti. Fortunatamente, esiste già la classe Matrix, che rappresenta la matrice, e tale classe espone già una marea di funzioni che possiamo usare per creare facilmente matrici per tutti gli usi possibili. Potremmo dividere i tipi di matrici in due gruppi: matrici di impostazione e matrici di trasformazione. Ora useremo quelle appartenenti al primo gruppo.
Nel codice di prima, aggiungiamo il codice necessario:
Public Class Game '... 'Matrice di vista Private ViewMatrixAs Matrix 'Matrice di proiezione Private ProjectionMatrixAs Matrix '... Protected Overrides Sub LoadContent() Shader = GetEffect(AppPath & "\SimpleShader.fx") SetVertices() 'Imposta ViewMatrix con la funzione Matrix.CreateLookAt, che ha 'il precipuo scopo di creare una matrica di vista. 'Il primo argomento è la posizione dell'oservatore. Dato che il 'triangolo che abbiamo tracciato giace su un solo piano (xz), 'posizioniamo la telecamera 3 unità al di sopra di esso. 'Il secondo argomento è il punto da guardare. In questo esempio 'fisseremo l'origine degli assi e, secondo la definizione di 'vertici che abbiamo dato, dovremmo vedere il vertice rosso 'esattamente al centro dello schermo. 'Il terzo parametro è un vettore speciale, che definisce la 'direzione "in alto" rispetto all'osservatore (serve per 'controllare la giusta angolazione). Dato che noi stiamo 'guardando dall'asse Y direttamente verso il centro del 'sistema cartesiano, abbiamo gli occhi paralleli al piano xz. 'Quindi, per noi, il vettore "in alto" corrisponde a un 'vettore parallelo a xz che si diriga nel verso negativo 'di z. Se non avete capito, guardate lo schema a seguire. ViewMatrix = Matrix.CreateLookAt( _New Vector3(0, 3, 0), _New Vector3(0, 0, 0), _New Vector3(0, 0, -1)) 'Imposta ProjectionMatrix con la funzione 'Matrix.CreatePerspectiveFieldOfView. 'Il primo argomento specifica quale porzione di mondo possiamo 'osservare. Questo parametro consiste in un angolo, misurato 'in radianti, che dice qual è l'ampiezza del nostro campo 'visivo. Normalmente, un uomo ha un campo visivo di 45°-60°, ma 'esistono animali, come il camaleonte, che riescono ad arrivare 'molto oltre, fino a quasi 180°. Noi specificheremo 45°, che in 'radianti corrisponde a π/4. La classe MathHelper è 'una specie di ampliamento di System.Math ed espone come costante 'anche il valore di cui abbiamo bisogno. 'Il secondo argomento specifica qual è la proporzione tra 'larghezza e altezza dello schermo. Non abbiamo bisogno di 'inventarci niente, poiché la proprietà Viewport.AspectRatio 'definisce già questo valore. 'Il terzo argomento è detto Near Clipping Plane e 'costituisce la distanza minima per far sì che un oggetto 'venga renderizzato. In pratica, tutti gli oggetti che distano 'dal nostro punto di vista meno di una unità non verranno 'disegnati sullo schermo. 'Il quarto argomento è detto Far Clipping Plane e 'costituisce la distanza massima per far sì che un oggetto 'venga renderizzato. In pratica, tutti gli oggetti che distano 'dal nostro punto di vista più di 100 unità non verranno 'disegnati sullo schermo. ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView( _ MathHelper.PiOver4, _Me .GraphicsDevice.Viewport.AspectRatio, _ 1, 100)MyBase .LoadContent()End Sub Protected Overrides Sub Draw(ByVal gameTimeAs GameTime)Me .Graphics.GraphicsDevice.Clear(Color.CornflowerBlue) 'Imposta la tecnica da usare. Niente di speciale per 'ora. La tecnica "Colored" semplicemente prende la 'posizione e il colore di un vertice e li disegna 'sullo schermo Shader.CurrentTechnique = Shader.Techniques("Colored") 'Per far sì che il triangolo venga davvero disegnato, 'è necessario passare come parametri le matrici alla 'tecnica che si sta utilizzando, proprio come si farebbe 'con un metodo in vb.net. La proprietà Effect.Parameters 'è un dizionario che contiene i parametri della 'tecnica corrente. Vedremo più in là questo 'meccanismo nel dettaglio. Shader.Parameters("View").SetValue(ViewMatrix) Shader.Parameters("Projection").SetValue(ProjectionMatrix) 'Ah, ultima cosa. Bisogna anche specificare una matrice che 'dica quali sono le trasformazioni del "mondo 3D". Questo tipo 'di matrice appartiene alle matrici di trasformazione. Dato 'che per ora non ne abbiamo bisogno, inseriamo come 'parametro l'Identità, uno speciale tipo 'di matrice che costituisce l'elemento neutro del 'prodotto matriciale, proprio come l'uno nel prodotto 'tra numeri. In breve, questa matrice non influenza 'in nessun modo il triangolo. Shader.Parameters("World").SetValue(Matrix.Identity)Me .GraphicsDevice.RenderState.CullMode = CullMode.None Shader.Begin()For Each PassAs EffectPassIn Shader.CurrentTechnique.Passes Pass.Begin()With Me .GraphicsDevice .VertexDeclaration = VDeclaration .DrawUserPrimitives(PrimitiveType.TriangleList, _ Vertices, 0, Vertices.Length / 3)End With Pass.End()Next Shader.End()MyBase .Draw(gameTime)End Sub End Class
Schema riassuntivo
E il fantastico risultato (il vertice rosso è al centro come ci aspettavamo):
Schema riassuntivo
The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.



