Wolfenstein's original rendering engine as 450 line shader with no external data
shadertoy.com
shadertoy.com
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.
It's not that characteristic a tune.
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.Here's an interesting post on the topic:
http://ascii.textfiles.com/archives/3786
And one with more detail:
True, but that doesn't stop the smart guys on Shadertoy from trying... ;)
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 replyEdit2: 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);
}I just see an unused c2.
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;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.
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.
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);
} 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));}Smooth run on the iPhone 7 as well.
Keep them coming, I'm always amazed by the creativity some people have :)
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.
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.
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.
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
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.
While some co-workers may recognize the game, others may be put off by large swastikas zooming around your monitor.
Good times. A game that worked without mini-map and without constantly patting the player on the back.
The wonderful world of mobile WebGL.
It looks great on the desktop, though.
Even most mobile games at something like 50MB are a huge waste of space!
https://github.com/id-Software/wolf3d/blob/0516778/WOLFSRC/W...
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...
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...
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.