Παρασκευή 24 Δεκεμβρίου 2010

ΧΝΑ - Η κάμερα και οι κοσμικές συντεταγμένες


Μάθημα 5ο

  • Μέχρι τώρα χρησιμοποιούσαμε Prestransformed συντεταγμένες, δηλαδή δηλώναμε τις x και y συντεταγμένες για την οθόνη μας. Τώρα θα χρησιμοποιήσουμε μη μετασχηματισμένες συντεταγμένες , οι οποίες αποκαλούνται κοσμικές συντεταγμένες (World coordinates). Αυτές επιτρέπουν σε μας να χρησιμοποιήσουμε συντεταγμένες για να ορίσουμε τα πάντα. Έτσι θα καταφέρουμε να τοποθετήσουμε και μια κάμερα μέσα στην σκηνή (scene).


    Ας ξεκινήσουμε ορίζοντας world coordinates για τα τρίγωνα μας, αλλάζοντας τον κώδικα για την δημιουργία των vertices:

    από:
    vertices[0].Position = new Vector3(-0.5f,-0.5f,0f); 
    vertices[1].Position = new Vector3(0f,0.5f,0f); 
    vertices[2].Position = new Vector3(0.5f,-0.5f,0f);
    σε:

    vertices[0].Position = new Vector3(0f,0f,0f); 
    vertices[1].Position = new Vector3(10f,10f,0f); 
    vertices[2].Position = new Vector3(10f,0f,-5f);
    Επειδή χρησιμοποιούμε world coordinates μπορούμε πλέον να χρησιμοποιούμε και τον άξονα z.
    Το (1,1,0) δεν έχει πια κάποια ιδιαίτερη σημασία.

  • Όμως επειδή χρησιμοποιούμε τέτοιες συντεταγμένες, πρέπει να χρησιμοποιήσουμε άλλου είδους τεχνική. Θέλουμε την τεχνική Colored από το effect αρχείο. Οπότε αλλάζουμε τον κώδικα στην μέθοδο Draw από:
    myEffect.CurrentTechnique = myEffect.Techniques["Pretransformed"];
    σε:
    myEffect.CurrentTechnique = myEffect.Techniques["Colored"];

    Και φυσικά, αν τρέξουμε τον κώδικα μας, εξαφανίζεται το τρίγωνο...Μα γιατί???

    Διότι δεν έχουμε ακόμα ορίσει την κάμερα μας!

  • Για να ορίσουμε την κάμερα πρέπει πρώτα να ορίσουμε μία μήτρα. Η γραμμική άλγεβρα αφορούσε στην πραγματικότητα αυτό που αποκαλούμε μήτρα (matrix) το οποίο δεν είναι τίποτα άλλο από ένα πίνακα με αριθμητικά στοιχεία. Για να προβάλουμε τα τρισδιάστατα αντικείμενα στην δισδιάστατη οθόνη μας θα χρειαστούμε έναν μετασχηματισμό. Στην ουσία θα πολλαπλασιάσουμε τα σημεία μας με μία μήτρα.

    Θα χρειαστούμε δύο μήτρες για αυτό, τις οποίες προσθέτουμε στην αρχή της κλάσης μας:

    Matrix viewMatrix; 
    Matrix projectionMatrix;
     Έπειτα γράφουμε άλλη μια συνάρτηση η οποία θα φτιάχνει την κάμερα μας:

    private void SetUpCamera()
    {
    viewMatrix = Μatrix.CreateLookAt(new Vector3(0, 0, 50), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
    projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, Gdevice.Viewport.AspectRatio, 1.0f, 300.0f);
    }

    Αν και φαίνεται περίπλοκο , δεν είναι. Η συνάρτηση Matrix.CreateLookAt θα δημιουργήσει μια μήτρα που αποθηκεύει την θέση και τον προσανατολισμό της κάμερας μας. Το πρώτο όρισμα δείχνει την θέση, το δεύτερο δείχνει το σημείο στο οποίο κοιτάμε και το τρίτο είναι ένα διάνυσμα που δείχνει το προς τα "πάνω". Άρα χρειαζόμαστε την θέση της κάμερας, το που εστιάζει, και πως είναι περιστραμμένη (διότι μπορεί να περιστραφεί χωρίς να αλλάξει ούτε θέση ούτε σημείο εστίασης).

    Η δεύτερη μέθοδος θα δημιουργήσει μια μήτρα που αποθηκεύει το πως η κάμερα βλέπει τον κόσμο. Είναι κάτι σαν τον φακό της κάμερας. Αρχικά βάζουμε την γωνία περιστροφής. Μετά βάζουμε την αναλογία ύψους πλάτους, την οποία αντλούμε από την ανάλυση που έχουμε(π.χ. 800 x 600). Μετά ακολουθούν τα επίπεδα αποκοπής. Οτιδήποτε πιο κοντά από 1f και πιο μακριά από 300.0f δεν θα εμφανιστεί στην οθόνη!
  • Τώρα πρέπει να πούμε στην τεχνική να χρησιμοποιήσει τις μήτρες που μόλις δημιουργήσαμε.
    Θα πρέπει να εισάγουμε τον παρακάτω κώδικα μέσα στην αρχή της μεθόδου Draw, μετά το Clear:
    myEffect.Parameters["xView"].SetValue(viewMatrix);
    myEffect.Parameters["xProjection"].SetValue(projectionMatrix);
    myEffect.Parameters["xWorld"].SetValue(Matrix.Identity);

    Θα μιλήσουμε για την τρίτη γραμμή πιο μετά.

    Για να ολοκληρωθεί το project πρέπει να καλέσουμε την δημιουργία της κάμερας στην μέθοδο LoadContent:

    SetUpCamera(); 

    Το ήξερες? : Αν δοκιμάσουμε να δούμε το τρίγωνο από την άλλη μεριά εξαφανίζεται! Για να σταματήσει να συμβαίνει αυτό, μπορούμε να γράψουμε:

    Gdevice.RenderState.CullMode = CullMode.None;
    Φυσικά αυτό σημαίνει επιπρόσθετη επεξεργασία στην μέθοδο Draw, και άρα είναι κάτι μη θεμιτό...
Το 5ο μάθημα έφτασε στο τέλος του.



Παρατηρούμε ότι το τρίγωνο κατοικεί πια σε τρισδιάστατο χώρο

Ασχολίες για εξάσκηση:
  1. Αλλάξτε την θέση του τριγώνου
  2. Αλλάξτε το viewMatrix μέσω της CreateLookAt

    Ερώτηση: γιατί δεν μπορούμε να βάλουμε 1 στο z του up vector αφήνοντας τα υπόλοιπα 0?

    Απάντηση: διότι το up vector πρέπει να είναι κάθετο στην κατεύθυνση που κοιτάει η κάμερα
Πηγές: Riemer's Tutorial

_________________________________________

Απλοποιημένος κώδικας (χωρίς περιττά σχόλια)




using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace SandboxGame
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        GraphicsDevice Gdevice;

        Effect myEffect;

        VertexPositionColor[] vertices;

        VertexDeclaration myVertexDeclaration;

        Matrix viewMatrix;
        Matrix projectionMatrix;

        private void SetUpCamera()
        {
            viewMatrix = Matrix.CreateLookAt(new Vector3(0f, 0f, 50.0f), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, Gdevice.Viewport.AspectRatio, 1.0f, 300.0f);
        }

        private void SetUpVertices()
        {
            myVertexDeclaration = new VertexDeclaration(Gdevice, VertexPositionColor.VertexElements);
            vertices = new VertexPositionColor[3];
            vertices[0].Position = new Vector3(0f,0f,0f);
            vertices[1].Position = new Vector3(10,10f,0f);
            vertices[2].Position = new Vector3(10f,0f,-5f);

            vertices[0].Color = Color.Red;
            vertices[1].Color = Color.Green;
            vertices[2].Color = Color.Yellow;
        }

        // Constructor
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        // Init
        protected override void Initialize()
        {
            graphics.PreferredBackBufferHeight = 600;
            graphics.PreferredBackBufferWidth = 800;
            graphics.IsFullScreen = false;
            graphics.ApplyChanges();
            Window.Title = "Το αλμυρό φυστίκι";

            base.Initialize();
        }

        // Load Content
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            Gdevice = graphics.GraphicsDevice;
            myEffect = Content.Load("effects");

            SetUpVertices();
            SetUpCamera();
        }

        protected override void UnloadContent()
        {

        }

        // Update (60 times per second)
        protected override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
        }

        // Draw
        protected override void Draw(GameTime gameTime)
        {
            Gdevice.Clear(Color.CornflowerBlue);

            Gdevice.RenderState.CullMode = CullMode.None;

            myEffect.Parameters["xView"].SetValue(viewMatrix);
            myEffect.Parameters["xProjection"].SetValue(projectionMatrix);
            myEffect.Parameters["xWorld"].SetValue(Matrix.Identity);

            myEffect.CurrentTechnique = myEffect.Techniques["Colored"];

            myEffect.Begin();

            foreach (EffectPass pass in myEffect.CurrentTechnique.Passes)
            {
                pass.Begin();

                Gdevice.VertexDeclaration = myVertexDeclaration;
                Gdevice.DrawUserPrimitives(PrimitiveType.TriangleList,vertices,0,1);

                pass.End();
            }
            myEffect.End();

            base.Draw(gameTime);
        }
    }
}

Δεν υπάρχουν σχόλια:

Δημοσίευση σχολίου