lunes, 4 de noviembre de 2013

Proyecto Visual Studio configurado para OGL 4.3

No tengo mucho tiempo últimamente de aprender y mucho menos de escribir post, por lo que la frecuencia de publicación la he reducido y se mantendrá así durante una temporada.

Lo que si he hecho ha sido configurar un proyecto para Visual Studio 2012 que funciona tanto en la versión Express como en la Ultimate. Tiene metidas las librerias GLEW, GL3W, vmath, loadShaders y SDL para tenerse que preocupar únicamente de la programación.

Ademas tiene metidos los ejemplos que están colgados en el blog hasta el ejemplo de geometry instancing. He modificado un par de cosas (#includes, localización relativa de los shaders, algún cast...) pero nada significativo. Iré actualizando el archivo según vaya actualizando el blog para añadir los nuevos ejemplos.

Espero que os sea de utilidad. Un saludo

https://db.tt/MTBftZDo
Descargar Proyecto

viernes, 13 de septiembre de 2013

III - B - Dibujando muchos cubos - Ampliación

Hace ya bastante de la última entrada, pero los exámenes son lo que tienen...

Como dije en el último post, vamos a:
  • Aprender a movernos con las teclas "W","A","S" y "D".
  • Aumentar el número de cubos de manera significativa.
  • Reducir el peso en memoria, en concreto, la cantidad de números que representan posición de los cubos, de 4 a 3.
  • Añadir un filtro de niebla.
Empezamos con los cambios en el .h:

#ifndef DEMO_OGL_IIIB_H_
#define DEMO_OGL_IIIB_H_
#include <iostream>
#include <GL/gl3w.h>
#include <GL/gl3.h>
#include "SDL2/SDL.h"
#include "../../LibsNUtils/vmath.h"
#include "LoadShaders.h"
#include "../Utils/Info_Manager.h"


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

    static const uint32_t   WIN_HEIGHT = 600; //px
    static const uint32_t   WIN_WIDTH  = 1000; //px
    static const char*      WIN_TITLE; //px
    static const int32_t    INST_LENGTH = 10;
    static const int32_t    INSTANCES = INST_LENGTH*INST_LENGTH*INST_LENGTH;
    Info_Manager info;
    /***************************************************/
    /***************************************************/
    /***************************************************/


    float aspect;
    static const GLfloat cube_positions[];
    static const GLfloat cube_colors[];
    GLfloat cube_rel_pos[INSTANCES*3];
    static const GLushort cube_indices[];

    static const vmath::vec3 X;
    static const vmath::vec3 Y;
    static const vmath::vec3 Z;
    static const GLfloat VELOCITY = 0.01f;
    GLuint ebo[1];
    GLuint vao[1];
    GLuint vbo[2];
    //GLuint rel_pos_buff[1];

    GLuint render_prog;
    GLuint render_model_matrix_loc;
    GLuint render_projection_matrix_loc;

    GLfloat posX;
    GLfloat posY;
    GLfloat posZ;
    GLfloat velocityXn;
    GLfloat velocityZn;
    GLfloat velocityYn;
    GLfloat velocityXp;
    GLfloat velocityZp;
    GLfloat velocityYp;
    GLfloat lastFrameTime;

public:

    Demo_OGL_3B();

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

    void CheckErr();
};


#endif
Lo primero que cambiamos INST_LENGTH e INSTANCES. Lo importante es que ahora INSTANCES es INST_LENGTH3 porque vamos a repartirlas en forma de cubo en lugar de un cuadrado. INST_LENGTH (que es la cantidad de cubos que definen las dimensiones de nuestro Gran Cubo hecho de cubos)vamos a cambiarlo por 5, 10, 20... para hacer las pruebas.
El siguiente cambio ha sido quitarle a cube_rel_pos los modificadores "static const" para poder definirlo dinámicamente y la cantidad de huecos de array que reservamos ahora es INSTANCES*3 en lugar de *4 por que vamos a obviar la componente w (las componentes normalmente son: x, y, z, w) porque suele ser 1.
Siguiente constante: VELOCITY, la vamos a utilizar para definir la velocidad a la que nos vamos a mover en el espacio. ¿Cómo la he calculado? Método científico... prueba y error! 
(si alguien esta pensando que velocity le suena un poco mal... a mi también me sonaba mal, pero lo he visto en muchos sitios y parece ser que es lo correcto)
Lo penúltimo son las variables de la forma: velocityAa, son las que van a contener la velocidad actual en las distintas direcciones (X, Y, Z en (n)egativo y (p)ositivo).
Y para terminar una variable que nos guarda la última vez que hemos actualizado las variables.

Prosigamos con el .cpp:

#include <iostream>
#include <string>
#include <sstream>
#include "Demo_OGL_3b.h"
#include "../../LibsNUtils/vmath.h"
#include <GL/gl3w.h>
#include <GL/gl.h>
#include "LoadShaders.h"

const char* Demo_OGL_3B::WIN_TITLE = "Titulo de la Ventana";
const GLfloat Demo_OGL_3B::cube_positions[] = {
        -0.3f, -0.3f, -0.3f, 1.0f,
        -0.3f, -0.3f,  0.3f, 1.0f,
        -0.3f,  0.3f, -0.3f, 1.0f,
        -0.3f,  0.3f,  0.3f, 1.0f,
         0.3f, -0.3f, -0.3f, 1.0f,
         0.3f, -0.3f,  0.3f, 1.0f,
         0.3f,  0.3f, -0.3f, 1.0f,
         0.3f,  0.3f,  0.3f, 1.0f
};
const GLfloat Demo_OGL_3B::cube_colors[] = {
         1.0f,  1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  0.0f,  1.0f,
         1.0f,  0.0f,  1.0f,  1.0f,
         1.0f,  0.0f,  0.0f,  1.0f,
         0.0f,  1.0f,  1.0f,  1.0f,
         0.0f,  1.0f,  0.0f,  1.0f,
         0.0f,  0.0f,  1.0f,  1.0f,
         0.5f,  0.5f,  0.5f,  1.0f
};
const GLushort Demo_OGL_3B::cube_indices[] = {
        0, 1, 2, 3, 6, 7, 4, 5, // First strip
        0xFFFF, // <<-- This is the restart index
        2, 6, 0, 4, 1, 5, 3, 7 // Second strip*/
};

const vmath::vec3 Demo_OGL_3B::X(1.0f, 0.0f, 0.0f);
const vmath::vec3 Demo_OGL_3B::Y(0.0f, 1.0f, 0.0f);
const vmath::vec3 Demo_OGL_3B::Z(0.0f, 0.0f, 1.0f);

Demo_OGL_3B::Demo_OGL_3B() : running(false), window(NULL), ctxt(NULL), info(),
            aspect(0), ebo(), vao(), vbo(), render_prog(0),
            render_model_matrix_loc(0), render_projection_matrix_loc(0),
            posX(0.0f), posY(0.0f), posZ(-2.0f), velocityXn(0.0f),
            velocityZn(0.0f), velocityYn(0.0f), velocityXp(0.0f),
            velocityZp(0.0f), velocityYp(0.0f),
            lastFrameTime(GetTickCount()){

    for(int32_t i = 0; i < INST_LENGTH; i++){
        for(int32_t j = 0; j < INST_LENGTH; j++){
            for(int32_t k = 0; k < INST_LENGTH; k++){

                cube_rel_pos[i*3*INST_LENGTH*INST_LENGTH+j*3*INST_LENGTH+k*3] = i-INST_LENGTH/2;
                cube_rel_pos[i*3*INST_LENGTH*INST_LENGTH+j*3*INST_LENGTH+k*3+1] = j-INST_LENGTH/2;
                cube_rel_pos[i*3*INST_LENGTH*INST_LENGTH+j*3*INST_LENGTH+k*3+2] = -k;
                //cube_rel_pos[i*3*INST_LENGTH*INST_LENGTH+j*3*INST_LENGTH+k*4+3] = 1;

            }
        }
    }

}

void Demo_OGL_3B::OnEvent(SDL_Event* event) {
    switch (event->type) {
        case SDL_KEYDOWN:
            switch(event->key.keysym.sym){
                case SDLK_w:
                    velocityZp = VELOCITY;
                    break;
                case SDLK_a:
                    velocityXp = VELOCITY;
                    break;
                case SDLK_s:
                    velocityZn = VELOCITY;
                    break;
                case SDLK_d:
                    velocityXn = VELOCITY;
                    break;
                case SDLK_SPACE:
                    velocityYp = VELOCITY;
                    break;
                case SDLK_LCTRL:
                    velocityYn = VELOCITY;
                    break;
                default:
                    break;
            }
            break;
        case SDL_KEYUP:
            switch(event->key.keysym.sym){
                case SDLK_w:
                    velocityZp = 0.0f;
                    break;
                case SDLK_a:
                    velocityXp = 0.0f;
                    break;
                case SDLK_s:
                    velocityZn = 0.0f;
                    break;
                case SDLK_d:
                    velocityXn = 0.0f;
                    break;
                case SDLK_SPACE:
                    velocityYp = 0.0f;
                    break;
                case SDLK_LCTRL:
                    velocityYn = 0.0f;
                    break;
                case SDLK_v:
                    std::cout << info.client_info() << std::endl << std::endl;
                    break;
                case SDLK_ESCAPE:
                    running = false;
                    break;
                default:
                    break;
            }
            break;
        case SDL_QUIT:
            running = false;
            break;
        default:
            break;
    }
}
void Demo_OGL_3B::OnLoop() {
    GLfloat now = GetTickCount();
    GLfloat elapsedTime = now - lastFrameTime;
    posX += (velocityXp - velocityXn) * elapsedTime;
    posZ += (velocityZp - velocityZn) * elapsedTime;
    posY += (velocityYn - velocityYp) * elapsedTime;
    lastFrameTime = now;
}

void Demo_OGL_3B::OnCleanup() {
    glUseProgram(0);
    glDeleteProgram(render_prog);
    glDeleteVertexArrays(1, vao);
    glDeleteBuffers(2, vbo);
    SDL_GL_DeleteContext(ctxt);
    SDL_DestroyWindow(window);
    SDL_Quit();
}

bool Demo_OGL_3B::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_3B::SetupOpenGL(){

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

    SDL_GL_SetSwapInterval(1);

    if (gl3wInit()) {
       std::cout << "Error al Inicializar GL3W" << std::endl;
    }
}
int Demo_OGL_3B::OnExecute() {
    if (!Init())
        return -1;

    SDL_Event event;

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

        Loop();
        Render();

        if(info.frame()){
            info.fps_ogl_ver_on_window(window, WIN_TITLE);
        }
    }
    Cleanup();
    return 0;
}

void Demo_OGL_3B::InitData(){
    // Seleccionamos los shaders que queremos cargar
    ShaderInfo shaders[] = {
         { GL_VERTEX_SHADER, "Resources/Demo_OGL_III/geometry_instancingB.vs.glsl" },
         { GL_FRAGMENT_SHADER, "Resources/Demo_OGL_III/simple.fs.glsl" },
         { GL_NONE, NULL }
    };
    // Cargamos los shaders
    render_prog = LoadShaders( shaders );
    // Decimos a opengl que los utilice
    glUseProgram( render_prog );

    // Seleccionamos la posicion dentro del shader de la matriz de modelado y la de proyeccion
    // Mas tarde vamos a usar esas posiciones para cambiar datos del cubo
    render_model_matrix_loc = glGetUniformLocation(render_prog, "model_matrix");
    render_projection_matrix_loc = glGetUniformLocation(render_prog, "projection_matrix");

    // Pedimos un buffer para el element buffer object
    glGenBuffers(1, ebo);
    // Le hacemos hueco diciendole el tipo de buffer
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
    // Lo rellenamos con los indices de los cubos
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cube_indices), cube_indices, GL_STATIC_DRAW);

    // Pedimos un array de vertices
    glGenVertexArrays(1, vao);
    // Le hacemos hueco
    glBindVertexArray(vao[0]);

    // Pedimos un buffer para el vertex buffer object
    glGenBuffers(2, vbo);
    // Le hacemos hueco
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    // Le decimos que el hueco tiene que ser de tamaño "tamaño de cube positions"+"tamaño de cube colors"
    glBufferData(GL_ARRAY_BUFFER, sizeof(cube_positions) + sizeof(cube_colors), NULL, GL_STATIC_DRAW);
    // Y como lo tenemos en dos arrays diferentes lo guardamos poco a poco
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(cube_positions), cube_positions );
    glBufferSubData(GL_ARRAY_BUFFER, sizeof(cube_positions), sizeof(cube_colors), cube_colors);

    // localizacion del atributo, numero de valores, tipo de valores,
    // normalizarlos, espacio entre valores, puntero al primer valor.
    // Por lo tanto decimos que nos pille los 4 primeros valores y
    // nos los meta en "posicion",
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, NULL);
    // Y apartir de cube positions que pille los colores.
    glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)sizeof(cube_positions));
    // Activamos los dos arrays de atributos
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);


    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cube_rel_pos), cube_rel_pos, GL_STATIC_DRAW);

    glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(2);
    glVertexAttribDivisor(2, 1);


    //Seleccionamos el color de fondo
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    //Los triangulos que no se ven, ni los pinta (los triangulos que estan de espaldas)
    glEnable(GL_CULL_FACE);
    //Utiliza el z buffer
    glEnable(GL_DEPTH_TEST);
    //Funcion para el z-buffer
    glDepthFunc(GL_LESS);


    //lo que ocupa la parte en la que se dibuja.
    glViewport(0, 0, WIN_WIDTH, WIN_HEIGHT);
    aspect = float(WIN_HEIGHT) / float(WIN_WIDTH);

    CheckErr();
}

void Demo_OGL_3B::OnRender() {
    // Un numero incremental en funcion del tiempo real
    float t = float(GetTickCount() & 0x1FFF) / float(0x1FFF);

    // Limpiamos el buffer de profundidad (se pone al valor por defecto)
    // y el de color (se pone al valor de glClearColor
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Decimos que Shader usar
    glUseProgram(render_prog);

    // Calculamos la matriz modelo
    vmath::mat4 model_matrix(vmath::translate(posX, posY, posZ) *
                                vmath::rotate(t * 360.0f, Y) *
                                vmath::rotate(t * 720.0f, Z));

    // Calculamos la matriz de proyección mediante un frustum
    vmath::mat4 projection_matrix(vmath::frustum(-1.0f, 1.0f, -aspect, aspect, 1.0f, 500.0f));


    // Posicion en el shader, cantidad de matrices, es traspuesta? , matriz a setear.
    // Guardamos las dos matrices en el shader para que dibuje.
    // Al ser uniform el valor, se aprovecha entre los shaders
    glUniformMatrix4fv(render_model_matrix_loc, 1, GL_FALSE, model_matrix);
    glUniformMatrix4fv(render_projection_matrix_loc, 1, GL_FALSE, projection_matrix);
    // Activamos el vertex array Object
    glBindVertexArray( vao[0] );
    // Activamos el buffer de indices
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);


#define USE_PRIMITIVE_RESTART 1
#if USE_PRIMITIVE_RESTART
    // When primitive restart is on, we can call one draw command
    // Le decimos que active el reinicio de primitivas,
    glEnable(GL_PRIMITIVE_RESTART);
    // Este es el valor del indice que se toma para el reinicio
    glPrimitiveRestartIndex(0xFFFF);
    // Dibujamos como triangle strip (aprovechamos los dos vertices
    // anteriores para dibujar el siguiente triangulo)
    // un total de 17 indices y de tipo unsigned short sin offset (NULL)
    glDrawElementsInstanced(GL_TRIANGLE_STRIP, 17, GL_UNSIGNED_SHORT, NULL, INSTANCES);


#else
    //Dibujamos un strip y otro.
    // Without primitive restart, we need to call two draw commands
    glDrawElementsInstanced(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_SHORT, NULL, INSTANCES);
    glDrawElementsInstanced(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_SHORT,
            (const GLvoid *)(9 * sizeof(GLushort)), INSTANCES);
#endif
    // Buffer swap
    SDL_GL_SwapWindow(window);
}




void Demo_OGL_3B::CheckErr() {
    GLenum err = glGetError();
    if ( err != GL_NO_ERROR )
        std::cerr << "Error: " <<err;
}

Poco a poco:


  • cube_rel_pos ahora NO lo inicializamos a manita por lo que desaparece del principio.
  • Ahora el constructor tiene algo de cuerpo. Aunque por lo general se recomienda hacer este tipo de inicializaciones en la función que arranca el sistema, es decir, en nuestro caso deberíamos de ponerlo en InitData.
    De las inicializaciones "rápidas" (las que se realizan antes del bloque de función) cabe destacar lastFrameTime que utilizamos el momento actual, igualmente, debería estar en InitData porque podría darnos problemas, pero no en nuestra prueba (para el siguiente lo cambio, prometido).
    En el cuerpo, hacemos un triple bucle para rellenar el array definiendo las tres dimensiones:
    X,Y,Z. Si no se entiende, es matemática pura: como nos hemos definido un array (una lista de una sola dimensión), tenemos que ubicar una fila detrás de otra en el array. hay una suma de 4 números. Empezando por el último:
    1. Un literal: +0 (X), +1(Y), +2(Z). posicionamos las tres variables.
    2. k*3, para que k implique cada cubo. (cada 3 huecos un cubo nuevo)
    3. j*3*INST_LENGTH, INST LENGTH es la longitud (en cubos) del nuestro Gran Cubo. *3 por las tres variables, por lo tanto, esto indica la fila.
    4. i*3*INST_LENGTH*INST_LENGTH. Esto representa cada cuadrado dibujado.
    5. Sumando las tres tenemos la posición de cada cubo.
    6. i-INST_LENGTH/2 y j-INST_LENGTH/2 centran en el eje de coordenadas el gran cubo. k esta puesta negativa para que se construya a partir de la camara.
  • OnEvent he añadido los manejadores para keyDown y keyUp de las teclas W, A, S, D, Espacio y Ctrl. Como quería que fuera algo sencillo pero que se entendiera la idea, cuando pulsamos cualquier tecla de las mencionadas, la velocidad a la que nos movemos en esa dirección la ponemos constante.
    Por lo tanto tenemos que al pulsar (y dejar pulsado)
    W, aumenta la velocidad en Z una cantidad VELOCITY. Cuando pulsamos A, aumenta X, cuando pulsamos Espacio, aumenta Y. Pero no le sumamos, si no que le ponemos una cantidad fija de velocidad para evitar bugs.
    En
    keyUp cambiamos las velocidades a 0. Si le restásemos VELOCITY, podría dar conflicto en algunas situaciones como por ejemplo: perder el focus de la ventana, pulsar W y recuperar el focus con la W pulsada. Al soltar la velocidad seria -1 hasta que pulsásemos W de nuevo.
  • OnLoop: la primera vez que escribimos en esta función. Es también la primera vez que el modelo sufre cambios de un dibujado al siguiente.
    Obtenemos el tiempo actual y obtenemos la diferencia con la última vez que dibujamos (creo que se podría llamar un tiempo delta, pero este tipo de jerga siempre cuesta más digerirla :-) ) y lo he llamado
    elapsedTime. Esto lo calculamos antes porque todas tienen que tener el mismo tiempo de diferencia. Podrían tener 1 milésima de segundo más y que el movimiento no se aplicase de manera correcta.
    Después a la posición de la cámara en
    X, Y y Z le añadimos los incrementos de posición (velocidad*tiempo). De esta manera, gracias al calculo del tiempo, si el ordenador no llega o se pasa de los 60fps, siempre se moverá a la misma velocidad.
Como hicimos bien la última vez nuestro código, creo que no hay más cambios en el .cpp

En el Vertex Shader solo ha cambiado una linea:


uniform mat4 model_matrix;
uniform mat4 projection_matrix;

layout (location = 0) in vec4 position;
layout (location = 1) in vec4 color;

// per instance attribute
layout (location = 2) in vec3 translation;

out vec4 vs_fs_color;

void main(void)
{
    mat4 trans = mat4(   1, 0, 0, 0,
                         0, 1, 0, 0,
                         0, 0, 1, 0,
                         translation, 1);
    vs_fs_color = color;
    gl_Position = projection_matrix  * ( (trans * model_matrix)  * position) ;
}
En la matriz trans se ha añadido al final un 1, ya que hemos reducido el numero de parámetros a 3, hay que añadírselo en el shader. Esto en lo que nos beneficia es en alojar algo menos de memoria, dando por supuesto cierto valor, en este caso, 1.

En cuanto al fragment shader, podéis usar el mismo de la vez anterior. Yo intentando hacer otra cosa, averigüe como crear un filtro que pueda parecer niebla, a mi por lo menos me lo parecía, seguramente si tuviésemos superficies de cielo y suelo daría mas esa impresión.

Aquí os dejo mi experimento:


#version 430
#define DARKNESS_FACTOR 0.8f
in vec4 vs_fs_color;

layout (location = 0) out vec4 color;

void main(void)
{
    float c = 1-gl_FragCoord.z;
    float r = vs_fs_color.r;
    float g = vs_fs_color.g;
    float b = vs_fs_color.b;
    c = 1-(c*c*c*c);
    for(int i = 0; i < 10; i++){
        c = c*c;
    }
    r = r+c;
    g = g+c;
    b = b+c;
    if( r > 1.0f ){
        r = 1.0f;
    }
    if( g > 1.0f ){
        g = 1.0f;
    }
    if( b > 1.0f ){
        b = 1.0f;
    }
    color = vec4(   r*DARKNESS_FACTOR, 
                    g*DARKNESS_FACTOR, 
                    b*DARKNESS_FACTOR, 
                    1);
}
En general es bastante legible, pero aun así voy a explicarlo.
La instrucción mas importante es gl_FragCoord.z que obtiene la profundidad en z de ese fragmento. Esto lo vamos a utilizar para definir hasta que punto hacemos blanquecinos los cubos. Cuanto mas cerca, más de su propio color, cuanto mas lejos, más blanco.
Como veis tenemos una variable c que es la que vamos a utilizar para definir la intensidad de la niebla. Le aplicamos un par de modificaciones de tal manera que la niebla no aumente linealmente. La funcion es: 

f(Z) = (1-((1-Z)4))1024
definida en el intervalo [0, 1] y Z número real 

y su dibujo resultante es (hecha con fooplot): 

Después de calcular la cantidad de niebla que hay que aplicar, a cada componente de color del fragmento, le añadimos esa cantidad truncando en 1 los resultados.
Por último DARKNESS_FACTOR es un modificador que hace que el escenario se oscurezca, la niebla totalmente blanca es un tanto extraña. 0.8 es un punto equilibrado en el que parece blanco pero no brilla demasiado, podéis probar con 1.0, 0.3...

Llega el momento de ejecutar nuestro programa. Si tenéis el código como el que está aquí (INST_LENGTH = 10)con el fragment shader de la niebla debería ejecutarse algo como esto:


La primera es según se ejecuta, la segunda es moviéndose hacia la derecha y hacia atrás para tomar perspectiva.

Si queréis poner a prueba vuestra máquina y OpenGL, podéis aumentar el número de INST_LENGTH. Recomiendo hacerlo de manera gradual. En mi caso puedo aumentarlo hasta 50 sin problemas, 51 ya no me deja, aunque creo que son restricciones de OpenGL (Si alguien puede confirmármelo dejad un comentario o escribidme a mi correo). Debería de quedar algo así:


La segunda es una ampliación de la primera. He tenido que alejarme mucho y desactivar la niebla para que se notara bien la cantidad de cubos que se pintan, por eso la he ampliado.
Bueno, esto se ha acabado, espero que os haya gustado. Cuando avance en mi aprendizaje colgare más post. Mientras tanto os invito a rellenar la "encuesta" de una sola pregunta que tenéis en la columna de la derecha para ayudarme a mejorar.




martes, 27 de agosto de 2013

III - A - Dibujando muchos cubos - Geometry Instancing

Hoy vamos a aprender una técnica que nos facilita OpenGL y de la que muchos habréis oido hablar. Personalmente he encontrado pocos ejemplos por internet, se llama: Geometry Instancing.
Esta técnica nos ayuda a dibujar muchas veces la misma pieza geométrica variando algunas cosas de ella como color, tamaño, posición... Se puede variar prácticamente todo pero, cuantas más cosas varíes, menos eficiente se vuelve.
Lo de dibujar muchas veces la misma pieza no es cuestión de ahorrarnos lineas de código (que ahora veréis que tampoco es un gran avance). El verdadero avance está en tiempo de ejecución, si nosotros ejecutamos un código algo así (pseudo-código):

for(int i = 0; i<1000; i++)
    DrawGeometry(geometry, position);

Esta haciendo 1000 peticiones a OpenGL de dibujar una geometría. En cambio, con esta técnica, hace únicamente una petición, y la gráfica (si lo soporta) dibuja las 1000 geometrías. El tiempo de hacer una petición es relativamente lento. Si pretendes ejecutar el código 60 veces por segundo y dibujar muchas veces la misma geometría, debes utilizar Geometry Instancing. Además el procesador de la tarjeta está especializado en cálculo en coma flotante y vectorial, por lo que las operaciones las hará más rápidamente que nuestro procesador (por muy i7-muchicore que sea y si nuestra gráfica es decente claro).
Vamos a poner unos ejemplos para ver que esto es útil y hay que utilizarlo.

  • El ejemplo más claro es: Minecraft. Intenté, con un motor gráfico que no soportaba Geometry Instancing, montar un Minecraft sencillo. Hice un "supercubo" a base de cubos de superficie 10x10x10 (1000) cubos y me iba a 15 fps. Con el mismo ordenador utilizando esta técnica y 50x50x50 (125000) cubos me iba a 70-80 fps. Se puede apreciar la sustancial mejora. Ademas se pueden aplicar otras técnicas como "occlusion" que iremos viendo.
  • Otro momento en el que se usa mucho esta técnica, es para dibujar elementos naturales: árboles, hierba, piedras... La hierba por ejemplo se dibuja igual en todos los sitios y se le aplica una modificación en función de una variable que pueda ser el aire, la gravedad...
  • Soldados en una batalla: Si queremos montar una batalla con cientos de soldados, lo normal es que todos sean instancias del mismo soldado (o de 3 o 4) cambiando texturas o posturas.
Hay muchos más ejemplos pero estos son los más representativos que se me han ocurrido.

Vamos a dibujar cubos en cuadricula 5x5. Como siempre: .h y .cpp

Demo_OGL_3a.h:

/*
 * Demo_3.h
 *
 *  Created on: 01/07/2013
 *      Author: Korgan
 */

#ifndef DEMO_OGL_IIIA_H_
#define DEMO_OGL_IIIA_H_
#include <iostream>
#include <GL/gl3w.h>
#include <GL/gl3.h>
#include "SDL2/SDL.h"
#include "../../LibsNUtils/vmath.h"
#include "LoadShaders.h"
#include "../Utils/Info_Manager.h"


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

    static const uint32_t   WIN_HEIGHT = 600; //px
    static const uint32_t   WIN_WIDTH  = 1000; //px
    static const char*      WIN_TITLE; //px
    static const int32_t    INST_LENGTH = 5;
    static const int32_t    INSTANCES = INST_LENGTH*INST_LENGTH;
    Info_Manager info;
    /***************************************************/
    /***************************************************/
    /***************************************************/


    float aspect;
    static const GLfloat cube_positions[];
    static const GLfloat cube_colors[];
    static const GLfloat cube_rel_pos[INSTANCES*4];
    static const GLushort cube_indices[];

    static const vmath::vec3 X;
    static const vmath::vec3 Y;
    static const vmath::vec3 Z;
    GLuint ebo[1];
    GLuint vao[1];
    GLuint vbo[2];

    GLuint render_prog;
    GLuint render_model_matrix_loc;
    GLuint render_projection_matrix_loc;

    GLfloat posX;
    GLfloat posY;
    GLfloat posZ;

public:

    Demo_OGL_3A();

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

    void CheckErr();
};


#endif /* DEMO_OGL_I_H_ */

Dos nuevas constantes para especificar la cuadricula, como son 5 filas y 5 columnas, INSTANCES = 25 (5x5)
Siguiente constante nueva: cube_rel_pos. Un array que contiene las posiciones en las que vamos a ubicar los cubos.
Modificacion: vbo[2] Un nuevo vertex buffer para guardar cube_rel_pos en la memoria de ogl.
Las tres siguientes variables nuevas: posX, posY, posZ. Guardan la posición del observador. Esto sobretodo esta pensado para la parte B.
Por último CheckErr(), es una función sobretodo para debug. Si ocurre un error, lo suelta por la salida estándar de errores.
No me he querido entretener mucho en el .h porque no tiene ninguna complicación.


/*
 * Demo_3.cpp
 *
 *  Created on: 01/07/2013
 *      Author: Korgan
 *
 *  mingw32
 *  glew32
 *  opengl32
 *  SDL2main
 *  SDL2
 *
 */

#include <iostream>
#include <string>
#include <sstream>
#include "Demo_OGL_3a.h"
#include "../../LibsNUtils/vmath.h"
#include <GL/gl3w.h>
#include <GL/gl.h>
#include "LoadShaders.h"

const char* Demo_OGL_3A::WIN_TITLE = "Titulo de la Ventana";
const GLfloat Demo_OGL_3A::cube_positions[] = {
        -0.3f, -0.3f, -0.3f, 1.0f,
        -0.3f, -0.3f,  0.3f, 1.0f,
        -0.3f,  0.3f, -0.3f, 1.0f,
        -0.3f,  0.3f,  0.3f, 1.0f,
         0.3f, -0.3f, -0.3f, 1.0f,
         0.3f, -0.3f,  0.3f, 1.0f,
         0.3f,  0.3f, -0.3f, 1.0f,
         0.3f,  0.3f,  0.3f, 1.0f
};
const GLfloat Demo_OGL_3A::cube_colors[] = {
         1.0f,  1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  0.0f,  1.0f,
         1.0f,  0.0f,  1.0f,  1.0f,
         1.0f,  0.0f,  0.0f,  1.0f,
         0.0f,  1.0f,  1.0f,  1.0f,
         0.0f,  1.0f,  0.0f,  1.0f,
         0.0f,  0.0f,  1.0f,  1.0f,
         0.5f,  0.5f,  0.5f,  1.0f
};
const GLfloat Demo_OGL_3A::cube_rel_pos[] = {
         -2.0f,   -2.0f,  0.0f,  1.0f,
         -1.0f,   -2.0f,  0.0f,  1.0f,
          0.0f,   -2.0f,  0.0f,  1.0f,
          1.0f,   -2.0f,  0.0f,  1.0f,
          2.0f,   -2.0f,  0.0f,  1.0f,
         -2.0f,   -1.0f,  0.0f,  1.0f,
         -1.0f,   -1.0f,  0.0f,  1.0f,
          0.0f,   -1.0f,  0.0f,  1.0f,
          1.0f,   -1.0f,  0.0f,  1.0f,
          2.0f,   -1.0f,  0.0f,  1.0f,
         -2.0f,    0.0f,  0.0f,  1.0f,
         -1.0f,    0.0f,  0.0f,  1.0f,
          0.0f,    0.0f,  0.0f,  1.0f,
          1.0f,    0.0f,  0.0f,  1.0f,
          2.0f,    0.0f,  0.0f,  1.0f,
         -2.0f,    1.0f,  0.0f,  1.0f,
         -1.0f,    1.0f,  0.0f,  1.0f,
          0.0f,    1.0f,  0.0f,  1.0f,
          1.0f,    1.0f,  0.0f,  1.0f,
          2.0f,    1.0f,  0.0f,  1.0f,
         -2.0f,    2.0f,  0.0f,  1.0f,
         -1.0f,    2.0f,  0.0f,  1.0f,
          0.0f,    2.0f,  0.0f,  1.0f,
          1.0f,    2.0f,  0.0f,  1.0f,
          2.0f,    2.0f,  0.0f,  1.0f,
};
const GLushort Demo_OGL_3A::cube_indices[] = {
        0, 1, 2, 3, 6, 7, 4, 5, // First strip
        0xFFFF, // <<-- This is the restart index
        2, 6, 0, 4, 1, 5, 3, 7 // Second strip*/
};

const vmath::vec3 Demo_OGL_3A::X(1.0f, 0.0f, 0.0f);
const vmath::vec3 Demo_OGL_3A::Y(0.0f, 1.0f, 0.0f);
const vmath::vec3 Demo_OGL_3A::Z(0.0f, 0.0f, 1.0f);

Demo_OGL_3A::Demo_OGL_3A() : running(false), window(NULL), ctxt(NULL), info(),
            aspect(0), ebo(), vao(), vbo(), render_prog(0),
            render_model_matrix_loc(0), render_projection_matrix_loc(0),
            posX(0.0f), posY(0.0f), posZ(-5.0f){}

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

void Demo_OGL_3A::OnCleanup() {
    glUseProgram(0);
    glDeleteProgram(render_prog);
    glDeleteVertexArrays(1, vao);
    glDeleteBuffers(2, vbo);
    SDL_GL_DeleteContext(ctxt);
    SDL_DestroyWindow(window);
    SDL_Quit();
}

bool Demo_OGL_3A::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_3A::SetupOpenGL(){

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

    SDL_GL_SetSwapInterval(1);

    if (gl3wInit()) {
       std::cout << "Error al Inicializar GL3W" << std::endl;
    }
}
int Demo_OGL_3A::OnExecute() {
    if (!Init())
        return -1;

    SDL_Event event;

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

        Loop();
        Render();

        if(info.frame()){
            info.fps_ogl_ver_on_window(window, WIN_TITLE);
        }
    }
    Cleanup();
    return 0;
}

void Demo_OGL_3A::InitData(){
    ShaderInfo shaders[] = {
         { GL_VERTEX_SHADER, "Resources/Demo_OGL_III/geometry_instancingA.vs.glsl" },
         { GL_FRAGMENT_SHADER, "Resources/Demo_OGL_III/simple.fs.glsl" },
         { GL_NONE, NULL }
    };
    render_prog = LoadShaders( shaders );
    glUseProgram( render_prog );

    render_model_matrix_loc = glGetUniformLocation(render_prog, "model_matrix");
    render_projection_matrix_loc = glGetUniformLocation(render_prog, "projection_matrix");

    glGenBuffers(1, ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cube_indices), cube_indices, GL_STATIC_DRAW);

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

    glGenBuffers(2, vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cube_positions) + sizeof(cube_colors), NULL, GL_STATIC_DRAW);
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(cube_positions), cube_positions );
    glBufferSubData(GL_ARRAY_BUFFER, sizeof(cube_positions), sizeof(cube_colors), cube_colors);



    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, NULL);
    glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)sizeof(cube_positions));
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    /**************************************
     * GEOMETRY INSTANCING
     **************************************/
    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cube_rel_pos), cube_rel_pos, GL_STATIC_DRAW);

    glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(2);
    glVertexAttribDivisor(2, 1);
/**************************************
     * END GEOMETRY INSTANCING
     **************************************/
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);


    glViewport(0, 0, WIN_WIDTH, WIN_HEIGHT);
    aspect = float(WIN_HEIGHT) / float(WIN_WIDTH);

    CheckErr();
}

void Demo_OGL_3A::OnRender() {
    float t = float(GetTickCount() & 0x1FFF) / float(0x1FFF);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glUseProgram(render_prog);

    // Calculamos la matriz modelo
    vmath::mat4 model_matrix(vmath::translate(posX, posY, posZ) *
                                vmath::rotate(t * 360.0f, Y) *
                                vmath::rotate(t * 720.0f, Z));

    vmath::mat4 projection_matrix(vmath::frustum(-1.0f, 1.0f, -aspect, aspect, 1.0f, 500.0f));


    glUniformMatrix4fv(render_model_matrix_loc, 1, GL_FALSE, model_matrix);
    glUniformMatrix4fv(render_projection_matrix_loc, 1, GL_FALSE, projection_matrix);
    glBindVertexArray( vao[0] );
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);


#define USE_PRIMITIVE_RESTART 1
#if USE_PRIMITIVE_RESTART

    glEnable(GL_PRIMITIVE_RESTART);

    glPrimitiveRestartIndex(0xFFFF);

    // Dibujamos como triangle strip (aprovechamos los dos vertices
    // anteriores para dibujar el siguiente triangulo)
    // un total de 17 indices y de tipo unsigned short sin offset (NULL)
    // dibujamos tantos cubos como ponga en INSTANCES
    glDrawElementsInstanced(GL_TRIANGLE_STRIP, 17, GL_UNSIGNED_SHORT, NULL, INSTANCES);


#else
    //Dibujamos un strip y otro.
    // Without primitive restart, we need to call two draw commands
    glDrawElementsInstanced(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_SHORT, NULL, INSTANCES);
    glDrawElementsInstanced(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_SHORT,
            (const GLvoid *)(9 * sizeof(GLushort)), INSTANCES);
#endif
    // Buffer swap
    SDL_GL_SwapWindow(window);
}




void Demo_OGL_3A::CheckErr() {
    GLenum err = glGetError();
    if ( err != GL_NO_ERROR )
        std::cerr << "Error: " <<err;
}

Vamos por partes como siempre, doy por hecho todo lo del anterior tutorial:
  • cube_positions, cube_colors, cube_indices, X, Y y Z ya están explicados del anterior tutorialcube_positions x, y & z se han ajustado a 0.3 en lugar de 1.
  • cube_rel_pos guarda las posiciones de cada cubo con respecto al cubo inicial. El cubo inicial esta dibujado en el centro del eje de coordenadas. Echándole un poco de imaginación con las coordenadas podemos imaginarnos como van a estar situados: 5 filas y 5 columnas empezando por la posición -2 hasta la 2 en las coordenadas X e Y. La Z está a 0 porque vamos a dibujarlas sólo en el plano XY.
  • Constructor: lo único que considero destacable es la inicialización de Z a -5. Movemos la posición del observador/cámara, alejándonos de los cubos para poder verlos. (Si a alguien se le olvida esto que no diga que no ve nada xD)
  • Hasta InitData las funciones no han sufrido cambios salvo OnCleanup que borramos (lógicamente) 2 vbo en lugar de 1.
  • InitData: Configuramos los shaders, configuramos el ebo, el vao, y a la hora de configurar el vbo pedimos 2 buffers en lugar de 1: glGenBuffers(2, vbo); 
    Configuramos el primero de los buffers igual que lo hicimos en el anterior con el color y las posiciones de los vertices y a partir del comentario empieza la configuración de la parte de Instancing.
    Como veis las primeras 4 lineas son IGUALES que siempre, utilizando el
    vbo[1] (que es nuestro nuevo buffer) y habilitando con el location adecuado, como siempre. Lo nuevo es añadir la 5º lineaglVertexAttribDivisor(2, 1);
    El primer parámetro es location y el segundo el número de instancias en las que se va a repetir el valor. Por lo general voy a usar 1, pero podría usarse cualquier número mayor que 0. En el caso de 0 se utiliza un valor por vértice (como se usa habitualmente).
    Ésta función indica cada cuantas instancias tiene que cambiar el valor. Es decir, nosotros hemos dicho que dibuje 25 instancias del cubo, y tenemos 25 posiciones, pero va a dibujar 8 vértices por cubo. Necesitamos que cada 8 vértices, cambie la posición. Si la cambiase en cada vértice (como se hace normalmente), nos saldría una masa amorfa, cosa que no queremos. Eso es lo que hace glVertexAttribDivisor, decirle que cada 8 vértices cambie la posición del cubo.

    Esta foto, aunque hay un poco de lío de líneas, pretende ilustrar cómo todos los cubos usan los 8 valores de los dos buffers azules y usan únicamente un valor del buffer rojo cada uno.
    El resto de líneas de la función las conocemos, por lo tanto, las paso por alto.
  • OnRender(): no ha cambiado demasiado, voy a las lineas que han cambiado:
    • model_matrix ahora la calculamos a partir de posX posY y posZ. Nada especial, igual que si pusieramos (0.0f, 0.0f, -5.0f)
    • glDrawElementsInstanced(GL_TRIANGLE_STRIP, 17, GL_UNSIGNED_SHORT, NULL, INSTANCES);
      Esta función es clave. Es igual que la anterior añadiendo el número de instancias que hay que dibujar.
    • Las llamadas con USE_PRIMITIVE_RESTART "off" son iguales tambien pero con INSTANCES.
Ahora los Shaders. Sólo os voy a hablar del vertex shader, el fragment shader es igual que el anterior.

geometry_instancingA.vs.glsl:

#version 330

uniform mat4 model_matrix;
uniform mat4 projection_matrix;

layout (location = 0) in vec4 position;
layout (location = 1) in vec4 color;

// per instance attribute
layout (location = 2) in vec4 translation;

out vec4 vs_fs_color;

void main(void)
{
    mat4 trans = mat4(   1, 0, 0, 0,
                         0, 1, 0, 0,
                         0, 0, 1, 0,
                         translation);
    vs_fs_color = color;
    gl_Position = projection_matrix  * ( (trans * model_matrix)  * position) ;
}

Tenemos un atributo nuevo, al que he llamado translation, es al que asignamos cube_rel_pos. No tiene nada especial, es un atributo igual que position y color.
En el main tenemos la matriz de traslación en la cual "cargamos" la matriz identidad y añadimos nuestro vector de traslación en la última linea.
La posición se calcula igual que se calculaba en el anterior ejemplo multiplicando la matriz modelo por la matriz de traslación.
Como veis no hay nada de especial en Geometry Instancing dentro de los shaders. Es OpenGL el que se encarga de hacer el "trabajo sucio".
Debería de saliros algo como esto:


Esto es todo, como veis hay más de concepto que de código. Con respecto a lo anterior son "un par de cambios".
Ésta es la primera parte del ejemplo 3. En cuanto tenga tiempo cuelgo la siguiente parte. Aprendemos a movernos con WASD y probaremos cuantos cubos soporta nuestro ordenador con Geometry Instancing.


domingo, 18 de agosto de 2013

II - Dibujando un Cubo 3D

En este nuevo post nos vamos a meter con lo que todos, o casi todos, queremos: La representación en un sistema 3D.
Vamos a dibujar un cubo dando vueltas sobre sí mismo a una velocidad constante con respecto al tiempo y no a la velocidad de procesamiento. Esto es muy importante tenerlo en cuenta, iremos viendo las diferencias que puede suponer.
Lo vamos a dibujar de una manera un poco especial, utilizando la función glDrawElements, que explicaremos más adelante. En otro post explicaré las diferentes maneras que tenemos de dibujar una misma cosa en OpenGL.
Lo primero decir que vamos a usar la librería vmath, como habíamos comentado en los post de introducción. Si no lo has visitado éste es el enlace. Además vamos a utilizar otra librería que he hecho yo mismo, se trata de unas pocas lineas que nos van a permitir mostrar en el titulo de la ventana los FPS (Frames Per Second - Imágenes por segundo) del entorno OpenGL. Esto nos va a permitir hacer mediciones de eficiencia más adelante. (No es una librería para ser reutilizada con otros entornos, sólo asegura funcionar en SDL 2.0. Es una librería de andar por casa para hacer pruebas.)

Info_Manager.h:
Tenemos 4 funciones:
  • frame: sirve para indicar que se ha dibujado un frame, devuelve true si es un fotograma clave (acaba de pasar un segundo desde el ultimo fotograma clave) o false si no lo es.
    El primer if es una precaución para el caso en que SDL_GetTicks llegue a MAX_INT y vuelva a 0. En ese caso se resetean los frames y se vuelve a empezar.
  • fps: es un getter o accesor
  • fps_ogl_ver_on_window: pretende tener un nombre intuitivo. Establece el nombre del titulo de la ventana poniendo primero el título como tal y concatenandolo con los FPS y la versión de OpenGL actual del componente.
  • client_info: Devuelve un String tipo C con diversas informaciones sobre el componente gráfico que se esta utilizando.
#ifndef FPS_CTRLR_H_
#define FPS_CTRLR_H_
#include <iostream>
#include <string>
#include <sstream>
#include <GL/gl3w.h>
#include <GL/gl3.h>
#include "SDL2/SDL.h"
#include "LoadShaders.h"


class Info_Manager {
private:
    Uint32 _last_time_FPS;
    Uint32 _frames;
    Uint32 _fps;
public:
    Info_Manager() : _last_time_FPS(SDL_GetTicks()), _frames(0), _fps(0){}

    bool frame(){
        _frames++;
        //Por si llega al limite de los int
        if (_last_time_FPS - 1000 > SDL_GetTicks()){
            _last_time_FPS = SDL_GetTicks();
            _frames = 0;
        }
        if ((_last_time_FPS + 1000) <= SDL_GetTicks()){
            _last_time_FPS += 1000;
            _fps = _frames;
            _frames = 0;
            return true;
        }
        return false;
    }
    Uint32 fps(){
        return _fps;
    }
    void fps_ogl_ver_on_window(SDL_Window* window, const char* win_title){
        std::stringstream sstring;
        sstring << win_title << " [FPS: " << _fps
                << ", OGL-version: " << glGetString(GL_VERSION)
                << "]";
        std::string s = sstring.str();
        SDL_SetWindowTitle(window, s.c_str());
    }
    const char* client_info(){
        std::stringstream sstring;
        sstring << "--------------------------------------------------------" << std::endl
                << " + OGL-version: " << glGetString(GL_VERSION) << std::endl
                << " + GLSL-version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl
                << " + Vendor: " << glGetString(GL_VENDOR) << std::endl
                << " + Renderer: " << glGetString(GL_RENDERER) << std::endl
                << "--------------------------------------------------------" << std::endl;
        std::string s = sstring.str();
        return s.c_str();
    }

};

#endif /* FPS_CTRLR_H_ */

En el ejemplo anterior. El array de vértices y de colores del triángulo los escribíamos dentro de la función, esta vez vamos a sacarlos para dejar un código mas claro. Veamos las principales diferencias en el ".h":

Lo primero a resaltar el Info_Manager, nuestra clase de utilidades.
Después nos encontramos con una variable aspect que guardará la relación de aspecto ancho-alto para la matriz de projección.
3 arrays para la información del cubo, tres constantes para facilitar la escritura de código, y 3 arrays, 2 de ellos buffer objects y uno de ellos vertex array object.
Las 3 últimas son información sobre el shader: nombre del programa y la localización de dos de sus variables.
Las funciones siguen siendo las mismas.

#ifndef DEMO_OGL_II_H_
#define DEMO_OGL_II_H_
#include <iostream>
#include <GL/gl3w.h>
#include <GL/gl3.h>
#include "SDL2/SDL.h"
#include "../../LibsNUtils/vmath.h"
#include "LoadShaders.h"
#include "../Utils/Info_Manager.h"

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

    static const uint32_t   WIN_HEIGHT = 768; //px
    static const uint32_t   WIN_WIDTH  = 1024; //px
    static const char*      WIN_TITLE; //px
    Info_Manager info;
    /***************************************************/
    /***************************************************/
    /***************************************************/


    float aspect;
    static const GLfloat cube_positions[];
    static const GLfloat cube_colors[];
    static const GLushort cube_indices[];
    static const vmath::vec3 X;
    static const vmath::vec3 Y;
    static const vmath::vec3 Z;
    GLuint ebo[1];
    GLuint vao[1];
    GLuint vbo[1];

    GLuint render_prog;
    GLuint render_model_matrix_loc;
    GLuint render_projection_matrix_loc;

public:

    Demo_OGL_2();

    /*
     * 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_ */

Pues sólo queda explicar el .cpp que como siempre es donde está lo realmente interesante.
#include <iostream>
#include <string>
#include <sstream>
#include "Demo_OGL_2.h"
#include "../../LibsNUtils/vmath.h"
#include <GL/gl3w.h>
#include <GL/gl.h>
#include "LoadShaders.h"

const char* Demo_OGL_2::WIN_TITLE = "Titulo de la Ventana";
const GLfloat Demo_OGL_2::cube_positions[] = {
        -1.0f, -1.0f, -1.0f, 1.0f,
        -1.0f, -1.0f,  1.0f, 1.0f,
        -1.0f,  1.0f, -1.0f, 1.0f,
        -1.0f,  1.0f,  1.0f, 1.0f,
         1.0f, -1.0f, -1.0f, 1.0f,
         1.0f, -1.0f,  1.0f, 1.0f,
         1.0f,  1.0f, -1.0f, 1.0f,
         1.0f,  1.0f,  1.0f, 1.0f
};
const GLfloat Demo_OGL_2::cube_colors[] = {
         1.0f,  1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  0.0f,  1.0f,
         1.0f,  0.0f,  1.0f,  1.0f,
         1.0f,  0.0f,  0.0f,  1.0f,
         0.0f,  1.0f,  1.0f,  1.0f,
         0.0f,  1.0f,  0.0f,  1.0f,
         0.0f,  0.0f,  1.0f,  1.0f,
         0.5f,  0.5f,  0.5f,  1.0f
};
const GLushort Demo_OGL_2::cube_indices[] = {
        0, 1, 2, 3, 6, 7, 4, 5, // First strip
        0xFFFF, // <<-- This is the restart index
        2, 6, 0, 4, 1, 5, 3, 7 // Second strip*/
};

const vmath::vec3 Demo_OGL_2::X(1.0f, 0.0f, 0.0f);
const vmath::vec3 Demo_OGL_2::Y(0.0f, 1.0f, 0.0f);
const vmath::vec3 Demo_OGL_2::Z(0.0f, 0.0f, 1.0f);

Demo_OGL_2::Demo_OGL_2() : running(false), window(NULL), ctxt(NULL), info(),
            aspect(0), ebo(), vao(), vbo(), render_prog(0),
            render_model_matrix_loc(0), render_projection_matrix_loc(0){}

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

void Demo_OGL_2::OnCleanup() {
    glUseProgram(0);
    glDeleteProgram(render_prog);
    glDeleteVertexArrays(1, vao);
    glDeleteBuffers(1, vbo);
    SDL_GL_DeleteContext(ctxt);
    SDL_DestroyWindow(window);
    SDL_Quit();
}

bool Demo_OGL_2::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_2::SetupOpenGL(){

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

    SDL_GL_SetSwapInterval(1);

    if (gl3wInit()) {
       std::cout << "Error al Inicializar GL3W" << std::endl;
    }
}
int Demo_OGL_2::OnExecute() {
    if (!Init())
        return -1;

    SDL_Event event;

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

        Loop();
        Render();

        if(info.frame()){
            info.fps_ogl_ver_on_window(window, WIN_TITLE);
        }
    }
    Cleanup();
    return 0;
}

void Demo_OGL_2::InitData(){
    // Seleccionamos los shaders que queremos cargar
    ShaderInfo shaders[] = {
         { GL_VERTEX_SHADER, "Resources/Demo_OGL_II/primitive_restart.vs.glsl" },
         { GL_FRAGMENT_SHADER, "Resources/Demo_OGL_II/primitive_restart.fs.glsl" },
         { GL_NONE, NULL }
    };
    // Cargamos los shaders
    render_prog = LoadShaders( shaders );
    // Decimos a opengl que los utilice
    glUseProgram( render_prog );

    // Seleccionamos la posicion dentro del shader de la matriz de modelado y la de proyeccion
    // Mas tarde vamos a usar esas posiciones para cambiar datos del cubo
    render_model_matrix_loc = glGetUniformLocation(render_prog, "model_matrix");
    render_projection_matrix_loc = glGetUniformLocation(render_prog, "projection_matrix");

    // Pedimos un buffer para el element buffer object
    glGenBuffers(1, ebo);
    // Le hacemos hueco diciendole el tipo de buffer
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
    // Lo rellenamos con los indices de los cubos
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cube_indices), cube_indices, GL_STATIC_DRAW);

    // Pedimos un array de vertices
    glGenVertexArrays(1, vao);
    // Le hacemos hueco
    glBindVertexArray(vao[0]);

    // Pedimos un buffer para el vertex buffer object
    glGenBuffers(1, vbo);
    // Le hacemos hueco
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    // Le decimos que el hueco tiene que ser de tamaño "tamaño de cube positions"+"tamaño de cube colors"
    glBufferData(GL_ARRAY_BUFFER, sizeof(cube_positions) + sizeof(cube_colors), NULL, GL_STATIC_DRAW);
    // Y como lo tenemos en dos arrays diferentes lo guardamos poco a poco
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(cube_positions), cube_positions );
    glBufferSubData(GL_ARRAY_BUFFER, sizeof(cube_positions), sizeof(cube_colors), cube_colors);

    // localizacion del atributo, numero de valores, tipo de valores,
    // normalizarlos, espacio entre valores, puntero al primer valor.
    // Por lo tanto decimos que nos pille los 4 primeros valores y
    // nos los meta en "posicion",
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, NULL);
    // Y apartir de cube positions que pille los colores.
    glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)sizeof(cube_positions));
    // Activamos los dos arrays de atributos
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    //Seleccionamos el color de fondo
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    //Los triangulos que no se ven, ni los pinta (los triangulos que estan de espaldas)
    glEnable(GL_CULL_FACE);
    //Utiliza el z buffer
    glEnable(GL_DEPTH_TEST);
    //Funcion para el z-buffer
    glDepthFunc(GL_LESS);

    //lo que ocupa la parte en la que se dibuja.
    glViewport(0, 0, WIN_WIDTH, WIN_HEIGHT);
    aspect = float(WIN_HEIGHT) / float(WIN_WIDTH);
}

void Demo_OGL_2::OnRender() {
    // Un numero incremental en funcion del tiempo real
    float t = float(GetTickCount() & 0x1FFF) / float(0x1FFF);

    // Limpiamos el buffer de profundidad (se pone al valor por defecto)
    // y el de color (se pone al valor de glClearColor
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Decimos que Shader usar
    glUseProgram(render_prog);

    // Calculamos la matriz modelo
    vmath::mat4 model_matrix(vmath::translate(0.0f, 0.0f, -5.0f) *
                                vmath::rotate(t * 360.0f, Y) *
                                vmath::rotate(t * 720.0f, Z));
    // Calculamos la matriz de proyección mediante un frustum
    vmath::mat4 projection_matrix(vmath::frustum(-1.0f, 1.0f, -aspect, aspect, 1.0f, 500.0f));

    // Posicion en el shader, cantidad de matrices, es traspuesta? , matriz a setear.
    // Guardamos las dos matrices en el shader para que dibuje.
    // Al ser uniform el valor, se aprovecha entre los shaders
    glUniformMatrix4fv(render_model_matrix_loc, 1, GL_FALSE, model_matrix);
    glUniformMatrix4fv(render_projection_matrix_loc, 1, GL_FALSE, projection_matrix);
    // Activamos el vertex array Object
    glBindVertexArray( vao[0] );
    // Activamos el buffer de indices
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);


#define USE_PRIMITIVE_RESTART 1
#if USE_PRIMITIVE_RESTART
    // When primitive restart is on, we can call one draw command
    // Le decimos que active el reinicio de primitivas,
    glEnable(GL_PRIMITIVE_RESTART);
    // Este es el valor del indice que se toma para el reinicio
    glPrimitiveRestartIndex(0xFFFF);
    // Dibujamos como triangle strip (aprovechamos los dos vertices
    // anteriores para dibujar el siguiente triangulo)
    // un total de 17 indices y de tipo unsigned short sin offset (NULL)
    glDrawElements(GL_TRIANGLE_STRIP, 17, GL_UNSIGNED_SHORT, NULL);
#else
    //Dibujamos un strip y otro.
    // Without primitive restart, we need to call two draw commands
    glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_SHORT, NULL);
    glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_SHORT,
            (const GLvoid *)(9 * sizeof(GLushort)));
#endif
    // Buffer swap
    SDL_GL_SwapWindow(window);
}
Los arrays de vértices guardan la información sobre el cubo, para que nos hagamos una idea, el cubo está centrado en el eje de axis y seria algo como lo siguiente (Esta hecho con Microsoft Word por rapidez, a si que no esperéis precisión técnica, es más un boceto):
Cubo en tres dimensiones con los vertices coloreados
Edito: El eje Y (el vertical) crece hacia abajo, lo que llevaría los colores de la parte superior a la inferior y viceversa. No considero necesario cambiar el dibujo.
Lo siguiente es una lista de vértices para construir la forma, cuando veamos el glDrawElements explicaré porque es así.
Después viene la inicialización de tres constantes que básicamente representan los 3 vectores básicos (x y z)
OnEvent no se diferencia mucho del anterior, ahora cuando le damos a la v, sale información más detallada sobre el cliente.
OnLoop sigue vacío.
OnCleanUp sigue igual, borramos todo lo que usamos.
OnInit y SetupOpenGL siguen prácticamente igual, glClearColor lo hemos movido a InitData.
OnExecute ha sido ligeramente modificado, hemos añadido una comprobación, recordemos: con frame() informamos que se ha dibujado un frame y si es un frame clave nos dice "true". En ese caso cambiamos el título de la ventana. En nuestro caso debería quedar algo así:
Bueno, hasta aquí lo sencillo, casi copia y pega del anterior. Ahora viene lo "gracioso" :D.

InitData

  • Los Shaders se cargan igual, los hemos puesto al principio para preparar la siguiente instrucción.
  • Primera cosa interesante de este ejemplo: glGetUniformLocation, ésta función nos sirve para buscar la localización de una variable uniforme en nuestro programa de shaders. Y ahora... ¿Qué es una variable uniforme? Se podría decir que es el equivalente a una variable static de Java o C/C++ pero en GLSL. La modificas una sola vez y tiene el mismo valor en todas las ejecuciones hasta que se vuelva a modificar.Por eso, utilizamos como variables uniformes la matriz de proyección y la matriz del modelo. Las dos matrices se van a encargar de alterar lo que estamos viendo. Esta modificación se produce en todos los vértices y objetos, y les va a afectar por igual. Después con el ejemplo se verá mejor esto de la matriz modelo. En el caso de la matriz de proyección es muy claro: <<Imagínate cualquier escenario, tu habitación por ejemplo, y a ti con una cámara de fotos. Todos los elementos se encuentran en el mismo punto del escenario, sin embargo si te mueves en la habitación los elementos salen en distinto sitio en tu foto o distinta rotación (normalmente, no seáis trollers girando alrededor de una botella perfecta :P) la matriz de proyección capta una pirámide delante de la cámara y la usa para dibujar la escena, pero obviamente esta pirámide es igual para cualquier objeto, lo importante es la posición de ese objeto dentro o fuera de la piramide.>>En el renderizado veremos como se inicializan estos elementos.
  • El siguiente paso se va a convertir en un clásico: generamos buffer, lo ligamos y lo rellenamos con información. ¿Qué tiene de especial? GL_ELEMENT_ARRAY_BUFFER que le informa del tipo de buffer que queremos ocupar y lo rellenamos con el array de índices.
  • Lo siguiente es otro clásico, generar un array de vértices, ligarlo, pedir un buffer para los vértices, ligarlo y... ¡OJO! hacer hueco para la información. Si os fijáis donde debería ir el puntero al array con los datos hay NULL y reservamos espacio para las posiciones y para los colores.
    ¿Por qué? Hay dos maneras de guardar en el mismo buffer la información conjunta:
    • Creamos un sólo array y cuando vayamos a darle las instrucciones de como leer al shader, le decimos donde tiene que empezar y listo. Esto tiene un problema cuando quieres añadir nueva información, hay que modificar todo lo implicado.
    • La solución correcta: por un lado haces hueco y por otro rellenas el hueco con la función glBufferSubData que nos permite rellenar por partes el buffer. La estructura de la función es casi la misma que la de glBufferData, con la diferencia de que hay que decirle dónde empieza la información.
      glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(cube_positions), cube_positions );
      glBufferSubData(GL_ARRAY_BUFFER, sizeof(cube_positions), sizeof(cube_colors), cube_colors);
  • Lo siguiente son los punteros para asociar los valores de los buffer al programa GLSL. Esto ya esta explicado en el anterior tutorial si no os quedo claro, podéis volver a ello.
  • glClearColor ya explicado también.
  • glEnable(GL_CULL_FACE) es una optimización para evitar cálculos innecesarios.
  • glEnable(GL_DEPTH_TEST) le decimos que use el test de profundidad, si desactivais esto, pueden salir cosas muy raras.
  • Lo siguiente es la función que se utiliza para el z-buffer, si el valor de z es menor, se guarda en el buffer. Es decir dibujamos lo mas cercano como es lógico. Aquí tenéis una referencia al resto de posibles funciones.
  • Establecemos el ancho y el alto del contexto y calculamos el aspect ratio que es una relación ancho alto para calcular la matriz de proyección.
Hasta aquí la inicialización "global" de la información. Ahora llega el momento del dibujado.

OnRender

  • Lo primero es establecer una medida de tiempo. En primera instancia se nos podría ocurrir que en el ejemplo de mover un objeto desde A hasta B en el eje X, Aumentamos o disminuimos X de manera constante hasta llegar a B.
    El problema a este planteamiento es el ordenador: Supongamos que el ordenador funciona perfectamente y va a 60 fps (tenemos la sincro vertical activada), eso quiere decir que aumentaríamos X 60 puntos. Hasta aquí bien, el problema llega cuando desactivamos la sincro vertical o el ordenador no es capaz de llegar a los 60 fps. Supongamos que otro ordenador paupérrimo intenta tirar nuestro juego o animación y solo es capaz de llegar a los 20 fps. Todo el juego le iría a un tercio de la velocidad a la que le iba al de 60 fps, esto aparte de un aburrimiento, podría suponer una ventaja o un inconveniente para el jugador. Supongamos un buen ordenador con la sincro vertical desactivada, podría llegar a correr el juego 10 o incluso 100 veces más rápido, en la mayoría de juegos, no se podría jugar. ¿Os imagináis jugar al plantas vs zombis 100 veces mas rápido? desaparecen los soles y los zombis entran hasta la cocina antes de que de tiempo de poner las plantas.
    Por ello necesitamos una medida de tiempo: ¿Cuánto tiempo ha pasado desde el último fps? Eso es lo que hace la primera linea, como vamos a poner a dar vueltas el cubo, nos viene bien una variable "circular": cuando llega a un valor se reinicia. Esto lo conseguimos usando un and lógico y una división con respecto al tiempo (
    GetTickCount()).
  • glClear limpia los dos buffer de dibujado básicos.
  • Informamos del shader que hay que usar para dibujar.
  • Calculamos la matriz modelo: Básicamente hacemos la multiplicación de 3 matrices. vmath nos facilita estas operaciones (si alguien tiene curiosidad por como se hace, investigad que hay mucha teoría por internet, aquí no me voy a parar en eso). La matriz de traslación traslada el modelo (es decir, el cubo) 5 puntos hacia el fondo (Z) y después hacemos las rotaciones del cubo en el eje Z e Y. Es importantísimo el orden de multiplicación. La rotación modifica el eje del objeto. Si primero hiciésemos la rotación y después la traslación se trasladaría en otro eje diferente de Z con respecto al eje "global". Tendríamos un cubo haciendo una órbita en lugar de una rotación en el sitio.
  • Calculamos la matriz de proyección: utilizamos el frustum [wikipedia en ingles]. Un frustum en informática gráfica 3D es una pirámide de base cuadrada que define el campo visual en cuyo vértice superior se situaría la cámara mirando en dirección y sentido a la base.
    En un frustum hay que definir (con respecto al origen 2D de coordenadas de la ventana) las proporciones. En este caso hemos elegido mantener los margenes izquierdo y derecho constantes (1 y -1) y utilizamos la variable aspect que hemos calculado antes para que nos de la proporción correcta de altura.
    Ademas de estos 4 valores hay que darle dos más: near y far.
    • Un frustum en realidad es una pirámide truncada, el punto de truncamiento lo define near. Es el punto mínimo para empezar "recolectar" objetos que salen en imagen. Lo que este antes de near, ni se calcula.
    • far define el punto mas lejano que se puede avistar.
    En la wiki viene un dibujo explicativo típico bastante intuitivo.
  • Ahora guardamos los valores que hemos calculado en las localizaciones que hemos calculado en InitData.
  • Activamos el array de vertices, activamos el buffer de elementos
  • Procedemos a dibujar como tal, para ello quiero pararme en un par de conceptos:
    • Hay varias maneras de dibujar en opengl. vimos en el primer ejemplo glDrawArrays que simplemente dibujaba lo que se encuentra en el buffer de vértices. Ahora vamos a utilizar glDrawElements que utiliza una lista de indices de vértices para dibujar. Es decir, si tenemos 10 vértices numerados del 0 al 9, glDrawArrays dibujaría del 0 al 9, en cambio, con glDrawElements podríamos dibujarlos así: 0, 5, 3, 2...
    • Esto nos va a traer unas ventajas porque ademas vamos a utilizar el GL_TRIANGLE_STRIP para dibujar. ¿Qué significa eso? que dibuja dos vértices y el siguiente vértice conformara el primer triángulo como viene siendo lógico, pero el cuarto vértice creará el segundo triángulo con los dos últimos vértices utilizados. Ejemplo: tenemos 4 vértices dispuestos en forma de cuadrado. Suponiéndolos numerados en sentido de las agujas del reloj el orden de los vértices en nuestro array sería: 0, 1, 3, 2. Lo que se transformaría en dos triangulos: 0, 1, 3 y 1, 3, 2. Cosa que no podríamos conseguir con glDrawArrays si no cambiamos el orden de los vértices en nuestro array.
      En 3 dimensiones tiene más gracia porque vamos a reutilizar todos los vértices para hacer el mínimo número de llamadas a funciones teniendo el mínimo número de vértices.
      Ademas nos trae otra ventaja, cuando dibujamos dos triángulos contiguos, el render puede dejar en medio una linea sin colorear, es un bug gráfico común que mas de uno habrá apreciado en juegos. 
      GL_TRIANGLE_STRIP evade esos huecos, la gráfica entiende que están unidos y no juntos y rellena el hueco.
    • Por último la activación de GL_PRIMITIVE_RESTART. Es una especie de "marca" que indica cuando tiene que volver a empezar a dibujar GL_TRIANGLE_STRIP. Hay que activarlo y decirle que marca es. Lo que hacemos es crear un "puzzle" de dos piezas que encajan a la perfección porque los vértices son los mismos.
      La mejor manera de captarlo es visualmente:
      Los vértices los he enumerado entre corchetes y las lineas punteadas representan los triángulos que no forman parte de las aristas del cubo.
  • Bueno pues entendidos los conceptos tenemos los dos ejemplos, con reinicio y sin reinicio de primitivas:
    • Con reinicio: 1º habilitamos el reinicio, 2º decimos que el indice de reinicio (nuestra marca) es 0xFFFF (un vértice es improbable que se use) y 3º dibujamos, le decimos el tipo de primitiva, el número de índices que tiene que leer (8+8+1 marca), el tipo de dato y un puntero al primer valor (NULL = primero).
    • Sin reinicio: dibujamos 8 vértices y después los otros 8 empezando por el 9º.
    Dicen que hay algunas tarjetas gráficas que no soportan el reinicio de primitivas. Por eso es importante entender las dos.
  • Por último cambiamos el buffer.

Shaders

Por otro lado tenemos los Shaders:
  • Fragment Shader: simple como el solo, una variable de entrada y otra de salida que se comunican como si el shader fuera una simple tubería.
  • Vertex Shader: Tiene la definición de las dos variables uniformes de las que hemos hablado largo y tendido. Las dos variables de entrada, y la variable de salida para comunicarse con el fragment shader.
    En el main pasamos el color directamente y la posición de cada vértice la calculamos multiplicando las matrices que hemos calculado previamente. El orden de multiplicación también es importante, a si que os invito a probar calculadoras online de matrices que os dan una idea de como funciona el asunto.
Debería de quedar algo así:

Cosas que podéis probar como curiosidad

Os dejo cosas que podéis probar si os sentís curiosos antes de pasar al siguiente ejemplo, si os equivocáis con dejar las cosas como estaban funciona.
  • Cambiar el frustum.
  • Desactivar la sincro vertical y ver como se disparan los fps (SDL_GL_SetSwapInterval(  0  )); a mi se me pone en una media de 4500fps.
  • Incrementar t sin que esté en función del tiempo para ver la diferencia con la sincro vertical activada y desactivada.
  • Cambiar el orden de las multiplicaciones de matrices y los valores de posicionamiento para entender bien el sistema de traslación y rotación.
  • EPIC: Intentar hacer un sistema de movimiento con WASD.
    (Spoiler: en el siguiente ejemplo está hecho, si lo quieres intentar no avances)
  • ¡¡¡EPIC!!!: Intentar hacer niebla con el fragment shader.
    PISTA: La clave está en Z (del Z-buffer), buscad en intenet las built-in variables de GLSL.
    (Spoiler: en el siguiente ejemplo está hecho, si lo quieres intentar no avances)

Espero que os haya gustado, cuando pueda cuelgo el siguiente que ya tengo en github, aunque puede que lo divida en dos partes.