- It is built over WebGL and it has a simple API
- It uses the
<canvas>HTML element
- It is made up of many classes. Three of the most basic are:
- THREE.Scene
- It is the root node of for the scene graph
- It is a holder (list) for all the objects of the 3D world, including lights, objects, and cameras.
- THREE.Camera
- It is a special object that represents a viewpoint.
- It represents a combination of a viewing transformation and a projection
- THREE.WebGLRenderer
- This is the most common renderer
- It uses WebGL 2 if available, or WebGL 1 if v2 isn’t available
- It is an object that can create an image from a scene graph
- There are other types of cameras
camera = new THREE.OrthographicCamera( left, right, top, bottom, near, far );- Similar to glOrtho() in OpenGL
camera = new THREE.PerspectiveCamera( fieldOfViewAngle, aspect, near, far );- Similar to gluPerspective() in OpenGL’s GLU library
fieldOfViewAnglethe vertical extent of the view volume, given as an angle measured in degreesaspectthe ratio between the horizontal and vertical extents (it’s like the window size); it should usually be set to the following division: canvas.width/canvas.heightnear, fargive the z-limits on the view volume as distances from the camera. For a perspective projection, both must be positive, with near less than far
- This command is essential in every Three.js app, as it produce the final results
renderer.render( scene, camera );
- THREE.Object3D
- The scene graph is made up of objects of type
THREE.Object3D - Cameras, lights, visible objects, and even
THREE.Scene - A THREE.Object3D object can holds a list of child THREE.Object3D objects (like linked list)
- This is for hierarchical modeling
- Using
node.add(obj)andnode.remove(obj) - Every node has a pointer to its parent that is automatically made, and it shouldn’t be set directly
obj.parent - The children of THREE.Object3D are stored in
obj.childrenwhich is a JS array. - All classes that inherit from the Obect3D has these properties that are used for object transformation:
- position
- scale
- rotation
- quaternion
Transformation
- Transform properties:
- position
- scale
- rotation
- quaternion
- These properties are compiled in matrices
- Axis in Three.js:
ygoes upwardzgoes backwardxgoes to the right
- Units in Three.js are abstract units. You can think of them like meters
THREE.AxisHelper()class allows you to add axis to the scene
- Transformations are applied to an object or to a group of object
- By if an object contains other objects, then applying transforms on the higher level object will affect the children objects.
- Example: by moving the group object, all the objects inside will be rotated accordingly
const group = new THREE.Group(); group.position.x = 2; scene.add(group); const cube1 = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: 0xff0000 }) ); const cube2 = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: 0xff00ff }) ); cube2.position.x = -2; cube2.position.y = 1; group.add(cube1); group.add(cube2);
Positioning
positioninherits fromVector3class, it has the following methods:mesh.poisition.legnth()gives you the distance between the origin and the center of the meshobj1.poisition.distance(obj2.position)gives you the distance between two objectsmesh.position.normalize()it normalizes position valuesmesh.position.set(x, y, z)can update all coordinates at once
Scaling
- Like
position, it also inherits fromVector3class
- You can update it with
mesh.scale.set()
Rotation
- Rotating the objects can be achieved with either
rotationorquaternion
- Updating one will update the other, so you can choose whatever you want!
Be careful about the ordering of rotation. The rotation goes by default in the
x, y, and z order and you may get trapped in gimbal lock issues (strange axis behaviors). Read more about that https://matthew-brett.github.io/transforms3d/gimbal_lock.htmlRemember that the axises direction change after rotation
- With
rotation: - It inherits from
Eulerclass - In this rotation representation, axis are like sticks come out of the object, and therefore the rotation is done around them
- It has
x, y, and zto update rotation values - You can use
Math.PIJS’s constant to achieve a half rotation around some axis (e.g.mesh.rotation.x = Math.PIwill rotate the mesh a half revolution around x-axis) - Rotation order can be changes using
object.rotation.reorder(’yxz’)before changing the rotation
- With
quaternion: - Euler representation is easy to understand, but most of the software use Quaternion representation of rotation
Object3Dinstances havelookAt()method that rotates the objects so that its-z(the fornt face) faces the target you provided- This method takes
Vector3object - this method can make the camera looks at the center of some object
camera.lookAt( object.position)
Animation
window.requestAnimationFrame(func)calls the functionfuncprovided on the next frame- It does NOT do the loop. It calls the function at every tick
- Animation runs at 60 FPS
- Template
const tick = () => { // Update objects mesh.rotation.x += 0.01; // Render renderer.render(scene, camera); // Call tick at every frame call window.requestAnimationFrame(tick); }; tick();
- If the computer runs at higher FPS, then the animation update will be faster. This need to be fixed to adapt all environments
Animation Adaption
- Solution 1: Use time delta as a factor for object update procedure
const time = Date.now(); // Animations const tick = () => { // Time Delta const currentTime = Date.now(); const deltaTime = currentTime - time; time = currentTime; // Update objects mesh.rotation.x += 0.002 * deltaTime; // Render renderer.render(scene, camera); window.requestAnimationFrame(tick); }; tick();
- Solution 2: Use
THREE.Clock.getElapsedTime()as a factor
// Time let clock = new THREE.Clock(); // Animations const tick = () => { // Clock const elapsedTime = clock.getElapsedTime(); // Update objects mesh.rotation.x = elapsedTime; // Render renderer.render(scene, camera); window.requestAnimationFrame(tick); }; tick();
Trigonometry in Animation
- Use
Math.sin()andMath.cos()to have alternating effect when you update and object’s position
- Example: move a mesh in a circle
// Animations const tick = () => { // Clock const elapsedTime = clock.getElapsedTime(); // Update objects mesh.position.x = Math.sin(elapsedTime); mesh.position.y = Math.cos(elapsedTime); // Render renderer.render(scene, camera); window.requestAnimationFrame(tick); }; tick();
- Use
Math.PI * 2as an update factor to have a full revolution when you update an object’s rotation
Advance Control with GSAP Library
- It allows you to have a full control of animation and create timelines
- This library has a tick function that runs at every frame
Camera
THREE.Camerais an abstract class- It cannot be used directly
- All cameras inherets from this
THREE.ArrayCamera- It is used to render the scene from different POVs on specific areas of area (splitting screen like in multiplayer games)
THREE.StereoCamerait is used for VR headsets and it mimics eye view
THREE.CubeCamerait does 6 renders
THREE.OrthographicCamerarenders the scene w/o perspective
THREE.PerspectiveCamerarenders the scene w/ perspective
Perspective Camera
- Parameters
new THREE.PerspectiveCamera(fov, aspect ratios, near, far) fov: vertical vision angle in degrees - 75 is goodaspect ratio: the width of the render divided by the height of the rendernear: how close the camera can see - 0.1 is goodclose: how far the camera can see - 100 is good- Any object or part of the object closer than
nearor further thanfarwill not show up - Don’t use extreme values like 0.0001 and 99999 to prevent z-fighting glitch
- The params values depend on the scene and the project
Orthographic Camera
- Objects has the same size regardless of their distance from the camera
- Parameters
new THREE.OrthographicCamera(left, right, top, bottom, near, far) - Instead of a FOV, we provide how far the camera can see in each direction
- Think of camera like a square window that view the scene
- For the rectangle canvas, we have to multiply the
leftandrightwith the aspect ratio in order to get right object appearance
const aspectRatio = sizes.width / sizes.height; const camera = new THREE.OrthographicCamera( -1 * aspectRatio, 1 * aspectRatio, 1, -1, 0.1, 100 );
Control with the mouse cursor
- The following code shows how to get the cursor location and update it
const cursor = { x: 0, y: 0, }; window.addEventListener("mousemove", (event) => { // divide it by the canvas size to have a value between [-0.5, 0.5] cursor.x = event.clientX / sizes.width - 0.5; cursor.y = -(event.clientY / sizes.height - 0.5); });
- Then, in the
tickfunction you can update the camera or objects position
- You can move the camera in a circular motion around the object by using trigonometry calculations in the tick function
function tick() { ... // Update camera position camera.position.x = Math.sin(cursor.x * Math.PI * 2) * 3; camera.position.z = Math.cos(cursor.x * Math.PI * 2) * 3; camera.position.y = cursor.y * 5; camera.lookAt(mesh.position); ... }
Control with Orientation Controls (built-in)
- Built-in controls:
DeviceOrientationControls: it uses the device sensorsFlyControls: it gives a control like if you are flyingFirstPersonControls: it is likeFlyControlswith a fixed up axisPointerLookControls: you can control the view using mouse and keyboard (good for games)OrbitControls: you can move, rotate, and zoomTrackballControlsis likeOrbitControlsbut w/o the vertical angle limitTransformControlsandDragControlshas nothing to do with the camera
Orbit Controls
- Import
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
- Instantiate it
// Controls const controls = new OrbitControls(camera, canvas); // enable damping effect controls.enableDamping = true;
- Then add this to the tick function to have the damping effect worked.
controls.update();
Built-in controls are handy, but they may have limitations. You can make your own controls if you don’t want to be limited.
Viewport
- It is the area in which the canvas is placed
Fullscreen
- You can make the canvas size corresponds to the webpage size like that:
const sizes = { width: window.innerWidth, height: window.innerHeight, }; // Use this size in camera and renderer const camera = new THREE.PerspectiveCamera( 75, sizes.width / sizes.height, 0.1, 100 ); renderer.setSize(sizes.width, sizes.height);
- To fix the additional default margins and disable the mouse scroll, add these CSS lines:
* { margin: 0; padding: 0; } .webgl { position: fixed; top: 0; left: 0; outline: none; } html, body { overflow: hidden; }
Handling Window Resizing
- We need to update the following on the window resizing event
- Canvas size
- Camera aspect
- Camera projection matrix
- Renderer size
// Handle resizing window.addEventListener("resize", () => { // Update window size sizes.width = window.innerWidth; sizes.height = window.innerHeight; // Update camera aspect camera.aspect = sizes.width / sizes.height; // Update camera projection matrix camera.updateProjectionMatrix(); // Update renderer renderer.setSize(sizes.width, sizes.height); });