QB CULT MAGAZINE
Vol. 2 Iss. 4 - October 2001

Graphics Coding, Part 4
A Basic 3D Engine

By Sane <sane@telia.com>

Finally, here's the 4th part in the graphics coding series, and this time we'll be making the base for a 3D engine, that we'll be building in a couple of articles from now on.

Basic 3D

A lot of you probably know this already, but I'm gonna write it anyways in case someone doesn't.

3D (3 dimensional) means that there are 3 dimensions: width, height and depth. A normal computer screen has only got width and height, but when making 3D on a computer, you simulate a third dimension, that goes into the screen (depth), and since X is horizontal position, and Y is vertical, Z is used when referring to how far away something is (whatever that should be called).

3D Scene Storage

First I'm gonna explain how we're gonna store the 3D scene for the engine. The method I like to use for storing a 3D scene is to have an array where the vertices (the points in 3D space), are stored, and another one where the polygons are stored, so that instead of storing the x, y and z values with the poly, the poly refers to vertices, and thus more than one poly can make use of the same vertex. This saves memory, makes it easier to avoid gaps between polys, and also lets you avoid calculating the position for the same point more than once per frame, in case several polys make use of the same point, which is true for approximately 99.9% of all 3D scenes with more than 3 polys :)

Here's the code we'll use for initializing the arrays:

TYPE VertexType
 X AS SINGLE
 Y AS SINGLE
 Z AS SINGLE
END TYPE

DIM SHARED Vertex(0 TO NumOfVertices) AS VertexType
DIM SHARED Poly(0 TO NumOfPolys, 2) AS INTEGER

3D->2D

The basic rule of how to do 3D on a 2D computer screen, is that objects that are twice as far away look half as big, and to achieve that effect, you divide X and Y by Z. Also, since the viewer is most often not pressing his/her face to the computer screen (as far as I know...), the formulas should involve the distance the viewer has from the screen. I usually use something like 300, which gives pretty good results.

I don't remember why exactly, but if you don't use field of view*x instead of just x, you get strange results, so we'll do that :) The field of view is also best around 300. The last thing the formulas should do is to make a point with the (X,Y) position 0,0 go to the center of the screen, cause otherwise it would look wierd, since the center of the viewpoint would be the top-left corner. The center of a screen in VGA mode 13h (SCREEN 13) is 160,100, so that's what we'll add to the calculated screen coordinates.

And here are the formulas we get:

ScreenX=FOV*X/(Distance-Z)+160
ScreenY=FOV*Y/(Distance-Z)+100

The Engine

This is all we need to know to make the first version of the engine, so here's the code for the engine:

'Made by Sane at the 9th of November 2001, for QBCM
'Set amount of vertices and polys
CONST NumOfVertices = 50
CONST NumOfPolys = 10

'Variable initializing
TYPE VertexType
 X AS SINGLE
 Y AS SINGLE
 Z AS SINGLE
END TYPE

DIM SHARED Vertex(0 TO NumOfVertices) AS VertexType
DIM SHARED Poly(0 TO NumOfPolys, 2) AS INTEGER

'Creating a random scene
FOR i = 0 TO NumOfVertices
 Vertex(i).X = INT(RND * 200) - 50
 Vertex(i).Y = INT(RND * 200) - 100
 Vertex(i).Z = INT(RND * 200) - 100
NEXT i

FOR i = 0 TO NumOfPolys
 FOR j = 0 TO 2
  Poly(i, j) = INT(RND * NumOfVertices)
 NEXT j
NEXT i

'Set screen mode
SCREEN 13

FOR i = 0 TO 100
 'A simple animation thing
 FOR j = 0 TO NumOfVertices
  Vertex(j).X = Vertex(j).X - 1
 NEXT j
 'Redraw, wait for retrace and clear the screen
 DrawScene
 WAIT &H3DA, 8
 CLS
NEXT i

SUB DrawScene
 FOR p = 0 TO NumOfPolys
  X1 = Vertex(Poly(p, 0)).X
  X2 = Vertex(Poly(p, 1)).X
  X3 = Vertex(Poly(p, 2)).X
  Y1 = Vertex(Poly(p, 0)).Y
  Y2 = Vertex(Poly(p, 1)).Y
  Y3 = Vertex(Poly(p, 2)).Y
  Z1 = Vertex(Poly(p, 0)).Z
  Z2 = Vertex(Poly(p, 1)).Z
  Z3 = Vertex(Poly(p, 2)).Z
  SX1 = 256 * (X1) / (256 - Z1) + 160
  SX2 = 256 * (X2) / (256 - Z2) + 160
  SX3 = 256 * (X3) / (256 - Z3) + 160
  SY1 = 256 * (Y1) / (256 - Z1) + 100
  SY2 = 256 * (Y2) / (256 - Z2) + 100
  SY3 = 256 * (Y3) / (256 - Z3) + 100
  LINE (SX1, SY1)-(SX2, SY2)
  LINE (SX2, SY2)-(SX3, SY3)
  LINE (SX3, SY3)-(SX1, SY1)
 NEXT p
END SUB

It's quite flickery, but the point with it was only to make a basic 3d engine that works, which was achieved :) As usual, the code is available with the downloadable version of this issue, as 3DENGINE.BAS.

Next article will probably be about 3D rotations, see ya then.

-Sane