PixelShift Studios Logo PixelShift Studios Contact Us
Contact Us

Creating Dynamic Lighting Effects with Shaders

Learn how to write shaders that respond to light sources in real-time. Includes examples of point lights, directional lights, and reflections.

Marcus Thorne, Senior GPU Programming Instructor

Author

Marcus Thorne

Senior GPU Programming Instructor & Technical Director

Why Lighting Matters in Real-Time Graphics

Dynamic lighting transforms a flat scene into something alive. When light responds to movement, changes position, or shifts intensity based on game events, it creates immersion that static lighting can’t match. You’ll notice this instantly in modern games — the way shadows follow characters, how torchlight flickers against walls, or how explosions briefly illuminate entire environments.

Building this effect requires understanding how shaders calculate light interaction. It’s not complicated once you break it down. We’re going to explore the core techniques, write actual code examples, and show you how to optimize for performance. By the end, you’ll understand point lights, directional lights, and how to blend them for convincing results.

Colorful gradient and light effects rendered in real-time, representing shader output visualization

Understanding Light Vectors

Every light calculation starts with direction. The vector from a surface point to the light source tells your shader everything about the angle of incidence. This single piece of information — combined with the surface normal — determines how bright that pixel should be.

For point lights, you’re calculating this vector every frame for every pixel. That’s computationally expensive if done naively. The trick is normalizing vectors and using dot products efficiently. A dot product between the light vector and surface normal gives you a value between -1 and 1, where 1 means the light hits dead-on and -1 means it’s hitting from behind.

Key insight: The dot product of normalized vectors is your primary lighting calculation. It’s fast, elegant, and works for all light types.

Point Lights vs Directional Lights

A point light sits at a specific location in your scene — think of a lamp, campfire, or explosion. Light rays emanate outward in all directions. Your shader needs to calculate the distance from the surface to the light source and apply attenuation. The farther away, the dimmer it gets. This is why a torch looks bright nearby but doesn’t illuminate the entire level.

Directional lights, by contrast, come from infinitely far away — like the sun. There’s no distance calculation because you assume all light rays are parallel. This makes directional lights computationally cheap. Most scenes use one or two directional lights for global illumination and then layer point lights for local effects. That’s your basic strategy right there.

  • Point Light: Position-based, distance matters, attenuation required
  • Directional Light: Direction-based, no distance, constant intensity

Attenuation and Distance

Point light brightness falls off with distance. The most common formula is inverse square law — intensity decreases by the square of the distance. If you double the distance, the light becomes one-quarter as bright. This matches real-world physics and looks natural.

In practice, you’ll calculate distance from the surface to the light, divide the light’s intensity by that distance squared, and clamp the result to prevent negative values. Some developers add a small epsilon value to avoid division by zero when surfaces touch the light source directly. Others use a maximum light radius to prevent distant pixels from being affected.

Performance matters here. You might optimize by using simplified attenuation curves or even pre-calculated lookup tables. We’ve seen real game projects use a combination of squared distance and a custom falloff function that feels right to artists even if it’s not physically accurate.

Specular Highlights and Reflections

Diffuse lighting shows you the basic shape of an object. Specular highlights add polish — that bright spot you see on a wet surface or polished metal. It’s created when light reflects directly toward the camera. Your shader calculates the reflection vector, compares it to the camera direction, and if they’re aligned, you get a bright pixel.

The Phong reflection model is a classic approach. You calculate the reflection of the incoming light across the surface normal, then compare it to the view direction using a dot product. Raise the result to a power (called the shininess or specular exponent) to control how sharp or soft the highlight appears. Higher powers create tight, sharp highlights on shiny surfaces. Lower powers spread the highlight across a wider area on rougher materials.

Modern game engines often use physically-based rendering (PBR) instead, which uses roughness and metallic values to control how light interacts. The math is more complex but produces consistent, realistic results across different lighting conditions.

Combining Multiple Lights

Here’s where it gets interesting. You rarely use just one light. Real scenes need depth. You’ll typically have a directional light for the main fill, then layer point lights for local effects. A torch indoors, an explosion nearby, a neon sign reflecting off wet pavement — each is a separate light contribution.

The approach is straightforward: calculate the contribution from each light independently, then add them together. But this gets expensive fast. If you have 32 point lights in your scene and you’re rendering at 1440p, that’s a lot of calculations. Game engines handle this through techniques like deferred rendering, light culling, or clustered forward rendering. You group nearby lights and only process the relevant ones for each pixel. It’s an optimization challenge that separates polished engines from prototypes.

Practical Implementation

Writing a basic lighting shader takes about 30-40 lines of code. You’ll have a vertex shader that transforms positions and normals, and a fragment shader that does the actual light calculations. Most of your time is spent tweaking values and handling edge cases.

Start with diffuse lighting — that’s your foundation. Get that working first, test with a single point light, then expand to multiple lights. Once diffuse feels right, add specular highlights. Test at different distances, with different surface orientations. Debug by visualizing the normal vectors, light directions, and distance values as colors. This visualization debugging is invaluable when your lighting looks wrong.

Performance profiling matters early. Render with 10 lights, 50 lights, 100 lights. See where your frame rate drops. On mobile, you might be limited to 4-6 dynamic lights. On desktop, you can be more ambitious. Build with your target hardware in mind.

Shader code displayed on a dark monitor, showing GLSL fragment shader with lighting calculations

Educational Note

This article provides educational information about shader programming techniques and real-time lighting implementation. The concepts and code examples are for learning purposes. Your actual implementation will vary depending on your target platform, graphics API (OpenGL, DirectX, Vulkan, Metal), and performance requirements. We recommend testing on your specific hardware and profiling your code to ensure it meets your project’s needs. Always consult your graphics API documentation and engine-specific guidelines when implementing these techniques in production environments.

Building Your Lighting System

Dynamic lighting isn’t a single feature — it’s a system. You’ve got light sources, shader calculations, performance optimization, and artist-friendly controls all working together. Start simple. Get diffuse lighting working with one light source. Test it thoroughly. Then add complexity gradually — specular highlights, multiple lights, attenuation curves, and distance-based optimization.

The shaders themselves are the easy part. The hard work is making it run fast enough, look good enough, and feel responsive to game events. That’s where iteration matters. You’ll write shaders, profile them, rewrite them, test on different devices, and refine until the balance between visual quality and performance is right for your project.

We’ve covered the fundamentals here. From light vectors to attenuation, from diffuse basics to specular highlights, you’ve got the mental model to build convincing lighting effects. The next step is implementation — fire up your engine, write your first shader, and start experimenting. That’s where the real learning happens.

Ready to Dive Deeper?

Explore our complete shader programming curriculum and learn advanced techniques from industry professionals.

Browse All Shader Topics