Lighting and Shadows in Raftel Engine

This tutorial explains how to implement dynamic lighting and shadows in your Raftel Engine application. We'll cover different light types, setting up materials for realistic lighting, and enabling shadow mapping.

Lighting System Overview

Raftel Engine's lighting system is built around these key components:

Lighting and Shadows Preview
Lighting and Shadows Example

Light Types in Raftel Engine

Raftel Engine supports four main types of lights that can be added to your scenes:

Directional Light

Represents distant light sources like the sun that affect all objects in the scene uniformly from a specific direction. Directional lights cast parallel shadows.

Directional Light

Point Light

Emits light in all directions from a specific position, like a light bulb. Intensity diminishes with distance. Point lights cast shadows in all directions.

Point Light

Spot Light

Emits light in a cone shape from a position in a specific direction, like a flashlight. Spot lights have inner and outer cone angles that control falloff.

Spot Light

Ambient Light

Provides a base level of illumination to all objects in the scene regardless of their position or orientation, preventing completely dark areas.

Ambient Light

Creating Lights with ECS

To add lights to your scene, you create entities with LightComponents:

Creating Different Light Types
// Create a spot light
auto spotLightEntity = ecs->CreateEntity();
spotLightEntity.addLightComp(Raftel::LightComponent(
    Raftel::LightComponent::LightType::SPOT,     // Light type
    glm::vec3(1.0f, 0.8f, 0.6f),                 // Warm white color
    1.0f,                                         // Intensity
    1000.0f,                                      // Range
    20.0f,                                        // Inner cone angle
    30.0f,                                        // Outer cone angle
    windowOpt->getScreenSize()                    // Screen size for shadow mapping
));
// Add mesh to visualize the light source
spotLightEntity.addMeshComp(Raftel::MeshFactory::createSphere(2.0f, 20));
spotLightEntity.addRenderComp(true);
// Position and orient the light
spotLightEntity.addTransformComp({ 
    glm::vec3(200.0f, -60.0f, -400.0f),          // Position
    glm::vec3(90.0f, 0.0f, 0.0f),                // Rotation (points downward)
    glm::vec3(10.0f)                             // Scale
});

// Create a point light
auto pointLightEntity = ecs->CreateEntity();
pointLightEntity.addLightComp(Raftel::LightComponent(
    Raftel::LightComponent::LightType::POINT,    // Light type
    glm::vec3(0.2f, 0.8f, 1.0f),                 // Blue-cyan color
    1.0f,                                         // Intensity
    500.0f,                                       // Range
    0.0f,                                         // Not used for point lights
    0.0f,                                         // Not used for point lights
    windowOpt->getScreenSize()
));
pointLightEntity.addMeshComp(Raftel::MeshFactory::createSphere(1.0f, 20));
pointLightEntity.addRenderComp(true);
pointLightEntity.addTransformComp({ 
    glm::vec3(0.0f, 150.0f, -400.0f),            // Position
    glm::vec3(0.0f, 0.0f, 0.0f),                 // Rotation (not relevant for point lights)
    glm::vec3(10.0f)                             // Scale
});

// Create a directional light (like the sun)
auto directionalLightEntity = ecs->CreateEntity();
directionalLightEntity.addLightComp(Raftel::LightComponent(
    Raftel::LightComponent::LightType::DIRECTIONAL,  // Light type
    glm::vec3(1.0f, 0.9f, 0.8f),                     // Sunlight color
    0.8f,                                             // Intensity
    2000.0f,                                          // Range
    0.0f,                                             // Not used for directional lights
    0.0f,                                             // Not used for directional lights
    windowOpt->getScreenSize()
));
directionalLightEntity.addMeshComp(Raftel::MeshFactory::createSphere(5.0f, 20));
directionalLightEntity.addRenderComp(true);
directionalLightEntity.addTransformComp({ 
    glm::vec3(500.0f, 300.0f, -200.0f),              // Position
    glm::vec3(-45.0f, 30.0f, 0.0f),                  // Direction (via rotation)
    glm::vec3(1.0f)                                  // Scale
});

Light Visualization

It's often helpful to add a mesh component to light entities to visualize their position in the scene. Use small spheres for point and spot lights, and larger ones for directional lights.

Setting Up Ambient Light

In addition to entity-based lights, Raftel Engine provides a global ambient light that affects the entire scene:

Configuring Ambient Light
// Enable ambient lighting
Raftel::Engine::Instance().has_ambient_light = true;

// Set ambient light color (low intensity for night scenes)
Raftel::Engine::Instance().ambient_light = glm::vec3(0.1f, 0.05f, 0.05f);

// Or for daylight scenes
// Raftel::Engine::Instance().ambient_light = glm::vec3(0.3f, 0.3f, 0.35f);

Ambient light is essential for preventing completely dark areas in your scene where direct light doesn't reach. Keep ambient light subtle to maintain the dramatic effect of your directional, point, and spot lights.

Material Properties for Lighting

To make objects respond realistically to light, you need to configure their material properties:

Setting Up Materials for Lighting
// Create a material with lighting properties
auto sphereMesh = Raftel::MeshFactory::createSphere(4.0f, 20);

// Set basic material properties
sphereMesh->GetMaterialByIndex(0)->setAlbedoColor(glm::vec3(1.0f, 0.1f, 0.1f));  // Base color
sphereMesh->GetMaterialByIndex(0)->setShininess(100.0f);                         // Specular shininess
sphereMesh->GetMaterialByIndex(0)->setRoughness(0.2f);                           // Surface roughness
sphereMesh->GetMaterialByIndex(0)->setMetallic(1.0f);                            // Metallic factor
sphereMesh->GetMaterialByIndex(0)->setFresnel(5.0f);                             // Fresnel effect strength

// Add PBR textures for more realistic lighting
// Albedo/diffuse texture
auto gold_a = Raftel::Texture::loadTexture("../assets/textures/gold/Metal048C_2K-JPG_Color.jpg");
sphereMesh->GetMaterialByIndex(0)->setAlbedo(gold_a);

// Normal map for surface detail
auto gold_n = Raftel::Texture::loadTexture("../assets/textures/gold/Metal048C_2K-JPG_NormalGL.jpg");
sphereMesh->GetMaterialByIndex(0)->setNormal(gold_n);

// Roughness map for varying surface smoothness
auto gold_r = Raftel::Texture::loadTexture("../assets/textures/gold/Metal048C_2K-JPG_Roughness.jpg");
sphereMesh->GetMaterialByIndex(0)->setRoughness(gold_r);

// Metallic map for varying reflectivity
auto gold_m = Raftel::Texture::loadTexture("../assets/textures/gold/Metal048C_2K-JPG_Metalness.jpg");
sphereMesh->GetMaterialByIndex(0)->setMetallic(gold_m);

Albedo/Diffuse

The base color of the material. This is the color that the material reflects when light hits it.

Roughness

Controls how rough or smooth a surface appears. Rough surfaces scatter light in many directions (diffuse), while smooth surfaces create sharp reflections.

Metallic

Determines whether the material behaves like a metal or a non-metal (dielectric). Metals absorb light and produce colored reflections.

Normal Map

Adds surface detail without increasing mesh complexity. Normal maps affect how light interacts with the surface on a per-pixel basis.

Enabling Shadows

Raftel Engine includes shadow mapping capabilities to create realistic shadows in your scene. Enabling shadows is simple:

Enabling Shadow Mapping
// In your main rendering loop, pass 'true' as the last parameter to enable shadows
Raftel::RenderSystem::UpdateRenderSystem(*ecs, camera, windowOpt->getScreenSize(), true);

Performance Consideration

Shadow mapping requires additional rendering passes and can impact performance, especially with multiple lights. If you're experiencing performance issues, you can disable shadows by passing false to the RenderSystem update function.

When shadows are enabled, each light that can cast shadows (directional, spot, and point lights) will render the scene from its perspective to generate shadow maps. These shadow maps are then used during the main rendering pass to determine which areas are in shadow.

Adding a Skybox

A skybox adds environmental context to your scene and can contribute to the lighting through reflections. Raftel Engine makes it easy to add a skybox:

Setting Up a Skybox
// Load cubemap textures for the skybox
std::shared_ptr skyboxtexture = Raftel::Texture::loadCubemap({
    "../assets/textures/skybox/px.png",  // Positive X
    "../assets/textures/skybox/nx.png",  // Negative X
    "../assets/textures/skybox/py.png",  // Positive Y
    "../assets/textures/skybox/ny.png",  // Negative Y
    "../assets/textures/skybox/pz.png",  // Positive Z
    "../assets/textures/skybox/nz.png"   // Negative Z
});

// Initialize the render system
Raftel::RenderSystem::Initialize();

// Set the skybox texture
Raftel::RenderSystem::SetSkyboxTexture(skyboxtexture);

A skybox creates the illusion of a distant environment surrounding your scene. It should be loaded after initializing the RenderSystem but before entering the main rendering loop.

Advanced Lighting Features

Shadow Quality

Shadow quality is determined by the resolution of the shadow maps and the filtering technique used. The ShadowMap class in Raftel Engine handles these details automatically.

Light Attenuation

The intensity of point and spot lights diminishes with distance. This attenuation is controlled by the range parameter in the LightComponent constructor.

Dynamic Lights

Lights can be moved and modified during runtime. Update the transform component of light entities to change their position and orientation.

PBR Lighting

Raftel Engine uses physically-based rendering (PBR) techniques for realistic light interaction with surfaces based on real-world physical properties.

Complete Lighting Example

Here's a complete example that demonstrates setting up various lights with shadows in a scene:

Comprehensive Lighting Example
#include "raftel/window.hpp"
#include "raftel/mesh.hpp"
#include "raftel/texture.hpp"
#include "raftel/shader.hpp"
#include "raftel/ecs.hpp"
#include "raftel/camera.hpp"
#include "raftel/imguiRenderer.hpp"
#include "raftel/systems.hpp"
#include "raftel/engine.hpp"

int main() {
    // Initialize window and systems
    auto windowSystemOpt = Raftel::WindowSystem::make();
    auto windowOpt = Raftel::Window::make("Lighting Demo", *windowSystemOpt);
    
    if (!windowOpt) {
        std::cerr << "Error creating window.\n";
        return -1;
    }
    
    windowOpt->MakeContextCurrent();
    
    // Create camera and ECS
    Raftel::Camera cam(windowOpt.get());
    auto ecs = std::make_unique();
    
    // Load terrain mesh and texture
    auto terrainMesh = Raftel::MeshFactory::createTerrain(
        "../assets/heightmap/heightMap_test3.png", 1.0f, 200.0f, true);
    auto terrainTexture = Raftel::Texture::loadTexture("../assets/textures/ground.png");
    terrainMesh->GetMaterialByIndex(0)->setAlbedo(terrainTexture);
    terrainMesh->setupMesh();
    
    // Create terrain entities
    glm::vec3 terrainPositions[3] = {
        {-510.0f, -200.0f, -400.0f},
        {   0.0f, -200.0f, -400.0f},
        { 510.0f, -200.0f, -400.0f}
    };
    
    for (int i = 0; i < 3; i++) {
        auto terrain = ecs->CreateEntity();
        terrain.addMeshComp(terrainMesh);
        terrain.addRenderComp(true);
        terrain.addTransformComp(Raftel::TransformComponent{
            terrainPositions[i],
            glm::vec3(0.0f),
            glm::vec3(1.0f)
        });
    }
    
    // Create a sphere with PBR material
    auto sphere = ecs->CreateEntity();
    auto sphereMesh = Raftel::MeshFactory::createSphere(4.0f, 20);
    
    // Apply PBR material
    auto gold_a = Raftel::Texture::loadTexture("../assets/textures/gold/Metal048C_2K-JPG_Color.jpg");
    auto gold_n = Raftel::Texture::loadTexture("../assets/textures/gold/Metal048C_2K-JPG_NormalGL.jpg");
    auto gold_r = Raftel::Texture::loadTexture("../assets/textures/gold/Metal048C_2K-JPG_Roughness.jpg");
    auto gold_m = Raftel::Texture::loadTexture("../assets/textures/gold/Metal048C_2K-JPG_Metalness.jpg");
    
    sphereMesh->GetMaterialByIndex(0)->setAlbedo(gold_a);
    sphereMesh->GetMaterialByIndex(0)->setNormal(gold_n);
    sphereMesh->GetMaterialByIndex(0)->setRoughness(gold_r);
    sphereMesh->GetMaterialByIndex(0)->setMetallic(gold_m);
    sphereMesh->GetMaterialByIndex(0)->setShininess(100.0f);
    sphereMesh->GetMaterialByIndex(0)->setFresnel(5.0f);
    
    sphere.addMeshComp(sphereMesh);
    sphere.addRenderComp(true);
    sphere.addTransformComp(Raftel::TransformComponent{
        glm::vec3(0.0f, -150.0f, -400.0f),
        glm::vec3(0.0f),
        glm::vec3(1.0f)
    });
    
    // Create spotlight
    auto spotLightEntity = ecs->CreateEntity();
    spotLightEntity.addLightComp(Raftel::LightComponent(
        Raftel::LightComponent::LightType::SPOT,
        glm::vec3(1.0f, 0.8f, 0.6f),          // Warm light
        1.0f, 1000.0f, 20.0f, 30.0f,
        windowOpt->getScreenSize()));
    spotLightEntity.addMeshComp(Raftel::MeshFactory::createSphere(2.0f, 20));
    spotLightEntity.addRenderComp(true);
    spotLightEntity.addTransformComp({ 
        glm::vec3(-100.0f, -60.0f, -400.0f), 
        glm::vec3(90.0f, 0.0f, 0.0f), 
        glm::vec3(5.0f) 
    });
    
    // Create point light
    auto pointLightEntity = ecs->CreateEntity();
    pointLightEntity.addLightComp(Raftel::LightComponent(
        Raftel::LightComponent::LightType::POINT,
        glm::vec3(0.2f, 0.8f, 1.0f),           // Cool blue light
        1.0f, 500.0f, 0.0f, 0.0f,
        windowOpt->getScreenSize()));
    pointLightEntity.addMeshComp(Raftel::MeshFactory::createSphere(1.0f, 20));
    pointLightEntity.addRenderComp(true);
    pointLightEntity.addTransformComp({ 
        glm::vec3(100.0f, -150.0f, -350.0f), 
        glm::vec3(0.0f), 
        glm::vec3(5.0f) 
    });
    
    // Set ambient light
    Raftel::Engine::Instance().has_ambient_light = true;
    Raftel::Engine::Instance().ambient_light = glm::vec3(0.1f, 0.05f, 0.05f);
    
    // Load skybox
    std::shared_ptr skyboxtexture = Raftel::Texture::loadCubemap({
        "../assets/textures/skybox/px.png",
        "../assets/textures/skybox/nx.png",
        "../assets/textures/skybox/py.png",
        "../assets/textures/skybox/ny.png",
        "../assets/textures/skybox/pz.png",
        "../assets/textures/skybox/nz.png"
    });
    
    // Initialize render system and set skybox
    Raftel::RenderSystem::Initialize();
    Raftel::RenderSystem::SetSkyboxTexture(skyboxtexture);
    
    // Set up ImGui for editor
    Raftel::imguiRenderer ImguiWindow(windowOpt->window_);
    Raftel::Editor editor;
    
    // Main render loop
    float time = 0.0f;
    while (!windowOpt->ShouldClose()) {
        windowOpt->input->updateKeys();
        windowOpt->clear();
        
        // Update time for animations
        time += 0.01f;
        
        // Move point light in a circular pattern
        if (auto transform = pointLightEntity.getTransformComp()) {
            float radius = 150.0f;
            transform->position.x = 100.0f + radius * cos(time);
            transform->position.z = -350.0f + radius * sin(time);
            transform->Update();
        }
        
        // Update camera
        cam.PresetCamera(windowOpt.get());
        cam.Update(windowOpt);
        
        // Render scene with shadows enabled
        Raftel::RenderSystem::UpdateRenderSystem(*ecs, cam, windowOpt->getScreenSize(), true);
        
        // Render ImGui editor
        ImguiWindow.newFrame();
        editor.Show(cam, *ecs);
        ImguiWindow.endFrame();
        
        windowOpt->swapBuffers();
    }
    
    Raftel::RenderSystem::Shutdown();
    return 0;
}

Light Animations

The example above demonstrates how to animate lights by updating their transform components in the render loop. This creates dynamic lighting effects that can enhance the atmosphere of your scene.

Best Practices

Lighting Tips

  • Performance: Shadows are expensive to render. Limit the number of shadow-casting lights in your scene.
  • Light Placement: Strategic light placement is key to good scene illumination. Use fewer well-placed lights rather than many dim lights.
  • Light Colors: Use complementary colors for different lights to create visual interest.
  • PBR Consistency: Keep PBR material parameters physically plausible (roughness between 0-1, etc.).
  • Ambient Light: Keep ambient light subtle (0.1-0.3 intensity) to preserve contrast and shadow definition.
  • Light Range: Set appropriate range values for your lights to control their influence area and performance impact.

Tweaking Lighting in the Editor

Raftel Engine's editor interface provides tools for adjusting lighting parameters in real-time:

Light Properties

The editor allows you to modify light color, intensity, range, and cone angles for spot lights directly in the inspector panel.

Material Adjustments

You can fine-tune material properties like roughness, metallic, and fresnel effects to achieve the desired surface appearance.

Transform Controls

Precise positioning and rotation of lights is crucial for good lighting. Use the transform controls to adjust light placement.

Ambient Light Settings

The editor provides access to global ambient light settings, allowing you to adjust the overall scene illumination.

Troubleshooting Common Lighting Issues

Shadow Acne

Problem: Moire-like patterns on surfaces receiving shadows.
Solution: Increase the shadow bias value or adjust the light's near plane distance.

Light Bleeding

Problem: Shadows appear to leak through solid objects.
Solution: Ensure proper shadow map resolution and adjust the light's far plane distance.

Washed-out Appearance

Problem: Scene appears too bright with little contrast.
Solution: Reduce ambient light intensity or adjust individual light intensities.

Performance Issues

Problem: Framerate drops with multiple lights.
Solution: Reduce the number of shadow-casting lights or disable shadows for less important lights.

Conclusion

Lighting is one of the most important aspects of creating visually compelling 3D scenes. Raftel Engine provides a robust lighting system with support for multiple light types, physically-based materials, and realistic shadows. By applying the techniques covered in this tutorial, you can create atmospheric, well-lit environments that enhance your application's visual quality.

For complete details on the lighting system, refer to these API references: