This is the second part of my article about tunnel animations.
If you didn't read the first one, please take a look over here :
Tunnel animation (Part 1)

Welcome back explorer ! 🖖

Thanks for reaching this new part of the article, I assume that you enjoyed the first one and you want to learn more 😙
Like I said at the end of the previous part, we will now see how to generate a tunnel with particles instead of a plain TubeGeometry().

  • This is the kind of animation you'll be able to create at the end

1. Calculate the positions of the particles

To achieve the result we want, we will generate circles of particles all along our path. ThreeJs is using the exact same way to generate a tube geometry, with one difference that it adds faces to create a plain tube.
We will have to first choose the detail you want to give to your tube and its radius.

The detail will be set with two values :

  // The amount of circles that will form the tube
var segments = 500;
// The amount of particles that will shape each circle
var circlesDetail = 10;
// The radius of the tube
var radius = 5;

Now that we know more or less the amount of particles we are gonna have (spoiler alert : segments * circlesDetail), we now have to calculate the Frenet frames.
I'm really not an expert in this domain but as far as we need to understand, those frenet frames are values calculated for all the segments of the tube. Each frame is made of a Tangent, a Normal and a Binormal. Roughly, those values are the rotations values for each segment and where it looks at.

If you want to understand more how those values are calculated, have a look at this Wikipedia article

Thanks to ThreeJs, we don't have to understand any of this to make our code work we can use a built in function from our path.

  var frames = path.computeFrenetFrames(segments, true);
// True specify if the path is closed or not, in our case it must be

The result of this function is a set of three arrays of Vector3().
Frenet frames result

Now that we have all the info needed for each segment, we can start to generate the particles along each segment.
We will store each particle as a Vector3() in a Geometry() so that we can reuse it later.

  // Create an empty Geometry where we will insert the particles
var geometry = new THREE.Geometry();

We now have to place particles along each segment. This is why we are gonna use a loop through all segments.
I'm not gonna explain here how the function works, check the code below, you will find all the details as comments ! ⬇️

  // Loop through all segments
for (var i = 0; i < segments; i++) {

  // Get the normal values of the segment from the Frenet frames
  var normal = frames.normals[i];
  // Get the binormal values of the segment from the Frenet frames
  var binormal = frames.binormals[i];

  // Calculate the index of the segment (from 0 to 1)
  var index = i / segments;

  // Get the coordinates of the point in the center of the segment
  // We already used the function in the first part to move the camera along the path
  var p = path.getPointAt(index);

  // Loop for the amount of particles we want along each circle
  for (var j = 0; j < circlesDetail; j++) {

    // Clone the point in the center of the circle
    var position = p.clone();

    // We need to position every point based on an angle from 0 to Pi*2
    // If you want only half a tube (like a water slide) you could calculate the angle from 0 to Pi.
    var angle = (j / circlesDetail) * Math.PI * 2;

    // Calculate the sine of the angle
    var sin = Math.sin(angle);
    // Calculate the negative cosine of the angle
    var cos = -Math.cos(angle);

    // Calculate the normal of each point based on its angle and the normal and binormal of the segment 
    var normalPoint = new THREE.Vector3(0,0,0);
    normalPoint.x = (cos * normal.x + sin * binormal.x);
    normalPoint.y = (cos * normal.y + sin * binormal.y);
    normalPoint.z = (cos * normal.z + sin * binormal.z);

    // Multiple the normal by the radius so that our tube is not a tube of 1 as radius

    // Add the normal values to the center of the circle

    // Push the vector into our geometry

Phew, this code is really not that easy to understand. I myself checked the source code of ThreeJs to create this code.

You can check this demo below to see the particles being calculate one by one.
(Click Rerun if the tube is already fully visible)

2. Create the tube

We now have a Geometry object filled with vertices. With ThreeJs you can create nice particles demos by using the Points constructor that allows you to render with great performances simple points. You can customize those points with a texture or different colors.

The same way we create Mesh, we need two elements to create a Points object. We need a material & a geometry. Since we already have the geometry from step 1, we have to define the material.

  var material = new THREE.PointsMaterial({
  size: 1, // The size of each point
  sizeAttenuation: true, // If we want the points to change size depending of distance with camera
  color: 0xff0000 // The color of the points

Finally, we have to create our Points object and add it into the scene like so :

  var tube = new THREE.Points(geometry, material);

3. Make it move

To make everything move, we will reuse the same code we had in the previous demos.

  var percentage = 0;
function render() {

  // Increase the percentage
  percentage += 0.0005;
  // Get the point where the camera should go
  var p1 = path.getPointAt(percentage % 1);
  // Get the point where the camera should look at
  var p2 = path.getPointAt((percentage + 0.01) % 1);
  camera.position.set(p1.x, p1.y, p1.z);

  // Render the scene
  renderer.render(scene, camera);

  // Animation loop

🎉 Hurray we have a basic tunnel made of particles 🎉

4. Let's get crazy

From what you learned in this second part, you have now access to an unlimited amount of different tunnels ! You can find below three examples of custom tunnels always based on what you learned above.

Colorful tunnel

For this pen, I apply a custom color for each Vector. I also have applied a Fog on the scene to create some fade effect in the tunnel

  // First create a new color based on the index of the vertice
var color = new THREE.Color("hsl(" + (index * 360 * 4) + ", 100%, 50%)");
// Push the color into the colors array in the Geometry object

var material = new THREE.PointsMaterial({
  size: 0.2,
  vertexColors: THREE.VertexColors // We specify that the colors must come from the Geometry

// Add some fog in the scene
scene.fog = new THREE.Fog(0x000000, 30, 150);

Squared cave

This tunnel is only made of Cubes. Instead of using a Points object, I'm creating a new Mesh at each position of the vertices. I also apply the colors based on a Perlin noise algorithm.

Octagonal tunnel

For this pen, I'm connecting the vertices of each circles together to create lines. I'm playing with the angle of the vertices and their colors to create that rotation illusion.

  for (var i = 0; i < tubeDetail; i++) {
  // Create a new geometry for each circle
  var circle = new THREE.Geometry();
  for (var j = 0; j < circlesDetail; j++) {
    // Push the position of the vector
  // Duplicate the first vector to make sure the circle is closed
  // Create a new material with a custom color
  var material = new THREE.LineBasicMaterial({
    color: new THREE.Color("hsl("+(noise.simplex2(index*10,0)*60 + 300)+",50%,50%)")
  // Create a Line object
  var line = new THREE.Line(circle, material);
  // Insert into the scene


Thanks for reading my posts about tunnel animations !

You can find all the demos from the pens into this collection if you want to see them together.

I hope you found this article interesting ! If you create some pretty tunnels, please share them with me --> Twitter. Please do not hesitate to poke me if you have any question 😉


1,899 0 37