Halo semuanya! Nama saya Grisha dan saya adalah pendiri CGDevs. Mari kita lanjutkan berbicara tentang matematika atau sesuatu. Mungkin aplikasi utama matematika dalam pengembangan game dan grafik komputer secara umum adalah VFX. Jadi mari kita bicara tentang satu efek seperti itu - hujan, atau lebih tepatnya tentang bagian utamanya, yang membutuhkan matematika - riak di permukaan. Berhasil menulis shader untuk riak di permukaan, dan menganalisis matematika. Jika tertarik - selamat datang di kucing. Proyek Github terlampir.

Terkadang ada saatnya dalam hidup ketika seorang programmer harus mengambil rebana dan meminta hujan. Secara umum, topik pemodelan hujan itu sendiri sangat dalam. Ada banyak karya matematika pada bagian berbeda dari proses ini, dari menjatuhkan tetes dan efek yang terkait dengan ini hingga distribusi tetesan dalam volume. Kami hanya akan menganalisis satu aspek - shader, yang akan memungkinkan kami untuk membuat efek yang mirip dengan gelombang dari drop yang dijatuhkan. Sudah waktunya untuk mengambil rebana!
Gelombang matematikaSaat mencari di internet Anda menemukan banyak ekspresi matematika yang lucu untuk menghasilkan riak. Seringkali mereka terdiri dari semacam angka "ajaib" dan fungsi periodik tanpa pembenaran. Tetapi secara umum, matematika dari efek ini cukup sederhana.
Kita hanya membutuhkan persamaan gelombang bidang dalam kasus satu dimensi. Mengapa kita akan menganalisis flat dan satu dimensi sedikit lebih lambat.
Persamaan gelombang bidang dalam kasus kami dapat ditulis sebagai:
Aresult = A * cos (2 * PI * (x / waveLength - t * frekuensi));Dimana:
Hasil - amplitude pada titik x, pada waktu t
A adalah amplitudo maksimum
panjang gelombang - panjang gelombang
frekuensi - frekuensi gelombang
PI -
PI number = 3.14159 (float)
ShaderMari kita bermain dengan shader. Untuk "atas" akan bertanggung jawab atas koordinat -Z. Ini lebih nyaman dalam kasus 2D di Unity. Jika diinginkan, shader tidak akan sulit untuk ditulis ulang menjadi Y.
Hal pertama yang kita butuhkan adalah persamaan lingkaran. Gelombang shader kami akan simetris di tengah. Persamaan lingkaran dalam kasus 2d digambarkan sebagai:
r ^ 2 = x ^ 2 + y ^ 2kita membutuhkan jari-jari, jadi persamaannya berbentuk:
r = sqrt (x ^ 2 + y ^ 2)dan ini akan memberi kita simetri tentang titik (0, 0) di jala, yang akan mengurangi segalanya menjadi kasus satu dimensi dari gelombang bidang.
Sekarang mari kita menulis shader. Saya tidak akan menganalisis setiap langkah penulisan shader, karena ini bukan tujuan artikel, tetapi dasarnya diambil dari Standard Surface Shader dari Unity, templat yang dapat diperoleh melalui Create-> Shader-> StandardSurfaceShader.
Selain itu, properti yang diperlukan untuk persamaan gelombang
ditambahkan :
_Frequency ,
_WaveLength dan
_WaveHeight . Properti
_Timer (akan dimungkinkan untuk menggunakan waktu dengan GPU, tetapi selama pengembangan dan animasi berikutnya lebih mudah untuk mengontrolnya secara manual.
Kami menulis fungsi getHeight untuk mendapatkan ketinggian (sekarang ini adalah koordinat Z) dengan mengganti persamaan lingkaran dalam persamaan gelombang
Dengan menulis shader dengan persamaan gelombang dan persamaan lingkaran, kita mendapatkan efek ini.
Kode shaderShader "CGDevs/Rain/RainRipple" { Properties { _WaveHeight("Wave Height", float) = 1 _WaveLength("Wave Length", float) = 1 _Frequency("Frequency", float) = 1 _Timer("Timer", Range(0,1)) = 0 _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0 _Metallic ("Metallic", Range(0,1)) = 0.0 } SubShader { Tags { "RenderType"= "Opaque" } LOD 200 CGPROGRAM #pragma surface surf Standard fullforwardshadows vertex:vert #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; half _Glossiness, _Metallic, _Frequency, _Timer, _WaveLength, _WaveHeight; fixed4 _Color; half getHeight(half x, half y) { const float PI = 3.14159; half rad = sqrt(x * x + y * y); half wavefunc = _WaveHeight * cos(2 * PI * (_Frequency * _Timer - rad / _WaveLength)); return wavefunc; } void vert (inout appdata_full v) { v.vertex.z -= getHeight(v.vertex.x, v.vertex.y); } void surf (Input IN, inout SurfaceOutputStandard o) { fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = _Color.a; } ENDCG } FallBack "Diffuse" }
Ada ombak. Tapi saya ingin animasi dimulai dan diakhiri dengan pesawat. Fungsi sinus akan membantu kita dengan ini. Mengalikan amplitudo dengan dosa (_Timer * PI), kita mendapatkan tampilan yang halus dan hilangnya gelombang. Karena _Timer mengambil nilai dari 0 hingga 1, dan sinus pada nol dan pada PI adalah nol, inilah yang Anda butuhkan.
Meskipun sama sekali tidak seperti jatuh jatuh. Masalahnya adalah energi gelombang hilang secara merata. Tambahkan properti _Radius, yang akan bertanggung jawab atas radius efeknya. Dan kita mengalikan amplitudo penjepit (_Radius - rad, 0, 1) dan mendapatkan efek yang lebih seperti kebenaran.
Nah, langkah terakhir. Fakta bahwa amplitudo pada setiap titik individu mencapai maksimum pada waktu yang sama dengan 0,5 tidak sepenuhnya benar, lebih baik untuk mengganti fungsi ini.

Kemudian saya merasa agak terlalu malas untuk menghitung, dan saya hanya mengalikan sinus dengan (1 - _Timer) dan mendapat kurva seperti itu.

Tetapi secara umum, dari sudut pandang matematika, di sini Anda juga dapat memilih kurva yang diinginkan berdasarkan logika pada titik waktu yang Anda inginkan puncak dan bentuk perkiraan, dan kemudian membangun interpolasi pada titik-titik ini.
Hasilnya adalah shader dan efek seperti itu.
Kode shader Shader "CGDevs/Rain/RainRipple" { Properties { _WaveHeight("Wave Height", float) = 1 _WaveLength("Wave Length", float) = 1 _Frequency("Frequency", float) = 1 _Radius("Radius", float) = 1 _Timer("Timer", Range(0,1)) = 0 _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0 _Metallic ("Metallic", Range(0,1)) = 0.0 } SubShader { Tags { "RenderType"= "Opaque" } LOD 200 CGPROGRAM #pragma surface surf Standard fullforwardshadows vertex:vert #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; half _Glossiness, _Metallic, _Frequency, _Timer, _WaveLength, _WaveHeight, _Radius; fixed4 _Color; half getHeight(half x, half y) { const float PI = 3.14159; half rad = sqrt(x * x + y * y); half wavefunc = _WaveHeight * sin(_Timer * PI) * (1 - _Timer) * clamp(_Radius - rad, 0, 1) * cos(2 * PI * (_Frequency * _Timer - rad / _WaveLength)); return wavefunc; } void vert (inout appdata_full v) { v.vertex.z -= getHeight(v.vertex.x, v.vertex.y); } void surf (Input IN, inout SurfaceOutputStandard o) { fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = _Color.a; } ENDCG } FallBack "Diffuse" }
Mesh mesh itu pentingMengembalikan sedikit ke topik
artikel sebelumnya . Gelombang diimplementasikan oleh vertex shader, sehingga mesh dari mesh memainkan peran yang agak besar. Karena sifat gerak diketahui, tugasnya disederhanakan, tetapi secara umum, visual akhir tergantung pada bentuk kisi. Perbedaannya menjadi tidak signifikan dengan poligonalitas tinggi, tetapi untuk kinerja, semakin sedikit poligon, semakin baik. Di bawah ini adalah gambar yang menggambarkan perbedaan antara kisi dan visual.
Dengan benar:
Salah:
Bahkan dengan poligon dua kali lebih banyak, mesh kedua memberikan visual yang salah (kedua mesh dihasilkan menggunakan Triangle.Net, hanya menggunakan algoritma yang berbeda).
Visual terakhirDalam versi shader yang berbeda, bagian khusus telah ditambahkan untuk membuat gelombang tidak hanya di tengah, tetapi di beberapa titik. Bagaimana ini diterapkan dan bagaimana Anda bisa melewati parameter seperti itu, saya bisa katakan di artikel berikut, jika topiknya menarik.
Ini shader itu sendiri:
Ripple vertex dengan kutub Shader "CGDevs/Rain/Ripple Vertex with Pole" { Properties { _MainTex ("Albedo (RGB)", 2D) = "white" {} _Normal ("Bump Map", 2D) = "white" {} _Roughness ("Metallic", 2D) = "white" {} _Occlusion ("Occlusion", 2D) = "white" {} _PoleTexture("PoleTexture", 2D) = "white" {} _Color ("Color", Color) = (1,1,1,1) _Glossiness ("Smoothness", Range(0,1)) = 0 _WaveMaxHeight("Wave Max Height", float) = 1 _WaveMaxLength("Wave Length", float) = 1 _Frequency("Frequency", float) = 1 _Timer("Timer", Range(0,1)) = 0 } SubShader { Tags { "IgnoreProjector" = "True" "RenderType" = "Opaque"} LOD 200 CGPROGRAM #pragma surface surf Standard fullforwardshadows vertex:vert #pragma target 3.0 sampler2D _PoleTexture, _MainTex, _Normal, _Roughness, _Occlusion; half _Glossiness, _WaveMaxHeight, _Frequency, _Timer, _WaveMaxLength, _RefractionK; fixed4 _Color; struct Input { float2 uv_MainTex; }; half getHeight(half x, half y, half offetX, half offetY, half radius, half phase) { const float PI = 3.14159; half timer = _Timer + phase; half rad = sqrt((x - offetX) * (x - offetX) + (y - offetY) * (y - offetY)); half A = _WaveMaxHeight * sin(_Timer * PI) * (1 - _Timer) * (1 - timer) * radius; half wavefunc = cos(2 * PI * (_Frequency * timer - rad / _WaveMaxLength)); return A * wavefunc; } void vert (inout appdata_full v) { float4 poleParams = tex2Dlod (_PoleTexture, float4(v.texcoord.xy, 0, 0)); v.vertex.z += getHeight(v.vertex.x, v.vertex.y, (poleParams.r - 0.5) * 2, (poleParams.g - 0.5) * 2, poleParams.b , poleParams.a); } void surf (Input IN, inout SurfaceOutputStandard o) { o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb * _Color.rgb; o.Normal = UnpackNormal(tex2D(_Normal, IN.uv_MainTex)); o.Metallic = tex2D(_Roughness, IN.uv_MainTex).rgb; o.Occlusion = tex2D(_Occlusion, IN.uv_MainTex).rgb; o.Smoothness = _Glossiness; o.Alpha = _Color.a; } ENDCG } FallBack "Diffuse" }
Proyek secara keseluruhan dan cara kerjanya dapat ditemukan di
sini . Benar, bagian dari sumber daya harus dihapus karena keterbatasan berat github (hdr skybox dan mobil).
Terima kasih atas perhatian anda! Saya harap artikel ini akan bermanfaat bagi seseorang, dan menjadi sedikit lebih jelas mengapa trigonometri, geometri analitik (semua yang berhubungan dengan kurva) dan disiplin matematika lainnya mungkin diperlukan.