Dari mana asal lubang pada mesh yang dihasilkan
Oleh Oleg Sidorkin, CTO dan Salah Satu Pendiri Cinevva

Saya menghasilkan seekor burung dengan TRELLIS.2, memasang rig, lalu begitu animasinya diputar, muncul lubang di bawah kedua sayapnya. Ada celah-celah terang yang memungkinkan kita melihat langsung menembus tubuhnya. Proses rigging menjadi tersangka pertama, dan memang memiliki masalah tersendiri, tetapi lubang-lubang itu sudah ada pada mesh sebelum satu pun bone menyentuhnya. Artikel ini membahas investigasi yang kemudian kami lakukan: apa sebenarnya lubang-lubang tersebut, sembilan hal yang gagal memperbaikinya beserta alasannya, dan satu hal yang berhasil. Saya sengaja menyertakan semua jalan buntunya. Kebanyakan merupakan solusi yang paling masuk akal untuk dicoba, dan masing-masing mengajarkan sesuatu yang kemudian menjadi dasar perbaikan akhir kami.
Langkah pertama: sepakati apa yang dimaksud dengan lubang
Pemeriksa mesh kami melaporkan nol boundary edge, dan pengujian paritas ray menyatakan bahwa permukaannya tertutup. Jadi, saya memberi tahu tim bahwa mesh tersebut bersih. Ternyata tidak, dan orang yang menandai celah-celah itu dengan warna merah memang benar untuk menyanggahnya. Sebuah mesh dapat tertutup tetapi masih memiliki terowongan. Satu-satunya ukuran yang dapat mendeteksinya adalah karakteristik Euler, V dikurangi E ditambah F, yang menghasilkan jumlah handle per shell. Burung tersebut memiliki 69 handle. Setiap dinding dalam hasil ekspor berlapis ganda, sehingga celah pada dinding luar yang terhubung ke rongga di belakangnya merupakan handle, bukan bukaan, dan metode apa pun yang mencari edge terbuka tidak akan pernah mendeteksinya.
Pelajaran kedua, yang lebih memalukan: saya sempat memeriksa piksel-piksel gelap untuk mencari lubang, karena lubang berwarna gelap, sampai seseorang menyuruh saya melihat gambarnya terlebih dahulu. Lubang-lubang itu terang. Yang terlihat adalah celah-celah tekstur hijau dari dinding seberang yang muncul melalui lipatan abu-abu. Algoritma yang belum diarahkan ke sasaran yang tepat tidak akan memberi tahu Anda apa pun, dan cara termurah untuk mengarahkannya adalah dengan melihat.
Langkah kedua: temukan tahap yang menciptakannya
TRELLIS.2 membangun mesh dalam tiga langkah. Decoder menghasilkan permukaan mentah pada resolusi 1024 pangkat tiga. Proses dual contouring narrow-band kemudian melakukan remeshing. Decimator menurunkannya menjadi seratus ribu face, lalu tekstur di-bake. Kami mengekspor geometri setelah setiap langkah dan menembakkan kumpulan ray yang sama melalui pangkal sayap pada setiap tahap.
Masalahnya bermula pada permukaan mentah. Permukaan itu berupa kumpulan sheet terbuka: 266.242 boundary loop, yang sebagian besar hanya berupa satu quad yang hilang, serta 65.371 edge non-manifold. Pada pangkal sayap, sepertiga ray pengujian menembus lurus. Proses pengisian lubang berikutnya nyaris tidak menutup apa pun, hanya 144 face dari lima juta. Remesher bekerja menggunakan unsigned distance field karena memang harus demikian. Tidak ada konsep bagian dalam atau luar yang bisa digunakannya, jadi remesher membungkus setiap sheet dengan shell offset tipis. Itulah sebabnya setiap dinding berlapis ganda, terpisah sekitar empat voxel, dan begitulah setiap garis berpori berubah menjadi lipatan yang memiliki celah. Decimation kemudian memperburuknya secara acak: mesh mentah yang sama, setelah di-decimate dua kali, menghasilkan 69 dan 107 terowongan.

Peta tersebut menjadi petunjuk nyata pertama. Porositasnya tidak muncul secara acak. Porositas berada di setiap sambungan antarbagian dan tidak ada di tempat lain.
Hal-hal yang tidak berhasil
Kami mencobanya kurang lebih berdasarkan urutan solusi yang paling jelas.
| percobaan | hasil |
|---|---|
| resolusi 1536, bukan 1024 | lebih buruk pada tiga dari lima model pengujian. Resolusi yang lebih tinggi menangkap lebih banyak kontak tipis, dan masing-masing menjadi sambungan baru |
| rangkaian cleanup bawaan | menghasilkan lebih banyak edge non-manifold, bukan lebih sedikit |
| mengisi setiap lubang kecil dengan MeshLib | 266 ribu loop terisi, tetapi pangkal sayap tidak berubah. Skin di sana memang tidak pernah dihasilkan, jadi tidak ada loop yang dapat diisi |
| signed remesh dari winding number | memecah mesh menjadi ribuan shell. Hasil mentah tidak memiliki interior yang dapat diberi sign |
| contouring band yang lebih lebar, 2 dan 3 voxel | jumlah terowongan keseluruhan berkurang, tetapi celahnya justru melebar, bukan menyempit. Terowongan kecil bergabung menjadi terowongan besar |
| morphological closing pada struktur voxel | memadatkan shell, lalu decoder menghasilkan permukaan di dalam setiap voxel interior, face bertambah 4x, 969 terowongan |
| voxel remesh global pada mesh akhir | genus 0 dan celah tertutup. Namun, setiap tepi bulu juga menjadi lebih halus. Langsung ditolak setelah dilihat, dan itu keputusan yang benar |
| menggeser ambang crossing decoder | edge terbuka turun dari 65 ribu menjadi 58 ribu. Hanya noise |
| melengkapi voxel tetangga yang hilang | 560 kasus di seluruh burung. Hanya noise |

Global closing layak dibahas lebih jauh karena itulah hal pertama yang mungkin dilakukan seorang pemodel: jalankan voxel remesh dengan ukuran sedikit lebih besar daripada celah. Secara topologi, cara ini berhasil. Namun, cara ini juga menghasilkan representasi model yang tidak jujur. Generator menghasilkan tepi bulu yang tajam, dan kami akan mengorbankannya demi menutup pangkal sayap. Dua dari sembilan percobaan berhasil menurunkan jumlah terowongan, dan keduanya langsung ditolak saat seseorang melihat burungnya, bukan hanya angkanya.
Membaca kode
Pada tahap itu, satu-satunya tindakan yang jujur adalah berhenti memperlakukan decoder sebagai kotak hitam dan membaca bagaimana hasilnya diubah menjadi mesh. TRELLIS.2 menggunakan representasi bernama O-Voxel, dan kode ekstraksinya singkat. Setiap voxel aktif menyimpan satu dual vertex dan tiga logit, satu untuk setiap grid edge yang keluar dari sudutnya, dan masing-masing berarti "permukaan melintasi edge ini." Logit di atas nol menghasilkan quad yang menghubungkan empat voxel di sekitar edge tersebut. Jika salah satu dari keempatnya hilang, quad itu dibuang tanpa peringatan. Hanya itu. Tidak ada sign field, occupancy, maupun konsep bagian dalam dan luar di mana pun dalam model.
Kami memodifikasi decoder agar mengekspor logit-logit tersebut sebelum penerapan ambang, pada burung yang sama, lalu menghitungnya.
Tetangga yang hilang: 560 dari 2,7 juta voxel. Logit yang berada di sekitar nol: 8.600 dari 8 juta. Keduanya bukan sumber masalah. Masalahnya terlihat saat kita memeriksa empat edge di sekeliling setiap grid face. Permukaan tertutup melintasi batas sebuah face dalam jumlah genap. Pada 126.542 face, atau 2,3% di antaranya, keempat flag tidak konsisten, dan setiap face berjumlah ganjil pasti merupakan edge terbuka atau non-manifold. Ketidakkonsistenan itu juga terjadi dengan keyakinan tinggi. Satu-satunya flag pada face dengan hitungan satu memiliki logit +10, sedangkan tetangganya memiliki logit -4. Biaya median untuk membalik satu flag dan memulihkan paritas adalah 2,3 logit. Ini bukan sekadar ambang yang sedikit meleset. Model yakin akan keberadaan permukaan yang tidak mungkin tertutup.

Kemudian kami mengukur garis itu sendiri, tepat pada titik permukaan yang terus diamati kamera tetap kami sepanjang minggu. Dalam jarak dua belas voxel dari titik tersebut, terdapat voxel aktif sebanyak 2,1 kali jumlah yang dibutuhkan oleh satu sheet datar. Sebagai perbandingan, pada bagian dada nilainya 1,2. Bagian bawah sayap dan tubuh merupakan dua skin yang berada dalam satu lapisan voxel. Representasi ini hanya memiliki satu vertex per voxel, sehingga tidak dapat menampung keduanya. Selama pelatihan, ground truth untuk voxel semacam itu merupakan rata-rata least-squares dari kedua skin dengan flag "any crossing", yang juga tidak konsisten. Jadi, model telah belajar dengan akurat untuk menghasilkan bentuk seperti sisir di sana. Remesher membungkus bentuk seperti sisir itu menjadi slab, dan slab tersebut memiliki terowongan.
Lebar garisnya dapat dijelaskan oleh geometri: kira-kira satu voxel dibagi tangen sudut kontak. Dua permukaan yang bertemu pada sudut sembilan puluh derajat menghasilkan lekukan selebar satu voxel dan tidak menimbulkan masalah. Sayap yang menempel datar pada tubuh, bulu yang menumpuk di atas bulu lain, dan jari kaki yang menyentuh tanah—semuanya menghasilkan garis selebar sepuluh atau lima belas voxel. Itulah penjelasan untuk peta porositas tersebut.
Saya memang mencoba perbaikan yang secara teori paling tepat, yaitu menyimpulkan sign dalam/luar untuk setiap sudut yang paling mampu menjelaskan flag, lalu menurunkan ulang flag tersebut. Cara ini tidak mungkin berhasil. Sign field hanya ada pada shell selebar satu voxel yang dihasilkan decoder, dan setiap perluasan ke luar shell tersebut tumbuh tak terkendali. Dua belas putaran proses itu menggandakan jumlah voxel dengan data sampah.
Yang akan dilakukan seorang pemodel
Seorang pemodel tidak akan pernah membiarkan dua skin hanya berjarak setipis rambut. Mereka akan melakukan union antara sayap dan tubuh, lalu membiarkannya membentuk sebuah lekukan. Global closing melakukan hal itu di seluruh bagian, dan itulah masalahnya. Jadi, lakukan hanya di tempat kedua skin tersebut berada.
Logika detektornya hanya terdiri dari satu gagasan. Hitung jumlah tetangga aktif setiap voxel dalam radius empat voxel. Pada satu sheet, jumlah tersebut sama di mana-mana dan setara dengan mediannya. Di tempat dua sheet berbagi lapisan yang sama, jumlahnya kira-kira dua kali lipat. Tandai semua yang berada di atas 1,5 kali median, perluas kumpulan yang ditandai sebanyak satu voxel, lalu tempatkan kubus satuan pada masing-masing titik. Ruang di antara kedua skin menjadi padat, proses contouring hanya melihat satu dinding, dan bagian lain pada model tidak tersentuh.

Kami terlebih dahulu mengujinya pada sebuah potongan, menggunakan MeshLib sebagai pengganti sementara untuk GPU contouring. Lima puluh tiga shell dan sembilan belas handle berubah menjadi dua shell dan lima handle. Kemudian kami memasukkannya ke pipeline sebenarnya, di antara decoder dan ekspor, lalu menjalankan burung yang sama melalui remesher dan proses bake tekstur yang sebenarnya.

| terowongan | edge non-manifold | lipatan pangkal sayap | lubang di punggung | |
|---|---|---|---|---|
| sebagaimana adanya | 79,5 | 318 | ada | ada |
| pengisian, radius 3 | 84,5 | 97 | hilang | hilang |
| pengisian, radius 4 | 72 | 38 | hilang | hilang |
Lipatannya hilang. Lubang kedua di punggung burung, pada sambungan yang sama tetapi terlihat dari belakang dan bahkan tidak sedang saya telusuri, juga hilang. Bulu-bulu tetap terpisah, jari-jari kaki tetap terpisah, dan tekstur dapat di-bake secara normal karena kubus-kubus tersebut berada di dalam shell, tempat volume atribut sudah memiliki data. Proses ini menambah enam detik pada pekerjaan berdurasi empat menit. Sekarang fitur tersebut aktif secara default dengan radius empat.

Yang tidak diperbaiki oleh metode ini, dan pelajaran yang dapat diambil
Jumlah terowongan hampir tidak berubah, dan saya ingin menjelaskan alasannya secara jujur. Terowongan yang tersisa berada di ujung bulu dan ujung jari kaki, tempat bagian tipis berakhir dan dinding berlapis ganda menjepit hingga tertutup. Ukurannya kecil, tidak terlihat, dan disebabkan oleh mekanisme yang berbeda. Pengisian ini menargetkan kontak antarbagian, yaitu lokasi munculnya lubang-lubang yang terlihat. Perbaikan yang sebenarnya harus dilakukan di hulu. Dekoder yang memprediksi tanda per sudut alih-alih flag per rusuk akan konsisten secara paritas sejak awal, seperti cara kerja generasi sebelumnya dari model ini dan alasan mesh yang dihasilkannya kedap air. Itu merupakan perubahan pada pelatihan, bukan sesuatu yang bisa kami tambal saat inferensi. Sampai saat itu, ada dua hal praktis yang dapat disimpulkan dari rumus garis tersebut. Jika Anda membuat karakter, pose dengan anggota tubuh yang tidak menempel pada badan akan menghasilkan mesh yang lebih bersih dibandingkan pose dengan anggota tubuh yang menempel rata pada badan. Dan jika Anda mengembangkan sesuatu di atas generator seperti ini, ukur mesh Anda menggunakan karakteristik Euler dan periksa hasilnya dengan mata kepala sendiri sebelum memercayai angka. Angkanya menyatakan bersih. Ternyata salah dua kali.
Dekoder berinstrumen, dump setiap tahap, analisis paritas, dan proses penambalan kini semuanya sudah ada dalam pipeline kami, sehingga model berikutnya yang menghasilkan lubang bisa ditangani dengan cara yang sama dalam hitungan menit, bukan seminggu.