Generación de Terreno

Generación de terreno utilizando un mapa de elevación, crearemos un terreno plano y usaremos un mapa de altura para establecer la elevación de cada uno de los vértices que componen el terreno, finalizamos aplicando una textura al terreno para darle un aspecto más realista, la textura puede contener rocas, nieve, fango, arena, y otros con lo que se logran efectos muy realistas.

Generar Terreno Plano


Lo primero que haremos será generar una serie de vértices que formaran el terreno plano, la componente Y de la coordenada de momento será 0, utilizaremos una imagen para definir la altura luego, por cada pixel de la imagen generamos un vértice, WIDTH establece el ancho máximo del terreno, corresponde al ancho de la imagen, DEPTH establece la profundidad y corresponde al alto de la imagen.

for (int j = 0; j < DEPTH; j++) {
    for (int i = 0; i < WIDTH; i++) {
        float x = (float(i) / (WIDTH - 1)) * 2.0f - 1.0f;
        float z = (float(j) / (DEPTH - 1)) * 2.0f - 1.0f;
        vertices.push_back(glm::vec3(x, 0, z));
    }
}

Las coordenadas de los vértices se encuentran en el rango de valores [-1, 1], seguido debemos definir como se forman cada uno de los triángulos que componen el terreno, por lo que vamos a definir los índices necesarios para generar la geometría.

std::vector<GLuint> indices((DEPTH - 1) * (WIDTH - 1) * 6);
std::vector<GLuint>::iterator id;
id = indices.begin();

for (int z = 0; z < DEPTH - 1; z++) {
    for (int x = 0; x < WIDTH - 1; x++) {
        int i0 = (z * DEPTH) + x;
        int i1 = i0 + 1;
        int i2 = ((z + 1) * DEPTH) + x;
        int i3 = i2 + 1;

        *id++ = i0; *id++ = i2; *id++ = i1;
        *id++ = i1; *id++ = i2; *id++ = i3;
    }
}

En cada pasada se generan los índices necesarios para crear un cuadrado compuesto por dos triángulos, para finalizar creamos e inicializamos el VAO del mismo modo como lo venimos haciendo el tutoriales anteriores, creamos también un shader simple que posiciona cada vértices y le asigna un color determinado.

tutorial opengl terreno plano

Mapa de Altura


No es más que una imagen o archivo que almacena la información de elevación de cada vértice del terreno, en este ejemplo utilizaremos una imagen de 8 bits por píxel, lo que nos permite almacenar valores entre [0, 255] en un solo canal, si vemos la imagen veremos algo como esto:

heightmap
Una imagen en escala de grises donde el color blanco más intenso representa mayor elevación, vamos a leer la imagen para obtener cada uno de los valores de cada píxel y asignarlo a componente Y de su correspondiente vértice, dividimos entre 255 para obtener valores entre [0, 1].

void loadTerrainHeight(const std::string& filename, std::vector<GLubyte>& altura, int& width, int& height)
{
    int channels;
    unsigned char *pData = stbi_load(filename.c_str(), &width, &height, &channels, STBI_grey);

    if (pData == nullptr) {
        std::cout << "Error al cargar: " << filename << std::endl;
        return;
    }

    altura.insert(altura.end(), pData, pData + (width * height));
    stbi_image_free(pData);
}

Esta función lee la imagen y rellena el std::vector<GLubyte> indicado con los valores de cada pixel, obtiene también la altura y ancho de la imagen, ahora podemos definir cada vértice estableciendo apropiadamente su elevación (componente Y) según lo indique el height map.

terreno heightmap tutorial

Calcular Normales


Hasta este punto hemos usado el modo de polígono GL_LINE, que muestra solo las líneas que forman la figura, si usamos el modo de relleno GL_FILL, el terreno no se mostrará de manera adecuada ya que no hemos aplicado efectos de iluminación, para poder hacerlo primero debemos calcular los vectores normales para cada uno de los vértices.

for (size_t i = 0; i < vertexCount; i += 3)
{
    glm::vec3 p0 = m_vertices[m_indices[i + 0]];
    glm::vec3 p1 = m_vertices[m_indices[i + 1]];
    glm::vec3 p2 = m_vertices[m_indices[i + 2]];

    glm::vec3 e1 = p0 - p1;
    glm::vec3 e2 = p0 - p2;
    glm::vec3 normal = glm::normalize(glm::cross(e1, e2));

    m_normals[m_indices[i + 0]] += normal;
    m_normals[m_indices[i + 1]] += normal;
    m_normals[m_indices[i + 2]] += normal;
}

Para calcular el vector normal de los vectores V1 y V2 utilizamos el producto cruz V1 x V2, luego normalizamos, podemos tener varias normales para un mismo vértice por lo que sumamos cada una de ellas.

calcular normal
Agregando el conjunto de normales al VAO y aplicando en modelo de iluminación Phong, por simplicidad solo calculamos la componente difusa, el resultado:

opengl tutorial
Por ultimo aplicaremos una textura, para simular la hierba y la tierra, se pueden aplicar varias texturas para lograr mejores resultados, para hacerlo simple aplicare una textura cualquiera, la misma no concuerda con el mapa de elevación, solo la utilizare para mostrar cómo aplicar la textura.

En el vertex shader usaremos las coordenadas de la posición de cada vértice para definir las coordenadas UV de la textura, tomamos las componentes X y Z, aplicamos la siguiente operación (P * 0.5 - 0.5) para obtener convertir el rango de valores de [-1, 1] a [0, 1] y pasamos al fragment shader, aquí aplicamos la textura2D como siempre lo hemos hecho.

terreno textura iluminacion
Existen aplicaciones de diseño que nos permiten generar los terrenos en modo diseño, los mismos generar el height map, las texturas que pueden ser exportadas para ser cargadas con la aplicación que este desarrollando.

GitHub: Generación de Terrenos OpenGL

Comentarios

Publicar un comentario

Temas relacionados

Entradas populares de este blog

tkinter Grid

Conectar SQL Server con Java

Controles y Contenedores JavaFX 8 - I

Histogramas OpenCV Python