
Wolfenstein's original rendering engine as 450 line shader with no external data - ingve
https://www.shadertoy.com/view/4sfGWX
======
nimbius
This game was responsible for two amazing moments in my childhood.

1\. Realizing I wanted to be a computer programmer

2\. My parents being confronted by school administrators because I was
whistling Horst Wessel Lied, or as i called it "the cool Wolfenstein song." at
school frequently.

~~~
Mantipath
I'm trying to figure out how that second point worked. Is it that you were
whistling too often and it would have been a problem even if you were going
with, say, the Andy Griffith theme, or did you both whistle so well and have
school administrators so educated that they identified that you were whistling
Nazi propaganda?

It's not that characteristic a tune.

~~~
boomlinde
It was the national anthem of Nazi Germany. If you have a even a passing
interest in WWII, which I hope at least some teachers would, it should be
familiar.

------
gpm
The aliasing in this was driving me insane, fortunately it's really easy to
implement anti aliasing.

Change the function signature of the current main function to

    
    
        vec3 getColor(in vec2 fragCoord ) {
    

And the last line to

    
    
        return col
    

Then just call it a bunch of times with nearby points in a new main function

    
    
        void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
            vec3 c1 = getColor(fragCoord + vec2( 0.25,  0.25));
            vec3 c2 = getColor(fragCoord + vec2( 0.25, -0.25));
            vec3 c3 = getColor(fragCoord + vec2(-0.25,  0.25));
            vec3 c4 = getColor(fragCoord + vec2(-0.25, -0.25));
            
            fragColor = vec4((c1 + c2 + c3 + c4) / 4., 1.0);
        }
    

Obviously this does 4x the amount of work as without antialiasing, but with
modern hardware you don't notice it and it looks _way_ better.

~~~
entropicdrifter
Figured out how to do Multi-Frame style Anti-Aliasing for a much lower
performance hit, looks nearly as good in motion:

    
    
      void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
        	float h1 = (hash(time + fragCoord.x) + hash(time + fragCoord.y)) / 4.0;
    	float h2 = (hash(time - fragCoord.x) + hash(time -fragCoord.y)) / 4.0;
            vec3 c1 = getColor(fragCoord + vec2(h1, h2));        
            fragColor = vec4(c1, 1.0);
        }
    

Or if you want it to look pretty much exactly as good as 4x MSAA (which is
what you're doing) for a little more than half the cost:

    
    
      void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
        	float h1 = (hash(time + fragCoord.x) + hash(time + fragCoord.y)) / 4.0;
    	float h2 = (hash(time - fragCoord.x) + hash(time -fragCoord.y)) / 4.0;
            vec3 c1 = getColor(fragCoord + vec2(h1, h2));  
        	vec3 c2 = getColor(fragCoord + vec2(-h1, -h2));
            fragColor = vec4((c1 + c2)/2., 1.0);
        }
    

EDIT: Changed per suggestions from reply

Edit2: Found another way to cut down on cost with even less sacrifice of
quality (no under-water feeling while holding still), you can do a 2x version
of the original 4x method, but flip-flop the x coordinates every frame, like
so:

    
    
      void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
        	bool even = mod(float(iFrame), 2.) == 0.;
        	float x;
        	float y = 0.25;
        	if(even) {
            	x = -0.25;
            }
        	else {
            	x = 0.25;
           	}
            vec3 c1 = getColor(fragCoord + vec2(x, y)); 
        	vec3 c2 = getColor(fragCoord + vec2(-x, -y));
            fragColor = vec4((c1 + c2)/2., 1.0);
    }

~~~
rzzzt
My attempt at temporal anti-aliasing:

1) Add the editor tab for "Buffer A"; move all code with the getColor
modification above to this editor; use the following mainImage method:

    
    
        #define SCALE_FACTOR 1.0
        #define BLUR_FACTOR 0.95
        
        void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
            float sum = floor(fragCoord.x / SCALE_FACTOR) + floor(fragCoord.y / SCALE_FACTOR);
            bool evenSum = mod(sum, 2.0) < 0.0001;
            bool evenFrame = (iFrame % 2) == 0;
            
            vec2 texCoord = fragCoord.xy / iResolution.xy;
            vec3 texColor = texture(iChannel0, texCoord).rgb;
            
            if (evenSum == evenFrame) {
                fragColor = vec4(mix(texColor, getColor(fragCoord), BLUR_FACTOR), 1.0);
            } else {
                fragColor = vec4(texColor * BLUR_FACTOR, 1.0);
            }
        }
    

2) The "Image" tab should only render the contents of iChannel0 on the
display:

    
    
        void mainImage( out vec4 fragColor, in vec2 fragCoord )
        {
            vec2 texCoord = fragCoord.xy / iResolution.xy;
            fragColor = texture(iChannel0, texCoord);
        }
    

3) Assign Buffer A as the input/output for iChannel0.

This should do the ray-casting part at effectively half resolution in an
alternating checkerboard pattern, blending the previous contents of the buffer
with the current output.

~~~
entropicdrifter
So I decided to try combining the way you did it with what I did before, and I
came up with this method, which is effectively equivalent to the 4x AA in the
top-level comment:

    
    
      void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
        	bool evenFrame =  (iFrame % 2) == 0;
            float x;
        	float y  = 0.25;
        	if(evenFrame) {
            	x = 0.25;
            }
            else {
                x = -0.25;
            }
            vec3 c1 = getColor(fragCoord + vec2(x, y)); 
        	vec3 c2 = getColor(fragCoord + vec2(-x, -y));
            vec2 texCoord = fragCoord.xy / iResolution.xy;
            vec3 texColor = texture(iChannel0, texCoord).rgb;
            fragColor = vec4(mix(texColor, (c1+c2), 0.5), 1.0);
    }

------
ngcazz
This is fun -- getting very trippy results by changing around some of the
constants...

    
    
       vec3 rotate(vec3 r, float v){ return vec3(v*0.1*r.x*cos(v)+r.z*sin(v),r.y,r.z*cos(v)-r.x*sin(v));}

~~~
itomato
Wolfenstein 3DMT

------
jcl
If you like this one, you'll probably also enjoy the "DOOM E1M1" shader, which
not only reproduces the DOOM graphics, but also the level audio in a separate
shader (note: sound autoplays):

[https://www.shadertoy.com/view/lsSXzD](https://www.shadertoy.com/view/lsSXzD)

------
tluyben2
I am reading Fabiens Wolfenstein 3d Black book (and implementing several parts
to really understand things); great to see this. The Wolfenstein 3d engine was
rather compact already but this is very cool.

Smooth run on the iPhone 7 as well.

~~~
city41
Interesting that it worked for you on the iPhone 7. On my iPhone 6 I just get
a black window.

~~~
tluyben2
I hit 60 fps on chrome on ios while black like you have on Safari. I assumed
they both use webkit and would be more or less the same.

~~~
city41
Yeah, that is also what I got, black screen at 60fps. iOS Chrome still uses
webkit as Apple requires it.

~~~
tluyben2
So must be some other incompatibility between the two.

------
oblio
It seems there's a wave of silly Wolfenstein implementations, about a week ago
we had the Microsoft Excel one.

Keep them coming, I'm always amazed by the creativity some people have :)

~~~
zaroth
Sooooo thankful your second sentence turned out the way it did.

Just enough time reading the first sentence for “WTF” to form in my mind in
anticipation of the condescension to come, and then the second sentence and it
was a nice bit of stress release.

Hurray for the hacker in Hacker News.

~~~
oblio
I'd love if everyone in the world could work on making me rich, healthy and
immortal, but it they don't, they might as well have fun instead :p

------
Gargoyle
Very tangentially related, but curious if anything thinks it would be worth
reading through Abrash's Zen Of Graphics Programming at this point. Is the
info in there still useful (even as a foundation) or is it totally outdated?

~~~
spitfire
Go with the black book. I've written a comment about this before but can't be
bothered to look for it.

The Abrash's black book is both totally outdated and highly relevant. All the
technical material is entirely outdated - which probably makes it a better
teaching tool. But the methods, approaches and thought processes he describes
through the book and the beginning vignettes are priceless.

Particularly for me the Video 7 story.

------
atesti
Where is the map stored?

So far there seems to be no texture and all textures are procedurally created,
but I can't figure out yet where the map is. Is it in isMap and isDoor? They
are very short

~~~
gpm
isWall, isDoor, isObject seem to determine if a location is a door, wall, or
object and be the primary map generating functions. They take a floor
coordinate and return true/false depending on if it is/isn't hit. Additionally
wall/room height seems to be hardcoded as 1 (from 0 to 1 specifically) in
getColorForPosition.

If it doesn't hit a wall, door, or object, then it is the floor/ceiling as
decided by how color is initialized in main above the call to castRay.

------
cwyers
Watching that is really unsettling. The graphics look very much like what I
remember, but movement is so very _off_. It's so smooth and the turning allows
for much more subtle changes in orientation than the game did. Kinda breaks by
brain a little. Still really cool.

~~~
hinkley
IIRC didn’t wolf and doom use ‘binary radians’ where 2π = 256

------
Hans_Dorn
Warning: NSFW if you're in Germany

~~~
jlmorton
Also NSFW if you're, you know, at work.

While some co-workers may recognize the game, others may be put off by large
swastikas zooming around your monitor.

------
CPLX
This is awesome. I was watching it waiting for a guard to scream at me in
unintelligible 8-bit German.

~~~
wukerplank
"Achtung!" "Mein Leben!"

Good times. A game that worked without mini-map and without constantly patting
the player on the back.

~~~
FranOntanaya
Guess you never got lost in the purple level!

------
pjmlp
"Oops your WebGL implementation has crashed!"

The wonderful world of mobile WebGL.

It looks great on the desktop, though.

~~~
modeless
What phone and browser?

~~~
pjmlp
LG Power X, Chrome.

------
etaioinshrdlu
It really makes you realize that 8GB (or bigger!) games these days are simply
not really trying procedural generation at all.

Even most mobile games at something like 50MB are a huge waste of space!

------
digi_owl
Watching the animation play i could almost hear the shouts of the soldiers.
And it has been decades since i fired up the game.

------
tomp
what's the `mix` function?

~~~
genpfault
[https://www.khronos.org/registry/OpenGL-
Refpages/es3.0/html/...](https://www.khronos.org/registry/OpenGL-
Refpages/es3.0/html/mix.xhtml)

------
StanislavPetrov
When I saw "original" I was expecting to see the 1980s version.

------
nukeop
The title is misleading - the shader does not implement the original
Wolfenstein rendering engine, it's an experiment in generating textures inside
the code.

~~~
jcl
It's actually closer to the Wolfenstein rendering engine than one might
expect... Like the original, it does a ray-march in the ground plane to find
map objects:

[https://github.com/id-
Software/wolf3d/blob/0516778/WOLFSRC/W...](https://github.com/id-
Software/wolf3d/blob/0516778/WOLFSRC/WL_DR_A.ASM#L414)

And, like the original, it separates the cases of wall blocks, door blocks,
and sprites. It also expedites ceiling/floor drawing by just picking a flat
color for the top and bottom halves of the screen:

[https://github.com/id-
Software/wolf3d/blob/0516778/WOLFSRC/W...](https://github.com/id-
Software/wolf3d/blob/0516778/WOLFSRC/WL_DRAW.C#L946)

But it naturally differs from the original in that it is rendering per-pixel
rather than per-column, so it does not need to accumulate a list of sprite
intersections to draw in reverse order over the walls -- it can just stop
raymarching when it encounters an opaque pixel:

[https://github.com/id-
Software/wolf3d/blob/0516778/WOLFSRC/W...](https://github.com/id-
Software/wolf3d/blob/0516778/WOLFSRC/WL_DRAW.C#L1157)

In a sense, the shader works like rewriting and optimizing the original
renderer so that it renders to a screen of a single pixel, then running
147,456 instances of the renderer to fill the 512x288 pixel screen.

------
mhd
About twice as many lines with a more conservative indent setting.

