Use of scene, camera, renderer and other method classes in ThreeJs

ThreeJs Notes

Introduction

Web Graphics Library (Web Graphics Library) is a JavaScript API that renders high-performance interactive 3D and 2D graphics in any compatible web browser without the use of plug-ins. WebGL does this by introducing an API that is very consistent with OpenGL ES 2.0 and can be used within HTML5 elements. This consistency allows the API to take advantage of the hardware graphics acceleration provided by the user’s device. Through these interfaces, developers can communicate directly with the GPU.

WebGL programs are divided into two parts:

  • A program written in Javascript that runs on the CPU
  • Shader programs written in GLSL to run on the GPU

The shader program receives the data from the CPU, performs certain processing, and finally renders it into colorful application styles. The shader program is as follows:

//Vertex shader
var vertex_shader_source =
    'void main() {' +
    ' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);' + // Set vertex coordinates
    ' gl_PointSize = 10.0;' + // Set the size of the vertices
    '' +
    '}';
 
// fragment shader
var fragment_shader_source =
    'void main(){' +
    ' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);' + //Set vertex color
    '}';

There are only three basic primitives that native WebGL can draw, namely points, line segments, and triangles, which correspond to points, lines, and surfaces in the physical world. All complex figures or cubes first use points to form a basic structure, then use triangles to fill the planes formed by these points, and finally use multiple planes to form a geometry. But the reality is that if you want to generate complex shapes that meet various application scenarios, the geometric structure will be very complex and the code will be very complicated to write. So we chose to use some 3D rendering frameworks to realize the complex shapes of the application scene.

For example, threeJs (Three.js is the most documented and widely used three-dimensional engine in China)

ThreeJs is a lightweight, cross-platform Javascript library based on native WebGL. It can be combined with HTML5 canvas, SVG or WebGL on the browser to create and display 3D models and animations. Allows us to create a GPU-accelerated 3D animation scene without relying on any browser plug-ins, which may benefit from the emergence of WebGL, because the underlying implementation of WebGL is based on OpenGL.

  • Three.js official website

Getting started with ThreeJs

Creating a WebGL program natively generally requires 4 steps:

  • Initialize the WebGL drawing context
  • Initialize shader program
  • Build models and data cache
  • Complete drawing and animation

But it is different for Treee.js. It uses an object-oriented approach to build the program, including three basic objects: scene, camera, and renderer.

1. Get three.js

demo version control

"node": "v16.18.1",
"three": "0.156.1",
"vue": "3.3.4",

Download dependencies

npm install three

introduced in the project

import * as THREE from 'three'; //Import all core packages
import {<!-- --> Scene } from 'three'; //Import on demand
//Introduce add-ons, add-ons must be imported explicitly, such as orbit controls OrbitControls
import {<!-- --> OrbitControls } from ' three/examples/jsm/controls/OrbitControl

Note: Add-ons and other dependencies need to be introduced separately. Three built-in dependencies are built in the examples folder

2. Create scene, camera, renderer (canvas)

  • Scene: used to add the created model to the canvas.
  • Camera: equivalent to the position of the human eye;
  • Renderer: equivalent to canvas canvas element.
  • After creating the three elements, insert the canvas into the html element
const scene = new THREE.Scene();
//Set scene background
const textureLoader = new THREE.TextureLoader()
// scene.background = new THREE.Color(0xaaaaaa)
scene.background = textureLoader.load('src/assets/images/t2.png', () => {<!-- -->
  renderer.render(scene, camera)
})

//Create camera
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000); // (angle, aspect ratio, nearest seen distance, farthest)
camera.position.x = -30;
camera.position.y = 40;
camera.position.z = 30;
camera.lookAt(scene.position); //Point the camera to the scene

//Create webgl renderer (canvas)
const renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xeeeeee); //canvas canvas color will be overwritten by scene.background
renderer.setSize(window.innerWidth, window.innerHeight); //canvas canvas size
\t
// Insert this canvas element into html
document.getElementById('chartlet-box').appendChild(renderer.domElement);
renderer.render(scene, camera);

Create camera PerspectiveCamera parameter diagram

3. Create an auxiliary coordinate system

  • Set the position of elements more intuitively
  • Use scene.add to add elements to the canvas
  • Must be inserted before appendChild
// Auxiliary coordinate system
const axesHelper = new THREE.AxesHelper(200) //Parameter 200 indicates the coordinate system size, which can be set according to the scene size
scene.add(axesHelper)

Display of results:

4. Creation of plane, cube, sphere and line

  • The creation of a plane requires two objects: size (PlaneGeometry) and material (MeshStandardMaterial);
  • THREE.Mesh creates a mesh model object. Geometry cannot be rendered. Only geometry and materials combined into a mesh can be rendered to the screen.
  • scene.add adds to the canvas
//Bottom seat
const planeGeometry = new THREE.PlaneGeometry(200, 200)
const planeMaterial = new THREE.MeshLambertMaterial({<!-- --> color: 0xaaaaaa, opacity: 1 })
const plane = new THREE.Mesh(planeGeometry, planeMaterial)
plane.rotation.x = -0.5 * Math.PI
plane.position.y = -25
//Tell the bottom plane that it needs to receive shadows
plane.receiveShadow = true
scene.add(plane)

// cuboid model
const geometry = new THREE.BoxGeometry(200, 20, 200)

//Model material
const material = new THREE.MeshLambertMaterial({<!-- -->
  color: 'rgb(39, 148, 177)'
})

// Model material picture
const texture = textureLoader.load('src/assets/images/t1.png', () => {<!-- -->
  renderer.render(scene, camera)
})
const material1 = new THREE.MeshLambertMaterial({<!-- -->
  map: texture
})
const mesh = new THREE.Mesh(geometry, [material, material, material, material, material1, material])
mesh.castShadow = true
scene.add(mesh)

// draw lines
const myLineMaterial = new THREE.LineBasicMaterial({<!-- --> color: 'red' })
const geometryBuffer = new THREE.BufferGeometry()
const points: any[] = []
points.push(new THREE.Vector3(50, 0, 101))
points.push(new THREE.Vector3(50, -100, 101))
geometryBuffer.setFromPoints(points)
const line = new THREE.Line(geometryBuffer, myLineMaterial)
scene.add(line)

// draw a circle
const geometryCircle = new THREE.CircleGeometry(5)
const materialCircle = new THREE.MeshBasicMaterial({<!-- --> color: 0xffff00 })
const circle = new THREE.Mesh(geometryCircle, materialCircle)
circle.position.x = 50
circle.position.y = -25
circle.position.z = 102
circle.name = 'test'
scene.add(circle)

Display effect: At this point we can see that the ground and cube have no color because there is no light source. Let’s add a light source below.

5. Creation of light source

  • Create the type and color of the light (the types of light sources can be viewed in the official documentation)
  • Set the position of the light source
  • Set the light source and the length of the projection
  • scene.add to canvas
//Light source Ambient light will illuminate all objects in the scene evenly
const ambient = new THREE.AmbientLight(0x404040, 16)
scene.add(ambient)

// Parallel light can cast shadows and display shadow textures
const directionalLight = new THREE.DirectionalLight()
directionalLight.position.set(200, 200, 200)
directionalLight.shadow.camera.near = 20 //The closest distance to produce shadow
directionalLight.shadow.camera.far = 200 //The farthest distance that produces shadows
directionalLight.shadow.camera.left = -50 //The leftmost position of the shadow distance position
directionalLight.shadow.camera.right = 50 //rightmost
directionalLight.shadow.camera.top = 50 //Top
directionalLight.shadow.camera.bottom = -50 //bottom
//These two values determine how many pixels are used to generate shadows. The default is 512.
directionalLight.shadow.mapSize.height = 1024
directionalLight.shadow.mapSize.width = 1024
//Tell the parallel light that shadow casting needs to be turned on
directionalLight.castShadow = true
scene.add(directionalLight)

Show results:

6. Mouse control of three-dimensional scenes

  • To control a three-dimensional scene with the mouse, you need to introduce the OrbitControls specified control plug-in to realize mouse zooming and dragging the canvas.
import {<!-- --> OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

// Track control
const controls = new OrbitControls(camera, renderer.domElement)
controls.addEventListener('change', () => {<!-- -->
  renderer.render(scene, camera)
})
  • Use OrbitControls to control a model to always face the camera
controls.addEventListener('change', () => {<!-- -->
  const {<!-- --> x, y, z } = camera.position
  scene.traverse((child) => {<!-- -->
    if (child.name === 'test') {<!-- -->
      child.lookAt(x, y, z)
    }
  })
  renderer.render(scene, camera)
})

7. Add click event

  • The generated dom of the canvas adds click events through addEventListener
  • Combined with the Raycaster class for mouse picking and calculating the position of the mouse or touch point
  • Obtain the clicked object array based on the ray calculation of the intersection points with all objects.
renderer.domElement.addEventListener('click', (event: any) => {<!-- -->
  const raycaster = new THREE.Raycaster() // Raycasting is used for mouse picking
  const mouse = new THREE.Vector2()
  // Calculate the position of the mouse or touch point
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
  // update ray
  raycaster.setFromCamera(mouse, camera)
  // Calculate intersection points with all objects
  const intersects = raycaster.intersectObjects(scene.children, true)
  if (intersects.length > 0) {<!-- -->
    // Handle click event
    // intersects[0] contains the first intersection point
    const clickedObject: any = intersects[0].object
    //Match by name by clicking on the model
    if (clickedObject.name === 'test') {<!-- -->
      console.log('Current model information obtained:', clickedObject)
      clickedObject.material.color.set('pink')
      renderer.render(scene, camera)
    }
  }
})

Introduction to other auxiliary classes of ThreeJs

1. Load the 3D model file glb

  • The corresponding loader needs to be introduced separately
  • Solve the error: THREE.GLTFLoader: No DRACOLoader instance provided
import {<!-- --> GLTFLoader } from 'three/addons/loaders/GLTFLoader'
import {<!-- --> DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'

const loader = new GLTFLoader()
/* Error: THREE.GLTFLoader: No DRACOLoader instance provided */
/*
Solution:

Get the draco corresponding to the three version from the package installed by node_modules, the path is node_modules\three\examples\js\libs\draco

Copy the folder to the public folder and set the corresponding path when DRACOLoader.setDecoderPath
----------------
Copyright statement: This article is an original article by CSDN blogger "Retire Early!" and follows the CC 4.0 BY-SA copyright agreement. Please attach the original source link and this statement when reprinting.
Original link: https://blog.csdn.net/weixin_48894212/article/details/127241897
*/
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/public/draco/') //Set the decoding path under public, pay attention to the / at the end
loader.setDRACOLoader(dracoLoader)
loader.load(
  '/src/assets/glb/LittlestTokyo.glb',
  function (gltf: any) {<!-- -->
    scene.add(gltf.scene)
  },
  undefined,
  function (error: any) {<!-- -->
    console.error(error)
  }
)

2. Create css3D renderer

  • The CSS3DObject provided by ThreeJs converts a document dom object into an Object3D object available for ThreeJs. CSS3DRenderer is the renderer responsible for rendering this CSS3DObject.
  • The creation method of the renderer is the same as that of the webgl renderer. You need to set the positioning style of the renderer dom separately to display it on the webgl canvas.
  • When the webgl renderer and the css3D renderer exist at the same time, the css3D renderer sets the style position to be displayed on the top layer.
  • In terms of track control and event processing, it is passed directly through the 3D renderer. If loaded on the webgl renderer, it will cause inoperability
const createTag = (obj: any) => {<!-- -->
  const element = document.createElement('div')
  element.className = 'tag'
  element.innerHTML = `<p>Name:${<!-- -->obj.name}</p><p>Temperature: 22°</p><p>Humidity: 29%</p>`
  // css3d adds click event
  element.addEventListener('pointerdown', () => {<!-- -->
    console.log('click')
  })
  const object = new CSS3DObject(element)
  object.visible = true
  //scaling ratio
  object.scale.set(0.2, 0.2, 0.2)
  //Specify placement position
  object.position.copy(obj.position)
  return object
}
const tags: any[] = []
scene.traverse((child) => {<!-- -->
  if (child.isObject3D & amp; & amp; child.children.length === 0) {<!-- -->
    //Add label text
    const tag = createTag(child)
    tags.push(tag)
    scene.add(tag) //Add to the specified scene
  }
})

const render3D = new CSS3DRenderer()
//Set renderer size
render3D.setSize(width, height)
//Need to set the location----------------------Key points---------------------- -----
render3D.domElement.style.position = 'absolute'
render3D.domElement.style.top = '0'
//The renderer should also add the same controller
const controls1 = new OrbitControls(camera, render3D.domElement)
render3D.render(scene, camera)

scene.tarverse: This method accepts a function as a parameter, traverses the caller and all its descendants, and executes the function

3. About materials and textures

  • For material types, please refer to the official website documents or recommended blogs.
// color
const material = new THREE.MeshLambertMaterial({<!-- -->
  color: 'rgb(39, 148, 177)'
})
// picture
const textureLoader = new THREE.TextureLoader()
const texture = textureLoader.load('src/assets/images/t1.png', () => {<!-- -->
  // After loading the image, refresh the page to display the material
  renderer.render(scene, camera)
})
const material1 = new THREE.MeshLambertMaterial({<!-- -->
  map: texture
})