Copy Link
Add to Bookmark
Report

3D Viewing System Tutorial

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

3D Viewing System Tutorial Release 1
Copyright (c) 1998, Gautam N. Lad.

Released January 10, 1998

This file is part of the GNL3D_R1.ZIP tutorial package. DO NOT distribute this file separately, or tamper with it in any way. Please DO NOT redistribute modified versions of this document. If you would like to modify this document, please send me the modified version, and I will release it as part of the next Release of the tutorial package (you will be acknowledged for any contributions).

Gautam N. Lad
E-Mail: gautam@interlog.com
Website: http://www.interlog.com/~gautam

INTRODUCTION

It is recommended that you understand some Algebra, mainly Vectors and Matrices, and basics of mathematics/trigonometry. Most importantly, you must know how Vectors and Matrices work, and what their purpose are. You should be fairly comfortable with them, otherwise refer to a school Algebra+Geometry textbook, topics, or the following site, which is very good at providing links, as well as tutorials on getting started in 3D computer graphics:

STEEL's Programming Resource Page
http://www.geocities.com/SiliconValley/Park/9784

Please be familiar with C (and maybe C++) as well. The included tutorial uses pointers a lot, so if you have problems with pointers (like I did once), please understand them first.

Also, I am not brilliant at 3D programming. I am good, but I may not be good enough to use methods the pros. use. My methods are simple, and are easy to understand; that's why I use them.

THE UVN VECTOR SYSTEM

Using the UVN vector system, we can add a camera to our 3D world. We need to know a few things about the camera, in order to transform the world. We need to know the Right, Up, and Direction vectors of a camera. Try this.

  • Point your Left hand straight up in the air (vertically)
  • Point your Right hand to the right (horizontally)
  • Look straight forward

From this, your Right hand is the Right vector (U), your Left hand is the Up vector (V), and your line of sight is the Direction vector (N). As you move your upper body around, looking at things, you notice the direction of your hands change. (NOTE: Don't move or tilt your head, but your entire upper body).

When your camera is oriented a certain way, we need to know these vectors: Right, Up, and Direction.

Here's how we calculate these vectors:

Finding the Direction vector (N)

The direction vector is easy to calculate.

  Direction = CameraLookFrom - CameraLookAt

You will need to turn this vector, into a Unit vector.

Example:

      CameraLookFrom = (5, 5, -10) 
CameraLookAt = (1, 1, 0)

N = CameraLookFrom - CameraLookAt
= (5-1, 5-1, -10-0)
= (4, 4, -10)
N = (0.3481552, 0.3481552, -0.8703879) (Normalized)
N = -N
N = (-0.3481552, -0.3481552, 0.8703879)

NOTE: We are doing N = -N to change the direction of the N vector, because if we don't do that, our image will look very different, from what we are expecting (just trust me on this).

Finding the Up vector (V)

The Up vector is a bit harder to calculate, but it goes something like this:

First, we need the Cosine of the angle between our assumed Up direction, which is (0,1,0), and the Direction vector. To find the Cosine of the angle, we use the Dot Product.

      v = DotProduct(V, N) 
V = V - (v*N)

Example:

      CameraLookFrom 	= (5, 5, -10) 
CameraLookAt = (1, 1, 0)
V = (0, 1, 0) (assumed Up vector)

V = (-0.3481552, -0.3481552, 0.8703879) (Normalized)

v = DotProduct(V, N);
= -0.348155

V = v*N
= (-0.129302, 0.937437, 0.323254)

Finding the Right vector (U)

The Right vector is easy to calculate.

  U = CrossProduct(V, N)

NOTE: You must do VxN. Doing NxV will give the normal facing the opposite way.

Example:

      CameraLookFrom 	= (5, 5, -10) 
CameraLookAt = (1, 1, 0)
V = (0, 1, 0) (assumed Up vector)

V = -0.129302, 0.937437, 0.323254)
N = (-0.3481552, -0.3481552, 0.8703879)

U = CrossProduct(V, N);
= (0.928477, -0.000000, 0.371391)

There you have it. You now have calculated the Right (U), Up (V), and Direction (N) vectors. Yippie! But hold the celebration; there is still some other things we have to do.

APPLYING THE UVN MATRIX TO POINTS

We now have calculated the UVN vectors, and therefore, we can now apply these vectors to our points in the World-space. Basically, the UVN vectors represent rotations based on the camera position and direction. These vectors will rotate the world while it stays stationary at the center.

We need to apply two matrices. The first matrices translates all the points so the camera will be at the origin, and to rotate the world so it's in the camera. Instead of doing this, we can multiply these matrices together, to obtain a single matrix.

The translation matrix:

      | 1 0 0  -CameraLocation | 
T = | 0 1 0 -CameraLocation |
| 0 0 1 -CameraLocation |

The Rotation matrix:

      | Ux  Uy  Uz 0 | 
R = | Vx Vy Vz 0 |
| Nx Ny Nz 0 |

The result of T*R=M

      | Ux  Uy  Uz -(Ux*Cx + Uy*Cy + Uz*Cz)| 
M = | Vx Vy Vz -(Vx*Cx + Vy*Cy + Vz*Cz)|
| Nx Ny Nz -(Nx*Cx + Ny*Cy + Nz*Cz)|

When you apply this matrix to your vector you have transformed the point P from World-space to the point P' which is in Camera-space.

When you've got the new point, P', it has to be transformed to another space, known as, yup you guessed it, the Screen-space. To draw it on the screen, you use the following pseud-code:

    FOV         = 40 
OneDeg = PI/180
AspectRatio = SCREEN_WIDTH / SCREEN_HEIGHT

HPC = HALF_SCREEN_WIDTH / tan((FOV/2)*OneDeg)
VPC = AspectRatio * HALF_SCREEN_HEIGHT / tan((FOV/2)*OneDeg)

ScreenX = ((HPC*P.x / P.z ) + HALF_SCREEN_WIDTH)
ScreenY = (HALF_SCREEN_HEIGHT + (VPC*P.y / P.z))

NOTE: The reason we're multiplying the VPC portion by AspectRatio, is because we don't want squashed objects. As you may see, your screen Width is usually larger then the Height by a factor of 1.3333...3 (or 4/3). This is true in most cases such as 640x480, 800x600, 1024x768, etc. but not sizes such as 320x200 (which is 1.6). Therefore to prevent any sort of objects from being displayed squashed we multiply by the aspect ratio.

EXAMPLE PROGRAM

The supplied program includes various functions that you are free to use in your own code (just let me know before you do). I have also added in some functions which are not demonstrated by the program (such as display 3D points on a 2D screen), because they are for you to try in your own programs. I have made the code as clear as possible, using mainly comments at the beginning of functions. I have also used variables that are easy to understand such as 'Apply_Matrix_To_Vector' instead of something else (that might confuse you). The C source as well as the DOS executable is supplied.

FINAL WARNING

The methods I have used, may not be perfect. Af far as I know, one thing is not done right. The way I calculate the screen co-ordinates can be achieved using matrices, but I don't know how to do this (yet).

Other than that, I hope this tutorial has been of help to you, and I hope I am right with the methods used. Please inform me if you find any mistakes in this document or the example program. You can contact me by E-Mail or by visiting my website.

GNL3D_R1.C

// ************************************************************************** 
//
// 3D Viewing System Tutorial Release 1 - Copyright (c) 1998, Gautam N. Lad.
//
// Released January 10, 1998
//
// This demonstration program is part of the GNL3D_R1.ZIP tutorial package.
// DO NOT distribute this program separately. Other than that, you are
// allowed to experiment with this program and modify it as you wish.
// DO NOT however, distribute your modified version with the original
// version.
//
// Gautam N. Lad
// E-Mail: gautam@interlog.com
// Website: http://www.interlog.com/~gautam
//
// **************************************************************************

#define OneDeg 0.0174532925199432955 // Equivalent to pi/180

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <conio.h>

// Vector structure
typedef struct {
float x, y, z;
} Vector;

// Camera structure
typedef struct {
Vector LookFrom, LookAt;
float FOV;
} Camera;

// Matrix structure
typedef struct {
float r[3][3];
float t[3];
} Matrix;


// --------------------------------------------------------------------------
// Find magnitude of vector 'a'. 'mag' holds calculated value
// --------------------------------------------------------------------------
void Magnitude(Vector *a, float *mag)
{
(*mag) = sqrt((a->x*a->x) + (a->y*a->y) + (a->z*a->z));
}


// --------------------------------------------------------------------------
// Normalizes vector 'a' (turn it into a unit vector)
// --------------------------------------------------------------------------
void Normalize(Vector *a)
{
float m;

Magnitude(a, &m); // find magnitude of vector 'a'

a->x=a->x/m;
a->y=a->y/m;
a->z=a->z/m;
}


// --------------------------------------------------------------------------
// Computes Dot Product of vector 'a' and 'b'.
// 'dot' holds the cosine of the calculated angle.
// To convert value to angle, use this:
// angle = acos(value)/(pi/180);
// --------------------------------------------------------------------------
void Dot_Product(Vector *a, Vector *b, float *dot)
{
Normalize(a);
Normalize(b);
(*dot) = (a->x*b->x + a->y*b->y + a->z*b->z);
}


// --------------------------------------------------------------------------
// Computes the Cross Product of vector 'a' and 'b'.
// Calculated vector, 'c', is then normalized.
// --------------------------------------------------------------------------
void Cross_Product(Vector *a, Vector *b, Vector *c)
{
c->x = (a->y*b->z)-(a->z*b->y);
c->y = (a->z*b->x)-(a->x*b->z);
c->z = (a->x*b->y)-(a->y*b->x);

Normalize(c);
}


// --------------------------------------------------------------------------
// Applies matrix 'm' to vector 'v'.
// --------------------------------------------------------------------------
void Apply_Matrix_To_Vector(Matrix *m, Vector *v)
{
v->x = (v->x*m->r[0][0]) + (v->y*m->r[0][1]) + (v->z*m->r[0][2]) + m->t[0];
v->y = (v->x*m->r[1][0]) + (v->y*m->r[1][1]) + (v->z*m->r[1][2]) + m->t[1];
v->z = (v->x*m->r[2][0]) + (v->y*m->r[2][1]) + (v->z*m->r[2][2]) + m->t[2];
}


// --------------------------------------------------------------------------
// Gets a matrix based on the camera data. A pointer to the
// camera is passed in 'c' and the resulting matrix is stored
// in 'm'.
// --------------------------------------------------------------------------
void Get_Camera_Matrix(Camera *c, Matrix *m)
{
Vector U={1,0,0}, V={0,1,0}, N={0,0,-1};
float v;

N.x = c->LookFrom.x-c->LookAt.x;
N.y = c->LookFrom.y-c->LookAt.y;
N.z = c->LookFrom.z-c->LookAt.z;
Normalize(&N);
N.x=-N.x; N.y=-N.y; N.z=-N.z;

Dot_Product(&V,&N,&v);
V.x-=(v*N.x); V.y-=(v*N.y); V.z-=(v*N.z);
Normalize(&V);

Cross_Product(&V,&N,&U);

m->r[0][0] = U.x; m->r[0][1] = U.y; m->r[0][2] = U.z;
m->r[1][0] = V.x; m->r[1][1] = V.y; m->r[1][2] = V.z;
m->r[2][0] = N.x; m->r[2][1] = N.y; m->r[2][2] = N.z;

m->t[0] = -(U.x*c->LookFrom.x + U.y*c->LookFrom.y + U.z*c->LookFrom.z);
m->t[1] = -(V.x*c->LookFrom.x + V.y*c->LookFrom.y + V.z*c->LookFrom.z);
m->t[2] = -(N.x*c->LookFrom.x + N.y*c->LookFrom.y + N.z*c->LookFrom.z);
}

// --------------------------------------------------------------------------
// Applies the camera matrix 'm' to the vector 'v'. This function also
// calculates the screen location of the calculated vector and
// passes the screen x and y co-ordinates to the variables 'sx', and
// 'sy'. You must also pass in the 'FOV' of the camera being used
// You must then supply the SCREEN_WIDTH and SCREEN_HEIGHT. Basically, these
// are the size of the viewport the image will be projected on.
// --------------------------------------------------------------------------
void Apply_Camera_Matrix(Matrix *m, Vector *v, float *sx, float *sy,
float *FOV, int SCREEN_WIDTH, int SCREEN_HEIGHT)
{
int HALF_SCREEN_WIDTH = SCREEN_WIDTH/2;
int HALF_SCREEN_HEIGHT = SCREEN_HEIGHT/2;

float AspectRatio = SCREEN_WIDTH/SCREEN_HEIGHT;

float HPC = HALF_SCREEN_WIDTH / tan((*FOV/2)*OneDeg);
float VPC = AspectRatio * HALF_SCREEN_HEIGHT / tan((*FOV/2)*OneDeg);

Apply_Matrix_To_Vector(m,v);

*sx = ((HPC*v->x / v->z ) + HALF_SCREEN_WIDTH);
*sy = (HALF_SCREEN_HEIGHT - (VPC*v->y / v->z));
}

void main()
{
Camera c;
Matrix m;

clrscr();
puts("************************************************************************\n");
puts("3D Viewing System Tutorial Release 1 - Copright (c) 1998, Gautam N. Lad.");
puts("Released January 10, 1998");
puts("************************************************************************\n");

printf("Enter camera Look From (eg. 3 3 -3): ");
scanf("%f %f %f",&c.LookFrom.x,&c.LookFrom.y,&c.LookFrom.z);

printf("Enter camera Look At (eg. 1 1 1): ");
scanf("%f %f %f",&c.LookAt.x,&c.LookAt.y,&c.LookAt.z);

Get_Camera_Matrix(&c, &m);

puts("\n\n\nHere's the output...\n");
printf("Camera LookFrom = (%5.2f, %5.2f, %5.2f)\n",c.LookFrom.x,c.LookFrom.y,c.LookFrom.z);
printf("Camera LookAt = (%5.2f, %5.2f, %5.2f)\n\n",c.LookAt.x,c.LookAt.y,c.LookAt.z);

printf("U = (%5.2f, %5.2f, %5.2f)\n",m.r[0][0],m.r[0][1],m.r[0][2]);
printf("V = (%5.2f, %5.2f, %5.2f)\n",m.r[1][0],m.r[1][1],m.r[1][2]);
printf("N = (%5.2f, %5.2f, %5.2f)\n",m.r[2][0],m.r[2][1],m.r[2][2]);
}

← 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