Monday, September 18, 2017

Zelda Majora's Mask point-lighting


Problems with emulation of lighting in Zelda MM are almost as old as N64 emulation itself. Many developers (including myself) tried to fix it, but there is no solution, which works 100% correct. Finally olivieryuyu decided to decode it. The task is hard (otherwise it would be already done), but after successful decoding of Star Wars - Rogue Squadron microcode nothing is impossible. olivieryuyu decoded lighting method of Zelda MM. The lighting method is really complex. Not as insanely complex as lighting in Conker's Bad Fur Day, but definitely the second place. I started to implement it. WIP screenshot:

WIP build is available for patrons with early access: https://www.patreon.com/Gliden64

Math behind point lighting can be found in many places. Algorithm of point lighting in Zelda MM is close to one, described in this tutorial. Existing implementations of Zelda MM point lighting are close to the truth, but it has subtle and not obvious nuances, which hardly can be guessed without close examination of ucode's asm code.

To be short: olivieryuyu decoded Zelda MM point lighting code completely. It was a hard piece of work due to lots of math. I implemented it. The sources added to master in October. If you want to test it, download latest WIP build from GitHub.

Monday, September 11, 2017

F-Zero fixes. Patreon.


I was on vacation after successful implementation of  microcode for Star Wars - Rogue Squadron. Now olivieryuyu and me continue to work on HLE issues. I just finished to work on issues in F-Zero. This game is playable, but two features were not emulated. They are:
reflection on vehicles during a race:
and red boarder around your vehicle in attack mode:

Reflections are missing only in HLE. F-Zero uses non-standard ucode F3DFLX to render vehicles. olivieryuyu discovered that this ucode has custom lighting routine. He spent a lot of time and efforts to decode it and finally found how it works. It is interesting and unique technique worth to describe it in details.

First, a short introduction to how N64 does reflections. Of course, N64 can't calculate true reflections on surfaces in real time. However, it is powerful enough to create pretty realistic effect of shiny metal surface, like this 
This effect requires special texture and special mode, in which texture coordinates for vertices calculated dynamically. Reflection texture is black picture with bright spot at its center, like this:

Special LookAt structure is used to calculate texture coordinates. LookAt structure consists of two Light structures, which contain only coordinates of 3D vector. This vector multiplied by transposed model matrix. Dot product of resulted vector with normal vector of vertex defines one texture coordinate. 2D texture has two coordinates. Thus, texture coordinates calculation requires, at first, two vertex-matrix multiplications and then two dot product calculations for each vertex. It is quite expensive set of calculations. Normally, if scene is complex, only few objects have reflection effect. In racing games, usually only player's car looks cool and shiny while other cars look more modestly.

In F-Zero all vehicles have reflections. While you can see one-two dozens of vehicles on screen at the same time, F-Zero is very fast game. Yes, vehicles models are very simple, but reflection calculation even for simple model is expensive. Normal reflection method could not work fast enough. Thus, F-Zero developers created custom reflection method, which works much faster than normal reflection.
We need calculate two texture coordinates to apply color from reflection texture. However, if we will use 1D texture, number of calculations reduced by half! N64 does not support 1D textures, but that can't stop crafty programmers. Since vehicles models are simple, we can do another optimization and does not use texturing for reflections at all. Instead of fetching texture color for each pixel we can set reflection color per vertex and use standard Gouraud shading. Texture mapping can be used for main vehicle's texture. That way we can apply both vehicle's texture and reflection in one step!

We calculated one texture coordinate in RSP, thus we can fetch reflection color from texture right on vertex loading. How it works: special command loads texture data into DMEM, memory space where microcode running.  Microcode calculates texture coordinate and uses it as index to fetch texel from texture in DMEM. But the problem is not solved yet. DMEM size is limited by 4kb, so it is expensive to keep color information in it. Besides, vertex already has color. Blending vertex color with texture color is not right kind of task for RSP, it is RDP task. We can't pass another set of colors with each vertex to RDP. But we have vertex alpha still unused. N64 usually uses vertex alpha as place for fog factor for opaque surfaces. Fog factor calculation depends on vertex Z and enabled by special flag in microcode. N64 blender mixes fog color with output of color combiner using vertex alpha as fog factor. This is exactly what is needed: lets texels of 1D reflection texture be factors for fog color, so they can be assigned to vertex alpha; then enable fog color in blender. Fog factor calculation flag must be disabled for this to work and the microcode disables it. In fact reflections on vehicles in F-Zero are not reflections but fog!

Red boarder problem is not related to microcode - it presents in LLE mode too. I tried to find, what is the source of that color. No components of color combiner equation have such color. I looked at blender and yes - blender uses blend color, which is set to bright red. However, force_blend flag set off. I supposed that when force_blend flag is off, blender is bypassed. I was wrong. force_blend off means that blender equation is not calculated, but first argument of blending equation is taken as result of blending. Usually the first argument of blender equation is color combiner output, so taking the first argument is equivalent to blender bypass. In case of boarder the first argument of blending equation is blend_color. That is blender ignores result of color combiner and outputs just bright red blend_color. I slightly corrected blender shader to support it.


Donations are not necessary for project development but always welcome. I set project page on Patreon service to simplify donations process. If you like my work and don't mind to help the project, you may do it on regular basis now. Any sum is welcome. Small dollar is better than big thanks. As a reward you may choose early access to beta builds of the plugin with new features. Beta build with F-Zero fixes is available for patrons right now. Patreon page:
Promotion in social networks is welcome too.