Dev Log 14: The Point of Lighting

January 27th, 2026

The Point of Lighting

This dev log covers some updates to lighting in Retro Game Engine. At the start of this dev log, I currently have just directional lighting and an ambient/fill lighting and they work well together, but they are certainly lacking.

Everything read, but it was flat. Objects felt floaty. Scenes didn’t have local focus. Nothing in the world was helping me build emotion or art direction. And I really needed to prove to myself that something with story could be contained in this thing I was building.

I didn’t want a giant lighting system. I wanted the minimum set of tools that makes a scene feel alive and grounded without turning Retro Game Engine into a general-purpose engine. Something that would let me add life to the world, without tanking performance at the same time.

Here are a couple examples of where things were when I started this. Directional lighting falls apart indoors as nothing occludes it, so ambient lighting is used to fill it out. I can alter color/intensity/etc, but its all applied uniformly to the scene and comes across as flat.

Flat light lighting example 1 Flat light lighting example 2

Constraints Before Features

This engine has a job. It’s meant to make a certain kind of game well, and do it in a way that respects the style of 90s hardware without being stuck in the past.

That means being careful about “standard engine features” that come with a lot of baggage. Lighting is one of those areas where it’s easy to accidentally sign up for a whole second engine.

So I went into this with a pretty strict set of constraints:

One practical benefit of these constraints is that the entire lighting system lives in a single render pass. There’s no G-buffer to manage, no lighting accumulation pass, and no synchronization headaches between stages.

That keeps GPU memory pressure low, makes debugging straightforward, and ensures lighting behaves consistently with the CRT post-process that comes afterward.

The goal is a happy middle ground: sell the effect, keep it predictable, and keep it cheap. A lot of this is about getting 80% of the value without paying for 300% of the complexity.

That’s also why the system is comfortable being a little selective. Lights and shadow-like effects can be prioritized near the camera, because that’s where the player will actually notice them.

Sidebar: Why Not Shadow Maps?

Shadow maps are one of those features that sounds simple on the surface. But my fear was I'd end up dealing with resolution tradeoffs, filtering, shimmering, bias issues, and a bunch of extra pipeline surfaces and passes.

That work can be worth it. It’s just not the direction I want Retro Game Engine to drift right now. A lot of that complexity doesn’t help the CRT look. It mostly helps realism. And above all else, this is not an engine aiming to replicate photorealism. Its about soul and artistic style, so I can take a different route.

Considering all this, the approach I'll follow: keep lighting immediate, local, and controllable. If I need something heavier later, I’d rather arrive there deliberately instead of by accident.

Entity-Driven Point Lights

The first real upgrade was point lights. Not as a global “lighting system,” but as scene entities.

That might sound like a small framing detail, but it matters. When lights are entities, they serialize. They’re editable like anything else. They live in the same mental model as meshes, sprites, cameras, and materials.

And more importantly: they let scenes have local contrast. It’s the first step toward building mood instead of just visibility.

Under the hood, the lighting model is intentionally simple. It’s classic Lambert diffuse with a small Blinn-Phong specular term, evaluated per pixel. No BRDF stacks, no energy conservation gymnastics. The goal is stability and readability, not physical correctness.

All point lights are gathered on the CPU each frame, packed into a fixed-size light buffer, and evaluated in a single forward pass. If the scene exceeds the light budget, extra lights simply don’t participate. That tradeoff is explicit and predictable.

First point light setup

I’m intentionally keeping the controls practical. The point is to place a light, tune its radius and intensity, set its color, and immediately see the scene respond. Each point light in the scene can be controlled like this:

First point light setup

It should be predictable enough that debugging isn’t a nightmare. Here is the first test adding point lights to the environment I showed earlier.

Scenes Were Still Floaty

Point lights helped a lot, but the next problem was obvious right away. Even with better shading, objects still felt like they were hovering.

Realistic shadows would solve that, but realistic shadows also come with a cost I don’t want to pay yet. I needed something that fit the engine’s constraints, fit the retro vibe, and still added a meaningful lift.

So the next step was a very deliberate, very retro technique: blob shadows.

Blob Shadows as a Stepping Stone

Blob shadows don’t try to be physically accurate. They’re about contact. They exist to glue objects to the world so the brain stops questioning everything.

The implementation is simple on purpose: a textured-free quad projected onto a ground plane, with a radial falloff in the pixel shader. The softness and opacity are tunable, and the fade is based on distance from the ground.

The pixel shader never samples a texture, it just computes distance from the quad center and shapes the alpha with a power curve. That keeps the cost flat regardless of shadow size.

Each entity has controls on it for casting a shadow and how it looks, giving me control over the appearance and performance at the same time.

Blob shadow grounding a crate on the ground plane

There are also scene-level controls to affect all entities in the scene that are simulating shadows. These let me fine-tune the overall result with ease.

Blob shadow grounding a crate on the ground plane

They’re entity-driven, cheap, and predictable. That makes them a good fit for this engine, and also a good stepping stone for whatever comes later. Here are a couple examples showing them in action.

Blob shadow grounding a crate on the ground plane Blob shadow grounding a crate on the ground plane

Render order matters here: opaque meshes render first with depth writes, blob shadows render next with depth testing but no depth writes, and transparent sprites come last. That ordering keeps contact shadows grounded without interfering with scene geometry.

The Debugging Part (Because Of Course)

Getting blob shadows in wasn’t just “add quad, ship it.” There were a few gotchas that only show up once you’re trying to render more than one.

The short version: if you’re updating a constant buffer per draw, you need to treat it like per-draw data. Mapping the same upload buffer and overwriting the same memory every iteration means every draw ends up reading the last values you wrote.

This is one of those Direct3D 12 rules that doesn’t hurt you until it really hurts you: constant buffers are just memory, and the GPU will happily read whatever happens to be there when the draw executes.

Treating shadow data as true per-draw state ( with 256-byte aligned slices and explicit offsets) fixed the issue immediately and made the behavior obvious again. This ensure each draw has its own slice.

Blob shadow grounding a crate on the ground plane

After that, multiple blob shadows render correctly, even when objects are close together and lit by the same point light.

Point Lights + Blob Shadows Together

This is the part I cared about. Point lights give me local contrast. Blob shadows give me grounding. Together they push scenes into something that feels intentional.

It’s also the first time the engine starts supporting story and mood instead of just rendering objects correctly. And it does it in a way that still feels retro appropriate: cheap, readable, and focused on the player’s experience.

Manual Ground Offsets (For Now)

The main “complaint” right now is that blob shadows need a ground offset tweak per object. It’s not hard, but it’s manual, and it’s the kind of thing that adds friction when you’re building scenes fast.

There are a few paths forward. The most obvious one is doing a raycast/trace to find the ground and auto-place the blob shadow. But that assumes a collision representation that’s reliable enough to trust, and I don’t want to bolt on a half-baked collision story just to avoid a slider.

So the current approach is intentional: keep it simple, keep it predictable, and treat this as a stepping stone. When collision and query systems mature, auto-placement becomes a clean upgrade instead of a hack.

What This Enables

Lighting is one of the main ways a scene communicates. Without it, everything feels like a debug view. With it, composition starts to matter.

The bigger win here isn’t “now Retro Game Engine has point lights.” It’s that the renderer finally supports building scenes with intent. Local light makes it easier to guide the eye. Ground contact makes it easier to believe what you’re looking at. And the CRT pass finally has something worth chewing on.

This is still work in progress. But it’s the kind of work that changes what the engine is usable for day to day.

Next

I don’t have a clean “next feature” picked out yet. The honest answer is that I want to build more scenes now that lighting isn’t fighting me.

That’s the real test anyway. If the tools hold up under real use, the next step will reveal itself naturally while I'm off exploring and building. See you next time.