You can’t really do anything tangent space on two texture units, because you need three register combiners just to do a single tangent space transform (it’s three dot3 operations).

However, just doing plain phong doesn’t require tangent space transforms. If Gouraud looks like this:

out1 =

interpolate(

diffuse( v1.N, v1.L ),

diffuse( v2.N, v2.L ),

diffuse( v3.N, v3.L ) ) +

interpolate(

specular(

reflect( v1.N, v1.E ),

v1.L ),

specular(

reflect( v2.N, v2.E),

v2.L ),

specular(

reflect( v3.N, v3.E),

v3.L ) );

then there’s two “vector based” interpolators, one of which is “phong” (I forget which):

out2 =

diffuse(

interpolate( v1.N, v2.N, v3.N ),

interpolate( v1.L, v2.L, v3.L ) ) +

specular(

interpolate(

reflect( v1.N, v1.E ),

reflect( v2.N, v1.E ),

reflect( v3.N, v1.E ) ),

interpolate( v1.L, v2.L, v3.L ) );

out2 =

diffuse(

interpolate( v1.N, v2.N, v3.N ),

interpolate( v1.L, v2.L, v3.L ) ) +

specular(

reflect(

interpolate( v1.N, v2.N, v3.N ),

interpolate( v1.E, v2.E, v3.E ) ),

interpolate( v1.L, v2.L, v3.L ) );

Assuming for a moment that you have an infinitely distant light, then all you need to interpolate is N and reflect( N, E ). Each of those can be calculated in software and sent down as texture coordinates in the range (0,1). These, in turn, look up in a cube map which “normalizes” the value to a unit-length RGB triple in (-128,127) space. You can bind the same cube map on both texture units.

Then your output looks like:

out2 =

dot_clamp( TEX0, L ) +

raise( dot_clamp( TEX1, L ), power ) ;

If you’re using register combiners, and use the final combiner to raise the specular value, you can probably get an exponent of 8 (IIRC). Especially if you cheat on the “power” part and add as much biasing and clamping to add as much linear fall-off as you can

Hmm, come to think of it, you can probably use NORMAL_MAP and REFLECTION_MAP texgen to actually give you the right texture coordinates, without having to calculate them in software. You’d still need the normalization, but this is a huge improvement in speed

If you want to get a diffuse color map in there, you need to do it in two passes; one to add in diffuse * colormap, and one to add in specular. At that point, you can raise specular to a higher power using the freed-up combiner, and/or use a gloss map (yay!). GF2 doesn’t allow dependent texture reads, so you can’t use a look-up texture for the specular function, though. (You can do awesome anisotropic things with that

Also note that I prefer to view specular as expressed through reflection (because that’s what it is) rather than as expressed with half-angle, but they are equivalent (modulo some n2 power of what you get out of the first dot product, I think). You can re-write in terms of half-angle just fine. I forget whether doing so GAINS you power, or LOSES power, compared to reflection.

Here’s a question, the answer to which I’m too busy to look up right now: which of out2 and out3 is “Phong” shading? And then, what’s the other one called? Or are they actually interchangeable?