Eksplorasi Journey permainan shader pasir

Awal dari serangkaian artikel di sini

gambar

Bagian 4: Gambar Cermin


Pada bagian ini, kita akan fokus pada pantulan cermin, berkat gundukan yang menyerupai lautan pasir.

Salah satu efek paling menarik dari rendering pasir Journey adalah bagaimana bukit pasir berkilau di bawah sinar cahaya. Refleksi ini disebut specular . Nama ini berasal dari kata Latin spekulum , yang berarti "cermin" . Refleksi specular adalah konsep "payung" yang menggabungkan semua jenis interaksi di mana cahaya sangat tercermin dalam satu arah daripada tersebar atau diserap. Berkat pantulan specular, baik air dan permukaan yang dipoles pada sudut tertentu terlihat berkilau.

Dalam PerjalananAda tiga jenis pantulan cermin: penerangan tepi , pantulan laut dan pantulan kilau , ditunjukkan dalam diagram di bawah ini. Pada bagian ini kita akan melihat dua tipe pertama.




Sebelum dan sesudah menerapkan pantulan cermin

Pencahayaan rim


Anda mungkin memperhatikan bahwa di setiap tingkat Perjalanan , serangkaian warna terbatas disajikan. Dan sementara ini menciptakan estetika yang kuat dan bersih, pendekatan ini memperumit rendering pasir. Bukit pasir diberikan hanya dengan jumlah warna yang terbatas, sehingga sulit bagi pemain untuk memahami di mana yang satu berakhir pada jarak yang jauh dan yang lainnya dimulai.

Untuk mengimbangi ini, tepi setiap gundukan memiliki efek cahaya sedikit, menyoroti konturnya. Hal ini memungkinkan bukit pasir untuk tidak bersembunyi di cakrawala dan menciptakan ilusi lingkungan yang jauh lebih luas dan lebih kompleks.

Sebelum mulai mencari tahu bagaimana menerapkan efek ini, mari kita perluas fungsi pencahayaan dengan menambahkan warna difus ke dalamnya (dipertimbangkan oleh kami di bagian sebelumnya artikel) dan komponen umum baru dari refleksi specular.

float4 LightingJourney (SurfaceOutput s, fixed3 viewDir, UnityGI gi)
{
    // Lighting properties
    float3 L = gi.light.dir;
    float3 N = s.Normal;

    // Lighting calculation
    float3 diffuseColor	= DiffuseColor (N, L);
    float3 rimColor     = RimLighting  (N, V);

    // Combining
    float3 color = diffuseColor + rimColor;

    // Final color
    return float4(color * s.Albedo, 1);
}

Dalam cuplikan kode di atas, kita melihat bahwa komponen cermin dari pencahayaan rim, yang disebut rimColor, hanya ditambahkan ke warna difus asli.

Rentang Dinamis Tinggi dan Efek Bloom
, โ€” RGB 01. . , 1.

, , 01. , , 1. High Dynamic Range, 1ยซยป . bloom, . .

Refleksi Fresnel


Cahaya tepi dapat diwujudkan dengan berbagai cara. Pengkodean shader paling populer menggunakan model refleksi Fresnel yang terkenal .

Untuk memahami persamaan yang mendasari refleksi Fresnel, akan sangat membantu untuk memvisualisasikan di mana itu terjadi. Diagram di bawah ini menunjukkan bagaimana bukit pasir terlihat melalui kamera (berwarna biru). Panah merah menunjukkan permukaan normal bagian atas bukit pasir, di mana ia harus menjadi gambar cermin. Sangat mudah untuk melihat bahwa semua tepi bukit pasir memiliki sifat yang sama: normal (N , merah) tegak lurus denganarah pandang(V , biru).


Mirip dengan apa yang kami lakukan di bagian tentang Diffuse Color, Anda dapat menggunakan produk skalar NdanV untuk mendapatkan ukuran paralelisme mereka. Pada kasus iniNโ‹…V sama dengan0 , karena dua vektor satuan adalah tegak lurus; alih-alih kita bisa menggunakan1โˆ’Nโ‹…V untuk mendapatkan ukuran non-paralelisme mereka.

Penggunaan langsung1โˆ’Nโ‹…V tidak akan memberi kita hasil yang baik, karena pantulannya akan terlalu kuat. Jika kita ingin membuat refleksilebih tajam, kita bisa mengambil ekspresi dalam derajat. Tingkat besarnya dari0 hingga1 tetap terbatas pada satu interval, tetapi transisi antara gelap dan terang menjadi lebih tajam.

Model refleksi Fresnel menyatakan bahwa kecerahan cahayaI didefinisikan sebagai berikut:

I=(1โˆ’Nโ‹…V)powerโˆ—strength(1)


Dimana powerdanstrength dua parameter yang dapat digunakan untuk mengontrol kontras dan kekuatan efeknya. Parameterpower danstrength kadang - kadang disebutspeculardangloss, tetapi namanya mungkin berbeda.

Persamaan (1) sangat mudah dikonversi ke kode:

float _TerrainRimPower;
float _TerrainRimStrength;
float3 _TerrainRimColor;

float3 RimLighting(float3 N, float3 V)
{
    float rim = 1.0 - saturate(dot(N, V));
    rim = saturate(pow(rim, _TerrainRimPower) * _TerrainRimStrength);
    rim = max(rim, 0); // Never negative
    return rim * _TerrainRimColor;
}

Hasilnya ditunjukkan dalam animasi di bawah ini.


Specular laut


Salah satu aspek paling asli dari gameplay Journey adalah bahwa kadang-kadang seorang pemain benar-benar dapat "menjelajahi" bukit pasir. Insinyur terkemuka John Edwards menjelaskan bahwa perusahaan itu berusaha membuat pasir lebih terasa tidak padat, tetapi cair.

Dan ini tidak sepenuhnya salah, karena pasir dapat dianggap sebagai perkiraan cairan yang sangat kasar. Dan dalam kondisi tertentu, misalnya, dalam jam pasir, ia bahkan berperilaku seperti cairan.

Untuk memperkuat gagasan bahwa pasir mungkin memiliki komponen cair, Journey telah menambahkan efek refleksi kedua, yang sering ditemukan dalam benda cair. John Edwards menyebutnya specular lautan: Idenya adalah untuk mendapatkan jenis refleksi yang sama yang terlihat di permukaan laut atau danau saat matahari terbenam (lihat di bawah).


Seperti sebelumnya, kami akan membuat perubahan pada fungsi pencahayaan LightingJourneyuntuk menambahkan tipe baru dari refleksi specular ke dalamnya.

float4 LightingJourney (SurfaceOutput s, fixed3 viewDir, UnityGI gi)
{
    // Lighting properties
    float3 L = gi.light.dir;
    float3 N = s.Normal;
    float3 V = viewDir;

    // Lighting calculation
    float3 diffuseColor	= DiffuseColor  (N, L);
    float3 rimColor     = RimLighting   (N, V);
    float3 oceanColor   = OceanSpecular (N, L, V);

    // Combining
    float3 specularColor = saturate(max(rimColor, oceanColor));
    float3 color = diffuseColor + specularColor;

    // Final color
    return float4(color * s.Albedo, 1);
}

Mengapa kita mengambil maksimum dua komponen refleksi?
, rim lighting ocean specular. , , -. , .

, .

Refleksi cermin pada air sering diwujudkan menggunakan refleksi Blinn-Fong , yang merupakan solusi berbiaya rendah untuk bahan mengkilap. Ini pertama kali dijelaskan oleh James F. Blinn pada tahun 1977 (artikel: " Model Refleksi Cahaya untuk Gambar yang Disintesis Komputer ") sebagai perkiraan teknik naungan sebelumnya yang dikembangkan oleh Bui Tyong Fong pada tahun 1973 (artikel: " Penerangan untuk Gambar Buatan Komputer ") .

Saat menggunakan Blinnu-Phong shading, luminosityIPermukaan I diberikan oleh persamaan berikut:

I=(Nโ‹…H)powerโˆ—strength(2)


Dimana

H=V+Lโ€–V+Lโ€–(3)


Penyebut persamaan (3) membagi vektor V+Lpada panjangnya. Ini memastikan hal ituH memiliki panjang 1. Fungsi shader yang setara untuk melakukan operasi ini adalah ini normalize. Dari sudut pandang geometris,H mewakili vektor antara Vdan L, dan karena itu disebut setengah vektor .



Mengapa H antara V dan L?
, , Hโ€” VL.

, . VLLVVL.

, , V+LL+V, . , :


, , . , ( V+L) . , ( ).


, V+Lโ€” VL, 1. , 1, ( ).

Penjelasan lebih rinci tentang refleksi Blinn-Fong dapat ditemukan di tutorial Model Rendering dan Pencahayaan Berbasis Fisik . Di bawah ini adalah implementasi sederhana dari kode shader.

float _OceanSpecularPower;
float _OceanSpecularStrength;
float3 _OceanSpecularColor;

float3 OceanSpecular (float3 N, float3 L, float3 V)
{
    // Blinn-Phong
    float3 H = normalize(V + L); // Half direction
    float NdotH = max(0, dot(N, H));
    float specular = pow(NdotH, _OceanSpecularPower) * _OceanSpecularStrength;
    return specular * _OceanSpecularColor;
}

Animasi ini menyajikan perbandingan bayangan difus tradisional menurut Lambert dan mirror menurut Blinn-Fong:


Bagian 5: refleksi cemerlang


Pada bagian ini, kita akan menciptakan refleksi cemerlang yang biasanya terlihat di bukit pasir.

Tak lama setelah menerbitkan seri artikel saya, Julian Oberbek dan Paul Nadelek melakukan upaya mereka sendiri untuk menciptakan kembali sebuah adegan yang terinspirasi oleh permainan Journey in Unity. Tweet di bawah ini menunjukkan bagaimana mereka menyempurnakan refleksi brilian untuk memberikan integritas temporal yang lebih besar. Baca lebih lanjut tentang penerapannya dalam sebuah artikel di IndieBurg Mip Map Folding .


Pada bagian sebelumnya dari kursus, kami mengungkapkan penerapan dua fungsi cermin dalam rendering pasir Journey : pencahayaan rim dan specular laut . Pada bagian ini, saya akan menjelaskan bagaimana mengimplementasikan versi terakhir dari refleksi specular: glitter .


Jika Anda pernah ke padang pasir, Anda mungkin memperhatikan bagaimana pasir sebenarnya berkilau. Seperti dibahas di bagian normals pasir, setiap butir pasir berpotensi memantulkan cahaya dalam arah acak. Karena sifat bilangan acak, sebagian dari sinar yang dipantulkan ini akan jatuh ke dalam kamera. Karena itu, sembarang titik pasir akan tampak sangat cerah. Kilau ini sangat sensitif terhadap pergerakan, karena perubahan sekecil apa pun akan mencegah sinar yang dipantulkan memasuki kamera.

Di gim lain, seperti Astroneer dan Slime Rancher, pantulan brilian digunakan untuk pasir dan gua.



Gloss: sebelum dan sesudah menerapkan efek.

Lebih mudah untuk mengevaluasi karakteristik gloss ini dalam gambar yang lebih besar:



Tanpa diragukan lagi, efek kilau pada bukit pasir nyata sepenuhnya bergantung pada kenyataan bahwa beberapa butir pasir secara acak memantulkan cahaya ke mata kita. Sebenarnya, ini adalah persis apa yang telah kita modelkan pada bagian kedua dari kursus yang ditujukan untuk pasir normal, ketika kita memodelkan distribusi acak normalnya. Jadi mengapa kita perlu efek lain untuk ini?

Jawabannya mungkin tidak terlalu jelas. Mari kita bayangkan bahwa kita sedang mencoba membuat ulang efek gloss hanya dengan bantuan orang normal. Sekalipun semua normals diarahkan ke kamera, pasir tetap tidak akan bersinar, karena normals hanya dapat memantulkan jumlah cahaya yang tersedia dalam adegan. Artinya, paling-paling, kita hanya akan memantulkan 100% cahaya (jika pasir benar-benar putih).

Tetapi kita membutuhkan sesuatu yang lain. Jika kita ingin piksel tampak begitu terang sehingga cahaya menyebar ke piksel yang berdekatan dengannya, maka warnanya harus lebih besar1. Ini terjadi karena di Unity, ketika filter bloom diterapkan ke kamera menggunakan efek post-processing , warnanya lebih cerah.1menyebar ke piksel tetangga dan menghasilkan lingkaran cahaya yang menciptakan perasaan bahwa beberapa piksel bersinar. Ini adalah dasar dari rendering HDR .

Jadi tidak, normal overlay tidak dapat digunakan dengan cara sederhana untuk membuat permukaan mengkilap. Oleh karena itu, efek ini lebih nyaman untuk diimplementasikan sebagai proses terpisah.

Teori Microfaces


Untuk mendekati situasi lebih formal, kita perlu memahami bukit pasir yang terdiri dari cermin mikroskopis, yang masing-masing memiliki arah acak. Pendekatan ini disebut teori microfacet , di mana masing-masing cermin kecil ini disebut microfacet . Fondasi matematis dari kebanyakan model naungan modern didasarkan pada teori wajah mikro, termasuk model shader standar dari Unity .

Langkah pertama adalah membagi permukaan bukit pasir menjadi permukaan mikro dan menentukan orientasi masing-masingnya. Seperti yang telah disebutkan, kami melakukan sesuatu yang serupa di bagian tutorial tentang normals of sand, di mana posisi UV model 3D gundukan digunakan untuk sampel tekstur acak. Pendekatan yang sama dapat digunakan di sini untuk melampirkan orientasi acak ke setiap sisi mikro. Ukuran setiap microface akan tergantung pada skala tekstur dan tingkat tekstur-mip . Tugas kita adalah menciptakan estetika tertentu, dan bukan keinginan untuk fotorealisme; pendekatan ini akan cukup baik bagi kita.

Setelah mencicipi tekstur acak, kita dapat mengaitkan arah acak dengan setiap butiran pasir / aspek mikro gundukan. Sebut saja diaG. Ini menunjukkan arah kecerahan , yaitu, arah normal butiran pasir yang kita lihat. Sinar cahaya yang jatuh pada butiran pasir akan dipantulkan, dengan mempertimbangkan fakta bahwa segi mikro adalah cermin ideal yang berorientasi pada arah.G. Sinar cahaya pantulan yang dihasilkan harus masuk ke kamera.R (Lihat di bawah).


Di sini kita dapat kembali menggunakan produk skalar Rdan Vuntuk mendapatkan ukuran paralelisme mereka.

Satu pendekatan adalah eksponensialRโ‹…V, seperti yang dijelaskan pada bagian artikel sebelumnya (keempat). Jika Anda mencoba melakukannya, kita akan melihat bahwa hasilnya sangat berbeda dari apa yang ada di Journey . Refleksi brilian harus langka dan sangat cerah. Cara termudah adalah dengan mempertimbangkan hanya refleksi brilian ituRโ‹…V di bawah nilai ambang tertentu.

Penerapan


Kita dapat dengan mudah menerapkan efek gloss yang dijelaskan di atas menggunakan fungsi reflectdalam Cg, yang membuatnya sangat mudah untuk dihitungR.

sampler2D_float _GlitterTex;
float _GlitterThreshold;
float3 _GlitterColor;

float3 GlitterSpecular (float2 uv, float3 N, float3 L, float3 V)
{
    // Random glitter direction
    float3 G = normalize(tex2D(_GlitterTex, uv).rgb * 2 - 1); // [0,1]->[-1,+1]

    // Light that reflects on the glitter and hits the eye
    float3 R = reflect(L, G);
    float RdotV = max(0, dot(R, V));
	
    // Only the strong ones (= small RdotV)
    if (RdotV > _GlitterThreshold)
        return 0;
	
    return (1 - RdotV) * _GlitterColor;
}

Sebenarnya, jika Gbenar-benar kebetulan Rjuga akan sepenuhnya acak. Tampaknya penggunaan itu reflectopsional. Dan meskipun ini berlaku untuk bingkai statis, tetapi apa yang terjadi jika sumber cahaya bergerak? Ini bisa karena gerakan matahari itu sendiri, atau karena sumber cahaya yang terikat pada pemain. Dalam kedua kasus, pasir akan kehilangan integritas temporal antara frame saat ini dan selanjutnya, karena efek glitter akan muncul di tempat acak. Namun, menggunakan fungsi ini reflectmemberikan rendering yang jauh lebih stabil.

Hasilnya ditunjukkan di bawah ini:


Seperti yang kita ingat dari bagian pertama tutorial, komponen gloss ditambahkan ke warna akhir.

#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);
}

Ada kemungkinan besar bahwa beberapa piksel pada akhirnya akan mendapatkan lebih banyak warna 1, Yang akan menyebabkan efek mekar. Itu yang kita butuhkan. Efeknya juga ditambahkan di atas pantulan specular yang sudah ada (dibahas di bagian sebelumnya artikel), sehingga butiran pasir yang berkilau bahkan dapat ditemukan di mana bukit pasir menyala dengan baik.

Ada banyak cara untuk meningkatkan teknik ini. Itu semua tergantung pada hasil yang ingin Anda capai. Dalam Astroneer dan Slime Rancher , misalnya, efek ini hanya digunakan pada malam hari. Ini dapat dicapai dengan mengurangi kekuatan efek kilap tergantung pada arah sinar matahari.

Sebagai contoh, nilai max(dot(L, fixed3(0,1,0),0))adalah1ketika matahari jatuh dari atas, dan sama dengan nol ketika berada di luar cakrawala. Tetapi Anda dapat membuat sistem Anda sendiri, tampilan yang tergantung pada preferensi Anda.

Mengapa refleksi tidak digunakan dalam refleksi Blinn-Fong?
ocean specular, , -.

, 3D- , , . , reflect . - Rโ‹…VNโ‹…H, .

Bagian 6: gelombang


Pada bagian terakhir artikel, kita akan menciptakan kembali gelombang pasir khas yang dihasilkan dari interaksi bukit pasir dan angin.




Gelombang di permukaan bukit pasir: sebelum dan sesudah

Secara teoritis, akan logis untuk menempatkan bagian ini setelah bagian tentang normals pasir. Saya meninggalkannya pada akhirnya karena itu adalah efek tutorial yang paling sulit. Bagian dari kompleksitas ini adalah karena cara peta normal disimpan dan diproses oleh shader permukaan yang melakukan banyak langkah tambahan.

Peta Normal


Pada bagian sebelumnya (kelima), kami mengeksplorasi metode untuk menghasilkan pasir heterogen. Pada bagian yang dikhususkan untuk normals pasir, teknik pemetaan normal yang sangat populer digunakan untuk mengubah cara cahaya berinteraksi dengan permukaan geometri . Ini sering digunakan dalam grafik 3D untuk menciptakan ilusi bahwa suatu objek memiliki geometri yang lebih kompleks, dan biasanya digunakan untuk membuat permukaan melengkung lebih halus (lihat di bawah).


Untuk mencapai efek ini, setiap piksel dipetakan ke arah normal , yang menunjukkan orientasinya. Dan itu digunakan untuk menghitung pencahayaan bukannya orientasi sebenarnya dari mesh.

Dengan membaca arah normals dari tekstur yang tampaknya acak, kami dapat mensimulasikan graininess. Meskipun ketidaktepatan fisik, dia masih terlihat dapat dipercaya.

Gelombang di pasir


Namun, bukit pasir menunjukkan fitur lain yang tidak dapat diabaikan: gelombang. Pada setiap gundukan ada bukit pasir yang lebih kecil, muncul karena pengaruh angin dan disatukan oleh gesekan butiran pasir individu.

Gelombang ini sangat terlihat dan terlihat di sebagian besar bukit pasir. Dalam foto yang ditunjukkan di bawah, diambil di Oman, terlihat bahwa bagian dekat bukit pasir memiliki pola bergelombang yang jelas.


Gelombang-gelombang ini sangat bervariasi tergantung tidak hanya pada bentuk bukit pasir, tetapi juga pada komposisi, arah dan kecepatan angin. Kebanyakan bukit pasir dengan puncak yang tajam hanya memiliki gelombang di satu sisi saja (lihat di bawah).


Efek yang disajikan dalam tutorial ini dirancang untuk bukit pasir yang lebih halus dengan gelombang di kedua sisi. Ini tidak selalu akurat secara fisik, tetapi cukup realistis untuk dapat dipercaya, dan merupakan langkah pertama yang baik menuju implementasi yang lebih kompleks.

Realisasi ombak


Ada banyak cara untuk mengimplementasikan gelombang. Yang paling murah adalah dengan menggambar mereka pada tekstur, tetapi dalam tutorial kami ingin mencapai sesuatu yang lain. Alasannya sederhana: gelombangnya tidak "rata" dan harus berinteraksi dengan cahaya dengan benar. Jika Anda hanya menggambar mereka, mustahil untuk mendapatkan efek realistis ketika kamera (atau matahari) bergerak.

Cara lain untuk menambahkan gelombang adalah mengubah geometri model bukit pasir. Tetapi meningkatkan kompleksitas model tidak dianjurkan, karena sangat mempengaruhi kinerja keseluruhan.

Seperti yang kita lihat pada bagian tentang normals pasir, Anda dapat mengatasi masalah ini menggunakan peta normal . Bahkan, mereka digambar di permukaan seperti tekstur tradisional, tetapi digunakan dalam perhitungan pencahayaan untuk mensimulasikan geometri yang lebih kompleks.

Yaitu, tugas telah berubah menjadi tugas lain: cara membuat peta normal ini. Render manual akan terlalu memakan waktu. Selain itu, setiap kali Anda mengubah bukit pasir, Anda harus menggambar ulang ombaknya lagi. Ini secara signifikan akan memperlambat proses penciptaan sumber daya, yang dihindari oleh banyak seniman teknis.

Solusi yang jauh lebih efektif dan optimal adalah menambahkan gelombang secara prosedural . Ini berarti bahwa arah normal dari bukit pasir berubah berdasarkan geometri lokal untuk memperhitungkan tidak hanya butiran pasir, tetapi juga gelombang.

Karena gelombang perlu disimulasikan pada permukaan 3D, akan lebih logis untuk menerapkan perubahan arah normal untuk setiap piksel. Lebih mudah menggunakan peta normal yang mulus dengan pola gelombang. Peta ini kemudian akan digabungkan dengan peta normal yang ada yang sebelumnya digunakan untuk pasir.

Peta Normal


Hingga saat ini, kami bertemu dengan tiga normals yang berbeda :

  • Geometry Normal : orientasi setiap wajah model 3D, yang disimpan langsung di simpul;
  • Pasir normal : dihitung menggunakan tekstur noise;
  • Wave Normal : Efek baru yang dibahas di bagian ini.

Contoh di bawah ini, diambil dari halaman contoh Unity Surface Shader , menunjukkan cara standar untuk menulis ulang normal model 3D. Ini membutuhkan perubahan nilai o.Normal, yang biasanya dilakukan setelah pengambilan sampel tekstur (paling sering disebut peta normal ).

  Shader "Example/Diffuse Bump" {
    Properties {
      _MainTex ("Texture", 2D) = "white" {}
      _BumpMap ("Bumpmap", 2D) = "bump" {}
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      struct Input {
        float2 uv_MainTex;
        float2 uv_BumpMap;
      };
      sampler2D _MainTex;
      sampler2D _BumpMap;
      void surf (Input IN, inout SurfaceOutput o) {
        o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
        o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

Kami menggunakan teknik yang sama persis untuk mengganti normal geometri dengan normal pasir.

Apa itu UnpackNormal?
. ( 1), , . X, Y Z R, G B .

โˆ’1+1. 01. , ยซยป ยซยป . (normal packing) (normal unpacking). :

R=X2+12G=Y2+12B=Z2+12(1)


:

X=2Rโˆ’1Y=2Gโˆ’1Z=2Bโˆ’1(2)


Unity (2), UnpackNormal. .

Normal Map Technical Details polycount.

Curamnya bukit pasir


Namun, kesulitan pertama terkait dengan fakta bahwa bentuk gelombang berubah tergantung pada ketajaman bukit pasir. Bukit pasir rendah dan datar memiliki gelombang kecil; pada bukit yang curam, pola gelombang lebih berbeda. Ini berarti Anda harus mempertimbangkan kecuraman bukit pasir.

Cara termudah untuk mengatasi masalah ini adalah membuat dua peta normal yang berbeda, masing-masing, untuk bukit pasir datar dan curam. Ide dasarnya adalah untuk mencampur antara dua peta normal berdasarkan kecuraman bukit pasir.


Peta Normal untuk Steep Dune


Peta Normal untuk Flat Dune

Peta normal dan saluran biru
, .

, . , (X Y) (Z) .

length(N)=1X2+Y2+Z2=1(3)


:

X2+Y2+Z2=1X2+Y2+Z2=12Z2=1โˆ’X2โˆ’Y2Z=1โˆ’X2โˆ’Y2(4)


UnpackNormal Unity. Shader API .

inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(SHADER_API_GLES)  defined(SHADER_API_MOBILE)
    return packednormal.xyz * 2 - 1;
#else
    fixed3 normal;
    normal.xy = packednormal.wy * 2 - 1;
    normal.z = sqrt(1 - normal.x*normal.x - normal.y * normal.y);
    return normal;
#endif
}

Unity DXT5nm, (packednormal.xy) - (packednormal.wy).

Normal Map Compression polycount.

Mengapa peta normal tampak ungu?
, , .

, . , [0,0,1].

[0,0,1][0.5,0.5,1], RGB.

; , [0,0,1].

, ยซยป. , B โˆ’1+1, 0+1. .

Kecuraman dapat dihitung menggunakan produk skalar , yang sering digunakan dalam pengkodean shader untuk menghitung tingkat โ€œparalelismeโ€ dua arah. Dalam hal ini, kita mengambil arah normal geometri (ditunjukkan di bawah dengan warna biru) dan membandingkannya dengan vektor yang menunjuk ke langit (ditunjukkan di bawah dengan warna kuning). Produk skalar dari kedua vektor ini mengembalikan nilai yang mendekati1ketika vektor hampir paralel (bukit pasir datar), dan dekat dengan 0ketika sudut di antara mereka adalah 90 derajat (bukit pasir curam).


Namun, kami dihadapkan dengan masalah pertama - dua vektor terlibat dalam operasi ini. Vektor normal yang diperoleh dari fungsi surfmenggunakan o.Normaldinyatakan dalam ruang singgung . Ini berarti bahwa sistem koordinat yang digunakan untuk mengkodekan arah normal relatif terhadap geometri permukaan lokal (lihat di bawah). Kami dengan singkat menyinggung topik ini di bagian tentang pasir normal.


Vektor yang menunjuk ke langit diekspresikan dalam ruang dunia . Untuk mendapatkan produk skalar yang benar, kedua vektor harus dinyatakan dalam sistem koordinat yang sama. Ini berarti bahwa kita perlu mengubah salah satunya sehingga keduanya diekspresikan dalam satu ruang.

Untungnya, Unity datang untuk menyelamatkan dengan fungsi WorldNormalVectoryang memungkinkan kita untuk mengubah vektor normal dari ruang singgung ke ruang dunia . Untuk menggunakan fitur ini, kita perlu mengubah struktur Input, sehingga disertakan float3 worldNormaldan INTERNAL_DATA:

struct Input
{
    ...

    float3 worldNormal;
    INTERNAL_DATA
};

Ini dijelaskan dalam sebuah artikel dari dokumentasi Unity Writing Surface Shaders yang mengatakan:

INTERNAL_DATA โ€” , o.Normal.

, WorldNormalVector (IN, o.Normal).

Seringkali ini menjadi sumber utama masalah saat menulis shader permukaan. Faktanya, nilai o.Normalyang tersedia dalam fungsi surf, bervariasi tergantung pada bagaimana Anda menggunakan. Jika Anda hanya membacanya, o.Normalberisi vektor normal dari piksel saat ini di ruang dunia . Jika Anda mengubah nilainya, ini o.Normalberada dalam ruang singgung .

Jika Anda merekam o.Normal, tetapi Anda masih membutuhkan akses ke ruang dunia normal (seperti dalam kasus kami), maka Anda dapat menggunakannya WorldNormalVector (IN, o.Normal). Namun, untuk ini, Anda perlu melakukan sedikit perubahan pada struktur yang ditunjukkan di atas Input.

Apa itu INTERNAL_DATA?
INTERNAL_DATA Unity.

, , WorldNormalVector. , , . ( ).

, 3D- 3ร—3. , , (tangent to world matrix), Unity TtoW.

INTERNAL_DATA TtoW Input. , ยซShow generated codeยป :


, INTERNAL_DATA โ€” , TtoW:

#define INTERNAL_DATA
    half3 internalSurfaceTtoW0;
    half3 internalSurfaceTtoW1;
    half3 internalSurfaceTtoW2;

half3x3, half3.

WorldNormalVector, , ( ) TtoW:

#define WorldNormalVector(data,normal)
    fixed3
    (
        dot(data.internalSurfaceTtoW0, normal),
        dot(data.internalSurfaceTtoW1, normal),
        dot(data.internalSurfaceTtoW2, normal)
    )

mul, TtoW , .

, :

[ToW1,1ToW1,2ToW1,3ToW2,1ToW2,2ToW2,3ToW3,1ToW3,2ToW3,3]โ‹…[N1N2N3]=[[ToW1,1ToW1,2ToW1,3]โ‹…[N1N2N3][ToW2,1ToW2,2ToW2,3]โ‹…[N1N2N3][ToW3,1ToW3,2ToW3,3]โ‹…[N1N2N3]]


LearnOpenGL.

Penerapan


Potongan kode di bawah ini mengubah normal dari garis singgung ke ruang dunia dan menghitung kemiringan relatif ke arah atas .

// Calculates normal in world space
float3 N_WORLD = WorldNormalVector(IN, o.Normal);
float3 UP_WORLD = float3(0, 1, 0);

// Calculates "steepness"
// => 0: steep (90 degrees surface)
//  => 1: shallow (flat surface)
float steepness = saturate(dot(N_WORLD, UP_WORLD));

Sekarang kita telah menghitung kecuraman bukit pasir, kita dapat menggunakannya untuk mencampur dua peta normal. Kedua peta normal disampel, keduanya datar dan dingin (dalam kode di bawah ini disebut _ShallowTexdan _SteepTex). Kemudian mereka dicampur berdasarkan nilai steepness:

float2 uv = W.xz;

// [0,1]->[-1,+1]
float3 shallow = UnpackNormal(tex2D(_ShallowTex, TRANSFORM_TEX(uv, _ShallowTex)));
float3 steep   = UnpackNormal(tex2D(_SteepTex,   TRANSFORM_TEX(uv, _SteepTex  )));

// Steepness normal
float3 S = normalerp(steep, shallow, steepness);

Seperti yang dinyatakan pada bagian tentang normals pasir, cukup sulit untuk menggabungkan peta normal dengan benar, dan ini tidak dapat dilakukan dengan lerp. Dalam hal ini slerp, lebih tepat untuk digunakan , tetapi sebaliknya, fungsi yang lebih murah disebut normalerp.

Pencampuran gelombang


Jika kita menggunakan kode yang ditunjukkan di atas, hasilnya mungkin mengecewakan kita. Ini karena bukit pasir memiliki kecuraman yang sangat sedikit, yang menyebabkan terlalu banyak pencampuran dua tekstur normal. Untuk mengatasinya, kita dapat menerapkan transformasi non-linear ke kecuraman, yang akan meningkatkan ketajaman campuran:

// Steepness to blending
steepness = pow(steepness, _SteepnessSharpnessPower);

Saat mencampur dua tekstur, sering digunakan untuk mengontrol ketajaman dan kontrasnya pow. Kami belajar bagaimana dan mengapa itu bekerja di tutorial Rendering Berbasis Fisik saya .

Di bawah ini kita melihat dua gradien. Bagian atas menunjukkan warna dari hitam ke putih, diinterpolasi secara linear sepanjang sumbu X dengan c = uv.x. Di bagian bawah, gradien yang sama diwakili dengan c = pow(uv.x*1.5)*3.0:



Mudah dilihat, yang powmemungkinkan Anda membuat transisi yang jauh lebih tajam antara hitam dan putih. Ketika kami bekerja dengan tekstur, ini mengurangi tumpang tindihnya, menciptakan tepi yang lebih tajam.

Arah bukit pasir


Semua yang kami lakukan sebelumnya bekerja dengan sempurna. Tetapi kita perlu memecahkan masalah terakhir lainnya. Gelombang bervariasi dengan kecuraman , tetapi tidak dengan arah . Seperti disebutkan di atas, gelombang biasanya tidak simetris karena fakta bahwa angin terutama bertiup dalam satu arah.

Untuk membuat gelombang lebih realistis, kita perlu menambahkan dua peta normal lagi (lihat tabel di bawah). Mereka dapat dicampur tergantung pada paralelisme gundukan sumbu X atau sumbu Z.

KerenDatar
Xkeren xdatar x
Zkeren zdatar z

Di sini kita perlu menerapkan perhitungan paralelisme bukit pasir relatif terhadap sumbu Z. Hal ini dapat dilakukan mirip dengan perhitungan kecuraman, tetapi float3 UP_WORLD = float3(0, 1, 0);dapat digunakan sebagai gantinya float3 Z_WORLD = float3(0, 0, 1);.

Langkah terakhir ini akan saya tinggalkan untuk Anda. Jika Anda memiliki masalah, maka di akhir tutorial ini ada tautan untuk mengunduh paket Unity lengkap.

Kesimpulan


Ini adalah bagian terakhir dari serangkaian tutorial tentang rendering pasir dari Journey.

Berikut ini menunjukkan sejauh mana kami bisa maju dalam seri ini:



Sebelum dan sesudahnya

saya ingin mengucapkan terima kasih untuk membaca tutorial yang agak panjang ini sampai akhir. Saya harap Anda menikmati menjelajahi dan menciptakan kembali shader ini.

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 gundukan 3D, latar belakang, dan opsi pencahayaan diciptakan oleh Jiadi Deng .

Model 3D dari karakter Journey ditemukan di forum FacePunch (sekarang ditutup).

Paket Persatuan


Jika Anda ingin membuat ulang efek ini, maka paket Unity lengkap tersedia untuk diunduh di Patreon . Ini memiliki semua yang Anda butuhkan, dari shader hingga model 3D.

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


All Articles