Setelah memahami dasar-dasar proses rendering mesh, Anda dapat menerapkan berbagai teknik untuk mengoptimalkan kecepatan rendering.
Pendahuluan
Berapa banyak poligon yang bisa saya gunakan? Ini adalah pertanyaan yang sangat umum yang ditanyakan seniman saat membuat model untuk render waktu-nyata. Pertanyaan ini sulit dijawab, karena bukan hanya soal angka.
Saya memulai karir saya sebagai seniman 3D di era PlayStation pertama, dan kemudian menjadi seorang programmer grafis. Saya ingin membaca artikel ini sebelum saya mulai membuat model 3D untuk game. Fondasi fundamental yang dipertimbangkan di dalamnya bermanfaat bagi banyak seniman. Meskipun sebagian besar informasi dalam artikel ini tidak akan secara signifikan mempengaruhi produktivitas pekerjaan harian Anda, ini akan memberi Anda pemahaman dasar tentang bagaimana unit pemrosesan grafis (GPU) membuat jerat yang Anda buat.
Kecepatan rendering biasanya tergantung pada jumlah poligon di mesh. Namun, meskipun jumlah poligon sering berkorelasi dengan frame rate per detik (FPS), Anda mungkin menemukan bahwa bahkan setelah mengurangi jumlah poligon, mesh masih merender perlahan. Tetapi dengan memahami bagaimana jerat dirender secara umum, Anda dapat menerapkan serangkaian teknik untuk meningkatkan kecepatan rendering.
Bagaimana data poligon disajikan
Untuk memahami cara GPU menggambar poligon, Anda harus terlebih dahulu mempertimbangkan struktur data yang digunakan untuk menggambarkan poligon. Poligon terdiri dari sekumpulan titik yang disebut simpul dan tautan. Verteks sering disimpan sebagai array nilai, misalnya, seperti Gambar 1.
Gambar 1. Array nilai poligon sederhana.Dalam hal ini, empat simpul dalam tiga dimensi (x, y dan z) memberi kita 12 nilai. Untuk membuat poligon, array nilai kedua menggambarkan simpul itu sendiri, seperti yang ditunjukkan pada Gambar 2.
Gambar 2. Array tautan ke simpul.Titik-titik ini terhubung bersama membentuk dua poligon. Perhatikan bahwa dua segitiga, masing-masing dengan tiga sudut, dapat dijelaskan oleh empat simpul, karena simpul 1 dan 2 digunakan di kedua segitiga. Agar GPU memproses data ini, diasumsikan bahwa setiap poligon berbentuk segitiga. GPU mengharapkan Anda untuk bekerja dengan segitiga karena dirancang khusus untuk menggambarnya. Jika Anda perlu menggambar poligon dengan jumlah simpul yang berbeda, maka Anda memerlukan aplikasi yang membaginya menjadi segitiga sebelum dirender ke GPU. Misalnya, jika Anda membuat kubus enam poligon, masing-masing memiliki empat sisi, maka ini tidak lebih efektif daripada membuat kubus 12 poligon yang terdiri dari tiga sisi; segitiga inilah yang akan digambar GPU. Ingat aturannya: Anda tidak perlu menghitung poligon, tetapi segitiga.
Data vertex yang digunakan dalam contoh sebelumnya adalah tiga dimensi, tetapi ini tidak perlu. Dua dimensi mungkin cukup untuk Anda, tetapi sering kali Anda perlu menyimpan data lain, misalnya, koordinat UV untuk tekstur dan normal untuk penerangan.
Gambar poligon
Saat merender poligon, GPU terlebih dahulu menentukan di mana menggambar poligon. Untuk melakukan ini, ia menghitung posisi di layar di mana tiga simpul seharusnya. Operasi ini disebut transform. Perhitungan ini dalam GPU dilakukan oleh program kecil yang disebut vertex shader.
Vertex shader sering melakukan operasi jenis lain, seperti memproses animasi. Setelah menghitung posisi ketiga simpul poligon, GPU menghitung piksel mana yang ada dalam segitiga ini, dan kemudian mulai mengisi piksel ini dengan program kecil lain yang disebut "fragment shader" (fragment shader). Sebuah shader fragmen biasanya dieksekusi satu kali per piksel. Namun, dalam beberapa kasus yang jarang, ini dapat dilakukan beberapa kali per piksel, misalnya, untuk meningkatkan anti-aliasing. Fragmen shaders sering disebut pixel shaders, karena dalam banyak kasus fragmen berhubungan dengan piksel (lihat Gambar 3).
Gambar 3. Satu poligon digambar di layar.Gambar 4 menunjukkan urutan tindakan yang dilakukan oleh GPU saat merender poligon.
Gambar 4. Urutan GPU yang membuat poligon.Jika Anda membagi segitiga menjadi dua dan menggambar kedua segitiga (lihat Gambar 5), maka prosedur akan sesuai dengan Gambar 6.
Gambar 5. Pembagian poligon menjadi dua.Gambar 6. Prosedur GPU menggambar dua poligon.Dalam hal ini, diperlukan dua kali lebih banyak transformasi dan persiapan, tetapi karena jumlah piksel tetap sama, operasi tidak perlu merasterisasi piksel tambahan. Ini menunjukkan bahwa menggandakan jumlah poligon tidak harus menggandakan waktu render.
Menggunakan vertex cache
Jika Anda melihat dua poligon dari contoh sebelumnya, Anda dapat melihat bahwa keduanya memiliki dua simpul umum. Dapat diasumsikan bahwa simpul-simpul ini harus dihitung dua kali, tetapi suatu mekanisme yang disebut vertex cache memungkinkan Anda untuk menggunakan kembali hasil perhitungan. Hasil perhitungan shader vertex untuk digunakan kembali disimpan dalam cache - area kecil memori yang berisi beberapa simpul terakhir. Prosedur untuk menggambar dua poligon menggunakan vertex cache ditunjukkan pada Gambar 7.
Gambar 7. Menggambar dua poligon menggunakan vertex cache.Berkat vertex cache, Anda dapat menggambar dua poligon hampir secepat satu jika mereka memiliki simpul umum.
Kami berurusan dengan parameter dari simpul
Agar vertex dapat digunakan kembali, harus tidak berubah setiap kali digunakan. Tentu saja, posisinya harus tetap sama, tetapi parameter lain juga tidak boleh berubah. Parameter yang diteruskan ke atas tergantung pada mesin yang digunakan. Berikut adalah dua parameter umum:
Ketika UV diterapkan pada objek 3D, setiap jahitan yang dibuat akan berarti bahwa simpul di sepanjang jahitan tidak dapat dibagi. Karena itu, dalam kasus umum, jahitan harus dihindari (lihat Gambar 8).
Gambar 8. Tekstur UV suture.Untuk penerangan yang tepat pada permukaan, setiap titik biasanya menyimpan normal - vektor diarahkan dari permukaan. Karena kenyataan bahwa semua poligon dengan simpul umum didefinisikan oleh satu normal, bentuknya tampak halus. Ini disebut shading halus. Jika setiap segitiga memiliki normalnya sendiri, maka tepi antara poligon menjadi jelas, dan permukaannya tampak rata. Oleh karena itu, ini disebut flat shaded. Gambar 9 menunjukkan dua jerat yang identik, satu dengan naungan halus dan yang lainnya dengan naungan datar.
Gambar 9. Perbandingan halus dengan naungan datar.Geometri teduh halus ini terdiri dari 18 segitiga dan memiliki 16 simpul umum. Naungan datar dari 18 segitiga memerlukan 54 (18 x 3) simpul, karena tidak ada satu pun dari simpul yang dibagi. Bahkan jika dua jerat memiliki jumlah poligon yang sama, kecepatan renderingnya akan tetap berbeda.
Pentingnya bentuk
GPU bekerja cepat terutama karena mereka dapat melakukan banyak operasi secara paralel. Materi pemasaran GPU sering berfokus pada jumlah pipa yang menentukan berapa banyak GPU dapat bekerja pada saat yang sama. Ketika GPU menggambar poligon, itu memberi tugas banyak pipa untuk mengisi kuadrat piksel. Ini biasanya berukuran delapan kali delapan piksel. GPU terus melakukan ini sampai semua piksel penuh. Jelas, segitiga bukan kotak, jadi beberapa piksel persegi akan berada di dalam segitiga, dan yang lainnya di luar. Peralatan bekerja dengan semua piksel dalam kotak, bahkan yang ada di luar segitiga. Setelah menghitung semua simpul di bujur sangkar, peralatan membuang piksel di luar segitiga.
Gambar 10 menunjukkan segitiga, yang membutuhkan tiga kotak (ubin) untuk menggambar. Sebagian besar piksel yang dihitung (cyan) digunakan, dan yang ditunjukkan dengan warna merah melampaui batas segitiga dan akan dibuang.
Gambar 10. Tiga ubin untuk menggambar segitiga.Poligon pada Gambar 11 dengan jumlah piksel yang persis sama, tetapi membentang, membutuhkan lebih banyak ubin untuk diisi; Sebagian besar hasil di setiap ubin (area merah) akan dibuang.
Gambar 11. Mengisi ubin dalam gambar yang diregangkan.Jumlah piksel yang diberikan hanyalah salah satu faktor. Bentuk poligon juga penting. Untuk meningkatkan efisiensi, coba hindari poligon yang panjang dan sempit dan berikan preferensi untuk segitiga dengan panjang sisi yang kira-kira sama, yang sudutnya mendekati 60 derajat. Dua permukaan datar pada Gambar 12 adalah triangulasi dalam dua cara yang berbeda, tetapi mereka terlihat sama ketika dirender.
Gambar 12. Permukaan triangulasi dalam dua cara berbeda.Mereka memiliki jumlah poligon dan piksel yang persis sama, tetapi karena permukaan kiri memiliki poligon yang lebih panjang dan lebih sempit dari kanan, renderingnya akan lebih lambat.
Menggambar ulang
Untuk menggambar bintang berujung enam, Anda dapat membuat jaring 10 poligon atau menggambar bentuk yang sama hanya dari dua poligon, seperti yang ditunjukkan pada Gambar 13.
Gambar 13. Dua cara berbeda dalam memberikan bintang berujung enam.Anda dapat memutuskan bahwa menggambar dua poligon lebih cepat dari 10. Namun, dalam hal ini, kemungkinan besar ini salah, karena piksel di tengah bintang akan ditarik dua kali. Fenomena ini disebut overdraw. Intinya, itu berarti piksel digambar ulang lebih dari sekali. Penggambaran ulang terjadi secara alami sepanjang seluruh proses rendering. Misalnya, jika sebagian karakter disembunyikan oleh kolom, maka karakter tersebut akan digambar secara keseluruhan, meskipun faktanya kolom tersebut tumpang tindih dengan sebagian karakter. Beberapa mesin menggunakan algoritma yang kompleks untuk menghindari render objek yang tidak terlihat dalam gambar akhir, tetapi ini adalah tugas yang sulit. CPU seringkali lebih sulit untuk mengetahui apa yang tidak perlu dirender daripada GPU untuk menggambarnya.
Sebagai seorang seniman, Anda harus menerima kenyataan bahwa Anda tidak dapat menghilangkan pengecatan ulang, tetapi praktik yang baik untuk menghilangkan permukaan yang tidak dapat dilihat. Jika Anda berkolaborasi dengan tim pengembang, maka minta untuk menambahkan mode debugging ke mesin permainan, di mana semuanya menjadi transparan. Ini akan membuatnya lebih mudah untuk menemukan poligon tersembunyi yang dapat dihapus.
Menerapkan laci di lantai
Gambar 14 menunjukkan adegan sederhana: sebuah kotak berdiri di lantai. Lantai hanya terdiri dari dua segitiga, dan kotak itu terdiri dari 10 segitiga. Gambar ulang dalam adegan ini ditampilkan dalam warna merah.
Gambar 14. Laci berdiri di lantai.Dalam hal ini, GPU akan menarik bagian lantai ke lantai dengan laci, meskipun tidak akan terlihat. Jika sebaliknya kita membuat lubang di lantai di bawah kotak, kita akan menerima lebih banyak poligon, tetapi lebih sedikit menggambar ulang, seperti yang dapat dilihat dari Gambar 15.
Gambar 15. Sebuah lubang di bawah laci untuk menghindari menggambar ulang.Dalam kasus seperti itu, semuanya tergantung pada pilihan Anda. Kadang-kadang ada baiknya mengurangi jumlah poligon, mendapatkan redrawing sebagai imbalan. Dalam situasi lain, ada baiknya menambahkan poligon untuk menghindari menggambar ulang. Contoh lain: dua gambar yang ditunjukkan di bawah ini adalah jerat permukaan yang tampak sama dengan titik mencuat dari itu. Pada mesh pertama (Gambar 16), ujungnya terletak di permukaan.
Gambar 16. Tip terletak di permukaan.Pada mesh kedua pada Gambar 17, lubang dipotong di permukaan di bawah ujung untuk mengurangi jumlah menggambar ulang.
Gambar 17. Lubang dipotong di bawah ujung.Dalam hal ini, banyak poligon ditambahkan untuk memotong lubang, beberapa di antaranya memiliki bentuk yang sempit. Selain itu, permukaan gambar ulang, yang kami singkirkan, tidak terlalu besar, jadi dalam hal ini teknik ini tidak efektif.
Bayangkan Anda membuat model rumah yang berdiri di tanah. Untuk membuatnya, Anda bisa membiarkan bumi tidak berubah, atau membuat lubang di tanah di bawah rumah. Redrawing lebih ketika lubang tidak dipotong di bawah rumah. Namun, pilihan tergantung pada geometri dan sudut pandang dari mana pemain akan melihat rumah. Jika Anda menggambar bumi di bawah dasar rumah, ini akan menghasilkan gambar ulang yang besar jika Anda masuk ke dalam rumah dan melihat ke bawah. Namun, perbedaannya tidak akan terlalu besar jika Anda melihat rumah dari pesawat. Dalam hal ini, yang terbaik adalah memiliki mode debugging di mesin permainan yang membuat permukaan transparan sehingga Anda dapat melihat apa yang digambar di bawah permukaan terlihat oleh pemain.
Ketika buffer-Z memiliki konflik-Z
Ketika GPU menggambar dua poligon yang tumpang tindih, bagaimana cara menentukan mana yang di atas yang lain? Peneliti grafik komputer pertama menghabiskan banyak waktu untuk meneliti masalah ini. Ed Catmell (yang kemudian menjadi presiden Pixar dan Walt Disney Animation Studios) menulis sebuah artikel yang menguraikan sepuluh pendekatan berbeda untuk tugas ini. Di salah satu bagian artikel, ia mencatat bahwa solusi untuk masalah ini akan sepele jika komputer memiliki cukup memori untuk menyimpan satu nilai kedalaman per piksel. Pada 1970-an dan 1980-an, itu adalah memori yang sangat besar. Namun, saat ini sebagian besar GPU bekerja seperti ini: sistem seperti ini disebut Z-buffer.
Z-buffer (juga dikenal sebagai buffer kedalaman) berfungsi sebagai berikut: dengan setiap piksel nilai kedalamannya dikaitkan. Saat peralatan menggambar objek, ia menghitung seberapa jauh piksel diambil dari kamera. Kemudian memeriksa nilai kedalaman piksel yang ada. Jika lebih jauh dari kamera daripada piksel baru, maka piksel baru diambil. Jika piksel yang ada lebih dekat ke kamera daripada yang baru, maka piksel baru tidak ditarik. Pendekatan ini memecahkan banyak masalah dan berfungsi bahkan jika poligon bersilangan.
Gambar 18. Poligon berpotongan diproses oleh buffer kedalaman.Namun, Z-buffer tidak memiliki akurasi yang tak terbatas. Jika dua permukaan hampir pada jarak yang sama dari kamera, maka ini membingungkan GPU dan dapat secara acak memilih salah satu permukaan, seperti yang ditunjukkan pada Gambar 19.
Gambar 19. Permukaan dengan kedalaman yang sama memiliki masalah tampilan.Ini disebut Z-fighting dan terlihat sangat buggy. Seringkali konflik Z menjadi lebih buruk semakin jauh dari permukaan kamera. Pengembang mesin dapat memasukkan koreksi ke dalamnya untuk memuluskan masalah ini, tetapi jika seorang seniman membuat poligon yang cukup dekat dan tumpang tindih satu sama lain, maka masalah masih bisa muncul. Contoh lain adalah dinding dengan poster yang tergantung padanya. Poster terletak hampir pada kedalaman yang sama dari kamera dengan dinding di belakangnya, sehingga risiko konflik-Z sangat tinggi. Solusinya adalah dengan memotong lubang di dinding di bawah poster. Ini juga akan mengurangi jumlah redrawing.
Gambar 20. Contoh konflik-Z dari poligon yang tumpang tindih.Dalam kasus ekstrim, konflik Z dapat terjadi bahkan ketika objek saling bersentuhan. Gambar 20 menunjukkan laci di lantai, dan karena kami tidak membuat lubang di lantai di bawah laci, z-buffer dapat dikacaukan di samping tepi tempat lantai bertemu laci.
Menggunakan panggilan draw
GPU telah menjadi sangat cepat - sangat cepat sehingga CPU mungkin tidak mengikutinya. Karena GPU pada dasarnya dirancang untuk melakukan satu tugas, mereka jauh lebih mudah untuk bekerja dengan cepat. Grafik secara inheren terkait dengan perhitungan beberapa piksel, sehingga Anda dapat membuat peralatan yang menghitung banyak piksel secara paralel. Namun, GPU hanya memberikan perintah untuk menggambar CPU. Jika CPU tidak dapat dengan cepat “memberi makan” GPU dengan data, maka kartu video akan menganggur. Setiap kali CPU memerintahkan GPU untuk menggambar sesuatu, itu disebut draw call. Panggilan undian yang paling sederhana terdiri dari rendering satu mesh, termasuk satu shader dan satu set tekstur.
Bayangkan sebuah prosesor yang lambat yang dapat mentransfer 100 draw drawings per frame, dan GPU cepat yang dapat menarik jutaan poligon per frame. Dalam hal ini, panggilan undian yang ideal dapat menarik 10.000 poligon. Jika jerat Anda hanya terdiri dari 100 poligon, maka GPU hanya dapat menggambar 10.000 poligon per bingkai. Artinya, 99% dari waktu GPU akan menganggur. Dalam hal ini, kita dapat dengan mudah meningkatkan jumlah poligon di jerat tanpa kehilangan apa pun.
Terdiri dari apa panggilan undian, dan biayanya, sangat bergantung pada mesin dan arsitektur tertentu. Beberapa mesin dapat menggabungkan banyak jerat menjadi satu panggilan undian (melakukan batching mereka, batch), tetapi semua jerat harus memiliki shader yang sama, atau mungkin memiliki batasan lain. API baru seperti Vulkan dan DirectX 12 dirancang khusus untuk mengatasi masalah ini dengan mengoptimalkan cara program berkomunikasi dengan driver grafis, sehingga meningkatkan jumlah panggilan draw yang dapat ditransfer dalam satu frame.
Jika tim Anda sedang menulis mesin sendiri, tanyakan kepada pengembang mesin batasan apa yang dimiliki panggilan. Jika Anda menggunakan mesin siap pakai seperti Unreal atau Unity, maka jalankan tolok ukur kinerja untuk menentukan batas kemampuan mesin.
Anda mungkin menemukan bahwa Anda dapat meningkatkan jumlah poligon tanpa menyebabkan penurunan kecepatan.Kesimpulan
Saya harap artikel ini berfungsi sebagai pengantar yang baik untuk membantu Anda memahami berbagai aspek kinerja rendering. Dalam GPU dari berbagai produsen, semuanya diimplementasikan sedikit dengan caranya sendiri. Ada banyak pemesanan dan kondisi khusus yang terkait dengan engine dan platform perangkat keras tertentu. Selalu pertahankan dialog terbuka dengan rendering programmer untuk menggunakan rekomendasi mereka dalam proyek Anda.Tentang penulis
Eskil Steenberg adalah pengembang game dan alat yang independen, dan ia bekerja sebagai konsultan dan proyek independen. Semua tangkapan layar diambil dalam proyek aktif menggunakan alat yang dikembangkan oleh Esquil. Anda dapat mempelajari lebih lanjut tentang pekerjaannya di situs web Quel Solaar dan di akun Twitter @quelsolaar-nya.