Skip to content
jlrouzies-frPublic

About

Doom 64 Ray Tracing

Resources

Stars

68 stars

Watchers

3 watching

Forks

Repository files navigation

Doom 64 — Ray Traced

Path-traced Doom 64: Retribution

Real ray tracing on the N64 original — no rasterized fallback, no RTX Remix.
Every light in the game is a real emitter, and every surface answers to it.

Engine: gzdoom-rt Renderer: RTGL1 Denoiser: A-SVGF Upscaler: DLSS 2 or FSR 2
Base: Retribution v1.5 Platform: Windows GPU: hardware ray tracing required

AI-DECLARATION: copilot

Buy Me a Coffee Support me on Ko-fi


⛧  Contents


⛧  Features

The renderer is RTGL1's. What this project adds is everything above it: the game's fake lighting replaced with real emitters, and a set of engine features Doom 64 never had. Every one of them is documented in full elsewhere — this table is the index, not the doc.

Category Feature Summary Doc
Lighting Painted light → real fixtures Nine repair families across the wad — sequence chains, blinks, ACS light calls, painted shafts/tints, sector lamps, panel lamps — replacing baked-in fake glow with real emitters. sequence-light-chains
Wall monitors 48 flicker lights across 8 maps, placed at the emissive mask's lit centroid. AGENTS.md
Inferred fixtures Ceiling insets, wall strips, hanging tech, solo bulbs, spin panels — derived from the texture, not hand-placed. solo-bulb-lamps · faux-lamp-panels
Flame lighting All 84 torch/fire/candle sprites lit engine-side (offset onto the flame, flicker) rather than by texture meta. flame-lighting
World emissives Lava, monitors, keys, EXIT signs, teleporters as masked emitters feeding GI. material-authoring-spec
Atmosphere The moon Sky disc + real directional light, aimed alike; shadow rays must prove they reached sky (rt_sun_require_sky) or it washes sealed rooms. moon CCMD. moon-and-sky-leaks
Clouds & Lightning (rt_clouds_*); tints and attenuates moonlight, flashes with MAP11's storm. thunder CCMD. rt-clouds-and-lightning
Volumetric clouds Raymarched cloud volume in the sky cubemap itself, not a painted shell — real interior density/self-shadowing that lights the level through GI for free (rt_vclouds_*). plan-volumetric-clouds
Fire sky Alternative sky for the five hell maps: dark backdrop, cloud deck, falling meteors, coloured lightning strikes aimed at the player (rt_fireskies_new, rt_firesky_*). plan-fire-skies
Per-map fog Froxel volume, near/far ramp tuned per level (rt_fog_*, fog CCMD). rt-fog · implementation
Volumetric smoke Muzzle/impact smoke as a participating medium inside the fog froxel, colour-lit by the room. Six sources; CPU sim. smoke CCMD. rt-smoke
Light shafts from lamps Ordinary ceiling lamps, bulb lattices and solo bulbs cast shafts too, not just the sun (rt_volume_shafts). plan-light-shafts
Dust motes Hashed-grid quads lit only where a shaft reaches (rt_dust_*). —
Water Stylized surface with projected caustics, tagged engine-side. rt-water
Lava Drifting quantized heat field as the emitter, slow whole-surface breath (rt_lava_flow*). —
Poison bubbles Bubbles swell out of the nukage, burst into a ring of droplets and throw a little green while they do. Sliced from one painted sheet into six frames; colour matched to the rendered pool, since the flat's own albedo is nearly black. They keep off a bridge deck standing over the poison, and switch off under Options › Effects. A row of d64_poison_* knobs (rate, distance, size, height, saturation) and a lab map to tune them in. poison-bubbles
Materials Limited PBR, on purpose 898 wall/flat textures + 132 sprite codes (1,087 frames) hand-classified into roughness/metalness — but both mix dials ship at 0.35, not 1, because full PBR breaks flat-normal sprites, exposes dithered art, and adds noise. rt_sprite_pbr_mix, rt_tex_pbr_mix. plan-sprite-materials
Sprites & gore Enemy eyes Brightmap-only emissive masks — glow without lighting the room or killing the shadow. —
Lost Souls Light rides the fire frames (A–F) only, so a corpse stays dark. —
Persistent blood Splats stay on the floor, explosive kills bleed, per-monster blood colour renders correctly (rt_gore_*). blood-persist
Spectres Rasterized translucent overlay with an alpha floor, not forced water/glass. spectre-issue-log
Impacts & destruction Sparks and debris P_LineAttack hook with true surface normal + hit texture; metal sparks, concrete chips, flesh neither, chips bounce and settle (rt_spark_*). rt-impact-fx
Projectile impacts Plasma, arachnotron bolt and BFG leave a branching electric mark with its own light and colour ramp (rt_arc_*). plan-projectile-impact-fx
Unmaker wall burns Its laser leaves a hot spot, glow and short arc where it lands (rt_laser_*). —
Exploding barrels Comes apart into curved plates that fly, tumble, settle and scorch the floor (rt_barrel_*). —
HUD & presentation Mugshot All 42 frames generated from one painted sheet, restyled to the D64 palette. hud-mugshot
Flashlight Warm beam, battery cycle, HUD meter, angled to catch muzzle smoke (rt_flsh*, F). —
Act title cards Title/logo art per act. act-title-cards
Menu patches Retribution's own font; rt/wad loads last so it doesn't override D64 art. compat-patches
Denoising & upscaling A-SVGF + DLSS 2 / FSR 2 A-SVGF is the shipping denoiser; upscale with DLSS 2 (NVIDIA) or FSR 2 (anywhere) — they share one slot, set only one. Keep rt_normalmap_stren/rt_heightmap_stren near 1. —
NRD (ReLAX) Experimental, off by default — an alternative denoiser lane behind rt_nrd 1 (console only, does not persist). Same downstream frame shape as A-SVGF, but a bit noisier at the default 1 sample/pixel; raise Options → Quality → Direct/Indirect samples per pixel to 2 and it closes the gap. plan-nrd-denoiser
DLSS Ray Reconstruction Alpha, ships OFF, not recommended — wired up for experiments but not stable enough to play with. The reason is upstream of RR itself: it expects a signal close to its training distribution, which means full BRDF/light MIS and real area-light transport — this renderer has neither. Without them RR only looks decent when brute-forced: samples per pixel and ReSTIR light candidates raised well above the shipping defaults, which costs more than it buys at 1 spp. A-SVGF is the intended path. RAYRECONSTRUCTION.md · plan-area-lights-mis
Shadow contact hardening Not implemented — the one place RR visibly beats A-SVGF. A real shadow is sharp where the blocker touches the floor and softens with distance; A-SVGF draws it uniformly soft everywhere. The blocker distance is never recovered from the shadow ray, so nothing downstream can know the difference, and at the default rt_shadow_samples 1 the penumbra is not in the signal at all — every bit of softness on screen is filter blur. RR reconstructs it from a learned prior. plan-shadow-contact-hardening

⛧  Preview

In-engine captures, uncropped, no post-processing beyond what the game itself does.

Volumetric fog over water, torchlit gate
Volumetric fog over water
Caged captive lit by a colored point light
Caged captive, colour bleed
Imp fireball at point-blank range
Point-blank fireball
Green and blue corridor lighting
Coloured corridor lighting
Moonlit hallway with a silhouette ahead
Moonlit hallway
PBR floor tile lit by a bright fixture
PBR tile under a bright fixture
Persistent blood pooling on the floor
Persistent blood
Plasma bolt impact with an electric arc mark
Plasma impact mark
Impact sparks beside a stained-glass window
Impact sparks
Storm clouds, moonlight and a flashlight beam
Storm clouds and flashlight
Outdoor daylight fog with a cacodemon ahead
Outdoor daylight fog

⛧  Known issues

Things that are wrong and known to be wrong.

  • MAP05: when at the outdoor area, the moon can get occluded by an invisible wall. It is like a form of "unculling", no fix found yet.
  • You can notice textures that look too bright / sticking out. It can happen if their emissive is not well updated, those get fixed over time as I do a real play through the whole game
  • Loading a save made on an older version will fail with a clear "save is from an older version" message. This is expected: each update can change map data, which invalidates old saves for that level. Start that level fresh from the console (map mapXX) or load a save from an earlier level instead.
  • If you played Doom 2 RT before, make sure you set rt_fluid false is your GZdoom_RT2.ini (Documents > My Games > GzDoom) or type it in the console

⛧  Install and play

Note

Tested on one machine only. The FSR path has never run on AMD hardware, and DLSS Ray Reconstruction is alpha and ships off. Releases cut from main are full releases; anything tagged off another branch publishes as a (beta) pre-release. Bug reports welcome.

You need a GPU with hardware ray tracing (NVIDIA RTX, AMD RDNA 2+, Intel Arc) and a DOOM II you own. Everything else is free.

Tip

On Linux? A native port is maintained by @acolomba as an AppImage — releases, source on the linux-port branch of their fork. It is a separate project, not built or tested here.

1. Download and extract this

Releases → Doom64-RT.zip (~124 MB). Extract it anywhere — it needs no installer, and the only thing it writes outside its own folder is GZDoom's config, at Documents\My Games\GZDoom\gzdoom-rt2.ini.

2. Get Doom 64: Retribution and its music, and extract both into game\

Download
Doom 64: Retribution v1.5 Extract the whole download into game\, not just the WAD — the brightmaps, the soundfont and the fluidsynth DLLs are all used.
OGG music pack v1.3 D64MUS.ZIP on the same page. Unzip it into game\ too.
Optional — D64ClassicRecolored Classic-hued Cacodemon and Pain Elemental — and, ticked with them, our recoloured fireball. Off by default; drop the wad in Addons\ (and tick it in the startup window on step 4.).

3. Have DOOM II installed

Steam or GOG — the launcher finds either by itself, so usually there is nothing to do here. If your doom2.wad lives somewhere unusual, the startup check has a Browse button. Freedoom Phase 2 works as a free stand-in, untested here.

4. Run launch-doom64-rt.cmd

It checks everything first and tells you what is missing, with a link to each download. Green ticks all the way down, then RIP AND TEAR.

Once every line is green, the window offers Setup is done — go straight to the game next time. Tick it and the check stops appearing: it writes configdone.txt beside the launcher, and from then on a double-click goes straight into Doom 64. The files are still tested on every start, so if one of them later moves or is deleted the window comes back on its own with the reason. To open it deliberately — to point at a different doom2.wad, or to turn either add-on on or off — run launch-doom64-rt.cmd setup, or just delete configdone.txt.

Two things in that window are yours to choose. Unseen Evil monsters is on by default and adds the Chaingunner, Revenant, Arch-Vile and Spider Mastermind — but they replace monsters already placed in each map rather than adding to it, so the kill count is unchanged and a converted Baron is one Baron fewer. Untick it for the stock Retribution roster. The classic recolour is off until you drop its wad in Addons\ and tick it. See the monsters for what is placed where.


⛧  Building it yourself

For working on the project. To just play it, use the release — it is the same thing, already built.

Important

Almost nothing needed to run the game is in git — engine build, RTGL1 build, SDKs, the Python venv, the sourcecode/gzdoom-rt checkout, IWAD, Retribution and music are all gitignored. What is tracked is our RT materials, our tools, our engine patches and the docs.

What you need

GPU Any GPU with Vulkan ray tracing — NVIDIA RTX, AMD RDNA 2 and later, Intel Arc. Path tracing only; there is no rasterized fallback, so hardware RT is not optional. Upscaling is DLSS 2 or FSR 2 (rt_upscale_dlss / rt_upscale_fsr2); DLSS is NVIDIA-only, FSR runs anywhere. Only NVIDIA has been tested here.
OS Windows — the build scripts and the win32 surface path.
Toolchain Visual Studio Build Tools 18 with the x64 native toolset, CMake, Python 3.13 with Pillow. The build scripts call VsDevCmd.bat from the …\Microsoft Visual Studio\18\BuildTools\… path — another edition or version means editing line 3 of both.
IWAD A doom2.wad you own — Retribution is a PWAD and cannot run without one. The Steam or GOG release of DOOM II both work; the launcher looks in the usual install locations and otherwise takes D64RT_IWAD.
Stock engine package A gzdoom-rt 1.0.2 release unpacked to gzdoom-rt-1.0.2\. Not optional: build-gzdoom-rt.cmd stages rt\, libsndfile-1.dll and openal32.dll out of it, and without that rt\ tree the engine has no shaders, no rt\data and no rt\wad.

Game files — download these yourself

None of this is redistributed here. Get it from the mod's own ModDB page and drop it in Doom64-Retribution\:

Download What to do with it
Doom 64: Retribution v1.5 — moddb.com/mods/doom-64-retribution Extract the whole thing, not just the WAD. It carries D64RTR[v1.5].WAD, D64RTR_BRIGHTMAPS.PK3, DOOMSND.SF2 and the two libfluidsynth DLLs — all of them are used, and picking files out one at a time is how you end up missing one.
OGG music pack v1.3 — the addon page Unzip D64MUS.ZIP in the same place. Not every track is in it, which is why the soundfont above still matters: without it the MIDI ones fail with Unable to load : Unable to read header (the title screen and MAP00 show this).

Note

The ModDB download is named D64RTR[v1.5].WAD. This repo also carries a shell-safe D64RTR_v15.WAD copy, and the launcher accepts either — those square brackets are a wildcard to PowerShell's Test-Path, so a naive check reports the file missing while it is sitting right there.

The launcher loads the OGG pack rather than the MIDI + DOOMSND.SF2 route, so the music pack is required, not optional — launch-retribution-rt.cmd passes it on every start. Retribution's own D64RTR_INSTRUCTIONS.TXT §"Music" covers the soundfont alternative if you would rather have the MIDIs.

The IWAD

DOOM II is still sold, so this project does not point anyone at a pirated IWAD. Buy it on Steam or GOG — or use the free Freedoom Phase 2 IWAD if you want a no-purchase route, though nothing here has been tested against it.

The launcher searches the usual Steam and GOG install paths, plus doom2.wad beside the repo. If yours lives elsewhere, point at it rather than editing the script:

$env:D64RT_IWAD = "C:\Path\To\doom2.wad"

Everything else — engine, deps, mod files, tools — now resolves relative to the repo, so the clone can live anywhere.

Dependencies — all under deps\, never Program Files

Important

The engine and the path tracer must be this project's forks, on the doom64-rt branch — not their upstreams. Both carry changes this repo depends on (the RT feature file split, the froxel changes fog and smoke need, the emissive and translation fixes). Upstream will build, then behave wrong in ways nothing reports. The project repo itself is on main; dev is where work lands before it is merged and tagged.

git clone https://github.com/jlrouzies-fr/doom64-rt.git Doom64-RT
cd Doom64-RT
git clone --recurse-submodules -b doom64-rt https://github.com/jlrouzies-fr/gzdoom-rt.git sourcecode\gzdoom-rt
git clone --recurse-submodules -b doom64-rt https://github.com/jlrouzies-fr/RTGL.git      deps\RTGL
git clone https://github.com/NVIDIA/DLSS.git                                              deps\DLSS   # NVIDIA only

Caution

--recurse-submodules is not optional. The engine keeps unordered_dense as a submodule under src\common\rendering\rt\, and RTGL1 keeps six (imgui, glfw, cgltf, glaze, glm, DirectX-Headers). Without them the engine compiles for several minutes and then fails every RT translation unit at once with Cannot open include file: 'unordered_dense/…'. If you already cloned flat: git -C sourcecode\gzdoom-rt submodule update --init --recursive (and the same for deps\RTGL).

Only the DLSS SDK comes from its own upstream — it supplies the NGX snippets, and build-rtgl.cmd currently requires it. On non-NVIDIA hardware that dependency has to come out of the build (-DRG_WITH_NATIVE_DLSS=OFF) and upscaling falls back to FSR2.

Build

.\tools\build-gzdoom-rt.cmd     # vcpkg + ZMusic + gzdoom-rt  -> build\RelWithDebInfo\gzdoom.exe
.\tools\build-rtgl.cmd          # shaders + RTGL1.dll         -> build\RelWithDebInfo\rt\bin\

Both scripts stage their output into sourcecode\gzdoom-rt\build\RelWithDebInfo\, so run the engine build first. Three things they do deliberately, each one paid for:

  • build-rtgl.cmd aborts if a shader fails. GenerateShaders.py exits 0 even when glslangValidator rejects a shader, which would otherwise ship the previous SPIR-V into a playtest.
  • It clears the object files when ShaderCommonC.h changes. The generated uniform struct is not a tracked CMake dependency, so a stale .obj keeps the old sizeof(ShGlobalUniform) and every field past the old size silently reads zero — no crash, no validation error. That cost a full day once.
  • It checks the copy of RTGL1.dll succeeded. The DLL is locked while gzdoom is running, and a silent failure means fresh shaders get tested against the old renderer. Kill gzdoom.exe before building.

First run

There is no first-run ritual any more — build-gzdoom-rt.cmd does it. Just play:

.\tools\launch-retribution-rt.cmd          # MAP01
.\tools\launch-retribution-rt.cmd 13       # any map, 1-34
.\tools\launch-retribution-rt.cmd menu     # boot to the title screen

For the record, these are the three things the build script now does for you at the end of staging, each of which fails silently when skipped, and each of which has to be redone after any clean rebuild because the stock rt\ tree is restaged wholesale:

Step Why it matters
writes rt\RTGL1.json with developerMode: true RTGL1 only ever reads this file (VulkanDevice_Init.cpp:228, .value_or(default)). No file means developerMode: false, every authored PNG material ignored, KTX2 only — and nothing warns you. The game just looks stock. A release additionally gets debugWindows: false: the two used to be one flag, so hiding RTGL1's ImGui window meant giving up the authored materials.
stages Retribution-RT-Materials\rt\ into the engine rt\ The materials themselves.
copies rt-wad-overlay\ into rt\wad rt\wad is appended after every -file PWAD, so without it the stock RT menu art overrides Retribution's.

None of that needs Python. The interpreter is only for the authoring tools — generators, scanners, gallery builders — and for RTGL1's own shader generation when you build it.


⛧  Launchers

Also the developer path — these run the game out of a source checkout, not out of the release.

CommandWhat you get
tools\launch-retribution-rt.cmd [1-34|menu] The game. Native RTGL1 path tracing, A-SVGF denoiser, DLSS or FSR upscaling.
tools\ab.cmd <arm> [map] A/B runner. Arms are config files in tools\arms\*.cfg, never console commands.
tools\launch-enemy-gallery-rt.cmd MAP98 — enemy eye / emissive debug hall.
tools\launch-texture-gallery-rt.cmd MAP99 — texture PBR gallery.
tools\ui-wpf.cmd · tools\ui-classic.cmd Open a startup-check window on its own, without starting the game — the WPF one that ships, or the WinForms fallback.

Note

The launcher pins ~600 cvars via +exec tools\d64rt-pins.cfg rather than on the command line. That is deliberate: cmd.exe truncates at 8191 characters, and the old inline form silently dropped the tail — arms ran on defaults while the tool printed values it never applied.

Note

The startup window is launch-doom64-rt-ui.ps1, WPF over Windows PowerShell 5.1 — chosen over WinForms because WPF scales itself on a 4K display and lets every control be retemplated, down to the checkbox tick and the scrollbar. The WinForms original stays as launch-doom64-rt-ui-classic.ps1: if WPF cannot start it answers exit code 2 and the .cmd runs that one instead, so a machine that cannot draw the window is never a machine that cannot play. What either window finds is in launch-doom64-rt-checks.ps1, shared by both — the two differ only in how they look. D64RT_UI=classic picks the fallback on purpose.


⛧  For developers

Everything in this project is written down — each feature, each fix, and each dead end, usually written the day it cost us something.

→  DEVELOPERS.md — the index of every doc in the repo

Grouped by feature docs, the lighting-repair pipeline, materials and models, tooling, generated inventories, and the historical briefs kept for why rather than how.

Three that matter more than the rest:

AGENTS.md The working handbook — diagnostic procedure for a wrongly-lit surface, the rt/ source map, how to add a cvar, and 36 numbered pitfalls not to repeat.
compat-patches.md Every engine and RTGL1 change we made, dated, with the reason.
docs/open-issues-rt-lighting.md What still fails.


⛧  Art changes

This project is mostly a lighting mod, but it does redraw a handful of textures — every one of them recorded here so none is a quiet change. All ship enabled, in the launcher's own file list, so this is what the game looks like out of the box — except the last one, which rides on the optional recolour add-on and is off until you tick it.

SFLATAS — the ceiling lamp pane

Before After
SFLATAS, four working bulbs SFLATAS, three bulbs smashed
2×2 working bulbs per 64-unit tile — four lights three smashed, one still burning — one light, nothing moved

The glass is repainted; no bulb is moved. That is the whole trick, and §"Why this edit and not another" below is why it matters.

Why. A grating only casts a legible shadow when its light comes from one compact source. Measured in the shadow lab (tools/build_shadow_lab.py, the same fixture alone in a dark room):

lights on the pane result
1 crisp diamond shadows on floor, walls and ceiling
4 shadows, once the intensity beats the bounce fill
16 nothing, at any source radius

The lamp pane places one light per painted bulb, and Doom 64 paints those bulbs 32 map units apart — one metre. The cage grating's openings are about half that. Once the lights are further apart than the occluder's features, each one lays down an offset copy of the mesh shadow that fills in the previous one's gaps, and the pattern cancels. That is not a renderer bug: a real ceiling of metre-spaced bulbs behind a half-metre mesh washes out too. It is why the caged zombie on MAP01 stood in a shadowless glow.

Why this edit and not another. The obvious version — repaint the pane as a single bulb in the middle of the tile — was built first, and it is worse. Moving painted geometry means moving it in the albedo and in the normal, height and roughness maps; then the light has to follow it; then every pane needs a per-sector texture offset so a wall does not slice the one bulb that is left, and the light has to follow that too. Breaking three bulbs changes nothing's position. All four housings still exist, so the authored relief maps are still correct and are left alone, and the flat keeps its original tiling, so no sector needs an offset. It also reads as a place rather than as a texture edit: this is an overrun military installation, and the maintenance stopped.

How it was made. tools/gen_broken_bulb_flat.py. The albedo is hand-painted; the tool works out which bulb is still intact by measuring luminance in the authored bulb windows (157 against 53/64/94 — not a judgement call), keeps that one blob in the _e emissive mask and clears the other three, and restores the authored _n/_h/_orm. The engine's SoloBulbTextures then puts one light on the survivor — at world (16, 16) for a bulb the image draws at (16, 48), because a ceiling flat's world Y is 64 − imageY.

Blood, poison and sludge — new art, and real depth on two of them

Doom 64's three non-water liquids (D64B* blood, D64N* poison, D64S* sludge) are the same flat 64-frame animated design in three palettes: a marbled cellular pattern with no depth, drawn to be lit by the classic renderer's flat ambient rather than a path tracer. Under RT lighting a liquid with no relief and no wet/dry contrast reads as a plastic colour swap of water, whatever body colour it is tinted.

Before (stock) After
stock blood flat coagulated blood, veins in relief
D64B1_/D64B2_ — flat cellular pattern, no depth coagulated plates with real relief; the veins have a flow map running along them
stock nukage flat marbled toxic swirl
D64N1_/D64N2_ — flat cellular pattern marbled toxic swirl; art only, no relief
stock sludge flat mud bed with lumps, rims and pits
D64S1_/D64S2_ — flat cellular pattern a mud bed: lumps, rims and pits in relief, reflection turned off

All three get new reference art, tiled and colour-matched to the game's stylized liquid shader (d64r-liquid-art.wad, one TEXTURES lump redefining every frame as a single unshifted image — the stock definition composites two copies of the patch offset one unit apart per frame purely to fake churn, and on a crisp tile that reads as a ghosted double image instead). Depth is not automatic from there: the primary pass overwrites any normal map on a liquid with the animated water wave, so it takes an engine-side liquid id and a per-liquid relief weight (rt_blood_relief, rt_sludge_relief) to give it back.

Blood additionally gets a flow map — a detail texture advected along the vein network's own baked direction, so liquid visibly runs down the channels rather than the whole surface pulsing brighter. Sludge additionally gets its water reflection switched off (rt_sludge_refl 0): a mirror is the single loudest thing saying "this is water with brown paint on it", and turning it off also took a stability bug with it — the checkerboard split that reflection relies on rebuilds half the screen's columns from their neighbours, which crawls under a moving light on a high-contrast authored normal and was mistaken for a denoiser problem for a while. Poison gets the art change only: no relief, no flow.

All three also stop projecting caustics onto nearby walls — a caustic is light refracted through a fluid and focused on what lies beyond it, and all three are painted opaque, so the rippling pool-light they used to throw (identical to water's) was the loudest single thing undercutting the new art. Only water still does it.

Why. docs/rt-blood-pools.md — the fullest post-mortem in this repo, including two motion-effect designs that were tried, judged not to read as flow, and thrown out before the current flow map, and the checkerboard bug above start to finish.

BAL2 — the Cacodemon fireball (add-on only)

Before After
Cacodemon fireball, orange throughout Cacodemon fireball with a violet and blue fringe
Retribution's ball — orange core to maroon fringe core untouched, midtones violet, extremities blue

The one art change here that does not ship enabled. D64ClassicRecolored repaints the Cacodemon and the Pain Elemental and nothing else — 71 lumps, not one of them BAL2 — so a classic-hued Cacodemon went on throwing Doom 64's orange fireball. This is the projectile catching up with the monster, and it is loaded on the same tick-box, from the same launcher variable: with the add-on off it is not in the file list at all.

Drawn from Retribution's sprite, never the add-on's. The core keeps its fire, the midtones pass through the violet classic Doom's own BAL2 carries around its rim, and the fringe — plus the speckles that fly off the burst — lands on blue. All ten frames, flight loop and explosion.

The light follows the paint. In flight the ball throws two lights that add at it — its warm core, which it always had, and a blue one for the fringe — because a single sphere light has a single colour and a mix can only be two. Where it lands, the fire it leaves burning alternates red and blue flame by flame, and the one light that mark is allowed is the average of the two, which is the violet the ball's own midtones pass through.

Why. docs/classic-recolored-addon.md — which of the two add-on downloads to take and what each gets wrong, why the offsets cannot be patched from a companion pk3, the three things a sprite recolour has to get right that each fail without a word (the grAb chunk PIL drops, why the gradient runs on the silhouette rather than the palette, and keeping the source luma so the hue moves and the drawing does not), and why recolouring the ball's dynamic light without overriding its intensity is invisible. docs/plan-projectile-impact-fx.md §9.8 is the impact fire.



👿  The Unseen Evil monsters

Doom 64 never had a Chaingunner, a Revenant, an Arch-Vile or a Spider Mastermind. Retribution declares those four classes, but only as scale tweaks on the stock Doom II monsters, so they use Doom II's art and are never placed in any Doom 64 map. DrPyspy drew all four in the Doom 64 style for Doom 64: Unseen Evil, and d64r-ue-monsters.pk3 brings them into Retribution, with a fifth redraw for the Shotgun Guy.

Only the sprites and sounds are DrPyspy's. The actors and the placement handler are written for this project, and Unseen Evil's own D64UE_MonsterBase framework is not used.

It is not in the public download. Unseen Evil ships no licence statement, so this project credits it rather than redistributing it — the release zip carries the actors, the materials and the launcher switch, but not the sprites. To play with the monsters, build d64r-ue-monsters.pk3 yourself with tools/pack_ue_monsters.py from your own copy of D64UnseenEvil-v1.0.3.pk3 and put it in mods\. The startup window's row says as much when the file is absent, and the game runs normally without it.

They replace, they do not add. Every one of them takes the place of a monster already placed in the map, so the total never changes and 100% kills stays reachable. A converted Baron is one Baron fewer, not one more monster.

It is not a fresh roll each playthrough. Selection runs off a private LCG seeded from the map name and its levelnum, never random(), and the candidate list is sorted by position before anything is drawn — because ThinkerIterator order is not contractual. The same map therefore lays out identically on every load, and identically for every client in a netgame. What it is not is hand-placed: within a map the choice is a seeded Fisher-Yates shuffle over the eligible monsters, so which particular Shotgun Guy becomes a Chaingunner is arbitrary — just arbitrary in the same way every time.

It unlocks as you go, so the early maps still feel like Doom 64:

Monster From Takes the place of How many
Former Sergeant MAP01 Shotgun Guy 35%
Chaingunner MAP05 Shotgun Guy 12%, ramping to 22% by MAP20
Revenant MAP10 Nightmare Imp, Hell Knight 10% / 8%
Arch-Vile listed maps only Baron of Hell, Hell Knight if none 1 per map
Spider Mastermind MAP22, OUT10 Arachnotron, then Cyberdemon or Mancubus 1 per map, fit-tested

The Sergeant and the Chaingunner draw from the same Shotgun Guy pool, so it is partitioned once — Chaingunners first, Sergeants from the remainder — rather than letting the two percentages compound.

The Mastermind takes an Arachnotron first and not a Cyberdemon, which looks backwards until you check: every Cyberdemon in Retribution but one is wired to a TID or an ACS special, because they are all scripted boss fights, so the handler correctly refuses them and a Cyberdemon-first order simply never fires. The Arachnotron is also the closest footprint the game actually places.

Three things it refuses to do. It never converts a monster carrying a TID or an ACS special, because a map script is addressing that actor by name. It fit-tests every replacement with TestMobjLocation and leaves the original where the new one will not fit, which is what makes a Spider Mastermind safe to try at all. And it calls ClearCounters() before destroying the original, because a plain Destroy() does not decrement total_monsters and the kill percentage would drift.

Everything above is tunable: d64_ue_enable, d64_ue_sergeant_pct, d64_ue_chaingunner_pct, d64_ue_revenant_pct, d64_ue_archvile_maps, d64_ue_mastermind_maps and d64_ue_debug, on their own page under Options. They take effect on the next map load. Untick Unseen Evil monsters in the startup window to switch the whole thing off and play the stock Retribution roster.



⛧  Credits

This project renders someone else's game, in someone else's engine, with someone else's path tracer. CREDITS.md is the full list — this is the short one.

ProjectBy
RTGL1 / RayTracedGL1 — the path tracer
MIT
Sultim Tsyrendashiev (© 2020–2023) and Vasilii Shirokii (© 2024). Sultim is also the author of Doom: Ray Traced, which this project's material and lighting conventions follow.
GZDoom: Ray Traced — the engine we build
GPLv3
Vasilii Shirokii (vs-shirokii) — the RT renderer our engine work extends is his.
GZDoom
GPLv3
The ZDoom / GZDoom teams — Christoph Oelckers, Magnus Norddahl, Randy Heit, Alexey Lysiuk, Rachael Alexanderson, Braden Obrzut and many contributors. Doom source © 1997 id Software and Raven Software.
Doom 64: Retribution v1.5 — the game Nevander, plus the long list of authors in Retribution's own credits — Kaiser (Doom 64 EX, WadGen, Absolution TC), Elbryan42, AgentSpork, Steven Searle, Dreadflame, Footman, Cage, Almonds, NMN and others.
Doom 64: Unseen Evil — the extra monsters DrPyspy — the Doom 64-styled Chaingunner, Revenant, Arch-Vile and Spider Mastermind sprites, the redrawn Shotgun Guy, and the Arch-Vile flame and Mastermind firing sounds. Sound edits by Cardboard Marty; the original unused Arch-Vile sounds are Aubrey Hodges'. Sprites and sounds only — the actors and the placement handler are written for this project. Ships no licence statement, so ask DrPyspy before redistributing. See CREDITS.md.
Doom 64 (1997) — the original Midway Games and id Software. Music and sound by Aubrey Hodges.
DLSS 2 / Ray Reconstruction NVIDIA.


Built on gzdoom-rt and RTGL1 over Doom 64: Retribution — see CREDITS.md.
Doom and Doom 64 are trademarks of id Software. This is a non-commercial fan project, not affiliated with id Software, Bethesda, Midway or NVIDIA.

🤫  Don't tell Alex it's vibe coded.
…it's declared right there at the top, actually. Every light still had to survive a human looking at it.

About

Doom 64 Ray Tracing

Resources

Stars

68 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages