Efek ini terinspirasi oleh
episode Powerpuff Girls . Saya ingin membuat efek penyebaran warna di dunia hitam dan putih, tetapi untuk
menerapkannya dalam koordinat ruang dunia , untuk melihat bagaimana
warna melukis objek , dan tidak hanya menyebar datar di layar, seperti dalam kartun.
Saya menciptakan efek dalam
Pipa Render Ringan yang baru
dari mesin Unity, contoh bawaan dari pipa Pipa Rendering Scriptable. Semua konsep berlaku untuk saluran pipa lain, tetapi beberapa fungsi atau matriks bawaan mungkin memiliki nama yang berbeda. Saya juga menggunakan tumpukan pemrosesan pasca baru, tetapi dalam tutorial saya akan menghilangkan deskripsi rinci pengaturannya, karena dijelaskan dengan sangat baik dalam manual lain, misalnya, dalam
video ini .
Efek post-processing dalam skala abu-abu
Hanya untuk referensi, seperti inilah pemandangan tanpa efek pasca pemrosesan.
Untuk efek ini, saya menggunakan paket Pasca Pemrosesan Unity 2018 yang baru, yang dapat diunduh dari manajer paket. Jika Anda tidak tahu cara menggunakannya, maka saya merekomendasikan
tutorial ini .
Saya menulis efek saya sendiri dengan memperluas kelas PostProcessingEffectSettings dan PostProcessEffectRenderer yang ditulis dalam C #, kode sumbernya dapat dilihat di
sini . Sebenarnya, saya tidak melakukan sesuatu yang sangat menarik dengan efek ini pada sisi CPU (dalam kode C #) kecuali bahwa saya menambahkan sekelompok properti umum ke Inspektur, jadi saya tidak akan menjelaskan bagaimana melakukan ini dalam tutorial. Saya harap kode saya berbicara sendiri.
Mari kita beralih ke kode shader dan mulai dengan efek grayscale. Dalam tutorial, kami tidak akan memodifikasi file shaderlab, struktur input, dan vertex shader, sehingga Anda dapat melihat kode sumbernya di
sini . Sebagai gantinya, kami akan mengurus shader fragmen.
Untuk mengonversi warna menjadi skala abu-abu, kami
mengurangi nilai setiap piksel menjadi nilai luminance yang menjelaskan
kecerahannya . Ini dapat dilakukan dengan mengambil produk skalar dari
nilai warna tekstur kamera dan
vektor tertimbang , yang menggambarkan kontribusi setiap saluran warna terhadap kecerahan warna secara keseluruhan.
Mengapa kami menggunakan produk skalar? Jangan lupa bahwa produk skalar dihitung sebagai berikut:
dot(a, b) = a x * b x + a y * b y + a z * b z
Dalam hal ini, kami mengalikan setiap saluran dari
nilai warna berdasarkan
berat . Lalu kami menambahkan produk-produk ini untuk mengurangi mereka ke nilai skalar tunggal. Ketika warna RGB memiliki nilai yang sama di saluran R, G, dan B, warnanya berubah menjadi abu-abu.
Seperti apa bentuk kode shader:
float4 fullColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.screenPos); float3 weight = float3(0.299, 0.587, 0.114); float luminance = dot(fullColor.rgb, weight); float3 greyscale = luminance.xxx; return float4(greyscale, 1.0);
Jika shader dasar dikonfigurasikan dengan benar, maka efek post-processing akan mewarnai seluruh layar dalam skala abu-abu.
Rendering efek warna di ruang dunia
Karena ini adalah efek pasca pemrosesan,
kami tidak memiliki informasi tentang geometri adegan di vertex shader. Pada tahap pasca pemrosesan, satu-satunya informasi yang kami miliki adalah
gambar yang diberikan oleh kamera dan
ruang koordinat terpotong untuk pengambilan sampelnya. Namun, kami ingin efek pewarnaan menyebar ke seluruh objek, seolah-olah itu terjadi di dunia, dan tidak hanya di layar datar.
Untuk menggambar efek ini dalam geometri pemandangan, kita membutuhkan
koordinat ruang dunia dari setiap piksel. Untuk berpindah dari
koordinat ruang koordinat terpotong ke
koordinat ruang dunia , kita perlu melakukan
transformasi ruang koordinat .
Biasanya, untuk berpindah dari satu ruang koordinat ke yang lain, diperlukan sebuah matriks yang mendefinisikan transformasi dari ruang koordinat A ke ruang B. Untuk beralih dari A ke B, kita mengalikan vektor dalam ruang koordinat A dengan matriks transformasi ini. Dalam kasus kami, kami akan melakukan transisi berikut:
ruang koordinat terpotong (ruang klip) ->
ruang tampilan (ruang tampilan) ->
ruang dunia (ruang dunia) . Artinya, kita membutuhkan matriks clip-to-view-space dan matriks view-to-world-space yang disediakan Unity.
Namun,
Koordinat kesatuan dari ruang koordinat terpotong tidak memiliki nilai z yang menentukan kedalaman piksel, atau jarak ke kamera. Kita membutuhkan nilai ini untuk bergerak dari ruang koordinat terpotong ke ruang spesies. Mari kita mulai dengan ini!
Mendapatkan nilai buffer kedalaman
Jika pipa rendering diaktifkan, maka ia menggambar tekstur di
viewport yang menyimpan
nilai z dalam struktur yang disebut
buffer kedalaman . Kami dapat mencicipi buffer ini untuk mendapatkan nilai
z yang hilang
dari ruang koordinat kami dari koordinat terpotong!
Pertama, pastikan bahwa
buffer kedalaman benar-benar diberikan dengan mengklik bagian "Tambahkan Data Tambahan" dari kamera di Inspektur dan memeriksa bahwa kotak "Membutuhkan Tekstur Kedalaman" dicentang. Pastikan juga bahwa opsi Allow MSAA diaktifkan untuk kamera. Saya tidak tahu mengapa efek ini perlu diperiksa, tetapi itu benar. Jika buffer kedalaman digambar, maka di
frame debugger Anda akan melihat tahap
"Depth Prepass" .
Buat sampler _CameraDepthTexture dalam
file hlsl TEXTURE2D_SAMPLER2D(_CameraDepthTexture, sampler_CameraDepthTexture);
Sekarang mari kita menulis fungsi GetWorldFromViewPosition dan untuk saat ini kita akan menggunakannya untuk memeriksa
buffer kedalaman . (Nanti kita akan mengembangkannya untuk mendapatkan posisi di dunia.)
float3 GetWorldFromViewPosition (VertexOutput i) { float z = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, i.screenPos).r; return z.xxx; }
Di shader fragmen, gambarkan nilai sampel tekstur kedalaman.
float3 depth = GetWorldFromViewPosition(i); return float4(depth, 1.0);
Ini adalah hasil saya terlihat ketika hanya ada satu dataran berbukit di tempat kejadian (saya mematikan semua pohon untuk lebih menyederhanakan pengujian nilai-nilai ruang dunia). Hasil Anda harus terlihat serupa. Nilai hitam dan putih menggambarkan jarak dari geometri ke kamera.
Berikut adalah beberapa langkah yang dapat Anda ambil jika Anda mengalami masalah:
- Pastikan kamera telah mengaktifkan rendering dalam.
- Pastikan kamera telah mengaktifkan MSAA.
- Coba ganti bidang dekat dan jauh dari kamera.
- Pastikan bahwa objek yang Anda harapkan untuk melihat di buffer kedalaman menggunakan shader dengan pass kedalaman. Ini memastikan bahwa objek menarik ke buffer kedalaman. Semua shader standar di LWRP melakukan ini.
Mendapatkan nilai dalam ruang dunia
Sekarang kita memiliki semua informasi yang diperlukan untuk
ruang koordinat terpotong , mari kita beralih ke
ruang spesies , dan kemudian ke
ruang dunia .
Perhatikan bahwa matriks transformasi yang diperlukan untuk operasi ini sudah ada di perpustakaan SRP. Namun, mereka terkandung dalam pustaka C # engine Unity, jadi saya memasukkannya ke shader di fungsi Render dari skrip
ColorSpreadRenderer :
sheet.properties.SetMatrix("unity_ViewToWorldMatrix", context.camera.cameraToWorldMatrix); sheet.properties.SetMatrix("unity_InverseProjectionMatrix", projectionMatrix.inverse);
Sekarang mari kita memperluas fungsi GetWorldFromViewPosition kami.
Pertama, kita perlu mendapatkan posisi di viewport dengan
mengalikan posisi dalam ruang koordinat terpotong oleh InverseProjectionMatrix . Kita juga perlu melakukan beberapa sihir voodoo dengan posisi layar, yang terkait dengan bagaimana Unity menyimpan posisinya di ruang koordinat terpotong.
Akhirnya, kita dapat
melipatgandakan posisi di viewport dengan ViewToWorldMatrix untuk mendapatkan posisi di
ruang dunia .
float3 GetWorldFromViewPosition (VertexOutput i) {
Mari kita lakukan pemeriksaan untuk memastikan bahwa posisi di ruang global sudah benar. Untuk melakukan ini, saya menulis
shader yang hanya mengembalikan posisi objek di
ruang dunia ; ini adalah perhitungan yang cukup sederhana berdasarkan shader biasa, kebenarannya dapat dipercaya. Matikan efek pasca-pemrosesan dan ambil tangkapan layar dari shader uji ini untuk
ruang dunia . Setelah saya menerapkan shader ke permukaan bumi dalam adegan terlihat seperti ini:
(Perhatikan bahwa nilai-nilai di ruang dunia jauh lebih besar dari 1,0, jadi jangan khawatir bahwa warna-warna ini masuk akal; sebagai gantinya, pastikan saja hasilnya sama untuk jawaban "benar" dan "dihitung"). Selanjutnya, mari kita kembali ke tes objek adalah bahan biasa (dan bukan bahan uji ruang dunia), dan kemudian nyalakan efek pasca-pemrosesan lagi. Hasil saya terlihat seperti ini:
Ini benar-benar mirip dengan uji shader yang saya tulis, yaitu, perhitungan ruang dunia kemungkinan besar benar!
Menggambar lingkaran di ruang dunia
Sekarang kita memiliki
posisi di ruang dunia , kita dapat menggambar lingkaran warna di TKP! Kita perlu mengatur
radius di mana efeknya akan menggambar warna. Di luar, efeknya akan membuat gambar dalam skala abu-abu. Untuk mengaturnya, Anda perlu menyesuaikan nilai untuk
radius efek (
_MaxSize ) dan pusat lingkaran (_Center). Saya mengatur nilai-nilai ini di kelas C #
ColorSpread sehingga mereka terlihat di inspektur. Mari perluas shader fragmen kami dengan memaksanya
untuk memeriksa apakah piksel saat ini ada di dalam radius lingkaran :
float4 Frag(VertexOutput i) : SV_Target { float3 worldPos = GetWorldFromViewPosition(i);
Akhirnya, kita bisa menggambar warna berdasarkan apakah itu berada dalam
radius di
ruang dunia . Seperti inilah efek dasarnya!
Menambahkan Efek Khusus
Saya akan melihat beberapa teknik lagi yang digunakan untuk membuat warna menyebar di tanah. Ada banyak lagi untuk efek penuh, tetapi tutorial sudah menjadi terlalu besar, jadi kita akan membatasi diri kita pada yang paling penting.
Animasi Lingkaran Pembesaran
Kami ingin efeknya menyebar ke seluruh dunia, yaitu, seolah tumbuh. Untuk melakukan ini, Anda perlu mengubah
jari -
jari tergantung pada waktu.
_StartTime menunjukkan waktu di mana lingkaran harus mulai tumbuh. Dalam proyek saya, saya menggunakan skrip tambahan yang memungkinkan Anda mengklik di mana saja di layar untuk memulai pertumbuhan lingkaran; dalam hal ini, waktu mulai sama dengan waktu mouse diklik.
_GrowthSpeed ββmengatur kecepatan meningkatkan lingkaran.
Kita juga perlu memperbarui pemeriksaan jarak untuk membandingkan jarak saat ini dengan meningkatnya
radius efek , dan bukan dengan _MaxSize.
Seperti apa hasilnya nanti:
Menambah radius noise
Saya ingin efeknya lebih seperti buram cat, bukan hanya lingkaran yang tumbuh. Untuk melakukan ini, mari kita
tambahkan noise ke radius efek sehingga distribusinya tidak merata.
Pertama, kita perlu sampel tekstur di
ruang dunia . Koordinat UV dari i.screenPos terletak di
ruang layar , dan jika kami sampel berdasarkan pada mereka, bentuk efeknya akan bergerak dengan kamera; jadi mari kita gunakan koordinat di
ruang dunia . Saya menambahkan parameter
_NoiseTexScale untuk mengontrol
skala sampel tekstur noise , karena koordinat di ruang dunia cukup besar.
Sekarang mari kita sampel tekstur noise dan tambahkan nilai ini ke radius efek. Saya menggunakan skala _NoiseSize untuk kontrol lebih besar atas ukuran noise.
Beginilah hasilnya setelah beberapa penyesuaian:
Kesimpulannya
Anda dapat mengikuti pembaruan tutorial di
Twitter saya, dan di
Twitch saya menghabiskan stream coding! (Juga, saya mengalirkan game dari waktu ke waktu, jadi jangan heran jika Anda melihat saya duduk di piyama dan bermain Kingdom Hearts 3.)
Ucapan Terima Kasih: