Selama ini kita cuma pakai BoxGeometry buat bikin kubus. Sekarang kita kenalan sama geometry-geometry lainnya, tapi sebelum itu kita harus paham dulu geometry itu sebenarnya apa.

Apa itu Geometry?

Di ThreeJS, geometry itu tersusun dari dua hal:

  1. Vertices, adalah titik-titik koordinat di ruang 3D. Bayangin kaya paku-paku yang kita tancepin di udara, satu paku itu namanya vertex (tunggal), banyaknya namanya vertices (jamak).
  2. Faces, adalah segitiga-segitiga yang menghubungkan titik-titik tadi sampai jadi permukaan. Jadi paku-paku tadi kita hubungkan pakai kain segitiga, nah kain itulah yang kelihatan sebagai bentuk objek kita.

Kenapa segitiga? Karena tiga titik itu pasti membentuk satu bidang datar. Kalau empat titik, bisa aja salah satunya kepeleset ke atas dikit dan bidangnya jadi bengkok, GPU bakal bingung. Makanya semua bentuk di 3D, sebulat apapun, ujung-ujungnya tetap kumpulan segitiga.

Geometry biasanya kita pakai untuk bikin mesh, tapi nanti geometry juga bisa dipakai untuk bikin particles, di mana satu vertex jadi satu partikel. Itu urusan bab lain.

Dan yang disimpan di dalam vertex bukan cuma posisi. Nanti kita juga bakal simpan UV coordinates (buat naruh tekstur) dan normals (buat ngitung arah cahaya), tapi itu juga bahasan bab-bab berikutnya.

Geometry Bawaan ThreeJS

ThreeJS punya banyak banget geometry bawaan. Kamu gak perlu hafal cara bikin satu-satu, cukup tau aja mereka ada, jadi pas butuh tinggal buka dokumentasinya. Kebanyakan halaman dokumentasinya juga sudah ada contoh yang bisa diutak-atik.

Satu hal penting: semua geometry bawaan ini turunan dari BufferGeometry. Class induk ini punya banyak method bawaan kaya translate(...), rotateX(...), normalize(), dan lain-lain, cuma di catatan ini gak kita pakai.

  1. BoxGeometry, untuk bikin kotak/kubus.
  2. PlaneGeometry, untuk bikin bidang datar persegi panjang. Ini yang biasanya dipakai jadi lantai.
  3. CircleGeometry, untuk bikin lingkaran penuh atau sepotong lingkaran, kaya potongan pie chart.
  4. ConeGeometry, untuk bikin kerucut atau sepotong kerucut. Alasnya bisa kita tutup atau kita biarin bolong.
  5. CylinderGeometry, untuk bikin tabung. Tutup atas-bawahnya bisa dibuka atau ditutup, dan radius tiap ujungnya bisa beda, jadi bisa dipakai bikin gelas juga.
  6. RingGeometry, untuk bikin cincin datar atau sepotong cincin datar. Kaya donat yang kegencet, gak punya ketebalan.
  7. TorusGeometry, untuk bikin cincin yang punya ketebalan, ini donat beneran, atau sepotong donat.
  8. TorusKnotGeometry, untuk bikin bentuk simpul yang ruwet itu. Sering dipakai orang buat pamer material karena permukaannya melengkung ke mana-mana.
  9. DodecahedronGeometry, untuk bikin bola dengan 12 sisi. Bisa ditambah details biar makin bulat.
  10. OctahedronGeometry, untuk bikin bola dengan 8 sisi. Bisa ditambah details juga.
  11. TetrahedronGeometry, untuk bikin bola dengan 4 sisi. Kalau details-nya gak dinaikin ya jangan harap bulat, bentuknya masih limas segitiga.
  12. IcosahedronGeometry, untuk bikin bola yang segitiga-segitiganya ukurannya kurang lebih sama rata semua.
  13. SphereGeometry, ini bola yang paling sering dipakai orang, permukaannya kelihatan kaya kotak-kotak (quads). Quad itu cuma nama lain dari dua segitiga yang digabung.
  14. ShapeGeometry, untuk bikin bentuk datar mengikuti sebuah garis/path.
  15. TubeGeometry, untuk bikin pipa yang jalannya ngikutin sebuah path.
  16. ExtrudeGeometry, untuk “menebalkan” sebuah path jadi 3D. Kaya adonan kue yang kita cetak terus kita tarik ke atas. Ketebalan dan lengkungan pinggirnya (bevel) bisa kita atur.
  17. LatheGeometry, untuk bikin vas bunga atau sepotong vas. Prinsipnya kaya gerabah yang diputar di atas roda pembentuk, satu garis diputar 360 derajat jadi bentuk 3D.
  18. TextGeometry, untuk bikin teks 3D. Syaratnya kita harus nyediain font-nya dalam format typeface json, bukan .ttf biasa.

Kalau bentuk yang kamu mau gak ada di daftar itu, ada dua jalan: bikin sendiri pakai JavaScript (nanti kita coba di bagian bawah catatan ini), atau bikin di software 3D kaya Blender terus di-export dan di-import ke project kita. Yang kedua itu bahasan bab lain.

Contoh: BoxGeometry

Kita udah pernah bikin kubus, tapi kita belum ngobrolin parameternya. Hampir semua geometry punya parameter, dan selalu cek dokumentasinya dulu sebelum dipakai, karena urutan parameternya beda-beda tiap geometry.

BoxGeometry punya 6 parameter:

  1. width, ukuran di sumbu x.
  2. height, ukuran di sumbu y.
  3. depth, ukuran di sumbu z.
  4. widthSegments, jumlah pembagian di sumbu x.
  5. heightSegments, jumlah pembagian di sumbu y.
  6. depthSegments, jumlah pembagian di sumbu z.

Tiga yang pertama gampang, itu ukuran. Tiga yang terakhir ini yang sering bikin bingung.

1. Segments alias Subdivisions

Subdivisions itu seberapa banyak segitiga yang menyusun satu sisi. Bawaannya 1, artinya satu sisi cuma dibentuk dari 2 segitiga (dua segitiga digabung jadi satu kotak, ingat quad tadi).

Kalau kita naikin jadi 2, satu sisi jadi disusun 8 segitiga:

const geometry = new THREE.BoxGeometry(1, 1, 1, 2, 2, 2)

Masalahnya, kalau dijalanin sekarang kita gak bisa lihat bedanya sama sekali. Kubusnya ya kubus aja, permukaannya rata merah semua.

2. Wireframe

Biar segitiganya kelihatan, kita kasih wireframe: true di material-nya. Wireframe bakal nampilin garis-garis pembatas tiap segitiga, jadi objeknya kelihatan kaya kerangka kawat:

const material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true })

Sekarang baru kelihatan, memang benar ada 8 segitiga di tiap sisi.

Perlu dicatat, wireframe itu properti material, bukan geometry. Salah taruh bakal diem aja gak error:

❌ salah, BoxGeometry gak kenal wireframe, jadi gak ada efeknya

const geometry = new THREE.BoxGeometry(1, 1, 1, 2, 2, 2, { wireframe: true })
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 })

✅ benar, wireframe masuk ke material

const geometry = new THREE.BoxGeometry(1, 1, 1, 2, 2, 2)
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true })

3. Kenapa Segments Itu Penting

Di kubus, nambah segments itu keliatannya sia-sia, wong sisinya datar, mau dibelah jadi 2 atau 200 segitiga hasilnya tetap kotak datar.

Bedanya baru kerasa di bentuk melengkung kaya bola:

const geometry = new THREE.SphereGeometry(1, 32, 32)

Makin banyak subdivisions, makin gak kelihatan patahan antar sisinya, bolanya makin mulus. Coba turunin ke new THREE.SphereGeometry(1, 4, 4), nanti bentuknya lebih mirip bola sepak jadul yang kotak-kotak daripada bola beneran.

Tapi jangan langsung napsu ngasih angka gede. Makin banyak vertices dan faces, makin berat kerja GPU. Logikanya sama kaya kita naruh gambar 8K buat foto profil yang tampilnya cuma sebesar jempol, hasilnya sama aja tapi laptopnya ngos-ngosan. Cari angka paling kecil yang hasilnya udah kelihatan mulus.

Bikin Geometry Sendiri (BufferGeometry)

Kadang bentuk yang kita mau gak ada di daftar bawaan. Kalau bentuknya rumit banget dan detail, mending bikin di software 3D (bahasan bab lain), tapi kalau bentuknya masih sederhana, kita bisa rakit sendiri pakai BufferGeometry.

Kita mulai dari yang paling sederhana: satu segitiga.

1. Bikin BufferGeometry Kosong

// Bikin BufferGeometry kosong
const geometry = new THREE.BufferGeometry()

Sekarang dia bener-bener kosong, gak ada vertex sama sekali, jadi belum ada yang bisa dilihat.

2. Siapkan Float32Array

Untuk masukin vertices ke BufferGeometry, kita harus mulai dari Float32Array.

Float32Array itu typed array bawaan JavaScript, bukan punya ThreeJS. Bedanya sama array biasa ada dua:

  1. Isinya cuma boleh angka desimal (float), gak bisa dicampur string atau objek.
  2. Panjangnya sudah dipatok dari awal, gak bisa push seenaknya.

Kenapa harus ribet gini? Karena array model begini bentuknya di memori itu rapat dan seragam, jadi bisa dikirim ke GPU apa adanya tanpa diterjemahin dulu. Array biasa mah isinya bisa apa aja, GPU-nya gak ngerti.

Cara pertama, tentukan dulu panjangnya, isi belakangan:

const positionsArray = new Float32Array(9)
 
// Vertex pertama
positionsArray[0] = 0
positionsArray[1] = 0
positionsArray[2] = 0
 
// Vertex kedua
positionsArray[3] = 0
positionsArray[4] = 1
positionsArray[5] = 0
 
// Vertex ketiga
positionsArray[6] = 1
positionsArray[7] = 0
positionsArray[8] = 0

Cara kedua, langsung kasih array-nya:

const positionsArray = new Float32Array([
    0, 0, 0, // Vertex pertama
    0, 1, 0, // Vertex kedua
    1, 0, 0  // Vertex ketiga
])

Kenapa 9 angka buat 1 segitiga? Karena 1 segitiga = 3 vertex, dan 1 vertex = 3 angka (x, y, z). Jadi 3 × 3 = 9.

Yang penting dipahami: array ini rata, satu dimensi. Gak ada kurung-kurung per vertex, gak ada penanda apapun. Kita nulisnya berurutan: x-y-z vertex pertama, terus x-y-z vertex kedua, terus x-y-z vertex ketiga, begitu seterusnya.

❌ salah, Float32Array gak nerima array bersarang kaya gini

const positionsArray = new Float32Array([
    [0, 0, 0],
    [0, 1, 0],
    [1, 0, 0]
])

✅ benar, semua angka ditulis berderet dalam satu array datar

const positionsArray = new Float32Array([
    0, 0, 0,
    0, 1, 0,
    1, 0, 0
])

3. Ubah Jadi BufferAttribute

Array tadi belum bisa langsung dikasih ke BufferGeometry, harus dibungkus dulu jadi BufferAttribute.

Parameter pertama adalah typed array-nya, parameter kedua adalah berapa angka yang dihitung jadi satu vertex. Karena tadi kita bilang satu posisi vertex itu terdiri dari 3 angka (x, y, dan z), maka isinya 3:

const positionsAttribute = new THREE.BufferAttribute(positionsArray, 3)

Angka 3 inilah yang jadi aturan bacanya. Dia yang ngasih tau ThreeJS supaya baca array datar tadi tiga-tiga. Kalau kita isi 2, ThreeJS bakal baca dua-dua dan bentuknya jadi ngawur total, dan gak bakal ada pesan error, cuma hasilnya aneh. Ini salah satu bug yang paling bikin pusing karena keliatannya kodenya bener semua.

4. Pasang ke Geometry Pakai setAttribute

Terakhir, kita tempelin attribute tadi ke geometry pakai method setAttribute(...). Parameter pertama itu nama attribute-nya, parameter kedua nilainya:

geometry.setAttribute('position', positionsAttribute)

Kenapa namanya harus 'position' persis? Karena shader internal ThreeJS nyari nama itu buat nentuin letak vertices-nya. Ini bukan nama bebas yang bisa kita karang. Bahasan shader ada di bab jauh nanti, tapi untuk sekarang cukup tau kalau nama ini harga mati.

❌ salah, ThreeJS gak nemu 'position', objeknya gak bakal muncul

geometry.setAttribute('posisi', positionsAttribute)

✅ benar

geometry.setAttribute('position', positionsAttribute)

Dan faces-nya? Gak perlu kita bikin. Segitiganya otomatis dibentuk mengikuti urutan vertices tadi, tiap tiga vertex jadi satu segitiga.

5. Semuanya Digabung

// Bikin BufferGeometry kosong
const geometry = new THREE.BufferGeometry()
 
// Bikin Float32Array berisi posisi vertices (tiga-tiga)
const positionsArray = new Float32Array([
    0, 0, 0, // Vertex pertama
    0, 1, 0, // Vertex kedua
    1, 0, 0  // Vertex ketiga
])
 
// Bikin attribute-nya dan kasih nama 'position'
const positionsAttribute = new THREE.BufferAttribute(positionsArray, 3)
geometry.setAttribute('position', positionsAttribute)

Jalanin, dan muncullah satu segitiga hasil karya kita sendiri.

Bikin Segitiga Acak Sebanyak-banyaknya

Kalau satu segitiga bisa, banyak segitiga juga bisa. Kita bikin 50 segitiga dengan posisi acak:

// Bikin BufferGeometry kosong
const geometry = new THREE.BufferGeometry()
 
// Bikin 50 segitiga (450 angka)
const count = 50
const positionsArray = new Float32Array(count * 3 * 3)
for(let i = 0; i < count * 3 * 3; i++)
{
    positionsArray[i] = (Math.random() - 0.5) * 4
}
 
// Bikin attribute-nya dan kasih nama 'position'
const positionsAttribute = new THREE.BufferAttribute(positionsArray, 3)
geometry.setAttribute('position', positionsAttribute)

Yang bikin dahi berkerut paling cuma count * 3 * 3, padahal gampang banget:

  • count = 50 segitiga yang kita mau,
  • × 3 karena satu segitiga punya 3 vertex,
  • × 3 lagi karena satu vertex punya 3 angka (x, y, z).

Jadi totalnya 50 × 3 × 3 = 450 angka. Bukan 50, bukan 150, tapi 450.

Bagian (Math.random() - 0.5) * 4 juga sama akal-akalannya kaya waktu kita bikin kursor di bab kamera kemarin. Math.random() cuma ngasih angka 0 sampai 1, semua positif, jadi semua segitiganya bakal numpuk di satu pojok. Dikurangi 0.5 biar nilainya jadi -0.5 sampai 0.5, artinya titik nolnya pindah ke tengah. Dikali 4 biar sebarannya lebih lebar, dari -2 sampai 2.

Kalau mau lebih rapat, kecilin pengalinya. Kalau mau lebih berantakan ke mana-mana, gedein.

Index

Ada satu hal menarik lagi dari BufferGeometry: kita bisa pakai ulang vertex yang sama lewat properti index.

Coba bayangin kubus. Vertex di pojok-pojoknya itu dipakai bareng oleh beberapa sisi sekaligus. Kalau diperhatiin lebih teliti lagi, hampir tiap vertex dipakai bareng sama segitiga-segitiga tetangganya.

Kalau gak pakai index, tiap segitiga nulis ulang koordinat vertex-nya sendiri-sendiri, padahal titiknya persis sama. Boros. Dengan index, koordinatnya cukup ditulis sekali, terus segitiganya tinggal nunjuk “aku pakai titik nomor 0, 3, dan 5”. Hasilnya array kita jadi lebih kecil dan performanya lebih enteng.

Kita gak bahas cara pakainya di catatan ini, cukup tau dulu kalau fasilitas ini ada.

Ringkasan

  1. Geometry itu kumpulan vertices (titik koordinat 3D) dan faces (segitiga yang menghubungkan titik-titik itu jadi permukaan). Semuanya segitiga, karena tiga titik pasti membentuk bidang datar.
  2. Vertex bisa nyimpen lebih dari sekadar posisi, nanti ada UV coordinates buat tekstur dan normals buat cahaya.
  3. ThreeJS punya belasan geometry bawaan dari BoxGeometry sampai TextGeometry, dan semuanya turunan BufferGeometry. Gak perlu dihafal, cukup tau ada, terus buka dokumentasinya pas butuh.
  4. Selalu cek dokumentasi buat urutan parameternya, karena tiap geometry beda-beda.
  5. segments / subdivisions itu jumlah segitiga penyusun satu sisi. Bawaannya 1 yang berarti 2 segitiga per sisi, kalau diisi 2 jadi 8 segitiga per sisi.
  6. Pakai wireframe: true di material kalau mau lihat segitiganya. Ingat, itu properti material, bukan geometry.
  7. Makin banyak vertices makin mulus, tapi makin berat. Di bentuk datar kaya kubus, nambah segments itu percuma; yang butuh justru bentuk melengkung kaya bola.
  8. Bikin geometry sendiri urutannya: BufferGeometry kosong → Float32Array → BufferAttribute → setAttribute('position', ...).
  9. Float32Array itu array datar satu dimensi, ditulis berderet x-y-z, x-y-z, x-y-z, gak boleh bersarang.
  10. Angka 3 di BufferAttribute itu aturan baca tiga-tiga. Salah angka gak bakal error, cuma bentuknya ngawur.
  11. Namanya wajib 'position', karena shader internal ThreeJS nyari nama itu.
  12. count * 3 * 3 artinya jumlah segitiga × 3 vertex × 3 angka per vertex.
  13. (Math.random() - 0.5) * 4 dipakai biar nilai acaknya nyebar ke arah negatif juga, sama kaya trik posisi kursor di bab kamera.
  14. index dipakai buat berbagi vertex antar segitiga biar array-nya lebih kecil dan performanya lebih baik, tapi belum kita pakai sekarang.

Bab selanjutnya kita akan pakai Debug UI biar bisa utak-atik nilai-nilai ini langsung dari layar tanpa bolak-balik ngedit kode.