Casino online









Mercato forex






B15. Creare una Refraction Map


Per creare una mappa di rifrazione, dobbiamo immaginare di tagliare tutta la scenza all'altezza dell'acqua: ogni cosa che si trova sotto il piano di sezione, sarà ciò che effettivamente viene visto attraverso la rifrazione. Per far questo, prima di tutto, bisogna dichiarare delle nuove variabili:
    Private WaterHeight As Single = 5.0
    Private RefractionTarget As RenderTarget2D
    Private RefractionMap As Texture2D 
WaterHeight rappresenta l'altezza dell'acqua, ossia la coordinata Y alla quale verrà posta la sua superficie. RefractionMap, invece, rappresenta un'immagine, che conterrà, ovviamente, la refraction map da noi calcolata in precedenza. RefractionTarget è una variabile di tipo nuovo: RenderTarget2D. Il render target ("bersaglio del rendering") costituisce il flusso sul quale viene disegnata la scena vera e propria. Abbiamo sempre usato un render target, senza accorgercene: lo schermo. Lo stream ad esso associato, che inviava informazioni direttamente al monitor per la visualizzazione, era il flusso di dati che usavamo. Ora che dobbiamo disegnare una speciale map, ed è necessario vedere una parte della scena, basta ridirigere il rendering da un'altra parte, ossia su un render target alternativo, che in questo caso si chiama RefractionTarget: ciò che vi verrà disegnato sopra sarà poi trasferito su una semplice immagine, la refraction map, appunto. Per inizializzare il render target, serve questo codice:
    Protected Overrides Sub LoadContent()
        Shader = GetEffect(AppPath & "\SimpleShader.fx")
        Grass = GetTexture(AppPath & "\grass.dds")
        Sand = GetTexture(AppPath & "\sand.dds")
        Rock = GetTexture(AppPath & "\rock.dds")
        Snow = GetTexture(AppPath & "\snow.dds")

        For I As Byte = 0 To 4
            SkyFaces(I) = GetTexture(AppPath & "\skyface" & (I + 1) & ".png")
        Next

        Dim Params As PresentationParameters = _
            Me.GraphicsDevice.PresentationParameters
        'Inizializza il render target. Il primo parametro è
        'il device grafico da usare; il secondo e il terzo sono
        'le dimensioni della finestra; il quarto indica i livelli
        'di mip map da creare (uno solo in questo caso); il
        'quinto indica il formato di superficie da usare: lo
        'otteniamo direttamente dal device
        RefractionTarget = New RenderTarget2D(Me.GraphicsDevice, _
            Params.BackBufferWidth, Params.BackBufferHeight, 1, _
            Me.GraphicsDevice.DisplayMode.Format)

        LoadHeightMap(GetTexture(AppPath & "\HeightMap.bmp"))
        LoadSkyDome()
        SetVertices()
        SetIndices()
        SetNormals()
        CopyToBuffer()

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

        CameraPosition = New Vector3(0, 40, TerrainLength / 2)
        Mouse.SetPosition(Me.GraphicsDevice.Viewport.Width / 2, _
                Me.GraphicsDevice.Viewport.Height / 2)
        PrevMouseState = Mouse.GetState
        UpdateView()

        MyBase.LoadContent()
    End Sub


Sezionare il mondo
Per prima cosa, dobbiamo tagliare in due il mondo 3D e far sì che sia visibile solo la parte di terreno che la telecamera può fisicamente vedere attraverso l'acqua. Questa è la funzione usata per creare un piano (Plane):
    'Crea un piano a partire dall'altezza e dalla sua normale
    Private Function CreatePlane(ByVal Height As Single, _
        ByVal Normal As Vector3, ByVal ClipAbove As Boolean)
        Normal.Normalize()

        Dim PlaneCoef As New Vector4(Normal, Height)
        'HSMatrix sta per Homogeneous Space Matrix
        Dim HSMatrix As Matrix = _
            Matrix.Transpose( _
            Matrix.Invert(ViewMatrix * ProjectionMatrix))

        If ClipAbove Then
            PlaneCoef *= -1
        End If
        PlaneCoef = Vector4.Transform(PlaneCoef, HSMatrix)

        Return New Plane(PlaneCoef)
    End Function 
Non ho commentato il sorgente perchè ci sarebbero troppe cose da dire. Prima di tutto, bisogna sapere cosa occorre per creare un piano: è necessario una normale, che ne indichi l'inclinazione, e un'altezza, che specifichi la distanza del piano dal centro. Si può dimostrare geometricamente che una distanza e una normale indicano uno e un solo piano dello spazio. Ora abbiamo questi due dati racchiusi in un Vector4. Una volta che avremo creato il piano, esso eliminerà automaticamente dalla scena tutto ciò che vi si trova al di sotto. Aggiungendo un'altra variabile booleana, possiamo anche introdurre una variazione sul tema e decidere se eliminare tutto ciò che si trova sotto o sopra: in questo modo potremo riusare la funzione per creare la reflection map. Per invertire la direzione di clipping (let. "tagliar via"), basta invertire il Vector4, moltiplicandolo per -1. Tuttavia, avrete notato che c'è dell'altro codice. Infatti, il processo di clipping avviene dopo che tutti i vertici sono passati attraverso il vertex shader e le cui coordinate, quindi, sono state trasformate in coordinate normalizzate per il device grafico. Questo significa che ogni vertice visibile possiede coordinate X e Y comprese tra -1 e 1, e Z compresa tra 0 e 1; ma noi abbiamo passato la normale e l'altezza in riferimento allo spazio 3d standard! Per questo, bisogna trasformare il vettore a quattro dimensioni PlaneCoef con la trasformazione inversa rispetto a quella operata dal vertex shader. Dato che quest'ultima moltiplica le posizioni per una matrice composta da View e Projection, noi dovremmo eseguire l'operazione inversa. Siccome non si può "dividere per una matrice", bisogna moltiplicare il vettore per la trasposta dell'invertita della matrice originaria. Non pretendo che capiate il funzionamento matematico, ma è necessario capire perchè lo si fa.


Disegnare la Refraction Map
Abbiamo completato la funzione che crea piani di sezione. Prima di usarla, dobbiamo apportare un'importante modifica al sorgente. Dato che avremo bisogno di disegnare solo il terreno per ottenere una mappa corretta, bisogna racchiudere tutto il codice che esegue questo compito in una nuova procedura. Modificate Draw come segue:
#Region "Aggiornamento mondo 3D"

    Private Sub DrawTerrain()
        Shader.CurrentTechnique = Shader.Techniques("MultiTextured")

        Shader.Parameters("View").SetValue(ViewMatrix)
        Shader.Parameters("Projection").SetValue(ProjectionMatrix)
        Shader.Parameters("World").SetValue( _
            Matrix.CreateTranslation( _
            New Vector3(-TerrainWidth / 2, 0, TerrainLength / 2)))

        Dim LightDirection As New Vector3(27.73, -17.67, 6.14)
        LightDirection.Normalize()
        Shader.Parameters("LightEnabled").SetValue(True)
        Shader.Parameters("LightDirection").SetValue(LightDirection)
        Shader.Parameters("AmbientFactor").SetValue(0.3F)

        Shader.Parameters("Texture0").SetValue(Sand)
        Shader.Parameters("Texture1").SetValue(Grass)
        Shader.Parameters("Texture2").SetValue(Rock)
        Shader.Parameters("Texture3").SetValue(Snow)

        Shader.Begin()
        For Each Pass As EffectPass In Shader.CurrentTechnique.Passes
            Pass.Begin()
            With Me.GraphicsDevice
                .VertexDeclaration = VDeclaration
                Me.GraphicsDevice.DrawIndexedPrimitives( _
                    PrimitiveType.TriangleList, 0, 0, _
                    Vertices.Length, 0, Indices.Length / 3)
            End With
            Pass.End()
        Next
        Shader.End()
    End Sub

    '...

    Protected Overrides Sub Draw(ByVal gameTime As GameTime)
        Me.Graphics.GraphicsDevice.Clear(Color.CornflowerBlue)

        DrawRefractionMap()

        DrawSky()
        Me.CopyToBuffer()
        DrawTerrain()

        MyBase.Draw(gameTime)
    End Sub
#End Region 
Ora possiamo passare alla creazione vera e propria della refraction map:
    'Crea una refraction map della scena corrente
    Private Sub DrawRefractionMap()
        'Crea un nuovo piano di sezione. La normale punta nella
        'direzione negativa delle Y e taglia via tutto ciò
        'che si trova sotto di lui. Il "sotto" è relativo
        'alla sua normale, poichè, dato che viene rivolto
        'verso il basso, in realtà elimina la parte superiore
        'del paesaggio
        Dim RefractionPlane As Plane = _
            CreatePlane(WaterHeight + 1.5, New Vector3(0, -1, 0), False)
        'Imposta il piano di sezione
        Me.GraphicsDevice.ClipPlanes(0).Plane = RefractionPlane
        Me.GraphicsDevice.ClipPlanes(0).IsEnabled = True
        'Cambia il render target del device
        Me.GraphicsDevice.SetRenderTarget(0, RefractionTarget)
        'Pulisce la scena, colorando tutto di nero
        Me.GraphicsDevice.Clear(ClearOptions.Target Or _ 
            ClearOptions.DepthBuffer, Color.Black, 1.0F, 0)
        'Disegna il terreno: tutto ciò che ne consegue
        'viene posto sul render target e NON sullo schermo
        DrawTerrain()
        'Disabilita il piano di sezione
        Me.GraphicsDevice.ClipPlanes(0).IsEnabled = False

        'Reimposta il render target sullo schermo. Notare
        'che impostarlo a Nothing equivale a impostare il
        'target predefinito, ossia lo schermo.
        Me.GraphicsDevice.SetRenderTarget(0, Nothing)
        'Ottiene la refraction map in forma di immagine
        RefractionMap = RefractionTarget.GetTexture()
        'Ripulisce lo schermo per il disegno vero e proprio
        Me.GraphicsDevice.Clear(Color.CornflowerBlue)
    End Sub
    
    '...
    
    Protected Overrides Sub Draw(ByVal gameTime As GameTime)
        Me.Graphics.GraphicsDevice.Clear(Color.CornflowerBlue)

        DrawRefractionMap()

        DrawSky()
        Me.CopyToBuffer()
        DrawTerrain()

        MyBase.Draw(gameTime)
    End Sub 
Facendo correre il programma, non vedrete niente, ed è normale, perchè la refraction map resta in memoria. Per vedere se tutto funziona correttamente, potete inserire questo codice:
    Protected Overrides Sub Update(ByVal GameTime As GameTime)
        ProcessMouseInput(GameTime.ElapsedGameTime.TotalSeconds)
        ProcessKeyboardInput()

        'Se si preme M, salva l'immagine sul file prova.jpg,
        'che si troveà nella cartella bin/Debug del progetto
        If k.IsKeyDown(Keys.M) Then
            RefractionMap.Save("prova.jpg", ImageFileFormat.Jpg)
            'Esce dal gioco - potete anche toglierlo
            Me.Exit()
        End If

        MyBase.Update(GameTime)
    End Sub 
E otterrete questo risultato:


Solo quello che si vede sotto l'acqua








 

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