domingo, 21 de julio de 2013

I - Dibujando un Triángulo

Lo primero que vamos a hacer es dibujar un triángulo con un vértice de cada color y un degradado según se aleja de los vértices. Si alguno habéis visto algún ejemplo de OpenGL, éste es el típico.
El ejemplo esta basado en el libro oficial de programación en OpenGL que ya he comentado en varios post, lo he simplificado y pasado a la estructura de juegos.

Lo primero como siempre el ".h".
Por un lado tenemos lo mismo de siempre: la ventana, el contexto, la variable running, las constantes de configuracion y las 5 funciones del gameLoop con sus correspondientes OnXXXXXX, y el constructor.
Por otro lado tenemos lo nuevo: 
  • Dos funciones, una para configurar OpenGL y otra para inicializar los datos que vamos a utilizar en el render.
  • Una constante que nos dice cuantos vértices se van a dibujar, como es un triángulo: 3
  • Dos arrays de una casilla que ya veremos para que sirven. GLuint, es el tipo genérico (cross-platform) de unsigned int, para evitar tener problemas.
  • Attrib_Locs es simplemente una manera cómoda de asignar dos constantes más y evitar que se solapen los valores.
El Demo_OGL_1.h:
#ifndef DEMO_OGL_I_H_
#define DEMO_OGL_I_H_
#include <iostream>
#include <GL/gl3w.h>
#include <GL/gl.h>
#include "SDL2/SDL.h"
#include "LoadShaders.h"

class Demo_OGL_1 {
private:
    bool running;
    SDL_Window* window;
    SDL_GLContext ctxt;

    static const uint32_t   WIN_HEIGHT = 512; //px
    static const uint32_t   WIN_WIDTH  = 512; //px
    static const char*      WIN_TITLE; //px

    /***************************************************/

    enum Attrib_Locs { vPosition = 0, vColor = 1 };

    GLuint  vao[1];
    GLuint  buffer[1];

    static const GLuint  NumVertices = 3;
public:

    Demo_OGL_1();
    /*
     * GAME LOOP FUNCTIONS
     */

    int Execute(){ return OnExecute(); }
    bool Init(){ return OnInit(); }
    void Loop(){ return OnLoop(); }
    void Render(){ return OnRender(); }
    void Cleanup(){ return OnCleanup(); }
    void Event(SDL_Event* Event){ OnEvent(Event); }


    int OnExecute();
    bool OnInit();
    void OnEvent(SDL_Event* Event);
    void OnLoop();
    void OnRender();
    void OnCleanup();

    /***************************************************/

    void SetupOpenGL();
    void InitData();
};


#endif /* DEMO_OGL_I_H_ */
Y ahora ponemos el Demo_OGL_1.cpp:
#ifndef DEMO_OGL_I_H_

#include "Demo_OGL_1.h"
#include <GL/gl3w.h>
#include <GL/gl.h>
#include "LoadShaders.h"

const char* Demo_OGL_1::WIN_TITLE = "Titulo de la Ventana";

Demo_OGL_1::Demo_OGL_1() : running(false), window(NULL), ctxt(NULL){}

void Demo_OGL_1::OnEvent(SDL_Event* event) {
    switch (event->type) {
        case SDL_KEYUP:
            switch(event->key.keysym.sym){
                case SDLK_v:
                    std::cout << glGetString(GL_VERSION) << std::endl;
                    break;
                case SDLK_ESCAPE:
                    running = false;
                    break;
                default:
                    break;
            }
            break;
        case SDL_QUIT:
            running = false;
            break;
        default:
            break;
    }
}
void Demo_OGL_1::OnLoop() {}

void Demo_OGL_1::OnCleanup() {
    glUseProgram(0);
    glDeleteVertexArrays(1, vao);
    glDeleteBuffers(1, buffer);
    SDL_GL_DeleteContext(ctxt);
    SDL_DestroyWindow(window);
    SDL_Quit();
}


int Demo_OGL_1::OnExecute() {
    if (!Init())
        return -1;

    SDL_Event event;

    while (running) {

        while (SDL_PollEvent(&event))
            Event(&event);

        Loop();
        Render();
    }

    Cleanup();

    return 0;
}

void Demo_OGL_1::OnRender() {

    glClear( GL_COLOR_BUFFER_BIT );

    glBindVertexArray( vao[0] );
    glDrawArrays( GL_TRIANGLES, 0, NumVertices );

    SDL_GL_SwapWindow(window);
}

bool Demo_OGL_1::OnInit() {
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
        return false;

    window = SDL_CreateWindow(WIN_TITLE,
            SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
            WIN_WIDTH, WIN_HEIGHT,
            SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);
    if(!window)
        return false;

    SetupOpenGL();
    InitData();

    running = true;

    return true;
}
void Demo_OGL_1::SetupOpenGL() {

   SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
   SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 32);
   ctxt = SDL_GL_CreateContext(window);

   //vsync ON
   SDL_GL_SetSwapInterval(1);

   if (gl3wInit()) {
       std::cout << "Error al Inicializar GL3W" << std::endl;
   }

   glClearColor(0.0, 0.0, 0.0, 1.0);

}
void Demo_OGL_1::InitData() {

    glGenVertexArrays( 1, vao );
    glBindVertexArray( vao[0] );

    struct VertexData {
        GLfloat color[3];
        GLfloat position[4];
    };

    VertexData vertices[NumVertices] = {
        {{  1.00, 0.00, 0.00 }, {  0.00, 0.90 }},
        {{  0.00, 1.00, 0.00 }, {  0.90, -0.90 }},
        {{  0.00, 0.00, 1.00 }, { -0.90, -0.90 }}

    };


    glGenBuffers( 1, buffer );
    glBindBuffer( GL_ARRAY_BUFFER, buffer[0] );
    glBufferData( GL_ARRAY_BUFFER, sizeof(vertices),
                  vertices, GL_STATIC_DRAW );

    ShaderInfo shaders[] = {
        { GL_VERTEX_SHADER, "Resources/Demo_OGL_I/gouraud.vert" },
        { GL_FRAGMENT_SHADER, "Resources/Demo_OGL_I/gouraud.frag" },
        { GL_NONE, NULL }
    };

    GLuint program = LoadShaders( shaders );
    glUseProgram( program );

    glVertexAttribPointer( vColor, 3, GL_FLOAT,
                           GL_TRUE, sizeof(VertexData), NULL );
    glVertexAttribPointer( vPosition, 2, GL_FLOAT,
                           GL_FALSE, sizeof(VertexData),
                           (const GLvoid*)sizeof(vertices[0].color) );

    glEnableVertexAttribArray( vColor );
    glEnableVertexAttribArray( vPosition );
}

Veamos:
  • La gestión de eventos se ha simplificado para poder salir del programa y mostrar la versión pulsando "v".
  • El OnLoop sigue vacío, no hacemos modificaciones en el modelo porque no hay.
  • OnCleanup, destruimos la información que hemos usado y limpiamos SDL como venimos haciendo hasta ahora.
  • OnExecute se mantiene igual, el Game Loop.
  • OnRender tiene 4 instrucciones:
    • glClear( GL_COLOR_BUFFER_BIT ); Limpia el buffer de color, lo que provoca que todo el contexto se ponga del color que hayamos prefijado. Se prefija en SetupOpenGL a si que ya lo comentaremos.
    • glBindVertexArray( vao[0] ); Volvemos activo el array de vertices para poder utilizarlo. Más tarde nos pararemos en esto.
    • glDrawArrays( GL_TRIANGLES, 0, NumVertices ); Con el array de vértices que hemos vuelto activo antes, dibujamos. GL_TRIANGLES indica que la primitiva que estamos usando son triángulos (cada 3 vértices se construye un triangulo (Ésta aclaración no es por tomaros por tontos, en otro tutorial veremos que otra primitiva nos permite dibujar triángulos pero no cada 3 vértices, sino en todos los vértices a partir del tercero)). Le indicamos el primer vértice (0) y el número de vértices que hay que dibujar.
    • SDL_GL_SwapWindow(window); Aprovechando el doble buffer.
  • OnInit no tiene nada nuevo salvo la llamada a inicializar opengl y la información.
  • SetupOpenGL es simple, activamos el doble buffer, utilizamos 32bit de profundidad, creamos el contexto con SDL, activamos la sincro vertical, inicializamos gl3w y aquí viene el momento de prefijar el color de fondo del contexto OpenGL, es RGBA, por lo tanto: 0 rojo, 0 azul, 0 verde, 1 alpha (negro opaco). Si queréis probar, podeis cambiar los colores con unos valores entre 0.0 y 1.0. Por ejemplo un gris: 0.9 en todo excepto alpha.
  • InitData es la clave de éste ejemplo:
    • glGenVertexArrays( 1, vao );glBindVertexArray( vao[0] );
      No se si soy torpe o no encontré la explicación adecuada, pero me costó entender esto un poco. Voy a intentar explicarlo de la mejor manera que se me ha ocurrido.
      Cuando creamos una variable en cualquier lenguaje de programación, se hace en tres fases:
      • Se nos da un nombre asociado a la variable.
      • Se reserva un hueco en memoria.
      • Inicializamos el hueco que se nos ha dado en memoria.
      Teniendo presente esto, estas dos instrucciones hacen algo parecido a las dos primeras fases de la creación de una variable.
      Le pedimos que genere 1 nombre de array de vertices, y le decimos que nos lo guarde en el
      vao (vertex array objects). Podríamos pedirle más nombres cambiando el 1 por el número que necesitemos, por eso utilizamos un array de objetos y no un puntero a un solo objeto.
      Con
      glBindVertexArray lo que hacemos es la segunda fase: reservamos un hueco de memoria asociado a ese nombre que anteriormente hemos generado. Ademas OpenGL funciona como máquina de estados, ¿Eso que quiere decir? que cuando hacemos el bind, hacemos que ese vertexArray pase a ser el activo, y todas las operaciones que se hagan, se harán (si proceden) sobre él. Por eso en la función OnRender hacemos un bind y después el draw. Primero lo volvemos activo y después lo dibujamos. Creo que se podría omitir en este ejemplo, pero normalmente no vamos a usar un solo vao a si que esta bien irse acostumbrando.
      De esta manera podemos decir que tenemos alojada una variable de tipo vertex array en la memoria de opengl y tenemos un nombre para poder acceder a ella.
    • Las dos siguientes instrucciones son puro C. Creamos una estructura que guarda color y posición de los vértices. Después creamos un array para guardar esa información.
    • glGenBuffers( 1, buffer ); glBindBuffer( GL_ARRAY_BUFFER, buffer[0] );glBufferData( GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW );
      Las dos primeras instrucciones son similares a las de los vao con la diferencia que al hacer el bind le avisamos que es un buffer array. Esto le da una pista a OpenGL acerca de donde debe guardar ese buffer de vértices. Existen mas "pistas" que iremos viendo a lo largo de los tutoriales.
      La última instrucción lo que hace es la tercera de las fases que hemos hablado antes, inicializa el hueco de memoria. Lo parámetros son:
      El hueco de memoria que queremos inicializar.
      El espacio que va a ocupar en bytes.
      El puntero a la estructura o array.
      El tipo de dibujado, en este caso usamos
      static_draw porque no va a cambiar la información del buffer muy amenudo, normalmente quiere decir que no va a cambiar prácticamente nunca en la ejecución del programa. Al igual que el GL_ARRAY_BUFFER, existen varios valores que iremos viendo más adelante.
    • El siguiente array se utiliza para pasárselo al cargador de shaders (que mal suena), simplemente asocia una constante a una ruta en la que se encuentra el shader.
    • Cargamos los shader, nos devuelve el nombre del programa (igual que el los buffers). Este programa ya esta almacenado y compilado en memoria. Una rápida explicación de los shaders: Los shaders son programas (con su main y todo!!) que usa la GPU para el postprocesamiento de las imágenes, para ello hay que darle un código en un lenguaje especifico llamado GLSL (OpenGL Shading Languaje). En tiempo de ejecución este programa se compila y se aloja en la memoria de la GPU.
    • Le decimos a OpenGL que se ponga a usar ese programa para postprocesado.
    • Las siguientes 4 instrucciones las voy a explicar junto con una breve introducción a los shaders, por lo que primero voy a pegar el codigo de los dos shaders. Me vais a perdonar pero el script de coloreado no colorea GLSL, a si que habrá que conformarse con que coloree lo que se parezca a C++:

El vertex shader: gouraud.vert:
#version 400 core

layout( location = 0 ) in vec4 vPosition;
layout( location = 1 ) in vec4 vColor;

out vec4  color;

void
main()
{
    color = vColor;
    gl_Position = vPosition;
}
El fragment shader: gouraud.frag:
#version 400 core

in  vec4 color;

out vec4 fColor;

void
main()
{
    fColor = color;
}
Los shader tienen una sintaxis muy parecida a C. Como por ejemplo: La primera linea donde pone version es una instrucción de preprocesador que le indica como tiene que entender el shader y toda la ejecución empieza por el main.
También existen unas cuantas diferencias que iremos viendo.
  • Hablaremos primero del vertex shader por ser el primero que se procesa.
    El vertex shader actúa vértice a vértice  por lo que tendremos que darle información acerca de los vértices.

    • Después de la versión lo primero que nos encontramos son dos variables. A diferencia de C, aquí las variables "globales" si se recomiendan porque los programas son pequeños y porque es la única manera de establecer valores de entrada y salida del programa.Estas dos variables tienen: 
      • layout( location = n), (n es un número) esto es una manera de localizar las variables fuera del programa.
      • in Se define como variable de entrada de datos. Podremos describir su valor desde nuestro programa C++.
      • vec4 Quiere decir que la variable es de tipo vector de 4 valores, es decir un array de 4 casillas.
      • Por último el nombre de la variable.
      Ahora veamos el código que teniamos escrito en C++:
      glVertexAttribPointer( vColor, 3, GL_FLOAT,
                             GL_TRUE, sizeof(VertexData), NULL );
      glVertexAttribPointer( vPosition, 2, GL_FLOAT,
                             GL_FALSE, sizeof(VertexData),
                             const GLvoid*)sizeof(vertices[0].color) );
      
      vColor y vPosition son dos constantes del enumerado (si os acordais), corresponden con el 0 y el 1 respectivamente.
      La función asocia unos valores del buffer con unas variables del programa.
      El primer valor de la función indica la localización de la variable, en nuestro caso estamos poniendo 0 y 1 para que coincida con el valor de
      location. El nombre de nuestra variable C da igual, lo importante es que el valor es 0 o 1 para poder asociarlo con la location interna.
      Lo siguiente es el número de valores a extraer del buffer y el tipo de valor (float).
      Lo siguiente es la normalización de los datos (si o no), de momento no nos vamos a parar en eso, pero un dato normalizado está entre -1 y 1.
      Los dos últimos valores son el espacio entre valor y valor, y el espacio desde el principio hasta el primer valor.
      Ahora cuando se ejecute el programa, por cada vértice ira secuencialmente seleccionando los valores del buffer para cada uno como se pretende ilustrar en las imágenes.

      Estado del buffer en el vertice primero

      Estado del buffer en el vertice primero

    • Lo siguiente que nos encontramos en el shader es una variable out.
      Las variables
      out sirven para comunicar las diferentes etapas del pipeline de los shaders. Un out, concatena con un in de la siguiente etapa.
    • Por último tenemos el programa en si, asignamos a la variable de salida de color, la variable de entrada directamente.
      Y a la variable que viene por defecto en GLSL (
      gl_Position) la posición en la que se encuentra el vértice.
      Los mas observadores se habrán dado cuenta de que el vector es de 4 y las variables que les pasamos son de 2 y 3 posiciones. Los valores no definidos se predefinen, en el caso de los tres primeros (RGB o XYZ) con 0 y el cuarto (alpha o w) con 1.
  • En el caso del fragment shader no hay mucho que explicar habiendo explicado el vertex antes.
    • Tenemos una variable de entrada color (que es la que tiene de salida el vertex) y una variable de salida color que es el color final que se pintara en pantalla.
    • El cuerpo del programa simplemente pasa el color que recibe. el pipeline hace automáticamente una interpolación de los valores de tal manera que lo que se verá es un degradado.
Por último solo queda explicar las últimas dos lineas de nuestro programa principal: 
glEnableVertexAttribArray( vColor );
glEnableVertexAttribArray( vPosition );

Hace que las dos variables estén habilitadas. Se el indica que ya tiene un valor asociado.

Pues después de tanto leer solo queda probarlo!!

Debería de salir lo siguiente:

Os invito a jugar un poco a cambiar los valores y entender como funciona el código. Una de las pruebas que yo hice fue elevar al cuadrado el valor del color para ver si salia lo que me imaginaba, y si! al estar en unos valores entre 0 y 1, al elevar al cuadrado los valores se hacen mas pequeños excepto en los vértices que se mantiene. Si elevásemos a la enésima potencia tendríamos sólo los vértices coloreados.
Para que os hagais a la idea, elevando a 4 cada componente (fragment shader)


    fColor = vec4(color.x*color.x*color.x*color.x, color.y*color.y*color.y*color.y, color.z*color.z*color.z*color.z, color.w*color.w*color.w*color.w);

tendríamos lo siguiente: 




Espero que os haya gustado.

Ampliando librerías para OpenGL: GLEW, GL3W, vmath, ShaderLoader

Para poder seguir avanzando vamos a tener que utilizar unas librerías que nos van a hacer la vida más fácil.
  • GLEW es una librería que en tiempo de ejecución determina que extensiones de OpenGL son soportadas por el cliente.
  • GL3W librería que carga solo la funcionalidad básica de OpenGL 3/4.
    Esta librería se puede obtener directamente desde GitHub y hace falta Python para compilar el script que te descargas. Sólo hace falta para generar el script, después lo podemos desinstalar: 
    https://github.com/skaslev/gl3w
  • vmath y el cargador de shaders son dos herramientas que he obtenido con el set de ejemplos del libro: "OpenGL Programming Guide. Eight Edition"
    Se trata de dos librerías que, facilitan las operaciones con vectores y matrices (
    vmath) y la carga de shaders (LoadShaders). El código de las mismas es el siguiente:
    vmath.h
    #ifndef __VMATH_H__
    #define __VMATH_H__
    
    
    #define _USE_MATH_DEFINES  1 // Include constants defined in math.h
    #include <math.h>
    
    namespace vmath
    {
    
    template <typename T> 
    inline T radians(T angleInRadians)
    {
     return angleInRadians * static_cast<T>(180.0/M_PI);
    }
    
    template <const bool cond>
    class ensure
    {
    public:
        inline ensure() { switch (false) { case false: case cond: break; } }
    };
    
    template <typename T, const int len> class vecN;
    
    template <typename T, const int len>
    class vecN
    {
    public:
        typedef class vecN<T,len> my_type;
    
        // Default constructor does nothing, just like built-in types
        inline vecN()
        {
            // Uninitialized variable
        }
    
        // Copy constructor
        inline vecN(const vecN& that)
        {
            assign(that);
        }
    
        // Construction from scalar
        inline vecN(T s)
        {
            int n;
            for (n = 0; n < len; n++)
            {
                data[n] = s;
            }
        }
    
        // Assignment operator
        inline vecN& operator=(const vecN& that)
        {
            assign(that);
            return *this;
        }
    
        inline vecN operator+(const vecN& that) const
        {
            my_type result;
            int n;
            for (n = 0; n < len; n++)
                result.data[n] = data[n] + that.data[n];
            return result;
        }
    
        inline vecN& operator+=(const vecN& that)
        {
            return (*this = *this + that);
        }
    
        inline vecN operator-() const
        {
            my_type result;
            int n;
            for (n = 0; n < len; n++)
                result.data[n] = -data[n];
            return result;
        }
    
        inline vecN operator-(const vecN& that) const
        {
            my_type result;
            int n;
            for (n = 0; n < len; n++)
                result.data[n] = data[n] - that.data[n];
            return result;
        }
    
        inline vecN& operator-=(const vecN& that)
        {
            return (*this = *this - that);
        }
    
        inline vecN operator*(const vecN& that) const
        {
            my_type result;
            int n;
            for (n = 0; n < len; n++)
                result.data[n] = data[n] * that.data[n];
            return result;
        }
    
        inline vecN& operator*=(const vecN& that)
        {
            return (*this = *this * that);
        }
    
        inline vecN operator*(const T& that) const
        {
            my_type result;
            int n;
            for (n = 0; n < len; n++)
                result.data[n] = data[n] * that;
            return result;
        }
    
        inline vecN& operator*=(const T& that)
        {
            assign(*this * that);
    
            return *this;
        }
    
        inline vecN operator/(const vecN& that) const
        {
            my_type result;
            int n;
            for (n = 0; n < len; n++)
                result.data[n] = data[n] * that.data[n];
            return result;
        }
    
        inline vecN& operator/=(const vecN& that)
        {
            assign(*this * that);
    
            return *this;
        }
    
        inline vecN operator/(const T& that) const
        {
            my_type result;
            int n;
            for (n = 0; n < len; n++)
                result.data[n] = data[n] / that;
            return result;
        }
    
        inline vecN& operator/(const T& that)
        {
            assign(*this / that);
        }
    
        inline T& operator[](int n) { return data[n]; }
        inline const T& operator[](int n) const { return data[n]; }
    
        inline static int size(void) { return len; }
    
        inline operator const T* () const { return &data[0]; }
    
    protected:
        T data[len];
    
        inline void assign(const vecN& that)
        {
            int n;
            for (n = 0; n < len; n++)
                data[n] = that.data[n];
        }
    };
    
    template <typename T>
    class Tvec2 : public vecN<T,2>
    {
    public:
        typedef vecN<T,2> base;
    
        // Uninitialized variable
        inline Tvec2() {}
        // Copy constructor
        inline Tvec2(const base& v) : base(v) {}
    
        // vec2(x, y);
        inline Tvec2(T x, T y)
        {
            base::data[0] = x;
            base::data[1] = y;
        }
    };
    
    template <typename T>
    class Tvec3 : public vecN<T,3>
    {
    public:
        typedef vecN<T,3> base;
    
        // Uninitialized variable
        inline Tvec3() {}
    
        // Copy constructor
        inline Tvec3(const base& v) : base(v) {}
    
        // vec3(x, y, z);
        inline Tvec3(T x, T y, T z)
        {
            base::data[0] = x;
            base::data[1] = y;
            base::data[2] = z;
        }
    
        // vec3(v, z);
        inline Tvec3(const Tvec2<T>& v, T z)
        {
            base::data[0] = v[0];
            base::data[1] = v[1];
            base::data[2] = z;
        }
    
        // vec3(x, v)
        inline Tvec3(T x, const Tvec2<T>& v)
        {
            base::data[0] = x;
            base::data[1] = v[0];
            base::data[2] = v[1];
        }
    };
    
    template <typename T>
    class Tvec4 : public vecN<T,4>
    {
    public:
        typedef vecN<T,4> base;
    
        // Uninitialized variable
        inline Tvec4() {}
    
        // Copy constructor
        inline Tvec4(const base& v) : base(v) {}
    
        // vec4(x, y, z, w);
        inline Tvec4(T x, T y, T z, T w)
        {
            base::data[0] = x;
            base::data[1] = y;
            base::data[2] = z;
            base::data[3] = w;
        }
    
        // vec4(v, z, w);
        inline Tvec4(const Tvec2<T>& v, T z, T w)
        {
            base::data[0] = v[0];
            base::data[1] = v[1];
            base::data[2] = z;
            base::data[3] = w;
        }
    
        // vec4(x, v, w);
        inline Tvec4(T x, const Tvec2<T>& v, T w)
        {
            base::data[0] = x;
            base::data[1] = v[0];
            base::data[2] = v[1];
            base::data[3] = w;
        }
    
        // vec4(x, y, v);
        inline Tvec4(T x, T y, const Tvec2<T>& v)
        {
            base::data[0] = x;
            base::data[1] = y;
            base::data[2] = v[0];
            base::data[3] = v[1];
        }
    
        // vec4(v1, v2);
        inline Tvec4(const Tvec2<T>& u, const Tvec2<T>& v)
        {
            base::data[0] = u[0];
            base::data[1] = u[1];
            base::data[2] = v[0];
            base::data[3] = v[1];
        }
    
        // vec4(v, w);
        inline Tvec4(const Tvec3<T>& v, T w)
        {
            base::data[0] = v[0];
            base::data[1] = v[1];
            base::data[2] = v[2];
            base::data[3] = w;
        }
    
        // vec4(x, v);
        inline Tvec4(T x, const Tvec3<T>& v)
        {
            base::data[0] = x;
            base::data[1] = v[0];
            base::data[2] = v[1];
            base::data[3] = v[2];
        }
    };
    
    typedef Tvec2<float> vec2;
    typedef Tvec2<int> ivec2;
    typedef Tvec2<unsigned int> uvec2;
    typedef Tvec2<double> dvec2;
    
    typedef Tvec3<float> vec3;
    typedef Tvec3<int> ivec3;
    typedef Tvec3<unsigned int> uvec3;
    typedef Tvec3<double> dvec3;
    
    typedef Tvec4<float> vec4;
    typedef Tvec4<int> ivec4;
    typedef Tvec4<unsigned int> uvec4;
    typedef Tvec4<double> dvec4;
    
    template <typename T, int n>
    static inline const vecN<T,n> operator * (T x, const vecN<T,n>& v)
    {
        return v * x;
    }
    
    template <typename T>
    static inline const Tvec2<T> operator / (T x, const Tvec2<T>& v)
    {
        return Tvec2<T>(x / v[0], x / v[1]);
    }
    
    template <typename T>
    static inline const Tvec3<T> operator / (T x, const Tvec3<T>& v)
    {
        return Tvec3<T>(x / v[0], x / v[1], x / v[2]);
    }
    
    template <typename T>
    static inline const Tvec4<T> operator / (T x, const Tvec4<T>& v)
    {
        return Tvec4<T>(x / v[0], x / v[1], x / v[2], x / v[3]);
    }
    
    template <typename T, int len>
    static inline T dot(const vecN<T,len>& a, const vecN<T,len>& b)
    {
        int n;
        T total = T(0);
        for (n = 0; n < len; n++)
        {
            total += a[n] * b[n];
        }
        return total;
    }
    
    template <typename T>
    static inline vecN<T,3> cross(const vecN<T,3>& a, const vecN<T,3>& b)
    {
        return Tvec3<T>(a[1] * b[2] - b[1] * a[2],
                        a[2] * b[0] - b[2] * a[0],
                        a[0] * b[1] - b[0] * a[1]);
    }
    
    template <typename T, int len>
    static inline T length(const vecN<T,len>& v)
    {
        T result(0);
    
        for (int i = 0; i < v.size(); ++i)
        {
            result += v[i] * v[i];
        }
    
        return (T)sqrt(result);
    }
    
    template <typename T, int len>
    static inline vecN<T,len> normalize(const vecN<T,len>& v)
    {
        return v / length(v);
    }
    
    template <typename T, int len>
    static inline T distance(const vecN<T,len>& a, const vecN<T,len>& b)
    {
        return length(b - a);
    }
    
    template <typename T, const int w, const int h>
    class matNM
    {
    public:
        typedef class matNM<T,w,h> my_type;
        typedef class vecN<T,h> vector_type;
    
        // Default constructor does nothing, just like built-in types
        inline matNM()
        {
            // Uninitialized variable
        }
    
        // Copy constructor
        inline matNM(const matNM& that)
        {
            assign(that);
        }
    
        // Construction from element type
        // explicit to prevent assignment from T
        explicit inline matNM(T f)
        {
            for (int n = 0; n < w; n++)
            {
                data[n] = f;
            }
        }
    
        // Construction from vector
        inline matNM(const vector_type& v)
        {
            for (int n = 0; n < w; n++)
            {
                data[n] = v;
            }
        }
    
        // Assignment operator
        inline matNM& operator=(const my_type& that)
        {
            assign(that);
            return *this;
        }
    
        inline matNM operator+(const my_type& that) const
        {
            my_type result;
            int n;
            for (n = 0; n < w; n++)
                result.data[n] = data[n] + that.data[n];
            return result;
        }
    
        inline my_type& operator+=(const my_type& that)
        {
            return (*this = *this + that);
        }
    
        inline my_type operator-(const my_type& that) const
        {
            my_type result;
            int n;
            for (n = 0; n < w; n++)
                result.data[n] = data[n] - that.data[n];
            return result;
        }
    
        inline my_type& operator-=(const my_type& that)
        {
            return (*this = *this - that);
        }
    
        // Matrix multiply.
        // TODO: This only works for square matrices. Need more template skill to make a non-square version.
        inline my_type operator*(const my_type& that) const
        {
            ensure<w == h>();
    
            my_type result(0);
    
            for (int j = 0; j < w; j++)
            {
                for (int i = 0; i < h; i++)
                {
                    T sum(0);
    
                    for (int n = 0; n < w; n++)
                    {
                        sum += data[n][i] * that[j][n];
                    }
    
                    result[j][i] = sum;
                }
            }
    
            return result;
        }
    
        inline my_type& operator*=(const my_type& that)
        {
            return (*this = *this * that);
        }
    
        inline vector_type& operator[](int n) { return data[n]; }
        inline const vector_type& operator[](int n) const { return data[n]; }
        inline operator T*() { return &data[0][0]; }
        inline operator const T*() const { return &data[0][0]; }
    
        inline matNM<T,h,w> transpose(void) const
        {
            matNM<T,h,w> result;
            int x, y;
    
            for (y = 0; y < w; y++)
            {
                for (x = 0; x < h; x++)
                {
                    result[x][y] = data[y][x];
                }
            }
    
            return result;
        }
    
        static inline my_type identity()
        {
            ensure<w == h>();
    
            my_type result(0);
    
            for (int i = 0; i < w; i++)
            {
                result[i][i] = 1;
            }
    
            return result;
        }
    
        static inline int width(void) { return w; }
        static inline int height(void) { return h; }
    
    protected:
        // Column primary data (essentially, array of vectors)
        vecN<T,h> data[w];
    
        // Assignment function - called from assignment operator and copy constructor.
        inline void assign(const matNM& that)
        {
            int n;
            for (n = 0; n < w; n++)
                data[n] = that.data[n];
        }
    };
    
    /*
    template <typename T, const int N>
    class TmatN : public matNM<T,N,N>
    {
    public:
        typedef matNM<T,N,N> base;
        typedef TmatN<T,N> my_type;
    
        inline TmatN() {}
        inline TmatN(const my_type& that) : base(that) {}
        inline TmatN(float f) : base(f) {}
        inline TmatN(const vecN<T,4>& v) : base(v) {}
    
        inline my_type transpose(void)
        {
            my_type result;
            int x, y;
    
            for (y = 0; y < h; y++)
            {
                for (x = 0; x < h; x++)
                {
                    result[x][y] = data[y][x];
                }
            }
    
            return result;
        }
    };
    */
    
    template <typename T>
    class Tmat4 : public matNM<T,4,4>
    {
    public:
        typedef matNM<T,4,4> base;
        typedef Tmat4<T> my_type;
    
        inline Tmat4() {}
        inline Tmat4(const my_type& that) : base(that) {}
        inline Tmat4(const base& that) : base(that) {}
        inline Tmat4(const vecN<T,4>& v) : base(v) {}
        inline Tmat4(const vecN<T,4>& v0,
                     const vecN<T,4>& v1,
                     const vecN<T,4>& v2,
                     const vecN<T,4>& v3)
        {
            base::data[0] = v0;
            base::data[1] = v1;
            base::data[2] = v2;
            base::data[3] = v3;
        }
    };
    
    typedef Tmat4<float> mat4;
    typedef Tmat4<int> imat4;
    typedef Tmat4<unsigned int> umat4;
    typedef Tmat4<double> dmat4;
    
    static inline mat4 frustum(float left, float right, float bottom, float top, float n, float f)
    {
        mat4 result(mat4::identity());
    
        if ((right == left) ||
            (top == bottom) ||
            (n == f) ||
            (n < 0.0) ||
            (f < 0.0))
           return result;
    
        result[0][0] = (2.0f * n) / (right - left);
        result[1][1] = (2.0f * n) / (top - bottom);
    
        result[2][0] = (right + left) / (right - left);
        result[2][1] = (top + bottom) / (top - bottom);
        result[2][2] = -(f + n) / (f - n);
        result[2][3]= -1.0f;
    
        result[3][2] = -(2.0f * f * n) / (f - n);
        result[3][3] =  0.0f;
    
        return result;
    }
    
    static inline mat4 perspective(float fovy /* in degrees */, float aspect, float n, float f)
    {
     float  top = n * tan(radians(0.5f*fovy)); // bottom = -top
     float  right = top * aspect; // left = -right
     return frustum(-right, right, -top, top, n, f);
    }
    
    template <typename T>
    static inline Tmat4<T> lookat(vecN<T,3> eye, vecN<T,3> center, vecN<T,3> up)
    {
        const Tvec3<T> f = normalize(center - eye);
        const Tvec3<T> upN = normalize(up);
        const Tvec3<T> s = cross(f, upN);
        const Tvec3<T> u = cross(s, f);
        const Tmat4<T> M = Tmat4<T>(Tvec4<T>(s[0], u[0], -f[0], T(0)),
                                    Tvec4<T>(s[1], u[1], -f[1], T(0)),
                                    Tvec4<T>(s[2], u[2], -f[2], T(0)),
                                    Tvec4<T>(T(0), T(0), T(0), T(1)));
    
    
        return M * translate(-eye);
    }
    
    template <typename T>
    static inline Tmat4<T> translate(T x, T y, T z)
    {
        return Tmat4<T>(Tvec4<T>(1.0f, 0.0f, 0.0f, 0.0f),
                        Tvec4<T>(0.0f, 1.0f, 0.0f, 0.0f),
                        Tvec4<T>(0.0f, 0.0f, 1.0f, 0.0f),
                        Tvec4<T>(x, y, z, 1.0f));
    }
    
    template <typename T>
    static inline Tmat4<T> translate(const vecN<T,3>& v)
    {
        return translate(v[0], v[1], v[2]);
    }
    
    template <typename T>
    static inline Tmat4<T> scale(T x, T y, T z)
    {
        return Tmat4<T>(Tvec4<T>(x, 0.0f, 0.0f, 0.0f),
                        Tvec4<T>(0.0f, y, 0.0f, 0.0f),
                        Tvec4<T>(0.0f, 0.0f, z, 0.0f),
                        Tvec4<T>(0.0f, 0.0f, 0.0f, 1.0f));
    }
    
    template <typename T>
    static inline Tmat4<T> scale(const Tvec4<T>& v)
    {
        return scale(v[0], v[1], v[2]);
    }
    
    template <typename T>
    static inline Tmat4<T> scale(T x)
    {
        return Tmat4<T>(Tvec4<T>(x, 0.0f, 0.0f, 0.0f),
                        Tvec4<T>(0.0f, x, 0.0f, 0.0f),
                        Tvec4<T>(0.0f, 0.0f, x, 0.0f),
                        Tvec4<T>(0.0f, 0.0f, 0.0f, 1.0f));
    }
    
    template <typename T>
    static inline Tmat4<T> rotate(T angle, T x, T y, T z)
    {
        Tmat4<T> result;
    
        const T x2 = x * x;
        const T y2 = y * y;
        const T z2 = z * z;
        float rads = float(angle) * 0.0174532925f;
        const float c = cosf(rads);
        const float s = sinf(rads);
        const float omc = 1.0f - c;
    
        result[0] = Tvec4<T>(T(x2 * omc + c), T(y * x * omc + z * s), T(x * z * omc - y * s), T(0));
        result[1] = Tvec4<T>(T(x * y * omc - z * s), T(y2 * omc + c), T(y * z * omc + x * s), T(0));
        result[2] = Tvec4<T>(T(x * z * omc + y * s), T(y * z * omc - x * s), T(z2 * omc + c), T(0));
        result[3] = Tvec4<T>(T(0), T(0), T(0), T(1));
    
        return result;
    }
    
    template <typename T>
    static inline Tmat4<T> rotate(T angle, const vecN<T,3>& v)
    {
        return rotate<T>(angle, v[0], v[1], v[2]);
    }
    
    #ifdef min
    #undef min
    #endif
    
    template <typename T>
    static inline T min(T a, T b)
    {
        return a < b ? a : b;
    }
    
    #ifdef max
    #undef max
    #endif
    
    template <typename T>
    static inline T max(T a, T b)
    {
        return a >= b ? a : b;
    }
    
    template <typename T, const int N>
    static inline vecN<T,N> min(const vecN<T,N>& x, const vecN<T,N>& y)
    {
        vecN<T,N> t;
        int n;
    
        for (n = 0; n < N; n++)
        {
            t[n] = min(x[n], y[n]);
        }
    
        return t;
    }
    
    template <typename T, const int N>
    static inline vecN<T,N> max(const vecN<T,N>& x, const vecN<T,N>& y)
    {
        vecN<T,N> t;
        int n;
    
        for (n = 0; n < N; n++)
        {
            t[n] = max<T>(x[n], y[n]);
        }
    
        return t;
    }
    
    template <typename T, const int N>
    static inline vecN<T,N> clamp(const vecN<T,N>& x, const vecN<T,N>& minVal, const vecN<T,N>& maxVal)
    {
        return min<T>(max<T>(x, minVal), maxVal);
    }
    
    template <typename T, const int N>
    static inline vecN<T,N> smoothstep(const vecN<T,N>& edge0, const vecN<T,N>& edge1, const vecN<T,N>& x)
    {
        vecN<T,N> t;
        t = clamp((x - edge0) / (edge1 - edge0), vecN<T,N>(T(0)), vecN<T,N>(T(1)));
        return t * t * (vecN<T,N>(T(3)) - vecN<T,N>(T(2)) * t);
    }
    
    template <typename T, const int N, const int M>
    static inline matNM<T,N,M> matrixCompMult(const matNM<T,N,M>& x, const matNM<T,N,M>& y)
    {
        matNM<T,N,M> result;
        int i, j;
    
        for (j = 0; j < M; ++j)
        {
            for (i = 0; i < N; ++i)
            {
                result[i][j] = x[i][j] * y[i][j];
            }
        }
    
        return result;
    }
    
    template <typename T, const int N, const int M>
    static inline vecN<T,N> operator*(const vecN<T,M>& vec, const matNM<T,N,M>& mat)
    {
        int n, m;
        vecN<T,N> result(T(0));
    
        for (m = 0; m < M; m++)
        {
            for (n = 0; n < N; n++)
            {
                result[n] += vec[m] * mat[n][m];
            }
        }
    
        return result;
    }
    
    };
    
    #endif /* __VMATH_H__ */
    
    

    LoadShaders.h
    //////////////////////////////////////////////////////////////////////////////
    //
    //  --- LoadShaders.h ---
    //
    //////////////////////////////////////////////////////////////////////////////
    
    #ifndef __LOAD_SHADERS_H__
    #define __LOAD_SHADERS_H__
    
    #include <GL/gl.h>
    
    #ifdef __cplusplus
    extern "C" {
    #endif  // __cplusplus
    
    //----------------------------------------------------------------------------
    //
    //  LoadShaders() takes an array of ShaderFile structures, each of which
    //    contains the type of the shader, and a pointer a C-style character
    //    string (i.e., a NULL-terminated array of characters) containing the
    //    entire shader source.
    //
    //  The array of structures is terminated by a final Shader with the
    //    "type" field set to GL_NONE.
    //
    //  LoadShaders() returns the shader program value (as returned by
    //    glCreateProgram()) on success, or zero on failure. 
    //
    
    typedef struct {
        GLenum       type;
        const char*  filename;
        GLuint       shader;
    } ShaderInfo;
    
    GLuint LoadShaders( ShaderInfo* );
    
    //----------------------------------------------------------------------------
    
    #ifdef __cplusplus
    };
    #endif // __cplusplus
    
    #endif // __LOAD_SHADERS_H__
    
    

    LoadShaders.cpp
    //////////////////////////////////////////////////////////////////////////////
    //
    //  --- LoadShaders.cxx ---
    //
    //////////////////////////////////////////////////////////////////////////////
    
    #include <cstdlib>
    #include <iostream>
    #include <cstdio>
    
    #define GLEW_STATIC
    #include <GL/glew.h>
    #include "LoadShaders.h"
    
    #ifdef __cplusplus
    extern "C" {
    #endif // __cplusplus
    
    //----------------------------------------------------------------------------
    
    static const GLchar*
    ReadShader( const char* filename )
    {
    
        FILE* infile = fopen( filename, "rb" );
    
        if ( !infile ) {
    #ifdef _DEBUG
            std::cerr << "Unable to open file '" << filename << "'" << std::endl;
    #endif /* DEBUG */
            return NULL;
        }
    
        fseek( infile, 0, SEEK_END );
        int len = ftell( infile );
        fseek( infile, 0, SEEK_SET );
    
        GLchar* source = new GLchar[len+1];
    
        fread( source, 1, len, infile );
        fclose( infile );
    
        source[len] = 0;
    
        return const_cast<const GLchar*>(source);
    }
    
    //----------------------------------------------------------------------------
    
    GLuint
    LoadShaders( ShaderInfo* shaders )
    {
        if(glewInit()){
            std::cerr << "Unable to initialize GLEW..." << std::endl;
        }
        if ( shaders == NULL ) { return 0; }
    
        GLuint program = glCreateProgram();
    
        ShaderInfo* entry = shaders;
        while ( entry->type != GL_NONE ) {
            GLuint shader = glCreateShader( entry->type );
    
            entry->shader = shader;
    
            const GLchar* source = ReadShader( entry->filename );
            if ( source == NULL ) {
                for ( entry = shaders; entry->type != GL_NONE; ++entry ) {
                    glDeleteShader( entry->shader );
                    entry->shader = 0;
                }
    
                return 0;
            }
    
            glShaderSource( shader, 1, &source, NULL );
            delete [] source;
    
            glCompileShader( shader );
    
            GLint compiled;
            glGetShaderiv( shader, GL_COMPILE_STATUS, &compiled );
            if ( !compiled ) {
    #ifdef _DEBUG
                GLsizei len;
                glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &len );
    
                GLchar* log = new GLchar[len+1];
                glGetShaderInfoLog( shader, len, &len, log );
                std::cerr << "Shader compilation failed: " << log << std::endl;
                delete [] log;
    #endif /* DEBUG */
    
                return 0;
            }
    
            glAttachShader( program, shader );
            
            ++entry;
        }
    
    #ifdef GL_VERSION_4_1
        if ( GLEW_VERSION_4_1 ) {
            // glProgramParameteri( program, GL_PROGRAM_SEPARABLE, GL_TRUE );
        }
    #endif /* GL_VERSION_4_1 */
        
        glLinkProgram( program );
    
        GLint linked;
        glGetProgramiv( program, GL_LINK_STATUS, &linked );
        if ( !linked ) {
    #ifdef _DEBUG
            GLsizei len;
            glGetProgramiv( program, GL_INFO_LOG_LENGTH, &len );
    
            GLchar* log = new GLchar[len+1];
            glGetProgramInfoLog( program, len, &len, log );
            std::cerr << "Shader linking failed: " << log << std::endl;
            delete [] log;
    #endif /* DEBUG */
    
            for ( entry = shaders; entry->type != GL_NONE; ++entry ) {
                glDeleteShader( entry->shader );
                entry->shader = 0;
            }
            
            return 0;
        }
    
        return program;
    }
    
    //----------------------------------------------------------------------------
    #ifdef __cplusplus
    }
    #endif // __cplusplus

    No se si son las mejores pero nos vienen al pelo.
Ademas, hay que cambiar las opciones de las librerías incluidas en el proyecto, ahora hay que añadir: glew32. El orden de inclusión importa por lo que copio, pego y modifico el que ya teníamos en el post de configuración de Eclipse.
  1. mingw32
  2. glew32
  3. opengl32
  4. SDL2main
  5. SDL2

sábado, 6 de julio de 2013

OpenGL 4.3 Pipeline

Vamos a explicar como funciona el pipeline de OpenGL 4.3.
Como es lógico junto con la API, el pipeline ha ido evolucionando a si que vamos a analizar la última versión. Los pasos que se le han añadido en la 4.3 son Tesselation y Compute, este último no lo vamos a ver aquí.


Foto que pertenece al libro oficial de programación en OpenGL: OpenGL Programming Guide Eight Edition. Que recomiendo encarecidamente su adquisición si tienes un nivel de ingles leído decente.
La foto ha sido obtenida de la página http://my.safaribooksonline.com que nos permite una previsualización del principio de todos los capítulos

Antes de nada: ¿que es un Shader?. Los que hemos jugado a minecraft nos suena la palabra shader y lo asociamos a "petar el PC".
Un Shader es un programa que se le pasa a la GPU y que nos permite cambiar información de las primitivas que se envían. Cuando escribimos un Shader podemos indicar una serie de parámetros de entrada como si fuera una función de cualquier lenguaje de programación, sólo que no tiene nada que ver la forma de ser llamada.
Cuando creamos un programa en OpenGL, básicamente definimos los vértices, color, texturas...

  • La primera de las fases (Vertex Shader) recibe los vértices colores de los vértices y texturas y con ello podemos modificar la posición de los vértices  el color y posición de la textura. Esta fase es obligatoria, pero el Shader puede ser tan simple como "coge vértice y pásalo".
  • Tessellation Shader recibe todos los vértices ya modificados por el vertex shader y permite crear más primitivas para poder hacer unos modelos más agradables. Esta fase es opcional.


  • Geometry Shader puede manipular las primitivas e incluso añadir nuevas. También es opcional.
  • Primitive Assembly, organiza toda la información que le llega en geometría dejándolo preparado para las siguientes dos fases.
  • Clipping, algunas veces alguno de los vértices está fuera de la ventana. Adapta esos vértices para que todos los píxel se queden dentro de la ventana.
  • Rasterization es la fase en la que se calculan todos los candidatos a ser uno de los píxel que aparezca en la pantalla. Cada uno de estos píxel se les llama fragment, como se puede deducir, son pasados al Fragment Shader.
  • El Fragment Shader es la última parte programable del pipeline, modifica el color de los píxel y puede modificar incluso la profundidad (posición en z). También puede terminar el procesado del fragmento descartándolo. Este shader es obligatorio, aunque al igual que el vertex puede ser tan simple como: "coge fragmento y pásalo"
  • Por último hay un per-fragment operation, que se encarga de decidir si un píxel debe ser pintado en función de su posición en z y lo que ya había en el z-buffer.

De todas estas fases sólo son programables los Shader (marcados en azul), el resto las maneja automáticamente la GPU.

La programación de shaders es casi imprescindible y tremendamente potente, una GPU esta especializada en cálculos en coma flotante y tratamiento de imágenes. Poder delegar cálculos a la gráfica ya es un avance, pero poder manejar incluso cada píxel que se vaya a dibujar deja una infinidad de posibilidades para realismo en gráficos.

jueves, 4 de julio de 2013

Inicialización de OpenGL

Hoy vamos a hacer nuestra primera inicialización de un contexto OpenGL en SDL.
Para ello me he basado en el "Hola Mundo II".
En el archivo de cabecera hemos hecho algunos cambios:
#ifndef DEMO_3_H_
#define DEMO_3_H_
#include "SDL2/SDL.h"

class Demo_3 {
private:
    bool running;
    SDL_Window* window;
    SDL_GLContext ctxt;

    int aux;

    static const uint32_t   WIN_HEIGHT = 512; //px
    static const uint32_t   WIN_WIDTH  = 512; //px
    static const char*      WIN_TITLE; //px
public:

    Demo_3();
    int Execute(){ return OnExecute(); }
    bool Init(){ return OnInit(); }
    void Loop(){ return OnLoop(); }
    void Render(){ return OnRender(); }
    void Cleanup(){ return OnCleanup(); }
    void Event(SDL_Event* Event){ OnEvent(Event); }



    int OnExecute();

    bool OnInit();
    void OnEvent(SDL_Event* Event);
    void OnLoop();
    void OnRender();
    void OnCleanup();



    void setupOpenGl();

};


#endif /* DEMO_3_H_ */
Hemos añadido dos variables, una es el contexto de OpenGL (SDL_GLContext) que es el lugar donde se va a dibujar, y aux, que es una variable dummy para poder realizar una pequeña interactuación con el entorno.
Al final del todo he añadido una función llamada: setupOpenGl que inicializa todo lo necesario para utilizar el contexto.
El header como siempre tiene poco que explicar, vayámonos al .cpp
#include "Demo_3.h"
#include "GL/gl.h"
#include <iostream>

const char* Demo_3::WIN_TITLE = "Titulo de la Ventana";

Demo_3::Demo_3() : running(false), window(NULL), ctxt(NULL), aux(0){}

void Demo_3::OnEvent(SDL_Event* event) {
    switch (event->type) {
        case SDL_KEYUP:
            switch(event->key.keysym.sym){
                case SDLK_KP_PLUS:
                    aux++;
                    aux %= 3;
                    break;
                case SDLK_v:
                    std::cout << glGetString(GL_VERSION) << std::endl;
                    break;
                case SDLK_ESCAPE:
                    running = false;
                    break;
                default:
                    break;
            }
            break;
        case SDL_QUIT:
            running = false;
            break;
        default:
            break;
    }
}
void Demo_3::OnLoop() {}
void Demo_3::OnRender() {

    glClearColor(aux==0? 1:0, aux==1? 1:0, aux==2? 1:0, 1.0);
    glClear( GL_COLOR_BUFFER_BIT);
    SDL_GL_SwapWindow(window);
}

void Demo_3::OnCleanup() {
    SDL_GL_DeleteContext(ctxt);
    SDL_DestroyWindow(window);
    SDL_Quit();
}

bool Demo_3::OnInit() {
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
        return false;

    window = SDL_CreateWindow(WIN_TITLE,
            SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
            WIN_WIDTH, WIN_HEIGHT,
            SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);
    if(!window)
        return false;

    setupOpenGl();

    running = true;

    return true;
}

int Demo_3::OnExecute() {
    if (!Init())
        return -1;

    SDL_Event event;

    while (running) {

        while (SDL_PollEvent(&event))
            Event(&event);

        Loop();
        Render();
    }

    Cleanup();

    return 0;
}


void Demo_3::setupOpenGl(){
   //SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
   //SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
   SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
   SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 32);
   ctxt = SDL_GL_CreateContext(window);

   //vsync ON
   SDL_GL_SetSwapInterval(1);

}
Empecemos por OnInit, como siempre inicializa SDL y crea la ventana, pero si habéis estado atentos hay un flag más: SDL_WINDOW_OPENGL, esto le dice a la ventana que se prepare para el entorno OpenGL, el segundo cambio es la llamada a setupOpenGl.
setupOpenGl configura los atributos del entorno OpenGL, crea el contexto y activa la sincronización vertical, vayamos linea por linea:
  • Las dos primeras lineas que están comentadas especifican que versión del contexto OpenGL deben utilizar, en este caso estarían indicando la versión 3.2. Si omites esas dos líneas crea el contexto con la versión más reciente utilizada en el ordenador, en mi caso en la actualidad 4.3.0 que es el entorno que vamos a aprender a usar.
  • La siguiente línea activa el "Doble Buffer", si no se activa, cuando tengamos objetos dibujados en el entorno, la imagen parpadeara. Esto es debido a que OpenGL utilizaría el mismo buffer para escribir que para mandar la imagen a la pantalla. Al utilizar Doble Buffer el ordenador utilizaría un buffer para escribir y otro para mandar la imagen a la pantalla, en el siguiente ciclo, los buffer se intercambiarían, el de la pantalla pasaría a rellenarse y el relleno pasaría a la pantalla.
    En los juegos siempre sale la opción: triple buffer!!. Informándome por Internet he leído que el triple buffer en OpenGL no existe por linea de comandos, pero existe un triple buffer simulado. El problema de esto es que NVidia (e imagino que ATI tendrá algo parecido) por lo visto tiene una opción para activar el triple buffer, con lo cual con esa simulación estaríamos haciendo un cuadrabuffer lo que reventaría definitivamente nuestra aplicación. Por lo tanto nos quedamos con nuestro doble buffer. Como nota curiosa dicen que un buffer ocupa unos 25-30Mb en memoria, lo que supone relativamente poco para las actuales GPUs ya que, las peores tienen aproximadamente 512Mb. La noticia donde lo leí tenia su tiempo, quizá no tenían en cuenta la resolución HD720p ni HD1080p, o quizá si.
  • La cuarta linea configura si no me equivoco lo que se denomina Z-Buffer. El Z-Buffer es el buffer de profundidad. Cuando toda la linea de ejecución de la tarjeta gráfica se ejecuta, se comprueba en que posición de profundidad se haya ese píxel y si había uno con mas profundidad se sustituye por el nuevo. Pensemos en una mesa en una medio de una habitación, tenemos el buffer que contiene el suelo renderizado, cuando renderice la mesa decidirá, que la mesa está por encima del suelo y, que se tiene que ver. Sin embargo si tomamos cierta perspectiva, una de las cuatro patas de la mesa no debería verse o debería verse parcialmente, aún pudiéndose dibujar después. Si el z-buffer no estuviera activado, cada cosa se dibujaría en el orden de dibujado uno encima de otro no permitiendo entrelazamiento de capas. Por ejemplo, un pañuelo enrollado a un palo en forma de espiral: verías el palo y un pañuelo detrás o un pañuelo tapando un palo, nunca un palo con un pañuelo enrollado.
  • Volviendo a la línea, aumentamos el buffer, de 16bit (que es el actual "por defecto") a 32bit.
  • En la siguiente linea le pedimos a SDL que nos cree un entorno OpenGL en la ventana proporcionada, lo enlace y nos pase una referencia al contexto.
  • Por último activamos la sincronización vertical para evitar el Screen Tearing. Pincha en el enlace para más información.
En la función de inicialización no queda nada que comentar, lo siguiente es la gestión de eventos, he añadido un evento a la letra v para ver la versión que nuestro ordenador ha elegido de OpenGL. También le he añadido un evento a la tecla "+" del pad numérico, para cambiar el color del fondo de nuestra ventana mediante la variable aux.
Lo siguiente es la función Render, que hace tres llamadas:
  1. glClearColor(aux==0? 1:0, aux==1? 1:0, aux==2? 1:0, 1.0); Le dice con que color tiene que rellenar el buffer del contexto OpenGL cuando se llame a glClear. He utilizado aux para seleccionar rojo, verde y azul. Para ahorrar lineas he utilizado el operador ternario, o un "if rápido" como yo lo llamo, que para los que no lo conozcan: la condición se coloca antes del interrogante y después se coloca la instrucción si se cumple, y despues de los dos puntos, la instrucción si no se cumple. El último valor es Alpha o transparencia que colocamos a 1 (significa opaco, 0 = transparente, para el resto de números < 1 y >0 sería translúcido.).
    Para los que tengáis experiencia de diseño en otros entornos y lenguajes como puede ser CSS en html, os resultará extraño que los valores oscilen entre 0.0 y 1.0 en lugar de 0 y 255, pero es así xD.
  2. glClear( GL_COLOR_BUFFER_BIT); Le indica que debe limpiar un buffer, la constante indica el buffer de dibujo, veremos que hay más buffers que se pueden limpiar.
  3. SDL_GL_SwapWindow(window); Esto intercambia los buffers como hemos explicado antes.
Por último pero no menos importante tenemos el cleanup al que hemos añadido una línea que destruye el contexto OpenGL.
Muy importante que no se nos olvide nunca limpiar todo porque puede quedar memoria residual en el ordenador. A nadie le gusta que después de abrir unos cuantos programas tengas que reiniciar porque tu pobre RAM está llena de mierda.

miércoles, 3 de julio de 2013

Entrada por Eventos con SDL

En este tutorial vamos a aprender a capturar algunos eventos de teclado y ratón. Os dejo una página del sitio oficial para que veáis todos los eventos que queráis capturar. Aun así habrá otro tutorial con Eventos Avanzado.

Al igual que en el tutorial anterior, tenemos dividido el código fundamentalmente en la cabecera (.h) y la implementacion (.cpp). Con respecto al anterior tutorial, la cabecera prácticamente no ha cambiado, donde antes ponía Demo_1, ahora pone Demo_2, a si que os la dejo aquí para copiar pero doy por hecha la explicación.
#ifndef DEMO_2_H_
#define DEMO_2_H_
#include "SDL2/SDL.h"

class Demo_2 {
private:
    bool running;
    SDL_Window* mainwindow;
    static const uint32_t WIN_HEIGHT = 512; //px
    static const uint32_t WIN_WIDTH  = 512; //px
public:

    Demo_2();
    int Execute(){ return OnExecute(); }
    bool Init(){ return OnInit(); }
    void Loop(){ return OnLoop(); }
    void Render(){ return OnRender(); }
    void Cleanup(){ return OnCleanup(); }
    void Event(SDL_Event* Event){ OnEvent(Event); }



    int OnExecute();

    bool OnInit();
    void OnEvent(SDL_Event* Event);
    void OnLoop();
    void OnRender();
    void OnCleanup();

};


#endif
Con respecto a la implementación si se han producido un par de cambios, pongo el código y lo comentamos.
#include "Demo_2.h"
#include <iostream>

Demo_2::Demo_2() : running(false), mainwindow(NULL) {}

void Demo_2::OnEvent(SDL_Event* event) {
    switch (event->type) {
        case SDL_MOUSEBUTTONUP:
            std::cout << "MOUSE_BUTTON_UP: x:" << event->button.x << "| y:"
                    << event->button.y
                    << "| button1:" << (event->button.button == SDL_BUTTON_LEFT)
                    << "| button2:" << (event->button.button == SDL_BUTTON_RIGHT)
                    << "| button3:" << (event->button.button == SDL_BUTTON_MIDDLE)
                    << "| clickState:" << event->motion.state
                    << std::endl << std::endl;
            break;
        case SDL_MOUSEBUTTONDOWN:
            std::cout << "MOUSE_BUTTON_DOWN: x:" << event->button.x
                    << "| y:" << event->button.y
                    << "| button1:" << (event->button.button == SDL_BUTTON_LEFT)
                    << "| button2:" << (event->button.button == SDL_BUTTON_RIGHT)
                    << "| button3:" << (event->button.button == SDL_BUTTON_MIDDLE)
                    << "| clickState:" << event->motion.state
                    << std::endl << std::endl;
            break;
        case SDL_MOUSEMOTION:
            std::cout << "MOUSE_MOTION: x:" << event->motion.x
                    << "| y:" << event->motion.y
                    << "| xrel:" << event->motion.xrel
                    << "| yrel:" << event->motion.yrel
                    << "| clickState:" << event->motion.state
                    << "| button1:" << ((event->motion.state  & 1) == 1)
                    << "| button2:" << ((event->motion.state  & 4) == 4)
                    << "| button3:" << ((event->motion.state  & 2) == 2)
                    << std::endl << std::endl;
            break;
        case SDL_KEYUP:
            running = event->key.keysym.sym != SDLK_ESCAPE;
            break;
        case SDL_QUIT:
            running = false;
            break;
        default:
            break;
    }
}
void Demo_2::OnLoop() {}
void Demo_2::OnRender() {}

void Demo_2::OnCleanup() {
    SDL_DestroyWindow(mainwindow);
    SDL_Quit();
}

bool Demo_2::OnInit() {
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
        return false;

    mainwindow = SDL_CreateWindow("Titulo de la Ventana",
            SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
            WIN_WIDTH, WIN_HEIGHT,
            SDL_WINDOW_SHOWN);
    if(!mainwindow)
        return false;

    running = true;

    return true;
}

int Demo_2::OnExecute() {
    if (!Init())
        return -1;

    SDL_Event event;

    while (running) {

        while (SDL_PollEvent(&event))
            Event(&event);

        Loop();
        Render();
    }

    Cleanup();

    return 0;
}
He quitado todas las trazas para dejar la salida limpia para lo que nos concierne, el Input, que ahora tiene las trazas pertinentes.
Los principales cambios se han producido en OnExecute y en OnEvent.

  • OnExecute: Hemos quitado el contador de 10 vueltas y ahora tenemos un objeto del tipo SDL_Event para capturar los eventos.
    Después tenemos igual que antes el bucle en el que tenemos la función Loop y Render y al principio hemos añadido otro bucle. Este bucle se encarga de sacar todos los eventos (SDL_PollEvent) que se han producido en esa vuelta de bucle y los envía a la función Event para tratar. Si os fijáis, lo que hacemos es pasarle el puntero al evento, por eso el PollEvent puede guardar en tu estructura el evento.
    Por lo demás el OnExecute no tiene mucho más que comentar.
  • OnEvent: Recibimos un evento, ¿Que forma tiene un evento?
    Según la página oficial esta es su representación de evento.
    typedef union{
      Uint8 type;
      SDL_ActiveEvent active;
      SDL_KeyboardEvent key;
      SDL_MouseMotionEvent motion;
      SDL_MouseButtonEvent button;
      SDL_JoyAxisEvent jaxis;
      SDL_JoyBallEvent jball;
      SDL_JoyHatEvent jhat;
      SDL_JoyButtonEvent jbutton;
      SDL_ResizeEvent resize;
      SDL_ExposeEvent expose;
      SDL_QuitEvent quit;
      SDL_UserEvent user;
      SDL_SywWMEvent syswm;
    } SDL_Event;
    Y si ahora vemos otro evento cualquiera vemos que es de la forma:
    typedef struct{
      Uint8 type;
      Uint8 state;
      SDL_keysym keysym;
    } SDL_KeyboardEvent;
    Todos empiezan con Uint8 type; y después tienen mas información. Si os fijáis y sabéis lo que son, la estructura del Evento es una Union y no una struct, por eso todas las implementaciones de evento deben tener el type al principio. 
    Para los que no sepan lo que es, una Union hace que todo lo que este dentro de las llaves ocupe la misma posición de memoria, dependiendo del tipo de evento se rellenan mas o menos bits. Esto que han hecho es una especie de simulación de herencia o interfaz para struct. La explicación se ha quedado un poco pobre, si no te ha quedado claro mira en Internet que encontraras mucha información.
    Por ello lo primero que hace la función OnEvent es mirar que tipo de evento estamos tratando, para ello hacemos un switch con el type del evento.
    Hemos capturado 5 tipos como ejemplo, aquí podéis encontrar todos (Input Events):
    http://wiki.libsdl.org/APIByCategory
    Después de capturar el tipo de evento accedemos al tipo de evento correspondiente en la estructura, por ejemplo: 
    event->button Y a la propiedad que deseemos, SDL_MOUSEBUTTONUP al ser un evento de ratón podemos capturar la posicion x e y, el botón del ratón que se ha liberado (lógicamente solo puede ser uno a la vez, por muy rápidos que seamos es casi imposible liberar dos a la vez) y el estado en el que está el ratón.
    En el caso de mousemotion existen xrel e yrel que te dan la posición relativa a la anterior posición del ratón. El caso del estado te da información acerca de que botones están pulsados con una mascara binaria, para poder obtener cuales están pulsados utilizo la & binaria y no la && lógica. He comprobado que obtiene hasta 5 botones del ratón en mi caso. Imagino que no dependerá del ratón, a si que, si quieren mas botones de ratón que se los bindeen a teclas especiales :P.
    Para ejemplificar el teclado he puesto que cuando se presiona la tecla Escape (Esc) se termine el programa. Esa porción de código es suficientemente simple, no me paro más.
    Por ultimo SDL_QUIT corresponde al evento de hacer click en la X de la ventana, también salimos del programa. Si no lo configuramos, no podremos salir dándole a la X.
Para probar el ejemplo igual que en el anterior, crearos un main con un objeto del tipo Demo_2 y llamad a Execute(). Deberiais tener una ventana y una traza en la consola de los eventos que hemos capturado, el mouse motion y el mouse click no se capturan si no estas encima de la ventana.
Aquí se termina este tutorial, espero os haya sido útil. Os dejo un enlace a la documentación de la API, en el apartado Input Events aparecen todos los eventos: http://wiki.libsdl.org/APIByCategory