TBA Perspective Texture-Mapping Scanliner

The information on this page is taken from a news from Martin Piper for TBA Software in the newsgroup rec.games.programmer.




/* (C) TBA Software 1996 */

/* A perspective texture mapping tutorial. */

/* Theory behind perspective correction is simple. During a normal texture
	map edge trace the u,v values are interpolated with a linear value. This
	is wrong if the 'z' value per pixel changes. To get around this you have
	to use a non-linear interpolation value for the u,v components OR turn the
	non-linear interpolation into a linear one typically by dividing by Z say.
	The latter is implemented here. */

/* Some simple five minute improvements that could be made but would make the
	idea behind perspectve correction harder to follow.

	1. Instead of dividing by the 'z' correction value per pixel only figure
		this out every 16 pixels then linearly interpolate.
	2. Instead of dividing by the 'z' correction value per pixel use quadratic
		approximation. This would add in two new adds and remove the 'z' add.
	3. Improve the edge tracing logic to remove unneccassary tests and to
		improve plotting accuracy. ( This would remove the jagged edges seen
		on the polygon. )
	4. Try adding smooth shading.
*/


#include <stdlib.h>
#include <stdio.h>
#include "TAG2_defs.h"

/* Data pointer for the front screen buffer */
char *frontscreenpixels;

/* Purpose, setup a screen with various options */
void main(int argc, char *argv[])
{
	TAG2_context mycontext;

	TAG2_Init();
	mycontext=TAG2_CreateContext();
	/* Sets up a 640x480x15 bit single buffered full screen mode*/
	TAG2_OutputFacility(mycontext,"Width=640:Height=480:Depth=15:FullScreen:Singlebuffer");

	/* Make the frontscreen pointer point to the screen pixels */
	frontscreenpixels=mycontext->TAG2_FrontOutputFacility->map_pixels;

	TAG2_Text(TAG2_COLOUR(255,255,255), "Homerton.Medium",15,"640,480 15 bit screen");

	/* Bung up a demo texture map on the screen, obviously I don't want to give the source
	for the real plotters here!!! */
	drawstuff();
			
	getchar();

	/* Restore mode and memory and exit */
	TAG2_DestroyContext(mycontext);
	TAG2_Exit();
	exit(EXIT_SUCCESS);
}


/* Note all values are fixed point ints hence the #defines */
/* Accuracy for a number */
#define FMATH 20
/* A value of 'one' with lots of fixed point accuracy */
#define BIGONE (1<<22)


/* Draws a perspective correct texture map with four corners */
/* Designed to explain the theory not optimal code */
/* Has no clipping what so ever */
/* Assumes the points are drawn in a clockwise order from the top left */
/* Poly must not be concave or twisted etc etc. */
void drawstuff(void)
{
	/* Vertex values and texture values at each vertex */
	int vertx[4],verty[4],vertz[4],verttu[4],verttv[4];
	/* 1/z, tu/z and tv/z values */
	int vert1z[4],verttu1z[4],verttv1z[4];
	int sx[4],sy[4];
	char *pos,*scrdat;
	short *poss;
	/* Edge counters and y positions */
	int edl=0, edr=0, scrypos=0, botmost;
	int nedl, nedr;
	/* Two values per side ( left and right side ) */
	int grdx[2],px[2],grdtu[2],grdtv[2],ptu[2],ptv[2],pz[2],grdz[2];
	/* Values for the scanline plotter */
	int stu,stv,sgtu,sgtv,sz,sgz,slength;
	int realu,realv;

	/* A counter */
	int i, numpoints=4;

	/* Pointer to the top left of pixel data */
	scrdat=(char *) frontscreenpixels;

	/* Setup the 3D points for the polygon */
	vertx[0]=-200; verty[0]= 200; vertz[0]=140;
	vertx[1]= 200; verty[1]= 200;	vertz[1]=380;
	vertx[2]= 200; verty[2]=-200;	vertz[2]=380;
	vertx[3]=-200; verty[3]=-200;	vertz[3]=180;

	/* u/v coordinates for each vertex */
	verttu[0]=8;	verttv[0]=8;
	verttu[1]=31;	verttv[1]=8;
	verttu[2]=31;	verttv[2]=31;
	verttu[3]=8;	verttv[3]=31;

	/* Xform to screen and add in the rest of the values. */
	for (i=0;i<numpoints;i++)
	{
		sx[i]=320+((vertx[i]*128)/vertz[i]);
		sy[i]=240-((verty[i]*128)/vertz[i]);

		vert1z[i]=BIGONE/vertz[i];

		verttu1z[i]=vert1z[i]*verttu[i];
		verttv1z[i]=vert1z[i]*verttv[i];
	}

	/* Find the top most vertex */
	scrypos=65536; botmost=-1;

	for (i=numpoints-1;i>=0;i--)
	{
		if (sy[i]<=scrypos)
		{
			edl=i; scrypos=sy[i];
		}
		if (sy[i]>=botmost)
		{
			botmost=sy[i];
		}
	}

	/* Set both edge flags to this the top most vertex */
	edr=edl;

	/* Setup the screen postition. */
	pos=scrdat+scrypos*640*2;

	/* Commence the screen plotting loop. */
	while (scrypos<=botmost)
	{
		/* Test for new left edge. */
		/* This always happens first time around in this loop */
		if (scrypos==sy[edl])
		{
			/* Compute new left edge index counter. Remember we are assuming a
				clockwise polygon */
			nedl=edl-1; if (nedl<0) nedl=numpoints-1;

			/* trap out the instance of point on the same line */
			/* This would technically mean a concave poly if not occuring on the
				first line wouldn't it?
				If so then remove this from the loop. */

			while (sy[edl]==sy[nedl])
			{
				edl=nedl;
				nedl=edl-1; if (nedl<0) nedl=numpoints-1;
			}

			/* Compute all inital left edge positions and gradients to the next
				vertex */
			/* In order :
						Start of scanline x pos,
						Start of scanline 'z' pos, ( its really 1/z )
						Start of scanline 'tu' pos, ( its really tu/z )
						Start of scanline 'tv' pos, ( its really tv/z )
			*/						

			px[0]=(sx[edl]<<FMATH);
			pz[0]=(vert1z[edl]);
			ptu[0]=(verttu1z[edl]);
			ptv[0]=(verttv1z[edl]);

			/* Compute gradients, all these are now linear because we have
				divided by Z */
			grdx[0]=((sx[nedl]-sx[edl])<<FMATH)/(sy[nedl]-sy[edl]);
			grdz[0]=(vert1z[nedl]-vert1z[edl])/(sy[nedl]-sy[edl]);
			grdtu[0]=(verttu1z[nedl]-verttu1z[edl])/(sy[nedl]-sy[edl]);
			grdtv[0]=(verttv1z[nedl]-verttv1z[edl])/(sy[nedl]-sy[edl]);

			/* Update next vertex counter */
			edl=nedl;
		}

		/* Test for new right edge. */
		/* This always happens first time around in this loop */
		if (scrypos==sy[edr])
		{
			nedr=edr+1; if (nedr>=numpoints) nedr=0;

			/* trap out the instance of point on the same line */
			/* This would technically mean a concave poly if not occuring on the
				first line wouldn't it?
				If so then remove this from the loop. */

			while (sy[edr]==sy[nedr])
			{
				edr=nedr;
				nedr=edr+1; if (nedr>=numpoints) nedr=0;
			}

			/* Compute all inital right edge positions and gradients to the next
				vertex */
			/* In order :
						End of scanline x pos,
						End of scanline 'z' pos, ( its really 1/z )
						End of scanline 'tu' pos, ( its really tu/z )
						End of scanline 'tv' pos, ( its really tv/z )
			*/						

			px[1]=(sx[edr]<<FMATH);
			pz[1]=(vert1z[edr]);
			ptu[1]=(verttu1z[edr]);
			ptv[1]=(verttv1z[edr]);

			/* Compute gradients, all these are now linear because we have
				used 1/Z */
			grdx[1]=((sx[nedr]-sx[edr])<<FMATH)/(sy[nedr]-sy[edr]);
			grdz[1]=(vert1z[nedr]-vert1z[edr])/(sy[nedr]-sy[edr]);
			grdtu[1]=(verttu1z[nedr]-verttu1z[edr])/(sy[nedr]-sy[edr]);
			grdtv[1]=(verttv1z[nedr]-verttv1z[edr])/(sy[nedr]-sy[edr]);

			/* Update next vertex counter */
			edr=nedr;
		}

		/* Compute the scanline values */
		slength=px[1]-px[0];
		slength=slength>>FMATH;

		/* Set some start scanline values */
		stu=ptu[0]; stv=ptv[0]; sz=pz[0];
		if (slength>0)
		{
			/* If length is not 0!! then compute the linear interpolants for this
				scanline */
			sgtu=(ptu[1]-ptu[0])/(slength);
			sgtv=(ptv[1]-ptv[0])/(slength);
			sgz=(pz[1]-pz[0])/(slength);
		}

		/* Compute the scanline start position remembering to remove accuracy */
		poss=(short *) ((char *)pos+(px[0]>>FMATH)*2);

		/* Plot the scanline */
		for (i=0;i<slength;i++)
		{
			/* Compute the real u and v values for the texture map. Here these
			are just plotted as red/blue components to avoid having to load a
			texture. */
			realu=stu/sz; realv=stv/sz;
			*poss++=realv + ((realu)<<10);

			/* Update the components for u/z v/z and 1/z */

			stu+=sgtu; stv+=sgtv; sz+=sgz;
		}

		/* Update screen and edge counters */
		scrypos++; pos+=640*2;

		/* Using the gradients computed for each edge */
		px[0]+=grdx[0];
		pz[0]+=grdz[0];
		ptu[0]+=grdtu[0];
		ptv[0]+=grdtv[0];
		px[1]+=grdx[1];
		pz[1]+=grdz[1];
		ptu[1]+=grdtu[1];
		ptv[1]+=grdtv[1];
	}
}


Back to the 3D Engines List