Quick recap for those who aren't aware: Project MD is an old unfinished homebrew platformer for the Mega Drive known for its weird aesthetics remiscent of Pulseman. Here's a YouTube playlist showcasing all the levels in the game.
The first level of Project MD (Infinite Field) is notorious because it has a reflection on a 3D floor. People keep thinking it's impressive (even those who should have known better) but normally I shrug it off because honestly it's not really CPU intensive. Now that I look at it though it's a horrible mess in terms of layers, so maybe it'd be a good idea to dissect it.
For context, this is how the level looks like:
Quick hardware overview
First let's do a quick recap of what the Mega Drive video hardware is capable of doing. There are four layers (besides background):
- Scroll plane A
- Scroll plane B
- Window plane
(the window plane is not used in-game so I won't mention it anymore)
On top of that, there's a priority bit for each tile in the scroll planes and for each sprite. High priority layers will appear above low priority layers. When two layers with the same priority overlap, they're sorted in this order: background < plane B < plane A < sprites. Also, sprites are sorted based on their order in the sprite table (earlier sprites appear over later sprites).
Just to get it out of the way. The HUD is just high priority sprites and the earliest ones in the list, so this means it'll appear above everything else, period. Let's move on.
The 3D floor
The 3D floor is just a graphic that already has perspective, and the 3D effect is done by changing its scroll value every line (the hardware provides a table just for this), so lines at the top scroll slower than lines at the bottom.
Also just to make it clear, the computation is absurdly simple because the deformation is linear. This means that for every line it just adds a fixed delta value (OK, internally done in fixed point since it needs subpixel accuracy, but point stands). This is rather trivial for the 68000.
The foreground reflection
This is where the mess starts.
The objects and the level scenery (blocks, etc.) are high priority sprites and plane A, respectively. Their reflections are done by drawing them twice, but the reflection is low priority, flipped upside down, and using darker palettes. (well sorta, for the level scenery the reflection is part of the scenery itself, only sprites are actually drawn twice)
The floor in the portion where the reflection happens is high priority plane B. What this means is that the objects and scenery will appear over it, but the reflections will appear under it. The reflections show through transparent pixels in the floor. The background color happens to be black, which together with the darker reflections, makes it look like the floor is made out of polarized glass.
Also a nifty trick: the grid is crafted such as to ensure scenery tiles will intersect with the columns. When put together with the reflections, that makes it look like it's going through the grid, which helps enhance the 3D-ness effect. That's a really subtle trick that's easy to overlook.
The background reflection
"But wait, what about the trees in the background?"
Things get messier! Those trees are also sprites, but they're low priority. This goes both for the tree itself and its reflection. (the reflection is still upside down and darker) This is needed to ensure they appear below the foreground, but then how do trees appear above the floor?
Priority on tilemaps is per-tile. In this area, the floor is low priority above the tree base, and high priority below the tree base. This means the reflection ends up under it, but the tree itself doesn't (since sprites appear over plane B by default). Note that this means the base of the trees have to be aligned to a tile boundary as well, since that's the granurality for priority in the scroll planes.
How is their position calculated, since the 68000 sucks at division and that scaling is most likely not a power of two? I don't remember the exact details right now but I recall the value being related to 14. That sounds bad, until you realize it can be computed as 16 - 2, and that's exactly what the game does: use bit shifting to get 16 and 2, then use those subtotals to get the final value. (mind you, I was still definitely lucky that it was an easy value to compute)
For the sake of completeness. They're just in plane B (above the floor), and this portion just scrolls much slower than everything else. That's all there's to it.
From a CPU standpoint, this effect is not really impressive, and it doesn't really push the video hardware either. However, it requires a really contrived layer setup, complex enough that I could just make a whole article out of it =P
Hope you enjoyed it! (・ω・)ｂ