Dark Reflection

Project information

Game

Dark Reflection

A game where you have to find the exit of a maze while avoiding enemies. Made with custom 2D CPU raytracing.

Project Overview

I had 6 weeks to make a small 2D ray tracing renderer and 2 weeks to make a small game to demonstrate it. This project taught me a lot about SIMD, optimization structures, and vector math. I implemented point lights, spot lights, mirrors, and rasterization.

The game

I had two weeks to make a small game that demonstrates my ray tracer. I came up with the idea for the game, the objectives, and the level design. I used Dark Echo for inspiration, but instead of sound, I used light.
The player has to escape a dark maze full of hostile creatures that are attracted to light. He can use his flashlight and throw flares to distract them. Throughout the maze there are treasures that give the player points and more flares.

Design of the level

Gameplay example:

The player can throw flares that bounce off walls.

Lights

I started by implementing point lights and spot lights. The spot lights can have soft edges.

I'm calculating the squared distance from the light sources to see if the pixel is in range. In this way I'm avoiding square roots which improves performance.
I'm using dot product to calculate the cone of the spot light. For soft edges, I have 2 cones - inner (normal calculation) and outer (using linear interpolation to blend).

Point lights and spot lights before I implemented occlusion. The spot lights follow the mouse cursor.

Mirrors

Having a mirror essentially doubles the amount of lights in the level. If the light ray hits a mirror, I calculate the hit position on the mirror and the mirrored position of the light source around the mirror (small white circle in the video below) and calculate as if the light was coming from there.

White mirror reflecting light from a spot light. Disabled soft edges for better visualization
Red mirror reflecting light from a spot light

Occluders

My ray tracer supports lines, circles, and more complex objects:

Occluders

Optimization

For optimization I used multithreading with OpenMP, SIMD, and I implemented 3 optimization grids. Also, I used the DDA algorithm to improve performance when searching for occluders.

I implemented 3 optimization grids: lights grid, shadows grid, and occluders grid:

  • The lights grid culls lights that are too far.
  • The shadows grid is used to speed up shadows. If all four corners of a cell are unlit, then we can assume that the pixels inside that cell will be unlit too.
  • The occluders grid is used so that we check for intersections only against relevant occluders.
Lights and shadows grid debug visualization: Point lights (green), Spot lights (red), Mirrors (magenta)
Occluders grid resizes to match the level
Debug DDA (Digital Differential Analyzer) algorithm: Draw circles on the intersections between the line and the cells

Project Features

  • 2D CPU Ray Tracer
  • Rasterization
  • Optimization structures
  • Multithreading
  • SIMD

Technologies Used

Unreal Engine 5 C++ OpenMP SIMD