Optimalisasi model 3D untuk adegan game

Artikel ini melengkapi serangkaian publikasi dari studio Krasnodar Plarium tentang berbagai aspek bekerja dengan model 3D di Unity. Artikel sebelumnya: "Fitur bekerja dengan Mesh in Unity" , "Unity: editing prosedural Mesh" , "Impor model 3D ke dalam Unity and pitfalls" , "Lekukan piksel dalam pemindaian tekstur" .

Hampir 2 tahun yang lalu kami menulis sebuah artikel di mana kami berbicara tentang opsi untuk mengoptimalkan geometri 3D dalam sebuah adegan dengan batasan sudut kamera dan rotasi objek yang sesuai. Tidak banyak yang mengalir sejak saat itu, tetapi kesempatan untuk meningkatkan solusi, mempertimbangkan berbagai pendekatan dan memata-matai orang lain menghantui pikiran pengembang. Pada artikel ini, kami akan menjelaskan versi peningkatan algoritma berdasarkan pada poligon lukisan, serta berbicara tentang mencoba mentransfer bagian dari pekerjaan ini ke paket 3D.



Pangkas di tempat kejadian


Kami telah mempertimbangkan prinsip dasar dari algoritma ini dalam artikel di atas: kami memadamkan semua efek dan objek transparan, melukis poligon yang tidak diproses dalam satu warna, dan yang diproses dalam berbagai warna, membuat, dan mengekstrak hasilnya. Dalam versi lama, mereka melukis sehingga semua hitam menjadi berlebihan, dan hanya satu segitiga yang ditandai dengan warna merah.

Dalam komentar untuk artikel itu, salah satu pembaca menunjukkan kemungkinan mengoptimalkan algoritma dengan membangun korespondensi satu-ke-satu antara set poligon dan beberapa set angka unik. Maka dimungkinkan untuk memproses lebih dari satu segitiga dengan cara yang sama. Pertimbangkan opsi ini.

Dalam hal ini, serta terakhir kali, beberapa pra-pelatihan seharusnya dihubungkan dengan menonaktifkan semua objek bersiul di atas panggung dan objek yang dijamin tidak akan mempengaruhi visibilitas model target. Tampilan kamera diproses hampir secara independen, mereka hanya dihubungkan oleh buffer indeks umum poligon yang terlihat. Selain itu, preprocessing geometri dilakukan untuk setiap sudut, selama poligon diputar yang dikembalikan ke kamera ( backface ). Ini dilakukan karena pada tahap algoritma tertentu mesh sementara dibuat dengan jumlah simpul yang jauh lebih besar daripada yang asli. Angka ini dapat dengan mudah melebihi ambang batas 65.535, yang akan membutuhkan gerakan tambahan dalam perhitungan dan akan menyebabkan penurunan kinerja. Bagaimanapun, poligon ini akan dihapus, karena warnanya tidak akan jatuh ke dalam bingkai. Namun, karena fakta bahwa setiap segitiga berpotensi menimbulkan tiga simpul sampah, menghilangkan poligon yang tidak perlu terlebih dahulu memfasilitasi tahap utama dari algoritma dan mengurangi biaya memori.

Biarlah ada beberapa model 3D, geometri yang diwakili oleh mesh. Untuk melukis poligon tertentu dalam warna yang unik, Anda harus melukis semua simpulnya dalam warna ini. Karena dalam kasus umum satu simpul dapat menjadi bagian dari poligon yang berbeda, tidak mungkin untuk menyelesaikan masalah secara langsung. Tidak masalah bagaimana kita mewarnai vertex apa pun, saat render, warnanya akan merayapi semua segitiga yang memilikinya, sesuai dengan algoritma interpolasi di sisi kartu video.


Contoh interpolasi warna saat menampilkan poligon dengan simpul umum

Oleh karena itu, perlu untuk membuat mesh dibagi menjadi beberapa poligon independen yang terpisah, sambil menjaga topologi dan geometri objek. Dictum factum. Kami mengubah array segitiga dan simpul sedemikian rupa sehingga untuk setiap segitiga 3 simpul unik akan dibuat, posisi yang ditentukan oleh simpul yang sesuai dari mesh asli. Perlu dicatat bahwa dalam kasus umum, mesh seperti itu akan memiliki jumlah simpul yang jauh lebih besar dibandingkan dengan yang asli. Dan jika angka ini melebihi 65.535, maka saat membuat mesh, Anda harus menentukan format pengindeksan yang sesuai.

Konversi mesh asli ke mesh dengan simpul unik untuk setiap poligon
private static Mesh GetNotSmoothMesh(Mesh origin) { var oVertices = origin.vertices; var oTriangles = origin.triangles; var vertices = new Vector3[oTriangles.Length]; var triangles = new int[oTriangles.Length]; for (int i = 0; i < triangles.Length; i++) { vertices[i] = oVertices[oTriangles[i]]; triangles[i] = i; } return new Mesh() { indexFormat = vertices.Length > 65535 ? IndexFormat.UInt32 : IndexFormat.UInt16, vertices = vertices, triangles = triangles }; } 


Sekarang Anda perlu menentukan poligon mesh ini sehingga setelah operasi rendering adalah mungkin untuk menentukan mana yang masuk layar. Seperti yang telah disebutkan, kami menghasilkan warna unik untuk poligon dan melukis masing-masing tiga simpul dalam warna yang sesuai. Hasilnya adalah mesh baru, yang kami sebut Byte-Colored Mesh .


Jaring berwarna byte

Pewarnaan mesh di mana setiap titik hanya satu poligon
 private static void ColorizePolygons(Mesh mesh) { var pColors = ColorsOfPolygons(mesh); var colors = new Color[mesh.vertexCount]; for (int i = 0; i < colors.Length; i++) { colors[i] = pColors[i / 3]; } mesh.colors = colors; } private static Color[] GetColorsOfPolygons(Mesh mesh) { var colors = new Color[mesh.triangles.Length / 3]; for (int i = 0; i < colors.Length; i++) { var color = Int2Color(i);//         ,     Color2Int //      ,       int     Color32 colors[i] = color; } return colors; } 


Ingat pewarnaannya. Sudah waktunya untuk membuat. Kami melakukan rendering 3D untuk semua sudut kamera dan, saat memproses masing-masing, mengisi kembali buffer indeks poligon unik yang warnanya terdeteksi dalam bingkai. Untuk saat perhitungan untuk kamera, Anda perlu mematikan anti-aliasing untuk menghindari tampilan warna baru karena interpolasi piksel tetangga.

Membaca dan menyimpan warna dari sudut kamera yang berbeda
 // CameraTransform β€”         //      SetCameraTransform private static HashSet<Color> GetVisibleColors(Camera camera, CameraTransform[] cameraTransforms) { var renderTexture = new RenderTexture(1920, 1080, 24);//for example var rtRect = new Rect(0, 0, renderTexture.width, renderTexture.height); var frame = new Texture2D(renderTexture.width, renderTexture.height, TextureFormat.RGB24, false);//    -  RGB24   ,    RGBA32 var visibleColorsSet = new HashSet<Color>(); foreach (var cameraTransform in cameraTransforms) { SetCameraTransform(camera, cameraTransform); CreateScreenShot(camera, renderTexture, frame, rtRect); visibleColorsSet.UnionWith(GetTextureColors(frame)); } return visibleColorsSet; } public static void SetCameraTransform(Camera camera, CameraTransform camTransform) { camera.transform.position = camTransform.Position; camera.transform.rotation = camTransform.Rotation; camera.fieldOfView = camTransform.FieldOfView; camera.orthographic = camTransform.IsOrthographic; camera.nearClipPlane = camTransform.NearClippingPlane; camera.farClipPlane = camTransform.FarClippingPlane; } private static HashSet<Color> GetTextureColors(Texture2D texture) { return new HashSet<Color>(texture.GetPixels()); } private static void CreateScreenShot(Camera cam, RenderTexture renderTexture, Texture2D screenShot, Rect renderTextureRect) { cam.targetTexture = renderTexture; cam.Render(); RenderTexture.active = cam.targetTexture; screenShot.ReadPixels(renderTextureRect, 0, 0); RenderTexture.active = null; cam.targetTexture = null; } } 


Perlu disebutkan bahwa karena diskritisasi, beberapa segitiga mungkin tidak ditampilkan karena ukuran proyeksi mereka yang kecil ke layar, dan bukan karena sesuatu tumpang tindih atau mereka diputar di sisi yang salah. Kami telah mengimplementasikan versi algoritma yang konservatif. Dalam hal ini, AABB dari proyeksi segitiga pada layar dihitung, dan jika setidaknya salah satu sisinya kurang dari sisi texel dalam gambar, maka poligon tersebut ditandai sebagai terlihat. Pendekatan ini melindungi terhadap artefak saat menjalankan algoritme dengan resolusi yang kurang dari resolusi layar perangkat target. Jika Anda mengabaikan poligon kecil, hasilnya juga akan dapat diterima asalkan resolusi tekstur render yang digunakan lebih tinggi daripada resolusi layar perangkat yang dimaksud.

Kami menerapkan algoritma pemangkasan ini di Unity dan menggunakannya untuk mengoptimalkan objek statis yang modelnya ditemukan di lokasi lebih dari sekali di berbagai posisi. Ini terutama pemandangan: batu, pohon, patung, vas, dll yang merujuk pada cetakan yang sering digunakan. Kami ingin mengoptimalkan objek seperti itu sebelumnya, pada tahap pembuatan dalam paket 3D, tetapi siapa yang tahu di mana pose fantasmagorik yang diinginkan oleh perancang level untuk meletakkan lilin favoritnya.

Memotong set objek dari jenis yang sama dengan alat seperti itu mengurangi ukuran adegan, karena selama batching statis, data mesh prefab umum tetap disalin pada tahap build sebanyak kali objek yang ditarik aktif dengan mesh ini diwakili dalam adegan. Metode kami juga membebaskan ruang di atlas tekstur, seperti lightmap . Kami menggunakan ruang yang disimpan untuk meningkatkan detail bagian-bagian dari model yang selamat dari pembersihan.

Pangkas 3D


Namun demikian, lebih baik jika artis dapat memotong semua yang tidak perlu dalam editornya, sehingga mengurangi jumlah tahapan persiapan konten. Ini dibenarkan ketika model digunakan dalam adegan dengan hanya satu rotasi yang telah ditentukan relatif terhadap kamera. Sebelumnya, objek-objek yang akan secara tepat dialihkan ke pengguna di satu sisi sering disederhanakan secara manual sebelum diintegrasikan ke dalam proyek. Penting untuk dicatat bahwa menerapkan penyederhanaan seperti itu secara pemrograman di Unity jauh lebih sulit karena kompleksitas pengemasan pengembangan UV , sehingga otomatisasi pada tahap paket 3D kadang-kadang membuat hidup lebih mudah bagi seorang seniman.

Salah satu alat untuk bekerja dengan model 3D di perusahaan kami adalah Blender . Kami naik ke sana. Tampaknya perangkat lunak "dewasa" seperti itu, seperti Blender , harus memiliki fungsi serupa. Namun, ternyata dia tidak boleh. Saya harus melihat sepeda sendiri.

Gagasan pertama adalah menggunakan alat seleksi yang familier - pada dasarnya ulangi bagian dari pekerjaan manual artis untuk satu sudut kamera: pilih poligon yang terlihat, seleksi terbalik, hapus. Rencananya adalah ini: gerakkan kamera, tentukan proyeksi AABB dari model di setiap posisi, lalu minta hasil pemilihan poligon pada area yang sesuai dengan AABB , dapatkan penyatuan kumpulan poligon dari tampilan saat ini dengan yang sebelumnya dan hapus poligon yang tidak dipilih di akhir.

Namun, selama implementasi skrip, kelemahan signifikan ditemukan dalam hal tugas. Alat seleksi di Blender (pilih persegi panjang, pilih lingkaran) kehilangan keakuratan dengan meningkatnya jumlah elemen yang dipilih per satuan luas layar (beberapa poligon tetap tidak dipilih), yang membuat penggunaannya dalam alat otomatisasi kami menjadi mustahil. Fakta menarik: dalam 3ds yang sama Max masalah seperti itu tidak diamati.


Menyoroti dari jauh di Blender


Hasil seleksi

Upaya berikutnya ditujukan untuk memecahkan masalah di dahi: kami mengirim sinar dari kamera melalui setiap piksel viewport dan melihat poligon mana yang pertama kali berpotongan dengan setidaknya satu sinar. Kami tidak berharap untuk hasil yang akurat dengan pendekatan ini, tetapi patut dicoba. Hasilnya jelas: produktivitas sangat rendah saat memproses pada CPU atau lubang yang sama dengan sejumlah kecil sinar.

Namun demikian, kami membuat pijakan untuk penerapan pendekatan yang lebih maju. Idenya adalah untuk memilih sejumlah titik acak pada setiap poligon dan kemudian mengirim sinar dari kamera ke arah mereka. Pendekatan ini bekerja dengan baik, tetapi kami memiliki beberapa kasus garis batas: poligon juga terputus, di mana sudut antara balok dan normalnya kira-kira sama dengan Ο€ / 2. Dengan demikian, ketika kamera memperbesar karena distorsi perspektif, area cut-out bisa terbuka.

Metode ini, menurut pendapat para seniman, terlalu agresif, jadi kami memutuskan untuk fokus hanya memotong bagian belakang .

Kesimpulan


Bukan rahasia lagi bahwa sikap hati-hati terhadap sumber daya perangkat saat membuat game adalah faktor paling penting yang mempengaruhi kualitas produk akhir. Ini terutama berlaku untuk platform seluler, yang ingin menggunakan RAM secara aktif. Mengurangi jumlah poligon memungkinkan Anda mengisi ruang atlas tekstur secara lebih efektif dan sedikit mengurangi beban komputasi.

Juga, jangan lupa tentang biaya jam kerja dan biaya kesalahan saat menggunakan alat yang dijelaskan di atas, dan sejenisnya. Pendekatan yang diusulkan mengasumsikan pipeline berfungsi dengan baik untuk pekerjaan departemen seni, terutama karyawan yang terlibat dalam integrasi model ke dalam proyek.

Dengan demikian, setelah kondisi dan alat dibahas dalam artikel ini, kami mematuhi aturan berikut. Jika diasumsikan bahwa model yang dibuat akan selalu diputar oleh satu sisi ke pengguna, dan juga jika dari sudut ini tumpang tindih dari beberapa bagian model oleh orang lain cukup kecil, maka artis menggunakan alat trim backface kami di editor 3D, memeriksa kebenaran dan hasil dengan kemasan pengembangan UV. . Jika model sering digunakan dalam posisi yang berbeda atau memiliki geometri yang lebih kompleks, maka setelah mengimpor ke dalam proyek, kami menjalankan algoritma yang dijelaskan di bagian pertama artikel, memproses semua objek statis dalam adegan dengannya.

Source: https://habr.com/ru/post/id484792/


All Articles