- Bulan dan planet gas untuk latar langit dapat menangani permukaan, rotasi, dan atmosfer hanya dengan disk melingkar serta pixel shader, tanpa mesh bola sungguhan
- UV sphere konvensional memudahkan pemetaan tekstur 2×1, tetapi juga menimbulkan kontur bergerigi, hilangnya sampling mipmap, pola berulang, seam, distorsi kutub, dan keterbatasan halo atmosfer
- Pendekatan disk memulihkan posisi permukaan bola dari UV pusat, lalu membuat permukaan yang tampak seperti bola per piksel melalui UV sferis berbasis
asin, matriks rotasi, dan spin berupa pergeseran koordinat u
- Shading dimulai dari pencahayaan Lambertian yang memakai posisi permukaan sebagai normal; untuk permukaan berbatu diterapkan matriks TBN dan normal map, sementara atmosfer didekati lewat kecerahan halo dan absorpsi per kanal
- Tekstur statis Substance Designer dan tekstur rendering dinamis Unreal dapat ditangani dengan cara yang sama, sehingga beban performa bisa dikurangi lewat tekstur dinamis yang lebih kecil dan penyesuaian tiling
Tujuan visual planet skybox
- Skybox proyek ini berfokus membangun komposisi seperti langit alien dengan bulan beranimasi dan planet gas
- Kedua planet berotasi, dan planet gas juga memiliki aliran atmosfer yang bergerak
- Gerakan sebenarnya halus, tetapi kecepatannya dinaikkan demi efek visual
- Arah artistiknya adalah gaya semi-realistis yang terinspirasi seni fiksi ilmiah era 1970–80-an
Input dan batasan tekstur permukaan
- Permukaan planet gas dibuat secara real-time dengan pixel shader dan metode render-to-texture
- Tekstur bulan dibuat di Substance Designer, dan berbagai look dapat dieksperimenkan dengan mengubah palet warna serta material seed
- Planet gas dinamis harus merender tekstur setiap frame, sehingga biaya performanya besar
- Jika resolusi tekstur dinaikkan 2 kali, biaya rendering menjadi 4 kali lipat
- Diperlukan solusi yang bekerja konsisten baik untuk tekstur Substance Designer statis maupun tekstur yang dirender dinamis di Unreal
- Cubemap sempat dipertimbangkan di awal, tetapi dapat memakai 6 kali resource dibanding tekstur persegi, dan Substance Designer tidak mendukung pembuatan cubemap, sehingga tidak praktis
Keterbatasan yang terlihat pada UV sphere
- UV sphere tampak seperti pilihan bawaan karena mudah diselaraskan dengan tekstur persegi panjang 2×1 dan koordinatnya
- Karena hanya satu sisi bola yang terlihat pada satu waktu, tekstur persegi yang di-tile juga bisa digunakan alih-alih tekstur persegi panjang unik untuk seluruh permukaan
- Dalam penerapan nyata, beberapa masalah muncul bersamaan
- Kontur luar terlihat jelas bergerigi, sehingga harus menambah subdivision atau menutupinya dengan mask pixel shader
- Sebagian besar permukaan bola terlihat miring sehingga disampling dari mipmap rendah, dan pada tekstur dinamis banyak piksel yang dibuat setiap frame terpakai untuk area yang nyaris tidak terlihat
- Tiling harus ditambah dalam kelipatan bilangan bulat agar keseluruhan pola tersambung, dan pengulangan sudah terlihat hanya dengan kenaikan 2 kali
- Fractional tiling membuat seam yang membentang dari kutub utara ke kutub selatan
- Di kutub terjadi texture pinching dan distorsi pemetaan segitiga, tampak mirip distorsi seperti tekstur PSX yang bergoyang
- Karena hanya dapat menggambar permukaan, atmospheric halo membutuhkan model terpisah
- UV sphere masih bisa digunakan untuk pemodelan planet, tetapi untuk keperluan skybox diperlukan banyak modifikasi dan hack
Pendekatan disk melingkar dan pixel shader
- Karena planet skybox diamati dari satu posisi yang jauh, mesh bola 3D yang kompleks tidak selalu diperlukan
- Gunakan disk poligon melingkar terisi yang sederhana, lalu tangani pemetaan tekstur di pixel shader
- Atmospheric halo juga dapat digambar dalam mesh yang sama
- Titik asal UV disk harus tepat berada di pusat
- Jika radius planet dianggap 1, koordinat UV harus diperluas ke luar radius tersebut untuk menyediakan ruang menggambar atmosfer
Rekonstruksi permukaan bola
- Sistem koordinat menggunakan sistem left-handed, Y-up yang konsisten dengan DirectX
- Unreal Engine juga left-handed, tetapi arah up-nya adalah Z, jadi arah dan format normal map perlu diperiksa
- Persamaan permukaan memetakan posisi 2D pada bidang disk ke posisi 3D di permukaan bola
- Pertama, untuk menentukan apakah piksel berada di dalam permukaan bola, periksa apakah panjang vektor UV lebih kecil daripada radius
float CircleMask( float2 uv, float radius)
{
return length(uv) < radius? 1.0: 0.0;
}
- Cara ini hanya bekerja dengan benar ketika koordinat UV disejajarkan ke pusat mesh
- Local position permukaan bola memakai x dan y dari UV apa adanya, lalu hanya z yang dipulihkan
float3 ReconstructSurface(float2 uv)
{
float zSquared = 1.0 - dot(uv, uv);
float z = sqrt(zSquared);
return float3(uv, z);
}
Pembuatan UV sferis
- Proses memetakan tekstur persegi ke permukaan bola terbagi menjadi tiga langkah
- Membungkusnya ke silinder
- Mengulangi proses yang sama pada sumbu y
- Mengubah hasilnya menjadi bentuk lingkaran
- Koordinat tekstur x sebanding dengan sudut pembungkus silinder; hitung
arcsine(x), lalu petakan ulang [-π, π] ke [0, 1]
- Koordinat di-pinching di kutub menggunakan generatrix, yaitu lebar lokal bola
float2 GenerateSphericalUV(float3 position)
{
float width = sqrt(1.0 - position.y * position.y);
float generatrixX = position.x / width * sign(position.z);
float2 generatrix = float2(generatrixX, position.y);
float2 uv = asin(generatrix) / 3.14159 + float2(0.5, 0.5);
return float2(uv);
}
Kemiringan sumbu dan rotasi
- Untuk komposisi planet yang lebih alami, sumbu planet dimiringkan dengan pitch dan roll
- Planet dalam seni fiksi ilmiah biasanya dimiringkan agar komposisinya tampak dinamis dan dapat menampilkan ice cap kutub
- Yaw setara dengan gerakan rotasi, dan ditangani pada tahap terpisah untuk menghindari masalah seam
- Pada mesh bola, planet biasanya ditransformasikan dengan matriks, tetapi pada pendekatan disk setiap piksel mendefinisikan posisi permukaan, sehingga cukup memakai matriks 3×3 sederhana yang hanya berisi rotasi
float3x3 CreateRotationMatrix(float pitch, float roll) {
float cosPitch = cos(pitch);
float sinPitch = sin(pitch);
float cosRoll = cos(roll);
float sinRoll = sin(roll);
return (float3x3)(
cosRoll, -sinRoll * cosPitch, sinRoll * sinPitch,
sinRoll, cosRoll * cosPitch, -cosRoll * sinPitch,
0.0, sinPitch, cosPitch
);
}
- Material editor Unreal tidak mendukung matriks sebagai format data, tetapi ada cara untuk mengakalinya
Penanganan scale, seam, dan spin
- Jika scale tekstur diubah, UV seam yang sebelumnya tersembunyi di tepi tekstur asli akan terlihat
- Seam tidak mudah dihilangkan sepenuhnya, tetapi akan kurang terlihat jika dipindahkan ke sisi belakang
- Bagi bola menjadi kuadran berdasarkan sign posisi, lalu terapkan offset arah u pada kuadran back-left dan back-right
float2 GenerateSphericalUV(float3 position, float spin, float scale)
{
float leftRightSign = sign(position.x);
float frontBackSign = sign(position.z);
float width = sqrt(1.0 - position.y * position.y);
float generatrixX = position.x / width * frontBackSign;
float2 generatrix = float2 (generatrixX, position.y);
float2 uv = asin(generatrix) / 3.14159 + float2 (spin, 0.5);
if(frontBackSign < 0.0)
{
uv = float2 (uv.x + 1.0 * leftRightSign, uv.y);
}
return float2 (uv / scale);
}
- Seam masih menghubungkan kutub utara dan selatan, tetapi jauh kurang terlihat karena dipindahkan ke belakang
- Rotasi ditangani bukan dengan memutar bolanya, melainkan dengan menggeser tekstur permukaan ke arah koordinat u
float2 GenerateSphericalUV(float3 position, float scale, float spin)
{
float width = sqrt(1.0 - position.y * position.y);
float generatrixX = position.x / width * sign(position.z);
float2 generatrix = float2(generatrixX, position.y);
float2 uv = asin(generatrix) / 3.14159 * scale + float2(0.5 + spin, 0.5);
return float2(uv);
}
float2 sphericalVU = GenerateSphericalUV(position, scale, time*speed)
Shading permukaan dan normal map
- Agar planet terlihat bulat, shading sangat penting
- Berbeda dari shading engine biasa, planet memiliki dua layer, yaitu permukaan dan atmosfer, sehingga pencahayaan dan blending kedua layer harus didefinisikan secara manual
- Jika radiusnya 1, posisi permukaan sebelum rotasi dapat langsung digunakan sebagai surface normal
- Pencahayaan menggunakan model Lambertian sederhana
- Tujuannya bukan realisme penuh, melainkan estetika sampul fiksi ilmiah bergaya yang terinspirasi foto NASA
float LambertianLight(float3 normal, float3 lightDirection) {
float NdotL = max(dot(normal, lightDirection), 0.0);
return NdotL;
}
- Untuk permukaan halus seperti gas giant, normal sederhana mungkin sudah cukup
- Planet berbatu lebih cocok mensimulasikan medan seperti pegunungan, punggungan, dan kawah dengan normal map
- Untuk memakai normal map, diperlukan matriks TBN yang terdiri dari Tangent, Bitangent, dan Normal
- Pada bola, perhitungan ini harus dilakukan per piksel
- Matriks TBN digunakan untuk mengubah normal dari normal map ke World Space Coordinates
Diskontinuitas UV dan artefak mipmap
- UV yang dibuat di pixel shader dapat mengalami masalah diskontinuitas
- Back seam seharusnya membuat komponen UV horizontal tersambung penuh dari 0.0 ke 1.0 di batasnya, tetapi pada praktiknya artefak blocky muncul di sepanjang seam
- Garis ini berkaitan dengan DDX UV
- DDX dan DDY mengukur laju perubahan UV pada sumbu ruang layar
- Texture sampler memakai nilai ini untuk menentukan mipmap yang akan digunakan
- Derivative UV rendah berkorespondensi dengan mipmap beresolusi tinggi, sedangkan derivative tinggi berkorespondensi dengan mipmap beresolusi rendah
- Jika nilai di kiri dan kanan seam melonjak tajam, DDX membesar dan sampling dilakukan dari mipmap terendah, sehingga dapat terlihat seperti garis abu-abu
- Pada planet, ini tidak menjadi masalah besar karena akan ditutupi polar patch, tetapi jika terlihat jelas, DDX dan DDY seam harus dikoreksi secara manual lalu diteruskan ke sampler
- Contoh fix hanya untuk penjelasan, dan nilai sebenarnya harus diturunkan sendiri agar sesuai dengan mapping
Polar patch dan koreksi pinching
- Distorsi kutub ditutupi dengan polar patch sesuai metode yang sudah teruji
- Pada planet atau bulan, wilayah kutub sering tertutup es dan secara visual berbeda dari wilayah lain, sehingga patch tekstur terpisah dapat menyembunyikan masalah sekaligus menambah daya tarik visual
- Polar patch dapat ditambahkan dengan memetakannya ke bidang yang tegak lurus terhadap sumbu rotasi dan memutar koordinat hasilnya
float2 PolarPatchMapping(float3 position, float scale, float spin)
{
float cosSpin = cos(spin);
float sinSpin = sin(spin);
float scale = 0.4;
float2 uv = float2(position.x, position.z) * scale;
float2 spinningUV = float2(uv.x * cosSpin - uv.y * sinSpin,
uv.x * sinSpin + uv.y * cosSpin);
return spinningUV;
}
- Karena UV map tidak disimpan di vertex melainkan dibuat per piksel, fleksibilitas untuk mengoreksi pinching kutub menjadi lebih besar
- Aspect ratio tekstur kutub dapat dikoreksi dengan pembagian sederhana
uv = float2(uv.x, uv.y/pow(width, 1/3));
- Hasil koreksi bisa terlihat seperti artefak lain, tetapi dalam adegan realistis ia tersambung lebih mulus dengan polar patch dan mengurangi ketidakselarasan visual
Aproksimasi halo atmosfer
- Karena permukaan planet tidak mengisi seluruh area disk, ruang tersisa dapat digunakan untuk menggambar atmospheric halo
- Jika halo digambar dalam shader yang sama, ia dapat menyatu secara alami dengan atmosfer di atas planet
- Rendering atmosfer berbasis fisika biasanya memakai Rayleigh scattering dan raymarching, tetapi tidak digunakan di sini
- Digunakan aproksimasi yang mirip dengan trik pencahayaan objek dinamis di Quake 1·2
- Static lighting Quake dihitung sebelumnya sebagai lightmap
- Monster dinamis men-sampling lightmap di bawahnya untuk menyesuaikan warna, dan walaupun tidak akurat secara fisika, hasilnya menyatu dengan lingkungan
- Dengan cara yang sama, permukaan planet digunakan untuk mengaproksimasi pencahayaan halo atmosfer
- Surface normal sebelum bump mapping diperluas untuk menghitung kecerahan halo
- Dengan me-remap kecerahan berbeda per kanal, absorpsi panjang gelombang cahaya yang berbeda dapat disimulasikan secara sederhana
- Karena atmosfer lebih terlihat pada sudut miring, komponen Z dari surface normal di-remap
- Jarak dari permukaan bola juga dihitung untuk efek fade halo
- Pada akhirnya, permukaan dan atmosfer digabung dengan alpha blending
Hasil akhir dan pemanfaatan
- Meski membutuhkan lebih banyak tahap dari perkiraan, hasil akhirnya tampak seperti bola penuh dan mendukung manipulasi tekstur serta shader yang diinginkan
- Tekstur permukaan dapat dibuat dengan satu graph Substance Designer, dan hasilnya tidak memerlukan post-processing tambahan
- Tekstur beranimasi juga dapat ditangani dengan cara yang sama
- Ukuran tekstur yang lebih kecil dapat digunakan untuk menghemat performa
- Tiling dapat disesuaikan sesuai kebutuhan
- Video hasilnya dapat dilihat di Video 3
1 komentar
Komentar Hacker News
Sepertinya penulis terlalu cepat mengesampingkan cubemap; saat saya mencobanya untuk merender planet gas dinamis dalam proyek pribadi, itu adalah solusi paling sederhana
Memakai cubemap tidak membuat memori membengkak 6 kali lipat; ini hanya membagi satu tekstur persegi panjang besar menjadi 6 sisi persegi panjang kecil, jadi total detail teksturnya sama
Kelebihannya adalah tidak perlu memikirkan pemampatan di kutub, dan dengan fungsi noise 3D atau 4D, mudah membuat medan aliran tanpa seam untuk animasi/distorsi tekstur
https://www.junkship.net/News/2016/06/09/jupiter-jazz
Tiap hemisfer diproyeksikan sebagai cakram, tetapi teksturnya dapat diisi sampai batas persegi, dan sebagian dari tiap hemisfer bisa diduplikasi ke tepi tekstur sisi lain
Ada perbedaan skala 1:2 antara pusat dan tepi cakram, sehingga bisa dianggap ada pemborosan piksel jika dilihat dari detail minimum; tetapi karena ini proyeksi konformal, cara mengambil sampel warna piksel target pada perspektif curam menjadi jauh tidak terlalu rumit, dan perhitungan maju/mundurnya pun hanya butuh 1 pembagian serta beberapa penjumlahan dan perkalian per titik proyeksi, bahkan lebih murah daripada proyeksi pusat pada cubemap
Jika perlu metode konformal yang secara konseptual tidak terlalu sulit sambil makin mengurangi perubahan skala dan pemborosan piksel di sudut, Anda bisa memakai 2 proyeksi Mercator yang saling tegak lurus dan sedikit tumpang tindih untuk menutupi bola seperti dua potong kulit bola bisbol
Tiap potongan bisa menjadi tekstur persegi panjang, dan ada makalah NOAA yang mengusulkan pendekatan ini untuk grid persamaan diferensial pada simulasi cuaca Bumi
Proyeksi paling efisien piksel yang saya tahu adalah membagi bola menjadi oktahedron, lalu menutupi tiap oktan dengan grid piksel heksagonal berbasis “koordinat luas bola”
Tiap oktan dapat direpresentasikan pada gambar piksel persegi biasa sebagai setengah persegi, yaitu segitiga siku-siku 45-45-90, menghasilkan sesuatu seperti https://observablehq.com/@jrus/sac-quincuncial, dan juga bisa memakai grid heksagonal seperti https://observablehq.com/@jrus/sphere-resample
Namun detail penanganan saat harus mengambil sampel melewati batas jauh lebih merepotkan daripada metode 2 proyeksi stereografik, dan artefak seam juga bisa muncul
Perlu lebih banyak matematika, tetapi jika Anda tidak berencana membagi permukaan lebih lanjut karena alasan lain, biasanya itu tidak sepadan
Pemampatan tekstur di kutub sebenarnya adalah bentuk ekstrem dari distorsi yang ada di seluruh permukaan
Biasanya hanya terlihat jelas di kutub, tetapi jika bola dibagi secara adaptif menjadi segitiga, distorsi ikut berubah saat level subdivisi berubah, sehingga bisa terlihat juga di tempat lain
Masalahnya adalah bola dibagi menjadi segi empat, dan tiap segi empat direpresentasikan di ruang UV sebagai 2 segitiga dengan luas yang sama, tetapi di ruang 3D salah satu segitiganya—terutama yang sisi horizontalnya lebih dekat ke kutub—lebih kecil
Meski begitu, UV diinterpolasi secara linear di dalam segitiga, sehingga separuh tekstur menyusut dan separuhnya melebar
Di kutub, luas 3D salah satu segitiga benar-benar menjadi 0, sehingga hanya separuh tekstur yang dirender, dan seam di antara segitiga menjadi jelas
Solusi yang benar adalah menghitung koordinat UV per piksel di pixel shader, bukan interpolasi linear per vertex; jika dilakukan dengan benar, kutub pun diproses tanpa seam
Misalnya saat merender bola yang punya kutub “utara sejati”, jika dilihat dari samping memakai kutub rendering di sisi utara sejati, dan jika dilihat dari dekat utara sejati memakai rendering ekuator 0',0’
Jadi ingin meninjau lagi displacement mapping
Mungkin bukan pengganti untuk masalah yang ingin dipecahkan penulis, tetapi lebih sederhana dan cukup menarik
Sekitar 25 tahun lalu saya membuat visualizer musik bernama “Eclipse” untuk SoundJam; inputnya adalah array level pada rentang frekuensi terdengar dan kanal kiri/kanan
Tujuannya adalah tampilan seperti matahari yang sedang gerhana dengan lontaran korona, dan data musik menjadi “lontaran” itu
Frekuensi data menentukan di bagian mana sepanjang keliling piringan matahari ia muncul
Seiring waktu, lontaran menjauh dari matahari dan “mendingin” lalu menghilang menjadi hitam; sinyal kuat mulai dari putih, lalu saat melemah berubah menjadi kuning, oranye, merah, dan cokelat
Saya harus menyimpan array nilai data suara dalam circular buffer yang cukup besar untuk menampung waktu sampai lontaran menghilang menjadi hitam
Keseluruhan “Eclipse” dan tampilan lontarannya pada akhirnya adalah displacement map, dan saya sudah menghitung sebelumnya bitmap yang tiap nilai pikselnya menjadi offset ke buffer level suara
Piksel yang dekat permukaan matahari memiliki offset ke data yang baru masuk, sedangkan piksel di bagian luar memiliki offset ke ekor buffer yang akan segera kedaluwarsa
Karena harus memetakan lontaran melingkar/radial ke koordinat Kartesius, pembuatan nilai displacement membutuhkan sedikit matematika
Loop utamanya menerima nilai suara baru lalu menimpa nilai tertua; kemudian menelusuri displacement map per baris dan kolom, mengambil data suara terkait, memetakannya ke warna palet tetap, dan memasukkannya ke buffer tampilan
Tidak semeriah visualizer lain, tetapi punya keindahan yang tenang dan cukup baik merefleksikan data musik
Rasanya berbeda dari visualizer-visualizer belakangan yang tidak bisa diam bahkan saat diberi keheningan
Artikelnya keren, tetapi makin jauh menggulir ke bawah, makin banyak shader yang dimuat, sehingga browser bisa hampir macet kalau komputer Anda bukan yang sangat kuat
Menarik melihat betapa berbeda orang menafsirkan “realisme”
Dalam game dari masa ketika sumber daya jauh lebih terbatas, perbedaan ini sangat mencolok; beberapa game, hanya dengan grafis berpallet rendah yang kotak-kotak dan berpiksel, bisa terasa lebih imersif daripada game beranggaran besar masa kini
Karena itu rasanya kita jadi lebih aktif terlibat
Ini mengingatkan saya pada tulisan yang membahas pendekatan serupa: https://bgolus.medium.com/rendering-a-sphere-on-a-quad-13c92...
Apakah GPU atau pustaka 3D tidak punya fungsi yang menyapu tiap garis horizontal pada lingkaran, lalu memetakan X,Y tekstur ke posisi “3D” di atas bola?
Bola itu lingkaran dan algoritma menggambar lingkaran sederhana, sedangkan memutar dan memproyeksikan sejuta vertex untuk segitiga terasa seperti pemborosan sumber daya yang besar
Sapukan secara horizontal, turun satu baris, lanjut menggambar, maka semua baris bisa digambar
Saya masih ingat pernah melakukan hal semacam ini pada akhir 80-an untuk pramenghitung sesuatu seperti lensa pembesar di Second Reality
Seluruh pipeline rendering sudah dibuat untuk menangani interpolasi linear
Dengan satu program shader dan 1 segitiga saja, kita bisa menggambar bola dengan antialiasing sempurna
Jika dirender di GPU, terlepas dari kompleksitas yang terlihat, pendekatan dalam tulisan itu tampaknya cukup bagus
Ada kumpulan tulisan bagus tentang struktur rendering berbasis GPU: https://fgiesen.wordpress.com/2011/07/09/a-trip-through-the-...
Bagian 6 membahas rasterisasi
Planet impostor pixel shader 2D di atas latar belakang sangat membantu untuk pekerjaan dengan banyak planet, seperti semesta prosedural
Karena memori dan bandwidth GPU, planet berbasis spherical cube dibatasi ke 1 instance, sementara benda langit yang dekat digambar dengan teknik latar belakang
Di ruang angkasa, ada jarak di sekitar tengah antara dua benda langit ketika keduanya dirender sebagai impostor, lalu setelah itu benda langit terdekat ditangani dengan metode quadtree sferis
Tidak sempurna, tetapi ilusinya hampir sulit dicela
Selain itu, karena berada di atas bidang datar, lebih mudah menambahkan efek fisika ringan dari lensa kamera untuk galaksi atau benda langit yang jauh
Misalnya efek bulan terlihat lebih besar daripada sebenarnya saat terbit, atau galaksi jauh tampak melengkung oleh gravitasi
Saya suka halaman terkait planet gas ini: https://emildziewanowski.com/flowfields/
Icosphere bisa dibentangkan jauh lebih mulus, dan posisi vertex serta ukuran elemennya juga teratur
Pembentangannya tidak sepele, tetapi bisa dilakukan