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:
- The
LightComponent
that defines different types of lights and their properties - The
Material
system that controls how surfaces respond to light - Shadow mapping capabilities for realistic shadow rendering
- Integration with the Entity Component System (ECS)

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.

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.

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.

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

Creating Lights with ECS
To add lights to your scene, you create entities with LightComponent
s:
// 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:
// 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:
// 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:
// 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:
// 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:
#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: