# Introducción al Texture Mapping en OpenGL.

En el desarrollo de **gráficos 3D en tiempo real**, uno de los principales retos es alcanzar **realismo visual** sin sacrificar rendimiento. Los motores gráficos modernos abordan este problema mediante técnicas como la **iluminación**, el **sombreado**, la optimización de la geometría y, de forma central, el **texture mapping** o mapeo de texturas.

Introducido por **Jim Blinn en 1978**, el texture mapping es una técnica esencial del **pipeline gráfico** que permite aplicar **imágenes 2D (texturas)** sobre **mallas tridimensionales** mediante **coordenadas UV**. En APIs gráficas como **OpenGL**, estas coordenadas son utilizadas por la **GPU** para muestrear la textura en el **fragment shader**, haciendo posible representar materiales complejos —como madera, ladrillo o metal— sin incrementar el número de polígonos.

Más allá de la simple asignación de una imagen, el texturizado moderno incorpora técnicas clave como el **mipmapping**, los **filtros de textura** (bilineal, trilineal y **anisotrópico**), los modos de **wrapping y tiling**, y la **corrección de distorsión por perspectiva**, todas orientadas a mejorar la calidad visual, la estabilidad de la imagen y la eficiencia del renderizado.

Este artículo presenta una introducción práctica al **texture mapping en OpenGL**, abordando tanto sus fundamentos como las extensiones más importantes que permiten obtener un texturizado robusto, eficiente y visualmente coherente en aplicaciones gráficas modernas.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760297196877/491efb5a-7172-4a50-86e2-4058cb9d7d1a.png align="center")

El ***texture mapping*** actúa como un puente entre el mundo bidimensional de las imágenes y el tridimensional de los modelos, aportando realismo y eficiencia al proceso de renderizado.

Las principales ventajas de esta técnica son:

* **Mayor realismo visual:** las texturas aportan color, detalle y variaciones naturales en la superficie.
    
* **Mejor rendimiento:** se reduce la complejidad geométrica del modelo, lo que permite procesar escenas más grandes en menos tiempo.
    
* **Versatilidad:** posibilita combinar distintos tipos de mapas (de color, normales, especular, entre otros) para simular materiales complejos.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760298204235/871bcebc-e242-4866-8b06-6723e7b36499.gif align="center")

## Soporte en hardware: *Texture Units*

Las **GPU modernas** incluyen unidades especializadas llamadas **Texture Units**, diseñadas para almacenar y procesar texturas directamente en el hardware.  
Gracias a ellas, un mismo objeto puede usar varios mapas —como color base (*diffuse*), normales (*normal map*) o brillo (*specular map*)— sin afectar el rendimiento.

En **OpenGL**, estas unidades se controlan mediante funciones como:

```c++
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, brickTexture);
```

Aquí se activa la unidad de textura 0 y se vincula la textura correspondiente.  
En el *fragment shader*, esta textura se accede a través de un **sampler**, que actúa como un enlace entre la unidad y el *shader*:

```c++
layout(binding = 0) uniform sampler2D samp;
vec4 color = texture(samp, texCoords);
```

De esta forma, cada fragmento obtiene su color desde la textura asociada, lo que permite representar materiales complejos con geometría mínima. El *texture mapping* es, por tanto, una de las bases del realismo visual en OpenGL: combina eficiencia de renderizado con un alto nivel de detalle.

---

## Componentes del *Texture Mapping*

Aplicar texturas correctamente en OpenGL requiere comprender cómo interactúan tres elementos clave: el **objeto de textura**, las **coordenadas de textura** y las **unidades de textura**.

### Texture Object

Un **texture object** almacena toda la información de una textura: imagen, formato, filtros, envoltura y niveles de *mipmap*.

Se crea y vincula con:

```c++
GLuint texID; glGenTextures(1, &texID);
glBindTexture(GL_TEXTURE_2D, texID);
```

Una vez vinculado, cualquier configuración o carga de datos afectará a ese objeto.

### Coordenadas de textura (UV)

Cada vértice del modelo tiene asociadas **coordenadas de textura (s, t)** que indican qué parte de la imagen se aplica sobre él. Estas se almacenan en un **VBO** y se asocian al *shader* mediante atributos de vértice:

```c++
glGenBuffers(1, &texCoordBuffer);
glBindBuffer(GL_ARRAY_BUFFER, texCoordBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(texCoords), texCoords, GL_STATIC_DRAW);  glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
```

En el *shader* correspondiente:

```c++
layout(location = 1) in vec2 texCoord;
```

### Texture Units

Las **texture units** son espacios de memoria dedicados dentro de la GPU que permiten usar múltiples texturas simultáneamente. Cada *sampler* en el *shader* se vincula a una de estas unidades mediante su *binding*.

Por ejemplo:

```c++
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureID);
```

OpenGL garantiza al menos **16 unidades** por defecto, aunque la mayoría de las GPU actuales soportan muchas más, lo que permite combinar texturas de color, normales, o reflexión en un mismo objeto.

En conjunto, estos componentes —el objeto de textura, las coordenadas UV y las unidades de textura— forman el núcleo del ***texture mapping***.

Comprender su interacción es esencial para aprovechar el potencial del pipeline gráfico antes de pasar a la etapa práctica, donde las texturas se cargan desde archivos mediante librerías como **SOIL2** y se aplican directamente a los modelos 3D.

---

## **Carga de texturas con SOIL2**

Una vez configurados los elementos básicos del *texture mapping*, el siguiente paso es **cargar imágenes desde archivos** para convertirlas en texturas utilizables por OpenGL. Aunque es posible hacerlo manualmente con las funciones nativas, resulta más práctico y eficiente emplear una librería como **SOIL2 (Simple OpenGL Image Library 2)**. SOIL2 se encarga de leer los archivos de imagen, decodificarlos y transferirlos directamente a la memoria de la GPU en formato de textura.

### Integración de SOIL2

Para usar SOIL2 basta con incluir su encabezado y enlazar la librería:

`#include <SOIL2/SOIL2.h>`

Una vez integrada, se puede cargar cualquier formato de imagen compatible (.jpg, .png, .bmp, .tiff, .gif, etc.) con la función `SOIL_load_OGL_texture()`, que realiza todo el proceso de forma automática.

### Función `Utils::loadTexture()`

La siguiente función implementa la carga de texturas mediante SOIL2 dentro de una clase de utilidades (`Utils.cpp`). Encapsula la lectura del archivo, la generación de mipmaps y la configuración del filtrado anisotrópico:

```c++
// Carga de textura con SOIL2 y configuración avanzada
GLuint Utils::loadTexture(const char *texImagePath)
{
    GLuint textureRef = SOIL_load_OGL_texture(
        texImagePath,            // Ruta del archivo
        SOIL_LOAD_AUTO,          // Detecta formato automáticamente
        SOIL_CREATE_NEW_ID,      // Crea un nuevo ID de textura
        SOIL_FLAG_INVERT_Y       // Invierte el eje Y (compatibilidad con OpenGL)
    );

    // Verificación de errores
    if (textureRef == 0)
        std::cout << "No se encontró el archivo de textura: " << texImagePath << std::endl;

    // --- Configuración de mipmaps y filtrado ---
    glBindTexture(GL_TEXTURE_2D, textureRef);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glGenerateMipmap(GL_TEXTURE_2D);

    // Filtrado anisotrópico (si está disponible)
    if (isExtensionSupported("GL_EXT_texture_filter_anisotropic")) {
        GLfloat maxAniso = 0.0f;
        glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &maxAniso);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, maxAniso);
    } else {
        std::cout << "Filtrado anisotrópico no soportado" << std::endl;
    }

    return textureRef;
}
```

### Desglose del proceso

1. **Carga de la imagen:**  
    `SOIL_load_OGL_texture()` abre el archivo, decodifica la imagen y genera automáticamente una textura 2D en la GPU. El uso de `SOIL_FLAG_INVERT_Y` es necesario porque muchas imágenes tienen el origen en la esquina superior izquierda, mientras que OpenGL lo maneja desde la inferior izquierda.
    
2. **Verificación de errores:**  
    Si la carga falla, la función devuelve `0` y se muestra un mensaje en consola.
    
3. **Generación de mipmaps:**  
    Los **mipmaps** son versiones reducidas de la textura que OpenGL selecciona según la distancia del objeto a la cámara, mejorando el rendimiento y la calidad visual.
    
4. **Filtrado anisotrópico:**  
    Aumenta la nitidez de las texturas vistas en ángulos oblicuos. Si la extensión `GL_EXT_texture_filter_anisotropic` está disponible, se aplica el máximo nivel permitido por la GPU.
    

Con esta función, cargar una textura se reduce a una sola línea:

```c++
GLuint brickTexture = Utils::loadTexture("brick1.jpg");
```

En una sola llamada, se realiza todo el proceso: lectura del archivo, creación de la textura, generación de mipmaps y configuración del filtrado. Este enfoque mantiene el código **modular, limpio y reutilizable**, facilitando la gestión de texturas en cualquier proyecto OpenGL.

## Ejemplo

El siguiente programa muestra el flujo esencial del *texture mapping*: **carga, asociación y muestreo** de una textura sobre un modelo 3D.  
Utiliza **SOIL** para cargar la imagen y **OpenGL 4.3** para gestionar los *buffers* y *shaders*.

Los **buffers** (VAO y VBO) almacenan la información necesaria para el renderizado: los **vértices** del cubo y sus **coordenadas de textura**, que van de `(0.0, 0.0)` a `(1.0, 1.0)`. Estas coordenadas indican qué parte de la imagen corresponde a cada vértice, definiendo cómo se “mapea” la textura sobre la superficie del modelo.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760296354153/e2f08923-8189-43fa-9961-77efbf356a50.jpeg align="center")

Los **shaders** procesan esa información directamente en la GPU:

* El **vertex shader** transforma los vértices del modelo al espacio de proyección y pasa las coordenadas UV al siguiente paso.
    
* El **fragment shader** usa esas coordenadas para **muestrear la textura** mediante la función `texture()`, determinando el color final de cada fragmento.
    

De este modo, se aplica una imagen a un cubo con gran realismo visual y un costo computacional mínimo.

### Resultado

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760296833360/f3b6a40c-8782-41ef-ba92-9678f602f6ab.gif align="center")

## Mipmapping

Cuando una textura se visualiza a distintas distancias de la cámara, su resolución efectiva cambia. Si siempre se utilizara la textura original, OpenGL tendría que muestrear muchos texels que finalmente terminan contribuyendo a un solo fragmento, lo que produce **aliasing**, parpadeos y pérdida de estabilidad visual.

El **mipmapping** soluciona este problema generando una pirámide de versiones reducidas de la textura. Cada nivel mipmap es una imagen con la mitad de resolución del nivel anterior, hasta llegar a una textura de 1×1 texel. Durante el renderizado, la GPU selecciona automáticamente el nivel más adecuado según el tamaño del fragmento en pantalla.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1765742071173/c7d89fa7-ef90-464e-bc74-e4b3dfd8743c.png align="center")

En OpenGL, los mipmaps pueden generarse de forma automática:

```cpp
glBindTexture(GL_TEXTURE_2D, textureID);
glGenerateMipmap(GL_TEXTURE_2D);
```

Para que OpenGL los utilice, es necesario configurar el filtro de minimización:

```cpp
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
```

Este modo combina **interpolación lineal** dentro de cada mipmap y entre niveles consecutivos, logrando transiciones suaves entre resoluciones.

Las ventajas del mipmapping son claras:

* Reduce el aliasing y el shimmering en objetos lejanos.
    
* Mejora el rendimiento, ya que se accede a texturas más pequeñas.
    
* Aumenta la estabilidad visual en escenas con movimiento.
    

Por estas razones, el uso de mipmaps no es opcional en motores gráficos modernos: es una práctica estándar.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1765741766987/b16025f8-0b45-48f9-af1e-c13c5adcc992.png align="center")

## Filtrado anisotrópico

El mipmapping mejora la calidad a distancia, pero no resuelve un problema frecuente: las **texturas vistas en ángulos oblicuos**. En estos casos, una superficie puede cubrir muchos texels en una dirección y muy pocos en otra. Los filtros bilineales o trilineales asumen una huella cuadrada del fragmento, lo cual no refleja la realidad geométrica.

El **filtrado anisotrópico** corrige este efecto adaptando el muestreo de la textura a una huella elíptica, alineada con la proyección del fragmento sobre la superficie. El resultado es una textura mucho más nítida cuando se observa en perspectiva rasante, como suelos, carreteras o paredes largas.

En OpenGL, este filtrado depende de la extensión:

```cpp
GL_EXT_texture_filter_anisotropic
```

Si está disponible, se puede configurar de la siguiente manera:

```cpp
GLfloat maxAniso;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &maxAniso);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, maxAniso);
```

El valor indica cuántas muestras adicionales puede tomar la GPU en la dirección dominante. Cuanto mayor sea, mejor será la calidad, aunque con un ligero costo adicional.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1765742679012/ca9c1b08-1749-4c07-8d6a-0045d023c9b6.jpeg align="center")

En la práctica, el filtrado anisotrópico ofrece una de las **mejores mejoras visuales por costo computacional**, por lo que suele activarse siempre que el hardware lo permita.

## Wrapping y Tiling de texturas

Las coordenadas de textura normalmente se definen en el rango `[0, 1]`. Sin embargo, es común que estas coordenadas excedan ese intervalo, ya sea intencionalmente (para repetir una textura) o por la forma en que se modela la geometría.

El **wrapping** define cómo OpenGL maneja las coordenadas fuera del rango válido. Los modos más comunes son:

* `GL_REPEAT`: la textura se repite indefinidamente.
    
* `GL_MIRRORED_REPEAT`: se repite invirtiéndose en cada ciclo.
    
* `GL_CLAMP_TO_EDGE`: se extiende el borde de la textura.
    
* `GL_CLAMP_TO_BORDER`: se usa un color constante en los bordes.
    

Ejemplo de configuración:

```cpp
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
```

El **tiling** es una consecuencia directa del wrapping. Si las coordenadas UV se escalan más allá de `[0,1]`, la textura se repite múltiples veces sobre la superficie:

```cpp
vec2 tiledUV = texCoord * 4.0;
vec4 color = texture(samp, tiledUV);
```

Esta técnica es especialmente útil para suelos, paredes o terrenos, donde una textura pequeña puede cubrir grandes áreas sin aumentar el uso de memoria.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1765742994881/5a3ed07e-5b9e-4242-8191-58395377178b.png align="center")

El control adecuado del wrapping y el tiling es esencial para evitar costuras visibles y patrones repetitivos poco realistas.

---

## Corrección de distorsión por perspectiva

Un error conceptual común es asumir que la interpolación de coordenadas UV es siempre correcta. En realidad, si la interpolación se realiza de forma lineal en espacio de pantalla, se produce una **distorsión por perspectiva**, visible como estiramientos o deslizamientos incorrectos de la textura en superficies inclinadas.

La solución es la **perspective-correct interpolation**, que tiene en cuenta el valor `w` del espacio homogéneo al interpolar las coordenadas. Afortunadamente, en OpenGL moderno esta corrección se realiza automáticamente cuando se usan shaders estándar y un pipeline correcto.

Internamente, la GPU interpola las coordenadas como:

$$\frac{u}{w}, \frac{v}{w}$$

y luego reconstruye el valor correcto por fragmento. Esto garantiza que la textura se proyecte correctamente en superficies tridimensionales.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1765743147769/c7333590-2442-4618-ad0a-d112b8875382.png align="center")

Solo en casos especiales —como el uso de interpoladores personalizados o técnicas de rasterización manual— es posible desactivar esta corrección, pero hacerlo casi siempre conduce a errores visuales evidentes.

Por tanto, la corrección de perspectiva no es una opción avanzada, sino un **requisito fundamental** para un texturizado físicamente coherente.

## Conclusión

El texture mapping constituye uno de los pilares fundamentales del renderizado moderno en OpenGL. A través de la asignación de coordenadas UV y el muestreo de imágenes en el fragment shader, es posible representar superficies visualmente ricas sin incrementar la complejidad geométrica de los modelos, logrando una combinación óptima entre realismo y rendimiento.

Sin embargo, la calidad final del texturizado no depende únicamente de aplicar una imagen sobre una malla. Técnicas complementarias como el **mipmapping** permiten adaptar la resolución de la textura a la distancia de la cámara, reduciendo aliasing y mejorando la estabilidad visual. El **filtrado anisotrópico** refina este proceso al preservar el detalle en superficies observadas en ángulos oblicuos, uno de los casos más críticos en escenas tridimensionales.

Por otro lado, el control del **wrapping y tiling** ofrece flexibilidad para reutilizar texturas y cubrir grandes superficies de forma eficiente, mientras que la **corrección de distorsión por perspectiva**, aplicada automáticamente en el pipeline moderno, garantiza que la interpolación de las coordenadas UV sea geométricamente coherente con la proyección 3D.

%[https://github.com/Nobody-1321/Computer-Graphics-Programming]
