Di antara banyak game indie yang dirilis selama 10 tahun terakhir, salah satu favorit saya adalah
Journey . Berkat estetika yang menakjubkan dan soundtrack yang indah,
Journey telah menjadi contoh keunggulan di hampir setiap aspek pembangunan.
Saya adalah pengembang game dan artis teknis, jadi saya paling tertarik dengan cara rendering yang diberikan. Ini tidak hanya cantik, tetapi juga terkait langsung dengan gameplay dasar dan gameplay secara keseluruhan.
Journey secara harfiah dibangun dari pasir, dan tanpa efek yang luar biasa, permainan itu sendiri tidak akan ada.
Dalam artikel dua posting ini, saya akan memberikan penghormatan kepada warisan
Journey dengan mengajari Anda cara membuat ulang rendering pasir yang sama persis dengan menggunakan shader. Terlepas dari apakah bukit pasir dibutuhkan dalam gim Anda, seri tutorial ini akan memungkinkan Anda belajar cara membuat ulang estetika tertentu di gim Anda sendiri. Jika Anda ingin membuat ulang shader pasir indah yang digunakan dalam
Journey , Anda harus terlebih dahulu memahami bagaimana itu dibuat. Dan meskipun terlihat sangat kompleks, sebenarnya terdiri dari beberapa efek yang relatif sederhana. Pendekatan ini untuk menulis shader diperlukan untuk menjadi seniman teknis yang sukses. Oleh karena itu, saya berharap Anda akan melakukan
perjalanan ini bersama saya, di mana kami tidak hanya mengeksplorasi penciptaan shader, tetapi juga belajar bagaimana menggabungkan estetika dan gameplay.
Analisis pasir dalam Journey
Artikel ini, seperti banyak upaya lain untuk menciptakan rendering pasir
Journey , didasarkan pada laporan dari GDC
bahwa insinyur utama perusahaan John Edwards yang berjudul "
Sand Rendering in Journey ". Dalam pembicaraan ini, John Edwards berbicara tentang semua lapisan efek yang ditambahkan ke bukit pasir
Journey untuk mendapatkan tampilan yang tepat.
Laporan ini sangat berguna, tetapi dalam konteks tutorial ini, banyak batasan dan keputusan yang dibuat oleh John Edwards tidak penting. Kami akan mencoba menciptakan kembali shaders pasir, yang mengingatkan pada
Journey shader, terutama dengan referensi visual.
Mari kita mulai dengan jaring 3D sederhana dari bukit pasir yang sangat halus. Kredibilitas rendering pasir tergantung pada dua aspek: pencahayaan dan butiran. Cara menarik untuk memantulkan cahaya dari pasir disediakan oleh
model pencahayaan yang dimodifikasi. Dalam konteks pengkodean shader, model pencahayaan menentukan bayangan dan sorotan berdasarkan sifat-sifat model dan kondisi pencahayaan adegan.
Namun, semua ini tidak cukup untuk menciptakan ilusi realisme. Masalahnya adalah bahwa pasir tidak bisa dimodelkan dengan permukaan datar. Butir pasir harus dipertimbangkan. Itulah sebabnya ada dua efek terpisah yang bekerja secara langsung dengan
normal ke permukaan , yang dapat digunakan untuk mensimulasikan partikel pasir kecil di permukaan bukit pasir.
Diagram di bawah ini menunjukkan semua efek yang akan kita pelajari dalam tutorial ini. Dari sudut pandang teknis, perhitungan normal dilakukan sebelum pencahayaan diproses. Untuk kemudahan belajar, efeknya akan dijelaskan dalam urutan yang berbeda.
Warna menyebar
Efek shader pasir yang paling sederhana adalah
warna difusnya , yang secara kasar menggambarkan komponen
kusam dari keseluruhan penampilan. Warna difus dihitung berdasarkan warna asli objek dan kondisi pencahayaan. Sebuah bola yang dicat putih tidak akan menjadi putih sempurna di mana-mana, karena warna difus tergantung pada insiden cahaya di atasnya. Warna difus dihitung menggunakan model matematika yang mendekati pantulan cahaya dari suatu permukaan. Berkat laporan oleh John Edwards dengan GDC, kami tahu persis persamaan yang digunakan, yang ia sebut
reflektansi kontras difus ; ini didasarkan pada model
refleksi Lambert yang terkenal.
Sebelum dan sesudah menerapkan persamaanPasir normal
Geometri asli sepenuhnya mulus. Untuk mengimbangi ini,
permukaan normal model diubah menggunakan teknik yang disebut
pemetaan bump . Ini memungkinkan Anda menggunakan tekstur untuk mensimulasikan geometri yang lebih kompleks.
Pencahayaan tepi
Setiap tingkat
Perjalanan menggunakan palet warna terbatas. Karena ini, sangat sulit untuk memahami di mana satu gundukan berakhir dan yang lainnya dimulai. Untuk meningkatkan keterbacaan, teknik penyorotan kecil dari apa yang terlihat hanya di sepanjang tepi bukit pasir digunakan. Ini disebut
pencahayaan rim , dan ada banyak cara untuk menerapkannya. Untuk tutorial ini, saya memilih metode berdasarkan refleksi
Fresnel yang memodelkan refleksi pada permukaan yang dipoles pada apa yang disebut
sudut kejadian .
Cermin pantulan lautan
Salah satu aspek yang paling menyenangkan dari gameplay
Journey adalah kemampuan untuk "menjelajahi" bukit pasir. Ini mungkin mengapa perusahaan itu ingin pasir terasa lebih seperti cairan daripada padat. Untuk ini, pantulan yang kuat digunakan, yang sering dapat ditemukan di water shader. John Edwards menyebut efek ini
specular laut , dan dalam tutorial kami menerapkannya menggunakan
refleksi Blinn-Fong .
Refleksi cahaya
Menambahkan komponen specular laut ke shader pasir memberikannya tampilan yang lebih cair. Namun, itu masih tidak memungkinkan salah satu aspek visual terpenting dari pasir untuk disampaikan: refleksi yang terjadi secara acak. Dalam bukit pasir nyata, efek ini terjadi karena setiap butiran pasir memantulkan cahaya ke arahnya dan sangat sering salah satu dari sinar pantulan ini memasuki mata kita.
Refleksi kilau seperti itu (pantulan pantulan) terjadi bahkan di tempat-tempat di mana sinar matahari langsung tidak jatuh; itu melengkapi specular lautan dan meningkatkan rasa kredibilitas.
Gelombang pasir
Mengubah normals memungkinkan kita untuk mensimulasikan efek butiran kecil pasir yang menutupi permukaan bukit pasir. Di bukit pasir di dunia nyata, ombak yang disebabkan oleh angin sering muncul. Bentuknya bervariasi tergantung pada kemiringan dan posisi masing-masing bukit pasir relatif terhadap arah angin. Berpotensi, pola seperti itu dapat dibuat melalui tekstur benjolan, tetapi dalam hal ini tidak mungkin untuk mengubah bentuk bukit pasir secara real time. Solusi yang diajukan oleh John Edwards mirip dengan teknik yang disebut
triplanar shading : menggunakan empat tekstur yang berbeda, dicampur tergantung pada posisi dan kemiringan masing-masing gundukan.
Journey Sand Shader Anatomy
Unity memiliki banyak template shader untuk membantu Anda memulai. Karena kita tertarik pada bahan yang dapat menerima pencahayaan dan bayangan, kita harus mulai dengan
pelindung permukaan (surface shader).
Semua
permukaan shader dilakukan dalam dua tahap. Pertama,
fungsi permukaan disebut yang mengumpulkan sifat-sifat permukaan yang perlu dirender, misalnya
albedo ,
kekasaran ,
sifat logam ,
transparansi , dan
arah normal . Kemudian semua properti ini dipindahkan ke
fungsi pencahayaan , yang memperhitungkan pengaruh sumber cahaya eksternal dan menghitung naungan dan pencahayaan.
Fungsi permukaan
Mari kita mulai dengan apa yang menjadi inti dari fungsi permukaan kita, yang disebut dalam kode
surf
bawah ini. Satu-satunya properti yang perlu kita atur adalah
warna pasir dan
normal ke permukaan . Normal dari model 3D adalah vektor yang menunjukkan posisi permukaan. Vektor normal digunakan oleh fungsi pencahayaan untuk menghitung bagaimana cahaya akan dipantulkan. Mereka biasanya dihitung selama impor mesh. Namun, mereka dapat dimodifikasi untuk mensimulasikan geometri yang lebih kompleks. Di sinilah efek
normal gelombang pasir dan
gelombang pasir mendistorsi norma pasir untuk mensimulasikan kekasarannya.
void surf (Input IN, inout SurfaceOutput o) { o.Albedo = _SandColor; o.Alpha = 1; float3 N = float3(0, 0, 1); N = RipplesNormal(N); N = SandNormal (N); o.Normal = N; }
Saat menulis normals ke
o.Normal
mereka harus diekspresikan dalam
ruang singgung . Ini berarti bahwa vektor dipilih relatif terhadap permukaan model 3D. Artinya,
float3(0, 0, 1)
sebenarnya berarti bahwa tidak ada perubahan yang sebenarnya dilakukan pada model 3D normal.
Kedua fungsi,
RipplesNormal
, dan
SandNormal
menerima vektor normal dan memodifikasinya. Nanti kita akan lihat bagaimana ini bisa dilakukan.
Fungsi pencahayaan
Dalam fungsi pencahayaanlah semua efek lain diterapkan. Kode di bawah ini menunjukkan bagaimana masing-masing komponen dihitung dalam fungsi terpisah (warna difus, pencahayaan tepi, specular laut dan pantulan glitter). Kemudian semuanya digabungkan.
#pragma surface surf Journey fullforwardshadows float4 LightingJourney (SurfaceOutput s, fixed3 viewDir, UnityGI gi) { float3 diffuseColor = DiffuseColor (); float3 rimColor = RimLighting (); float3 oceanColor = OceanSpecular (); float3 glitterColor = GlitterSpecular (); float3 specularColor = saturate(max(rimColor, oceanColor)); float3 color = diffuseColor + specularColor + glitterColor; return float4(color * s.Albedo, 1); }
Metode menggabungkan komponen cukup sewenang-wenang dan memungkinkan kami mengubahnya untuk mempelajari kemungkinan artistik.
Biasanya, pantulan specular menumpuk di atas warna yang menyebar. Karena di sini kita tidak memiliki satu, tetapi tiga refleksi specular (
pelek cahaya ,
specular laut dan
glitter specular ), kita perlu lebih berhati-hati agar tidak membuat pasir
terlalu berkelap-kelip. Karena rim light dan specular laut adalah bagian dari efek yang sama, kita hanya dapat memilih nilai maksimum dari mereka. Glitter specular ditambahkan secara terpisah karena komponen ini menciptakan pasir yang berkedip-kedip.
Bagian 2. Warna Diffuse
Di bagian kedua posting, kita akan fokus pada model pencahayaan yang digunakan dalam game dan itu. cara membuatnya kembali di Unity.
Pada bagian sebelumnya, kami meletakkan dasar untuk apa yang secara bertahap akan berubah menjadi versi kami dari shader pasir Journey. Seperti disebutkan sebelumnya,
fungsi pencahayaan digunakan dalam
shader permukaan untuk menghitung efek pencahayaan, sehingga bayangan dan sorotan muncul di permukaan. Kami menemukan bahwa Journey memiliki beberapa efek yang termasuk dalam kategori ini. Kita akan mulai dengan efek paling dasar (dan paling sederhana) yang ditemukan di inti shader ini:
pencahayaan difus (
pencahayaan difus / difus).
Untuk saat ini, kami menghilangkan semua efek dan komponen lainnya, dengan fokus pada
pencahayaan pasir .
Fungsi pencahayaan yang kami
DiffuseColor
di bagian sebelumnya dari postingan yang disebut
LightingJourney
hanya mendelegasikan perhitungan warna pasir yang difus ke fungsi yang disebut
DiffuseColor
.
float4 LightingJourney (SurfaceOutput s, fixed3 viewDir, UnityGI gi) {
Karena fakta bahwa setiap efek mandiri dan disimpan dalam fungsinya sendiri, kode kami akan lebih modular dan bersih.
Lambert Reflection
Sebelum membuat pencahayaan yang tersebar "seperti di Journey", ada baiknya untuk melihat seperti apa fungsi pencahayaan yang tersebar "dasar". Teknik naungan paling sederhana untuk bahan matte disebut
reflektansi Lambertian . Model ini mendekati penampilan sebagian besar permukaan non-mengkilap dan non-logam. Namanya diambil dari ilmuwan ensiklopedis Swiss
Johann Heinrich Lambert , yang mengusulkan konsepnya pada 1760.
Konsep refleksi Lambert didasarkan pada ide sederhana:
kecerahan permukaan tergantung pada jumlah insiden cahaya di atasnya . Secara geometris, ini dapat ditunjukkan pada diagram di bawah ini, di mana bola diterangi oleh sumber cahaya jarak jauh. Meskipun area merah dan hijau pada bola menerima jumlah iluminasi yang sama, area permukaannya sangat berbeda. Jika lampu di wilayah merah didistribusikan ke area yang lebih besar, ini berarti bahwa setiap unit dari kotak merah menerima lebih sedikit cahaya daripada hijau.
Secara teoritis, pantulan Lambert tergantung pada sudut relatif antara
permukaan dan
cahaya yang datang . Dari sudut pandang matematika, kita mengatakan bahwa ini adalah fungsi dari
normal ke permukaan dan
arah iluminasi . Kuantitas ini dinyatakan dengan menggunakan dua vektor satuan panjang (disebut
vektor satuan )
N dan
L . Vektor tunggal adalah cara standar untuk menentukan
arah dalam konteks pengkodean shader.
Nilai N dan LNormal ke permukaan N Adalah satuan vektor yang diarahkan menjauh dari permukaan itu sendiri.
Dengan analogi, kita dapat mengasumsikan bahwa arah pencahayaan L. menunjuk dari sumber cahaya dan mengikuti ke arah mana cahaya bergerak. Tetapi tidak demikian: arah iluminasi adalah vektor tunggal yang menunjuk ke arah dari mana cahaya datang.
Ini bisa membingungkan, terutama jika Anda baru membuat shader. Namun, berkat notasi tersebut, persamaan menjadi lebih sederhana.
Refleksi Lambert dalam PersatuanSebelum
Standar Unity 5
Shader , refleksi Lambert adalah model standar untuk permukaan yang diarsir.
Anda masih dapat mengaksesnya di Inspektur Material: di
Legacy shader, itu disebut
Diffuse .
Jika Anda menulis shader permukaan Anda sendiri, maka refleksi Lambert tersedia sebagai fungsi pencahayaan yang disebut
Lambert
:
#pragma surface surf Lambert fullforwardshadows
Implementasinya dapat ditemukan dalam fungsi
LightingLambert
didefinisikan dalam file
CGIncludes\Lighting.cginc
.
Lambert Reflection and ClimateRefleksi Lambert adalah model yang cukup lama, tetapi memberikan pemahaman tentang konsep-konsep kompleks seperti bayangan permukaan. Ini juga dapat digunakan untuk menjelaskan banyak fenomena lainnya. Sebagai contoh, diagram yang sama menjelaskan mengapa lebih dingin di kutub planet daripada di ekuator.
Setelah melihat lebih dekat, kita dapat melihat bahwa permukaan menerima jumlah iluminasi maksimum ketika normalnya sejajar dengan arah iluminasi. Dan sebaliknya: tidak ada cahaya jika dua vektor satuan saling tegak lurus.
Jelas, sudut antara
N dan
L kritis untuk refleksi menurut Lambert. Apalagi kecerahannya maksimum dan sama dengan
100% ketika sudutnya
0 dan minimal (
0% ) ketika sudut cenderung
90 circ . Jika Anda terbiasa dengan
aljabar vektor , Anda bisa memahami bahwa kuantitas mewakili refleksi Lambert
I sama dengan
N cdotL dimana operatornya
cdot disebut
produk skalar .
(1)
$$ menampilkan $$ \ mulai {persamaan *} I = N \ cdot L \ end {persamaan *} $$ menampilkan $$
Produk skalar adalah ukuran "kebetulan" dari dua vektor relatif satu sama lain, dan bervariasi dalam interval dari
+1 (untuk dua vektor identik) ke
−1 (untuk dua vektor yang berlawanan). Produk skalar adalah dasar dari peneduhan, yang saya teliti secara rinci dalam tutorial
Rendering dan Model Pencahayaan Berbasis Fisik .
Implementasi
Dan untuk
N dan untuk
L Anda dapat dengan mudah mengakses fitur pencahayaan shader permukaan melalui
s.Normal
dan
gi.light.dirin
. Untuk mempermudah, kami akan mengganti nama mereka dalam kode shader menjadi
N
dan
L
float3 DiffuseColor(float3 N, float3 L) { float NdotL = saturate( dot(N, L) ); return NdotL; }
Fungsi
saturate
membatasi nilai dari
0 sebelumnya
1 . Namun, karena produk skalar berada dalam kisaran dari
−1 sebelumnya
+1 , kita hanya perlu bekerja dengan nilai negatifnya. Itulah sebabnya refleksi Lambert sering dilaksanakan sebagai berikut:
float NdotL = max(0, dot(N, L) );
Refleksi kontras cahaya sekitar
Meskipun refleksi Lambert menaungi sebagian besar material, itu tidak akurat secara fisik maupun fotorealistik. Dalam game yang lebih lama, shader Lambert digunakan secara luas. Game yang menggunakan teknik ini sering
tampak kuno karena secara tidak sengaja dapat mereproduksi estetika game lama. Jika Anda tidak berusaha untuk ini, maka refleksi Lambert harus dihindari dan menggunakan teknologi yang lebih modern.
Salah satu model tersebut adalah model
refleksi Oren-Nayyar , yang pada awalnya ditetapkan dalam artikel
Generalisasi Lamect's Reflectance Model , yang diterbitkan pada 1994 oleh Michael Oren dan Sri C. Nayyar. Model Oren-Nayyar adalah generalisasi dari refleksi Lambert dan dirancang khusus untuk permukaan kasar. Awalnya, pengembang Journey ingin menggunakan refleksi Oren-Nayyar sebagai dasar untuk shader pasir mereka. Namun, ide ini ditinggalkan karena biaya komputasi yang tinggi.
Dalam laporannya tahun 2013, seniman teknis John Edwards menjelaskan bahwa model refleksi yang dibuat untuk Journey sand didasarkan pada serangkaian trial and error. Pengembang bermaksud untuk tidak menciptakan rendering fotorealistik gurun, tetapi untuk menghidupkan kembali kehidupan menjadi beton, estetika yang segera dikenali.
Menurutnya, model bayangan yang dihasilkan sesuai dengan persamaan ini:
(2)
$$ menampilkan $$ \ mulai {persamaan *} I = 4 * \ kiri (\ kiri (N \ odot \ kiri [1, 0,3, 1 \ kanan] \ kanan) \ cdot L \ kanan) \ end {persamaan *} $$ tampilan $$
dimana
odot -
elemen -
produk bijak dari dua vektor.
float3 DiffuseColor(float3 N, float3 L) { Ny *= 0.3; float NdotL = saturate(4 * dot(N, L)); return NdotL; }
Reflection Model (2) John Edwards menyebut
kontras kontras , jadi kami akan menggunakan nama ini sepanjang tutorial.
Animasi di bawah ini menunjukkan perbedaan dalam naungan Lambert (kiri) dan kontras yang berbeda dari Journey (kanan).
Apa arti dari 4 dan 0,3?Meskipun perbedaan kontras tidak dirancang untuk akurat secara fisik, kita masih dapat mencoba untuk memahami apa fungsinya.
Pada intinya, masih menggunakan refleksi Lambert. Perbedaan yang jelas pertama adalah bahwa hasil keseluruhan dikalikan dengan 4 . Ini berarti bahwa semua piksel yang biasanya diterima 25 % pencahayaan sekarang akan bersinar seolah menerima 100 % pencahayaan. Dengan mengalikan semuanya dengan 4 naungan yang lemah menurut Lambert menjadi jauh lebih kuat, dan wilayah transisi antara gelap dan terang lebih kecil. Dalam hal ini, bayangan menjadi lebih tajam.
Pengaruh penggandaan komponen y
pada arah normal 0 , 3 menjelaskan jauh lebih sulit. Sebagai komponen dari perubahan vektor, arah umum di mana ia menunjuk perubahan. Mengurangi nilai komponen y
untuk semuanya 30 % dari nilai aslinya, pantulan kontras yang menyebar menyebabkan bayangan menjadi lebih vertikal.
Catatan: produk skalar secara langsung mengukur sudut antara dua vektor hanya jika keduanya memiliki panjang 1 . Perubahan yang dibuat mengurangi panjang normal N yang tidak lagi menjadi vektor satuan.
Dari nuansa abu-abu hingga warna
Semua animasi yang ditunjukkan di atas memiliki nuansa abu-abu, karena mereka menunjukkan nilai-nilai model refleksi mereka, bervariasi dalam interval dari
0 sebelumnya
1 ". Kita dapat dengan mudah menambahkan warna dengan menggunakan
NdotL
sebagai koefisien interpolasi antara dua warna: satu untuk teduh sepenuhnya dan yang lainnya untuk pasir yang menyala sepenuhnya.
float3 _TerrainColor; float3 _ShadowColor; float3 DiffuseColor(float3 N, float3 L) { Ny *= 0.3; float NdotL = saturate(4 * dot(N, L)); float3 color = lerp(_ShadowColor, _TerrainColor, NdotL); return color; }
Bagian 3. Pasir normal
Pada bagian ketiga, kita akan fokus pada pembuatan peta normal yang mengubah model 3D halus menjadi bukit pasir.
Di bagian sebelumnya dari tutorial, kami menerapkan pencahayaan difus dari pasir Journey. Bila hanya menggunakan efek ini, bukit pasir gurun akan terasa agak datar dan membosankan.
Salah satu efek Journey yang paling menarik adalah butiran pasir. Melihat screenshot apa pun, tampaknya bagi kita bahwa bukit pasir tidak mulus dan homogen, tetapi dibuat dari jutaan butiran pasir mikroskopis.
Efek ini dapat dicapai dengan menggunakan teknik yang disebut
pemetaan benjolan , yang memungkinkan cahaya memantul dari permukaan datar seolah-olah lebih kompleks. Lihat bagaimana efek ini mengubah tampilan render:
Perbedaan kecil dapat dilihat dengan meningkatnya:
Kami berurusan dengan peta normal
Pasir terdiri dari butiran pasir yang tak terhitung jumlahnya, masing-masing memiliki bentuk dan komposisi sendiri (lihat di bawah). Setiap partikel individu memantulkan cahaya ke arah yang berpotensi acak. Salah satu cara untuk mewujudkan efek ini adalah dengan membuat model 3D yang mengandung semua butiran pasir mikroskopis ini. Tetapi karena banyaknya jumlah poligon yang diperlukan, pendekatan ini tidak layak.
Tetapi ada solusi lain yang sering digunakan untuk mensimulasikan geometri yang lebih kompleks dibandingkan dengan model 3D nyata. Setiap dhuwur atau muka model 3D dikaitkan dengan parameter yang disebut
arah normalnya . Ini adalah vektor satuan panjang yang digunakan untuk menghitung pantulan cahaya pada permukaan model 3D. Artinya, untuk mensimulasikan pasir, Anda perlu mensimulasikan distribusi butiran pasir yang tampaknya acak ini, dan oleh karena itu bagaimana mereka memengaruhi permukaan normal.
Ini bisa dilakukan dengan banyak cara. Yang paling sederhana adalah membuat tekstur yang mengubah arah normals asli dari model bukit pasir.
Normal ke permukaan N dalam kasus umum, dihitung dengan geometri model 3D. Namun, Anda dapat memodifikasinya menggunakan
peta normal . Peta normal adalah tekstur yang memungkinkan Anda mensimulasikan geometri yang lebih kompleks dengan mengubah orientasi lokal normals ke permukaan. Teknik ini sering disebut
pemetaan benjolan .
Mengubah normals adalah tugas yang cukup sederhana yang dapat dilakukan dalam fungsi
surf
permukaan shader . Fungsi ini mengambil dua parameter, salah satunya adalah
struct
disebut
SurfaceOutput
. Ini berisi semua properti yang diperlukan untuk rendering bagian dari model 3D, dari warnanya (
o.Albedo
) ke transparansi (
o.Alpha
). Parameter lain yang dikandungnya adalah arah normal (
o.Normal
), yang dapat ditulis ulang untuk mengubah cara cahaya tercermin pada model.
Menurut dokumentasi Unity pada
Surface Shaders , semua normals yang ditulis ke struktur
o.Normal
harus dinyatakan dalam
ruang singgung :
struct SurfaceOutput { fixed3 Albedo;
Dengan demikian, kami dapat melaporkan bahwa vektor satuan harus dinyatakan dalam sistem koordinat relatif terhadap normal mesh. Misalnya, saat menulis ke
o.Normal
nilai
float3(0, 0, 1)
normal akan tetap tidak berubah.
void surf (Input IN, inout SurfaceOutput o) { o.Albedo = _SandColor; o.Alpha = 1; o.Normal = float3(0, 0, 1); }
Ini karena vektor
float3(0, 0, 1)
sebenarnya adalah vektor normal yang dinyatakan relatif terhadap geometri model 3D.
Jadi, untuk mengubah normal ke permukaan di
permukaan shader , kita hanya perlu menulis vektor baru di
fungsi permukaan di
o.Normal
:
void surf (Input IN, inout SurfaceOutput o) { o.Albedo = _SandColor; o.Alpha = 1; o.Normal = ...
Di sisa posting, kita akan membuat perkiraan awal, yang akan kita susun pada bagian keenam dari tutorial.
Pasir normal
Bagian yang paling bermasalah adalah memahami
bagaimana butiran pasir berubah normal ke permukaan. Meskipun secara individual setiap butir pasir dapat menyebarkan cahaya ke segala arah, secara keseluruhan, sesuatu yang lain terjadi. Setiap pendekatan yang akurat secara fisik harus mempelajari distribusi vektor normal pada permukaan pasir dan memodelkannya secara matematis. Model seperti itu benar-benar ada, tetapi solusi yang disajikan dalam tutorial kami jauh lebih sederhana, dan pada saat yang sama sangat efektif.
Pada setiap titik dalam model,
vektor satuan acak disampel dari tekstur. Kemudian, normal ke permukaan miring dengan jumlah tertentu ke arah vektor ini. Dengan terciptanya tekstur acak yang benar dan pemilihan jumlah pencampuran yang tepat, kita dapat menggeser normal ke permukaan sedemikian rupa untuk menciptakan rasa butiran, tanpa kehilangan kelengkungan keseluruhan bukit pasir.
Nilai acak dapat disampel menggunakan tekstur yang diisi dengan warna acak. Komponen R, G, dan B dari setiap piksel digunakan sebagai komponen X, Y, dan Z dari vektor normal. Komponen warna berada dalam kisaran
left[0,1 right] , jadi mereka perlu dikonversi ke interval
kiri[−1,+1 kanan] . Kemudian vektor yang dihasilkan dinormalisasi sehingga panjangnya sama dengan
1 .
Buat tekstur acakAda banyak cara untuk menghasilkan tekstur acak. Untuk mendapatkan efek yang diinginkan, yang terpenting adalah distribusi umum vektor acak yang dapat disampel dari tekstur.
Pada gambar di atas, setiap piksel benar-benar acak. Tidak ada arah umum (warna) yang berlaku dalam tekstur, karena setiap nilai memiliki probabilitas yang sama dengan yang lainnya. Tekstur ini memberi kita sejenis pasir yang menyebarkan cahaya ke segala arah.
Selama pembicaraan GDC, John Edwards memperjelas bahwa tekstur acak yang digunakan untuk pasir di Journey dihasilkan dari distribusi Gaussian. Ini memastikan bahwa arah yang berlaku bertepatan dengan normal ke permukaan.
Apakah vektor acak perlu dinormalisasi?Gambar yang saya gunakan untuk sampel vektor acak dihasilkan menggunakan proses yang sepenuhnya acak. Tidak hanya setiap piksel yang dihasilkan secara individual: komponen R, G dan B dari satu piksel juga tidak tergantung satu sama lain. Artinya, dalam kasus umum, vektor sampel dari tekstur ini tidak akan dijamin memiliki panjang yang sama dengan 1 .
Tentu saja, Anda dapat menghasilkan tekstur di mana setiap piksel saat mengkonversi dari l e f t [ 0 , 1 r i g h t ] masuk kiri[−1,+1 kanan] dan sebenarnya harus memiliki panjang 1 . Namun, dua masalah muncul di sini.
, . -, mip-, .
, .
Implementasi
Di bagian sebelumnya tutorial, kami memperkenalkan konsep "peta normal" ketika muncul di garis pertama fungsi permukaan surf
. Mengingat diagram yang ditunjukkan di awal artikel, Anda dapat melihat bahwa dua efek diperlukan untuk membuat ulang rendering pasir Journey. Yang pertama ( pasir normal ) yang kita bahas di bagian artikel ini, dan yang kedua ( gelombang pasir ) kita pelajari di bagian keenam. void surf (Input IN, inout SurfaceOutput o) { o.Albedo = _SandColor; o.Alpha = 1; float3 N = float3(0, 0, 1); N = RipplesNormal(N);
Pada bagian sebelumnya, kami memperkenalkan konsep pemetaan benjolan, yang menunjukkan kepada kami bahwa bagian dari efek akan memerlukan pengambilan sampel tekstur (disebut dalam kode uv_SandTex
).Masalah dengan kode di atas adalah bahwa untuk perhitungan Anda perlu mengetahui posisi sebenarnya dari titik yang kita gambar. Faktanya, Anda memerlukan koordinat UV untuk sampel tekstur , yang menentukan piksel mana yang akan dibaca. Jika model 3D yang kita gunakan relatif datar dan memiliki konversi UV, maka kita dapat menggunakan UV-nya untuk sampel tekstur acak. N = WavesNormal(IN.uv_SandTex.xy, N); N = SandNormal (IN.uv_SandTex.xy, N);
Atau Anda juga dapat menggunakan posisi di dunia ( IN.worldPos
) dari titik yang diberikan.Sekarang kita akhirnya bisa fokus pada SandNormal
implementasinya. Seperti yang dinyatakan sebelumnya di bagian ini, idenya adalah untuk mengambil sampel piksel dari tekstur acak dan menggunakannya (setelah dikonversi ke vektor satuan) sebagai normal baru. sampler2D_float _SandTex; float3 SandNormal (float2 uv, float3 N) {
Bagaimana cara memperbesar tekstur acak?UV- 3D- , . , .
, Unity . ,
_SandText_ST
. Unity ( )
_SandTex
.
_SandText_ST
: . ,
Tiling Offset :
,
TRANSFORM_TEX
:
sampler2D_float _SandTex; float4 _SandTex_ST; float3 SandNormal (float2 uv, float3 N) {
Miringkan normals
Cuplikan kode yang ditunjukkan di atas berfungsi, tetapi tidak memberikan hasil yang sangat baik. Alasannya sederhana: jika kita hanya mengembalikan normal secara acak, tetapi pada dasarnya kehilangan perasaan kelengkungan. Bahkan, arah normal digunakan untuk menghitung bagaimana cahaya harus dipantulkan dari permukaan, dan tujuan utamanya adalah untuk menaungi model sesuai dengan kelengkungannya.Perbedaannya bisa dilihat pada gambar di bawah ini. Di atas, normals dari bukit pasir sepenuhnya acak, dan tidak mungkin untuk memahami di mana ujung yang satu dan yang lainnya dimulai. Dari bawah, hanya model normal yang digunakan, karena itu permukaannya terlalu halus.Kedua solusi tidak cocok untuk kita. Kami membutuhkan sesuatu di antaranya. Arah acak yang diambil dari tekstur harus digunakan untuk memiringkan normal dengan jumlah tertentu, seperti yang ditunjukkan di bawah ini:Operasi yang dijelaskan dalam diagram disebut slerp , yang merupakan singkatan dari interpolasi linear bola (interpolasi linear bola). Slerp bekerja persis sama dengan lerp, dengan satu pengecualian - dapat digunakan untuk interpolasi dengan aman di antara vektor satuan, dan hasil operasi akan menjadi vektor satuan lainnya.Sayangnya, implementasi slerp yang benar cukup mahal. Dan untuk efeknya, setidaknya berdasarkan kebetulan, tidak logis untuk menggunakannya.Tunjukkan pada saya persamaan slerp,
p0 dan
p1 , . Lalu
slerp :
(1)
Ω —
p0 dan
p1 , :
(2)
Penting untuk dicatat bahwa jika kita menggunakan interpolasi linier tradisional , vektor yang dihasilkan akan terlihat sangat berbeda:Operasi Lerp antara dua vektor satuan yang terpisah tidak selalu membuat vektor satuan lainnya. Sebenarnya, ini tidak pernah terjadi, kecuali jika koefisiennya1 atau0 .
Meskipun demikian, menormalkan hasil lerp sebenarnya menghasilkan vektor satuan yang secara mengejutkan mendekati hasil yang dihasilkan oleh slerp: float3 nlerp(float3 n1, float3 n2, float t) { return normalize(lerp(n1, n2, t)); }
Teknik ini, yang disebut nlerp , memberikan perkiraan dekat dari slerp. Penggunaannya dipopulerkan oleh Casey Muratori , salah satu pengembang The Witness . Jika Anda tertarik untuk mempelajari lebih lanjut tentang topik ini, saya sarankan Memahami artikel Slerp. Then Not Using It oleh Jonathan Blow dan Math Magician - Lerp, Slerp, dan Nlerp .Berkat nlerp, sekarang kita dapat memiringkan vektor normal secara efisien ke sisi acak, diambil sampel dari _SandTex
: sampler2D_float _SandTex; float _SandStrength; float3 SandNormal (float2 uv, float3 N) {
Hasilnya ditunjukkan di bawah ini:Apa selanjutnya
Pada bagian selanjutnya, kita akan mempertimbangkan refleksi yang berkelap-kelip, berkat bukit-bukit pasir yang akan menyerupai lautan.Ucapan Terima Kasih
Permainan video Journey dikembangkan oleh Thatgamecompany dan diterbitkan oleh Sony Computer Entertainment . Ini tersedia untuk PC ( Epic Store ) dan PS4 ( PS Store ).Model 3D latar belakang bukit pasir dan opsi pencahayaan diciptakan oleh Jiadi Deng .Model 3D karakter Journey ditemukan di forum FacePunch (sekarang sudah ditutup).Paket Persatuan
Jika Anda ingin membuat ulang efek ini, maka paket Unity lengkap dapat diunduh dari Patreon . Ini mencakup semua yang Anda butuhkan, dari shader hingga model 3D.