scene_lighting - ryzom/ryzomcore GitHub Wiki


title: Scene lighting description: Add dynamic point lights and configure the sun in a scene published: true date: 2026-03-14T00:00:00.000Z tags: editor: markdown dateCreated: 2026-03-14T00:00:00.000Z

In the previous tutorial we loaded a shape into a scene with a simple directional light. Now we'll explore NeL's lighting system in more depth: configuring the scene's sun, creating dynamic point lights that move around the scene, and understanding the difference between driver-level and scene-level lighting.

This tutorial covers:

  • Configuring the scene sun (ambient, diffuse, specular, direction)
  • Creating and positioning UPointLight instances in the scene
  • Setting attenuation and color on point lights
  • The difference between ULight (driver) and UPointLight (scene graph)

Two kinds of lights

NeL has two separate lighting mechanisms:

Driver-level lights (ULight) are set directly on the driver with setLight()/enableLight(). These apply globally when drawing with the driver's immediate-mode functions (drawQuad, etc.) or when no scene is active. They're simple but don't interact with the scene graph's visibility or attenuation systems.

Scene-graph lights are managed by the scene and come in two forms:

  • The sun is a global directional light configured through UScene methods. It affects all objects in the scene.
  • Point lights (UPointLight) are created through UScene::createPointLight(). They have position, color, and attenuation, and NeL automatically determines which objects they affect based on distance.

For scene-based rendering, use the scene sun and UPointLight. Reserve ULight for immediate-mode drawing or cases where you need to bypass the scene.

Configuring the scene sun

The sun is the primary light source and is configured directly on the scene:

	// Enable the lighting system
	m_Scene->enableLightingSystem(true);

	// Global ambient light (affects everything uniformly)
	m_Scene->setAmbientGlobal(NLMISC::CRGBA(40, 40, 50));

	// Sun colors
	m_Scene->setSunAmbient(NLMISC::CRGBA(40, 40, 50));
	m_Scene->setSunDiffuse(NLMISC::CRGBA(255, 255, 240));
	m_Scene->setSunSpecular(NLMISC::CRGBA(255, 255, 255));

	// Sun direction (pointing down and to the side)
	m_Scene->setSunDirection(NLMISC::CVector(1.f, 1.f, -2.f).normed());

The ambient color provides a base level of light everywhere, simulating indirect illumination. The diffuse color is the main directional light. The specular color controls shiny highlights on materials that support it.

You can also limit how many dynamic lights affect each object:

	m_Scene->setMaxLightContribution(3);

Creating a point light

A UPointLight is a scene-graph object with position, color, and attenuation range. It inherits from UTransform, so you position it using setPos():

#include <nel/3d/u_point_light.h>
	NL3D::UPointLight pointLight = m_Scene->createPointLight();

	// Position the light
	pointLight.setPos(NLMISC::CVector(2.f, 0.f, 3.f));

	// Set colors
	pointLight.setAmbient(NLMISC::CRGBA(0, 0, 0));
	pointLight.setDiffuse(NLMISC::CRGBA(255, 100, 50));
	pointLight.setSpecular(NLMISC::CRGBA(255, 200, 150));

	// Attenuation: light fades from full at 'begin' to zero at 'end'
	pointLight.setupAttenuation(1.f, 8.f);

The setupAttenuation parameters define the distance range. Between attenuationBegin and attenuationEnd, the light intensity smoothly falls off to zero. Objects beyond attenuationEnd are not affected.

You can also set all three color channels at once with setColor(), which sets both diffuse and specular to the same value:

	pointLight.setColor(NLMISC::CRGBA(100, 200, 255));

Animating a point light

Since UPointLight inherits from UTransform, you can move it each frame:

	float lightAngle = m_Time * 2.f;
	float lightRadius = 3.f;
	m_PointLight.setPos(NLMISC::CVector(
		cosf(lightAngle) * lightRadius,
		sinf(lightAngle) * lightRadius,
		2.f));

You can also animate the color to create flickering or pulsing effects:

	float pulse = (sinf(m_Time * 5.f) + 1.f) * 0.5f;
	uint8 r = (uint8)(200.f * pulse + 55.f);
	m_PointLight.setDiffuse(NLMISC::CRGBA(r, r / 3, r / 6));

Cleaning up

Delete point lights before deleting the scene:

	m_Scene->deletePointLight(m_PointLight);

Complete source code

This example loads a shape, sets up the sun, and adds two orbiting colored point lights.

#include <nel/misc/types_nl.h>
#include <nel/misc/app_context.h>
#include <nel/misc/event_listener.h>
#include <nel/misc/debug.h>
#include <nel/misc/time_nl.h>
#include <nel/misc/path.h>
#include <nel/3d/u_driver.h>
#include <nel/3d/u_scene.h>
#include <nel/3d/u_camera.h>
#include <nel/3d/u_instance.h>
#include <nel/3d/u_point_light.h>

using namespace std;
using namespace NLMISC;

class CMyGame : public IEventListener
{
public:
	CMyGame();
	~CMyGame();
	void run();
	virtual void operator()(const CEvent &event) NL_OVERRIDE;

private:
	NL3D::UDriver *m_Driver;
	NL3D::UScene *m_Scene;
	NL3D::UInstance m_Entity;
	NL3D::UPointLight m_Light0;
	NL3D::UPointLight m_Light1;
	bool m_CloseWindow;
	double m_LastTime;
	float m_Time;
	float m_CamAngle;
};

CMyGame::CMyGame()
	: m_CloseWindow(false)
	, m_Scene(NULL)
	, m_Time(0.f)
	, m_CamAngle(0.f)
{
	m_Driver = NL3D::UDriver::createDriver(0, NL3D::UDriver::OpenGl3);
	if (!m_Driver) { nlerror("Failed to create driver"); return; }

	m_Driver->EventServer.addListener(EventCloseWindowId, this);
	m_Driver->setDisplay(NL3D::UDriver::CMode(800, 600, 32));
	m_Driver->setWindowTitle("Scene Lighting");

	CPath::addSearchPath("data", true, false);
	CPath::remapExtension("dds", "tga", true);

	// Create scene
	m_Scene = m_Driver->createScene(true);
	if (!m_Scene) { nlerror("Failed to create scene"); return; }

	// Camera
	NL3D::UCamera cam = m_Scene->getCam();
	cam.setTransformMode(NL3D::UTransformable::DirectMatrix);
	cam.setPerspective(float(Pi / 3.0), 800.f / 600.f, 0.1f, 1000.f);
	cam.lookAt(CVector(0.f, -6.f, 3.f), CVector(0.f, 0.f, 0.f));

	// Configure the sun
	m_Scene->enableLightingSystem(true);
	m_Scene->setAmbientGlobal(CRGBA(30, 30, 40));
	m_Scene->setSunAmbient(CRGBA(30, 30, 40));
	m_Scene->setSunDiffuse(CRGBA(140, 140, 160));
	m_Scene->setSunSpecular(CRGBA(200, 200, 200));
	m_Scene->setSunDirection(CVector(1.f, 1.f, -2.f).normed());
	m_Scene->setMaxLightContribution(3);

	// Create two orbiting point lights
	m_Light0 = m_Scene->createPointLight();
	m_Light0.setAmbient(CRGBA(0, 0, 0));
	m_Light0.setDiffuse(CRGBA(255, 120, 50));
	m_Light0.setSpecular(CRGBA(255, 200, 150));
	m_Light0.setupAttenuation(1.f, 8.f);
	m_Light0.setPos(CVector(3.f, 0.f, 2.f));

	m_Light1 = m_Scene->createPointLight();
	m_Light1.setAmbient(CRGBA(0, 0, 0));
	m_Light1.setDiffuse(CRGBA(50, 120, 255));
	m_Light1.setSpecular(CRGBA(150, 200, 255));
	m_Light1.setupAttenuation(1.f, 8.f);
	m_Light1.setPos(CVector(-3.f, 0.f, 2.f));

	// Load a shape
	m_Entity = m_Scene->createInstance("sphere01.shape");
	if (!m_Entity.empty())
		m_Entity.setPos(CVector(0.f, 0.f, 0.f));

	m_LastTime = CTime::ticksToSecond(CTime::getPerformanceTime());
}

CMyGame::~CMyGame()
{
	if (!m_Entity.empty()) m_Scene->deleteInstance(m_Entity);
	if (!m_Light0.empty()) m_Scene->deletePointLight(m_Light0);
	if (!m_Light1.empty()) m_Scene->deletePointLight(m_Light1);
	if (m_Scene) m_Driver->deleteScene(m_Scene);
	m_Driver->release();
	delete m_Driver;
}

void CMyGame::operator()(const CEvent &event)
{
	if (event == EventCloseWindowId)
		m_CloseWindow = true;
}

void CMyGame::run()
{
	while (m_Driver->isActive() && !m_CloseWindow)
	{
		m_Driver->EventServer.pump();

		double now = CTime::ticksToSecond(CTime::getPerformanceTime());
		float dt = float(now - m_LastTime);
		m_LastTime = now;
		m_Time += dt;
		m_CamAngle += dt * 0.2f;

		// Orbit camera
		CVector eye(cosf(m_CamAngle) * 6.f, sinf(m_CamAngle) * 6.f, 3.f);
		m_Scene->getCam().lookAt(eye, CVector(0.f, 0.f, 0.f));

		// Orbit the point lights in opposite directions
		float r = 3.f;
		m_Light0.setPos(CVector(cosf(m_Time) * r, sinf(m_Time) * r, 2.f));
		m_Light1.setPos(CVector(cosf(m_Time + float(Pi)) * r,
			sinf(m_Time + float(Pi)) * r, 1.5f));

		m_Driver->clearBuffers(CRGBA(10, 10, 15));
		m_Scene->animate(CTime::getLocalTime() / 1000.0);
		m_Scene->render();
		m_Driver->swapBuffers();
	}
}

int main(int argc, char *argv[])
{
	CApplicationContext applicationContext;
	CMyGame myGame;
	myGame.run();
	return EXIT_SUCCESS;
}

To run this, place a shape file (e.g. sphere01.shape from nel/samples/3d/cluster_viewer/shapes/) in a data directory next to the executable.

What's next

In the next tutorial, you will learn how to play animations on a skeleton.

⚠️ **GitHub.com Fallback** ⚠️