B6. Colorazione e illuminazione
Il paesaggio sambrerà anche bello così, ma è ancora un po' scheletrico, nel vero senso della parola. Vediamo di movimentare un po' le cose.
Un colore, più colori
Fin'ora abbiamo sempre usato un colore unico per ogni vertice del terreno. Ora cambiamo il colore a seconda dell'altezza del vertice:
quelli più bassi saranno azzurri (acqua), quelli al centro verdi (erba), quelli un po' più in alto marroni (roccia), mentre
i più alti bianchi (neve). Basta modificare poco codice:
E si ottiene questo risultato:Public Class Game '... Private Sub SetVertices() ReDim Vertices(TerrainWidth * TerrainLength - 1) 'Diviamo il terreno in quattro strisce di altezza Dim StripAs Single = (MaxHeight - MinHeight) / 4For XAs Int32 = 0To TerrainWidth - 1For ZAs Int32 = 0To TerrainLength - 1With Vertices(X + Z * TerrainWidth)Dim YAs Single = HeightData(X, Z) 'Impostiamo la posizione .Position =New Vector3(X, Y, -Z)If Y <= MinHeight + StripThen 'Acqua .Color = Color.LightBlueElseIf (Y > MinHeight + Strip)And _ (Y <= MinHeight + Strip * 2)Then 'Erba .Color =New Color(115, 171, 58)ElseIf (Y > MinHeight + Strip * 2)And _ (Y <= MinHeight + Strip * 3)Then 'Roccia .Color =New Color(125, 85, 49)Else 'Neve .Color = Color.WhiteEnd If End With Next Next End Sub '... Protected Overrides Sub Draw(ByVal gameTimeAs GameTime)Me .Graphics.GraphicsDevice.Clear(Color.CornflowerBlue) Shader.CurrentTechnique = Shader.Techniques("Colored") Shader.Parameters("View").SetValue(ViewMatrix) Shader.Parameters("Projection").SetValue(ProjectionMatrix) Shader.Parameters("World").SetValue( _ Matrix.CreateTranslation( _New Vector3(-TerrainWidth / 2, 0, TerrainLength / 2)) * _ Matrix.CreateRotationY(Angle)) 'Ora il colore è completo, togliamo il fil di ferro Shader.Begin()For Each PassAs EffectPassIn Shader.CurrentTechnique.Passes Pass.Begin()With Me .GraphicsDevice .VertexDeclaration = VDeclaration .DrawUserIndexedPrimitives(PrimitiveType.TriangleList, _ Vertices, 0, Vertices.Length, _ Indices, 0, Indices.Length / 3)End With Pass.End()Next Shader.End()MyBase .Draw(gameTime)End Sub End Class
Paesaggio colorato
Illuminare il terreno
Per illuminare il terreno faremo uso di una nuova tecnica, ColoredPlus. Non badate al nome, perchè può essere deciso arbitrariamente
durante la scrittura del file shader e io ho scelto questo nome fantasioso. Per usare questa tecnica ci sarà bisogno di tre nuovi
parametri da passare:
- LightEnabled : determina se la tecnica di illuminazione è attiva. Noi useremo, ovviamente, True, altrimenti non varrebbe la pena di usare questa tecnica
- LightDirection : la direzione della luce. È definita da un Vector3 di lunghezza unitaria
- AmbientFactor : la percentuale di diffusione ambientale. Se questo valore è 0, i triangoli non illuminati direttamente dal raggio di luce saranno completamente neri. Dato che ciò non accade in realtà, aggiungiamo questo fattore, che sarebbe come un tocco di luce aggiuntivo per far sì che anche le parte buie del terreno non siano nere
Il calcolo della luce
Questo disegno mostra i raggi di luce in arrivo, e le normali di ciascuna faccia. È evidente che le facce direttamente rivolte verso la luce saranno quelle più illuminate, mentre quelle inclinate riceveranno meno luce. Le normali servono proprio a questo, ossia a vedere di quanto la superficie esaminata sia inclinata rispetto ai raggi entranti e, di conseguenza, quanta luce riceva e diffonda. Dato che, per i calcoli, non è possibile considerare un'intera superficie, ma solo un vertice, calcoleremo la stessa normale per tutti i tre vertici di un triangolo. Tuttavia, la struttura che abbiamo usato per contenere i vertici (ossia VertexPositionColor) non offre nessun campo in cui si possa memorizzare la normale, perciò dobbiamo dichiarare una nuova struttura, in quanto non ne esiste una predefinita per questo compito. I dettagli del codice verranno spiegati più in là nel tutorial: per ora definiamo VertexPositionNormalColor e sostituiamola come tipo in tutte le vecchie espressioni
Ora serve un metodo che calcoli le normali:Imports Microsoft.Xna.FrameworkImports Microsoft.Xna.Framework.InputImports Microsoft.Xna.Framework.Graphics 'E' necessario un Imports aggiuntivo per un 'calcolo di basso livello sulla memoria Imports System.Runtime.InteropServicesPublic Class GameInherits Microsoft.Xna.Framework.Game#Region "Strutture aggiuntive" 'Definisce la dimensione in Byte di un Double Private Shared DoubleSizeAs Byte = Marshal.SizeOf(GetType (Double ))Private Structure VertexPositionNormalColorPublic PositionAs Vector3Public NormalAs Vector3Public ColorAs ColorPublic Shared SizeInBytesAs Int16 = 7 * 4Public Shared VertexElements()As VertexElement = _New VertexElement() _ { _New VertexElement(0, 0, VertexElementFormat.Vector3, _ VertexElementMethod.Default, _ VertexElementUsage.Position, 0), _New VertexElement(0, DoubleSize * 3, _ VertexElementFormat.Color, _ VertexElementMethod.Default, _ VertexElementUsage.Color, 0), _New VertexElement(0, DoubleSize * 4, _ VertexElementFormat.Vector3, _ VertexElementMethod.Default, _ VertexElementUsage.Normal, 0) _ }End Structure #End Region '... Private Vertices()As VertexPositionNormalColor '... Private Sub LoadHeightMap(ByVal HeightMapAs Texture2D) TerrainWidth = HeightMap.Width TerrainLength = HeightMap.HeightDim Colors(TerrainWidth * TerrainLength - 1)As Color HeightMap.GetData(Colors)For IAs Int32 = 0To TerrainWidth - 1 ReDim HeightData(I, TerrainLength - 1)Next For XAs Int32 = 0To TerrainWidth - 1For ZAs Int32 = 0To TerrainLength - 1 HeightData(X, Z) = MinHeight + _ (Colors(X + Z * TerrainWidth).R / 255) * _ (MaxHeight - MinHeight)Next Next VDeclaration =New VertexDeclaration(Me .GraphicsDevice, _ VertexPositionNormalColor.VertexElements)End Sub '... End Class
'... 'Calcola le normali di ogni vertice Private Sub SetNormals() 'Prima le azzera tutte For IAs Int32 = 0To Vertices.Length - 1 Vertices(I).Normal =New Vector3(0, 0, 0)Next 'Itera attraverso tutti gli indici definiti, 'prendendone tre alla volta. Ricordate che tre indici 'definiscono sempre un triangolo. Infatti ci eravamo 'proposti di calcolare tre normali uguali per i 'vertici di ogni triangolo For IAs Int16 = 0To (Indices.Length / 3) - 1 'Ottiene i tre indici che definiscono un triangolo Dim Index1As Int16 = Indices(I * 3)Dim Index2As Int16 = Indices(I * 3 + 1)Dim Index3As Int16 = Indices(I * 3 + 2) 'Calcola i vettori che rappresentano due lati 'del triangolo. Se non vi è chiara 'l'operazione, guardate il disegno successivo Dim Side1As Vector3 = _ Vertices(Index1).Position - Vertices(Index3).PositionDim Side2As Vector3 = _ Vertices(Index1).Position - Vertices(Index2).Position 'Esegue un prodotto vettoriale, che restituisce un momento, 'perpendicolare al piano di giacenza degli 'altri due: la normale Dim NormalAs Vector3 = Vector3.Cross(Side1, Side2) 'Aggiunge il vettore ottenuto in questo modo al 'campo Normal di ogni vertice. Notate che l'operazione 'è una somma vettoriale e non un semplice 'assegnamento. Questo viene fatto perchè ogni 'vertice può appartenere anche a due o tre triangoli. 'In quel caso, prenderà una parte di normale 'dal calcolo di ciascuna delle facce che forma Vertices(Index1).Normal += Normal Vertices(Index2).Normal += Normal Vertices(Index3).Normal += NormalNext 'Normalizza tutte le normali. Scusate il gioco di parole. 'L'operazione di normalizzazione non c'entra affatto 'con le normali. Semplicemente reimposta le componenti 'x, y e z del vettore affinchè la sua lunghezza 'totale sia uno. Infatti, come detto precedentemente, 'la normale (in questo caso) deve essere un vettore 'di lunghezza unitaria. For Each VAs VertexPositionNormalColorIn Vertices V.Normal.Normalize()Next End Sub '...
Il calcolo della normale
In questa figura, 1, 2 e 3 indicano i vertici contrassegnati dagli indici Index1, Index2 e Index3. La differenza tra la posizione di 1 e la posizione di 3 (ossia l'operazione Vertices(Index1).Position - Vertices(Index3).Position) dà come risultato il vettore azzurro che congiunge 3 con 1: e lo stesso vale per 2 e 1. Una volta ottenuti questi vettori, il loro prodotto vettoriale fornisce in output il vettore verde evidenziato in figura: quella è la normale.
Dopo aver aggiunto il metodo SetNormals() in LoadContent(), procediamo a modificare Draw:
'Il risultato finale:... Protected Overrides Sub Draw(ByVal gameTimeAs GameTime)Me .Graphics.GraphicsDevice.Clear(Color.CornflowerBlue) 'Questa è la nuova tecnica, ColoredPlus Shader.CurrentTechnique = Shader.Techniques("ColoredPlus") Shader.Parameters("View").SetValue(ViewMatrix) Shader.Parameters("Projection").SetValue(ProjectionMatrix) Shader.Parameters("World").SetValue( _ Matrix.CreateTranslation( _New Vector3(-TerrainWidth / 2, 0, TerrainLength / 2)) * _ Matrix.CreateRotationY(Angle)) 'Imposta la direzione della luce. I valori delle componenti 'x, y e z sono stati ottenuti con una serie di tentativi, 'per stabilire quale fosse l'angolazione giusta per un 'buon risultato Dim LightDirectionAs New Vector3(-0.27, 0.33, -0.86) 'Ruota la direzione della luce insieme col paesaggio LightDirection = Vector3.Transform(LightDirection, _ Matrix.CreateRotationY(Angle)) 'Normalizza la direzione LightDirection.Normalize() 'Attiva l'illuminazione Shader.Parameters("LightEnabled").SetValue(True) 'Imposta la direzione della luce Shader.Parameters("LightDirection").SetValue(LightDirection) 'Imposta il fattore di diffusione ambientale sul 10%. La '"F" dopo 0.1 indica di considerare 0.1 come se fosse un 'Single, poichè il metodo SetValue richiede 'obbligatoriamente il tipo Single quando si tratta di valori 'decimali. Era la stessa cosa scrivere CSng(0.1) Shader.Parameters("AmbientFactor").SetValue(0.1F) Shader.Begin()For Each PassAs EffectPassIn Shader.CurrentTechnique.Passes Pass.Begin()With Me .GraphicsDevice .VertexDeclaration = VDeclaration .DrawUserIndexedPrimitives(PrimitiveType.TriangleList, _ Vertices, 0, Vertices.Length, _ Indices, 0, Indices.Length / 3)End With Pass.End()Next Shader.End()MyBase .Draw(gameTime)End Sub '...
Paesaggio colorato e illuminato
Molto meglio di prima, vero?
The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.



