Here are some texture mapping tricks I use in BM. This mainly helps eliminate divides in the polygon/edge setup part, without using tables or changing pixel/texel stepping part. I haven't seen this before, but it's not terribly complicated so I'm not claiming it's in any way new. But it's interesting in any case.
Whether this is useful or not depends on how many edges you need to set up and what you're doing with attributes but it can be a good saving if divide is expensive, mul is cheap and/or you don't have space for big tables (e.g DSP).
The 'classic' way to do perspective correction is as follows:
Code: Select all
; set up screen-z interpolant for a range or span of pixels
t1 = 1/z1;
t2 = 1/z2;
; set up screen-u interpolant
uz1 = u1*t1;
uz2 = u2*t2;
; interpolate screen-z, screen-u for pixel 'i'
ti = linear_interpolate(t1,t2,i);
uzi = linear_interpolate(uz1,uz2,i);
; find world-u at pixel 'i'
u = uzi / ti;
The 'trick' I brewed for BM takes advantage of a relationship between the true perspective curve generated across the range for (uz/t), the curve generated by the inverse range (uz*t) and the z interpolant direction. In other words, by flipping the z interpolant range, the range being interpolated can be u*z instead of u/z.
So the modified code looks like this:
Code: Select all
; set up screen-z interpolant
; USE *INVERTED Z RANGE*, and reciprocal of [1/z] i.e. just [z]
; this saves two divides per Z range, or one per Z term
t1 = z2;
t2 = z1;
; set up screen-u interpolant
uz1 = u1*t1;
uz2 = u2*t2;
; interpolate screen-z, screen-u
ti = lerp(t1,t2,i);
uzi = lerp(uz1,uz2,i);
; find world-u
u = uzi / ti;
...the problem with this is that you can't easily access the true z at each pixel, which might be useful for depth cue, a linear (?) zbuffer or something else. However you can easily recover it as follows:
Code: Select all
; set up screen-z interpolant
; USE *INVERTED RANGE* -, reciprocal of [1/z] i.e. just [z]
t1 = z2;
t2 = z1;
; interpolating to retrieve true z: lerp(z1*z2, z2*z1, i) is a constant! so we don't have to interpolate.
zc = z1*z2;
; set up screen-u interpolant
uz1 = u1*t1;
uz2 = u2*t2;
; interpolate screen-z, screen-u
ti = lerp(t1,t2,i);
uzi = lerp(uz1,uz2,i);
; rcp
rt = 1/ti;
; find world-u
u = uzi * rt;
; find true z
z = zc * rt;
One more trick which can be used with the above - you can recover an approximate z even without a multiply, just by flipping [ti] with respect to the original z1,z2 range. The downside is, it's not perspective correct. It can do for depth cue lighting though or low precision zbuffer.
e.g.
There are a few other unusual things in the BM renderer. When I dig more of it out I'll post.
[EDIT]
One other TM trick worth noting from the older BM code (but now excised, due to simplification efforts) is the same range-inversion approach combined with extraction of the perspective curve itself into a distortion curve, which can be applied cheaply to any attribute while stepping. This means you don't have to do any edge setup for the attributes (good if you have many of them e.g. u,v,colour.. etc) as they can be combined with the curve term using just a multiply.
The problem with it IIRC is the fact it degenerates when z1=z2 (view-perpendicular surfaces) and I hid this problem previously by using a linear texture mapping path for those walls. This was fine because it also speeds up those walls. However the whole thing was quite complicated and used more code space, and I don't like special cases in code, so it got chucked away in favour of the simpler method (which ends up faster because there is only a single u term and depth cue which is easy to generate several different ways).