Σάββατο 25 Δεκεμβρίου 2010

XNA - Indices

Μάθημα 7ο

  • Θα χρησιμοποιήσουμε τον κώδικα του προηγούμενου μαθήματος (μην ξεχάσετε την δήλωση του namespace αλλιώς θα υπάρξει σφάλμα). Υπάρχει όμως ένα μικρό πρόβλημα με τον κώδικα αυτό. Αν και φτιάχνουμε ένα τετράγωνο από δύο τρίγωνα, στην πραγματικότητα έχουμε εισάγει 6 σημεία ενώ χρειαζόμαστε μόνο τέσσερα. Υπάρχουν δύο ζεύγη σημείων τα οποία είναι όμοια μεταξύ τους. Για να λυθεί αυτό το πρόβλημα, χρησιμοποιούμε indices. Έτσι η κάρτα γραφικών μας θα γλιτώνει από περιττό κόπο.

Τα σημεία A και C πρέπει να οριστούν μία φορά

  • Ορίζουμε λοιπόν μόνο τα σημεία που χρειαζόμαστε στην συνάρτηση SetUpVertices:      

    vertices = new VertexPositionColor[4];
    vertices[0].Position = new Vector3(0f, 0f, 0f);
    vertices[1].Position = new Vector3(10, 10f, 0f);
    vertices[2].Position = new Vector3(20f, 0f, 0f);
    vertices[3].Position = new Vector3(10, -10f, 0f);

    vertices[3].Color = Color.Blue;
    vertices[0].Color = Color.Blue;
    vertices[1].Color = Color.Green;
    vertices[2].Color = Color.Yellow





    Το ήξερες? : Το XNA θεωρεί ότι ο άξονας z αυξάνεται προς τα εμάς, και μειώνεται καθώς πηγαίνουμε πιο μακριά.
    Χ,Υ,Ζ άξονες





     
  • Τώρα θα δηλώσουμε τα indices. Στην αρχή της κλάσης δηλώνουμε:

    int[] indices;

    Έπειτα δημιουργούμε μια συνάρτηση , την SetUpIndices:

    private void SetUpIndices()
    indices = new ints[6];
    indices[0] = 0;
    indices[1] = 1;
    indices[2] = 2;
    indices[3] = 0;
    indices[4] = 2;
    indices[5] = 3; 
    } 

  • Τώρα καλούμε την μέθοδο SetUpIndices μέσα στην LoadContent:

    SetUpIndices();

  • Τώρα πρέπει στην Draw μέθοδο να αλλάξουμε την κλήση της σχεδίασης από:

    Gdevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 2);
    σε:

    Gdevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, indices.Length / 3);

    Κλασσικά, λέμε ποιο είναι το array των vertices/indices, από ποιο σημείο του να ξεκινήσουμε και τέλος, πόσα τρίγωνα θα φτιάξουμε (6/3 = 2 τρίγωνα, αφού 3 indices φτιάχνουν ένα τρίγωνο). Το vertices.Length δείχνει πόσα vertices υπάρχουν στο array vertices; 
    Το ήξερες? : μπορείς να ζωγραφίσεις μόνο το περίγραμμα των τριγώνων με τον παρακάτω κώδικα (πρώτη γραμμή στην μέθοδο Draw):

    Gdevice.RenderState.FillMode = FillMode.WireFrame;

Το 7ο μάθημα έφτασε στο τέλος του.


Το τρίγωνο αποτελείται από μόνο 4 vertices!

Ασχολίες για εξάσκηση:
  1. Δημιουργήστε δύο τρίγωνα με ένα κοινό σημείο
Πηγές: 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;
        int[] indices;

        VertexDeclaration myVertexDeclaration;

        Matrix viewMatrix;
        Matrix projectionMatrix;

        float angle = 0f;

        private void SetUpCamera()
        {
            viewMatrix = Matrix.CreateLookAt(new Vector3(0f, 50.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[4];

            vertices[0].Position = new Vector3(0f, 0f, 0f);
            vertices[1].Position = new Vector3(10, 10f, 0f);
            vertices[2].Position = new Vector3(20f, 0f, 0f);

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

            vertices[3].Position = new Vector3(10, -10f, 0f);

            vertices[3].Color = Color.Red;
        }


        private void SetUpIndices()
        {
            indices = new int[6];

            indices[0] = 0;
            indices[1] = 1;
            indices[2] = 2;
            indices[3] = 0;
            indices[4] = 2;
            indices[5] = 3;
        }
        

        // 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();
            SetUpIndices();
        }

        protected override void UnloadContent()
        {

        }

        // Update (60 times per second)
        protected override void Update(GameTime gameTime)
        {
            angle += 0.005f;

            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);
         
            Matrix worldMatrix = Matrix.CreateTranslation(-10f,0, 0) * Matrix.CreateRotationY(3*angle);

            myEffect.Parameters["xWorld"].SetValue(worldMatrix);

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

            myEffect.Begin();

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

                Gdevice.VertexDeclaration = myVertexDeclaration;
                Gdevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length,indices,0,indices.Length/3);

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

            base.Draw(gameTime);
        }
    }

}

XNA - Περιστροφές και Μετατοπίσεις

Μάθημα 6ο

  • Για ευκολία, ορίζουμε το παρακάτω τρίγωνο
    vertices[0].Position = new Vector3(0f, 0f, 0f);
    vertices[1].Position = new Vector3(10, 10f, 0f);
    vertices[2].Position = new Vector3(20f, 0f, 0f);
  • Το τρίγωνο μας κάθεται ακίνητο εκεί που το δημιουργήσαμε, και ο μόνος τρόπος να κινηθεί στην οθόνη είναι να μεταφέρουμε την κάμερα. Τώρα θα μπορέσουμε να μετατοπίσουμε και να περιστρέψουμε το ίδιο το τρίγωνο, και αυτό θα μπορεί να γίνει και σε πραγματικό χρόνο. Ξεκινάμε ορίζοντας μια μεταβλητή angle στην αρχή στης κλάσης μας:
    float angle = 0f;
    Τώρα θέλουμε σε κάθε καρέ (60 καρέ το δευτερόλεπτο) το angle να αυξάνεται. Επομένως ένα πολύ καλό μέρος να τοποθετήσουμε αυτήν την αύξηση είναι στην μέθοδο Update:

    angle += 0.005f;
    Το μόνο που χρειαζόμαστε πια είναι να περιστρέψουμε τις world coordinates. Αυτό θα γίνει με μια μήτρα μετασχηματισμού. Ευτυχώς δεν χρειάζεται να ορίσουμε εμείς αυτήν την μήτρα, το XNA θα ασχοληθεί με αυτήν όταν του δώσουμε τον άξονα περιστροφής και την γωνία περιστροφής. Προσθέτουμε λοιπόν τον παρακάτω κώδικα στην κλάση Draw πριν το effect.Begin():

    Matrix worldMatrix = Matrix.CreateRotationY(3 * angle);
    myEffect.Parameters["xWorld"].SetValue(worldMatrix);
    Η τελευταία γραμμή αντικαθιστά μια άλλη γραμμή που είχαμε ήδη ορίσει, και αντί για worldMatrix είχε παράμετρο το "Matrix.Identity". Αυτός ο μετασχηματισμός αφορά τις συντεταγμένες του κόσμου, οπότε ότι και να σχεδιάσουμε θα αρχίσει να περιστρέφεται μαζί με τα υπόλοιπα αντικείμενα.

    Όπως φαίνεται, δημιουργούμε μια μήτρα worldMatrix η οποία περιέχει μια περιστροφή γύρω από τον άξονα Y. Αυτή την μήτρα την δίνουμε στις παραμέτρους της effect πριν αυτή ξεκινήσει να σχεδιάζει.

  • Όπως φαίνεται, το τρίγωνο περιστρέφεται γύρω από το σημείο (0,0,0) διότι από εκεί περνάει ο άξονας Y. Τι θα γίνει όμως αν θέλουμε να περιστρέψουμε το τρίγωνο γύρω από το κέντρο του?

    Μια καλή λύση θα ήταν να μετατοπίσουμε πρώτα το τρίγωνο ώστε να βρίσκεται στο κέντρο του κόσμου μας, και μετά να το περιστρέψουμε. Για να γίνει αυτό πρέπει να πολλαπλασιάσουμε την μήτρα περιστροφής με μια μήτρα μετατόπισης. Στην μέθοδο Draw γράφουμε:

    Matrix worldMatrix = Matrix.CreateTranslation(-10f,0, 0) * Matrix.CreateRotationY(angle); 
    αντικαθιστώντας τον προηγούμενο κώδικα που υπήρχε για τον ορισμό του worldMatrix.

    Στην πραγματικότητα δεν πειράζουμε τις συντεταγμένες του τριγώνου, αλλά το πως αυτές προβάλλονται στην οθόνη μας. Η περιστροφή και η μετατόπιση εφαρμόζονται στα ήδη υπάρχοντα σημεία του τριγώνου, τα οποία έχουν μείνει ανέπαφα.

    Προσοχή διότι η σειρά με την οποία πολλαπλασιάζονται οι μήτρες έχει σημασία. Αν πρώτα περιστραφεί και μετά μετατοπιστεί θα έχουμε άλλο αποτέλεσμα. (θα περιστρέφεται γύρω από ένα ακίνητο σημείο στην γωνία).

  • Ένα τελευταίο ζήτημα είναι η περιστροφή γύρω από δικό μας άξονα. Πρώτα φτιάχνουμε ένα διάνυσμα που θα καθορίζει τον άξονα περιστροφής. Έπειτα κανονικοποιούμε το διάνυσμα ώστε να έχει μέτρο 1. Τέλος αλλάζουμε για τελευταία φορά το worldMatrix, οπότε στην μέθοδο Draw γράφουμε:

    Vector3 rotAxis = new Vector3(3, 1, 2); 
    rotAxis.Normalize();
    Matrix worldMatrix = Matrix.CreateTranslation(-10.0f, 0, 0) * Matrix.CreateFromAxisAngle(rotAxis, angle);
    Η CreateFromAxisAngle δημιουργεί μια μήτρα περιστροφής με βάση ένα κανονικοποιημένο διάνυσμα και μια γωνία. Για αυτό τον λόγο έπρεπε να κανονικοποιήσουμε το διάνυσμα.

    Επειδή είναι πιο ωραίο να μπορεί κανείς να δει δύο τρίγωνα μαζί, ο παρακάτω κώδικας θα περιέχει μια ελαφριά παραλλαγή των όσων κάναμε εδώ.

Το 6ο μάθημα έφτασε στο τέλος του.



Το τρίγωνο περιστρέφεται σε έναν καινούργιο άξονα

Ασχολίες για εξάσκηση:
  1. Δοκιμάστε να αλλάξετε τους άξονες περιστροφής
  2. Αλλάξτε την μετατόπιση των συντεταγμένων
  3. Δοκιμάστε να αλλάξετε την σειρά πολλαπλασιασμού στις μήτρες
Πηγές: 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, 50.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[6];
            vertices[0].Position = new Vector3(0f, 0f, 0f);
            vertices[1].Position = new Vector3(10, 10f, 0f);
            vertices[2].Position = new Vector3(20f, 0f, 0f);

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

            vertices[3].Position = new Vector3(0f, 0f, 0f);
            vertices[4].Position = new Vector3(10, -10f, 0f);
            vertices[5].Position = new Vector3(20f, 0f, 0f);

            vertices[3].Color = Color.Blue;
            vertices[4].Color = Color.Red;
            vertices[5].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)
        {
            angle += 0.005f;

            base.Update(gameTime);
        }

        float angle = 0f;

        // 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);

            // define worldMatrix
            Vector3 rotAxis = new Vector3(3 , 1, 2);
            rotAxis.Normalize();
            Matrix worldMatrix = Matrix.CreateTranslation(-10f,0, 0) * Matrix.CreateFromAxisAngle(rotAxis,3*angle);

            myEffect.Parameters["xWorld"].SetValue(worldMatrix);
            // worldMatrix used

            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, 2);

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

            base.Draw(gameTime);
        }
    }
}

Παρασκευή 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);
        }
    }
}

ΧΝΑ - Το πρώτο μας τρίγωνο



Μάθημα 4ο

  • Όπως είπαμε όλα αποτελούνται από τρίγωνα. Ένα τρίγωνο καθορίζεται από τρία σημεία. Τα σημεία με την σειρά τους ορίζονται από ένα διάνυσμα το οποίο κρατάει τις συντεταγμένες τους στον τρισδιάστατο χώρο. Όμως εκτός από την θέση, ίσως χρειαζόμαστε και το χρώμα των σημείων. Ένα vertex κρατάει όλες αυτές τις σημαντικές πληροφορίες ενός σημείου.(πληθυντικός, vertices).

    Ευτυχώς το XNA έχει μια δομή η οποία μπορεί να κρατήσει της πληροφορίες ενός vertex. Η δομή αυτή είναι η VertexPositionColor.
  • Για να δημιουργήσουμε ένα τρίγωνο, χρειαζόμαστε τρία vertices τα οποία και θα βάλουμε σε έναν πίνακα. Οπότε, χωρίς να καθυστερούμε, γράφουμε τον παρακάτω κώδικα στην αρχή της κλάσης:
    VertexPositionColor[] vertices;

    Και τώρα φτιάχνουμε μια συνάρτηση η οποία θα γεμίζει αυτόν τον πίνακα:

    private void SetUpVertices()

    {
    vertices = new VertexPositionColor[3]; 
vertices[0].Position = new Vector3(-0.5f, -0.5f, 0f);
vertices[0].Color = Color.Red;
vertices[1].Position = new Vector3(0f, 0.5f, 0f);
vertices[1].Color = Color.Green;
vertices[2].Position = new Vector3(0.5f, -0.5f, 0f);
vertices[2].Color = Color.Yellow;

Το ήξερες? : private σημαίνει ότι η μέθοδος δεν μπορεί να χρησιμοποιηθεί από άλλες κλάσεις. void σημαίνει ότι δεν επιστρέφει τιμή. Υπάρχει τρόπος μια μέθοδος να επιστρέψει πάνω από μια τιμές, εφόσον δηλώσει μια παράμετρο της ως out!

Αυτές οι συντεταγμένες αφορούν την οθόνη μας. Για παράδειγμα το (-1,-1,0) είναι το κάτω αριστερό άκρο ενώ το (1,1,0) είναι το πάνω δεξιά. Αφού αυτές οι συντεταγμένες είναι ήδη σωστές, δεν χρειάζονται κάποιον μετασχηματισμό για να γίνουν από τρισδιάστατες δισδιάστατες. Για αυτόν τον λόγο, είχαμε χρησιμοποιήσει το Prestransformed για τεχνική του effect.

Το ήξερες? : το f δίπλα στους αριθμούς σημαίνει ότι είναι float, και χρειάζεται διότι ένας αριθμός όπως ο 0.5 θεωρείται double από μόνος του. Το XNA χρησιμοποιεί κυρίως floats.

  • Όταν πούμε στο XNA να σχεδιάσει τα τρίγωνα, αυτό πρέπει να στείλει τα vertices σε ένα byte stream το οποίο θα δεχτεί η κάρτα γραφικών. Η κάρτα γραφικών βλέπει το byte stream αλλά δεν ξέρει τι είναι εκεί μέσα. Θα χρειαστούμε λοιπόν ένα αντικείμενο VertexDeclaration για να πούμε στην κάρτα γραφικών τι είδους vertices να περιμένει.

    Αυτό το αντικείμενο είναι πάρα πολύ σημαντικό για όλο το project μας (και άρα θα το βάλουμε στην αρχή της κλάσης μας), μιας και χωρίς αυτό δεν θα μπορούμε να σχεδιάσουμε vertices με την κάρτα γραφικών. Επιπλέον, πρέπει να το κάνουμε initialize μόνο μια φορά. (Αν προσπαθήσουμε να το ξανακάνουμε, εφόσον τρέχουμε το παιχνίδι σε Xbox360 το .NET framework θα κρασάρει!). Άρα γράφουμε στην αρχή της κλάσης μας:

    VertexDeclaration myVertexDeclaration;
  • Τώρα αρχικοποιούμε αυτήν την μεταβλητή, κάπου μέσα στον κώδικα της συνάρτησης που γράψαμε:
    myVertexDeclaration = new VertexDeclaration(Gdevice,VertexPositionColor.VertexElements);

    Αυτό το αντικείμενο πρέπει να το στείλουμε στην κάρτα γραφικών ώστε να ξέρει ότι έρχονται αντικείμενα τύπου VertexPositionColor προς αυτήν.

  • Πρέπει φυσικά να καλέσουμε την συνάρτηση, και αφού η συνάρτηση χρησιμοποιεί το Gdevice, καλύτερα αυτό να γίνει στο τέλος της LoadContent μεθόδου:

    SetUpVertices(); 
  • Τέλος θα χρειαστεί φυσικά να σχεδιάσουμε το τρίγωνο. Ανάμεσα στο pass.Begin() και pass.End στην μέθοδο Draw γράφουμε:

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

    Η τελευταία παράμετρος είναι ο αριθμός των τριγώνων, ενώ η προτελευταία ο αριθμός του vertex από το οποίο ξεκινάμε. Φυσικά πρέπει να δώσουμε τον πίνακα των vertices και να πούμε στην κάρτα γραφικών ότι της στέλνουμε μια λίστα από τρίγωνα. Υπάρχει για παράδειγμα και η TriangleStrip η οποία σχεδιάζει πιο γρήγορα αλλά αφορά τρίγωνα που έχουν κάποιες ακμές ενωμένες.


  • Ιδιαίτερη προσοχή πρέπει να δοθεί στην σειρά με την οποία δηλώνονται τα σημεία, διότι αν τα σημεία του τριγώνου δηλωθούν αριστερόστροφα, ο προσανατολισμός του αντικειμένου είναι προς την αντίθετη μεριά από αυτήν που κοιτάμε. Με λίγα λόγια, το αντικείμενο θα εξαφανιστεί. Πρέπει να δηλώνουμε τα σημεία με την φορά του ρολογιού.
Το 4ο μάθημα έφτασε στο τέλος του.



Το αποτέλεσμα της δουλειάς μας μέχρι τώρα

Ασχολίες για εξάσκηση:
  1. Προσπαθήστε να αλλάξετε το μέγεθος του αρχικού τριγώνου
  2. Δημιουργήστε δύο τρίγωνα τα οποία να καλύπτουν την οθόνη (και να μην είναι έξω από αυτήν)
  3. Δημιουργήστε ένα τρίγωνο με αριστερόστροφο προσανατολισμό


Πηγές: 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;

        private void SetUpVertices()
        {
            myVertexDeclaration = new VertexDeclaration(Gdevice, VertexPositionColor.VertexElements);
            vertices = new VertexPositionColor[3];
            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].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<Effect>("effects");

            SetUpVertices();
        }

        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);

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

            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);
        }
    }
}