FIG. 05 / 2026 · FIRST-PERSON HORROR
Castlescape
You wake up inside a castle with a torch in your hand and only one way out. Written from scratch in C++ and Vulkan, with no engine and no renderer library.
- Type
- Horror exploration
- Stack
- C++20 · Vulkan · GLSL
- Team
- Two developers
- Course
- Computer Graphics, PoliMi
The game
Castlescape is a first-person horror-exploration game set in a haunted castle. The place is dark, the corridors are long, and something else lives there.
Every now and then something changes, and the castle knows you are there. Survive it, find your way through, and walk out into the daylight. It was the final project for the Computer Graphics course at Politecnico di Milano, built on the Vulkan starter framework the course provides. Everything above that framework is ours.
The flame
PROCEDURAL FIRE
There is no flame texture in this game, and no animation on it. A flame is three flat cards facing the camera, and a fragment shader that decides, for every pixel, how hot that point is right now. The fire below is that shader, compiled for the browser out of the same GLSL the game runs.
Move the pointer across it: the flame leans away, the way it leans when you walk.
- Three cards
- Co-located quads, separated along the view axis and drawn back to front. Each scrolls its noise at a different speed, so they never line up into one flat plane.
- Domain warp
- A four-octave fBm, sampled at a point that a second, slower fBm has already pushed around. That is what makes the bright regions curl instead of sliding.
- Eighty sparks
- One draw call, no particle system. Each spark's whole life (spawn, rise, drift, stretch, cool, die) is a function of a baked seed and the clock.
- One light
- Everything you can see of the torch is lit by its own flame. Same colour, same brightness envelope, so when the fire gutters the wood goes dark with it.
One flame, five steps
Left to right, the fragment shader stopping earlier or later. Nothing is pre-rendered: all five panels are the same flame at the same moment, cut short at a different line.
- 01ProfileA half-width curve, necked at the wick, tapering to a point.
- 02CarveNoise eats into the silhouette, harder near the tip, where wisps detach.
- 03BubblesA thresholded low-frequency field adds buoyant pockets of hot gas.
- 04RampFour colour stops keyed on heat, not on height: dark red to white core.
- 05HDRThe hot end pushed to six times white, then bloomed and tone mapped.
The ghosts
SPECTRAL
A ghost reflects nothing, is lit by nothing and casts no shadow. It never touches the lighting model the rest of the castle uses: its shader is three ideas and no BRDF at all.
- Fresnel rim
- Density and emission rise where the surface turns away from you. That one term draws the entire outline, and it is what makes a hollow shell read as a volume.
- Object-space noise
- The mottling is sampled in the ghost's own space, so it stays glued to the body as it drifts and turns instead of swimming across it.
- An inverted mask
- The texture is a painted face, used the opposite way round: its dark texels force the eyes and mouth solid, then strip them of light.
- Drawn twice
- A depth prepass records the nearest surface, then the colour pass shades only that. Without it a translucent closed mesh blends against itself and you see the far side of the robe through the near one.
The hunt cycle
The castle runs on a clock. It is running right now, on this page, and every flame you have scrolled past is reading from it.
- Calm 45s Ghosts patrol their route and ignore you.
- Warning 3s The telegraph: every flame bleeds violet and pulses.
- Hunt 18s All of them chase. A touch ends the run.
Phase Calm 45.0s
The light drops to 80% during a hunt, not to nothing: you cannot dodge what you cannot see. The switch in the corner of the screen does the same as this button, from anywhere on the page.
The hub
THE CENTRAL ROOM
The room every run passes through, rendered live from the game's own level files. Nothing in it was modelled for this page. It is lit by the four torches on its walls and the candle on the table, and nothing else.
- From the level files
- Every instance whose id starts with
hbinscene.json, with its materials, flames and patrol, baked into one file: 225,000 triangles at 16 bytes a vertex. - Five lights, five shadows
- Each flame is a point light with its own cube of distances to the nearest wall. Nothing that casts a shadow ever moves here, so the cubes are captured once, when the room loads.
- Cut open
- The south wall is sliced away above waist height so the camera can see in, and the cut is filled dark, the way an architect's section drawing shows solid masonry.
- Patrol, chase, return
- The ghost walks its authored route at 1.5 m/s. Start a hunt and put the pointer over the room: it comes for you at 3.4 m/s, finding its way round the table, chairs and barrels, then walks its own trail back and picks the patrol up where it left off.
Under the hood
HOW IT WAS BUILT
Everything is rendered by hand: no game engine, no renderer library, only the Vulkan API and the course framework.
-
01
Cook-Torrance
LightingPhysically based shading with a GGX microfacet model, evaluated per pixel. Every torch and every candle is a real light source putting real radiance into the room around it.
-
02
Cube shadow maps
ShadowsA flame throws light in every direction, so it needs a shadow in every direction. Each one captures six faces on the fly into a cube map array, and the corridor behind you darkens as you carry it.
-
03
HDR + bloom
PostThe scene is rendered in high dynamic range, where fire is allowed to be far brighter than white. A soft-knee bright pass, a separable Gaussian and a tone map turn that back into something a screen can show.
-
04
Procedural fire
ShadersThe flames, their flicker, their sparks and the way they lean when you walk are all computed in the shaders. Nothing about the fire was animated by hand, and nothing about it is a texture.
-
05
Ghosts
GameplayTranslucent, rim-lit silhouettes that patrol a route, break off to chase you during a hunt, and find their own way back to where they left off afterwards.
-
06
A data-driven level
ContentGeometry, materials, lights, flames and gameplay timings all live in data files. The castle can be re-laid and retuned without recompiling a line.
Controls
HOW IT PLAYS
Press any of these and the key lights up. The page is listening, the same way the game is.
Move
- SpaceJump
- CtrlSprint
- EInteract: open a door, pick up an object, light a candle
- GDrop the key you are holding
- RRestart the run
- F11Fullscreen
- EscPause menu
Last input
·
Press a key
Render resolution and anti-aliasing can be changed in the Settings menu, reachable from both the title screen and the pause menu.
The castle
GALLERY




In motion
GAMEPLAY
A walk through the castle, from waking up to the daylight.