Di 10 Materials > Nambah Cahaya kita udah sempat nyalain dua lampu asal-asalan biar objeknya kelihatan. Sekarang kita bedah semuanya satu-satu.
Nambahin cahaya itu segampang nambahin mesh: bikin instance dari class yang sesuai, terus masukin ke scene. Kita udah kenalan sama AmbientLight dan PointLight sekilas, sekarang kita bahas semua jenisnya secara lengkap.
Setup
Scene-nya udah disiapin di starter: ada bola, kubus, donat, dan satu bidang datar sebagai lantai.
Karena kita mau main cahaya, materialnya wajib yang bereaksi sama cahaya. Bisa aja pakai MeshLambertMaterial, MeshPhongMaterial, atau MeshToonMaterial, tapi kita pakai MeshStandardMaterial karena paling realistis. roughness-nya juga dikecilin ke 0.4 biar pantulan cahayanya kelihatan jelas.
❌ percuma, material ini cuek total sama cahaya
const material = new THREE.MeshBasicMaterial()✅ benar, material ini bereaksi sama cahaya
const material = new THREE.MeshStandardMaterial()
material.roughness = 0.4Kalau starter-nya udah jalan, hapus dulu AmbientLight dan PointLight-nya biar kita mulai dari nol. Hasilnya layar hitam total tanpa apa-apa. Tenang, itu bukan error, itu emang gelap gulita karena gak ada cahaya sama sekali.
AmbientLight
AmbientLight itu cahaya yang nyinarin semua geometry di scene dari segala arah sekaligus. Parameter pertama warnanya, parameter kedua kekuatannya:
// Ambient light
const ambientLight = new THREE.AmbientLight(0xffffff, 1)
scene.add(ambientLight)Sama kaya material, propertinya juga bisa di-set setelah instance-nya jadi:
// Ambient light
const ambientLight = new THREE.AmbientLight()
ambientLight.color = new THREE.Color(0xffffff)
ambientLight.intensity = 1
scene.add(ambientLight)Hasilnya sama persis. Tapi perhatiin, color itu wajib dibungkus new THREE.Color(...), aturannya sama kaya di 10 Materials > 2. Color:
❌ salah, color gak nerima angka mentah kalau di-set langsung
ambientLight.color = 0xffffff✅ benar
ambientLight.color = new THREE.Color(0xffffff)Dan sama kaya material juga, propertinya bisa dimasukin ke Debug UI biar gampang dicoba-coba:
gui.add(ambientLight, 'intensity').min(0).max(3).step(0.001)Seterusnya kita gak nambahin tweak lagi ya biar catatannya gak penuh, tapi silakan tambahin sendiri kalau mau enak nyoba-nyobanya.
Nah ini yang penting dipahami: kalau di scene kamu cuma ada AmbientLight doang, hasilnya sama persis kaya pakai MeshBasicMaterial, karena semua sisi objek disinari sama rata. Gak ada gelap-terang, gak ada dimensi.
Terus buat apa dong? Gini logikanya: di dunia nyata, sisi objek yang membelakangi cahaya itu gak pernah hitam pekat, karena cahaya mantul-mantul dulu ke tembok dan benda lain sebelum nyampe ke situ. Pantulan cahaya kaya gitu gak didukung ThreeJS karena berat banget buat performa. Jadi AmbientLight yang redup itu dipakai buat mura-mura jadi pantulan cahaya tadi, biar bagian gelapnya gak hitam mati.
DirectionalLight
DirectionalLight itu cahaya kaya matahari, sinarnya datang sejajar semua. Parameter pertama warna, kedua kekuatannya:
// Directional light
const directionalLight = new THREE.DirectionalLight(0x00fffc, 0.9)
scene.add(directionalLight)Bawaannya cahayanya datang dari atas. Buat ngubahnya, kita geser lampunya pakai position kaya objek ThreeJS biasa yang kita pelajari di 03 Transform Object > `position` Memindahkan Object:
directionalLight.position.set(1, 0.25, 0)Yang unik: jaraknya gak ngaruh sama sekali di sini. Yang ngaruh cuma arahnya. Sinarnya dianggap datang dari ruang tak terhingga dan menuju ke arah tak terhingga sebaliknya, sejajar semua. Persis kaya matahari yang mau kamu deketin sedekat apapun ya sinarnya tetap segitu arahnya.
HemisphereLight
HemisphereLight itu mirip AmbientLight, bedanya warna dari langit dan warna dari tanah itu beda. Sisi objek yang menghadap ke atas disinari satu warna, sisi yang menghadap ke bawah disinari warna lain.
Bayangin lapangan rumput di sore hari: bagian atas objek kena warna oranye langit, bagian bawahnya kena pantulan hijau dari rumput.
Parameter pertama warna langit, kedua warna tanah (groundColor), ketiga kekuatannya:
// Hemisphere light
const hemisphereLight = new THREE.HemisphereLight(0xff0000, 0x0000ff, 0.9)
scene.add(hemisphereLight)PointLight
PointLight itu kaya nyala korek api. Sumber cahayanya satu titik yang kecilnya tak terhingga, dan sinarnya nyebar rata ke segala arah. Parameter pertama warna, kedua kekuatannya:
// Point light
const pointLight = new THREE.PointLight(0xff9000, 1.5)
scene.add(pointLight)Bisa digeser kaya objek biasa:
pointLight.position.set(1, - 0.5, 1)Buat hasil yang realistis, biasanya kita cukup main di intensity aja. Tapi sebenarnya ada dua parameter lagi yang bisa kita atur.
1. Distance
Parameter ketiga namanya distance, yaitu jarak di mana kekuatan cahayanya jatuh sampai nol. Bawaannya 0, dan 0 di sini artinya tak terhingga, bukan artinya mati.
Coba isi 0.5:
const pointLight = new THREE.PointLight(0xff9000, 1.5, 0.5)Kelihatannya mirip, tapi cahayanya anjlok drastis begitu mendekati batas 0.5 itu. Kita balikin dulu ke tak terhingga:
const pointLight = new THREE.PointLight(0xff9000, 1.5, 0)2. Decay
Parameter keempat namanya decay, yaitu seberapa cepat cahayanya meredup. Makin kecil nilainya, makin cepat redupnya. Bawaannya 2.
Coba isi 0.5:
const pointLight = new THREE.PointLight(0xff9000, 1.5, 0, 0.5)Cahayanya jadi meredup lebih cepat. Tapi buat hasil yang realistis dan sesuai fisika, mendingan biarin di nilai bawaannya:
const pointLight = new THREE.PointLight(0xff9000, 1.5, 0, 2)RectAreaLight
RectAreaLight itu kaya lampu kotak gede yang dipakai di studio foto. Sifatnya campuran antara cahaya berarah dan cahaya menyebar.
Parameternya: warna, kekuatan, lebar kotaknya, dan tinggi kotaknya:
// Rect area light
const rectAreaLight = new THREE.RectAreaLight(0x4e00ff, 6, 1, 1)
scene.add(rectAreaLight)Peringatan penting: RectAreaLight cuma jalan di MeshStandardMaterial dan MeshPhysicalMaterial. Kalau kamu pasang di material lain, dia bakal diam aja tanpa error dan kamu bakal bingung setengah mati nyari salahnya di mana.
Lampu ini bisa digeser dan diputar. Biar gampang muternya, kita pakai method lookAt(...) yang udah kita kenal di 03 Transform Object > Look at this!:
rectAreaLight.position.set(- 1.5, 0, 1.5)
rectAreaLight.lookAt(new THREE.Vector3())Vector3 yang dibikin tanpa parameter apapun itu isinya 0, 0, 0, alias tepat di tengah scene. Jadi baris itu artinya “hadap ke tengah scene”.
SpotLight
SpotLight itu kaya senter. Bentuknya kerucut cahaya yang berangkat dari satu titik ke satu arah.
Parameternya paling banyak di antara semua:
color, warnanya.intensity, kekuatannya.distance, jarak di mana kekuatannya jatuh ke nol.angle, seberapa lebar kerucut cahayanya.penumbra, seberapa kabur pinggiran kerucutnya.decay, seberapa cepat cahayanya meredup.
// Spot light
const spotLight = new THREE.SpotLight(0x78ff00, 4.5, 10, Math.PI * 0.1, 0.25, 1)
spotLight.position.set(0, 2, 3)
scene.add(spotLight)Math.PI * 0.1 itu satuan radian lagi, sama kaya waktu kita muter objek.
Muter SpotLight Itu Aneh
Nah, muter SpotLight itu agak ribet dan ada jebakannya.
Instance-nya punya properti target yang isinya sebuah Object3D, dan SpotLight itu selalu ngeliat ke objek target itu. Tapi kalau kamu coba geser posisinya, lampunya gak bergeming sama sekali:
spotLight.target.position.x = - 0.75Kok bisa? Ini perilaku yang aneh tapi ada alasannya.
Posisi, rotasi, dan skala sebuah objek itu dirangkum jadi satu hal yang namanya transform matrix, dan matrix itulah yang sebenarnya dipakai buat ngerender. Kedengeran rumit? Untungnya kita gak perlu ngurusin itu, ThreeJS yang ngurusin.
Tapi ThreeJS cuma ngitungin transform matrix buat objek yang ada di dalam scene. Ngapain juga repot-repot ngitungin objek yang gak ada di sana?
Nah, spotLight.target itu objek kosong yang belum pernah kita masukin ke scene. Jadi solusinya ya masukin aja:
❌ salah, target-nya gak pernah dihitung karena gak ada di scene
spotLight.target.position.x = - 0.75✅ benar, target-nya dimasukin ke scene walaupun dia objek kosong
scene.add(spotLight.target)
spotLight.target.position.x = - 0.75Sekarang lampunya beneran nengok ke kiri.
Performa
Cahaya itu bagus dan bisa bikin hasil realistis kalau dipakai dengan benar. Masalahnya, cahaya itu mahal banget buat performa. GPU harus ngitung banyak hal: jarak tiap face ke lampu, seberapa miring face itu terhadap lampu, apakah face-nya masuk ke dalam kerucut spotlight, dan seterusnya.
Pakai lampu sesedikit mungkin, dan pilih yang paling murah kalau bisa.
Biaya murah:
- AmbientLight
- HemisphereLight
Biaya sedang:
- DirectionalLight
- PointLight
Biaya mahal:
- SpotLight
- RectAreaLight
Baking
Ada satu teknik pencahayaan yang jempolan namanya baking. Idenya: cahayanya dipanggang langsung ke dalam texture. Jadi bayangan dan sorotan cahayanya udah jadi bagian dari gambar, bukan diitung ulang tiap frame.
Ini dikerjain di software 3D, dan hasilnya bisa luar biasa bagus dengan performa yang enteng banget, karena scene-nya sebenarnya gak punya lampu satupun.
Kelemahannya jelas: lampunya gak bisa digerakin lagi, wong emang gak ada lampunya. Dan kamu bakal butuh banyak texture.
Contoh bagusnya ada di halaman depan Three.js Journey itu sendiri. Cara bikinnya ada di bab Portal nanti.
Helpers
Naruh dan ngarahin lampu itu susah, soalnya lampunya sendiri gak kelihatan, yang kelihatan cuma efeknya. Buat bantuin kita, ada yang namanya helper, konsepnya sama kaya Axes Helper yang dulu kita pakai buat lihat arah sumbu.
Yang didukung cuma lima ini:
- HemisphereLightHelper
- DirectionalLightHelper
- PointLightHelper
- RectAreaLightHelper
- SpotLightHelper
Cara pakainya tinggal bikin instance-nya, kirim lampunya sebagai parameter pertama, terus masukin ke scene. Parameter kedua buat ngatur ukuran helper-nya:
const hemisphereLightHelper = new THREE.HemisphereLightHelper(hemisphereLight, 0.2)
scene.add(hemisphereLightHelper)
const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 0.2)
scene.add(directionalLightHelper)
const pointLightHelper = new THREE.PointLightHelper(pointLight, 0.2)
scene.add(pointLightHelper)
const spotLightHelper = new THREE.SpotLightHelper(spotLight)
scene.add(spotLightHelper)Khusus RectAreaLightHelper agak beda, karena class-nya belum masuk ke variabel THREE. Jadi harus di-import sendiri dari folder examples, persis kaya waktu kita ngambil OrbitControls dan FontLoader:
import { RectAreaLightHelper } from 'three/examples/jsm/helpers/RectAreaLightHelper.js'Baru setelah itu bisa dipakai:
const rectAreaLightHelper = new RectAreaLightHelper(rectAreaLight)
scene.add(rectAreaLightHelper)Code Lengkap
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { RectAreaLightHelper } from 'three/examples/jsm/helpers/RectAreaLightHelper.js'
import GUI from 'lil-gui'
/**
* Debug
*/
const gui = new GUI()
/**
* Base
*/
// Canvas
const canvas = document.querySelector('canvas.webgl')
// Scene
const scene = new THREE.Scene()
// Sizes
const sizes = {
width: window.innerWidth,
height: window.innerHeight
}
/**
* Lights
*/
// Ambient light
const ambientLight = new THREE.AmbientLight(0xffffff, 1)
scene.add(ambientLight)
gui.add(ambientLight, 'intensity').min(0).max(3).step(0.001)
// Directional light
const directionalLight = new THREE.DirectionalLight(0x00fffc, 0.9)
directionalLight.position.set(1, 0.25, 0)
scene.add(directionalLight)
// Hemisphere light
const hemisphereLight = new THREE.HemisphereLight(0xff0000, 0x0000ff, 0.9)
scene.add(hemisphereLight)
// Point light
const pointLight = new THREE.PointLight(0xff9000, 1.5, 0, 2)
pointLight.position.set(1, - 0.5, 1)
scene.add(pointLight)
// Rect area light
const rectAreaLight = new THREE.RectAreaLight(0x4e00ff, 6, 1, 1)
rectAreaLight.position.set(- 1.5, 0, 1.5)
rectAreaLight.lookAt(new THREE.Vector3())
scene.add(rectAreaLight)
// Spot light
const spotLight = new THREE.SpotLight(0x78ff00, 4.5, 10, Math.PI * 0.1, 0.25, 1)
spotLight.position.set(0, 2, 3)
scene.add(spotLight)
scene.add(spotLight.target)
spotLight.target.position.x = - 0.75
/**
* Helpers
*/
const hemisphereLightHelper = new THREE.HemisphereLightHelper(hemisphereLight, 0.2)
scene.add(hemisphereLightHelper)
const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 0.2)
scene.add(directionalLightHelper)
const pointLightHelper = new THREE.PointLightHelper(pointLight, 0.2)
scene.add(pointLightHelper)
const spotLightHelper = new THREE.SpotLightHelper(spotLight)
scene.add(spotLightHelper)
const rectAreaLightHelper = new RectAreaLightHelper(rectAreaLight)
scene.add(rectAreaLightHelper)
/**
* Objects
*/
const material = new THREE.MeshStandardMaterial()
material.roughness = 0.4
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 32, 32),
material
)
sphere.position.x = - 1.5
const cube = new THREE.Mesh(
new THREE.BoxGeometry(0.75, 0.75, 0.75),
material
)
const torus = new THREE.Mesh(
new THREE.TorusGeometry(0.3, 0.2, 32, 64),
material
)
torus.position.x = 1.5
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(5, 5),
material
)
plane.rotation.x = - Math.PI * 0.5
plane.position.y = - 0.65
scene.add(sphere, cube, torus, plane)
/**
* Camera
*/
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
camera.position.x = 1
camera.position.y = 1
camera.position.z = 2
scene.add(camera)
// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true
/**
* Renderer
*/
const renderer = new THREE.WebGLRenderer({
canvas: canvas
})
renderer.setSize(sizes.width, sizes.height)
/**
* Animate
*/
const clock = new THREE.Clock()
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
// Update objects
sphere.rotation.y = 0.1 * elapsedTime
cube.rotation.y = 0.1 * elapsedTime
torus.rotation.y = 0.1 * elapsedTime
sphere.rotation.x = 0.15 * elapsedTime
cube.rotation.x = 0.15 * elapsedTime
torus.rotation.x = 0.15 * elapsedTime
// Update controls
controls.update()
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
tick()Ringkasan
- Nambah cahaya itu sama kaya nambah mesh: bikin instance class-nya, terus
scene.add(...). - Materialnya wajib yang bereaksi sama cahaya.
MeshBasicMaterialdanMeshMatcapMaterialcuek total, pakaiMeshStandardMaterialbiar realistis. AmbientLightnyinarin semua sisi objek sama rata, jadi kalau cuma dia doang hasilnya sama kayaMeshBasicMaterial, gak ada dimensi.- Pantulan cahaya gak didukung ThreeJS karena berat.
AmbientLightyang redup dipakai buat mura-mura jadi pantulan itu, biar sisi gelap objek gak hitam mati. coloryang di-set langsung wajib dibungkusnew THREE.Color(...), sama kaya di material.DirectionalLightitu cahaya matahari, sinarnya sejajar semua. Jaraknya gak ngaruh, cuma arahnya yang ngaruh.HemisphereLightituAmbientLightdua warna: satu dari langit, satu dari tanah.PointLightitu kaya korek api, nyebar rata ke segala arah dari satu titik.distance0artinya tak terhingga, dandecaymakin kecil makin cepat redup (bawaannya2, biarin aja).RectAreaLightitu lampu studio foto, dan cuma jalan diMeshStandardMaterialdanMeshPhysicalMaterial. Kalau dipasang di material lain, dia diam aja tanpa error.new THREE.Vector3()tanpa parameter itu isinya0, 0, 0, alias titik tengah scene.SpotLightitu kaya senter dengan 6 parameter, dan arahnya diatur lewat propertitarget.spotLight.targetwajib dimasukin ke scene, karena ThreeJS cuma ngitung transform matrix buat objek yang ada di dalam scene. Kalau nggak, digeser kemanapun lampunya gak bergeming.- Cahaya itu mahal buat performa. Urutan dari murah ke mahal:
AmbientLightdanHemisphereLight, terusDirectionalLightdanPointLight, terusSpotLightdanRectAreaLight. - Baking itu manggang cahaya langsung ke dalam texture lewat software 3D. Hasilnya bagus dan enteng banget, tapi lampunya jadi gak bisa digerakin karena emang gak ada.
- Ada 5 helper buat bantuin naruh lampu, dan
RectAreaLightHelperharus di-import sendiri karena belum masuk ke variabelTHREE.
Bab selanjutnya kita akan belajar bikin bayangan, karena sekarang objek kita udah kena cahaya tapi belum ada satupun yang punya bayangan, jadi masih kerasa melayang gak natural.