Sebelumnya kita sudah belajar buat object di bab 02 Elemen Utama ThreeJS dan Mengubah bentuk object tersebut di bab 03 Transform Object, paling cocok lanjut ke pengenalan bikin animasi dari object tersebut.
Tujuan dari membuat animasi di ThreeJS yang baik adalah bagaimana caranya membuat gerakan animasi dengan mulus berapapun frame rate yang mampu di hasilkan komputer. Walaupun komputernya butut tapi animasi bisa tetap mulus.
Ada Beberapa cara untuk membuat animasi tersebut
1. Pakai RequestAnimationFrame
Tujuan utama pakai RequestAnimationFrame adalah jangan sampai jalanin fungsi ini di setiap frame. Langsung aja contohnya biar paham:
/**
/* Animasi
*/
const tick = () =>
{
window.requestAnimationFrame(tick)
}
tick()INFO
Pola ini sangat umum di pakai untuk animasi di web
Jadi, kalau lihat di kode di atas, polanya akan terus looping untuk memanggil fungsi requestAnimationFrame.
tick()di panggil pertama kali,- pas
tick()di panggil, di dalam fungsitick()eh ada methodrequestAnimationFrameyang meminta untuk memanggil lagi fungsitick()lagi sebelum frame selanjutnya di-render. - Kalau belum tahu apa gunanya
requestAnimationFrame, ini adalah method yang fungsinya membuat animasi secara halus, method ini ngasih tahu browser untuk menjalankan suatu fungsi yang di panggil tepat sebelum layar me-render frame berikutnya. - Oleh karena itu, ketika browser me-render frame selanjutnya, browser akan manggil fungsi
tick()lagi, yang mana di dalamnya nyuruh lagi manggil fungsitick(). Gitu aja terus sampai komputernya jebol. - Nah, proses berulang ini menghasilkan 60-120 kali perulangan per detik tergantung dari refresh-rate layar.
Kalau sudah paha cara buat infinite loop tiap frame, pola selanjutnya yang wajib di hafal adalah seperti ini:
/**
/* Animasi
*/
const tick = () =>
{
//1. Update posisi/logika objek di sini
//2. Render ulang canvas/scene
//3. Minta frame berikutnya
window.requestAnimationFrame(tick)
}
tick()kalau ingin ada animasi maka proses render dan update gerakan, posisi, atau logika yang ingin di ulang masukin ke fungsi ini. Contoh:
const tick = () =>
{
//1. Update posisi/logika objek di sini
mesh.rotation.y += .01
//2. Render ulang canvas/scene
renderer.render(scene, camera)
//3. Minta frame berikutnya
window.requestAnimationFrame(tick)
}Nah, dengan begini animasi sudah berhasil di buat. TAPI ANIMASI INI TERGANTUNG SPEK KOMPUTER, KALAU KOMPUTERNYA KENCENG ANIMASI BAKAL KENCENG, KALAU KOMPUTERNYA KENTANG ANIMASI BAKAL LAMBAT.
2. Adaptasi dengan framerate
Cara yang sebelumnya menghasilkan kecepatan animasi yang berbeda-beda tergantung kualitas frame-rate layar. Kenapa? Karena begini:
- Kalau layarnya 60Hz, maka fungsi
tick()akan di panggil 60x dalam 1 detik. Maka, total rotasinya 60 x 0,01 (nilai yang kita tentuin di baris codemesh.rotation.y) = 0,6 rad/detik, sedangkan - Kalau layarnya 144Hz, maka fungsi
tick()akan di panggil 144x dalam 1 detik. Maka, total rotasinya 144 x 0,01 = 1,44 rad/detik, gerakan animasi akan lebih cepat 2,4 kali lipat.
Nah, biar animasi tetap terlihat sama berapapun nilai frame-rate layar kamu, maka gerakan animasinya daripada ngiket sama frame-rate layar, kita iket sama jumlah waktu (Delta time Δt) nyata alias durasi yang waktu yang terlewat, karena 1 detik di layar bagus ataupun murah ya tetap 1 detik.
let time = Date.now()
const tick = () =>
{
//0. Update Time
const currTime = Date.Now()
const deltaTime = currTime - time
time = deltaTime
// 1. Update posisi/logika objek di sini
mesh.rotation.y += .01 * deltaTime
// 2. Render ulang canvas/scene
....
}Kalau kita update seperti ini fungsi tick-nya, maka hasilnya gerakan animasinya terikat sama jumlah durasi waktu yang udah berjalan. Jelasnya begini:
- Kalau layarnya 60Hz, maka jeda antar frame sekitar 16,6ms, sehingga total rotasinya di layar tersebut per frame adalah 0,01 x 16,6 = 0,166, maka dalam 1 detik jumlah rotasinya adalah 60 x 0,166 = 10 rad/detik
- Kalau layarnya 144Hz, maka jeda antar frame sekitar 6,9ms, total rotasi per frame adalah 0,01 x 6,9 = 0,069, maka dalam 1 detik jumlah rotasinya adalah 144 x 0,069 = 10 rad/detik.
3. Fungsi Clock Three JS
Cara di atas ada yang lebih sederhana, ada fungsi Clock dari ThreeJS yang menghasilkan hal yang sama seperti di atas.
INFO
Saya sengaja mengajarkan dulu 1. Pakai `RequestAnimationFrame` dan 2. Adaptasi dengan *framerate* supaya paham apa yang dihasilkan oleh
Clock
Code di atas bisa di ubah sederhana jadi begini:
/**
* Animate
*/
const clock = new THREE.Clock()
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
// Update objects
camera.position.x = Math.cos(elapsedTime)
camera.position.y = Math.sin(elapsedTime)
camera.lookAt(mesh.position)
// ...
}
tick()4. Animasi pakai library GSAP
Kalau kalian pernah dengar library GSAP, itu adalah library untuk animasi di web untuk hasil yang advanced. Kalau kalian terbiasa pakai GSAP kalian juga bisa memanfaatkannya di ThreeJS.
Import GSAP
Seperti biasa kalian install dulu, saya pakai versi 3.12: npm i [email protected] run ini di terminal.
Kalau sudah install lanjutkan dengan import di script.js
import './style.css'
import * as THREE from 'three'
import gsap from 'gsap'Contoh cara pakainya seperti ini jika di pakai bersama dengan animasi ThreeJS:
/**
* Animate
*/
gsap.to(mesh.position, { duration: 1, delay: 1, x: 2 })
const tick = () =>
{
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
tick()Kalau kalian ingin lebih dalam mempelajari GSAP, silakan cek di sini: https://greensock.com/gsap/. Ini hanya informasi tambahan saja, jadi tidak akan di pelajari lebih dalam, kalau kalian mau silakan baca sendiri di sana.
Hasil akhir script.js
import * as THREE from 'three'
import gsap from 'gsap'
/**
* Base
*/
// Canvas
const canvas = document.querySelector('canvas.webgl')
// Scene
const scene = new THREE.Scene()
/**
* Base
*/
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 })
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
/**
* Sizes
*/
const sizes = {
width: 800,
height: 600
}
/**
* Camera
*/
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height)
camera.position.z = 3
scene.add(camera)
/**
* Renderer
*/
const renderer = new THREE.WebGLRenderer({
canvas: canvas
})
renderer.setSize(sizes.width, sizes.height)
/**
* Animate
*/
gsap.to(mesh.position, { duration: 1, delay: 1, x: 2 })
const tick = () =>
{
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
tick()