Texture Mapping — UV Coordinates, Filtering and Mipmaps
Texture Mapping is the Process of applying 2D images onto 3D surfaces by mapping texture coordinates (UVs) to geometry, enabling detailed surface appearance without increasing polygon count.
What You'll Learn & Why It Matters
In this tutorial, you will learn how Texture Mapping works — UV coordinate systems, texture filtering methods, mipmapping for anti-aliasing, wrapping and clamping modes, and how to implement texturing in OpenGL.
Real-world use: Every 3D game and movie relies on Texture Mapping. DodaZIP uses texture-mapped icons and UI elements. Photorealistic rendering depends on high-resolution textures with proper filtering.
Prerequisites
- OpenGL Basics (previous)
- Shader Programming (previous)
- Digital image basics
Learning Path
flowchart LR A[OpenGL Basics] --> B[Texture Mapping] B --> C[Lighting Models] B --> D[Procedural Textures] B --> E[Post-Processing] C --> F[BRDF and Materials] B:::current classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px
UV Coordinates
UV coordinates define how a 2D texture maps onto a 3D surface. They range from (0,0) at the bottom-left to (1,1) at the top-right in texture space.
import numpy as np
# UV coordinates for a simple quad
uv_coords = np.array([
[0.0, 0.0], # Bottom-left
[1.0, 0.0], # Bottom-right
[1.0, 1.0], # Top-right
[0.0, 1.0], # Top-left
])
# A point on the texture at UV (0.5, 0.25)
uv_point = np.array([0.5, 0.25])
texel_x = int(uv_point[0] * texture_width)
texel_y = int(uv_point[1] * texture_height)
print(f"Texture coordinates ({uv_point[0]}, {uv_point[1]})")
print(f"Map to texel ({texel_x}, {texel_y})")
Expected output:
Texture coordinates (0.5, 0.25)
Map to texel (256, 128)
Loading Textures in OpenGL
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// Set texture parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Load image data (using stb_image)
int width, height, channels;
unsigned char* data = stbi_load("texture.jpg", &width, &height, &channels, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
stbi_image_free(data);
Texture Filtering
When a texture is displayed larger or smaller than its native resolution, filtering determines how texels are sampled:
flowchart TD A[Texture Filtering] --> B[Minification] A --> C[Magnification] B --> D[Nearest: Blocky, fast] B --> E[Bilinear: Smooth, blurry] B --> F[Trilinear: Mipmap smooth] C --> G[Nearest: Pixelated] C --> H[Bilinear: Smooth]
// Fragment shader with texture sampling
#version 330 core
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main()
{
vec4 texColor = texture(ourTexture, TexCoord);
FragColor = texColor;
}
Mipmaps
Mipmaps are pre-computed, progressively lower-resolution versions of a texture. When a textured surface is far from the camera, the GPU uses a smaller mip level instead of filtering the full-resolution image.
def generate_mipmap(image, levels):
"""Generate mipmap levels by halving resolution."""
mipmaps = [image]
current = image
for i in range(1, levels):
h, w = current.shape[0] // 2, current.shape[1] // 2
if h == 0 or w == 0:
break
downsampled = np.zeros((h, w, 3), dtype=current.dtype)
for y in range(h):
for x in range(w):
block = current[y*2:y*2+2, x*2:x*2+2].mean(axis=(0, 1))
downsampled[y, x] = block
mipmaps.append(downsampled)
current = downsampled
return mipmaps
# Example: 512x512 texture generates mips: 256, 128, 64, 32, 16, 8, 4, 2, 1
mips = generate_mipmap(np.random.rand(512, 512, 3), 10)
print(f"Generated {len(mips)} mip levels: {[m.shape[0] for m in mips]}")
Expected output:
Generated 10 mip levels: [512, 256, 128, 64, 32, 16, 8, 4, 2, 1]
Texture Wrapping Modes
| Mode | Behavior | Use Case |
|---|---|---|
| GL_REPEAT | Tile the texture | Floor tiles, walls |
| GL_CLAMP_TO_EDGE | Clamp to edge color | Single decal, UI |
| GL_CLAMP_TO_BORDER | Use border color | Framing effects |
| GL_MIRRORED_REPEAT | Mirror at edges | Symmetric patterns |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
Multiple Textures (Multitexturing)
#version 330 core
in vec2 TexCoord;
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform float mixValue;
void main()
{
vec4 tex1 = texture(texture1, TexCoord);
vec4 tex2 = texture(texture2, TexCoord);
FragColor = mix(tex1, tex2, mixValue);
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glUniform1i(glGetUniformLocation(shader, "texture1"), 0);
glUniform1i(glGetUniformLocation(shader, "texture2"), 1);
Common Errors & Mistakes
1. Texture Not Appearing (White/Black Object)
Mistake: Not binding the texture unit or not setting the sampler uniform to the correct unit.
Fix: Bind textures with glActiveTexture(GL_TEXTURE0) and set the uniform: glUniform1i(samplerLoc, 0).
2. UV Coordinates Outside 0-1 Range
Mistake: UVs outside 0-1 without proper wrap mode set, causing seams or clamping artifacts.
Fix: Set GL_REPEAT for tiling textures or GL_CLAMP_TO_EDGE for single-use textures.
3. Power-of-Two Texture Sizes (Legacy)
Mistake: Using non-power-of-two (NPOT) textures on older hardware that does not support them.
Fix: Modern GPUs support NPOT textures. For maximum compatibility, use power-of-two dimensions (256, 512, 1024, 2048).
4. Forgetting to Generate Mipmaps
Mistake: Setting GL_LINEAR_MIPMAP_LINEAR without calling glGenerateMipmap, causing black textures.
Fix: Always call glGenerateMipmap after uploading texture data if using mipmap filtering.
5. Incorrect Channel Order
Mistake: Loading an RGBA image but specifying GL_RGB in glTexImage2D, causing color shifts.
Fix: Match the internal format and data format: use GL_RGBA for images with alpha channels.
Practice Questions
Question 1
What is the difference between GL_LINEAR and GL_NEAREST filtering?
Show answer
GL_NEAREST picks the closest texel, producing a blocky appearance. GL_LINEAR bilinearly interpolates the four nearest texels, producing smooth but slightly blurry results.Question 2
What problem do mipmaps solve?
Show answer
Mipmaps prevent aliasing (shimmering/scintillating artifacts) when textured surfaces are viewed at a distance. The GPU automatically selects an appropriately sized mip level.Question 3
How do UV coordinates map to texels?
Show answer
UV (0,0) maps to the texel at the bottom-left of the texture image. UV (1,1) maps to the top-right. Values between are linearly mapped to intermediate texels.Question 4
What is anisotropic filtering?
Show answer
Anisotropic filtering improves texture quality when viewed at oblique angles (e.g., looking down a road). It samples more texels along the view direction to reduce blurring.Challenge
Implement a texture baking tool that takes a 3D model and bakes ambient occlusion into a texture map. Use ray casting to compute occlusion per texel and output the result as a PNG image.
FAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Author: DodaTech | Last updated: June 21, 2026
DodaTech tutorials are built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro — security tools used by millions worldwide.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro