Floor Sampling in Three.js | Codrops


From our sponsor: Attempt Mailchimp at present.

Sooner or later I acquired misplaced within the Three.js documentation and I got here throughout one thing known as “MeshSurfaceSampler“. After studying the little data on the web page, I opened the offered demo and was blown away!

What precisely does this class do? In brief, it’s a instrument you connect to a Mesh (any 3D object) then you may name it at any time to get a random level alongside the floor of your object.

The perform works in two steps:

  1. Choose a random face from the geometry
  2. Choose a random level on that face

On this tutorial we’ll see how one can get began with the MeshSurfaceSampler class and discover some good results we are able to construct with it.

💡 If you’re the sort of one that needs to dig straight away with the demos, please do! I’ve added feedback in every CodePen that can assist you perceive the method.

⚠️ This tutorial assumes fundamental familiarity with Three.js

Making a scene

Step one in (virtually) any WebGL undertaking is to first setup a fundamental scene with a dice.
On this step I can’t go into a lot element, you may examine the feedback within the code if wanted.

We’re aiming to render a scene with a wireframe dice that spins. This manner we all know our setup is prepared.

⚠️ Don’t overlook to additionally load OrbitControls as it’s not included in Three.js package deal.

// Create an empty scene, wanted for the renderer
const scene = new THREE.Scene();
// Create a digital camera and translate it
const digital camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
digital camera.place.set(1, 1, 2);

// Create a WebGL renderer and allow the antialias impact
const renderer = new THREE.WebGLRenderer( antialias: true );
// Outline the dimensions and append the <canvas> in our doc
renderer.setSize(window.innerWidth, window.innerHeight);

// Add OrbitControls to permit the person to maneuver within the scene
const controls = new THREE.OrbitControls(digital camera, renderer.domElement);

// Create a dice with fundamental geometry & materials
const geometry = new THREE.BoxGeometry(1, 1, 1);
const materials = new THREE.MeshBasicMaterial(
  coloration: 0x66ccff,
  wireframe: true
const dice = new THREE.Mesh(geometry, materials);

/// Render the scene on every body
perform render ()   
  // Rotate the dice a bit of on every body
  dice.rotation.y += 0.01;
  renderer.render(scene, digital camera);


See the Pen by Louis Hoebregts (@Mamboleoo) on CodePen.

Making a sampler

For this step we’ll create a brand new sampler and use it to generate 300 spheres on the floor of our dice.

💡 Word that MeshSurfaceSampler shouldn’t be built-in with Three.js. Yow will discover it within the official repository, within the ‘examples’ folder.

After getting added the file in your imported scripts, we are able to provoke a sampler for our dice.

const sampler = new THREE.MeshSurfaceSampler(dice).construct();

This must be carried out solely as soon as in our code. If you wish to get random coordinates on a number of meshes, you have to to retailer a brand new sampler for every object.

As a result of we might be displaying a whole lot of the identical geometry, we are able to use the InstancedMesh class to attain higher efficiency. Juste like a daily Mesh, we outline the geometry (SphereGeometry for the demo) and a fabric (MeshBasicMaterial). After to have these two, you may move them to a brand new InstancedMesh and outline what number of objects you want (300 on this case).

const sphereGeometry = new THREE.SphereGeometry(0.05, 6, 6);
const sphereMaterial = new THREE.MeshBasicMaterial(
 coloration: 0xffa0e6
const spheres = new THREE.InstancedMesh(sphereGeometry, sphereMaterial, 300);

Now that our sampler is prepared for use, we are able to create a loop to outline a random place and scale for every of our spheres.

Earlier than we loop, we’d like two dummy variables for this step:

  • tempPosition is a 3D Vector that our sampler will replace with the random coordinates
  • tempObject is a 3D Object used to outline the place and scale of a sphere and generate a matrix from it

Contained in the loop, we begin by sampling a random level on the floor of our dice and retailer it into tempPosition.
These coordinates are then utilized to our tempObject.
We additionally outline a random scale for the dummy object in order that not each sphere will look the identical.
As a result of we’d like the Matrix of the dummy object, we ask Three.js to replace it.
Lastly we add the up to date Matrix of the thing into our InstancedMesh’s personal Matrix on the index of the sphere we need to transfer.

const tempPosition = new THREE.Vector3();
const tempObject = new THREE.Object3D();
for (let i = 0; i < 300; i++) 
  tempObject.place.set(tempPosition.x, tempPosition.y, tempPosition.z);
  tempObject.scale.setScalar(Math.random() * 0.5 + 0.5);
  spheres.setMatrixAt(i, tempObject.matrix);

See the Pen #1 Floor Sampling by Louis Hoebregts (@Mamboleoo) on CodePen.

Wonderful isn’t it? With only some steps we have already got a working scene with random meshes alongside a floor.

Phew, let’s simply take a breath earlier than we transfer to extra artistic demos ✨

Enjoying with particles

As a result of all people loves particles (I do know you do), let’s see how we are able to generate hundreds of them to create the sensation of quantity solely from tiny dots. For this demo, we might be utilizing a Torus knot as a substitute of a dice.

This demo will work with a really comparable logic as for the spheres earlier than:

  • Pattern 15000 coordinates and retailer them in an array
  • Create a geometry from the coordinates and a fabric for Factors
  • Mix the geometry and materials right into a Factors object
  • Add them to the scene
/* Pattern the coordinates */
const vertices = [];
const tempPosition = new THREE.Vector3();
for (let i = 0; i < 15000; i ++) 
  vertices.push(tempPosition.x, tempPosition.y, tempPosition.z);

/* Create a geometry from the coordinates */
const pointsGeometry = new THREE.BufferGeometry();
pointsGeometry.setAttribute('place', new THREE.Float32BufferAttribute(vertices, 3));

/* Create a fabric */
const pointsMaterial = new THREE.PointsMaterial(
  coloration: 0xff61d5,
  measurement: 0.03
/* Create a Factors object */
const factors = new THREE.Factors(pointsGeometry, pointsMaterial);

/* Add the factors into the scene */

Right here is the outcome, a 3D Torus knot solely produced from particles ✨
Attempt including extra particles or play with one other geometry!

See the Pen #3 Floor Sampling by Louis Hoebregts (@Mamboleoo) on CodePen.

💡 For those who examine the code of the demo, you’ll discover that I don’t add the torus knot into the scene anymore. MeshSurfaceSampler requires a Mesh, however it doesn’t even must be rendered in your scene!

Utilizing a 3D Mannequin

Thus far we’ve got solely been enjoying with native geometries from Three.js. It was a great begin however we are able to take a step additional by utilizing our code with a 3D mannequin!

There are lots of web sites that present free or paid fashions on-line. For this demo I’ll use this elephant from poly.pizza.

See the Pen #4 Floor Sampling by Louis Hoebregts (@Mamboleoo) on CodePen.

#1 Loading the .obj file

Three.js doesn’t have built-in loaders for OBJ fashions however there are numerous loaders obtainable on the official repository.

As soon as the file is loaded, we’ll replace its materials with wireframe activated and scale back the opacity so we are able to see simply by.

/* Create world variable we'll want for later */
let elephant = null;
let sampler = null;
/* Load the .obj file */
new THREE.OBJLoader().load(
  (obj) => 
    /* The loaded object with my file being a gaggle, I would like to choose its first little one */
    elephant = obj.kids[0];
    /* Replace the fabric of the thing */
    elephant.materials = new THREE.MeshBasicMaterial(
      wireframe: true,
      coloration: 0x000000,
      clear: true,
      opacity: 0.05
    /* Add the elephant within the scene */
    /* Create a floor sampler from the loaded mannequin */
    sampler = new THREE.MeshSurfaceSampler(elephant).construct();

    /* Begin the rendering loop */ 

#2 Setup the Factors object

Earlier than sampling factors alongside our elephant we have to setup a Factors object to retailer all our factors.

That is similar to what we did within the earlier demo, besides that this time we’ll outline a customized coloration for every level. We’re additionally utilizing a texture of a circle to make our particles rounded as a substitute of the default sq..

/* Used to retailer every particle coordinates & coloration */
const vertices = [];
const colours = [];
/* The geometry of the factors */
const sparklesGeometry = new THREE.BufferGeometry();
/* The fabric of the factors */
const sparklesMaterial = new THREE.PointsMaterial(
  measurement: 3,
  alphaTest: 0.2,
  map: new THREE.TextureLoader().load("path/to/texture.png"),
  vertexColors: true // Let Three.js is aware of that every level has a unique coloration
/* Create a Factors object */
const factors = new THREE.Factors(sparklesGeometry, sparklesMaterial);
/* Add the factors into the scene */

#3 Pattern a degree on every body

It’s time to generate the particles on our mannequin! However you recognize what? It really works the identical manner as on a local geometry 😍

Because you already know the way to do this, you may examine the code under and spot the variations:

  • On every body, we add a brand new level
  • As soon as the purpose is sampled, we replace the place attribute of the geometry
  • We decide a coloration from an array of colours and add it to the coloration attribute of the geometry
/* Outline the colours we wish */
const palette = [new THREE.Color("#FAAD80"), new THREE.Color("#FF6767"), new THREE.Color("#FF3D68"), new THREE.Color("#A73489")];
/* Vector to pattern a random level */
const tempPosition = new THREE.Vector3();

perform addPoint() 
  /* Pattern a brand new level */
  /* Push the purpose coordinates */
  vertices.push(tempPosition.x, tempPosition.y, tempPosition.z);
  /* Replace the place attribute with the brand new coordinates */
  sparklesGeometry.setAttribute("place", new THREE.Float32BufferAttribute(vertices, 3)  );
  /* Get a random coloration from the palette */
  const coloration = palette[Math.floor(Math.random() * palette.length)];
  /* Push the picked coloration */
  colours.push(coloration.r, coloration.g, coloration.b);
  /* Replace the colour attribute with the brand new colours */
  sparklesGeometry.setAttribute("coloration", new THREE.Float32BufferAttribute(colours, 3));

perform render(a) 
  /* If there are lower than 10,000 factors, add a brand new one*/
  if (vertices.size < 30000) 
  renderer.render(scene, digital camera);

Animate a rising path

A cool impact we are able to create utilizing the MeshSurfaceSampler class is to create a line that can randomly develop alongside the floor of our mesh. Listed here are the steps to generate the impact:

  1. Create an array to retailer the coordinates of the vertices of the road
  2. Choose a random level on the floor to start out and add it to your array
  3. Choose one other random level and examine its distance from the earlier level
    1. If the gap is brief sufficient, go to step 4
    2. If the gap is just too far, repeat step 3 till you discover a level shut sufficient
  4.  Add the coordinates of the brand new level within the array
  5. Replace the road geometry and render it
  6. Repeat steps 3-5 to make the road develop on every body

The important thing right here is the step 3 the place we’ll decide random factors till we discover one that’s shut sufficient. This manner we gained’t have two factors throughout the mesh. This might work for a easy object (like a sphere or a dice) as all of the strains will keep inside the thing. However take into consideration our elephant, what if we’ve got a degree linked from the trunk to one of many again legs. You’ll find yourself with strains the place there needs to be ’empty’ areas.

Verify the demo under to see the road coming to life!

See the Pen #5 Floor Sampling by Louis Hoebregts (@Mamboleoo) on CodePen.

For this animation, I’m creating a category Path as I discover it a cleaner manner if we need to create a number of strains. Step one is to setup the constructor of that Path. Just like what we’ve got carried out earlier than, every path would require 4 properties:

  1. An array to retailer the vertices of the road
  2. The ultimate geometry of the road
  3. A cloth particular for Line objects
  4. A Line object combining the geometry and the fabric
  5. The earlier level Vector
/* Vector to pattern the brand new level */
const tempPosition = new THREE.Vector3();
class Path 
  constructor () 
    /* The array with all of the vertices of the road */
    this.vertices = [];
    /* The geometry of the road */
    this.geometry = new THREE.BufferGeometry();
    /* The fabric of the road */
    this.materials = new THREE.LineBasicMaterial(coloration: 0x14b1ff);
    /* The Line object combining the geometry & the fabric */
    this.line = new THREE.Line(this.geometry, this.materials);
    /* Pattern the primary level of the road */
    /* Retailer the sampled level so we are able to use it to calculate the gap */
    this.previousPoint = tempPosition.clone();

The second step is to create a perform we are able to name on every body so as to add a brand new vertex on the finish of our line. Inside that perform we’ll execute a loop to seek out the subsequent level for the trail.
When that subsequent level is discovered, we are able to retailer it within the vertices array and within the previousPoint variable.
Lastly, we have to replace the road geometry with the up to date vertices array.

class Path 
  constructor () ...
  replace () 
    /* Variable used to exit the whereas loop once we discover a level */
    let pointFound = false;
    /* Loop whereas we have not discovered a degree */
    whereas (!pointFound) 
      /* Pattern a random level */
      /* If the brand new level is much less 30 models from the earlier level */
      if (tempPosition.distanceTo(this.previousPoint) < 30) 
        /* Add the brand new level within the vertices array */
        this.vertices.push(tempPosition.x, tempPosition.y, tempPosition.z);
        /* Retailer the brand new level vector */
        this.previousPoint = tempPosition.clone();
        /* Exit the loop */
        pointFound = true;
    /* Replace the geometry */
    this.geometry.setAttribute("place", new THREE.Float32BufferAttribute(this.vertices, 3));

perform render() 
  /* Cease the development as soon as we've got reached 10,000 factors */
  if (path.vertices.size < 30000) 
    /* Make the road develop */
  renderer.render(scene, digital camera);

💡 The worth of how quick the gap between the earlier level and the brand new one relies on your 3D mannequin. When you’ve got a really small object, that distance might be ‘1’, with the elephant mannequin we’re utilizing ’30’.

Now what?

Now that you understand how to make use of MeshSurfaceSampler with particles and contours, it’s your flip to create funky demos with it!
What about animating a number of strains collectively or beginning a line from every leg of the elephant, and even popping particles from every new level of the road. The sky is the restrict ⛅

See the Pen #6 Floor Sampling by Louis Hoebregts (@Mamboleoo) on CodePen.

This text doesn’t present all of the obtainable options from MeshSurfaceSampler. There may be nonetheless the burden property that means that you can have roughly likelihood to have a degree on some faces. After we pattern a degree, we may additionally use the traditional or the colour of that time for different artistic concepts. This might be a part of a future article someday… 😊

Till subsequent time, I hope you discovered one thing at present and that you could’t wait to make use of that new data!

When you’ve got questions, let me know on Twitter.

Leave a Reply

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

Related Posts