Hacker News new | past | comments | ask | show | jobs | submit login
Wolfenstein's original rendering engine as 450 line shader with no external data (shadertoy.com)
278 points by ingve on Feb 26, 2018 | hide | past | web | favorite | 92 comments

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.

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.

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.

That is really, really funny.

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.

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);

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.

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);

Brilliant! This was my first time playing around with GLSL and I wasn't sure how to implement the buffer to do the blending.

This (edit: the first one) works way better than I expected!

Your h1 and h2 are always the same since hash is deterministic when I assume you want them to be distinct, and they're in the range [-1, 1] instead of [0.5, -0.5] which I think makes more sense. I'm also not sure why you made r instead of just hashing fragCoord directly?

I also think adding time to the hash makes it look better, though if you stand still it's somewhat nauseating (switch all time variables in getColor to 0.0). Anyways I ended up with the following which I think looks great :)

    float h1 = (hash(time + fragCoord.x) + hash(time + fragCoord.y)) / 4.0;
    float h2 = (hash(time - fragCoord.x) + hash(time -fragCoord.y)) / 4.0;

Hash is pseudorandom. It amplifies the rounding error of the sine function, as explained here: https://stackoverflow.com/a/4275343

The reason I didn't use fragCoord was because I wasn't sure it would be within range, though thinking about it, I can't really see why it wouldn't be since before the engine was using it directly (d'oh). Anyways I did the mod 9 and divide by 10 to ensure it was in the range of [0, 0.8999999...]. The idea there was I got initial the impression (though I didn't really dig) that the hash function generates a point between x and y of the vector. Reviewing, it seems this was a silly notion.

I think you're right, now that I'm looking at it again. Your way of getting h1 and h2 is much more efficient.

should the last line of the second function be fragColor = vec4(c1, c2)?

I just see an unused c2.

Thanks! Fixed it.

The aliasing is highly authentic for the original 320x200 render mode :)

It would have been softened by the CRTs of the time, though (or, at least, the CRTs I had...).

CRTs in general are complex pieces of machinery which do odd, hard-to-emulate things to graphics.

Here's an interesting post on the topic:


And one with more detail:


> CRTs in general are complex pieces of machinery which do odd, hard-to-emulate things to graphics.

True, but that doesn't stop the smart guys on Shadertoy from trying... ;)



Maybe yours, but I remember big bulky pixels at anything below 800x600 back then.

VGA seemed downright HD compared to CGA.


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));}

Wolfenstein 3DMT

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):


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.

A co-worker gave me that book recently. Excellent. For anyone who enjoyed this post, I suggest you look into Fabien Sanglard's work (books/blog-posts).

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

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.

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

So must be some other incompatibility between the two.

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 :)

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.

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

Wolf3D is a pretty easy-to-understand renderer by nature (it's a 3D representation of what is really a 2D game, so it's a matter of matching columns), and that simplicity lends itself to creativity.

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?

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.

Still useful in the sense that he teaches how to do profiling right: measure, measure, and measure. Don't just jump to attack the code without profiling, try to reason about the code, try to reason about the algorithms and data structures, and only at the end go for "low level performance tricks" (as in writing everything in assembly).

It's an entertaining read, mostly, but you'll surely don't get much from the 386, 486 and Pentium quirks (and cycle counting) to extract the last drop of performance, other that "back in the old days, Pentium would issue 2 instructions at the same time given a very particular set of conditions". But again, as he does focus on analyzing the problem at large, finding the correct algorithm and data structure, it's still useful.

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

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.

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.

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

Warning: NSFW if you're in Germany

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.

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

"Achtung!" "Mein Leben!"

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

Guess you never got lost in the purple level!


"Oops your WebGL implementation has crashed!"

The wonderful world of mobile WebGL.

It looks great on the desktop, though.

What phone and browser?

LG Power X, Chrome.

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!

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

what's the `mix` function?

Linear interpolation.

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

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

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:


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:


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:


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.


It's arguably the first First Person Shooter game, recreated as per the title as a shader.

The enemies are Nazis, since few people would object to a game about an American soldier escaping a Nazi Castle and leaving a pile of Nazis in his wake.

Or at least, that was the case in 1992.

A very old FPS game, with the story arc being "break free from a WW2 Nazi prison" https://en.wikipedia.org/wiki/Wolfenstein_3D

If you google "wolfenstein", 100% of the first page results are related to the game series.

Wolfenstein, the game whose textures are being generated by this, was set in World War II Germany.

Just the Wolfenstein theme. See: https://en.wikipedia.org/wiki/Wolfenstein_3D

Wolfenstein is id's/Bethesda's "Super-Advanced-Secret-Nazi-Scientists"-Franchise. Hence Nazis.


No sir, am I not. This is HN, give me the benefit of the doubt, please.

On the other hand, a quick Google or Wikipedia search would have enlightened you fast enough.

I must say I am surprised by the number of upvotes this gets, and the number of downvotes crescentfresh seems to be getting (I actually upvoted GP). Not knowing about Wolfenstein is very understandable nowadays.

Those aren't nazi symbols silly. They are buddhist symbols abused by the 3rd reich!

Not helpful. Sometimes technically correct isn't correct enough or good enough. The symbol was - in large parts of the world - tainted by the Nazis. By Germans (I'm one).

Sorry for Buddhists that suffer in Europe, but I am a supporter of the ban of this symbol (and lots of other speech, Free Speech is a different concept from elsewhere).

This definitely is a case where use and intent should matter.

I agree with making the reverence of a /Nazi/ symbol something to censor for the public good.

However, use in a historically accurate context? Or even an alternate history, either of which should present it as a symbol being used to spearhead hate and atrocities? I feel like that is a truthful non-worshiping view that should be retained.

Uses outside of that context, such as the symbol's older geometric and religious meanings (probably identifiable by their lack of historically / physically WW-II ties) should be fine.



I find it weird that anyone would find this nsfw, however, also after reading the linked article in a child comment of this thread, I am wondering in how many places this would be frowned upon. Most people I know played at least one of the Wolfensteins and enjoyed it; it is not like this is some collected memorabilia you are looking to buy on ebay; it’s tongue in cheek game.

Nazi symbols are illegal in France and Germany; games which include them are censored when sold in those countries.

Lived in the EU all my life and I did not know that. Bit silly to surpress important (nasty but important) history: it is also not taught in school then or are the exceptions?

Edit: specifically asking about highschool kids learning about the symbols, not about WOII in general?

Edit2: as far as I can find online the bans are about the flags and greet. I have been to brocantes in France where they sold SS pins etc so maybe it is not as strict?

https://www.legifrance.gouv.fr/affichCodeArticle.do?idArticl... - refers to "display in public" except for film, show, or "d'une exposition comportant une évocation historique".

The history is not suppressed and very much is taught in school. It's just the public display of the symbols. It sounds like you can acquire them in private, provided you're not actually trying to be a member of the banned organisations.

"membres d'une organisation déclarée criminelle en application de l'article 9 du statut du tribunal militaire international annexé à l'accord de Londres du 8 août 1945" refers to the Nuremberg accords.

In Germany, it's allowed for educational or scientific purposes, artworks, clearly negative symbols (e.g. crossed-out swastika symbols of anti-nazi activists) and for religious purposes. Everyone knows what a Swastika looks like.

I would consider the entire Wolfenstein game as negative material, but that is probably going too much into taste of individual to allow it.

But yes Wolfenstein was banned in DE. I did not know it was that strict.

Regarding the recent Wolfenstein 2 this is even weirder: For being able to sell it the publisher removed all Nazi symbols (including renaming Hitler to Mr. Heiler) but also changing the back story. The main character is supposed to be descent of Jews in a Extermination Camp, in the German version his mom died in a (regular) jail and Jews become "traitors". Which means that the result (apparently - I haven't played either edition) is a game full of Nazi-style settings ignoring all the evil cruelty of that regime, which is criticized in the original. https://www.br.de/radio/bayern2/sendungen/zuendfunk/netz-kul...

Thanks for sharing that! I wasn't aware of either the next story line nor the absurd and crazy German adaption.

That feels painfully wrong and is quite disgusting.

> That feels painfully wrong and is quite disgusting.

The problem is that German legislature, jurisdiction and the self-regulating body are... not exactly the definition of agile, more the contrary... it took multiple court jurisdictions to get a final clearance for a f...ing crossed out swastika because some public prosecutors were hellbent on convicting antifas for reproduction of nazi propaganda (yes, for real!).

The fact that most of these people are old white ... who never have played a computer game in their life doesn't exactly help gamers' rights in that area either.

Do gamers care do you know? It is trivial to get versions of any software; cannot imagine people actually adhere to these laws in this case? Buy the censored version; get a torrent of the real thing?

You don't reach everybody. I also got the original Wolfenstein back in the days, long before Internet.

However the reason for the regulation is relevant: This Nazi stuff shouldn't be fun or anything. Playing with it makes it simple to forget that those symbols represent evil mass murder. Also going back to the times where the initial versions of those laws were made: Back then Nazi symbolism was everywhere in the county and the Allies didn't want to allow any Nazi-heroism. Balancing this against "legit" use is tough, especially in context of historic games.

The self-censorship mentioned in the article above is the result of a publisher trying to avoid hitting a law but still selling as much as they can, without really thinking about what they were doing.

> Buy the censored version; get a torrent of the real thing?

The problem with torrents or warez in general is: do you trust them? Or: do you trust someone hasn't taken a virus-free package and embedded a Bitcoin/Monero miner or other malware in there?

Especially as many warez groups distribute keygens or cracks compressed and crypted so that other groups cannot easily reverse engineer the secrets, it's hard to check what's in the package - and many cracks actually trigger virus scanners due to the compression, so people turn off the AV and then get f..d...

> renaming Hitler to Mr. Heiler

I don't know if it's the same Wolfenstein but I was somewhat amused to read, or see on Youtube, recently they renamed Himmler to Hoeller. (Himmel being heaven in German, and Hoelle being hell.)

Nazi symbols are SFW for me.


By this reasoning the US Navy apparently would.


Showing a symbol is very different from endorsing the "ideals" behind it. I'm 100% against fascism and nazism, but if we make illegal or even mark as unsafe showing their symbols, how do we let children watch documentaries about those atrocities before their brains are caught and ruined by the neo nazi/fascist propaganda?

Your speech is free in the US, but you can still be fired for it.

I am located in Europe.

Your comment implies you're either in Germany or the US, and since you said "SFW", I inferred you're in the US.

Either way, freedom of speech doesn't automatically make all allowed speech into SFW speech.

I'm located in Eastern Europe and I am a European citizen. If I were in Germany it would probably be NSFW indeed. I wouldn't want to trigger them.

In my country and my working environment there'd be no risk for me whatsoever.

Why is it that much NSFW to read an article that describes the technical merits of a game where the player kills Nazis?

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

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact