How video games use lookup tables
blog.frost.kiwi
blog.frost.kiwi
https://twitter.com/zeta0134/status/1756988843851383181
The clever bit is that it's two lookup tables here: the big one stores the lighting details for a circle with a configurable radius around the player (that's one full lookup table per radius), but the second one is a pseudo-random ordering for the background rows. I only have time to actually update 1/20th of the screen each time the torchlight routine is called, but by randomizing the order a little bit, I can give it a sortof "soft edge" and hide the raster scan that you'd otherwise see. I use a table because the random order is a grab bag (to ensure rows aren't starved of updates) and that bit is too slow to calculate in realtime.
This should run on real hardware, I'm just waiting on a dev board from my cart supplier before I'll be able to test that properly. Mesen is what I use in the meantime. It is pretty darn accurate, and I haven't overclocked it or cheated any of the console's constraints away.
I think the search keyword for this is "quasirandom", FWIW.
You have a look-up table such that for each pixel on the screen, you know its angle and distance from the center of the screen. With this you can pick which texel to put at each pixel location.
It looks as if you are moving in a tunnel with 3D geometry, but it's so cheap you can even do it on pico: https://www.lexaloffle.com/bbs/?pid=63818
At first I thought the game Stardust must have used this effect, but reading up on it just now they actually just play a repeating 6 frame animation for the background! https://codetapper.com/amiga/sprite-tricks/stardust/
You now basically have a two step lookup: palette_rgb[colormap[color, light]]
https://www.youtube.com/watch?v=fWDxdoRTZPc
edit: This comment really summarizes just how impressive the demo is https://old.reddit.com/r/Demoscene/comments/wjxqve/area_5150...
(Make sure you're properly seated, then click Show Options »)
Unreal by Future Crew has an effect that comes to mind that is probably implemented exactly like that (on VGA mode 0x13), just more of a wormhole than a tunnel: https://www.youtube.com/watch?v=InrGJ7C9B3s&t=4m40s
They would draw the next frame of the game direct to the screen in either the high nybble (4 bits) or the low nybble (alternating each frame).
The color palette was either one of two: one where the same color would be displayed regardless of the high nybble and the other palette had colors chosen where they were the same regardless of the bits in the low nybble.
To be sure the game threw away 256 possible colors to have instead only 16. But when the next frame was being rendered, the user was never aware until, at the very last instant, the next palette was slammed in revealing the new frame (and of course the rendering began for the next).
https://web.engr.oregonstate.edu/~mjb/cs557/Projects/Papers/...
"Here is every single colormap that matlibplot supports,..."
But this is a technical topic in a visual concept. I appreciate visualizations and absolutely praise shaders in contexts like these.
Edit: nevermind, once started it's impossible to pause the videos, they always continue playing after scrolling.
Battery optimization disables the top video element of a muted video once you scroll past it, which stops the WebGL samples from playing. Landing in the middle of the article from another page also prevents the WebGL samples from working with video, which is supposed to illustrate the realtime processing aspect. But unmuted cannot be auto played.
I'll rethink how I approach this for my next article. Maybe a pause button under every WebGL sample?
These uses of LUTs are great and I'm happy to see such a neat article describing them. Also love the use of webgl and the ability to upload your own data!
I was a bit surprised however by the way colour grading using LUTs was presented. The article makes it sound like a cool niche solution used by L4D2, but it's been the industry standard for a long while and was used on every game I've worked on, from AAA games like NFS (2015) to larger indie games like Lost in Random.
> but it's been the industry standard for a long while
Yeah, I mixed messages with my colorful writing style I guess, thought the passage "Using 3D LUTs to style your game’s colors via outside tools is a very well known workflow." was enough to state, that it's not niche use. Clarified now with an extra sentence.
If you want a little insight into my corner of the making of LiR, check out this post I wrote about the fog: https:// agentlien.github.io/fog
[1] https://www.copetti.org/writings/consoles/game-boy-advance/#...
Often you can simplify a code base full of conditional statements with a tidy lookup table.
I think the reason this isn’t always thought of is because a lookup table might end up with 50k rows even for something that might seem simple. This might some like a lot to end users, but thankfully the computer doesn’t mind.
Also, the look up tables are so repetitive that the tables are usually pretty easy to manage, and still worth it even when they aren’t.
On top of that, they can be configured by end users without code changes.
All in all, a really useful concept, especially for a lot of scenarios that always pop up when coding business logic.
and that is how you end up with a DSL.
You’re reading way too much into this, this simply solves a particular set of problems well in this context and that’s that.
Anything you use can get turned into something ugly given enough time and lack of supervision.
where did i say it's bad? DSLs aren't inherently bad (or good for that matter).
> You’re reading way too much into this
i think you're projecting your own ideas onto me!
I love that channel, he reworked the entire Mario 64 code [0] to make it run at stable 60FPS...because he wanted his mods to run faster.
Heck even sprites movements often weren't done using math but using precomputed tables: storing movements in "pixels per frame".
It worked particularly well on some platforms because they had relatively big RAM amounts compared to the slow CPU and RAM access weren't as taxing as today (these CPUs didn't have L1/L2/L3 caches).
The N64 is already a more modern machine.
I'm not denying the effect is impressive, but assuming you're referring to the surface ripples/capillary waves, would a 1D cellular automaton be beyond the abilities of the NES to calculate in real time?
https://festivalofthespokennerd.com/podcast/series-3-episode...
It absolutely did! Even the NES had one. In addition to the (cloned) Motorola 65c816 SNES CPU, the PPU (picture processing unit) was a custom chip that offered several different layouts for spitting out tile based backgrounds and sprites as each scanline physically drew across the CRT TV. It was even capable of fancy things like background rotation and scaling (Mode 7, think Mario Kart) and hardware transparences which you can see in many games. Furthermore, in the case of Doom (and games like Starfox) the cart was baked in with the "Super FX" chip that handled the 3d calculations. So you were actually dealing with two graphics processors for that specific title, heh.
Here's a wonderfully (and painfully) detailed series about the SNES hardware by Retro Game Mechanics Explained:
https://www.youtube.com/watch?v=57ibhDU2SAI&list=PLHQ0utQyFw...
- Atmospheric scattering.
- Tinting Sprites.
- Nightvision Scopes.
- FLIR scopes.
- B&W “video feed” effect.
- Glitch effects.
- Heightmap Shading.
- Alpha dot factor for spaceship exhaust plumes.
- Heatmap of website visitors mouse dwellings.
- Crystalline Effects.
- And finally, post processing colorization in raw color space.
LUTs are a visualization of an array of values known to you, and it’s amazingly useful.Kids these days have forgotten all about palettized sprites! Where do ya think the term “palette swap” comes from? ;)
http://www.effectgames.com/demos/canvascycle/
It fell out of favor because OG Xbox was bad at it and Photoshop doesn’t support it. But, modern hardware can totally handle palette swaps make in AESprite.
It fell out of favor because hardware no longer needed to use palettes as they had enough memory to store the actual color.
You can do so much more than tint with palettes. The animations in the link I posted each a composed of a single 8-bit image and a function to rotate specific sections of the palette.
i'd add: or fpgas, or eproms, or m6
why: because given a "weird machine" that is nearly omnipresent (in the back end, anyway) and is also a Perlis language, who among us would not wish to program it? (and who among us would willingly admit that 1959's https://en.wikipedia.org/wiki/IBM_1620#Model_I programmers had had better code-fu?)
I must go down to the sed(1) again, to the pattern space and the hold,
And all I ask is a t branch and a s/ub/st/ to map and fold;
And the Jolt’s kick and the keys' clack and the write lines breaking,
And a green glint in mirror shades, and a grey* dawn waking.
* "the color of television, tuned to a dead channel"; no Ἠὼς Ῥοδοδάκτυλος here!(Briefly: memoization is local shared state, caches are global shared state, which is a whole other set of problems. And caching is hoping you'll need something eventually, where as memoization is knowing you will need it immediately.)
A couple years ago I watched a very long video on Dynamic Programming and learned a new term: Tabulation. If memoization is encountering a common sub-problem and holding onto it, tabulation is seeking them out, answering the sub-problems before they can come up organically.
It's much harder to derive the tabulation answer, but I find it's usually easier to reason about after the fact. Which is enormously helpful when you go back to add new features or fix old bugs.
Look-Up Tables are fixed-size tabulations. The space complexity of tabulation is often ≥ O(n), while LUT are a fixed size, but big enough to reduce the computation time by a constant factor, like 10 or 100.
Each one of them is a time capsule of the techniques and game design philosophies at the time of development. Pretty sure there is a walk through on youtube for every single one of them, if you don't want to get the games yourself.
The DooM Engine Black Book goes into details.
https://fabiensanglard.net/doom_fire_psx/ also
Other games use seeded random number generators to be entirely deterministic even though random; Factorio does this to allow multiplayer without having to sync the entire game state all the time.
That magazine got me into fractals and raytracing, BTW.
Tunnel effect (exactly as user bemmu described below):
- see it live: https://htmlpreview.github.io/?https://github.com/sylefeb/gf...
- lookup table: https://github.com/sylefeb/gfxcat/blob/main/tunnel/tunnel.h
- how it is computed: https://github.com/sylefeb/Silice/blob/master/projects/ice-v...
Julia fractal, with a table to do integer multiply! (2.a.b = (a+b)^2 - a^2 - b^2, so just precompute all x^2 in a table! )
- see it live: https://htmlpreview.github.io/?https://github.com/sylefeb/gf...
- code (see 'sq' table): https://github.com/sylefeb/gfxcat/blob/main/julia/julia.c
- credits (mul trick): http://cowlark.com/2018-05-26-bogomandel/index.html
In-hardware lookup division for perspective correct texturing!
- doom-chip on-ice (rC3 2021 talk): https://www.youtube.com/watch?v=2ZAIIDXoBis
- detailed write up: https://github.com/sylefeb/tinygpus?tab=readme-ov-file#preco...
- actual lookup table generation: https://github.com/sylefeb/tinygpus/blob/498be1b803d0950328a...
Sine tables are also very typical, for animating things on screen or plain trigonometry, for instance my small fixed integer raytracer uses a sine table to animate the spheres (and the cos is easy to get from the sin, especially if the table has a power of two size ;) ):
- see it live: https://htmlpreview.github.io/?https://github.com/sylefeb/gf...
- source code: https://github.com/sylefeb/gfxcat/blob/main/raytrace/raytrac...
And of course procedural textures!! Perlin noise is made of lookup tables, and often multiple lookups are combined to then lookup a clolormap (https://redirect.cs.umbc.edu/~ebert/691/Au00/Notes/procedura...)
There are so many other examples. Also, FPGAs are quite literally made of LookUp Tables, or LUTs:
- https://github.com/sylefeb/Silice/tree/master/learn-silice#f...
- https://github.com/sylefeb/silixel
(edit: formatting)
A problem I imagine on modern processors is the cache might be shared amongst several cores and interrupts etc.
Back in the nineties, 3DFX made a speed of light rendering demo for the Voodoo1 where they took over the entire machine and through intimate knowledge of cache behaviour effectively set aside an area in the L1 cache that would contain some data they could stream in and speed up their triangle setup.
AMD Zen platforms have always put that functionality (ironically) in the secret platform security coprocessor.
All of this looping was happening in another tight loop, and there were multiple LUTs.
It was a failure with overall decreased performance and increased cache misses. Plus, I could never get the same precision.