Loading and Rendering 3D Meshes

This tutorial will guide you through the process of loading 3D models, applying textures, and rendering them in your Raftel Engine application.

Final Result Preview
Rendered 3D Mesh

Prerequisites

Before proceeding, make sure you:

Including the Required Headers

To work with meshes in Raftel Engine, start by including the necessary headers:

Mesh Headers
#include "raftel/mesh.hpp"

This header provides access to all mesh-related functionality, including loading 3D models, creating primitive shapes, and managing material properties.

Setting Up the Window

First, let's set up a window to render our 3D meshes:

Window Initialization
auto windowSystemOpt = Raftel::WindowSystem::make();
auto windowOpt = Raftel::Window::make("OBJ Loader Test", *windowSystemOpt);
if (!windowOpt) {
  std::cerr << "Error creating window.\n";
  return -1;
}
windowOpt->MakeContextCurrent();

Window Context

The MakeContextCurrent() method must be called before performing any rendering operations. This establishes the OpenGL context for the current thread.

Loading a 3D Model

Raftel Engine supports loading various 3D model formats. Let's load a model from an OBJ file:

Loading a Mesh
Raftel::Mesh myModel;
if (!myModel.loadMesh("../assets/obj/luffytrial.obj")) {
  std::cerr << "Error loading the initial mesh.\n";
  return -1;
}

Supported Formats

Raftel Engine currently supports the following 3D model formats:

  • OBJ (.obj) - Wavefront Object format
  • FBX (.fbx) - Autodesk FBX format
  • STL (.stl) - StereoLithography o Standard Tessellation Language
  • DAE (.dae) - Digital Asset Exchange
  • GLB (.glb) - Binary version of GLTF
  • GLTF (.gltf) - GL Transmission Format

Make sure your 3D models are in one of these supported formats.

Working with Materials (MTL Files)

Raftel Engine now supports Material Template Library (MTL) files commonly used with OBJ models. These files define material properties for your 3D models.

Using MTL Files

When loading an OBJ file that references an MTL file (using the mtllib directive), Raftel Engine will automatically:

  • Load the referenced MTL file from the same directory as the OBJ file
  • Parse material definitions and properties
  • Apply materials to the appropriate submeshes based on usemtl directives

Make sure to keep your MTL files in the same directory as your OBJ files for proper loading.

Loading a Model with Materials
// Load a model with its materials
auto shipModel = Raftel::Mesh::Create("../assets/models/ship-medium.obj");

// The MTL file (ship-medium.mtl) is automatically loaded
// No additional code needed to handle materials!

// You can still override specific material properties if needed
shipModel->GetMaterialByIndex(0)->setRoughness(0.7f);

Material Properties

The engine will parse and apply the following common MTL properties:

  • Kd - Diffuse color (maps to albedo color)
  • Ks - Specular color (influences metallic property)
  • Ns - Specular exponent (maps to shininess and roughness)
  • map_Kd - Diffuse texture map (albedo texture)
  • map_Bump - Normal map texture

Applying Textures

To apply a texture to your loaded model, follow these steps:

Texture Application
auto luffy_t = Raftel::Texture::loadTexture("../assets/textures/luffytextrial.png");
myModel.GetMaterialByIndex(0)->setAlbedo(luffy_t);
myModel.setupMesh();

Materials and Submeshes

A 3D model may contain multiple submeshes, each with its own material. You can access these materials using their index. For a model with a single mesh, you can access its material using index 0.

After modifying any material properties, always call setupMesh() to apply the changes.

Setting Up Shaders

Shaders are essential for rendering meshes with the desired visual effects. Let's set up a basic shader program:

Shader Setup
Raftel::ShaderProgram shaderProgram;
if (!shaderProgram.load("../assets/shaders/test.vs", "../assets/shaders/test.fs")) {
  std::cerr << "Error loading shaders.\n";
  return -1;
}
shaderProgram.use();

The shader program requires both a vertex shader (.vs) and a fragment shader (.fs) file. These files contain GLSL code that defines how vertices are transformed and how fragments (pixels) are colored.

Creating the Render Loop

Now that we have our model, texture, and shaders set up, let's create a render loop to display our 3D mesh:

Render Loop
while (!windowOpt->ShouldClose()) {
  windowOpt->clear();
  myModel.setUniforms(shaderProgram.GetProgramID());
  myModel.draw(shaderProgram);
  windowOpt->swapBuffers();
}

The render loop performs the following steps in each iteration:

  1. Clears the window's color and depth buffers
  2. Sets the uniform variables for the shader program
  3. Draws the model using the active shader program
  4. Swaps the front and back buffers to display the rendered frame

Creating Primitive Meshes

In addition to loading 3D models from files, Raftel Engine provides a MeshFactory to easily create common primitive shapes:

Primitive Creation
// Create various primitive shapes
auto sphere = Raftel::MeshFactory::createSphere(30.0f, 50);
auto cube = Raftel::MeshFactory::createCube(30.0f);
auto plane = Raftel::MeshFactory::createPlane(10.0f, 20.0f);
auto terrain = Raftel::MeshFactory::createTerrain("../assets/heightmap/heightMap_test3.png", 1.0f, 200.0f, true);

Sphere

createSphere(float radius, int segments)
Creates a sphere with the specified radius and resolution (segments).

Cube

createCube(float size)
Creates a cube with equal width, height, and depth dimensions.

Plane

createPlane(float width, float height)
Creates a flat rectangular surface with the specified dimensions.

Terrain

createTerrain(string heightmapPath, float heightScale, float terrainSize, bool smoothing)
Creates a terrain mesh from a heightmap image.

Cone

createCone(float radius, float height, int segments);
Creates a cone mesh with the specific radius for base, height and resolution (segments).

Rendered 3D Mesh

Rendering Multiple Meshes

You can render multiple meshes in the same scene by drawing each one in sequence:

Multi-Mesh Rendering
// Render loop with multiple meshes
while (!windowOpt->ShouldClose()) {
  windowOpt->clear();
  
  // Draw each mesh
  sphere->setUniforms(shaderProgram.GetProgramID());
  sphere->draw(shaderProgram);
  
  cube->setUniforms(shaderProgram.GetProgramID());
  cube->draw(shaderProgram);
  
  plane->setUniforms(shaderProgram.GetProgramID());
  plane->draw(shaderProgram);
  
  terrain->setUniforms(shaderProgram.GetProgramID());
  terrain->draw(shaderProgram);
  
  windowOpt->swapBuffers();
}

Optimizing Multiple Mesh Rendering

For better performance when rendering many meshes of the same type, consider using instanced rendering. This allows you to draw multiple instances of the same mesh with a single draw call.

Check the Instanced Rendering section in the API documentation for more details.

Complete Example

Here's a complete example that demonstrates loading and rendering a 3D mesh with texture:

Complete Mesh Example
#include "raftel/window.hpp"
#include "raftel/mesh.hpp"
#include "raftel/shader.hpp"
#include "raftel/texture.hpp"

int main() {
  // Initialize window system
  auto windowSystemOpt = Raftel::WindowSystem::make();
  auto windowOpt = Raftel::Window::make("Mesh Example", *windowSystemOpt);
  
  if (!windowOpt) {
    std::cerr << "Error creating window.\n";
    return -1;
  }
  
  windowOpt->MakeContextCurrent();
  
  // Load a 3D model
  Raftel::Mesh myModel;
  if (!myModel.loadMesh("../assets/obj/luffytrial.obj")) {
    std::cerr << "Error loading the mesh.\n";
    return -1;
  }
  
  // Apply texture
  auto texture = Raftel::Texture::loadTexture("../assets/textures/luffytextrial.png");
  myModel.GetMaterialByIndex(0)->setAlbedo(texture);
  myModel.setupMesh();
  
  // Setup shaders
  Raftel::ShaderProgram shaderProgram;
  if (!shaderProgram.load("../assets/shaders/test.vs", "../assets/shaders/test.fs")) {
    std::cerr << "Error loading shaders.\n";
    return -1;
  }
  
  // Main render loop
  while (!windowOpt->ShouldClose()) {
    windowOpt->clear();
    shaderProgram.use();
    myModel.setUniforms(shaderProgram.GetProgramID());
    myModel.draw(shaderProgram);
    windowOpt->swapBuffers();
  }
  
  return 0;
}

Next Steps

Now that you've learned to load and render 3D meshes, you might want to explore the following topics:

Implementing Lighting

Learn how to add dynamic lighting to your scenes for more realistic rendering.

Advanced Materials

Discover how to create PBR (Physically Based Rendering) materials for realistic surfaces.

Scene Management

Organize your 3D objects in a scene graph for easier manipulation and rendering.

Animation

Implement skeletal animation to bring your 3D characters to life.

For complete API details, see the Mesh Class Reference in the API documentation.