# 3D Starfield in HTML5 Canvas

This article will show you how to create a 3D starfield using HTML5 Canvas. Below you can see the 3D starfield in action (well, if your browser supports HTML5 canvas).

An interesting aspect of this simulation is that the stars that are far away look smaller and darker than closer stars.

In this article I assume that the reader has at least a basic knowledge of HTML and JavaScript. I will begin by presenting the idea and some theory behind the simulation and after that I will present the code. If any question arises, please don’t hesitate to leave a comment.

## The idea

The screen is filled with a constant number of stars. In this simulation I filll the screen with 512 stars. For each star we are interested in knowing only its coordinates. Since this is a 3D simulation, the coordinates must be in 3D. So, each star will be represented in the code as an object with properties X, Y, and Z.

After the page is completely loaded, we do the initial positioning of the stars, assigning them random positions, and of course ensuring that they stay within the field of view. Then we enter an infinite loop, where in each iteration (or frame) we decrease the value of the Z coordinate of each star making it move towards the user. When a star goes out of the field of view, ie, when Z is less than or equal to zero, it is repositioned deep inside the screen and is assigned random values ​​for X and Y. The final step is to draw the star, but wait, remember that the stars are in 3D. So before we draw them they should be mapped to 2D. To do the mapping to 2D we will use the following formulas:

```px = 128 * star.x / + star.z + half_canvas_width
py = 128 * star.y / + star.z + half_canvas_height```

In the formula, px and py are the resulting coordinates mapped to 2D. This operation is called a perspective projection.

Having the coordinates in 2D we can immediately draw the star. However, I preferred to make the simulation a little bit more interesting by making distant stars look smaller and darker than closer stars. To achieve this, linear interpolation was applied to calculate the size and color of the stars based on the Z coordinate. Here are the formulas:

```size = (1 - star.z / max_depth) * 5
color = (1 - star.z / max_depth) * 255```

The size and color are inversely proportional to the value of Z. Thus, when the star is at the maximum depth, size and color are both zero, approaching 5 and 255, respectively as the value of Z decreases.

## Code

HTML Structure
Let’s begin with the structure of the HTML5 page that will run the 3D starfield.

```<!DOCTYPE html5>
<html>
<title>3D Starfield in HTML5 Canvas</title>
<body>
<canvas id='tutorial' width='500' height='400'>
Your browser does not support the HTML5 Canvas element.
</canvas>
</body>
</html>
```

The statement in the first line tells the browser that the page is based on HTML5. Not including this statement may cause some browsers to ignore the new HTML5 elements. Internet Explorer is an example.
We have defined a canvas element inside the body element. The canvas is 500 pixels wide and 400 pixels tall, and has id tutorial.

JavaScript Code
With the HTML structure defined, the next step is to enter the JavaScript code that performs the simulation. Below is the complete code. Notice that I have introduced the script inside the head element and just below the title element.

```<!DOCTYPE html5>
<html>
<title>3D Starfield in HTML5 Canvas</title>

<script type='text/javascript'>
MAX_DEPTH = 32;

var canvas, ctx;
var stars = new Array(512);

canvas = document.getElementById("tutorial");
if( canvas && canvas.getContext ) {
ctx = canvas.getContext("2d");
initStars();
setInterval(loop,33);
}
}

/* Returns a random number in the range [minVal,maxVal] */
function randomRange(minVal,maxVal) {
return Math.floor(Math.random() * (maxVal - minVal - 1)) + minVal;
}

function initStars() {
for( var i = 0; i < stars.length; i++ ) {
stars[i] = {
x: randomRange(-25,25),
y: randomRange(-25,25),
z: randomRange(1,MAX_DEPTH)
}
}
}

function loop() {
var halfWidth  = canvas.width / 2;
var halfHeight = canvas.height / 2;

ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(0,0,canvas.width,canvas.height);

for( var i = 0; i < stars.length; i++ ) {
stars[i].z -= 0.2;

if( stars[i].z <= 0 ) {
stars[i].x = randomRange(-25,25);
stars[i].y = randomRange(-25,25);
stars[i].z = MAX_DEPTH;
}

var k  = 128.0 / stars[i].z;
var px = stars[i].x * k + halfWidth;
var py = stars[i].y * k + halfHeight;

if( px >= 0 && px <= 500 && py >= 0 && py <= 400 ) {
var size = (1 - stars[i].z / 32.0) * 5;
var shade = parseInt((1 - stars[i].z / 32.0) * 255);
ctx.fillRect(px,py,size,size);
}
}
}
</script>

<body>
<canvas id='tutorial' width='500' height='400'>
Your browser does not support HTML5 canvas.
</canvas>
</body>
</html>
```

Description of code:
The first line defines the MAX_DEPTH constant that stores the maximum distance or depth where a star can be. Then, the canvas variable is declared which will be used store the reference to the canvas element. The variable ctx will store the canvas context and will be used to draw inside the canvas. Obviously the stars array will store 512 stars.

Next we define a function that is executed when the page is fully loaded. The function begins by getting the reference of the canvas element. After that, we determine whether the browser supports the new canvas element. If so, we get the reference of the context of the canvas that will be used to draw inside the canvas. Then, we invoke the function initStars() that prepares the stars by positioning them randomly in space. Finally, we set the loop() function to be invoked every 33 milliseconds.

It is inside the loop() function where the simulation takes place. First, we clear the canvas with a black color, and then start a loop that processes the stars. In each iteration, we start by decreasing the Z value of the star. Next, we check if the star is still within the field of view. If not, the star is positioned at the maximum depth and assigned random values ​​for the coordinates X and Y. Then, we do the mapping of coordinates from 3D to 2D. Finally, we draw the star only if it is within the limits of the canvas. See the section The idea to understand the algorithm I use to draw the stars.

## Conclusion

1. Fabyola

looks preety n_n, the only thing i would remove is the central star xd

• lefam

Thank you.
You are right. The central star is annoying 2. niandrei

cool demo. I’ll try and use it in my game. thanks • lefam

Good. Please, let me know when you finish the development.

• niandrei

Hello, I’ve just launched my game. You can see your starfield at http://www.phiara.com

Again thanks!

3. kiran

simple and cool..
thank you

4. Shane Thompson

Hi there.

Great tutorial, just one thing I don’t understand.
When you map the co-ordinates to 3D, the number 128 appears apparently out of nowhere. How did you figure out this number? Is there any way I can figure out what number works best in my own projects?

Thanks.

5. Kanthesha

Can Anybody Please tell me how to do this same thing in WPF and C#, I am very new to WPF.

• Kanthesha

I really Liked this.

6. Daniel

Thanks for the algorithm and explanation! It’s one thing I can’t really understand though, why are you decreasing the z-value?

I’m transferring this to P5.js, and it surely doesnt work if i increase the Z-value, but I don’t understand why https://editor.p5js.org/[email protected]/sketches/J1D5zIhjJ