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>
<head>
<title>3D Starfield in HTML5 Canvas</title>
<body>
<canvas id='tutorial' width='500' height='400'>
Your browser does not support the HTML5 Canvas element.
Please update your browser.
</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>
<head>
  <title>3D Starfield in HTML5 Canvas</title>

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

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

    window.onload = function() {
      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.fillStyle = "rgb(" + shade + "," + shade + "," + shade + ")";
          ctx.fillRect(px,py,size,size);
        }
      }
    }
  </script>

<body>
  <canvas id='tutorial' width='500' height='400'>
    Your browser does not support HTML5 canvas.
    Please upgrade your browser.
  </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

In this article I have shown how to make an interesting 3D starfield using the new HTML5 Canvas element. More articles on HTML5 Canvas will come. So, please subscribe to our feed to receive updates.

If you like this article, please click one of the buttons below to share the article. Consider also leaving a comment expressing what you think about this article.

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

  2. cool demo. I’ll try and use it in my game. thanks :)

  3. 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.

Leave a Comment

Notify me of followup comments via e-mail. You can also subscribe without commenting.