Textures

Textures are fundamental components in Raftel Engine that allow you to add visual detail and realism to 3D objects. They provide a way to map 2D images onto 3D surfaces, creating the appearance of complex materials and surfaces without needing to model every detail in geometry.

Key Features

  • Support for common image formats (PNG, JPG, BMP, TGA, etc.)
  • 2D textures for standard surface mapping
  • Cubemap textures for environment mapping and skyboxes
  • Automatic mipmap generation for optimized rendering at different distances
  • Error handling and resource management with shared pointers
  • Built on top of stb_image for versatile image loading

Architecture Overview

The texture system in Raftel Engine consists of two primary classes:

TextureImage Class

The TextureImage class serves as a container for raw image data loaded from files. It stores:

This class is primarily used as an intermediate representation before creating OpenGL textures. It handles the memory management of the loaded image data.

Texture Class

The Texture class wraps the OpenGL texture object, providing:

This class is the primary interface for working with textures in Raftel Engine.

Class Structure

namespace Raftel {
    class TextureImage {
    public:
        unsigned char* data;  // Raw image data
        int width;            // Image width
        int height;           // Image height
        int n_channel;        // Number of channels (3=RGB, 4=RGBA)
        
        // Methods for construction and memory management
        // ...
    };
    
    enum class TextureType {
        TEXTURE_2D,  // Standard 2D texture
        CUBEMAP      // Cube map texture for environment mapping
    };
    
    class Texture {
    public:
        GLuint id;            // OpenGL texture ID
        TextureType type;     // Type of texture (2D or Cubemap)
        
        // Methods for loading, binding, and managing textures
        // ...
        
        static std::shared_ptr loadTexture(const std::string& path);
        static std::shared_ptr loadCubemap(const std::vector& faces);
        
        void bind(int unit) const;
    };
}

Basic Usage

Here are some common examples of how to use the Texture system in Raftel Engine:

Loading a 2D Texture

Basic Texture Loading
#include "raftel/texture.hpp"
#include "raftel/material.hpp"

int main() {
    try {
        // Load a texture from a file
        auto brickTexture = Raftel::Texture::loadTexture("assets/textures/brick.png");
        
        // Create a material and apply the texture
        auto material = std::make_shared();
        material->setAlbedo(brickTexture);
        
        // The texture will be used when rendering objects with this material
        myMesh->setMaterial(material);
        
        // ...
    } catch (const std::exception& e) {
        std::cerr << "Error loading texture: " << e.what() << std::endl;
    }
    
    return 0;
}

Creating a Skybox with Cubemap

Loading and Using Cubemap Textures
#include "raftel/texture.hpp"
#include "raftel/shader.hpp"

void setupSkybox() {
    // Define paths to the six faces of the cubemap
    std::vector skyboxFaces = {
        "assets/skybox/right.jpg",  // +X
        "assets/skybox/left.jpg",   // -X
        "assets/skybox/top.jpg",    // +Y
        "assets/skybox/bottom.jpg", // -Y
        "assets/skybox/front.jpg",  // +Z
        "assets/skybox/back.jpg"    // -Z
    };
    
    // Load the cubemap texture
    auto skyboxTexture = Raftel::Texture::loadCubemap(skyboxFaces);
    
    // Use the cubemap in a shader
    skyboxShader->use();
    skyboxTexture->bind(0);
    skyboxShader->setUniform("skybox", 0);
    
    // Render skybox...
}

TextureImage Class Reference

Attributes

unsigned char* data

A pointer to the raw image data in memory. The format depends on the number of channels (RGB or RGBA).

int width

The width of the image in pixels.

int height

The height of the image in pixels.

int n_channel

The number of color channels in the image (3 for RGB, 4 for RGBA).

Constructor and Destructor

TextureImage(unsigned char* data, int width, int height, int n_channel)

Constructs a TextureImage with the provided image data and dimensions.

Parameters:

  • data - The raw image data
  • width - The width of the image in pixels
  • height - The height of the image in pixels
  • n_channel - The number of color channels (3 for RGB, 4 for RGBA)
~TextureImage()

Destructor that frees the image data using stbi_image_free().

Move Operations

TextureImage(TextureImage&& other) noexcept

Move constructor that transfers ownership of image data from another TextureImage.

Parameters:

  • other - The TextureImage to move from
TextureImage& operator=(TextureImage&& other) noexcept

Move assignment operator that transfers ownership of image data.

Parameters:

  • other - The TextureImage to move from

Returns: A reference to this TextureImage

TextureType Enumeration

enum class TextureType

Defines the different types of textures supported by Raftel Engine.

Values:

  • TEXTURE_2D - Standard 2D texture used for most surface mapping
  • CUBEMAP - Cubemap texture used for skyboxes and environment mapping

Texture Class Reference

Attributes

GLuint id

The OpenGL texture ID assigned to this texture.

TextureType type

The type of texture (TEXTURE_2D or CUBEMAP).

Constructors and Destructor

Texture()

Default constructor that initializes the texture ID to 0 and type to TEXTURE_2D.

Texture(TextureImage image)

Constructor that creates an OpenGL texture from a TextureImage. This generates mipmaps automatically.

Parameters:

  • image - The TextureImage containing the raw image data
Texture(const std::vector<std::string>& faces)

Constructor that creates a cubemap texture from six image files.

Parameters:

  • faces - A vector containing the file paths of the six cubemap faces in order: right (+X), left (-X), top (+Y), bottom (-Y), front (+Z), back (-Z)

Throws: std::runtime_error if any face fails to load or if fewer than 6 faces are provided

~Texture()

Destructor that deletes the OpenGL texture.

Static Factory Methods

static std::shared_ptr<Texture> loadTexture(const std::string& texturePath)

Static factory method to load a 2D texture from a file.

Parameters:

  • texturePath - The file path to the texture image

Returns: A shared pointer to the loaded Texture

Throws: std::runtime_error if the texture fails to load

static std::shared_ptr<Texture> loadCubemap(const std::vector<std::string>& faces)

Static factory method to load a cubemap texture from six image files.

Parameters:

  • faces - A vector containing the file paths of the six cubemap faces

Returns: A shared pointer to the loaded cubemap Texture

Throws: std::runtime_error if any face fails to load or if fewer than 6 faces are provided

static TextureImage LoadTexture(const char* path)

Static method to load raw image data from a file.

Parameters:

  • path - The file path to the image

Returns: A TextureImage containing the loaded image data

Throws: std::runtime_error if the image fails to load

Instance Methods

void bind(int unit) const

Binds this texture to the specified texture unit for use in shaders.

Parameters:

  • unit - The texture unit to bind to (0, 1, 2, etc.)
GLuint getID() const

Gets the OpenGL texture ID.

Returns: The OpenGL texture ID

const std::string& getLastError() const

Gets the last error message from texture operations.

Returns: The last error message

Move Operations

Texture(Texture&& other) noexcept

Move constructor that transfers ownership of the OpenGL texture.

Parameters:

  • other - The Texture to move from
Texture& operator=(Texture&& other) noexcept

Move assignment operator that transfers ownership of the OpenGL texture.

Parameters:

  • other - The Texture to move from

Returns: A reference to this Texture

Supported Image Formats

Raftel Engine uses the stb_image library to load textures, which supports most common image formats:

PNG (.png)

Supports full transparency and lossless compression. Ideal for textures with sharp edges, text, or transparency.

JPEG (.jpg, .jpeg)

Provides smaller file sizes with lossy compression. Good for photo-realistic textures without transparency.

BMP (.bmp)

Uncompressed bitmap format. Simple but produces larger files with no compression artifacts.

TGA (.tga)

Supports alpha channels and is common in game development. Good for textures requiring transparency.

PSD (.psd)

Adobe Photoshop format. Only the flattened image is imported.

HDR (.hdr)

High Dynamic Range format for environment maps and lighting scenarios requiring a wider color range.

Texture Parameters and Settings

When creating textures, Raftel Engine applies default OpenGL texture parameters to control filtering, wrapping, and other behaviors. These defaults are:

Default Texture Parameters
// For 2D textures:
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);

// For cubemaps:
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

Parameter Explanations

Using Textures with Materials and Shaders

Textures are most commonly used as part of material definitions. Here's how to use textures with materials and shaders in Raftel Engine:

Complete Texture Usage Example
#include "raftel/texture.hpp"
#include "raftel/material.hpp"
#include "raftel/shader.hpp"
#include "raftel/mesh.hpp"

void setupTexturedObject() {
    // 1. Load textures
    auto diffuseMap = Raftel::Texture::loadTexture("assets/textures/stone_diffuse.png");
    auto normalMap = Raftel::Texture::loadTexture("assets/textures/stone_normal.png");
    auto roughnessMap = Raftel::Texture::loadTexture("assets/textures/stone_roughness.png");
    
    // 2. Create material with textures
    auto material = std::make_shared();
    material->setAlbedo(diffuseMap);
    material->setNormal(normalMap);
    material->setRoughness(roughnessMap);
    
    // 3. Apply material to mesh
    auto mesh = Raftel::MeshFactory::createCube(1.0f);
    mesh->setMaterial(material);
    
    // 4. Render in your main loop
    auto shader = Raftel::ShaderProgram::Create("shaders/pbr.vert", "shaders/pbr.frag");
    
    // ... in render loop
    shader->use();
    
    // The material system will automatically:
    // - Bind textures to appropriate texture units
    // - Set uniforms in the shader for texture samplers
    // - Set flags for which textures are available
    
    mesh->draw(*shader);
}

// In your fragment shader:
// uniform sampler2D albedoMap;
// uniform sampler2D normalMap;
// uniform sampler2D roughnessMap;
// uniform int hasAlbedoMap;
// uniform int hasNormalMap;
// uniform int hasRoughnessMap;
//
// void main() {
//     vec3 albedo = hasAlbedoMap == 1 ? texture(albedoMap, texCoord).rgb : albedoColor;
//     vec3 normal = hasNormalMap == 1 ? perturbNormal() : normalVector;
//     float roughness = hasRoughnessMap == 1 ? texture(roughnessMap, texCoord).r : roughnessValue;
//     
//     // Continue with PBR calculations...
// }

Creating and Using Cubemaps for Skyboxes

Cubemaps are a special type of texture consisting of six faces arranged in a cube configuration. They are commonly used for skyboxes, environment maps, and reflections. Here's how to create and use a skybox with cubemaps in Raftel Engine:

Complete Skybox Implementation
// 1. Load the cubemap texture
std::vector skyboxFaces = {
    "assets/skybox/right.jpg",  // +X
    "assets/skybox/left.jpg",   // -X
    "assets/skybox/top.jpg",    // +Y
    "assets/skybox/bottom.jpg", // -Y
    "assets/skybox/front.jpg",  // +Z
    "assets/skybox/back.jpg"    // -Z
};
auto skyboxTexture = Raftel::Texture::loadCubemap(skyboxFaces);

//2. Initialize Render System and Set SkyBox
Raftel::RenderSystem::Initialize();
Raftel::RenderSystem::SetSkyboxTexture(skyboxTexture);

// 3. Render in your render loop
void renderSkybox(Camera& camera) {
  [...code]
  Raftel::RenderSystem::UpdateRenderSystem(...);

}
// 4. Shutdown Render System
Raftel::RenderSystem::Shutdown();

Advanced Texture Techniques

Procedural Textures

Instead of loading textures from files, you can generate textures procedurally for effects like noise patterns, gradients, or other algorithmic textures:

Creating a Procedural Checkerboard Texture
// Create a procedural checkerboard texture
std::shared_ptr createCheckerboardTexture(int width = 256, int height = 256) {
    // Create image data
    unsigned char* data = new unsigned char[width * height * 4]; // RGBA
    
    // Generate checkerboard pattern
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int index = (y * width + x) * 4;
            
            // Create a checkerboard pattern
            bool isWhite = ((x / 32) + (y / 32)) % 2 == 0;
            
            data[index + 0] = isWhite ? 255 : 64;     // R
            data[index + 1] = isWhite ? 255 : 64;     // G
            data[index + 2] = isWhite ? 255 : 64;     // B
            data[index + 3] = 255;                    // A (fully opaque)
        }
    }
    
    // Create a TextureImage with our data
    Raftel::TextureImage image(data, width, height, 4);
    
    // Don't delete 'data' - TextureImage takes ownership of it
    
    // Create and return the texture
    return std::make_shared(std::move(image));
}

Terrain Texture Generator Tool

Raftel Engine includes a Python-based utility for generating terrain textures from heightmaps. This tool allows you to quickly create color-mapped textures that represent different terrain features based on elevation data.

Key Features

  • Convert grayscale heightmaps (8bpp) to colored terrain textures
  • Multiple pre-defined color schemes for different biomes
  • Customizable color gradients
  • Support for various terrain types: tropical, desert, volcanic, tundra, and more

Location

The tool is located in the /tools/ directory of the Raftel Engine project.

Usage

To use the terrain texture generator:

  1. Open the generate_terrain_texture.py file
  2. Update the path to your heightmap file:
    heightmap_image = Image.open('PATH_TO_YOUR_HEIGHTMAP.png').convert('L')
  3. Set the output path for the generated texture:
    output_path = 'PATH_TO_OUTPUT/texture_name.png'
  4. Optionally, select or customize a color map (see examples below)
  5. Run the script: python generate_terrain_texture.py
Basic Usage Example
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap

# Load heightmap image
heightmap_image = Image.open('../assets/heightmap/my_terrain.png').convert('L')
heightmap = np.array(heightmap_image)

# Normalize the heightmap array to float values between 0 and 1
heightmap_normalized = heightmap / 255.0

# Select a colormap (tropical terrain in this example)
cmap = LinearSegmentedColormap.from_list(
    "TropicalCmap",
    [
        (0.0, "#0D4F8B"),     # Deep water (dark blue)
        (0.05, "#1A75BB"),    # Shallow water
        (0.1, "#E4D5A3"),     # Beach
        (0.15, "#D6C58A"),    # Sand/earth
        (0.2, "#9CB65A"),     # Coastal vegetation
        (0.3, "#5E9732"),     # Low tropical vegetation
        (0.4, "#3F7521"),     # Medium tropical vegetation
        (0.5, "#2E5C1A"),     # Low jungle
        (0.6, "#1F4213"),     # Dense jungle
        (0.7, "#50624A"),     # Vegetated hills
        (0.8, "#6B7866"),     # Rocky hills
        (0.9, "#878C7E"),     # Low mountains
        (1.0, "#A4A296")      # Mountain peaks
    ]
)

# Apply the colormap to the image
fig, ax = plt.subplots()
ax.imshow(heightmap_normalized, cmap=cmap, interpolation='nearest')
plt.axis('off')  # Disable axes
plt.subplots_adjust(left=0, right=1, top=1, bottom=0)  # Adjust margin to maximize the image on canvas

# Set output path
output_path = '../assets/textures/terrain_texture.png'
plt.savefig(output_path, bbox_inches='tight', pad_inches=0)
plt.show()

Available Color Maps

The tool includes several predefined color maps for different terrain types. You can select one by changing the colormap definition:

Tropical

Green and lush vegetation with blue water and sandy beaches. Ideal for jungle or tropical island terrain.

Coastal Cliffs

Rocky coastal terrain with dramatic cliff formations, featuring earthy tones and blue waters.

Snowy Mountains

Forest transitioning to rocky terrain and snow-capped peaks at higher elevations.

Desert

Arid landscape with sand dunes and rocky outcroppings in warm earth tones.

Volcanic

Dark volcanic rock with flowing lava and burnt earth, using dark grays and bright reds.

Tundra

Cold northern landscape with sparse vegetation and dark rocky peaks.

Example Color Maps
# Coastal Cliffs Colormap
cmap = LinearSegmentedColormap.from_list(
    "CustomCmap",
    [
        (0.0, "#0D4F8B"),     # Deep water (dark blue)
        (0.05, "#D1B36C"),    # Shallow water - last water zone
        (0.1, "#D1B36C"),     # Start of land/coastal sand
        (0.15, "#B39B65"),    # Coastal sand/earth
        (0.25, "#8E7F50"),    # Low land/cliff base
        (0.35, "#6E6A53"),    # Start of cliff rock
        (0.45, "#5D5D54"),    # Main cliff rock
        (0.6, "#7A7A74"),     # Exposed rock/mid cliff zone
        (0.75, "#9A9A94"),    # Upper cliff rock
        (0.85, "#BAB6A7"),    # Cliff top
        (0.95, "#D5D1C2"),    # Highest areas
        (1.0, "#E9E6DA")      # Highest rocky peaks
    ]
)

# Snowy Mountains Colormap
cmap = LinearSegmentedColormap.from_list(
    "MontaƱanevadaCmap",
    [
        (0.0, "#0D4F8B"),     # Deep water (dark blue)
        (0.05, "#1A75BB"),    # Shallow water
        (0.1, "#3A623F"),     # Low vegetation/forest
        (0.2, "#5A7E50"),     # Dense forest
        (0.3, "#76876A"),     # Forest limit
        (0.4, "#938B74"),     # Transition zone
        (0.5, "#A99D88"),     # Low rock
        (0.6, "#BEB8AA"),     # Exposed rock
        (0.7, "#D1CDC6"),     # Transition to snow
        (0.8, "#E3E2DF"),     # Partial snow
        (0.9, "#F0F0F0"),     # Heavy snow
        (1.0, "#FFFFFF")      # Snowy peaks
    ]
)

# Desert Colormap
cmap = LinearSegmentedColormap.from_list(
    "DesiertoCmap",
    [
        (0.0, "#0D4F8B"),     # Deep water (dark blue)
        (0.05, "#1A75BB"),    # Shallow water
        (0.1, "#E7C99F"),     # Wet sand/shore
        (0.2, "#E6C188"),     # Golden sand
        (0.3, "#D9B678"),     # Low dunes
        (0.4, "#CCAA6A"),     # Medium dunes
        (0.5, "#BF9958"),     # High dunes
        (0.6, "#AD8242"),     # Low rocky terrain
        (0.7, "#96703C"),     # Rocky terrain
        (0.8, "#7C5A32"),     # High rocks
        (0.9, "#634628"),     # Plateaus
        (1.0, "#4A321E")      # Rocky peaks
    ]
)

# Volcanic Colormap
cmap = LinearSegmentedColormap.from_list(
    "VolcƔnicoCmap",
    [
        (0.0, "#0D4F8B"),     # Deep water (dark blue)
        (0.05, "#1A75BB"),    # Shallow water
        (0.1, "#2E2E2E"),     # Black volcanic rock
        (0.2, "#3A3A3A"),     # Volcanic rock
        (0.3, "#474747"),     # Low rocks
        (0.4, "#5A5A5A"),     # Volcanic slope
        (0.5, "#707070"),     # Mid volcanic rock
        (0.6, "#8A6F55"),     # Transition to burnt earth
        (0.7, "#A85A3A"),     # Burnt earth
        (0.8, "#C14B28"),     # Hot zone
        (0.9, "#E33E14"),     # Surface magma
        (1.0, "#FF3300")      # Crater/lava
    ]
)

# Tundra Colormap
cmap = LinearSegmentedColormap.from_list(
    "TundraCmap",
    [
        (0.0, "#0D4F8B"),     # Deep water (dark blue)
        (0.05, "#1A75BB"),    # Shallow water
        (0.1, "#C9D2D0"),     # Ice/melting snow
        (0.2, "#B8C3C1"),     # Ice-land transition
        (0.3, "#9BA7A0"),     # Rocky tundra
        (0.4, "#7C8980"),     # Tundra with moss
        (0.5, "#5E6C62"),     # Low tundra vegetation
        (0.6, "#485548"),     # Resistant vegetation
        (0.7, "#38473D"),     # Tundra hills
        (0.8, "#2C3C34"),     # Low mountains
        (0.9, "#212F29"),     # Medium mountains
        (1.0, "#19231F")      # Dark mountain peaks
    ]
)

Customizing Color Maps

You can create your own custom color maps by defining a list of color stops. Each stop consists of:

This allows you to create terrain textures for any biome or artistic style you need for your game or visualization.

Example Results

Here's an example of a heightmap and the resulting terrain texture generated with the tool:

Input: Heightmap

Grayscale heightmap image (8-bit).

Example heightmap image

Output: Generated Texture

The resulting colored terrain texture.

Example generated terrain texture

Running the Tool

After configuring the heightmap path, output path, and selecting/customizing your color map, run the tool using Python:

Command Line Execution
python generate_terrain_texture.py

If the script executes successfully, you'll see the generated texture displayed in a matplotlib window, and the texture file will be saved to your specified output path.

Related Topics

Materials

Learn about the material system that uses textures to define surface properties.

Shaders

Understand how to use textures in GLSL shader programs.

Loading 3D Meshes

Tutorial on loading meshes with UV coordinates for texturing.

Render System

Advanced techniques using textures for screen-space effects.