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 windRotationdan 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 TriangleStreamfungsi 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 maxvertexcountuntuk 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 CGINCLUDEberikut 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 VertexOutputuntuk 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 widthberikut 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 GenerateGrassVertexuntuk menambahkan simpul ke aliran segitiga. Kami juga akan menambahkan satu panggilan di GenerateGrassVertexluar loop untuk membuat ujung bilah rumput. 
Lihatlah garis dengan deklarasi float3x3 transformMatrix- di sini kita memilih salah satu dari dua matriks transformasi: kita ambil transformationMatrixFacinguntuk simpul dari basis dan transformationMatrixuntuk 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 GenerateGrassVertexsehingga mendapat offset di Y , yang akan kita panggil forward. 
Untuk menghitung perpindahan setiap simpul, kami mengganti pownilai ke dalam fungsi t. Setelah tnaik 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 _BladeForwarddan _BladeCurvekami 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 LightModepenting 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, ForwardBasekita bisa menggunakan makro untuk mendapatkan nilai yang floatmenunjukkan 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 ForwardBasearahan 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 #ifsehingga 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 ForwardBaseuntuk memeriksa hasil pekerjaan kita. 
Karena Cullnilai diberikan pada shader kami Off, kedua sisi bilah rumput diberikan. Agar normal diarahkan pada arah yang benar, kami menggunakan parameter tambahan VFACEyang kami tambahkan ke shader fragmen.Argumen fixed facingakan 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 forwardditeruskan 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.