GLSL: Pusat atau Centroid? Atau saat shader menyerang

Memodifikasi shader untuk game yang akan datang, saya mengalami artefak yang tidak menyenangkan yang hanya memanifestasikan dirinya ketika perangkat keras MSAA dihidupkan. Pada tangkapan layar lanskap Anda dapat melihat beberapa piksel terlalu terang. Nilai warna di beberapa dari mereka begitu besar sehingga setelah mekar, mereka berubah menjadi "hantu" multi-warna.

gambar

Saya membawa kepada Anda terjemahan sebuah artikel yang menjelaskan secara rinci alasan fenomena ini dan cara mengatasinya.

gambar

Gambar 1 - Gambar yang benar (kiri) dan salah (kanan). Perhatikan bilah kuning di tepi kiri gambar "salah". Meskipun variabel myMixer bervariasi dari 0 hingga 1, entah bagaimana itu melampaui kisaran ini dalam gambar "salah".

Pertimbangkan shader fragmen sederhana dengan transformasi non-linear sederhana:

smooth in float myMixer; //      . //  sqrt    . void main( void ) { const vec3 blue = vec3( 0.0, 0.0, 1.0 ); const vec3 yellow = vec3( 1.0, 1.0, 0.0 ); float a = sqrt( myMixer ); //    myMixer < 0.0 vec3 color = mix( blue, yellow, a ); //   gl_FragColor = vec4( color, 1.0 ); } 

Dari mana asal garis kuning di gambar yang salah? Untuk lebih memahami apa yang salah, mari kita lihat kasus di mana semuanya bekerja dengan benar (hampir) selalu.

gambar

Ini adalah rasterisasi klasik dengan satu sampel. Kotak abu-abu adalah piksel, dan titik-titik kuning adalah pusat piksel yang terletak di koordinat jendela setengah bilangan bulat (secara default, koordinat piksel kiri bawah dalam gl_FragCoord adalah (0,5, 0,5) - trans. ).

gambar

Pada gambar di atas, garis potong memisahkan setengah ruang primitif. Di atas dan di sebelah kiri baris ini, variabel myMixer positif, dan di bawah dan di sebelah kanan negatif.

Rasterisasi satu sampel klasik mengklasifikasikan piksel dengan menjadi bagian dari primitif, dan membuat fragmen hanya untuk piksel yang pusatnya terletak di dalam primitif. Dalam contoh ini, enam fragmen akan diproduksi, ditampilkan di kiri atas. Piksel yang ditandai dalam warna yang dibungkam tidak termasuk dalam primitif. Fragmen tidak akan dihasilkan untuk mereka.

gambar

Hijau menunjukkan titik di mana shader fragmen akan dihitung. Nilai myMixer akan dihitung untuk pusat setiap piksel. Perhatikan bahwa titik-titik hijau di atas dan di sebelah kiri garis, sehingga nilai myMixer di dalamnya akan positif. Semua data input yang terkait dengan simpul (variabel bervariasi atau keluar / masuk) juga akan diinterpolasi pada titik-titik ini.

Shader sederhana kami tidak menggunakan turunan (eksplisit atau implisit, misalnya, ketika mengambil sampel dari tekstur dengan level mip), tetapi turunan dFdx (horizontal) dan dFdy (vertikal) ditandai dengan panah. Di dalam primitif, mereka didefinisikan dengan cukup baik dan teratur.

Untuk meringkas: dalam satu pilihan, fragmen dihasilkan hanya jika pusat pixel jatuh "di dalam" primitif, data fragmen dihitung untuk pusat pixel, interpolasi data vertex dan perhitungan shader dilakukan hanya di dalam primitif. Semuanya baik dan "benar." (Hampir selalu. Untuk saat ini, mari kita hilangkan ketidakakuratan beberapa turunan pada piksel di sepanjang perbatasan primitif).

Jadi, semuanya (hampir) sangat baik di rasterisasi dengan satu pilihan. Tapi apa yang salah ketika multisampling dinyalakan?

gambar

Ini adalah rasterisasi multisampling klasik. Kotak abu-abu menunjukkan piksel. Titik kuning adalah pusat piksel dalam koordinat setengah bilangan bulat. Pada titik biru, pengambilan sampel terjadi. Contoh ini menunjukkan diagram sederhana dari dua sampel yang diputar. Semua argumen dapat digeneralisasi untuk jumlah sampel yang sewenang-wenang.

gambar

Garis masih memisahkan setengah ruang primitif. Di atas dan di sebelah kiri, nilai myMixer positif. Lebih rendah dan ke kanan - negatif.

Saat rasterisasi dengan multisampling, pengklasifikasi piksel akan menghasilkan fragmen jika setidaknya satu sampel piksel berada di dalam primitif.

Dalam contoh ini, 10 fragmen akan dihasilkan, ditunjukkan pada setengah bidang kiri atas. Perhatikan empat fragmen yang ditambahkan di sepanjang wajah, di mana satu sampel jatuh di dalam primitif, meskipun pusat berada di luar. Piksel di luar primitif masih ditandai redup.

gambar

Apa yang akan terjadi ketika menghitung di tengah piksel?

Shader akan dihitung dalam titik-titik hijau dan merah untuk masing-masing fragmen. Data terkait myMixer dihitung di tengah setiap piksel. Di titik hijau, nilai-nilai ini akan positif, karena mereka berada di atas dan di sebelah kiri perbatasan. Titik merah di luar primitif, karena nilai-nilai myMixer di dalamnya negatif. Pada titik merah, data terkait diekstrapolasi bukan interpolasi.

Di shader kami, nilai sqrt (myMixer) tidak didefinisikan dengan myMixer negatif. Bahkan ketika nilai myMixer yang direkam oleh vertex shader terletak pada interval dari nol hingga satu, dalam fragmen shader myMixer dapat melampaui interval ini karena ekstrapolasi. Jadi, dengan myMixer negatif, hasil shader fragmen tidak didefinisikan.

gambar

Kami masih mempertimbangkan menghitung shader di tengah-tengah piksel, panah pada gambar menunjukkan dFdx dan dFdy. Pada fragmen internal poligon, mereka didefinisikan dengan cukup baik karena semua perhitungan dilakukan di pusat piksel yang terletak pada interval yang sama.

gambar

Apa yang akan terjadi ketika menghitung titik selain pusat piksel?

Poin hijau adalah titik di mana shader akan dihitung. Nilai terkait myMixer dihitung dalam centroid setiap piksel.

Sentroid suatu piksel adalah pusat gravitasi dari persimpangan kuadrat piksel dan interior primitif. Untuk piksel yang tertutup sepenuhnya, centroid adalah pusatnya. Untuk piksel yang tertutup sebagian, centroid biasanya berbeda dari pusat.

Standar OpenGL memungkinkan implementasi untuk memilih titik arbitrer di persimpangan primitif dan piksel, bukan centroid yang ideal. Misalnya, itu bisa menjadi titik pengambilan sampel.

Dalam contoh ini, jika pusat terletak di dalam primitif, data titik dihitung untuk pusat. Kalau tidak, mereka dihitung pada salah satu titik sampel yang terletak di dalam primitif. Ini terjadi selama empat piksel di sepanjang perbatasan. Semua titik hijau terletak di atas dan di sebelah kiri perbatasan, sehingga nilai di dalamnya selalu diinterpolasi dan tidak pernah diekstrapolasi.

Mengapa tidak selalu menghitung centroid shader? Secara umum, ini lebih mahal daripada komputasi di pusat. Namun, ini bukan faktor utama.

Ini semua tentang menghitung turunan. Perhatikan tanda panah di antara titik-titik hijau. Jarak antara keduanya tidak sama untuk pasangan titik yang berbeda. Selain itu, y tidak konstan untuk dFdx, dan x tidak konstan untuk dFdy. Derivatif kurang akurat ketika dihitung dalam centroid .

Ini adalah kompromi, dan karena itu OpenGL, dimulai dengan GLSL 1.20, menawarkan pengembang shader pilihan antara pusat dan centroid menggunakan kualifikasi centroid:

 centroid in float myMixer; //  centroid  smooth //      . //  sqrt    . void main( void ) { const vec3 blue = vec3( 0.0, 0.0, 1.0 ); const vec3 yellow = vec3( 1.0, 1.0, 0.0 ); float a = sqrt( myMixer ); //    myMixer < 0.0 vec3 color = mix( blue, yellow, a ); //   gl_FragColor = vec4( color, 1.0 ); } 

Kapan Anda harus menggunakan centroid?

  1. Ketika nilai ekstrapolasi dapat menyebabkan hasil yang tidak jelas. Perhatikan fungsi bawaan, deskripsi yang mengatakan "hasilnya tidak didefinisikan jika ..."
  2. Ketika nilai ekstrapolasi digunakan dengan fungsi yang sangat non-linier atau terputus-putus. Ini termasuk fungsi langkah atau perhitungan suar, terutama ketika eksponen cukup besar.

Kapan sebaiknya Anda tidak menggunakan centroid?

  1. Jika Anda membutuhkan turunan yang tepat. Derivatif dapat berupa eksplisit (panggilan dFdx) atau implisit, misalnya, sampel dari tekstur dengan tingkat mip atau dengan penyaringan anisotropik. Dalam spesifikasi GLSL, turunan dalam centroid dianggap sangat tidak dapat digunakan sehingga dinyatakan tidak terdefinisi. Dalam kasus seperti itu, cobalah untuk menulis:

     centroid in float myMixer; //  ! smooth in float myCenterMixer; //     . 

  2. Jika grid diberikan di mana sebagian besar batas primitif bersifat internal dan selalu didefinisikan dengan baik. Contoh paling sederhana adalah setrip 100 segitiga (TRIANGLE_STRIP), di mana hanya segitiga pertama dan terakhir yang dapat diekstrapolasi. Kualifikasi centroid akan menghasilkan interpolasi pada dua segitiga ini dengan biaya kehilangan akurasi dan kontinuitas pada 98 segitiga yang tersisa.
  3. Jika Anda tahu bahwa artefak dapat muncul dari fungsi yang tidak terbatas, nonlinier, atau terputus-putus, tetapi dalam praktiknya artefak ini hampir tidak terlihat. Jika shader tidak menyerang - jangan perbaiki!

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


All Articles