- 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
fieldOfViewAngle
the vertical extent of the view volume, given as an angle measured in degreesaspect
the 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, far
give 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.children
which 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:
y
goes upwardz
goes backwardx
goes 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
position
inherits fromVector3
class, 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 fromVector3
class
- You can update it with
mesh.scale.set()
Rotation
- Rotating the objects can be achieved with either
rotation
orquaternion
- 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
Euler
class - 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 z
to update rotation values - You can use
Math.PI
JS’s constant to achieve a half rotation around some axis (e.g.mesh.rotation.x = Math.PI
will 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
Object3D
instances havelookAt()
method that rotates the objects so that its-z
(the fornt face) faces the target you provided- This method takes
Vector3
object - this method can make the camera looks at the center of some object
camera.lookAt( object.position)
Animation
window.requestAnimationFrame(func)
calls the functionfunc
provided 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 * 2
as 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.Camera
is 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.StereoCamera
it is used for VR headsets and it mimics eye view
THREE.CubeCamera
it does 6 renders
THREE.OrthographicCamera
renders the scene w/o perspective
THREE.PerspectiveCamera
renders 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
near
or further thanfar
will 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
left
andright
with 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
tick
function 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 likeFlyControls
with a fixed up axisPointerLookControls
: you can control the view using mouse and keyboard (good for games)OrbitControls
: you can move, rotate, and zoomTrackballControls
is likeOrbitControls
but w/o the vertical angle limitTransformControls
andDragControls
has 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); });