Sebelumnya kita udah bisa bikin bentuk apa aja dan ngutak-atik nilainya dari panel. Tapi objek kita masih polos warna merah doang. Sekarang kita kasih “kulit”.
Bosen kan lihat kubus merah terus? Saatnya kita kasih texture. Tapi sebelum tempel-tempel, kita harus paham dulu texture itu apa dan sebenarnya bisa dipakai buat apa aja.
Apa itu Texture?
Texture itu gambar yang nutupin permukaan geometry kita. Tapi jangan salah paham: texture itu bukan cuma soal warna. Ada banyak jenis texture dan tiap jenis punya efek yang beda-beda ke tampilan objek.
Contoh yang paling terkenal buat belajar ini adalah texture pintu bikinan João Paulo. Kalau kamu suka karyanya, boleh banget traktir dia lewat Ko-fi atau jadi Patreon-nya.
Ini jenis-jenis texture yang paling sering dipakai:
- Color (atau albedo), adalah yang paling sederhana. Dia cuma ambil piksel dari gambarnya terus ditempel ke permukaan objek. Ini yang kita bayangin waktu denger kata “texture”.
- Alpha, adalah gambar hitam-putih di mana bagian putih bakal kelihatan dan bagian hitam bakal hilang. Ini yang dipakai buat bikin lubang atau bagian transparan tanpa harus ngubah bentuk geometry-nya.
- Height, adalah gambar hitam-putih yang beneran menggeser vertices buat bikin permukaan naik-turun. Karena yang digeser itu vertices, kamu wajib nambah subdivision dulu kalau mau efeknya kelihatan. Kalau geometry-nya cuma punya 2 segitiga per sisi, mau digeser gimana juga gak ada yang bisa digeser.
- Normal, adalah texture yang nambahin detail-detail kecil. Bedanya sama height: dia gak menggeser vertices sama sekali, dia cuma nipu cahaya biar mengira permukaannya menghadap ke arah yang beda. Ini favorit banyak orang karena detailnya dapet tapi performanya tetap enteng, gak perlu subdivision sama sekali.
- Ambient occlusion, adalah gambar hitam-putih yang bikin bayangan palsu di celah-celah permukaan. Secara fisika ini gak akurat, tapi hasilnya bikin kontras dan objeknya kelihatan lebih “nyata”.
- Metalness, adalah gambar hitam-putih yang nentuin bagian mana yang logam (putih) dan bukan logam (hitam). Informasi ini dipakai buat bikin pantulan.
- Roughness, adalah gambar hitam-putih pasangannya metalness, yang nentuin bagian mana yang kasar (putih) dan mana yang halus (hitam). Informasi ini dipakai buat menyebarkan cahaya. Karpet itu kasar banget, jadi kita gak lihat pantulan cahaya di situ; sedangkan permukaan air itu halus banget, jadi pantulannya kelihatan jelas. Di texture pintu tadi kayunya rata semua karena dilapisi pernis.
PBR
Texture-texture tadi (terutama metalness dan roughness) ngikutin prinsip yang namanya PBR, singkatan dari Physically Based Rendering. Ini kumpulan teknik yang berusaha ngikutin aturan main dunia nyata biar hasilnya realistis.
Teknik lain masih banyak, tapi PBR ini udah jadi standar buat render realistis dan dipakai di mana-mana, dari software 3D, game engine, sampai library kaya ThreeJS.
Untuk sekarang kita fokus dulu ke cara load texture, cara pakainya, transformasi apa aja yang bisa dilakuin, dan cara ngoptimalinnya. PBR dibahas lebih dalam di bab-bab berikutnya. Kalau penasaran duluan, dua artikel ini bagus banget:
Cara Load Texture
1. Dapetin URL Gambarnya
Buat load texture, kita butuh URL file gambarnya dulu. Karena kita pakai Vite sebagai build tool, ada dua cara.
Cara pertama, taruh gambarnya di folder /src/ terus di-import kaya kita nge-import dependency JavaScript:
import imageSource from './image.png'
console.log(imageSource)Cara kedua, taruh gambarnya di folder /static/ terus akses cuma dengan nulis path-nya tanpa /static:
const imageSource = '/image.png'
console.log(imageSource)Hati-hati, folder /static/ ini jalan karena konfigurasi template Vite-nya, bukan aturan alam. Kalau kamu pakai bundler lain, struktur foldernya mungkin perlu disesuaikan.
Seterusnya kita pakai cara /static/ aja karena lebih simpel.
2. Load Pakai JavaScript Biasa
Texture pintu tadi udah ada di folder /static/. Cara load-nya ada beberapa, kita mulai dari yang paling manual biar paham apa yang sebenarnya terjadi.
Pakai JavaScript biasa, kita bikin instance Image, pasang “kuping” buat event load, terus baru isi properti src-nya biar mulai men-download:
const image = new Image()
image.onload = () =>
{
console.log('image loaded')
}
image.src = '/textures/door/color.jpg'Di Console bakal muncul 'image loaded'. Perhatiin, path-nya '/textures/door/color.jpg' tanpa /static di depannya.
Urutannya penting: pasang dulu kupingnya, baru isi src-nya. Kalau kebalik, gambarnya bisa keburu selesai load sebelum kupingnya kepasang, jadi event-nya kelewat.
Tapi gambar ini belum bisa langsung dipakai. Kita harus ubah dulu jadi Texture. Kenapa? Karena WebGL butuh format yang sangat spesifik yang bisa dibaca GPU, dan karena nanti bakal ada perlakuan tambahan ke texture-nya kaya mipmapping yang kita bahas di bawah.
Kita bikin texture-nya pakai class Texture:
const image = new Image()
image.addEventListener('load', () =>
{
const texture = new THREE.Texture(image)
})
image.src = '/textures/door/color.jpg'Tapi ada masalah: variabel texture lahir di dalam fungsi, jadi gak bisa diakses dari luar fungsi itu. Ini batasan JavaScript yang namanya scope.
Kita bisa aja bikin mesh-nya sekalian di dalam fungsi itu, tapi ada cara yang lebih rapi: bikin texture-nya di luar fungsi, terus pas gambarnya udah selesai load, kita kasih tau texture-nya buat memperbarui diri lewat properti needsUpdate:
❌ salah, texture kekurung di dalam fungsi, gak bisa dipakai di material
const image = new Image()
image.addEventListener('load', () =>
{
const texture = new THREE.Texture(image)
})
image.src = '/textures/door/color.jpg'✅ benar, texture di luar, tinggal dikasih tau kalau gambarnya udah siap
const image = new Image()
const texture = new THREE.Texture(image)
image.addEventListener('load', () =>
{
texture.needsUpdate = true
})
image.src = '/textures/door/color.jpg'Dengan cara ini, variabel texture bisa langsung dipakai detik itu juga. Selama gambarnya belum selesai load, texture-nya cuma transparan, terus otomatis muncul begitu siap.
Sekarang tinggal dipasang ke material. Properti color yang dulu kita pakai buat ngewarnain kubus kita ganti jadi map:
const material = new THREE.MeshBasicMaterial({ map: texture })Pintunya muncul di keenam sisi kubus. Tapi warnanya kok agak keabu-abuan dan kusam?
Itu karena gambarnya di-encode pakai color space sRGB, sementara ThreeJS gak tau soal itu. Perbaikannya satu baris, set colorSpace-nya ke THREE.SRGBColorSpace:
const texture = new THREE.Texture(image)
texture.colorSpace = THREE.SRGBColorSpaceSekarang warnanya baru bener.
Aturan umumnya: texture yang dipakai di properti map atau matcap itu memang di-encode dalam sRGB, jadi colorSpace-nya harus di-set. Tapi cuma yang itu, jangan semua texture main di-set sRGB. Texture kaya normal, height, atau roughness itu isinya bukan warna, tapi data angka yang kebetulan disimpan sebagai gambar. Kalau ikut di-sRGB-kan, angkanya jadi geser dan hasilnya salah.
❌ salah, texture data ikut dikasih sRGB
const normalTexture = textureLoader.load('/textures/door/normal.jpg')
normalTexture.colorSpace = THREE.SRGBColorSpace✅ benar, cuma texture warna yang dikasih sRGB
const colorTexture = textureLoader.load('/textures/door/color.jpg')
colorTexture.colorSpace = THREE.SRGBColorSpace
const normalTexture = textureLoader.load('/textures/door/normal.jpg')3. Load Pakai TextureLoader
Cara JavaScript biasa tadi sebenarnya gak susah-susah amat, tapi ada cara yang jauh lebih singkat pakai TextureLoader.
Bikin variabel dari class TextureLoader, terus panggil method .load(...)-nya:
const textureLoader = new THREE.TextureLoader()
const texture = textureLoader.load('/textures/door/color.jpg')
texture.colorSpace = THREE.SRGBColorSpaceTiga baris, selesai. Di dalamnya ThreeJS ngelakuin persis yang kita lakuin manual tadi: bikin Image, dengerin event-nya, terus nge-update texture-nya pas siap.
Dan yang penting: satu instance TextureLoader bisa dipakai buat load texture sebanyak apapun. Gak perlu bikin loader baru tiap gambar.
❌ boros, tiap texture bikin loader sendiri
const colorTexture = new THREE.TextureLoader().load('/textures/door/color.jpg')
const alphaTexture = new THREE.TextureLoader().load('/textures/door/alpha.jpg')✅ benar, satu loader dipakai bareng-bareng
const textureLoader = new THREE.TextureLoader()
const colorTexture = textureLoader.load('/textures/door/color.jpg')
const alphaTexture = textureLoader.load('/textures/door/alpha.jpg')Setelah path-nya, kita juga bisa ngirim 3 fungsi yang bakal dipanggil di tiga kejadian berbeda:
- load, waktu gambarnya berhasil dimuat.
- progress, waktu proses muatnya lagi jalan.
- error, waktu ada yang gagal.
const textureLoader = new THREE.TextureLoader()
const texture = textureLoader.load(
'/textures/door/color.jpg',
() =>
{
console.log('loading finished')
},
() =>
{
console.log('loading progressing')
},
() =>
{
console.log('loading error')
}
)
texture.colorSpace = THREE.SRGBColorSpaceKalau suatu saat texture-nya gak muncul dan kamu bingung kenapa, tempelin tiga fungsi ini dulu. Biasanya ketauan salahnya di situ, entah path-nya keliru atau nama filenya beda huruf besar-kecil.
4. Load Pakai LoadingManager
Terakhir, kalau gambarnya banyak dan kita mau satu tempat buat mantau semuanya sekaligus, misalnya biar tau kapan semua gambar udah selesai, kita pakai LoadingManager.
Bikin instance-nya, terus kirim ke TextureLoader:
const loadingManager = new THREE.LoadingManager()
const textureLoader = new THREE.TextureLoader(loadingManager)Terus kita isi properti onStart, onLoad, onProgress, dan onError dengan fungsi kita sendiri:
const loadingManager = new THREE.LoadingManager()
loadingManager.onStart = () =>
{
console.log('loading started')
}
loadingManager.onLoad = () =>
{
console.log('loading finished')
}
loadingManager.onProgress = () =>
{
console.log('loading progressing')
}
loadingManager.onError = () =>
{
console.log('loading error')
}
const textureLoader = new THREE.TextureLoader(loadingManager)Sekarang kita load semua texture pintunya sekaligus:
const colorTexture = textureLoader.load('/textures/door/color.jpg')
colorTexture.colorSpace = THREE.SRGBColorSpace
const alphaTexture = textureLoader.load('/textures/door/alpha.jpg')
const heightTexture = textureLoader.load('/textures/door/height.jpg')
const normalTexture = textureLoader.load('/textures/door/normal.jpg')
const ambientOcclusionTexture = textureLoader.load('/textures/door/ambientOcclusion.jpg')
const metalnessTexture = textureLoader.load('/textures/door/metalness.jpg')
const roughnessTexture = textureLoader.load('/textures/door/roughness.jpg')Perhatiin, variabel texture udah kita ganti nama jadi colorTexture, jadi jangan lupa ganti juga di materialnya:
const material = new THREE.MeshBasicMaterial({ map: colorTexture })LoadingManager ini gunanya kerasa banget kalau kamu mau bikin layar loading yang baru ilang setelah semua aset kelar. Nanti dia juga bisa dipakai bareng jenis loader lain, bukan cuma texture.
UV Unwrapping
Nempelin gambar ke kubus sih gampang dibayangin, tinggal tempel ke tiap sisi. Tapi gimana kalau bentuknya bola atau donat?
Coba ganti-ganti geometry-nya pakai yang udah kita kenal di 07 Geometry > Geometry Bawaan ThreeJS:
const geometry = new THREE.BoxGeometry(1, 1, 1)
// Atau
const geometry = new THREE.SphereGeometry(1, 32, 32)
// Atau
const geometry = new THREE.ConeGeometry(1, 1, 32)
// Atau
const geometry = new THREE.TorusGeometry(1, 0.35, 32, 100)Kelihatan kan, texture-nya ketarik-tarik dan kegencet dengan cara yang beda-beda buat nutupin permukaannya.
Nah, itu namanya UV unwrapping. Bayangin kaya ngebuka bungkus permen atau origami sampai jadi lembaran datar. Tiap vertex punya satu koordinat 2D di lembaran datar itu (biasanya berbentuk persegi), makanya sumbunya dinamain u dan v, bukan x dan y, biar gak ketuker sama koordinat 3D.
Koordinat UV ini bisa kita intip beneran di properti geometry.attributes.uv:
console.log(geometry.attributes.uv)Ingat waktu kita bikin geometry sendiri dan harus nulis attribute `position` sendiri? Nah, uv itu attribute yang sama jenisnya. Bedanya, kalau kita pakai geometry bawaan, koordinat UV-nya udah dibikinin otomatis sama ThreeJS. Kalau kita bikin geometry sendiri terus mau ditempelin texture, ya kita sendiri yang harus nulis koordinat UV-nya.
Kalau bikinnya di software 3D kaya Blender, di situ juga ada proses UV unwrapping-nya. Tenang, hampir semua software 3D punya fitur auto unwrap yang biasanya udah cukup buat kebutuhan normal.
Mengubah Texture
Balik lagi ke kubus dengan satu texture. Sekarang kita lihat transformasi apa aja yang bisa dikasih ke texture-nya.
1. Repeat
Texture bisa diulang pakai properti repeat, yang bentuknya Vector2, artinya dia punya properti x dan y.
const colorTexture = textureLoader.load('/textures/door/color.jpg')
colorTexture.colorSpace = THREE.SRGBColorSpace
colorTexture.repeat.x = 2
colorTexture.repeat.y = 3Lho, kok gak keulang? Yang terjadi malah texture-nya jadi kecil dan piksel terakhirnya ketarik memanjang kaya kena smudge.
Itu karena bawaannya texture emang gak di-set buat mengulang dirinya sendiri. Kita harus kasih tau dulu lewat properti wrapS dan wrapT pakai konstanta THREE.RepeatWrapping:
wrapSbuat sumbu x.wrapTbuat sumbu y.
colorTexture.wrapS = THREE.RepeatWrapping
colorTexture.wrapT = THREE.RepeatWrappingSekarang baru keulang beneran.
Kalau mau tiap ulangannya dibolak-balik kaya cermin (biar sambungannya gak kelihatan), pakai THREE.MirroredRepeatWrapping:
colorTexture.wrapS = THREE.MirroredRepeatWrapping
colorTexture.wrapT = THREE.MirroredRepeatWrapping2. Offset
Texture bisa digeser pakai properti offset, yang juga Vector2 dengan properti x dan y. Yang digeser sebenarnya koordinat UV-nya, bukan gambarnya:
colorTexture.offset.x = 0.5
colorTexture.offset.y = 0.53. Rotation
Texture bisa diputar pakai properti rotation, yang isinya angka biasa dalam satuan radian, sama kaya waktu kita muter objek:
colorTexture.rotation = Math.PI * 0.25Math.PI * 0.25 itu seperempat dari setengah putaran, alias 45 derajat.
Kalau offset dan repeat tadi kita hapus, bakal kelihatan kalau muternya bukan di tengah, tapi di pojok kiri bawah. Itu karena titik putarnya ada di koordinat UV 0, 0. Buat mindahin titik putarnya ke tengah, pakai properti center yang juga Vector2:
colorTexture.rotation = Math.PI * 0.25
colorTexture.center.x = 0.5
colorTexture.center.y = 0.5Sekarang muternya di tengah, sebagaimana yang kita harapkan.
Filtering dan Mipmapping
Coba lihat sisi atas kubus dari sudut yang hampir sejajar mata, sampai sisi itu nyaris gak kelihatan. Texture-nya jadi buram banget kan?
Itu urusannya filtering dan mipmapping.
Mipmapping itu teknik bikin versi kecil dari sebuah texture berulang-ulang, tiap kali setengahnya, sampai jadi ukuran 1x1 piksel. Jadi dari 1024x1024 jadi 512x512, 256x256, terus sampai 1x1. Semua versi itu dikirim ke GPU, terus GPU yang milih versi paling pas buat dipakai sesuai jarak dan sudut pandangnya.
ThreeJS dan GPU udah ngurus semua itu sendiri. Yang perlu kita tentuin cuma algoritma filter mana yang dipakai, dan ada dua jenis: minification filter dan magnification filter.
1. Minification Filter
Minification filter jalan waktu piksel texture-nya lebih kecil daripada piksel hasil render. Bahasa gampangnya: texture-nya kegedean buat permukaan yang dia tutupin.
Diatur lewat properti minFilter, dengan 6 pilihan nilai:
THREE.NearestFilterTHREE.LinearFilterTHREE.NearestMipmapNearestFilterTHREE.NearestMipmapLinearFilterTHREE.LinearMipmapNearestFilterTHREE.LinearMipmapLinearFilter
Bawaannya THREE.LinearMipmapLinearFilter. Kalau hasilnya kurang sreg, ya tinggal coba-coba yang lain.
Kita gak bahas satu-satu, tapi coba THREE.NearestFilter karena hasilnya paling beda:
colorTexture.minFilter = THREE.NearestFilterKalau layar kamu pixel ratio-nya di atas satu, bedanya mungkin tipis. Coba arahin kamera sampai sisinya hampir gak kelihatan, nanti bakal muncul detail-detail aneh.
Biar makin jelas, coba pakai texture papan catur checkerboard-1024x1024.png yang ada di folder /static/textures/:
const colorTexture = textureLoader.load('/textures/checkerboard-1024x1024.png')Pola aneh bergelombang yang muncul itu namanya moiré pattern, dan biasanya kita pengen ngehindarin ini.
2. Magnification Filter
Magnification filter kebalikannya, jalan waktu piksel texture-nya lebih besar daripada piksel render. Bahasa gampangnya: texture-nya kekecilan buat permukaan yang dia tutupin.
Coba pakai checkerboard-8x8.png yang juga ada di /static/textures/:
const colorTexture = textureLoader.load('/textures/checkerboard-8x8.png')Hasilnya buram total, wajar aja, texture 8x8 piksel dipaksa nutupin permukaan segede itu.
Mungkin kelihatan jelek, tapi sebenarnya ini yang paling aman. Kalau efeknya gak lebay, user biasanya gak bakal nyadar.
Diatur lewat properti magFilter, dan pilihannya cuma dua:
THREE.NearestFilterTHREE.LinearFilter
Bawaannya THREE.LinearFilter. Coba ganti ke THREE.NearestFilter:
colorTexture.magFilter = THREE.NearestFilterSekarang gambarnya gak diburamin, tapi jadi kotak-kotak tajam. Nah, ini justru menguntungkan kalau kamu emang mau gaya Minecraft yang pikselnya kelihatan. Coba pakai minecraft.png di folder yang sama:
const colorTexture = textureLoader.load('/textures/minecraft.png')Satu catatan penutup soal filter: THREE.NearestFilter itu paling murah dibanding yang lain, jadi performanya lebih enteng.
Dan ini yang sering kelewat: mipmap itu cuma kepakai buat minFilter. Kalau kamu pakai THREE.NearestFilter, mipmap-nya jadi mubazir, jadi matiin aja biar GPU-nya gak kerja sia-sia:
❌ mubazir, mipmap tetap dibikin padahal gak kepakai
colorTexture.minFilter = THREE.NearestFilter✅ lebih enteng, mipmap dimatiin
colorTexture.generateMipmaps = false
colorTexture.minFilter = THREE.NearestFilterFormat dan Optimasi Texture
Waktu nyiapin texture, ada 3 hal yang wajib diinget: beratnya, ukurannya, dan datanya.
1. Berat (Weight)
Jangan lupa, user yang buka website kamu itu harus men-download semua texture ini. Kita bisa pakai format gambar web pada umumnya, kaya .jpg (kompresinya lossy, biasanya lebih ringan) atau .png (kompresinya lossless, biasanya lebih berat).
Pakai cara-cara biasa buat bikin gambar seringan mungkin tapi masih enak dilihat. Bisa pakai website kompresi kaya TinyPNG (jangan ketipu namanya, dia juga bisa buat jpg) atau software apapun yang kamu punya.
2. Ukuran (Size)
Tiap piksel dari texture harus disimpan di GPU, gak peduli berapa berat file-nya. Dan sama kaya harddisk, GPU juga punya batas penyimpanan. Malah lebih parah, karena mipmapping yang otomatis itu nambahin jumlah piksel yang harus disimpan.
Jadi kecilin ukuran gambarnya sebisa mungkin.
Terus, ingat lagi cara kerja mipmapping: ThreeJS bakal terus bikin versi setengah lebih kecil sampai jadi 1x1. Karena itu, lebar dan tinggi texture kamu wajib angka pangkat 2. Ini bukan saran, ini keharusan, biar ukurannya bisa terus dibagi 2 dengan rapi.
Contohnya 512x512, 1024x1024, atau 512x2048. Angka 512, 1024, dan 2048 itu bisa dibagi 2 terus sampai ketemu 1.
Kalau kamu pakai ukuran yang bukan pangkat 2, ThreeJS bakal maksa narik gambarnya ke angka pangkat 2 terdekat. Hasilnya bisa jelek dan kamu bakal dapet peringatan di Console.
❌ salah, 500x300 bukan pangkat 2, bakal ditarik paksa dan dapet warning
✅ benar, 512x256, dua-duanya bisa dibagi 2 terus sampai 1
3. Data
Texture itu mendukung transparansi. Masalahnya, file .jpg gak punya alpha channel, jadi kalau butuh transparansi kamu harus pakai .png. Alternatif lain, pakai alpha map kaya jenis texture nomor 2 yang kita bahas di atas.
Terus, kalau kamu pakai normal texture (yang warnanya ungu-ungu itu), kamu butuh nilai merah, hijau, dan biru tiap pikselnya persis apa adanya, karena itu bukan warna, tapi data arah permukaan. Buat itu pakai .png, karena kompresinya lossless jadi nilainya kejaga. Kalau dipaksa .jpg, angkanya bakal geser dikit-dikit dan hasilnya muncul glitch aneh di permukaan objek.
Di Mana Cari Texture
Sayangnya nyari texture yang pas itu selalu susah. Websitenya banyak, tapi seringnya kurang cocok atau harus bayar.
Mulai aja dari nyari di web. Ini beberapa yang sering jadi tujuan:
Selalu pastiin kamu punya hak buat make texture-nya kalau bukan buat keperluan pribadi.
Kamu juga bisa bikin sendiri dari foto pakai software 2D kaya Photoshop, atau bikin texture prosedural pakai software kaya Substance Designer.
Code Lengkap
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
/**
* Base
*/
// Canvas
const canvas = document.querySelector('canvas.webgl')
// Scene
const scene = new THREE.Scene()
// Sizes
const sizes = {
width: window.innerWidth,
height: window.innerHeight
}
/**
* Textures
*/
const loadingManager = new THREE.LoadingManager()
loadingManager.onStart = () =>
{
console.log('loading started')
}
loadingManager.onLoad = () =>
{
console.log('loading finished')
}
loadingManager.onProgress = () =>
{
console.log('loading progressing')
}
loadingManager.onError = () =>
{
console.log('loading error')
}
const textureLoader = new THREE.TextureLoader(loadingManager)
const colorTexture = textureLoader.load('/textures/door/color.jpg')
colorTexture.colorSpace = THREE.SRGBColorSpace
const alphaTexture = textureLoader.load('/textures/door/alpha.jpg')
const heightTexture = textureLoader.load('/textures/door/height.jpg')
const normalTexture = textureLoader.load('/textures/door/normal.jpg')
const ambientOcclusionTexture = textureLoader.load('/textures/door/ambientOcclusion.jpg')
const metalnessTexture = textureLoader.load('/textures/door/metalness.jpg')
const roughnessTexture = textureLoader.load('/textures/door/roughness.jpg')
// colorTexture.repeat.x = 2
// colorTexture.repeat.y = 3
// colorTexture.wrapS = THREE.RepeatWrapping
// colorTexture.wrapT = THREE.RepeatWrapping
// colorTexture.offset.x = 0.5
// colorTexture.offset.y = 0.5
// colorTexture.rotation = Math.PI * 0.25
// colorTexture.center.x = 0.5
// colorTexture.center.y = 0.5
colorTexture.generateMipmaps = false
colorTexture.minFilter = THREE.NearestFilter
colorTexture.magFilter = THREE.NearestFilter
/**
* Object
*/
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ map: colorTexture })
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
/**
* Camera
*/
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
camera.position.z = 3
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 tick = () =>
{
// Update controls
controls.update()
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
tick()Ringkasan
- Texture itu gambar yang nutupin permukaan geometry, dan bukan cuma soal warna. Ada 7 jenis yang umum: color/albedo, alpha, height, normal, ambient occlusion, metalness, dan roughness.
heightmenggeser vertices beneran (jadi butuh subdivision), sedangkannormalcuma nipu cahaya tanpa nambah vertex, makanya jauh lebih enteng.- PBR (Physically Based Rendering) itu kumpulan teknik yang ngikutin aturan dunia nyata biar hasilnya realistis, dan sekarang jadi standar di mana-mana.
- Gambarnya bisa ditaruh di
/src/(di-import) atau di/static/(diakses lewat path tanpa/static). Kita pakai cara kedua, dan itu jalan karena konfigurasi Vite. - Gambar mentah gak bisa langsung dipakai, harus diubah jadi
Texturedulu, karena WebGL butuh format khusus yang bisa dibaca GPU. - Kalau load manual, bikin texture-nya di luar fungsi terus kasih
texture.needsUpdate = truedi dalam fungsi, biar gak kejebak scope. TextureLoaderjauh lebih singkat, dan satu instance bisa dipakai buat semua texture. Bisa dikasih 3 fungsi buat load, progress, dan error yang berguna banget waktu ngedebug.LoadingManagerdipakai kalau mau mantau semua gambar sekaligus, contohnya buat bikin layar loading yang ilang setelah semua aset siap.- Cuma texture warna (
map/matcap) yang di-setcolorSpace = THREE.SRGBColorSpace. Texture data kaya normal atau roughness jangan, nanti angkanya geser. - UV unwrapping itu proses “ngebuka bungkus” objek 3D jadi lembaran datar, koordinatnya ada di
geometry.attributes.uv. Geometry bawaan udah otomatis, geometry bikinan sendiri harus kita tulis sendiri. repeatgak bakal jalan sebelumwrapSdanwrapTdi-set keTHREE.RepeatWrappingatauTHREE.MirroredRepeatWrapping.offsetmenggeser,rotationmemutar dalam radian, dancentermindahin titik putarnya yang bawaannya ada di pojok kiri bawah (0, 0).- Mipmapping itu bikin versi setengah lebih kecil berulang-ulang sampai 1x1, dan GPU yang milih versi mana yang dipakai.
minFilterbuat texture kegedean (6 pilihan),magFilterbuat texture kekecilan (2 pilihan).THREE.NearestFilterpaling murah dan bikin hasil kotak-kotak ala Minecraft.- Mipmap cuma kepakai buat
minFilter, jadi kalau pakaiNearestFiltermatiin aja pakaigenerateMipmaps = false. - Tiga hal wajib waktu nyiapin texture: berat file, ukuran piksel, dan jenis datanya.
- Lebar dan tinggi texture wajib angka pangkat 2 (
512,1024,2048), kalau nggak bakal ditarik paksa sama ThreeJS dan hasilnya jelek plus dapat warning. - Butuh transparansi atau pakai normal texture? Pakai
.png, karena.jpggak punya alpha channel dan kompresinya bikin nilai datanya geser.
Bab selanjutnya kita akan belajar Materials, karena sejauh ini kita cuma pakai MeshBasicMaterial yang cuek sama cahaya, padahal texture-texture tadi baru kelihatan sakti kalau materialnya bisa bereaksi sama cahaya.