Starfields
Starfields
|
Starfields are about as old as the hills. The very first demos all had scrolling text
against a background of zooming stars. Games have had them, most graphics programmers have
made one at some time. Despite this, I have seen some very bad examples indeed. I have also
seen some very good ones.
Starfields give the impression that you're flying through space, with stars whizzing
by. Though, obviously, you'd have to be moving very fast indeed if you were to do this in
real life. There is more than one way to achieve this, some ways being better than others.
I will explain the various methods, how good they are, and where you can find examples of
them.
A horrendous method
If you have ever played the game Supremacy on the PC, you will have seen just about
the worst starfield there is. The rest of the games is very good, so I have no idea how
they managed to produce a starfield that gives absolutely no impression of movement
whatsoever. Stars appear at the center of the screen, and travel in towards the edge. Many
of them travel exactly horizontally or vertically. Massively unimpressive.
variables
Center_of_screen_X = 160
Center_of_screen_Y = 100
array of integers: star_x(0 to 15) = Center_of_screen_X
array of integers: star_y(0 to 15) = Center_of_screen_Y
array of integers: star_vx(0 to 15) = random numbers between -2 and 2
array of integers: star_vy(0 to 15) = random numbers between -2 and 2
program
loop forever
loop i from 0 to 15
(erase the old position of the star)
draw a black pixel at (star_x(i), star_y(i))
star_x(i) = star_x(i) + star_vx(i)
star_y(i) = star_y(i) + star_vy(i)
if (star_x(i), star_y(i)) is off then screen then
star_x(i) = Center_of_screen_X
star_y(i) = Center_of_screen_Y
star_vx(i) = a random number between -2 and 2
star_vy(i) = a random number between -2 and 2
end if
(draw in the star at the new position)
draw a white pixel at (star_x(i), star_y(i))
end of i loop
end loop
|
Improved motion
What was missing there was a sense that the stars were coming closer to you. There is
no illusion of three dimensionality, the stars look as is they are simply running along the
screen. The previous method must be abandoned altogether.
Now, we shall imagine the stars to be points in 3D space, all of them moving towards
the viewer, along the Z-axis. At each time step, the 3D coordinates of the stars will be
projected onto the screen, and displayed.
variables
(coordinates of stars in 3D)
array of floats: star_x(0 to 63)
array of floats: star_y(0 to 63)
array of floats: star_z(0 to 63)
(location of stars on the screen)
array of integers: star_screenx(0 to 63)
array of integers: star_screeny(0 to 63)
Center_of_screen_X = 160
Center_of_screen_Y = 100
program
(initialise positions of stars)
loop i from 0 to 63
star_x(i) = random number between -500 and 500;
star_y(i) = random number between -500 and 500;
star_z(i) = random number between 100 and 1000;
end of i loop
(now draw the starfield)
loop forever
loop i from 0 to 63
(erase the old position of the star)
draw a black pixel at (star_screenx(i), star_screeny(i))
(move the star closer)
star_z(i) = star_z(i) - 5;
(calculate screen coordinates)
star_screenx(i) = star_x(i) / star_z(i) * 100 + Center_of_screen_X
star_screeny(i) = star_y(i) / star_z(i) * 100 + Center_of_screen_Y
if (star_screenx(i), star_screeny(i)) is off then screen,
or star_z(i) < 1 then
star_x(i) = random number between -500 and 500;
star_y(i) = random number between -500 and 500;
star_z(i) = random number between 100 and 1000;
end if
(draw in the star at the new position)
draw a white pixel at (star_screenx(i), star_screeny(i))
end of i loop
end of loop
|
Fading in
That was definately an improvement. However, it's not terribly realistic to have stars
simply appearing in the distance and flying towards you. For a smoother effect, we can make
the stars black when they first appear (so you don't notice them) then get brighter as they
get closer.
Assuming you can display 256 shades of grey, change the line
draw a white pixel at (star_screenx(i), star_screeny(i))
to
b = 255-(starz[i]*(255./1000.))
draw a pixel at (star_screenx(i), star_screeny(i)) with brightness b
A sense of scale
Space is big. But the starfield gives little sense of scale. There are two ways the
sense of vastness can be modeled. The first is simply to model a huge area of space, which
is impractical to say the least. The second is to make the stars move with a range of
velocities. You can get away with this because the stars have no apparent size, so slow
moving stars will simply appear to be very far away.
In addition, the slower stars will be drawn dimmer to make them look more distant.
variables
(coordinates of stars in 3D)
array of floats: star_x(0 to 63)
array of floats: star_y(0 to 63)
array of floats: star_z(0 to 63)
(location of stars on the screen)
array of integers: star_screenx(0 to 63)
array of integers: star_screeny(0 to 63)
(velocity of stars)
array of floats: star_zv(0 to 63)
Center_of_screen_X = 160
Center_of_screen_Y = 100
program
(initialise positions of stars)
loop i from 0 to 63
star_x(i) = random number between -1000 and 1000;
star_y(i) = random number between -1000 and 1000;
star_z(i) = random number between 100 and 1000;
star_zv(i)= random number between .5 and 5;
end of i loop
(now draw the starfield)
loop forever
loop i from 0 to 63
(erase the old position of the star)
draw a black pixel at (star_screenx(i), star_screeny(i))
(move the star closer)
star_z(i) = star_z(i) - star_zv(i);
(calculate screen coordinates)
star_screenx(i) = star_x(i) / star_z(i) * 100 + Center_of_screen_X
star_screeny(i) = star_y(i) / star_z(i) * 100 + Center_of_screen_Y
if (star_screenx(i), star_screeny(i)) is off screen
or star_z(i) < 1 then
star_x(i) = random number between -500 and 500;
star_y(i) = random number between -500 and 500;
star_z(i) = random number between 100 and 1000;
star_zv(i)= random number between .5 and 5;
end if
(draw in the star at the new position)
b = ( (255/5) * star_zv(i)) * (1000 / starz[i])
draw a pixel at (star_screenx(i), star_screeny(i)) with brightness b
end of i loop
end of loop
|
Smoother motion
Now, if you watch the stars carefully as they travel out from the center of the screen
you will see them wobble as they jump discretely from one pixel to the next. It would be
much nicer if they could move smoothly from one to the next. One way to overcome this is to
use a higher resolution, but, even then the effect will be noticable. A good way to smooth
out the motion is to use anti-aliased particles. See the article on
wu-pixels.
Motion blur
An even better way to smooth out the motion is to use motion blur. Imagine the stars are
being filmed by a real camera. The shutter is open for some amount of time, say 1/25th of a
second. In that time it is recieving light from the scene it is facing. If anything moves in
that time, it's image on the camera film will be blurred. So the moving stars should produce
little streaks as they fly past the camera. The faster a star is moving, the darker that
streak will be.
Two new arrays will need to be created to hold the old positions of the stars:
.
.
[snip]
.
.
(old positions of the stars on the screen)
array of floats: star_Oldscreenx(0 to 63)
array of floats: star_Oldscreeny(0 to 63)
.
.
[snip]
.
.
|
And this time lines will be drawn from the old to the new position:
(erase old star)
Erase Wu Line from (star_screenx(i), star_screeny(i)) to (star_Oldscreenx(i), star_Oldscreeny(i))
(move the star closer)
star_z(i) = star_z(i) - star_zv(i);
(calculate screen coordinates)
x = star_x(i) / star_z(i) * 100 + Center_of_screen_X
y = star_y(i) / star_z(i) * 100 + Center_of_screen_Y
(draw in the star at the new position)
(firstly, calculate the length of the streak)
xd = x - star_screenx(i)
yd = y - star_screenx(i)
length = square_root( xd*xd + yd*yd )
(calculate the brightness of the star due to it's speed and distance)
b = (5000*starzv(i)) / starz(i)
(if the star is producing motion blur, dim it)
if length > 1 then b = (b/length)
draw a WuLine from (x, y) to (star_screenx(i), star_screeny(i)) with brightness b
star_Oldscreenx(i) = star_screenx(i)
star_Oldscreeny(i) = star_screeny(i)
star_screenx(i) = x
star_screeny(i) = y
|
Non-linear motion
Traveling forwards at great speed is fun for a couple of seconds, however traveling
at 80 miles per hour is more fun on a roller coaster than in a car. Next, we'll try
rotating the camera around a little as it flies along.
On each frame, you will need to update the values of angleX, angleY and angleZ.
New bits are shown in bold.
.
.
[snip]
.
.
(velocity of stars)
array of floats: star_zv(0 to 63)
Center_of_screen_X = 160
Center_of_screen_Y = 100
(Camera Movement)
a = .00001
(rotations of the camera)
float: angleXvel = 0
float: angleYvel = 0
float: angleZvel = 0
float: angleX = 0
float: angleY = 0
float: angleZ = 0
program
(initialise positions of stars)
loop i from 0 to 63
star_x(i) = random number between -1000 and 1000;
star_y(i) = random number between -1000 and 1000;
star_z(i) = random number between 100 and 1000;
star_zv(i)= random number between .5 and 5;
end of i loop
(now draw the starfield)
loop forever
(alter the rotation acceleration of the camera)
angleXacc = angleXacc + some small random value between -a and a.
angleYacc = angleYacc + some small random value between -a and a.
angleZacc = angleZacc + some small random value between -a and a.
(accelerate the camera)
angleXvel = angleXvel + angleXacc
angleYvel = angleYvel + angleYacc
angleZvel = angleZvel + angleZacc
(damp the motion of the camera)
angleXacc = angleXacc * damping
angleYacc = angleYacc * damping
angleZacc = angleZacc * damping
angleXvel = angleXvel * damping
angleYvel = angleYvel * damping
angleZvel = angleZvel * damping
(move the camera)
angleX = (angleX + angleXvel) * damping
angleY = (angleY + angleYvel) * damping
angleZ = (angleZ + angleZvel) * damping
loop i from 0 to 63
.
.
[snip]
.
.
|
Then, you'll need to rotate each star before projecting it to the screen:
.
.
[snip]
.
.
loop i from 0 to 63
(erase the old position of the star)
.
.
.
(calculate screen coordinates)
temp_x = star_x(i)
temp_y = star_y(i)
temp_z = star_z(i)
Rotate (temp_x, temp_y, temp_z) by angleX degrees in the X-axis
Rotate (temp_x, temp_y, temp_z) by angleY degrees in the Y-axis
Rotate (temp_x, temp_y, temp_z) by angleZ degrees in the Z-axis
star_screenx(i) = temp_x / temp_z * 100 + Center_of_screen_X
star_screeny(i) = temp_y / temp_z * 100 + Center_of_screen_Y
if (star_screenx(i), star_screeny(i)) is off then screen,
or star_z(i) < 1 then
star_x(i) = random number between -500 and 500;
star_y(i) = random number between -500 and 500;
star_z(i) = random number between 100 and 1000;
end if
(draw in the star at the new position)
.
.
[snip]
.
.
|
The Final Result
So, after that's all that's been done, the finished product is quite effective. You can watch it for ages
and drift off into some kind of semi-relaxed, altered brain state.

Source to Stars5