Blitz2D Advanced: 3D Starfields in Blitz 2D
by Shockwave / DBF

How To Create 3D Starfields Using Blitz 2D By Shockwave / DBF.

Loads of people have asked me how to code 3D stuff using only the B2D command set and as it's my favorite area I decided that I'd do something to get you started.

After you have read this tutorial, if you have the hunger for more knowledge you can get gratification Here : CLICK FOR DBF FORUMS

If for any reason you want to email me : CLICK TO EMAIL SHOCKWAVE

Indeed a lot of very proficient programmers never learn the intricacies of 3D programming which is a shame, the internet is full of baffling information about it and after all, who has the patience to sit through it? Well, I'll tell you one thing, I certainly don't! 

We're going to start simply, before you start with this tutorial I have assumed that you have a reasanoble knowledge of the following;

Things You Need To Understand Before You Read This;
Custom Types (At the most basic level, you'll need to be able to define types and understand how data is stored / accessed in their fields).
Double Buffering (You'll need to understand what the Setbuffer and Flip commands do).
 Loops (You'll Need to understand them as they effect Types and also you'll need to know about While /Wend loops).
Variables (Locals, Globals and Constants) Also you'll need to understand the concept of Hex and also basic mathematical ( + - / * )operators.
Program Flow (The order of execution and Functions).
Writepixelfast (Just a vague idea is ok).

Ah great, you're still here, well let's begin to thing about the problems posed in getting a 3D image on a Monitor screen, take a look at the monitor, it is made up of dots, for our purposes we understand that every one of these dots has it's own X and Y co-ordinate.

X being Horizontal (Left/Right)

Y being Vertical (up/Down)

This presents us with no problems if we want to draw flat images but it presents us with a few brain teasers if we decide to add another dimension and give everything a depth co-ordinate as well. There are generally very well practiced techniques to computing things in 3 dimensions and then converting them to two dimensions for the purposes of displaying them on a TV or Monitor.

Think back and I'm sure you've seen 3D starfields before, they present an image made up of many individual dots that fly towards the screen and then peel away at the last instant before crashing through the screen ;-) Lets think of what we are trying to achieve then;

A multitude of "Stars" slowly flying into the screen in perspective, the closer the star is, the faster it should appear to move, just like in real life.

The first thing that we need to do then is to open a screen for our graphics;

 

Const xres = 640;                         X Res.
Const yres = 480;                        
Y Res.
Const depth = 32;                        
Depth.
Const window = 1;                       Window.
Const nstars = 800;                      
How Many Stars?
Const escape = 1;                        
Escape Scancode.
Const halfx = xres/2;                    
For Neatness later on X centre of screen.
Const halfy = yres/2;                    
For Neatness later on Y centre of screen.

;======================-----------------------------------------------------------------------------
; Open Graphics Screen;
;======================-----------------------------------------------------------------------------

Graphics xres,yres,depth,window
SetBuffer BackBuffer()

Generally I like to use constants for as many things in my code as I can, they are faster than other types of variable such as Globals, the only snag is that they can't be changed once they are set up, but for the things I've used them for here that's ok. I won't go into too much detail on this bit, suffice to say that I intend this routine to be flexible, able to change resolutions and the amount of stars by only having to alter one variable.

Please bear this in mind when coding 3D in B2D as things can very quickly get messy if you are a sloppy coder.

After the variables are set, I open the screen and set the double buffer to draw on the logical screen to avoid flicker :-)

Lets move onto the next bit;

;=================================------------------------------------------------------------------
; Create Type Definition For Star;
;=================================------------------------------------------------------------------

Type star
        Field xpos;
X position of star.
        Field ypos; Y position of star.
        Field zpos#; Z position of star.
        Field colour; Colour of star.
End Type 

;---------------------------------------------------------------------------------------------------
;---------------------------------------------------------------------------------------------------

We're going to use Types here for this starfield, they are not as fast as arrays but the differences are negligible and the neatness that they provide makes them more that worthwhile, as I have already hinted earlier on we have three positional fields in this type;

XPOS (Horizontal)

YPOS (Vertical)

ZPOS (Depth)

These will be the three factors concerning the stars position in our 3D world.

Point the forefinger of your left hand at the screen.

Point your thumb upwards.

Point your middle finger to the side.

Forefinger is Z, Thumb is Y Middle finger is X, if you imagine your fingers have a length of 1 then you can begin to understand how a point can be described as;

Z=100, Y=100, X=20

One hundred middle fingers across, 100 thumbs up, 20 forefingers in. Using this co-ordinate system it is also quite possible to have negative numbers too. Basically this is how 3D objects are created at their most simple level, vertices (the word vertices comes from vertex meaning point). Each point has an X,Y,Z co-ordinate, just like our stars will have!

Also there is another field;

COLOUR

It's actually cool that Blitz recognises the Americal version of the word "colour" as a reserved word, freeing it up for use in variables for Brits like me :-p We're going to assign the field "Colour" one of several values to denote what colour it should be drawn in, I thought it would be nice to have all different shades of star.

Let's move on and actually create the stars then;

;==============-------------------------------------------------------------------------------------
; Create Stars;
;==============-------------------------------------------------------------------------------------

For loop = 1 To nstars
    starfield.star = New star
    starfield\xpos = Rand(-10000,10000);
         Large X To Be Divided By Z.
    starfield\ypos = Rand(-10000,10000);         Large Y To Be Divided By Z.
    starfield\zpos# = Rnd(0,30);                        
Z,the Depth into the screen.
    starfield\colour = Rand(1,7);                        
Seven Possible colours.
Next

;---------------------------------------------------------------------------------------------------
;---------------------------------------------------------------------------------------------------

Here we create the stars and put values into them, you'll notice a few things about this, firstly X and Y are HUGE ranges compared to the Z. The way I'd like you to think about this is a large box that is a lot taller and wider than it is deep having 800 points placed inside it. (X,Y,Z) There is a good reason to use these huge numbers for X and Y. Basically it's for perspective.

The real revelation here should be that Z is a floating point number, if you take the number 10,000 and divide it by 30 you get; 333.

If you multiply 333*2 you get 666, it's not a satanic reference but it's as near as damnit to our X resolution (640).

So let's say that X = -10000 and Z = 30, X/Z would be -333

This is how we convert a 3D co-ordinate into a 2D one, we divide X by Z and Y by Z

Imagine that X was 1000 and Z was 30 and we divide 100/30 we get 33, so in actual fact we convert a 3D world co-ordinate of 1000 into a display co-ordinate of 33 and of course if we happen to be decreasing the Z progressively to 0 our dot will move off the screen as the result changes.

That's perspective in a nutshell really and as soon as you can visualise that concept you're sorted.

Here's a simple little equation;

DISPLAYX = (WORLDX / WORLDZ)+XOFFSET

DISPLAYY = (WORLDY / WORLDZ)+YOFFSET

Stuck? Go back and re-read!

Understand? Good, let's move on;

;===========----------------------------------------------------------------------------------------
; Main Loop;
;===========----------------------------------------------------------------------------------------

While Not KeyDown(escape)
;          Loop Starts.
                starfield();                         Do Starfield.
                Flip;                                
Double Buffer.
                Cls;                                
Clear The Screen.
                Wend;                            
Loop Ends.
End
;---------------------------------------------------------------------------------------------------
;---------------------------------------------------------------------------------------------------

The above code is just the main loop, if I have to explain that you should be reading a newbies tutorial!

However, we have now come to the real meat of the thing! Yes folks, it's time to compute the stars and draw them;


;=======================--------------------------------------------------------------------------
; 3D Starfield Function;
;=======================--------------------------------------------------------------------------

Function starfield()
    LockBuffer
;                                                 Lock The Screen For WPF.
;---------------------------------------------------------------------------------------------------
;---------------------------------------------------------------------------------------------------
    For starfield.star = Each star
    tx=(starfield\xpos/starfield\zpos#)+halfx;                                
Get X Screen Co-Ord (X/Z)+Centre
    ty=(starfield\ypos/starfield\zpos#)+halfy;                               
  Get Y Screen Co-Ord (Y/Z)+Centre
        If tx>0 And tx0 And tyIs Star On Screen?
;---------------------------------------------------------------------------------------------------
            If starfield\colour = 1 mulv=$080606:mulv2=$040303;    
If Colour 1 Select Red.
            If starfield\colour = 2 mulv=$060806:mulv2=$030403;    
If Colour 2 Select Green.
            If starfield\colour = 3 mulv=$080608:mulv2=$030304;    
If Colour 3 Select Blue.
            If starfield\colour = 4 mulv=$080808:mulv2=$040404;    
If Colour 4 Select White.
            If starfield\colour = 5 mulv=$080608:mulv2=$040304;    
If Colour 5 Select Purple.
            If starfield\colour = 6 mulv=$080800:mulv2=$040403;    
If Colour 6 Select Yellow.
            If starfield\colour = 7 mulv=$060808:mulv2=$030404;    
If Colour 7 Select Cyan.
;---------------------------------------------------------------------------------------------------
            mul=Int(30-starfield\zpos#);                                             
Invert Z Co-Ords (For colour multiply).
;---------------------------------------------------------------------------------------------------
            mulv=mulv*mul;            
Generate Bright Colour.
            mulv2=mulv2*mul;        
Generate Dark Colour.
;---------------------------------------------------------------------------------------------------
                WritePixelFast tx,ty,mulv;                  
Draw Bright Centre.
                WritePixelFast tx-1,ty,mulv2;            
Dark Left.
                WritePixelFast tx+1,ty,mulv2;            
Dark Right.
                WritePixelFast tx,ty-1,mulv2;            
Dark Up.
                WritePixelFast tx,ty+1,mulv2;            
Dark Down.
                    starfield\zpos#=starfield\zpos#-.2;    
Move Star.
        Else;                        
Generate New Star If Off Screen;
;---------------------------------------------------------------------------------------------------
                starfield\xpos = Rand(-10000,10000);     Large X To Be Divided By Z.
                starfield\ypos = Rand(-10000,10000);    
Large Y To Be Divided By Z.
                starfield\zpos#=30
        End If
    Next
;---------------------------------------------------------------------------------------------------
;---------------------------------------------------------------------------------------------------
UnlockBuffer; Free The Screen.
End Function

That' a big chunk of code and it's plenty to get your head around no matter how well commented it appears to be so let's step through it bit by bit, firstly if you've used Writepixelfast before then you'll know that Blitz requires you to lock the screen before you use it, it's a simple thing to do and just use the Lockbuffer() command, and in fact the only other thing to be careful of is that all your drawing operations are actually on the screen, I know it sounds stupid but you should make sure that you do not WPF outside the screen or you'll most probably crash your IDE.

Next we loop through each one of our stars, bearing in mind that they all have 3D co-ordinates at the moment, the first thing we need to do is to convert them to 2D co-ordinates;

    tx=(starfield\xpos/starfield\zpos#)+halfx;                                 Get X Screen Co-Ord (X/Z)+Centre
    ty=(starfield\ypos/starfield\zpos#)+halfy;                                
Get Y Screen Co-Ord (Y/Z)+Centre

As I described above, now please note that negative values are perfectly acceptable but if we add the centre co-ordinates (halfx and halfy) to them then we bring them all on screen and centre them around the middle.

Because we need to be careful that the star is onscreen we need to check;

        If tx>0 And tx0 And tyIs Star On Screen?

and scan down the program a few lines

        Else;                         Generate New Star If Off Screen;
;---------------------------------------------------------------------------------------------------
                starfield\xpos = Rand(-10000,10000);     Large X To Be Divided By Z.
                starfield\ypos = Rand(-10000,10000);    
Large Y To Be Divided By Z.
                starfield\zpos#=30
        End If

If  it's not on screen we don't draw and we assume that it has flown off screen so we generate a new X+Y position (to stop it repeating) and we set the Z to 30 (maximum depth).

Let's assume that our transformed point is onscreen though and we're going to draw it! :-)

            If starfield\colour = 1 mulv=$080606:mulv2=$040303;     If Colour 1 Select Red.
            If starfield\colour = 2 mulv=$060806:mulv2=$030403;    
If Colour 2 Select Green.
            If starfield\colour = 3 mulv=$080608:mulv2=$030304;    
If Colour 3 Select Blue.
            If starfield\colour = 4 mulv=$080808:mulv2=$040404;    
If Colour 4 Select White.
            If starfield\colour = 5 mulv=$080608:mulv2=$040304;    
If Colour 5 Select Purple.
            If starfield\colour = 6 mulv=$080800:mulv2=$040403;    
If Colour 6 Select Yellow.
            If starfield\colour = 7 mulv=$060808:mulv2=$030404;    
If Colour 7 Select Cyan.

First of all, please don't be frightened by that, remember that we had a type field called colour? Well, depending on this colour we're just going to set a basic colour for our star, there are 7 possiblities here as indicated by the comments, now this is how the colour component of Writepixelfast is made up;

Writepixelfast  XPOS , YPOS, $000000

Notice how it breaks down into 3 blocks? Well each block is a byte and can represent 256 different numbers (0 to 255)

Need to know how? Ok, well in decimal you can represent 10 values with each digit;

0123456789 

In Hex you can have 16 values with one digit;

$ 0123456789ABCDEF

The $ before a number signifies it is in hex. 

So in Decimal;

9*9 = 81

In Hex;

F*F ($FF) =255

FF is obviously a lot more than 00, so basically with these byte pairs we can describe colours for Writepixelfast. In the starfield code they are small numbers because we're going to be multiplying them by the Stars Z co-ordinate so they get brighter as they fly into the screen!

But we've hit another brick wall there as we said earlier that the Z needs to decrease to move the stars towards us so if we multiply then they will get dimmer as they fly towards us which is not the result we want... To invert this I've used this code;

            mul=Int(30-starfield\zpos#);                                              Invert Z Co-Ords (For colour multiply).

Now, please note that the INT command removes the numbers sign, ie it changes negative into positive, calculations in brackets are performed FIRST, remember the rule of  BODMAS (Brackets, of, divide, multiply, addition, subtraction) Imagine if the Z co-ordinate is 25 so it would be quite far into the screen, we subtract 30 from it (the depth of our starfield) like this;

30-25 = -5

and then turn it into a whole positive number

int(-5) = 5

So our -25 has effectively turned into a 5 which is useful when we do this;

            mulv=mulv*mul;             Generate Bright Colour.
            mulv2=mulv2*mul;         Generate Dark Colour.

This is how we generate our colours.

Don't understand? Read it again.

Understand? Good, carry on;

                WritePixelFast tx,ty,mulv;                   Draw Bright Centre.
                WritePixelFast tx-1,ty,mulv2;            
Dark Left.
                WritePixelFast tx+1,ty,mulv2;            
Dark Right.
                WritePixelFast tx,ty-1,mulv2;            
Dark Up.
                WritePixelFast tx,ty+1,mulv2;            
Dark Down.

The Above draws the stars.... Below moves them :-)

                    starfield\zpos#=starfield\zpos#-.2;     Move Star.

We just subtract a small amount from the Z co-ordinate of each star, when the perspective throws them off the screen don't worry, we'll reset the Z and make a new one :)

Don't forget to use Unlockbuffer to free the screenbuffer again by the way.

Well done, here's the finished code;

 

;=========================--------------------------------------------------------------------------
; (C) Shockwave / DBF 2003
;=========================--------------------------------------------------------------------------
;
; Tutorial and code created by Shockwave / DBF (C) 2003.
; For More Sample Code visit the DBF development forums;
;
; http://pub161.ezboard.com/bdbf
;
; Or The Blitzcoder forums;
;
; www.Blitzcoder.com
;
;---------------------------------------------------------------------------------------------------
;---------------------------------------------------------------------------------------------------
;
; This Routine Draws A 3D Starfield Using Only B2D Commands, It Is Based Around Types And 
; Writepixelfast.
;
;---------------------------------------------------------------------------------------------------
;---------------------------------------------------------------------------------------------------

Const xres = 640; X Res.
Const yres = 480; Y Res.
Const depth = 32; Depth.
Const window = 1; Window.
Const nstars = 800; How Many Stars?
Const escape = 1; Escape Scancode.
Const halfx = xres/2; For Neatness later on X centre of screen.
Const halfy = yres/2; For Neatness later on Y centre of screen.

;======================-----------------------------------------------------------------------------
; Open Graphics Screen;
;======================-----------------------------------------------------------------------------

Graphics xres,yres,depth,window
SetBuffer BackBuffer()

;=================================------------------------------------------------------------------
; Create Type Definition For Star;
;=================================------------------------------------------------------------------

Type star
Field xpos; X position of star.
Field ypos; Y position of star.
Field zpos#; Z position of star.
Field colour; Colour of star.
End Type 

;---------------------------------------------------------------------------------------------------
;---------------------------------------------------------------------------------------------------


;==============-------------------------------------------------------------------------------------
; Create Stars;
;==============-------------------------------------------------------------------------------------

For loop = 1 To nstars
starfield.star = New star
starfield\xpos = Rand(-10000,10000); Large X To Be Divided By Z.
starfield\ypos = Rand(-10000,10000); Large Y To Be Divided By Z.
starfield\zpos# = Rnd(0,30); Z, the Depth into the screen.
starfield\colour = Rand(1,7); Seven Possible colours.
Next

;---------------------------------------------------------------------------------------------------
;---------------------------------------------------------------------------------------------------

;===========----------------------------------------------------------------------------------------
; Main Loop;
;===========----------------------------------------------------------------------------------------

While Not KeyDown(escape); Loop Starts.
starfield(); Do Starfield.
Flip; Double Buffer.
Cls; Clear The Screen.
Wend; Loop Ends.
End
;---------------------------------------------------------------------------------------------------
;---------------------------------------------------------------------------------------------------


;=======================--------------------------------------------------------------------------
; 3D Starfield Function;
;=======================--------------------------------------------------------------------------

Function starfield()
LockBuffer; Lock The Screen For WPF.
;---------------------------------------------------------------------------------------------------
;---------------------------------------------------------------------------------------------------
For starfield.star = Each star
tx=(starfield\xpos/starfield\zpos#)+halfx; Get X Screen Co-Ord (X/Z)+Centre
ty=(starfield\ypos/starfield\zpos#)+halfy; Get Y Screen Co-Ord (Y/Z)+Centre
If tx>0 And tx0 And ty ;---------------------------------------------------------------------------------------------------
If starfield\colour = 1 mulv=$080606:mulv2=$040303; If Colour 1 Select Red.
If starfield\colour = 2 mulv=$060806:mulv2=$030403; If Colour 2 Select Green.
If starfield\colour = 3 mulv=$080608:mulv2=$030304; If Colour 3 Select Blue.
If starfield\colour = 4 mulv=$080808:mulv2=$040404; If Colour 4 Select White.
If starfield\colour = 5 mulv=$080608:mulv2=$040304; If Colour 5 Select Purple.
If starfield\colour = 6 mulv=$080800:mulv2=$040403; If Colour 6 Select Yellow.
If starfield\colour = 7 mulv=$060808:mulv2=$030404; If Colour 7 Select Cyan.
;---------------------------------------------------------------------------------------------------
mul=Int(30-starfield\zpos#); Invert Z Co-Ords (For colour multiply).
;---------------------------------------------------------------------------------------------------
mulv=mulv*mul; Generate Bright Colour.
mulv2=mulv2*mul; Generate Dark Colour.
;---------------------------------------------------------------------------------------------------
WritePixelFast tx,ty,mulv; Draw Bright Centre.
WritePixelFast tx-1,ty,mulv2; Dark Left.
WritePixelFast tx+1,ty,mulv2; Dark Right.
WritePixelFast tx,ty-1,mulv2; Dark Up.
WritePixelFast tx,ty+1,mulv2; Dark Down.

starfield\zpos#=starfield\zpos#-.2; Move Star.
;---------------------------------------------------------------------------------------------------
Else; Generate New Star If Off Screen;
;---------------------------------------------------------------------------------------------------
starfield\xpos = Rand(-10000,10000); Large X To Be Divided By Z.
starfield\ypos = Rand(-10000,10000); Large Y To Be Divided By Z.
starfield\zpos#=30
End If
Next
;---------------------------------------------------------------------------------------------------
;---------------------------------------------------------------------------------------------------
UnlockBuffer; Free The Screen.
End Function


For a printable copy of this article, please click HERE.


This site is CopyrightŠ 2000-2004, BlitzCoder. All rights reserved.