Author Topic: REMAKES TUTORIAL #4 - 2D Stars  (Read 8988 times)

0 Members and 1 Guest are viewing this topic.

Offline stormbringer

  • Time moves by fast, no second chance
  • Amiga 1200
  • ****
  • Posts: 453
  • Karma: 73
    • View Profile
    • www.retro-remakes.net
REMAKES TUTORIAL #4 - 2D Stars
« on: June 16, 2009 »
Ok... I hope you all managed to understand my VBL explanation... so as promised, here is the 4th tutorial: 2d stars!

So let's have a look at this over-used "old-school" effect and see how we can get a modern implementation for it using OpenGL.

First of all, this tutorial is the first to use the VBL/delta-timing mechanism explained in TUTORIAL #3. So if the words VBL and delta-timing mean nothing to you, please read TUTORIAL #3.

Stars.... what is it all about? Let's start with some background explanation about this classic effect.

On old computers like the C64 or the Amiga, this effect was achieved using sprites. A sprite was a small graphical object that unlike classic bitmaps had some intersting properties:

- fixed dimensions
- own set of colors
- ability to detect collisions with bitmaps
- independent control from bitmaps.

As explained in the TUTORIAL #2, these old computers had a way to control the graphics and change their properties at every scanline, simply by waiting the refresh mechanism to reach a specific scanline, change the contents and continue in the same way with other scanlines. What's the relationship with our stars??

2D stars were achieved by simply using one or more sprites and changing their x position and color at some specific scanlines, giving the illusion of several layers of dots moving horizontally, like stars would be seen in the sky through the window of a very fast moving spaceship. This effect was not only used in demos and intros but also in games (especially shoot'em ups such as Delta and the classic R-Type).

How do we implement such a mechanism with OpenGL? well, we do not! OpenGL does not know anything about sprites. It can just render primitives in a frame buffer. One of these primitives is the point. Mathematically speaking, a point does not have any dimension. However, OpenGL is nice to us and allows us to set a "size" for a point and also a color (amont some other properties).

If we can draw a point, we can draw several and actually OpenGL is very good at this. The point primitive is hardware accelerated and is a very good choice to simulate the old-schoold 2d starfield effect.

Where do we start?

First of all we define a structure that will be used for each one of our stars:

Code: [Select]
/*

  definitionof a "star"

*/
typedef struct _Star2D
{
CFloat32 x;
CFloat32 y;
CByte color;
CByte speed;
} Star2D, *pStar2D;

As you can see each star will have a x and y coordinate a color and a speed value. Several stars having the same value for the color and for the speed, will move together. If we have different speeds, we will have several "layers" of points moving at different speeds, thus giving the illusion of a starfield.

How do we manage those stars?

We define an "object" that will manage them:

Code: [Select]
/*

  the "object" that manages all the stars

*/
typedef struct _Stars2DObject
{
CInt32 min_x;
CInt32 max_x;
CInt32 min_y;
CInt32 max_y;
CuInt32 count;
CuInt32 planes;
CInt32 speed;
pStar2D stars;
CByte planeColors[8][3];
} Stars2DObject, *pStars2DObject;

This object will hold the minimum and maximum x as well as y coordinates that will define the area of our starfield. It will also hold the count of stars to be displayed, the number of planes, a general speed factor that will be applied to the different planes (allowing a single point of control for layers of stars with different speeds) and of course the colors for the stars, per plane. Chosing the colors so that the slowest plane is displayed with a dark color and the fastest plane with a light color will give the illusion of depth and.. space!

We now need some functions to manage this "object", here they are:

to create/initialize the starfield:

CBool stars2d_create(pStars2DObject obj);

to destroy/discard any allocated resource:

CBool stars2d_destroy(pStars2DObject obj);

to process the motion changes for each star:

CBool stars2d_process(pStars2DObject obj, CFloat32 deltaTime);

and finally to draw the stars:

CBool stars2d_draw(pStars2DObject obj, CuInt32 winWidth, CuInt32 winHeight, CuInt32 left, CuInt32 top, CuInt32 scaleFactor);




Let's have a look at the implementation:

1) Creating/Initializing the stars

Code: [Select]
/*

  stars2d_create()

  create stars

*/
CBool stars2d_create(pStars2DObject obj)
{
CuInt32 c;

/* allocates memory for the requested number of
stars. this buffer contains the properties of each star we want to display
*/
obj->stars=malloc(C_SIZE_OF(Star2D)*obj->count);

/* has the buffer? */
if (obj->stars)
{
CuInt32 width;

/* compute the x range for the stars */
width=MAX(obj->min_x,obj->max_x)-MIN(obj->min_x,obj->max_x);

/* generate some pseudo coordinates & properties for each star */
for (c=0; c < obj->count; c++)
{
/* generate a random x,y position within the give bounds */
obj->stars[c].x=(CFloat32)(obj->min_x+rand()%width);
obj->stars[c].y=(CFloat32)(obj->min_y+(rand()%(obj->max_y-obj->min_y)));

/* pick a random speed for this star, but with respect to the number of star planes requested */
obj->stars[c].speed=(CByte)(1+(rand()%obj->planes));

/* assign a color index to the star based on it's speed (i.e. depending to which plane it belongs) */
obj->stars[c].color=(CByte)(obj->stars[c].speed-1);
}
}
else
{
/* sorry, cannot allocate stars */
obj->count=0;
}

/* done */
return(TRUE);
}

Here we allocate a buffer that will hold a star structure as defined above, for each one of our stars. In this way we can manage them independently. Star position is picked at random within the min/max x and y. Then the speed is picked up at random among the number of planes we have. The color is dependent on the plane number. This allows us to create the illusion of depth by proving colors with an increase in intensity.



2) Cleaning the starfield object

Code: [Select]
/*

  stars2d_destroy()

  destroy stars

*/
CBool stars2d_destroy(pStars2DObject obj)
{
/* do we have the stars buffer? */
if(obj->stars)
{
/* discard the buffer */
free(obj->stars);
}

/* done */
return(TRUE);
}

This piece of code is called at the end, when we do not need the starfield anymore. Since a buffer has been allocated to hold the stars and their properties, we need to free that buffer when no longer needed.



3) Processing motion for the stars

Code: [Select]
/*

  stars2d_process()

  process stars (move stars)

*/
CBool stars2d_process(pStars2DObject obj, CFloat32 deltaTime)
{
CuInt32 c;
CuInt32 width;

/* compute maximum x range */
width=MAX(obj->min_x,obj->max_x)-MIN(obj->min_x,obj->max_x);

/* is speed positive? */
if (obj->speed > 0)
{
/* check for every star if it needs to have its x position reset */
for (c=0; c < obj->count; c++)
{
/* move star according to it speed and the delta time */
obj->stars[c].x -= ((CFloat32)(obj->stars[c].speed*obj->speed))*deltaTime;

/* is out of the screen? */
while (obj->stars[c].x < (obj->min_x))
{
/* reset its position */
obj->stars[c].x += width;
}

}
}
else
{
/* check for every star if it needs to have its x position reset */
for (c=0; c < obj->count; c++)
{
/* move star according to it speed and the delta time */
obj->stars[c].x -= ((CFloat32)(obj->stars[c].speed*obj->speed))*deltaTime;

/* is out of the screen? */
while (obj->stars[c].x > (obj->max_x))
{
/* reset its position */
obj->stars[c].x -= width;
}

}
}

/* done */
return(TRUE);
}

In this function we simply add the speed to each star in our array of stars. The speed depends on each star and is stored in the star structure. We compensate the speed by the value provided by our delta-timer/VBL mechanism, so the stars move at the same speed, whatever the computer used is. Eventually we check that if a star gets out of the visible area, it is reset on the other side, thus providing the illusion of scrolling stars.



4) Painting the stars in the sky

Code: [Select]
/*

  stars2d_draw()

  draw 2d stars

*/
CBool stars2d_draw(pStars2DObject obj, CuInt32 winWidth, CuInt32 winHeight, CuInt32 left, CuInt32 top, CuInt32 scaleFactor)
{
CuInt32 c;

/* set 2d mode */
gltk_tools_set_2d_mode(winWidth,winHeight);

/* disable any texturing, blending, etc */
glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glDisable(GL_ALPHA_TEST);

/* set the point size to the scale factor (in case we simulate low resolutions) */
glPointSize((CFloat32)scaleFactor);

/* begin to plot points (our actual stars) */
glBegin(GL_POINTS);

/* plot them all! */
for (c=0; c < obj->count; c++)
{
/* set color according to the star properties */
glColor4f(
((CFloat32)obj->planeColors[obj->stars[c].color][0])/256.0f,
((CFloat32)obj->planeColors[obj->stars[c].color][1])/256.0f,
((CFloat32)obj->planeColors[obj->stars[c].color][2])/256.0f,
1.0f
);

/* output a point at the star x,y position */
glVertex2f(left+obj->stars[c].x,top+obj->stars[c].y);
}

/* end of plotting */
glEnd();

/* done */
return(TRUE);
}

Drawing the stars is pretty simple. We set a 2D projection, disable any texturing, blending, etc. Then we loop through the stars and for each of them we plot a point using the GL_POINT primitive and of course the star's specific color.




5) calling the star routines from the main program

Code: [Select]
/* RETRO-REMAKES TUTORIAL #4 - 2D Stars

Written by Stormbringer (stormbringer@retro-remakes.net)

June 16, 2009

This tutorial demonstrates how to implement an "old-school" effect often called "2d stars" or "2d starfield"

It basically display moving dots in the x direction with different sets of speed thus creating the illusion of
a starfield moving horizontally. The implementation here uses the OpenGL point primitive to take advantage of the
hardware acceleration.

*/

/*

  include the GLTK (Graphics Library Toolkit)

*/
#include "..\gltk\gltk.h"




/* routines
*/

/* background copperlist manager
*/
#include "..\routines\copper_list_bkg.h"

/* 2d stars manager
*/
#include "..\routines\stars_2d.h"





/* objects
*/

/* the copper list object
*/
CopperListBkgObject copperBkg_obj;

/* the starfield object
*/
Stars2DObject stars_obj;





/*

  raster table for the "copper list"

  see tutorial #2

*/
CopperListBkgEntry copperBkgList[]=
{
0,0,0,0,
20,255,255,255,
21,0,0,17,
220,255,255,255,
221,0,0,0,
};



/*

  our screen "object" that holds the properties of our screen/window

*/
GLTKScreenProperties screenProperties;






/*

  intro_on_mouse_left_down()

  user pressed the left mouse button

*/
CError intro_on_mouse_left_down(pCVoid userData)
{
/* quit */
gltk_screen_quit();

/* done */
return(0);
}






/*

  intro_on_vsync()

  this method works like a timer interrupt. we will use it to track time changes and update the animation of or elements, etc

*/
CError intro_on_vsync(pCVoid userData, CFloat64 deltaTime)
{
if ((screenProperties.vsyncFPS == screenProperties.vsyncActual) && screenProperties.vsync)
{
stars2d_process(&stars_obj,(CFloat32)1.0f);
}
else
{
CFloat32 timeValue;


timeValue=(CFloat32)deltaTime*screenProperties.speedFactor*((CFloat32)screenProperties.vsyncFPS);

/* process objects */
stars2d_process(&stars_obj,(CFloat32)timeValue);
}

/* done */
return(0);
}







/*

  intro_on_paint()

  this method is used to paint the graphic elements on screen

*/
CError intro_on_paint(pCVoid userData)
{
/* clean the screen
*/
/* black background */
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);


/* clear depth buffer with default value */
glClearDepth(1.0f);

/* disable the depth test */
glDisable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);

/* disable any clippin */
glDisable(GL_SCISSOR_TEST);


/* draw the copper list
*/
copper_list_bkg_draw(
&copperBkg_obj,
screenProperties.width,
screenProperties.height,
0,
0,
2 /* scale factor change this, depending on which resolution you want to simulate */
);

/* draw the stars */
stars2d_draw(
&stars_obj,
screenProperties.width,
screenProperties.height,
0,
0,
2 /* scale factor change this, depending on which resolution you want to simulate */
);

/* done */
return(0);
}




/*

  entry point

*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
/* hide cursor */
gltk_screen_show_cursor(FALSE);

/* init screen structure */
memset(&screenProperties,0,C_SIZE_OF(screenProperties));

/* name of the  */
screenProperties.name="My new cracktro";

/* define window dimensions
*/
screenProperties.width=640;
screenProperties.height=480;

/* request vsync

NOTE: in order for this option to work fine, you need to set your graphics driver Vertical Sync option on "Application Controlled"
*/
screenProperties.vsync=FALSE;

/* request refresh rate

NOTE: this will only work 100% in full screen mode, if the graphics card is capable of it
*/
screenProperties.vsyncFPS=60;

/* flag for window mode/full screen mode
*/
screenProperties.fullScreen=TRUE;

/* our method for painting the screen
*/
screenProperties.paintMethod=intro_on_paint;

/* our method for managing the time (where we change animation, etc)
*/
screenProperties.vsyncMethod=intro_on_vsync;

/* our method to intercept the left mouse button
*/
screenProperties.leftMouseDown=intro_on_mouse_left_down;


/* init our copper list here
*/
/* number of entries in our list */
copperBkg_obj.count=C_SIZE_OF(copperBkgList)/C_SIZE_OF(copperBkgList[0]);
/* address of the list */
copperBkg_obj.list=copperBkgList;


/* init out starfield object
*/
memset(&stars_obj,0,C_SIZE_OF(stars_obj)); // clear the structure, make sure what's not used is set to 0
stars_obj.min_x=0; // stars start at the leftmost x coordinate
stars_obj.max_x=screenProperties.width; // stars get out at the rightmost x coordinate
stars_obj.min_y=22*2; // start y just below the first white raster line
stars_obj.max_y=219*2; // end y just above the second white raster line
stars_obj.count=200; // the total number of stars
stars_obj.planes=3; // 3 planes (layers) of stars
stars_obj.speed=3; // speed factor. the slowest plane of stars moves at 1 * stars_obj.speed,
// the second at 2 * stars_obj.speed, etc
/* RGB color of the slowest star plane */
stars_obj.planeColors[0][0]=64;
stars_obj.planeColors[0][1]=64;
stars_obj.planeColors[0][2]=64;

/* RGB color of the "middle" star plane */
stars_obj.planeColors[1][0]=180;
stars_obj.planeColors[1][1]=180;
stars_obj.planeColors[1][2]=180;

/* RGB color of the fastest star plane */
stars_obj.planeColors[2][0]=255;
stars_obj.planeColors[2][1]=255;
stars_obj.planeColors[2][2]=255;

/* create the starfield */
stars2d_create(&stars_obj);


/* open the screen/window using the give properties
*/
if (gltk_screen_open(&screenProperties))
{
/* run the main loop, intercept system events, etc
*/
gltk_screen_run_loop(&screenProperties);


/* close screen window
*/
gltk_screen_close(&screenProperties);

stars2d_destroy(&stars_obj);
}


/* exit and return code to system */
return(0);
}

And this is the main program, with the calls to our 2d starfield manager functions. The stars manager is initialized in the program's entry point just before we open the screen/window. Drawing occurs after we draw the background rasters. Finally and the most important, the processing of the motion is done by calling the processing function within the vsync method. As explained in TUTORIAL #3, we make the difference between the ideal case and the general case. It would not really be necessary for a simple 2d starfield effect as demonstrated here. However for the sake of being coherent, we do it.

Happy demo making now!
« Last Edit: March 06, 2011 by Shockwave »
We once had a passion
It all seemed so right
So young and so eager
No end in sight
But now we are prisoners
In our own hearts
Nothing seems real
It's all torn apart

Offline Rbz

  • Founder Member
  • DBF Aficionado
  • ********
  • Posts: 2731
  • Karma: 485
    • View Profile
    • http://www.rbraz.com/
Re: TUTORIAL #4 - 2D Stars
« Reply #1 on: June 17, 2009 »
I always thought you did effect like this using software rendering :)

This is almost the same way that I did it for my remakes and more recently I'm using DirectX9.
Challenge Trophies Won:

Offline benny!

  • Senior Member
  • DBF Aficionado
  • ********
  • Posts: 4381
  • Karma: 228
  • in this place forever!
    • View Profile
    • bennyschuetz.com - mycroBlog
Re: TUTORIAL #4 - 2D Stars
« Reply #2 on: June 17, 2009 »
N1!  :clap:
[ mycroBLOG - POUET :: whatever keeps us longing - for another breath of air - is getting rare ]

Challenge Trophies Won:

Offline stormbringer

  • Time moves by fast, no second chance
  • Amiga 1200
  • ****
  • Posts: 453
  • Karma: 73
    • View Profile
    • www.retro-remakes.net
Re: TUTORIAL #4 - 2D Stars
« Reply #3 on: June 18, 2009 »
@rbz: well, it's a bit of a waste of CPU time to use software rendering for this effect especially when you have OpenGL or in your case DirectX. A software rendering technique would require to clear the screen buffer and plot the points as well, but CPU would be used for that. In the case of the remakes, the resolution is quite low and the buffer to clear is very small, so there is little impact on the processing time. However if you go to higher resolutions it can become a problem.

OpenGL & DirectX also allow subpixel positioning which can be very handy. Of course you can do it in software but at the cost of extra processing. It's not quite obvious in this tutorial that OpenGL or DirectX are superior to software rendering in terms of speed. However in the next tutorial (about displaying graphics/logos/etc) this will become more obvious.
We once had a passion
It all seemed so right
So young and so eager
No end in sight
But now we are prisoners
In our own hearts
Nothing seems real
It's all torn apart

Offline xyzzy

  • ZX 81
  • *
  • Posts: 2
  • Karma: 1
    • View Profile
Re: TUTORIAL #4 - 2D Stars
« Reply #4 on: September 22, 2009 »
All,

Tried to download the tutorial (4), but got a 404 page error.  It's the same for tutorial 3. Please can the download links be fixed.  Thank you in advance.

Best regards,
Xyzzy

Offline Shockwave

  • good/evil
  • Founder Member
  • DBF Aficionado
  • ********
  • Posts: 17378
  • Karma: 497
  • evil/good
    • View Profile
    • My Homepage
Re: TUTORIAL #4 - 2D Stars
« Reply #5 on: September 22, 2009 »
Hi, welcome to the forum, we had a problem a short time ago with our hosting which meant that we lost a lot of files, seems like these tutorials were affected, I will ask Stormbringer to re-upload them.
Shockwave ^ Codigos
Challenge Trophies Won:

Offline Yaloopy

  • Death From Above
  • DBF Aficionado
  • ******
  • Posts: 2872
  • Karma: 35
    • View Profile
    • UltraPaste
Re: TUTORIAL #4 - 2D Stars
« Reply #6 on: September 22, 2009 »
All,

Tried to download the tutorial (4), but got a 404 page error.  It's the same for tutorial 3. Please can the download links be fixed.  Thank you in advance.

Best regards,
Xyzzy
Hey Xyzzy, just wanted to say that I love the name. The files are up now, thanks to Moroboshisan and Shockwave. :)
Fuck L. Ron Hubbard and fuck all his clones.
Challenge Trophies Won:

Offline xyzzy

  • ZX 81
  • *
  • Posts: 2
  • Karma: 1
    • View Profile
Re: TUTORIAL #4 - 2D Stars
« Reply #7 on: September 23, 2009 »
Moroboshisan and Shockwave,

Thank you for uploading the tutorials.

Stormbringer,

Thanks for writing such great tutorials.  Hope to see more.

Yaloopy,

Thanks for letting me know the links were fixed and the comments on my pseudonym.

Everyone,

Great site.

Best regards,
Xyzzy

Offline sanitykey

  • ZX 81
  • *
  • Posts: 1
  • Karma: 0
    • View Profile
Re: TUTORIAL #4 - 2D Stars
« Reply #8 on: September 23, 2010 »
Hi All!

Just joined this forum and have been looking at these tutorials, they're the best I've seen so far. Most tutorials I've tried are way way outdated using depreciated libraries or ancient programs, which are usually a giant pain to get working. However with these I literally just downloaded the zips, double clicked the "TUTORIALX.. .dsw" files (opening them in Visual Studio 2010) and could compile and run them perfectly. The code is well commented and I think the tutorial posts are interesting with the right amount of information.

Thanks a lot stormbringer ^_^

Offline stormbringer

  • Time moves by fast, no second chance
  • Amiga 1200
  • ****
  • Posts: 453
  • Karma: 73
    • View Profile
    • www.retro-remakes.net
Re: TUTORIAL #4 - 2D Stars
« Reply #9 on: September 23, 2010 »
thanks guys!

sorry for not having posted the other tutorials I wanted to post.... I just ran out of time.. I'll try to post one soon..

We once had a passion
It all seemed so right
So young and so eager
No end in sight
But now we are prisoners
In our own hearts
Nothing seems real
It's all torn apart

Offline carlsson

  • ZX 81
  • *
  • Posts: 2
  • Karma: 0
    • View Profile
Re: REMAKES TUTORIAL #4 - 2D Stars
« Reply #10 on: August 09, 2012 »
I have not done anything like this before but when I have read your tutorails I'm understanding what I am doing. Great job!