Copy Link
Add to Bookmark
Report

3D Texture Mapping

DrWatson's profile picture
Published in 
atari
 · 27 Nov 2023

Sixth part of The 3D Coding Blackhole tutorial series

  • Overview
  • The Magic Numbers
  • Perspective Correct Texture Mapping

Overview

The first things you must think about when doing texture mapping, is having an array of textures and initializing 3D texture coordinates. The textures will be stored in:

#define MAXTEXTURES 16 
bitmap_t Textures[MAXTEXTURES];

We will allocate and load them from PCX files. I chosed to make all of them 64x64. We will use the texture coordinates of the polygon_t structure:

vertex_t P,M,N;

We will initialize them in a function that we will called after creating the polygons. P is the origin of the texture, M the horizontal end of the texture, and N the vertical end.

void TEX_Setup(Polygon_t * Polygon, Object_t *Object) 
{
Polygon->P.Local=P3D(Vert(1).Local.x,
Vert(1).Local.y,
Vert(1).Local.z);
Polygon->M.Local=P3D(Vert(0).Local.x,
Vert(0).Local.y,
Vert(0).Local.z);
Polygon->N.Local=P3D(Vert(2).Local.x,
Vert(2).Local.y,
Vert(2).Local.z);
}

We need to transform them like any other vertices of the object, so we will create a world transforming and an align transforming function right now:

void TR_Object(Object_t *Object, float matrix[4][4]) 
{
int v,p;
for(v=0; v<Object->VertexCount; v++)
VEC_MultMatrix(&Object->Vertex[v].Local,matrix,&Object->Vertex[v].World);
for(p=0; p<Object->PolygonCount; p++)
{
VEC_MultMatrix(&Object->Polygon[p].P.Local,matrix,&Object->Polygon[p].P.World);
VEC_MultMatrix(&Object->Polygon[p].M.Local,matrix,&Object->Polygon[p].M.World);
VEC_MultMatrix(&Object->Polygon[p].N.Local,matrix,&Object->Polygon[p].N.World);
}
}
void TR_AlignObject(Object_t *Object, float matrix[4][4])
{
int v,p;
for(v=0; v<Object->VertexCount; v++)
VEC_MultMatrix(&Object->Vertex[v].World,matrix,&Object->Vertex[v].Aligned);
for(p=0; p<Object->PolygonCount; p++)
{
VEC_MultMatrix(&Object->Polygon[p].P.World,matrix,&Object->Polygon[p].P.Aligned);
VEC_MultMatrix(&Object->Polygon[p].M.World,matrix,&Object->Polygon[p].M.Aligned);
VEC_MultMatrix(&Object->Polygon[p].N.World,matrix,&Object->Polygon[p].N.Aligned);
}
}

The Magic Numbers

Now that we have the transformed texture coordinates, our objective is to find what is the horizontal and the vertical coordinates of the pixel in the texture bitmap that should be drawn on the screen. These texture coordinates will be called u and v. And the equations that will give us these coordinates are:

u = \frac {a \cdot TEXTURE\_SIZE}{c}

and

v = \frac {b \cdot TEXTURE\_SIZE}{c}

Now these magic coordinates have equations for themselves:

a = O_x + V_x j + H_x i \\ b = O_y + V_y j + H_y i \\ c = O_z + V_z j + H_z i

These O, H, V numbers are the magic numbers. They are computed from the texture coordinates in the following way:

O_x = N_xP_y - N_yP_x \\ H_x = N_yP_z - N_zP_y \\ V_x = N_zP_x - N_xP_z \\ O_y = M_xP_y - M_yP_x \\ H_y = M_yP_z - M_zP_y \\ V_y = M_zP_x - M_xP_z \\ O_z = M_yN_x - M_xN_y \\ H_z = M_zN_y - M_yN_z \\ V_z = M_xN_z - M_zN_x \\

I don't really know why it works, but it does! It seems to be some strange cross product... Anyway, now that you have the equations, let's implement the code!

Perspective Correct Texture Mapping

The computation of the O, H, V numbers requires some modifications, so we will add the following to ENG3D_SetPlane:

   //Used to fix errors that happen when the numbers get too big 
#define FIX_FACTOR 1/640

//Initialize texture vectors
P=Polygon->P.Aligned;
M=VEC_Difference(Polygon->M.Aligned,Polygon->P.Aligned);
N=VEC_Difference(Polygon->N.Aligned,Polygon->P.Aligned);

P.x*=Focal_Distance;
P.y*=Focal_Distance;

M.x*=Focal_Distance;
M.y*=Focal_Distance;

N.x*=Focal_Distance;
N.y*=Focal_Distance;

And here is the implementation of VEC_Difference (Not extremely complex...):

_3D VEC_Difference(_3D Vector1, _3D Vector2) 
{
return P3D(Vector1.x-Vector2.x,Vector1.y-Vector2.y,Vector1.z-Vector2.z);
}

Then we can compute them.

_3D O, H, V;

In ENG3D_SetPlane:

   H.x=(N.y*P.z-N.z*P.y)*FIX_FACTOR; 
V.x=(N.z*P.x-N.x*P.z)*FIX_FACTOR;
O.x=(N.x*P.y-N.y*P.x)*FIX_FACTOR;

H.z=(M.z*N.y-M.y*N.z)*FIX_FACTOR;
V.z=(M.x*N.z-M.z*N.x)*FIX_FACTOR;
O.z=(M.y*N.x-M.x*N.y)*FIX_FACTOR;

H.y=(M.y*P.z-M.z*P.y)*FIX_FACTOR;
V.y=(M.z*P.x-M.x*P.z)*FIX_FACTOR;
O.y=(M.x*P.y-M.y*P.x)*FIX_FACTOR;

Now it's time to change our VID_HLine for a TEX_HLine so that it uses texture mapping (the tough part). We must at first initialize our magic coordinates:

   a=-((long)O.x+((long)V.x*(long)j)+((long)H.x*(long)i))*64L; 
b= ((long)O.y+((long)V.y*(long)j)+((long)H.y*(long)i))*64L;
c= ((long)O.z+((long)V.z*(long)j)+((long)H.z*(long)i));
long Hx,Hy,Hz;
int u,v;
BYTE color=0;
BYTE *mapping=Textures[texture].Picture;

Then we multiply H.x and H.y by 64 so we don't have to do it for every pixel. We use long variables instead of floats too:

   Hx=H.x*-64; 
Hy=H.y*64;
Hz=H.z;

Then for each pixel, change the last parameter for texture and instead of plotting the old one do:

         if(c) 
{
u=a/c;
v=b/c;
color=mapping[((v&63)<<6)+(u&63)];
if(color)
{
zbuffer[offset]=z;
SCREENBUFFER[offset]=LightTable[light][color];
}
}
a+=Hx;
b+=Hy;
c+=Hz;

And we've got ourselves a texture mapper!

Copyright © 1996-1998 Jerome St-Louis

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT