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.




No hay comentarios:

Publicar un comentario