Casino online









Mercato forex






B5. Le Height Maps


Anatomia di una Height Map
La Height Map (mappa di altezza) serve per definire l'altezza di ogni singolo vertice che compone un paesaggio: è, infatti, molto comodo avere tutte le altezze salvate in un file, dal quale poi, possiamo caricare tutto il terreno mediante due semplici metodi. In genere, le height map non sono altro che immagini, in cui ogni pixel rappresenta l'altezza del vertice corrispondente. Di conseguenza, le dimensioni dell'immagine determinano anche le dimensioni del terreno 3D: un'immagine di 128x128 pixel produrrà un terreno lungo 128 unità e largo 128 unità (ovviamente è possibile ingrandire o ridurre le dimensioni e del terreno e dell'immagine a proprio piacimento). Il singolo pixel viene rappresentato da una combinazione di 4 componenti: Alpha (trasparenza), Red (Rosso), Green (Verde) e Blue (Blu). Perciò basta considerare una o più di queste come se fossero un'altezza. Di solito, le height map sono totalmente in bianco e nero, perciò i valori RGB sono sempre uguali. Nel nostro caso, il bianco definirà una montagna o un altopiano, il grigio un terreno a metà e il nero i fondali mairini. In questo capitolo preleveremo la componente Rossa di ogni pixel della heightmap per definire la coordinata Y del vertice corrispondente.


Useremo questa Height Map, prelevata da questo bellissimo sito



Leggere l'HeightMap e impostare i vertici
Dopo aver salvato l'immagine sul vostro hard disk, copiatela nella cartella bin/Debug del vostro progetto. Ecco il codice:
Imports Microsoft.Xna.Framework
Imports Microsoft.Xna.Framework.Input
Imports Microsoft.Xna.Framework.Graphics

Public Class Game
    Inherits Microsoft.Xna.Framework.Game

    'Cominciamo a dividere il sorgente in sezioni, per
    'renderlo più ordinato. Alla fine di questo
    'tutorial mi ringrazierete, perchè ci sarà
    'un bel po' di codice.
    'Usiamo lo statement #Region, che non ho spiegato nella
    'guida sul Vb.Net. È molto semplice:
    '#Region "Nome regione"
    '   'codice
    '#End Region
    'Non ha alcuna funzione dinamica: serve solo per rendere
    'il codice più ordinato

#Region "Variabili globali"

    Private AppPath As String = _
        My.Application.Info.DirectoryPath

    Private Graphics As GraphicsDeviceManager

    Private Shader As Effect
    Private Vertices(5) As VertexPositionColor
    Private Indices(11) As Int32
    Private VDeclaration As VertexDeclaration

    Private ViewMatrix As Matrix
    Private ProjectionMatrix As Matrix
    Private WorldMatrix As Matrix

    Private DX, DZ As Single
    Private Angle As Single

    'La variabile HeightData è un array a 
    'due dimensioni, che a due coordinate x e z
    'fa corrispondere una coordinata y. Ad esempio
    'HeightData(1, 2) restituirà l'altezza y
    'del vertice di coordinate x = 1, z = -2
    '(ricordate sempre che z è invertito)
    Private HeightData(,) As Single
    'Definiamo qui come costanti l'altezza minima
    'e massima raggiungibili da un vertice. Dato
    'che la componente R di un colore può
    'andare da 0 a 255, certo non vorremmo che un
    'vertice abbia altezza 255, altrimenti il
    'terreno sarebbe enormemente deformato verso
    'l'alto!
    Private Const MinHeight As Single = 0
    Private Const MaxHeight As Single = 30
    'Queste variabili assumeranno il valore di
    'larghezza e lunghezza del terreno
    Private TerrainWidth, TerrainLength As Int32
#End Region

#Region "Funzioni utili"

    Private Function GetEffect(ByVal FileName As String) As Effect
        Dim CompEffect As CompiledEffect = _
            Effect.CompileEffectFromFile(FileName, _
            Nothing, Nothing, _
            CompilerOptions.None, _
            TargetPlatform.Windows)

        Return New Effect(Me.GraphicsDevice, _
            CompEffect.GetEffectCode, _
            CompilerOptions.None, Nothing)
    End Function

    'Ridefiniamo la cara funzione GetTexture, già usata
    'nel tutorial sul 2D
    Private Function GetTexture(ByVal FileName As String)
        Return Texture2D.FromFile(Me.GraphicsDevice, FileName)
    End Function

#End Region

#Region "Caricamento Heightmap"
    'Carica l'array HeightData
    Private Sub LoadHeightMap(ByVal HeightMap As Texture2D)
        'Imposta le dimensioni del terreno
        TerrainWidth = HeightMap.Width
        TerrainLength = HeightMap.Height '2D!

        'Crea un array tabulare per contenere i colori
        'di ogni pixel dell'heightmap
        Dim Colors(TerrainWidth * TerrainLength - 1) As Color
        'Riempie l'array prelevando i colori dall'immagine
        HeightMap.GetData(Colors)

        'Ridimensiona HeightData.
        'Ricordate che ReDim ha un effetto particolare 
        'sugli array a due dimensioni. Infatti,
        'bisogna ridimensionare ogni singola dimensione
        'dell'array. Per rinfrescarvi la memoria,
        'guardate il capitolo sugli array nella guida
        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
                'Vedi Nota 1 per la spiegazione di questo calcolo
                HeightData(X, Z) = MinHeight + _
                    (Colors(X + Z * TerrainWidth).R / 255) * _
                    (MaxHeight - MinHeight)
            Next
        Next

        VDeclaration = New VertexDeclaration(Me.GraphicsDevice, _
            VertexPositionColor.VertexElements)
    End Sub

    'Ora che abbiamo la dimensione del terreno e l'altezza di
    'ogni vertice, popoliamo l'array Vertices
    Private Sub SetVertices()
        'Consideriamo Vertices come un array tabulare
        ReDim Vertices(TerrainWidth * TerrainLength - 1)

        For X As Int32 = 0 To TerrainWidth - 1
            For Z As Int32 = 0 To TerrainLength - 1
                With Vertices(X + Z * TerrainWidth)
                    'Impostiamo la posizione
                    .Position = New Vector3(X, HeightData(X, Z), -Z)
                    'E il colore
                    .Color = Color.White
                End With
            Next
        Next
    End Sub

    'Questo metodo è più complesso di prima,
    'quindi fate attenzione
    Private Sub SetIndices()
        'Vedi Nota 2 per la spiegazione di questo codice
        ReDim Indices((TerrainWidth - 1) * (TerrainLength - 1) * 6 - 1)

        'Noi stiamo leggendo una tabella, ma Indices
        'è un array a una dimensione, quindi ci
        'serve un contatore per impostarne i valori
        Dim Counter As Int32 = 0

        For X As Int32 = 0 To TerrainWidth - 2
            For Z As Int32 = 0 To TerrainLength - 2
                'Questi valori rappresentano delle coordinate
                'nell'array tabulare Vertices. Infatti,
                'ogni quattro pixel nell'immagine, si hanno
                'quattro vertici, perciò due triangoli
                'e quindi 6 indici. È un po' confusionario,
                'lo so, ma fateci l'abitudine.

                'Vertice inferiore sinistro
                Dim LowerLeft As Int16 = X + Z * TerrainWidth
                'Vertice inferiore destro
                Dim LowerRight As Int16 = (X + 1) + Z * TerrainWidth
                'Vertice superiore sinistro
                Dim TopLeft As Int16 = X + (Z + 1) * TerrainWidth
                'Vertice superiore destro
                Dim TopRight As Int16 = (X + 1) + (Z + 1) * TerrainWidth

                'Ricordate che le coordinate X e Z sono prese da
                'un'immagine a due dimensioni. Perciò
                'leggendo l'immagine dall'alto verso il basso, la
                'coordinata y dei pixel aumenta

                'Primo triangolo, formato, in ordine, dai vertici:
                '- Superiore sinistro
                '- Inferiore destro
                '- Inferiore sinistro
                Indices(Counter) = TopLeft
                Indices(Counter + 1) = LowerRight
                Indices(Counter + 2) = LowerLeft
                Counter += 3

                'Secondo triangolo, formato, in ordine, dai vertici:
                '- Superiore sinistro
                '- Superiore destro
                '- Inferiore destro
                Indices(Counter) = TopLeft
                Indices(Counter + 1) = TopRight
                Indices(Counter + 2) = LowerRight
                Counter += 3
            Next
        Next
    End Sub
#End Region

#Region "Gestione risorse"

    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()
        Shader = GetEffect(AppPath & "\SimpleShader.fx")
        'Carica la HeightMap
        LoadHeightMap(GetTexture(AppPath & "\HeightMap.bmp"))
        'Imposta i vertici
        SetVertices()
        'Imposta gli indici
        SetIndices()

        'Spostiamo la telecamera molto in fuori, poiché
        'il terreno sarà piuttosto grande
        ViewMatrix = Matrix.CreateLookAt( _
            New Vector3(70, 100, -70), _
            New Vector3(0, 0, 0), _
            New Vector3(0, 1, 0))

        ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView( _
            MathHelper.PiOver4, _
            Me.GraphicsDevice.Viewport.AspectRatio, _
            1, 500)

        MyBase.LoadContent()
    End Sub

    Protected Overrides Sub UnloadContent()
        MyBase.UnloadContent()
    End Sub

#End Region

#Region "Aggiornamento mondo 3D"
    Protected Overrides Sub Update(ByVal GameTime As GameTime)
        'Facciamo sì che il terreno ruoti, per poterlo
        'ammirare da ogni angolazione
        Angle += MathHelper.Pi / 600
        MyBase.Update(GameTime)
    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)
        'Trasliamo il mondo in modo che tutto il terreno sia
        'al centro del nostro sguardo, puoi ruotiamolo
        Shader.Parameters("World").SetValue( _
            Matrix.CreateTranslation( _
            New Vector3(-TerrainWidth / 2, 0, TerrainLength / 2)) * _
            Matrix.CreateRotationY(Angle))

        Me.GraphicsDevice.RenderState.FillMode = FillMode.WireFrame

        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 Region
End Class 
Allora, passiamo a spiegare i tre passaggi più complessi del codice: E il risultato:


Il paesaggio in wireframe









 

The Totem's Lair - Copyright (C) 2009
È vietata la riproduzione sia totale che parziale del sito.