Free Direction Texture Mapping

DrWatson

The idea of free-direction texture-mapping seems to be new to many few people, so I decided to post this short introduction.

Warning: The level of this discussion is quite introductory, if you know (or guess) what I'm going to talk about, you probably know as much as I do.

First look at the principles. In Doom (and in Wolfenstain) the method used to draw the walls is quite simple. You divide the wall into vertical lines. Then you calculate where the wall should start and where to end on the screen (A and B in my nice ascii-picture), and where in the texture space the corresponding line should start and end.

Wall: Texture: 
\ Y
\B \/\^\/\/\
^\ /\/./\/\/
. \ \/\.\/\/\
. / /\/./\/\/
./ X
/A
/

Then you simply do a highly optimized loop in which you do all the pixels in the vertical line, pick a color from the texture, put it onto the screen, and move to next position in the texture.

The floor is a bit more complicated. (I understand that Wolfenstain had no floor texturing, am I correct?) This time, the floor segment is mapped to a horizontal line, which is simple enough. However, in texture space that same line may be in any direction, so you'll have a 2D line in the texture, like this:

Floor: Texture: 
/\ Y
A/. >\B \/\/\.\/\
/ \ /\/\.\/\/
\/./\/\/\
/./\/\/\/
X

This is old and dull. Now for the new and exciting part: suppose we wish to draw a polygon in 3-space that has free orientation. A bit of thought and a simple extension of the above ideas tell us that we should use a free-direction line in the display coordinates as well.

When we map a plane with free orientation to the screen, there is always one direction on the screen, in which the z-coordinate (distance) stays the same. In doom's walls it is vertical, in doom's floors it is horizontal. But there is one such direction for every plane.

Why is constant z-coordinate important? These lines have the special property that constant movement along them corresponds to constant movement in texture space.

Read the above two paragraphs again until you have understood them, since they are the key thing. The rest is only implementation, following is a short explanation on how I did it.

For each polygon you are about to draw on the screen, do the following. Find the plane equation. From that, derive the "constant-z" direction. (Come on, take a piece of paper and a pen, it is quite easy.)

It helps to make the distinction between two cases here. Either the "constant-z" direction is more horizontal, or it is more vertical. Suppose that it is more horizontal. The constant-z line equation is now something like y = p*x, where -1

 ---- 
--- Example of a constant-z line
----
----

Now, a change in the coordinate system is in order. x is the same x as before, but y is "slanted" by the factor of p. This means that the x-axis will be "slanted" but y-axis will be the same as before.

The next thing is to convert the polygon to this coordinate system. Scan convert it line by line, but along these "slanted" (constant-z) lines.

Suppose that we are about to draw a triangle shown below, and the slanted line is the one shown above. So the path to follow on the is as follows (ascii art is back again). The path in the texture is also determined.

On the screen: In texture (eg.): 

\------- Y
A. -----/ /./\/\/\/
\ . / \/./\/\/\
\ . / /\/./\/\/
\ /B \/\/./\/\
\ / X
\ /
X

So when you render the triangle, the result would be like this. The numbers are lines of constant Z-value.

 22221110 
3332221111000
44333222211
544433332
5554444
66555
766
7

Note: you should stack the constant-z lines just as shown in the picture.

Implementation notes: this will be a bit slower than DOOM floors, since the algorithm is a bit more complicated. Another thing is that it will not be quite as cache-coherent.

If you are rendering big polygons (and have a large cache), it helps to precalculate the pixels lying on the line, so you need not worry about your Bresenham having to choose right pixels. All you need to do is offset the line to right memory offset.

The inner loop of this machine could look something like this:

zbufpointer = zbufbase + offset; 
pixelpointer = pixelbase + offset;

while (--count >= 0) <
off = *precalculatedline++;
if (z > zbufpoiner[off]) <
zbufpointer[off] = z;
pixelpointer[off] = texture(x,y);
>
x += dx;
y += dy;
>

There is an error of about 0.5 pixel-lengths, since the pixels lying on the constant-z lines are rounded to nearest pixels.

Another error can also be seen in the above picture, the line marked with 0's has a small "gap" in it, what should we do with it?

 Hannu dm@stekt.oulu.fi || You have been hacking too long when you 
Helminen dm@phoenix.oulu.fi || talk of people as users (or end-users)