Tutorial ini akan menunjukkan kepada Anda bagaimana menulis shader geometris untuk menghasilkan bilah rumput dari bagian atas jala yang masuk dan menggunakan tessellation untuk mengontrol kepadatan rumput.
Artikel ini menjelaskan proses selangkah demi selangkah dari penulisan shader rumput di Unity. Shader menerima mesh yang masuk, dan dari setiap simpul mesh menghasilkan pisau rumput menggunakan
shader geometris . Demi minat dan realisme, bilah rumput akan memiliki
ukuran dan
rotasi acak , dan mereka juga akan terpengaruh oleh
angin . Untuk mengontrol kepadatan rumput, kami menggunakan
tessellation untuk memisahkan jala yang masuk. Rumput akan dapat
dilemparkan dan
menerima bayangan.
Proyek yang sudah selesai diposting di akhir artikel. File shader yang dihasilkan berisi sejumlah besar komentar yang memudahkan pemahaman.
Persyaratan
Untuk menyelesaikan tutorial ini, Anda akan membutuhkan pengetahuan praktis tentang mesin Unity dan pemahaman awal tentang sintaks dan fungsionalitas shader.
Unduh konsep proyek (.zip) .
Mulai bekerja
Unduh konsep proyek dan buka di editor Unity. Buka adegan
Main
, dan kemudian buka shader
Grass
di editor kode Anda.
File ini berisi shader yang menghasilkan warna putih, serta beberapa fungsi yang akan kita gunakan dalam tutorial ini. Anda akan melihat bahwa fungsi-fungsi ini bersama dengan vertex shader termasuk dalam blok
CGINCLUDE
terletak di
luar SubShader
. Kode yang ditempatkan di blok ini akan secara
otomatis dimasukkan dalam semua lintasan di shader; ini akan berguna nanti karena shader kami akan memiliki beberapa lintasan.
Kami akan mulai dengan menulis
shader geometris yang menghasilkan segitiga dari setiap titik pada permukaan mesh kami.
1. Geometris Shaders
Pembagi geometris adalah bagian opsional dari pipa render. Mereka dieksekusi
setelah shader vertex (atau shader tessellation jika tessellation digunakan) dan sebelum simpul diproses untuk shader fragmen.
Direct3D Graphics Pipeline 11. Perhatikan bahwa dalam diagram ini fragmen shader disebut pixel shader .Pembagi geometris menerima satu
primitif pada input dan dapat menghasilkan nol, satu atau banyak primitif. Kita akan mulai dengan menulis shader geometris yang menerima
titik (atau
titik ) pada input, dan yang memberi makan
satu segitiga yang mewakili bilah rumput.
Kode di atas menyatakan shader geometris yang disebut
geo
dengan dua parameter. Yang pertama,
triangle float4 IN[3]
, melaporkan bahwa ia akan mengambil satu segitiga (terdiri dari tiga titik) sebagai input. Yang kedua, seperti
TriangleStream
, mengatur shader untuk menampilkan aliran segitiga sehingga setiap titik menggunakan struktur
geometryOutput
Output untuk mengirimkan datanya.
Kami mengatakan di atas bahwa shader akan menerima satu simpul dan menghasilkan bilah rumput. Lalu mengapa kita mendapatkan segitiga?Mengambil
sebagai input lebih murah. Ini bisa dilakukan sebagai berikut.
void geo(point vertexOutput IN[1], inout TriangleStream<geometryOutput> triStream)
Namun, karena mesh masuk kami (dalam hal ini
GrassPlane10x10
, terletak di folder
Mesh
) memiliki
topologi segitiga , ini akan menyebabkan ketidakcocokan antara topologi mesh yang masuk dan input primitif yang diperlukan. Meskipun ini
diizinkan dalam DirectX HLSL, itu tidak
diizinkan di OpenGL , jadi kesalahan akan ditampilkan.
Selain itu, kami menambahkan parameter terakhir dalam tanda kurung di atas deklarasi fungsi:
[maxvertexcount(3)]
. Ia memberi tahu GPU bahwa kami akan menampilkan (tetapi tidak
diharuskan melakukan)
tidak lebih dari 3 simpul. Kami juga membuat
SubShader
menggunakan shader geometris dengan mendeklarasikannya di dalam
Pass
.
Shader geometris kami belum melakukan apa pun; untuk menggambar segitiga, tambahkan kode berikut di dalam geometri shader.
geometryOutput o; o.pos = float4(0.5, 0, 0, 1); triStream.Append(o); o.pos = float4(-0.5, 0, 0, 1); triStream.Append(o); o.pos = float4(0, 1, 0, 1); triStream.Append(o);
Ini memberikan hasil yang sangat aneh. Saat Anda memindahkan kamera, menjadi jelas bahwa segitiga ditampilkan di
ruang layar . Ini logis: karena shader geometris dieksekusi segera sebelum memproses simpul, maka shader vertex menghilangkan tanggung jawab untuk simpul yang akan ditampilkan dalam
ruang pemotongan . Kami akan mengubah kode kami untuk mencerminkan hal ini.
Sekarang segitiga kita ditampilkan dengan benar di dunia. Namun, tampaknya hanya satu yang dibuat. Faktanya, satu segitiga
digambar untuk setiap verteks dari mesh kita, tetapi posisi yang ditetapkan untuk verteks dari segitiga adalah
konstan - mereka tidak berubah untuk setiap verteks yang masuk. Oleh karena itu, semua segitiga terletak satu di atas yang lain.
Kami akan memperbaiki ini dengan membuat
offset posisi titik keluar relatif terhadap titik masuk.
Mengapa beberapa simpul tidak membuat segitiga?Meskipun kami telah menentukan bahwa primitif yang masuk akan berupa
segitiga , sebilah rumput ditransmisikan hanya dari
salah satu titik segitiga, membuang dua lainnya. Tentu saja, kita dapat mentransfer sebilah rumput dari ketiga titik yang masuk, tetapi ini akan mengarah pada fakta bahwa segitiga tetangga secara berlebihan menciptakan bilah rumput di atas satu sama lain.
Atau Anda dapat mengatasi masalah ini dengan mengambil jerat yang memiliki jenis
titik Topologi sebagai jerat yang masuk dari shader geometris.
Segitiga sekarang digambar dengan benar, dan pangkalannya terletak di puncak yang memancarkannya. Sebelum melanjutkan, buat objek
GrassPlane
tidak aktif dalam adegan, dan buat objek
GrassBall
aktif . Kami ingin rumput menghasilkan dengan benar pada berbagai jenis permukaan, jadi penting untuk mengujinya pada jerat berbagai bentuk.
Sejauh ini, semua segitiga dipancarkan dalam satu arah, dan tidak keluar dari permukaan bola. Untuk mengatasi masalah ini, kita akan membuat bilah rumput di
ruang bersinggungan .
2. Ruang singgung
Idealnya, kami ingin membuat bilah rumput dengan menetapkan lebar, tinggi, kelengkungan dan rotasi yang berbeda, tanpa memperhitungkan sudut permukaan dari mana bilah rumput dipancarkan. Sederhananya, kita mendefinisikan sebilah rumput di ruang
lokal ke titik memancarkannya , dan kemudian mengubahnya sehingga
lokal ke jala . Ruang ini disebut
ruang singgung .
Dalam ruang singgung, sumbu X , Y, dan Z didefinisikan relatif terhadap normal dan posisi permukaan (dalam kasus kami, simpul).Seperti ruang lainnya, kita dapat mendefinisikan ruang singgung titik dengan tiga vektor:
kanan ,
maju dan
atas . Dengan menggunakan vektor-vektor ini, kita dapat membuat matriks untuk memutar bilah rumput dari garis singgung ke ruang lokal.
Anda dapat mengakses vektor ke
kanan dan
atas dengan menambahkan data titik masukan baru.
Vektor ketiga dapat dihitung dengan mengambil
produk vektor antara dua lainnya. Produk vektor mengembalikan vektor
tegak lurus ke dua vektor yang masuk.
Mengapa hasil vektor dikalikan dengan koordinat garis singgung w?Saat mengekspor mesh dari editor 3D, biasanya binormals (juga disebut
garis singgung ke dua titik ) sudah disimpan dalam data mesh. Alih-alih mengimpor binormals ini, Unity hanya mengambil arah setiap binormal dan menugaskan mereka ke koordinat
w tangen. Ini memungkinkan Anda untuk menghemat memori, sementara pada saat yang sama memberikan kemampuan untuk membuat ulang binormal yang benar. Diskusi terperinci tentang topik ini dapat ditemukan di
sini .
Memiliki ketiga vektor, kita dapat membuat matriks untuk transformasi antara ruang singgung dan ruang lokal. Kami akan melipatgandakan setiap simpul bilah rumput dengan matriks ini sebelum meneruskannya ke
UnityObjectToClipPos
, yang mengharapkan simpul di ruang lokal.
Sebelum menggunakan matriks, kami mentransfer kode output titik ke fungsi agar tidak menulis baris kode yang sama berulang kali. Ini disebut
prinsip KERING , atau
jangan ulangi diri Anda sendiri .
Akhirnya, kita mengalikan simpul output dengan matriks
tangentToLocal
, menyelaraskan mereka dengan normal dari titik input mereka.
triStream.Append(VertexOutput(pos + mul(tangentToLocal, float3(0.5, 0, 0)))); triStream.Append(VertexOutput(pos + mul(tangentToLocal, float3(-0.5, 0, 0)))); triStream.Append(VertexOutput(pos + mul(tangentToLocal, float3(0, 1, 0))));
Ini lebih seperti yang kita butuhkan, tetapi tidak tepat. Masalahnya di sini adalah bahwa pada awalnya kita menetapkan arah "atas" (atas) dari sumbu
Y ; Namun, dalam ruang singgung, arah naik biasanya terletak di sepanjang sumbu
Z. Sekarang kita akan melakukan perubahan ini.
3. Penampilan rumput
Untuk membuat segitiga lebih mirip bilah rumput, Anda perlu menambahkan warna dan variasi. Kami mulai dengan menambahkan
gradien turun dari atas bilah rumput.
Gradien warna 3.1
Tujuan kami adalah memungkinkan seniman untuk mengatur dua warna - atas dan bawah, dan untuk menyisipkan di antara dua warna ini ia ujung ke pangkal pisau rumput. Warna-warna ini sudah didefinisikan dalam file shader sebagai
_TopColor
dan
_BottomColor
. Untuk pengambilan sampel yang tepat, Anda harus meneruskan
koordinat UV ke shader fragmen.
Kami menciptakan koordinat UV untuk bilah rumput dalam bentuk segitiga, dua simpul dasar yang terletak di kiri bawah dan kanan, dan ujung atas terletak di tengah di atas.
Koordinat UV dari tiga simpul bilah rumput. Meskipun kami mengecat bilah rumput dengan gradien sederhana, pengaturan tekstur yang serupa memungkinkan Anda untuk melapisi tekstur.Sekarang kita dapat mencicipi warna atas dan bawah dalam shader fragmen dengan UV dan kemudian interpolasi dengan
lerp
. Kita juga perlu memodifikasi parameter fragmen shader, menjadikan
geometryOutput
sebagai input, dan bukan hanya posisi
float4
.
3.2 Arah sudu acak
Untuk menciptakan variabilitas dan memberikan tampilan yang lebih alami pada rumput, kami akan membuat setiap helai rumput terlihat secara acak. Untuk melakukan ini, kita perlu membuat matriks rotasi yang memutar bilah rumput jumlah acak di sekitar sumbu
atas .
Ada dua fungsi dalam file shader yang akan membantu kami melakukan ini:
rand
, yang menghasilkan angka acak dari input tiga dimensi, dan
AngleAxis3x3
, yang menerima sudut (dalam
radian ) dan mengembalikan matriks yang memutar nilai ini di sekitar sumbu yang ditentukan. Fungsi terakhir bekerja persis sama dengan fungsi C #
Quaternion.AngleAxis (hanya
AngleAxis3x3
mengembalikan matriks, bukan angka empat).
Fungsi
rand
mengembalikan angka dalam kisaran 0 ... 1; kita kalikan dengan
2 Pi untuk mendapatkan rentang nilai sudut penuh.
Kami menggunakan posisi
pos
masuk sebagai seed untuk rotasi acak. Karena ini, setiap helai rumput akan memiliki rotasi sendiri, konstan di setiap bingkai.
Rotasi dapat diterapkan pada bilah rumput dengan mengalikannya dengan matriks
tangentToLocal
dibuat. Perhatikan bahwa perkalian matriks
tidak komutatif ; urutan operan itu
penting .
3.3 Tekuk ke depan secara acak
Jika semua bilah rumput sejajar sempurna, mereka akan tampak sama. Ini mungkin cocok untuk rumput yang terawat baik, misalnya, di halaman yang dipangkas, tetapi di alam rumput tidak tumbuh seperti itu. Kami akan membuat matriks baru untuk memutar rumput di sepanjang sumbu
X , serta properti untuk mengontrol rotasi ini.
Sekali lagi kami menggunakan posisi bilah rumput sebagai benih acak, kali ini dengan
menyapunya untuk membuat benih yang unik. Kami juga akan mengalikan
UNITY_PI
dengan
0,5 ; ini akan memberi kita interval acak 0 ... 90 derajat.
Kami sekali lagi menerapkan matriks ini melalui rotasi, mengalikan semuanya dalam urutan yang benar.
3.4 Lebar dan tinggi
Sedangkan ukuran bilah rumput terbatas pada lebar 1 unit dan tinggi 1 unit. Kami akan menambahkan properti untuk mengontrol ukuran, serta properti untuk menambahkan variasi acak.
Segitiga sekarang jauh lebih mirip bilah rumput, tetapi juga terlalu sedikit. Tidak ada cukup puncak di jala yang masuk untuk menciptakan kesan bidang yang terlalu padat.
Salah satu solusinya adalah membuat mesh baru yang lebih padat, baik menggunakan C # atau dalam editor 3D. Ini akan berhasil, tetapi tidak akan memungkinkan kita untuk mengontrol kepadatan rumput secara dinamis. Sebagai gantinya, kami akan membagi mesh yang masuk menggunakan
tessellation .
4. Tessellation
Tessellation adalah tahap opsional dari render pipeline, dilakukan setelah vertex shader dan sebelum geometric shader (jika ada). Tugasnya adalah untuk membagi satu permukaan yang masuk ke banyak primitif. Tessellation diimplementasikan dalam dua langkah yang dapat diprogram:
hull dan
domain shaders.
Untuk shader permukaan, Unity memiliki
implementasi tessellation bawaan . Namun, karena kita
tidak menggunakan shader permukaan, kita harus mengimplementasikan shell dan domain shader kita sendiri. Pada artikel ini, saya tidak akan membahas implementasi tessellation secara rinci, dan kami hanya menggunakan file
CustomTessellation.cginc
ada. File ini diadaptasi dari
artikel Coding Catlike , yang merupakan sumber informasi yang sangat baik tentang implementasi tessellation di Unity.
Jika kita memasukkan objek
TessellationExample
dalam adegan, kita akan melihat bahwa itu sudah memiliki materi yang mengimplementasikan tessellation. Mengubah properti
Tessellation Uniform menunjukkan efek subdivisi.
Kami menerapkan tessellation di shader rumput untuk mengontrol kepadatan pesawat, dan oleh karena itu untuk mengontrol jumlah bilah rumput yang dihasilkan. Pertama, Anda perlu menambahkan file
CustomTessellation.cginc
. Kami akan merujuknya dengan jalur
relatifnya ke shader.
Jika Anda membuka
CustomTessellation.cginc
, Anda akan melihat bahwa
vertexOutput
dan
vertexOutput
, serta vertex shaders, sudah ditentukan di dalamnya. Tidak perlu mendefinisikan ulang mereka di shader rumput kami; mereka dapat dihapus.
Perhatikan bahwa
vert
vertex shader di
CustomTessellation.cginc
hanya meneruskan input langsung ke tahap tessellation; fungsi
vertexOutput
, yang disebut di dalam domain shader, mengambil tugas untuk menciptakan struktur
vertexOutput
.
Sekarang kita bisa menambahkan
shell dan
domain shader ke shader rumput. Kami juga akan menambahkan properti
_TessellationUniform
baru untuk mengontrol ukuran unit - variabel yang sesuai dengan properti ini telah dideklarasikan di
CustomTessellation.cginc
.
Sekarang mengubah properti
Tessellation Uniform memungkinkan kita untuk mengontrol kepadatan rumput. Saya menemukan bahwa hasil yang baik diperoleh dengan nilai
5 .
5. Angin
Kami menerapkan angin dengan mengambil sampel
tekstur distorsi . Tekstur ini akan terlihat seperti
peta normal , hanya di dalamnya hanya akan ada dua bukan tiga saluran. Kami akan menggunakan dua saluran ini sebagai arah angin sepanjang
X dan
Y.Sebelum mencicipi tekstur angin, kita perlu membuat koordinat UV. Alih-alih menggunakan koordinat tekstur yang ditetapkan untuk mesh, kami menerapkan posisi titik masuk. Berkat ini, jika ada beberapa jerat rumput di dunia, ilusi akan dibuat bahwa mereka semua adalah bagian dari sistem angin yang sama. Kami juga menggunakan
_Time
shader untuk menggulir tekstur angin di sepanjang permukaan rumput.
Kami menerapkan skala dan offset
_WindDistortionMap
ke posisi, dan kemudian menggesernya ke
_Time.y
, ditingkatkan ke
_WindFrequency
. Sekarang kita akan menggunakan UVs ini untuk mencicipi tekstur dan membuat properti untuk mengontrol kekuatan angin.
Perhatikan bahwa kami skala nilai sampel dari tekstur dari interval 0 ... 1 ke interval -1 ... 1. Selanjutnya, kita dapat membuat vektor dinormalisasi yang menunjukkan arah angin.
Sekarang kita dapat membuat matriks untuk memutar vektor ini dan melipatgandakannya dengan Matriks
transformationMatrix
kita.
Akhirnya, kami mentransfer tekstur
Wind
(terletak di akar proyek) ke bidang
Peta Distorsi Angin dari materi rumput di editor Unity. Kami juga mengatur parameter
Ubin tekstur ke
0.01, 0.01
.
Jika rumput tidak menjiwai di jendela
Adegan , lalu klik tombol
Toggle skybox, kabut, dan berbagai efek lainnya untuk mengaktifkan bahan animasi.
Dari kejauhan, rumput terlihat benar, tetapi jika kita perhatikan dengan cermat bilah rumput, kita melihat bahwa seluruh bilah rumput berputar, itulah sebabnya alas tidak lagi menempel ke tanah.Pangkal bilah rumput tidak lagi melekat pada tanah, tetapi berpotongan dengan itu (ditunjukkan dengan warna merah ), dan menggantung di atas bidang tanah (ditunjukkan oleh garis hijau ).Kami akan memperbaikinya dengan mendefinisikan matriks transformasi kedua, yang hanya berlaku untuk dua simpul basis. Dalam matriks ini tidak akan disertakan matriks windRotation
dan bendRotationMatrix
, berkat yang dasar melekat pada permukaan rumput.
6. Lengkungan bilah rumput
Sekarang setiap helai rumput ditentukan oleh satu segitiga. Pada jarak yang jauh, ini bukan masalah, tetapi di dekat bilah rumput mereka terlihat sangat kaku dan geometris, bukan organik dan bersemangat. Kami akan memperbaikinya dengan membuat bilah rumput dari beberapa segitiga dan menekuknya di sepanjang kurva .Setiap helai rumput akan dibagi menjadi beberapa segmen . Setiap segmen akan memiliki bentuk persegi panjang dan terdiri dari dua segitiga, dengan pengecualian segmen atas - itu akan menjadi satu segitiga yang menunjukkan ujung bilah rumput.Sejauh ini, kami hanya menggambar tiga simpul, membuat segitiga tunggal. Lalu, bagaimana, jika ada lebih banyak simpul, apakah geometri shader tahu mana yang akan bergabung dan membentuk segitiga? Jawabannya ada dalam struktur datastrip segitiga . Tiga simpul pertama bergabung dan membentuk segitiga, dan setiap simpul baru membentuk segitiga dengan dua sebelumnya.Bilah rumput yang terbagi lagi, direpresentasikan sebagai strip segitiga dan menciptakan satu simpul pada satu waktu. Setelah tiga simpul pertama, setiap simpul baru membentuk segitiga baru dengan dua simpul sebelumnya.Ini tidak hanya lebih efisien dalam hal penggunaan memori, tetapi juga memungkinkan Anda untuk membuat urutan segitiga dengan mudah dan cepat dalam kode Anda. Jika kita ingin membuat beberapa garis segitiga, kita bisa memanggil RestartStrip untuk TriangleStream
fungsi tersebut . Sebelum kita mulai menggambar lebih banyak simpul dari geometri shader, kita perlu meningkatkannya . Kami akan menggunakan desain untuk memungkinkan penulis shader mengontrol jumlah segmen dan menghitung jumlah simpul yang ditampilkan dari itu.maxvertexcount
#define
Awalnya, kami menetapkan jumlah segmen menjadi 3 dan memperbarui maxvertexcount
untuk menghitung jumlah simpul berdasarkan jumlah segmen.Untuk membuat bilah rumput yang tersegmentasi, kami menggunakan siklus for
. Setiap iterasi dari loop akan menambahkan dua simpul : kiri dan kanan . Setelah menyelesaikan ujungnya, kami menambahkan simpul terakhir di ujung bilah rumput.Sebelum kita melakukan ini, akan berguna untuk memindahkan bagian posisi komputasi dari simpul bilah kode ke dalam fungsi, karena kita akan menggunakan kode ini beberapa kali di dalam dan di luar loop. Tambahkan CGINCLUDE
berikut ini ke blok : geometryOutput GenerateGrassVertex(float3 vertexPosition, float width, float height, float2 uv, float3x3 transformMatrix) { float3 tangentPoint = float3(width, 0, height); float3 localPosition = vertexPosition + mul(transformMatrix, tangentPoint); return VertexOutput(localPosition, uv); }
Fungsi ini melakukan tugas yang sama karena ia melewati argumen yang sebelumnya kami lewati VertexOutput
untuk menghasilkan simpul dari bilah rumput. Memperoleh posisi, tinggi dan lebar, itu benar mengubah simpul menggunakan matriks yang ditransmisikan dan memberikannya koordinat UV. Kami akan memperbarui kode yang ada agar fungsi berfungsi dengan benar.
Fungsi mulai bekerja dengan benar, dan kami siap untuk memindahkan kode generasi vertex ke dalam loop for
. Tambahkan yang float width
berikut di bawah baris : for (int i = 0; i < BLADE_SEGMENTS; i++) { float t = i / (float)BLADE_SEGMENTS; }
Kami mengumumkan siklus yang akan dijalankan sekali untuk setiap bilah segmen rumput. Di dalam loop, tambahkan variabel t
. Variabel ini akan menyimpan nilai dalam rentang 0 ... 1, yang menunjukkan seberapa jauh kita telah bergerak di sepanjang bilah rumput. Kami menggunakan nilai ini untuk menghitung lebar dan tinggi segmen di setiap iterasi loop.
Saat bergerak ke atas rumput, tingginya meningkat dan lebarnya berkurang. Sekarang kita dapat menambahkan panggilan ke loop GenerateGrassVertex
untuk menambahkan simpul ke aliran segitiga. Kami juga akan menambahkan satu panggilan di GenerateGrassVertex
luar loop untuk membuat ujung bilah rumput.
Lihatlah garis dengan deklarasi float3x3 transformMatrix
- di sini kita memilih salah satu dari dua matriks transformasi: kita ambil transformationMatrixFacing
untuk simpul dari basis dan transformationMatrix
untuk semua yang lain.Bilah rumput sekarang dibagi menjadi banyak segmen, tetapi permukaan bilahnya masih datar - segitiga baru belum terlibat. Kami akan menambahkan rumput kelengkungan, menggeser posisi vertex dari Y . Pertama, kita perlu memodifikasi fungsi GenerateGrassVertex
sehingga mendapat offset di Y , yang akan kita panggil forward
.
Untuk menghitung perpindahan setiap simpul, kami mengganti pow
nilai ke dalam fungsi t
. Setelah t
naik ke daya, pengaruhnya pada perpindahan ke depan akan menjadi nonlinear dan mengubah bilah rumput menjadi kurva.
Ini adalah potongan kode yang cukup besar, tetapi semua pekerjaan dilakukan mirip dengan apa yang dilakukan untuk lebar dan tinggi bilah rumput. Dengan nilai yang lebih rendah _BladeForward
dan _BladeCurve
kami mendapatkan halaman yang tertata rapi, dan nilai yang lebih besar akan memberikan efek sebaliknya.7. Pencahayaan dan bayangan
Sebagai langkah terakhir untuk menyelesaikan shader, kami akan menambahkan kemampuan untuk melemparkan dan menerima bayangan. Kami juga akan menambahkan pencahayaan sederhana dari sumber utama cahaya directional.7.1 Bayangan Casting
Untuk memberikan bayangan di Unity, Anda perlu menambahkan pass kedua ke shader. Bagian ini akan digunakan oleh sumber cahaya yang menciptakan bayangan di tempat kejadian untuk membuat kedalaman rumput ke dalam peta bayangan mereka . Ini berarti bahwa shader geometris harus diluncurkan di lorong bayangan, sehingga bilah rumput dapat melemparkan bayangan.Karena shader geometris ditulis di dalam blok CGINCLUDE
, kita dapat menggunakannya dalam lintasan file apa pun. Buat pass kedua yang akan menggunakan shader yang sama dengan yang pertama, dengan pengecualian shader fragmen - kita akan mendefinisikan yang baru di mana kita akan menulis makro yang memproses output.
Selain membuat shader fragmen baru, ada beberapa perbedaan penting dalam bagian ini. Label LightMode
penting ShadowCaster
, bukan ForwardBase
- ini memberitahu Unity bahwa bagian ini harus digunakan untuk membuat objek menjadi peta bayangan. Ada juga arahan preprosesor di sini multi_compile_shadowcaster
. Ini memastikan bahwa shader mengkompilasi semua opsi yang diperlukan untuk membuat bayangan.Jadikan objek game Fence
aktif di TKP; jadi kami mendapatkan permukaan di mana bilah rumput bisa memberi bayangan.7.2 Mendapatkan Bayangan
Setelah Unity merender peta bayangan dari sudut pandang bayangan yang menciptakan sumber cahaya, ia meluncurkan bagian yang "mengumpulkan" bayangan ke dalam tekstur ruang layar . Untuk mencicipi tekstur ini, kita perlu menghitung posisi simpul di ruang layar dan mentransfernya ke shader fragmen.
Dalam fragmen shader dari bagian itu, ForwardBase
kita bisa menggunakan makro untuk mendapatkan nilai yang float
menunjukkan apakah permukaan dalam bayangan atau tidak. Nilai ini berada dalam kisaran 0 ... 1, di mana 0 adalah naungan penuh, 1 adalah penerangan penuh.Mengapa koordinat UV dari ruang layar disebut _ShadowCoord? Ini tidak sesuai dengan konvensi penamaan sebelumnya.Unity ( ).
SHADOW_ATTENUATION
.
Autolight.cginc
, , .
#define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
- , .
Akhirnya, kita perlu membuat shader dikonfigurasi dengan benar untuk menerima bayangan. Untuk melakukan ini, kami akan menambahkan ForwardBase
arahan preprocessor ke pass sehingga mengkompilasi semua opsi shader yang diperlukan.
Setelah mendekatkan kamera, kita bisa melihat artefak di permukaan bilah rumput; mereka disebabkan oleh fakta bahwa bilah rumput individu membayangi diri mereka sendiri. Kita dapat memperbaikinya dengan menerapkan pergeseran linier atau memindahkan posisi simpul di ruang pemotongan sedikit jauh dari layar. Kami akan menggunakan makro Unity untuk ini dan memasukkannya ke dalam desain #if
sehingga operasi hanya dilakukan di jalur bayangan.
Setelah menerapkan pergantian bayangan linier, artefak bayangan dalam bentuk garis-garis menghilang dari permukaan segitiga.Mengapa ada artefak di sepanjang tepi bilah rumput yang teduh?(multisample anti-aliasing
MSAA ) Unity
, . , .
— , ,
Unity . ( );
Unity .
7.3 Pencahayaan
Kami akan menerapkan pencahayaan menggunakan algoritma perhitungan pencahayaan tersebar sangat sederhana dan umum.... di mana N adalah normal ke permukaan, L adalah arah yang dinormalisasi dari sumber utama pencahayaan terarah, dan I adalah pencahayaan yang dihitung. Dalam tutorial ini kami tidak akan menerapkan pencahayaan tidak langsung.Saat ini, normals tidak ditugaskan untuk simpul dari bilah rumput. Seperti halnya posisi vertex, pertama-tama kita menghitung normals dalam ruang singgung dan kemudian mengubahnya menjadi lokal.Ketika Angka Kelengkungan Pisau adalah 1 , semua bilah rumput di ruang singgung diarahkan dalam satu arah: tepat di seberang sumbu Y. Sebagai langkah pertama dari solusi kami, kami menghitung yang normal, dengan asumsi tidak ada lengkungan.
tangentNormal
, didefinisikan secara langsung berlawanan dengan sumbu Y , ditransformasikan oleh matriks yang sama yang kami gunakan untuk mengkonversi titik singgung ke ruang lokal. Sekarang kita bisa meneruskannya ke suatu fungsi VertexOutput
, dan kemudian ke suatu struktur geometryOutput
.
Perhatikan bahwa sebelum kesimpulan, kita mengubah yang normal menjadi ruang dunia ; Unity menyampaikan kepada shaders arah sumber utama cahaya terarah di ruang dunia, sehingga transformasi ini diperlukan.Sekarang kita dapat memvisualisasikan normals dalam fragmen shader ForwardBase
untuk memeriksa hasil pekerjaan kita.
Karena Cull
nilai diberikan pada shader kami Off
, kedua sisi bilah rumput diberikan. Agar normal diarahkan pada arah yang benar, kami menggunakan parameter tambahan VFACE
yang kami tambahkan ke shader fragmen.Argumen fixed facing
akan mengembalikan angka positif jika kita menampilkan permukaan depan, dan angka negatif jika sebaliknya. Kami menggunakan ini dalam kode di atas untuk membalik normal jika perlu.Ketika Jumlah Kelengkungan Blade lebih besar dari 1, posisi garis singgung Z dari setiap simpul akan digeser dengan jumlah yang forward
diteruskan ke fungsi GenerateGrassVertex
. Kami akan menggunakan nilai ini untuk secara proporsional skala sumbu Z dari normal.
Terakhir, tambahkan kode ke shader fragmen untuk menggabungkan bayangan, pencahayaan terarah, dan pencahayaan sekitar. Saya merekomendasikan mempelajari informasi yang lebih terperinci tentang penerapan pencahayaan khusus dalam shader di tutorial saya tentang toon shader .
Kesimpulan
Dalam tutorial ini, rumput mencakup area kecil 10x10 unit. Agar shader dapat menutup ruang terbuka besar dengan tetap mempertahankan kinerja tinggi, optimisasi harus diperkenalkan. Anda dapat menerapkan tessellation berdasarkan jarak sehingga bilah rumput lebih sedikit dihasilkan dari kamera. Selain itu, untuk jarak yang jauh, bukannya bilah rumput individu, kelompok bilah rumput dapat ditarik menggunakan quadrangle tunggal dengan tekstur yang ditumpangkan.Tekstur rumput termasuk dalam paket Aset Standar mesin Unity. Banyak bilah rumput digambar pada satu segi empat, yang mengurangi jumlah segitiga dalam adegan.Meskipun secara alami kita tidak dapat menggunakan pembagi geometris dengan pembungkus permukaan, untuk meningkatkan atau memperluas fungsi pencahayaan dan naungan, jika Anda perlu menggunakan model pencahayaan Unity standar, Anda dapat mempelajari repositori GitHub ini , yang menunjukkan solusi untuk masalah ini dengan menunda render dan pengisian G-buffer secara manual.Kode sumber Shader di repositori GitHubPenambahan: kerja sama
Tanpa interoperabilitas, efek grafis mungkin tampak statis atau tidak bernyawa bagi pemain. Tutorial ini sudah sangat panjang, jadi saya tidak menambahkan bagian tentang interaksi objek dunia dengan rumput.Implementasi herbal interaktif yang naif akan mengandung dua komponen: sesuatu di dunia game yang dapat mengirimkan data ke shader untuk memberi tahu bagian mana dari rumput yang sedang berinteraksi dengan, dan kode dalam shader untuk menafsirkan data ini.Contoh bagaimana ini dapat diterapkan dengan air ditunjukkan di sini . Dapat diadaptasi untuk bekerja dengan rumput; alih-alih menggambar riak di tempat karakter berada, Anda dapat membalik bilah rumput ke bawah untuk mensimulasikan efek langkah.