Wolfenstein Project 51



Return to Castle Wolfenstein™ cooperative mission - Project 51 v1.2.

  1. Return To Castle Wolfenstein Project 51
  2. Wolfenstein Project 51 Download

Since the introduction of the Nvidia RTX graphics cards last summer, ray tracing is back again. In the last months, my Twitter feed got flooded with a continuous stream of RTX On / RTX Off comparisons.

After seeing so many nice images, I really wanted to get some experience in combining a classical forward renderer and a ray tracer myself.

Suffering from the not-invented-here syndrome, I ended up in creating my own hybrid rendering engine using WebGL1. You can try out this demo rendering a Wolfenstein 3D level with some spheres (because of ray tracing) here: https://reindernijhoff.net/wolfrt.

Prototype

I started this project by creating a prototype trying to recreate the Ray Traced Global Illumination of Metro Exodus.

  • The maps in Wolfenstein can be fully encoded in a 2D 64×64 grid. The map used in the demo is based on the first level of episode 1 of Wolfenstein 3D. At startup, all geometry needed for the forward pass is created. A mesh for the walls is generated based on the map.
  • A patch for the English version of his superb Return to Castle Wolfenstein modification The Dark Army. Of the RtCW SP mod Project 51, created.

The prototype is based on a forward renderer that draws all the geometry in the scene. The shader used to rasterize the geometry not only calculates direct lighting but also casts random rays from the surface of the rendered geometry to collect the indirect light reflection due to non-shiny surfaces (Diffuse GI) using a ray tracer.

On the image to the right, you can see how all spheres are correctly lit by indirect lighting only (the light rays bounce on a wall behind the camera). The light source itself is occluded by the brown wall on the left of the image.

Wolfenstein 3D

The prototype uses a very simple scene. There is only one single light and just a few spheres and cubes are rendered. This makes the ray tracing code in the shader straightforward. A brute force intersection loop where the ray is tested with all cubes and spheres in the scene is still fast enough to get a program that runs fine in real-time.

After creating the prototype, I wanted to make something more complex by having more geometry and by adding a lot of lights to the scene.

The problem of having a more complex environment is that I still had to be able to ray trace the scene in real-time. Normally a bounding volume hierarchy (BVH) would be used as an acceleration structure to speed up the ray trace process, but my decision to make this project in WebGL1 didn’t help here: in WebGL1 it is not possible to upload 16-bit data to a texture and you cannot use binary operations in a shader. This makes it hard to pre-calculate and use BVH’s in WebGL1 shaders.

That is why I decided to use a Wolfenstein 3D level for this demo. In 2013, I already created a single WebGL fragment shader on Shadertoy that not only renders a Wolfenstein-like level but also procedurally creates all textures needed. From this experience, I knew that the grid-based level design of Wolfenstein could also be used as a fast and simple acceleration structure and that ray tracing through this structure would be very fast.

You can play the demo in the iframe below, or play it full-screen here: https://reindernijhoff.net/wolfrt.

Overview

The demo uses a hybrid rendering engine. It uses traditional rasterization technologies to render all the polygons in a frame and then combines the result with ray traced shadows, diffuse GI and reflections.

Forward rendering

The maps in Wolfenstein can be fully encoded in a 2D 64×64 grid. The map used in the demo is based on the first level of episode 1 of Wolfenstein 3D.

At startup, all geometry needed for the forward pass is created. A mesh for the walls is generated based on the map. A plane for the ground and ceiling is created as well as separate meshes for the lights, doors and the randomly placed spheres.

All the textures used for the walls and doors are packed in a single texture-atlas, so all walls can be drawn using a single draw call.

Shadows and lighting

Direct lighting is calculated in the shader used for the forward rendering pass. Each fragment can be lit by (at most) four different lights. To know which lights could affect a fragment in the shader, a look-up texture is pre-calculated at startup. This look-up texture is 64 by 128 and encodes the 4 nearest visible light positions for every position in the grid of the map.

To get soft shadows, for each fragment, for each light, a random position in the light is sampled. Using the ray trace code available in the shader (see below: Ray tracing), a shadow ray is cast to the sample point in order to determine the visibility of the light.

Eventually, after adding (optional) reflections (see below: Reflection), diffuse GI is added to the calculated fragment color by doing a lookup in the Diffuse GI Render Target (see below).

Ray tracing

Whereas in the prototype the ray trace code for the diffuse GI was combined with the forward shader, I decided to decouple both in the final demo.

The decoupling is done by drawing all geometry a second time to a separate render target (the Diffuse GI Render Target), using a different shader that only casts the random rays to collect the diffuse GI (see below: Diffuse GI). The collected light in this render target is added to the calculated direct lighting in the forward render pass.

By decoupling both the forward pass and the diffuse GI, it is possible to cast less than one diffuse GI ray per screen pixel. You can do this by decreasing the Buffer Scale (adjust the slider in the controls at the top right of the screen).
If, for example, the Buffer Scale is .5, only one ray for every four screen pixels will be cast. This gives a huge performance boost. Using the same UI in the top right of the screen, you can also change the samples per pixel of the render target (SPP) and the number of bounces of the ray.

Cast a ray

To be able to cast a ray through the scene, a representation of all geometry in the level is needed in a format that can be used by a ray tracer in a shader. A Wolfenstein level is encoded in a 64×64 grid, so it is pretty simple to encode all data in a single 64×64 texture:

  • In the red channel of the texture, all objects at the corresponding x,y cell in the grid of the map are encoded. If the red channel is zero, no object exists in the cell, otherwise, a wall (values 1 to 64), a door, a light or a sphere occupies the cell and should be tested for intersection.
  • If a sphere occupies the cell in the grid of the level, the green, blue and alpha channels are used to encode the radius and the relative x and y position of the sphere inside the grid cell.

Casting a ray through the scene is done by stepping through this texture, using the following code:

Return to castle wolfenstein project 51

Similar ray trace code through a grid can be found in this Wolfenstein shader on Shadertoy.

After calculating the intersection point with a wall or a door (using a box intersection test), a lookup in the same texture-atlas as used in the forward pass gives the albedo of the intersection point. The spheres have a color that is procedurally determined based on their x,y position in the grid and a color gradient function.

Doors are a bit problematic because they can move. To make sure that the scene representation on the CPU (used to render the meshes in the forward pass) is the same as the scene representation on the GPU (used for the ray tracing), all doors are moved automatically and deterministically based on the distance between the camera and the door.

Diffuse GI

The diffuse GI is calculated by casting rays in a shader that is used to draw all geometry to the Diffuse GI Render Target. The direction of these rays is based on the normal of the surface using cosine weighted hemisphere sampling.

Given ray direction rd and starting point ro, bounced lighting is calculated using the following loop:

11 third grade james testament. To reduce noise, direct light sampling is added to the loop. This is similar to the technique used in my shader Yet another Cornell Box on Shadertoy.

Reflection

Having the option to ray trace the scene in a shader makes it really easy to add reflections. In this demo, reflections are added by calling the same getBounceCol method as displayed above, using the reflected camera-ray:

Reflections are added in the forward rendering pass, consequently, always one reflection ray per screen pixel will be cast.

Temporal anti-aliasing

As only ~1 sample per pixel is used for both the soft shadows in the forward rendering pass and the approximation of the diffuse GI, the end result is extremely noisy. To reduce noise, temporal anti-aliasing (TAA) is implemented following Playdead’s TAA implementation: Temporal Reprojection Anti-Aliasing in INSIDE.

Reprojecting

The main idea behind TAA is quite simple: TAA computes a single subpixel per frame and then averages its value with the correlated pixel of the previous frame.

To know where the current pixel was located in the previous frame, the position of the fragment is reprojected using the model-view-projection matrix of the previous frame.

Sample rejection and neighbourhood clamping

In some cases, the history sample is not valid, for example when the camera has moved in such a way that the fragment of the current frame was occluded in the previous frame. To reject those invalid samples, neighbourhood clamping is used. I ended up using the most simple type of clamping:

I also tried to use a bounding-box-based clamp method, but I didn’t see a lot of difference with the current approach. This is probably because the scene in the demo has a lot of similar, dark colors and there are almost no moving objects.

Camera Jitter

To get anti-aliasing, the camera is jittered each frame using a (pseudo) random subpixel offset. This is done by modifying the projection matrix:

Noise

Noise is the basis of the algorithms used to calculate diffuse GI and soft shadows. Using good noise will have a big impact on image quality, whereas using bad noise will give artefacts or slow converging images.

I’m afraid that the white noise used in this demo is not very good.

Probably, using good noise is the most important thing to improve the image quality of this demo. For example, by using blue noise.

I did some experiments with golden-ratio-based noise, but this didn’t work very well. So for now, the infamous Hash without Sine by Dave Hoskins is used:

Noise Reduction

Even with the TAA enabled, there is still a lot of noise visible in this demo. Especially the ceiling is hard to render because it is lit by indirect lighting only. The fact that the ceiling is a large flat surface with a solid color doesn’t help either: if it would have a texture or geometric details the noise would be less visible.

I didn’t want to spend a lot of time on this part of my demo, so I only tried one noise reduction filter: a Median3x3 filter by Morgan McGuire and Kyle Whitson. Unfortunately, this filter didn’t work well with the “pixel-art” graphics of the wall textures: it removed all detail in the distance and rounded the corners of nearby wall pixels.

In another experiment, I used the same filter on the Diffuse GI Render Target. Although this did reduce the noise a bit and kept the texture detail of the wall intact, I decided that the improvement was not good enough to justify the extra ms spent.

Return To Castle Wolfenstein Project 51

Demo

You can try out the demo here: https://reindernijhoff.net/wolfrt.

Similar posts

If you like this post, you may also like one of my other posts:

Wolfenstein: Ray Tracing On using WebGL1

The evolution of the Castle Wolfenstein franchise would make Charles Darwin proud. Gameplay has advanced from sneaking past guards in the original to the innovative first-person perspective of Wolfenstein 3D. Even the very walls of the infamous castle have changed from 2D to 3D, from sprites to polygons. The inherent fun of infiltrating the stronghold and defeating the Nazis has remained a constant throughout.

Now, more than 20 years since its humble beginnings, you once again assume the role of William 'BJ' Blazkowicz in Return to Castle Wolfenstein, the latest incarnation that maintains the high standard of quality in gameplay and technology. Seems as though the Nazi S. S. Paranormal Division, in addition to creating horrific biological weapons known as X Creatures, is working to resurrect a 1000-year-old entity to lead Germany to world domination. You, as Blazkowicz (the allies best agent), must overcome zombies, mutants, and a host of Nazi soldiers to save the world from certain doom.

From the start, it's apparent that the development team gave the single-player experience top priority. The story-driven missions are linked by nice cut-scenes and, rather than merely serving as a reason to hunt Nazis, the tale takes on more urgency and intrigue throughout the game. Return to Castle Wolfenstein is comparable to Half-Life in that single-player mode is worth playing to the very end.

The game is a return to serious gunfights as well. Over a dozen lethal choices are available to eradicate the Nazi threat, including submachine guns, sniper rifles, and experimental weapons. Damage is location specific, rewarding headshots more than body parts. More emphasis is placed on sniping than charging in with guns blazing. Even at close quarters, the one-shot sniper kill will have you reaching for the Mauser Rifle instead of the MP-40. Sadly, the Tesla and Venom weapons don't deliver the same accuracy or damage in Blazkowicz's hands as when the enemies use them.

As good as the game is, there are a few missed opportunities. Only one vehicle is operational, a tram on rails. After spending a mission stealing a prototype rocket jet, it would have been nice to have a short flying sequence. Also, the original Castle Wolfenstein incorporated stealing uniforms to sneak past problem areas and the manual makes reference to using uniforms in Wolfenstein 3D, but the designers chose to stick to action over stealth. Even though Return to Castle Wolfenstein has a few stealth missions, adding the uniform swapping would have been a welcome nod to the original.

Multiplayer games feature Axis vs. Allies in team-based combat. Similar to the Team Fortress mod for Half-Life, players can choose from four classes: soldier, engineer, lieutenant, and medic. A variety of maps contain specific objectives, from destroying a submarine to claiming flags in a ruined shell of a town. Especially exciting is the beachhead map where players must storm a hill and get past bunkers in action reminiscent of the opening of Saving Private Ryan. The system requirements for smooth multiplayer action are, however, somewhat hefty so don't take it on without the requisite computing power.

Graphically, Return to Castle Wolfenstein is simply stunning. Bavaria has never been rendered so beautifully, and a bevy of locations such as secret labs, crypts, villages, and, of course, Castle Wolfenstein, create an immersive atmosphere. Landscapes are powered by the Quake III Arena engine, so curved architecture, realistic water and fire effects, and high polygon counts are the order of the day. Characters and vehicles are slightly blocky, but not terribly so. As expected, this quality of graphics require a fairly powerful computer -- certainly giving you the reason you've been looking for to upgrade to more memory and that top-of-the-line accelerator card.

While Return to Castle Wolfenstein is still a FPS at heart, it's not the mindless shooter with the muddled inept storyline so often encountered in the genre. In fact, it's nice to have a positive example of gameplay and story telling that keeps pace with technology. While the designers stuck a bit too closely to the 'gunplay over stealth' mindset, the missions requiring sneaking are a nice change of pace. Perhaps not as innovative as Wolfenstein 3D, the art form of the FPS nonetheless has now been refined to rival the best of the genre.

Graphics: From the quiet villages to the creepy castle, graphics are uniformly superb. The powerful Quake III Arena engine is put through its paces and performs beautifully.

Sound: Gamers need to listen for audio cues (running, directional gunfire, voices) in order to survive. Guns bark with authority and Germans call out warnings to comrades before trying to hunt you down. The background music swells during gunfights, adding to the tension commensurate with infiltration of the Nazi fortress.

Enjoyment: The single-player mode is well thought out and exciting. Multiplayer is non-stop squad action requiring teamwork to achieve objectives.

Replay Value: Some missions are worth reloading after finishing the main game. Multiplayer design requires practice, and will undoubtedly spawn many clans of players looking to hone their skills.


How to run this game on modern Windows PC?

Wolfenstein Project 51 Download

This game has been set up to work on modern Windows (10/8/7/Vista/XP 64/32-bit) computers without problems. Please choose Download - Easy Setup (761 MB).

People who downloaded Return to Castle Wolfenstein have also downloaded:
Wolfenstein: Enemy Territory, Wolfenstein 3D, DOOM³, Quake 4, Quake, Doom, Doom 2, Medal of Honor: Allied Assault