Casino online









Mercato forex






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:
Public Class Game

    '...
    
    Private Sub SetVertices()
        ReDim Vertices(TerrainWidth * TerrainLength - 1)

        'Diviamo il terreno in quattro strisce di altezza
        Dim Strip As Single = (MaxHeight - MinHeight) / 4
        For X As Int32 = 0 To TerrainWidth - 1
            For Z As Int32 = 0 To TerrainLength - 1
                With Vertices(X + Z * TerrainWidth)
                    Dim Y As Single = HeightData(X, Z)

                    'Impostiamo la posizione
                    .Position = New Vector3(X, Y, -Z)

                    If Y <= MinHeight + Strip Then
                        'Acqua
                        .Color = Color.LightBlue
                    ElseIf (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.White
                    End If
                End With
            Next
        Next
    End Sub
    
    '...
    
    Protected Overrides Sub Draw(ByVal gameTime As 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 Pass As EffectPass In 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 
E si ottiene questo risultato:


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: Inoltre, è necessario aggiungere un po' di nuovi concetti al vostro bagaglio culturale. Per calcolare correttamente la diffusione della luce, lo shader ha bisogno di conoscere una particolare caratteristica dei triangoli su cui tale luce incide: la normale. La normale è semplicemente un vettore di lunghezza unitaria perpendicolare a una superficie (nel nostro caso, alla superficie del triangolo). Vi spiego subito perchè questo è necessario.


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
Imports Microsoft.Xna.Framework
Imports Microsoft.Xna.Framework.Input
Imports Microsoft.Xna.Framework.Graphics
'E' necessario un Imports aggiuntivo per un
'calcolo di basso livello sulla memoria
Imports System.Runtime.InteropServices

Public Class Game
    Inherits Microsoft.Xna.Framework.Game

#Region "Strutture aggiuntive"
    'Definisce la dimensione in Byte di un Double
    Private Shared DoubleSize As Byte = Marshal.SizeOf(GetType(Double))

    Private Structure VertexPositionNormalColor
        Public Position As Vector3
        Public Normal As Vector3
        Public Color As Color

        Public Shared SizeInBytes As Int16 = 7 * 4
        Public 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 HeightMap As Texture2D)
        TerrainWidth = HeightMap.Width
        TerrainLength = HeightMap.Height

        Dim Colors(TerrainWidth * TerrainLength - 1) As Color
        HeightMap.GetData(Colors)

        For I As Int32 = 0 To TerrainWidth - 1
            ReDim HeightData(I, TerrainLength - 1)
        Next

        For X As Int32 = 0 To TerrainWidth - 1
            For Z As Int32 = 0 To 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 
Ora serve un metodo che calcoli le normali:
    '...

    'Calcola le normali di ogni vertice
    Private Sub SetNormals()
        'Prima le azzera tutte
        For I As Int32 = 0 To 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 I As Int16 = 0 To (Indices.Length / 3) - 1
            'Ottiene i tre indici che definiscono un triangolo
            Dim Index1 As Int16 = Indices(I * 3)
            Dim Index2 As Int16 = Indices(I * 3 + 1)
            Dim Index3 As 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 Side1 As Vector3 = _
                Vertices(Index1).Position - Vertices(Index3).Position
            Dim Side2 As 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 Normal As 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 += Normal
        Next

        '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 V As VertexPositionNormalColor In 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:
   '...

    Protected Overrides Sub Draw(ByVal gameTime As 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 LightDirection As 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 Pass As EffectPass In 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
    
    '... 
Il risultato finale:

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.