Di three.js, tiap benda yang tampil adalah Mesh = geometry (bentuknya: titik dan segitiga) + material (permukaannya: warna, tekstur, kilap). Bayangin membuat mug di bengkel keramik: cetakan tanah liatnya adalah geometry, glasir dan sablonnya adalah material. Dan seperti foto produk, glasir terbaik pun terlihat hitam di ruangan gelap: material yang realistis butuh cahaya.
Di lesson ini kita membuat produk baru Raka, Mug Sablon Kopi (Rp 65.000), dalam 3D: badan silinder, gagang setengah donat, dan sablon Kopi dari lesson Canvas API Dasar sebagai tekstur.
🧪 Tebakan cepat: mugnya sudah tampil dengan
MeshBasicMaterial. Raka menggantinya denganMeshStandardMaterialsupaya terlihat lebih 3D, tapi belum menambah lampu apa pun. Mugnya jadi seperti apa? Tebak dulu, jawabannya ada di bagian "Cahaya".
Geometry: bentuk mug
Setup scene, kamera dan renderer sama dengan lesson Three.js Setup; bedanya kamera diangkat sedikit lalu diarahkan ke tengah dengan camera.lookAt(0, 0, 0), supaya permukaan kopinya kelihatan.
// Badan: CylinderGeometry(radiusAtas, radiusBawah, tinggi, segmenKeliling, segmenTinggi, terbuka, thetaStart)
const badan = new THREE.CylinderGeometry(0.8, 0.8, 1.6, 48, 1, false, Math.PI);
// Gagang: TorusGeometry(radius, tebalPipa, segmenPipa, segmenLingkar, busur)
const gagangGeo = new THREE.TorusGeometry(0.42, 0.1, 16, 32, Math.PI);
- Segmen adalah jumlah potongan untuk meniru lengkungan. Silinder 48 segmen punya 6 × 48 + 4 = 292 titik dan 4 × 48 = 192 segitiga; 8 segmen cuma 52 titik dan 32 segitiga, dan mugnya jadi segi delapan. Makin banyak segmen makin halus, tapi makin banyak kerja vertex shader.
- Silinder punya tiga bagian (group): sisi (0), tutup atas (1), tutup bawah (2). Masing-masing bisa diberi material sendiri lewat array.
- Tekstur dililitkan mulai dari
thetaStart. Bawaannya 0, artinya tepi kiri gambar ada di depan dan bagian tengahnya di belakang mug.thetaStart = Math.PImemutar awalnya ke belakang, jadi tengah gambar menghadap kamera. arc = Math.PImembuat torus cuma setengah lingkaran, sama denganarc(200, 160, 24, -Math.PI / 2, Math.PI / 2)gagang di sablon. Torus berdiri di bidang XY dengan busur di atas, jadirotation.z = -Math.PI / 2memutarnya ke kanan.- Bentuk siap pakai lainnya:
BoxGeometry(lebar, tinggi, tebal)seperti kotak kemasan di lesson sebelumnya, danSphereGeometry(radius, segmenLebar, segmenTinggi)untuk bola, misalnyaSphereGeometry(1, 32, 32).
Material: Basic atau Standard
const mug = new THREE.Mesh(badan, [
new THREE.MeshStandardMaterial({ map: tekstur, roughness: 0.5 }), // 0: sisi, bersablon
new THREE.MeshStandardMaterial({ color: '#c47a3d', roughness: 0.3 }), // 1: atas, kopi
new THREE.MeshStandardMaterial({ color: '#1c1917', roughness: 0.5 }), // 2: dasar
]);
const gagang = new THREE.Mesh(gagangGeo, new THREE.MeshStandardMaterial({ color: '#1c1917', roughness: 0.5 }));
gagang.rotation.z = -Math.PI / 2;
gagang.position.x = 0.8; // menempel di sisi kanan badan
mug.add(gagang); // ikut berputar bersama mug
scene.add(mug);
MeshBasicMaterial |
MeshStandardMaterial |
|
|---|---|---|
| Menghitung cahaya | gak, warnanya tampil apa adanya | ya, PBR (physically based rendering) |
| Tanpa lampu | tetap berwarna | hitam |
| Parameter khas | color, map, wireframe |
color, map, roughness (0 licin, 1 kasar), metalness (0 bukan logam, 1 logam) |
| Cocok untuk | wireframe, debug, UI, gaya flat | produk yang harus terlihat nyata |
Bola merah MeshBasicMaterial({ color: 0xff0000 }) tampil merah rata dari sisi mana pun, ada lampu atau gak. Basic lebih ringan untuk GPU karena gak menghitung cahaya sama sekali.
Cahaya
scene.add(new THREE.AmbientLight('#ffffff', 0.8)); // dari segala arah, rata
const lampu = new THREE.DirectionalLight('#ffffff', 2.5); // sejajar, seperti matahari
lampu.position.set(3, 4, 5); // datang dari kanan atas depan menuju (0, 0, 0)
scene.add(lampu);
MeshStandardMaterial tanpa satu lampu pun gak memantulkan apa-apa: mugnya jadi siluet hitam pekat di atas latar krem. Itu jawaban tebakannya. Gak ada error; latar tetap tampil karena scene.background bukan benda yang disinari.
Sejak r155 intensitas cahaya di three.js dihitung secara fisik. DirectionalLight intensitas 1 yang menyinari tegak lurus cuma memberi sekitar sepertiga terang warna aslinya (1/π, dihitung di ruang warna linear), jadi tutorial lama yang ditulis sebelum r155 terlihat gelap. Panduan migrasinya: kalikan intensitas ambient dan directional lama dengan π (1 jadi sekitar 3). Di sini 2,5 dan 0,8.
Tekstur dari canvas
Sablon Kopi 300 × 300 kita taruh di canvas yang perbandingannya sama dengan keliling mug: keliling 2π × 0,8 = 5,03, tinggi 1,6, jadi lebarnya π kali tinggi: 942 × 300.
const kanvas = document.createElement('canvas');
kanvas.width = 942;
kanvas.height = 300;
const ctx = kanvas.getContext('2d');
ctx.fillStyle = '#1c1917';
ctx.fillRect(0, 0, 942, 300);
ctx.translate(321, 0); // sablon 300 × 300 di tengah: (942 - 300) / 2
gambarSablonKopi(ctx); // langkah-langkah dari lesson Canvas API Dasar
const tekstur = new THREE.CanvasTexture(kanvas);
tekstur.colorSpace = THREE.SRGBColorSpace;
CanvasTexturemengirim isi canvas ke GPU sebagai gambar. Kalau canvas-nya digambar ulang, settekstur.needsUpdate = true.- Canvas yang lebih sempit akan ditarik melebar mengelilingi mug, jadi cangkirnya gepeng atau melar.
Ruang warna
three.js menghitung cahaya di ruang warna linear, sedangkan hex seperti #c47a3d dan pixel canvas memakai sRGB. Warna hex di color dikonversi otomatis (ColorManagement aktif sejak r152). Tekstur gak bisa ditebak isinya, jadi kamu yang menandai: tekstur.colorSpace = THREE.SRGBColorSpace untuk tekstur warna (map). Tanpa itu, nilainya dianggap sudah linear dan jadi terlalu terang saat ditampilkan: dengan MeshBasicMaterial, hitam kaos #1c1917 keluar sebagai abu-abu #5d5855 dan sablonnya pudar. Tekstur data seperti normalMap atau roughnessMap tetap tanpa colorSpace. (Ini juga sebabnya segitiga three.js di lesson WebGL Konsep tengahnya sedikit lebih terang: campurannya dihitung di linear.)
dispose(): membersihkan memori GPU
Geometry, material dan tekstur yang sudah dikirim ke GPU gak dibersihkan garbage collector selama renderer masih menyimpannya. Saat mengganti geometry:
function gantiSegmen(n) {
mug.geometry.dispose(); // buang yang lama dari memori GPU
mug.geometry = new THREE.CylinderGeometry(0.8, 0.8, 1.6, n, 1, false, Math.PI);
renderer.render(scene, camera);
}
console.log(renderer.info.memory.geometries); // 2: badan dan gagang
Tanpa dispose(), renderer.info.memory.geometries naik dari 2 jadi 4 setelah dua kali ganti segmen, dan terus naik. Material dan tekstur punya dispose() juga.
⚠️ Jebakan yang sering ketemu
MeshStandardMaterialtanpa lampu: siluet hitam, tanpa error. TambahkanAmbientLightdanDirectionalLight, atau pakaiMeshBasicMaterial.- Lupa
SRGBColorSpacedi tekstur warna: sablon pucat, hitam jadi abu-abu. - Intensitas dari tutorial lama: sebelum r155 intensitas 1 sudah terang; sekarang perlu sekitar 3.
- Sablon di belakang mug atau melar: atur
thetaStart(atau putar mesh-nya) dan samakan perbandingan canvas dengan keliling : tinggi. - Ganti geometry tanpa
dispose(): memori GPU terus bertambah.
TL;DR: Mesh = geometry + material, lalu scene.add. CylinderGeometry(rAtas, rBawah, tinggi, segmen, segmenTinggi, terbuka, thetaStart): segmen menentukan kehalusan (48 segmen = 192 segitiga, 8 = segi delapan), group 0 sisi, 1 atas, 2 bawah; TorusGeometry(radius, pipa, segPipa, segLingkar, arc), Math.PI untuk setengah lingkaran; BoxGeometry(l, t, d). MeshBasicMaterial gak menghitung cahaya (debug, flat, UI); MeshStandardMaterial (roughness, metalness) butuh cahaya, tanpa lampu jadi hitam. AmbientLight rata, DirectionalLight sejajar dari position ke (0, 0, 0); intensitas fisik sejak r155 (lama × π). Sablon canvas jadi CanvasTexture dengan colorSpace = THREE.SRGBColorSpace. Ganti geometry atau material? dispose() yang lama, cek renderer.info.memory.