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?

Variables

<script>// the main three.js componentsvar 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

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.

requestAnimationFrame

Strictly speaking we should use the more recent requestAnimationFrame which is very cool, but you never really know how fast it will fire. For the sake of simplicity in this tutorial I’m sticking to the old-school method.

But requestAnimationFrame is cool and important. Remind me to write something about it in the future, along with how to deal with its timing implications.

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 secondfunction 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

// creates a random field of Particle objectsfunction 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.

Random ranges

You’re often going to need a random value between two ranges, if you’ve done much of this type of work I’m sure you’ll have seen this technique:

particle.position.x=Math.random()*1000-500;

Let’s look at the maths here :

Math.random() returns a floating point number between 0 and 1.

We then multiply this by 1000 which gives us a number between 0 and 1000.

Then we subtract 500, which gives us a number between -500 and 500.

I often make this into a handy function :

function randomRange(min, max){returnMath.random()*(max-min)+ min;}

This will work with any two numbers. Bear in mind that Math.random() may return 0. It can get very close to, but shouldn’t return, 1.

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 :

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 particlefor(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 backif(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.

Exercises

Why not try some of the following to see how well you understand the code :

Make the particles go backwards instead of forwards

Try changing the particle colours, sizes, and even the shape of the particle to squares, triangles or stars

Change how the mouse affects the movement – perhaps you could move the particles left and right too?

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.

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”

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?

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.

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 :

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!

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.

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

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?

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!

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

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

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.

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.

Subscribe to CreativeJS via email

CreativeJS

CreativeJS.com is the go-to place to find exciting JavaScript demos, projects, games, and well, anything else that we think is awesome. We've got a whole team here to bring you the best of what's out there. Find out all about us.

Suggest a site

Do have a project or site you think we should feature? Whether it's yours or just something you've seen that you love, go on and tell us about it!