Three.js part 1 – make a star-field

Tutorial

Time required : 30 minutes
Pre-requisites : Basic JavaScript and HTML. Some Canvas experience useful.
What is covered : The basics of three.js and using the Particle object.
Now updated for three.js revision 48

Remember FastKat? Wanna know how you’d make it yourself? Well it’s quite easy to create a similar effect with three.js and particles. And as particles are the simplest 3D objects you can make, this is a fun way to get started with this powerful library.

In this tutorial, we’ll set up the core elements in three.js and fill the world with loads of white particles. Yes, that’s right! It’s your basic star-field effect. What’s not to like?

Setting up the page

Apart from three.js1, there’s just one HTML file and I’ve liberally peppered it with comments throughout so hopefully it’s easy to follow.

First we set up the HTML, some simple CSS rules, and import the three.js file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE HTML>
<html lang="en">
	<head>
		<title>three.js particle tutorial</title>
		<meta charset="utf-8">
 
		<style type="text/css">
			body {
				background-color: #000000;
				margin: 0px;
				overflow: hidden;
			}
		</style>
 
	</head>
	<body>
 
		<script src="Three.js"></script>

Variables

Then we declare our global variables :

20
21
22
23
24
25
26
27
28
29
30
31
32
<script>
 
	// the main three.js components
	var camera, scene, renderer,
 
	// to keep track of the mouse position
		mouseX = 0, mouseY = 0,
 
	// an array to store our particles in
		particles = [];
 
	// let's get going! 
	init();

Initialising three.js

The init() function sets up the three main objects that we need to create in order to start working with three.js:

  • scene – the scene contains all the 3D object data
  • camera – the camera has a position, rotation and field of view (how wide the lens is)
  • renderer – figures out what the objects in the scene look like from the point of view of the camera

Camera

34
35
36
37
38
39
40
41
42
function init() {
 
	// Camera params : 
	// field of view, aspect ratio for render output, near and far clipping plane. 
	camera = new THREE.PerspectiveCamera(80, window.innerWidth/window.innerHeight, 1, 4000);
 
	// move the camera backwards so we can see stuff! 
	// default position is 0,0,0. 
	camera.position.z = 1000;

The first parameter for the Camera constructor is field of view. This is an angle in degrees – the larger the angle, the wider the virtual camera lens.

The second parameter is the aspect ratio between the width and the height of the output. This has to match the aspect of the CanvasRenderer, which we’ll come to in a moment.

The camera can only see objects within a certain range, defined by near and far, set here to 1 and 4000 respectively. So any object that is closer than 1 or further away than 4000 will not be rendered.

By default the camera is at the origin of the 3D world, 0,0,0, which isn’t very useful because any 3D object you create will also be positioned at at the origin. It’s a good idea to move the camera back a bit by giving it a positive z position (z increases as it comes out of the screen towards you). In this case we’re moving it back by 1000.

Scene

44
45
46
	// the scene contains all the 3D object data
	scene = new THREE.Scene();
	scene.add(camera);

Note that you now have to add the camera into the scene too (older versions of three.js didn’t require this).

Renderer

50
51
52
53
54
55
56
57
	// and the CanvasRenderer figures out what the 
	// stuff in the scene looks like and draws it!
 
	renderer = new THREE.CanvasRenderer();
	renderer.setSize( window.innerWidth, window.innerHeight );
 
	// the renderer's canvas domElement is added to the body
	document.body.appendChild( renderer.domElement );

The CanvasRenderer creates its own DOM element – it’s an ordinary 2D canvas object that we add into the document body, otherwise we wouldn’t see it. We want it to fill the whole browser window so we set its size to window.innerWidth and window.innerHeight.

Render loop

We then make the particles (we’ll look at that function later), add the mousemove listener to track the mouse position, and finally set up an interval calls the update function 30 times a second.

58
59
60
61
62
63
64
65
66
 
	makeParticles(); 
 
	// add the mouse move listener
	document.addEventListener( 'mousemove', onMouseMove, false );
 
	// render 30 times a second (should also look 
	// at requestAnimationFrame) 
	setInterval(update, 1000/30);

setInterval requires a value in milliseconds, so we need to convert frames per second into mils per frame. To do that take 1000 (the number of mils in a second) and divide it by our frame rate.

Take a quick look at the update function:

70
71
72
73
74
75
76
77
78
79
	// the main update function, called 30 times a second
 
	function update() {
 
		updateParticles();
 
		// and render the scene from the perspective of the camera
		renderer.render( scene, camera );
 
	}

We’ll look at the updateParticles function in detail later, all it does is move the particles forward.

Until renderer.render(…) method is called, you won’t see any of your 3D stuff! Remember that renderer takes care of the canvas, and draws everything into it. It figures out what things in the scene look like from the point of view of the camera and draws it all into its canvas object.

Particles!

Three.js has three main types of 3D objects : triangles, lines and particles. Particles are the easiest to work with as they represent a single point in 3D space. They can be rendered as a 2D image, like a simple circle or square, or just a bitmap image. The important thing about particles is that they look the same from any angle, but of course they get bigger and smaller depending on how far away they are!

Sometimes this sort of 3D particle is referred to as a point sprite or a bill-board.

Making the particles

80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
 
// creates a random field of Particle objects
 
function makeParticles() { 
 
	var particle, material; 
 
	// we're gonna move from z position -1000 (far away) 
	// to 1000 (where the camera is) and add a random particle at every pos. 
	for ( var zpos= -1000; zpos < 1000; zpos+=20 ) {
 
		// we make a particle material and pass through the 
		// colour and custom particle render function we defined. 
		material = new THREE.ParticleCanvasMaterial( { color: 0xffffff, program: particleRender } );
		// make the particle
		particle = new THREE.Particle(material);
 
		// give it a random x and y position between -500 and 500
		particle.position.x = Math.random() * 1000 - 500;
		particle.position.y = Math.random() * 1000 - 500;
 
		// set its z position
		particle.position.z = zpos;
 
		// scale it up a bit
		particle.scale.x = particle.scale.y = 10;
 
		// add it to the scene
		scene.add( particle );
 
		// and to the array of particles. 
		particles.push(particle); 
	}
 
}

The for loop iterates with zpos going from -1000 to 1000 incrementing by 20 each time.

Within the loop, we make a new material (more on that later) and then create a particle. Particles have a position property with x, y and z values (as do all three.js 3D objects).

We give each particle a random x and y position, and set its z position to the loop iterator zpos. Why don’t we just give the particle a random z position? There are reasons why we want them evenly distributed by z but I’m not going to tell you what they are yet! Not until the next instalment of this tutorial. :)

We have to add our 3D objects to the scene or we won’t see them! Notice that we’re also putting them in our own array called particles. This isn’t part of three.js, it’s just so that we can keep track of our particles and do stuff with them in the update loop.

Making the particle material

All 3D objects have materials that define how they’re drawn, things like colour and alpha. Here’s where we create each particle’s material object :

90
material = new THREE.ParticleCanvasMaterial( { color: 0xffffff, program: particleRender } );

We’re using a dynamic object (as defined within the {}) as the only parameter for the constructor. You’ll see this initialisation method a lot in three.js. (Pro-tip : if you want to find out what an object’s initialisation options are, check the the un-minified source.)

Believe it or not, three.js doesn’t have a circular particle material built-in. So we need to tell it how to draw a circle, and we do that by passing in the function particleRender with the necessary canvas drawing API calls:

119
120
121
122
123
124
125
126
127
function particleRender( context ) {
 
	// we get passed a reference to the canvas context
	context.beginPath();
	// and we just have to draw our shape at 0,0 - in this
	// case an arc from 0 to 2Pi radians or 360º - a full circle!
	context.arc( 0, 0, 1, 0,  Math.PI * 2, true );
	context.fill();
};

It might seem a bit weird to pass in a function to a material but it’s actually a really flexible system. It gets a reference to the canvas context, so we could draw any shape we want. And as the canvas’ co-ordinate system will have been scaled and translated relative to the particle, we just need to draw the shape centred at the origin. (If you haven’t used the canvas API much, this tutorial on MDN is a good start.)

Moving the particles

Now let’s look at updateParticles(), remember that this is called every update cycle right before we render.

132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
function updateParticles() { 
 
	// iterate through every particle
	for(var i=0; i<particles.length; i++) {
 
		particle = particles[i]; 
 
		// and move it forward dependent on the mouseY position. 
		particle.position.z +=  mouseY * 0.1;
 
		// if the particle is too close move it to the back
		if(particle.position.z>1000) particle.position.z-=2000; 
 
	}
 
}

The lower the mouse is, the higher the mouseY value and the faster we move each particle! And when the particles get too close to the camera, we move them to the back, so that our star-field is endless. As all good star-fields should be. :)

What’s next?

We’re well on our way to making a FastKat like effect, but we’re not there yet. In the original game the circles fade into the distance – this effect is called a fog filter and I’ll show you how to make one. Also see how the particles are arranged into spirals? We’ll recreate this effect using sine waves.

View the example here and get the source on github. This tutorial is part of my CreativeJS workshop series.

1 three.js revision 48. Note that the three.js API is still in flux so if you update this file, the example may not work in future. (You can get the very latest version on github if you want to risk it).

39 thoughts on “Three.js part 1 – make a star-field

  1. Great tutorial thanks! One question though, I was following along by writing the code myself and my version seems to run choppy. Copy and pasting the code from the tutorial in and it runs smooth so there must be something I missed. Looking over the code I can’t seem to find it, any ideas what might be causing slow performance?

    • Hi Mat,

      Glad you like it! I would check the bit where you make the particles. Are you iterating through from -1000 to 1000 and incrementing by 20 like me? If you increment in smaller steps you’ll make many more particles and that’d be much slower to render. That’d be my first guess! Let me know.

      Seb

  2. Awesome tutorial, Seb. I hope you’ll find time to make more of those.
    Good to see I’m not the only one who is still not convinced to use requestAnimFrame =D

  3. Hi xonecas, exercises look good! One thought while looking at them was about how you’re making your random colours, which is a bit complex. The ParticleCanvasMaterial just requires a hex value for a colour, and a hex value is just an ordinary number, represented as hex. So your randomColour method could be simplified to :

    Math.random()*0xffffff

    But this (and your method) would return absolutely any colour including brown, black, grey, vomit, and mucus (yes they totally are colours ;-) )

    So it might be fun to set a random hue but a fixed saturation and brightness. If we were setting a css property or a canvas fill or stroke style we could use :

    "hsl("+Math.floor(Math.random()*360))+", 100%, 50%)"

    Sadly we don’t have that option in three.js as it manages its own colour objects. But we could reach in and change the material colour once we’ve made the material :

    material = new THREE.ParticleCanvasMaterial({
             program: particleRender
          });
    material.color.setHSV(Math.random(), 1,0.5);

    Which should do the same thing. *

    I guess you didn’t get the sideways moving working? That is a little tricky…

    HTH!

    Seb

    * It may be that setHSV is implemented differently from the CSS hsl, so you might get different results. Try 1 for the third param if you don’t get rainbows!

    • I didn’t know I could manipulate the colors hex value like a number, I’ll update the code :-)

      I gave it a shot on the side movement, but only got as far as having the particles re-appear on the left or right depending on your mouse position. Cool, but not what you asked for. Meanwhile, this morning I got it. I just did to the x coordinate what you initially did to the z coordinate.

      Thanks again, I can’t wait for you to bring your workshop out to the us west coast again!

  4. Thanks for the awesome introduction. Small typo:

    setInterval(render, 1000/30)

    should be

    setInterval(update, 1000/30)

  5. The inline code samples reference
    setInterval(render, 1000/30);
    but it looks like the handler method ended up being named update() instead of render().

    I would imagine the rename was to prevent confusion between the interval event handler and the built in THREE.CanvasRenderer.render() method.

    I’ve been meaning to get into canvas and of course the classic starfield screensaver holds a special place in my heart :)

    Love the site and keep up the excellent and inspiring work!!!!

  6. Seb great work! This is EXACTLY what JS needs. Simple, clear cut tutorials where we can learn and dive in further. I started with this and then was hacking one of the particle examples from Three, as I wanted to add a texture to the particle. Well that brought the framerate to a crawl. I changed the renderer to WebGLRenderer, but then the particles don’t render. I changed the particles into a particle system and then they render, but I can’t figure out how to move them. I’ve tried manipulating the vertices of the system and updating that all with dynamic, __dirtyVertices, __dirtyNormals set to true. My question is can the WebGLRenderer work with the regular particle or does it need to be contained within a system to go that route? The complete lack of documentation is really what is preventing me from taking the next step with this library. Thanks for all your hard work and knowledge sharing.

  7. Really cool stuff here! I’ve been messing around with this, but I’m trying to implement a trailing blur to all the “stars”. Any idea how to go about that?

    • It’s a little complex, you need to switch off three.js’ automatic canvas clearing and fill the canvas with a transparent black rectangle every time. Maybe I’ll add this to the next part of the tutorial. Which I haven’t entirely forgotten about :)

  8. Great tutorial for us canvas beginners. Thanks for publishing this.

    The tutorial however is missing some code, which causes it to be incomplete. The init() function includes this line of code:

    document.addEventListener( ‘mousemove’, onMouseMove, false );

    However, no where in the tutorial is the “onMouseMove” function declared. For others running into this issue, you need to include the following function in your javascript:

    function onMouseMove(event) {
    // store the mouseX and mouseY position
    mouseX = event.clientX;
    mouseY = event.clientY;
    }

  9. Is the rendering to the browser optimized in ways that we do not see? The code shows that every single “setInterval” is executed, the whole scene is rendered from zero. In this case it might be useful as all particles are on the move, but what happens when you have static scenes where just one object is being kicked around?

    • Hi fhil,

      Three.js focusses on rendering, so it doesn’t have any automatic selective rendering capabilities. This becomes less of a problem with WebGL but of course you’ll need to implement these kind of optimisations yourself if you’re using canvas. Hope this helps!

      Seb

  10. I love this tutorial really you are amazing men!!! i suffer a little bit but it was because there was some changes in the three.js insteat of use on the scene addObject you only need to use .add and also you need to add the camera and that solve the problem with new versions of three.js

    P.S. Sorry for the bad english :D

  11. Really well explained!! Thank you. I’m a filmmaker with limited knowledge of JS, interested in learning Three.js for a web experiment. I can say this tutorial was very easy to follow and helped me a lot.
    Best

  12. Hi, I have translated this tutorial to Spanish and I would like to know if I can post that translation in my personal blog … of course I will say what the original source is :-)

    Sorry for my bad english, I’m trying to learn english and Javascript at the same time :-)

  13. Great tutorial!
    Is that possible to make a particle spinning? I was doing a demo of snow, and I’d like to see the snowflake spinning slowly. Is that doable? If so, how can I make that?

    Thank you!

  14. First off, Thank you SO much for doing this tutorial, even though I just finished a relatively large Three.js Project ( http://cabbibo.com/recursion/ ) I learned so much from this tutorial. I can’t believe how little I knew about Three when i did my old project

    One problem that I had is that I tried to switch to the WebGL renderer instead of the canvas. Although I get no errors in my console, It gives me a black screen. I was assuming that the problem was with the line ‘ material = new THREE.ParticleCanvasMaterial( { color: 0xffffff, program: particleRender } ); ‘ well, because it says the word canvas in it, but changing it to basic material did nothing to help fix the problem. I was wondering if there were any additional instructions that are neccesary to follow when using the THREE.WebGLRenderer, or if I am just failing at finding a typo of some sort.

    Thanks again, this really is a brilliant tutorial!

  15. Thanks for the tutorial!

    Just wondering if there’s a part two anywhere. Or is it just me not able to find it anywhere …

  16. This is a really cool tutorial. Thanks. Unfortunately, I cannot get it running. I get an error: Uncaught TypeError: undefined is not a function. I get the same error when I copy the source code and paste it.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Current ye@r *