Moment For Technology

Write a small scene with three.js

Posted on Dec. 2, 2022, 10:25 a.m. by Matthew Skinner
Category: The front end Tag: webgl three.js

Last time we wrote a rain animation with three.js, mainly with particles. This time, we built a small scene with three.js.

The project address is still: , and the exercise demo for the following three.js is here.

As a small item for exercise, this small scene can be practiced:

  1. Use of all kinds of geometry and texture
  2. Importing an external model
  3. Scenarios interact with users
  4. Simple animation
  5. Debug the three.js project

It's very simple, but it's very rewarding to write it all down.

The grass scene

The scene construction uses the scene Template Template from the rain animation.

The first is to build a house on the grass. The realization of the grass is a large plane geometry, and then the texture of the grass is repeated to cover the whole plane.

const groundGeometry = new PlaneGeometry( 20000, Const groundTexture = new TextureLoader().load('/images/room/grass. JPG ') // Load the grass texture = groundtexture.wrapt = RepeatWrapping // set repeattexture.repeat.set (50, 16) groundtexture.anisotropy = 16 const groundMaterial = new MeshLambertMaterial({// generate texture map: GroundTexture}) const ground = new Mesh(groundGeometry, groundMaterialCopy the code

In order not to be obtuse, we used the sky color as the background color of the canvas, and then added the atomization effect in the distance.

RendererColor = new Color(0xcce0ff) // Set the canvas background Color //renderer.setClearColor(this.renderercolor) this.scene.fog = new Fog(0xcce0ff, 2500, 10000) // Add the effect of atomizationCopy the code

In this process, constantly adjust the camera position and field of view to a suitable field of view. We finally choose the position and the distance between the near and far sides is:

this.PCamera.far = 10000
this.PCamera.near = 1
this.cameraPostion = new Vector3(1000, 600, 1500)
Copy the code

Building a house

Our house is built to the left and right of the origin to facilitate the setting of coordinates.

To facilitate debugging, we add an AxesHelper to the scenario, which is used for the three axes in a simple simulation scenario. Red represents the X-axis, green represents the Y-axis, and blue represents the z-axis.

Const axesHelper = new axesHelper (700) // Create the axesHelper, 700 is the length of three lines this.scene.add(axesHelper) // Add the axesHelper to the sceneCopy the code

With the AxesHelper, it's a lot easier to set the position in the frame, the rotation and so on.

  • First, create a ground, just like the grass above,PlaneGeometryAnd map.
const floorGeometry = new PlaneGeometry( 800, 1000 ) const floorTexture = new TextureLoader().load('/images/room/floor.png') floorTexture.wrapS = floorTexture.wrapT =  RepeatWrapping floorTexture.repeat.set( 25, 25 ) floorTexture.anisotropy = 16 const floorMaterial = new MeshLambertMaterial({ map: floorTexture }) const floor = new Mesh( floorGeometry, floorMaterial )Copy the code

  • Then there are the walls. According to the shape of the wall is mainly divided into: front wall, back wall and side wall. The back wall is the simplest, an ordinary cube, the side wall is an irregular cube, and the front wall is a cube with a door and a window cut into it.

For the back wall we use BoxGeometry directly.

const boxGeometry = new BoxGeometry( ... arguments ) const boxMaterial = new MeshLambertMaterial({ color: 0xe5d890 }) const box = new Mesh( boxGeometry, boxMaterial )Copy the code

ExtrudeGeometry for side wall and front wall. ExtrudeGeometry creates a three-dimensional shape out of a two-dimensional shape. We can first draw a two-dimensional shape, and ExtrudeGeometry will "thicke" this two-dimensional shape to produce a column. The analogy goes from a flat circle to a cylinder.

In the case of the side wall, we will first draw the following shape and then "thicken" it:

Const shape = const shape (); const shape = const shape (); 0) shape.lineto (400, 400) 0) lineTo(0,500) const extrudeSettings = {//Extrude configuration, Amount: 8, bevelSegments: 2, steps: 2, bevelSize: 1, bevelThickness: 1} Extrude Extrude = new ExtrudeGeometry const geometry = new ExtrudeGeometry(shape, extrudeSettings ) } const wallGeometry = drawShape() const wallMaterial = new MeshLambertMaterial({ color: 0xe5d890 }) const wall = new Mesh( wallGeometry, wallMaterial )Copy the code

The front wall is painted similar to the side wall, except that two holes are "dug" for the door and window. It's also digging into two-dimensional shapes.

Const shape = new shape () const shape = const shape (); 0) shape.lineto (-500,400) const window = new Path() window.moveto (100,100) Window.lineto (100,250) window.lineto (300,250) window.lineto (300,100) shape.holes. Push (window) // Add the shape of the window to the shape.holes array, Will subtract the window shape from the current shape. MoveTo (-330,30) door.lineto (-330, 250) door.lineto (-210, -330, 250) LineTo (-210, 30) Shap.holes.push (door) // Add the shape of the door to the shap.holes array const extrudeSettings = {amount: 8, bevelSegments: 2, steps: 2, bevelSize: 1, bevelThickness: 5 } const geometry = new ExtrudeGeometry( shape, extrudeSettings ) return geometry }Copy the code

In this way, the four walls are drawn, the ExtrudeGeometry can be realized as a hollow column of various shapes, and the door and window frames at the back are also based on it.

  • And finally, the roof. The roof is used for twoBoxGeometry, set the appropriate position and rotation Angle to achieve eachBoxGeometryOne of them is textured and the remaining five are solid colors.
const roofGeometry = new BoxGeometry( 500, 1300, Const roofTexture = new TextureLoader().load('/images/room/roof.png') // Import roofTexture Repeattexture.wrapt = repeattexture.repeat.set (2, 2) const materials = [] Const colorMaterial = new MeshLambertMaterial({color: 'grey' }) const textureMaterial = new MeshLambertMaterial({ map: roofTexture }) for(let i=0; i6; I++){materials.push(colorMaterial)} materials[5] = textureMaterial Const roof = new Mesh(roofGeometry, materials)Copy the code

Then you have to adjust its position, and the Angle, so that the roof fits the side wall.

Join the doors and Windows

Doors are divided into panels and frames. They are different shapes and materials, but they are one. The same goes for Windows and window frames.

In three.js, we use the Group class to manage a Group of objects.

Const group = new group () // Create the group group.add(this.frame) // Add the door frame to the group group.add(this.door) // Add the door panel to the groupCopy the code

One of the benefits of this is that the door panels and frames as a whole can be set in position, direction of rotation, etc. For example, to adjust the position and orientation of the door, we only need to move and rotate the group, instead of operating the door panel and door frame respectively.

Of course, the door panels and frames also have displacement and rotation relative to the group, such as opening and closing animation.

InitFrame () {const frameGeometry = this.drawShape() // The shape of the door frame is const frameematerial = new using 'ExtrudeGeometry' MeshLambertMaterial({// 0x8d7159 }) const frame = new Mesh( frameGeometry, FrameMaterial) this.frame = frame} initDoor () {const doorGeometry = new BoxGeometry(100,210,4 doorTexture = new TextureLoader().load('/images/room/wood.jpg') const doorMaterial = new MeshLambertMaterial({ map: Const door = new Mesh(doorGeometry, doorMaterial) This.param = {positionX: 60, positionZ: 0, rotationY: 0} door.position.set(this.param.positionx, 105, this.param.positionz) // The displacement and rotation of the door relative to the group. door.rotation.y = this.param.rotationY this.door = door this.status = 'closed' }Copy the code

Opening and Closing animation

Determine whether the mouse clicks on an object, convert the mouse click position to the position in three-dimensional space, emit rays from the camera position to the three-dimensional space position after the click, determine whether the object is on this ray, if it is, it means that the object is clicked.

window.addEventListener('click', Function onMouseDown (event) {let vector = new Vector3(// normalize the mouse position to the device coordinates. The x and Y values range from (-1 to +1) (event.clientx/window.innerWidth) * 2-1, -(event.clienty/window.innerHeight) * 2 +1, 0.5) vector = vector.unproject( const Raycaster = new Raycaster(// update raycaster to this. Camera. Position, Vector.sub ( // Calculate the intersection point of the object and the ray const Intersects =. Intersects = intersects = intersects = intersects = intersects = intersects = intersects = intersects = intersects = intersects = intersects = intersects raycaster.intersectObjects([this.doorSet.door]) if(intersects.length  0){ this.doorSet.animate() } }Copy the code

After the door is clicked, it determines whether the state of the door is open or closed, and sets the position and rotation of the next state (relative to the group) based on the state.

animate () {
    if(this.status === 'closed'){  
      this.param.positionX = 10
      this.param.positionZ = 50
      this.param.rotationY = -Math.PI/2
      this.status= 'open'
      this.param.positionX = 60
      this.param.positionZ = 0
      this.param.rotationY = 0
      this.status= 'closed'

onUpdate (param) {
    this.door.position.x = param.positionX
    this.door.position.z = param.positionZ
    this.door.rotation.y = param.rotationY
Copy the code

Draw a window

The window frame is drawn with the same operation as the front wall, using ExtrudeGeometry.

The Windows are made of BoxGeometry and MeshPhysicalMaterial, which is set to a certain transparency to simulate the effect of glass.

const windowGeometry = new BoxGeometry( 150, 200, 4 ) const windowMaterial = new MeshPhysicalMaterial( { map: Null, color: 0xcfCFCF, Metalness: 0, Roughness: 0, opacity: 0.45, height: true, envMapIntensity: 0 10, premultipliedAlpha: true } ) const window = new Mesh( windowGeometry, windowMaterial )Copy the code

Like doors, Windows and window frames are added to a group.

Import table and flowers

Tables and flowers are imported external models. For complex models, it is quite troublesome to directly build with three.js. We can use special modeling software to model and then export the model.

Three. js supports importing a variety of 3D models, here we use OBJ.

OBJ and MTL are two complementary formats and are often used together. The OBJ file defines the geometry, while the MTL file defines the materials used. They are imported by the responding Loader. Take the import table as an example:

Import {OBJLoader} from "three/examples/JMS/loaders/OBJLoader" / / introduce OBJLoader import from {MTLLoader} "Three/examples/JMS/loaders/MTLLoader" / / introduce MTLLoader function addTable (scene) {const MTLLoader = new MTLLoader const ()  objLoader = new OBJLoader() mtlLoader.load( '.. /.. /images/room/table/table.mtl', (material) = {// import objloader.setMaterials (material) // set objloader.load ('.. /.. /images/room/table/table.obj', (object) = {// import the shape object.position.set(600,0,0) // set the position of the shape scene.add(object) // add the shape to the scene}); }) } addTable(this.scene)Copy the code


Three.js provides a number of camera controls that you can use to control the camera in your scene. Here are a few of the most commonly used controls.We're using an orbital controller (OrbitControls), which can be used to control the rotation and translation of objects in the scene around the center of the scene. The way to use it is simple:

import { OrbitControls } from "three/examples/jsm/controls/OrbitControls" function addOrbitControls (camera, el) { const controls = new OrbitControls( camera, El) // the parameter is the upper limit of the camera to be controlled and the HTML element (usually renderer.domElement) controls. MaxPolarAngle = Math.pi * 0.45 // the upper limit of the vertical rotation Angle } addOrbitControls(this. Camera, this. Renderer. DomElement)Copy the code

OrbitControls allows us to hold down the left mouse button to rotate the screen, hold down the right mouse button to pan the screen, and use the mouse wheel to shrink the screen. These are all configurable, so here we forbid panning and set the upper limit of the vertical rotation Angle to prevent the screen from moving out of the grass.


Dat.gui can easily create interface components that can change code variables, which can simplify debugging of three.js. In the official case of Three, we can often see the use of dat.gui.

Use methods can refer to this article, hand to hand teach you to use dat. GUI, very detailed.

We use it here to control the display and hide of axes and roofs.

Import {GUI} from 'dat.gui' export function GUI () { Const controls = new function () {this.showaxes = false this.showroof = true} const GUI = new GUI() gui.add(controls, 'showAxes') gui.add(controls, 'showRoof') return controls }Copy the code
//director.js import { Gui } from ".. /tools/dat.gui" this.controls = GUI ()" Roof animate () {if(this.controls. ShowAxes){this.scene.add(this.axesHelper)}else{if(this.controls. this.scene.remove( this.axesHelper ) } if(this.Controls.showRoof){ this.scene.add( this.roof_1 ) this.scene.add( this.roof_2 ) }else{ this.scene.remove( this.roof_1 ) this.scene.remove( this.roof_2 ) } this.renderer.render(this.scene, requestAnimationFrame(this.animate.bind(this)) }Copy the code

That's all for today, the Threejs-Tutorial project on Github will continue to update a variety of three cases, welcome to pay attention to oh ~~

About (Moment For Technology) is a global community with thousands techies from across the global hang out!Passionate technologists, be it gadget freaks, tech enthusiasts, coders, technopreneurs, or CIOs, you would find them all here.