الرياضيات في Gamedev بسيطة. منحنيات الوحدة وتموجات لتأثير المطر

مرحبا بالجميع! اسمي Grisha وأنا مؤسس CGDevs. دعنا نواصل الحديث عن الرياضيات أو شيء ما. ولعل التطبيق الرئيسي للرياضيات في تطوير اللعبة ورسومات الكمبيوتر بشكل عام هو VFX. لذلك دعونا نتحدث عن أحد هذه التأثيرات - المطر ، أو بالأحرى عن الجزء الرئيسي الذي يتطلب الرياضيات - تموجات على السطح. اكتب تظليلًا متتاليًا للتموجات على السطح ، وحلل الرياضيات. إذا كانت مهتمة - مرحبا بكم في القط. مشروع جيثب المرفق.



في بعض الأحيان تأتي لحظة في الحياة عندما يضطر مبرمج للاستيلاء على الدف والطلب من المطر. بشكل عام ، موضوع نمذجة المطر نفسه عميق للغاية. هناك العديد من الأعمال الرياضية على أجزاء مختلفة من هذه العملية ، من إسقاط قطرات والآثار المرتبطة بذلك إلى توزيع قطرات في الحجم. سنحلل جانبًا واحدًا فقط - التظليل ، والذي سيسمح لنا بإنشاء تأثير مماثل للموجة الناتجة عن الهبوط المتساقط. حان الوقت لاتخاذ الدف!


موجة الرياضيات

عند البحث على الإنترنت ، ستجد الكثير من التعبيرات الرياضية المضحكة لتوليد تموجات. غالبًا ما تتألف من أرقام "سحرية" ووظائف دورية دون مبرر. لكن بشكل عام ، فإن رياضيات هذا التأثير بسيطة للغاية.

نحتاج فقط إلى معادلة موجة مستوية في الحالة أحادية البعد. لماذا سنقوم بتحليل مسطحة وأحادية الأبعاد في وقت لاحق قليلا.

يمكن كتابة معادلة الموجة المستوية في حالتنا على النحو التالي:

Aresult = A * cos (2 * PI * (x / waveLength - t * frequency)) ؛
حيث:
Aresult - السعة عند النقطة x ، في الوقت t
A هو أقصى سعة
الطول الموجي - الطول الموجي
تردد موجة تردد
PI - رقم PI = 3.14159 (تعويم)

شادر


دعنا نلعب مع تظليل. ل "الأعلى" ستكون مسؤولة عن تنسيق -Z. هذا هو أكثر ملاءمة في حالة 2D في الوحدة. إذا رغبت في ذلك ، لن يكون من الصعب إعادة كتابة التظليل إلى Y.

أول شيء نحتاجه هو معادلة الدائرة. ستكون موجة تظليلنا متناظرة حول المركز. يتم وصف معادلة الدائرة في الحالة ثنائية الأبعاد على النحو التالي:

r ^ 2 = x ^ 2 + y ^ 2

نحن بحاجة إلى دائرة نصف قطرها ، لذلك تأخذ المعادلة الشكل:

r = sqrt (x ^ 2 + y ^ 2)

وهذا سيعطينا تناسقًا حول النقطة (0 ، 0) في الشبكة ، والتي ستقلل كل شيء إلى الحالة أحادية البعد لموجة مستوية.

الآن دعنا نكتب تظليل. لن أقوم بتحليل كل خطوة من خطوات كتابة تظليل ، لأن هذا ليس غرض المقالة ، ولكن الأساس مأخوذ من تظليل السطح القياسي من الوحدة ، والذي يمكن الحصول على القالب من خلال إنشاء-> تظليل-> StandardSurfaceShader.

بالإضافة إلى ذلك ، يتم إضافة الخصائص اللازمة لمعادلة الموجة: _Frequency و _WaveLength و _WaveHeight . خاصية _Timer (قد يكون من الممكن استخدام الوقت مع hcp ، ولكن أثناء التطوير والرسوم المتحركة اللاحقة ، يكون التحكم فيه يدويًا أكثر ملاءمة.

نكتب دالة getHeight للحصول على الارتفاع (الآن هذا هو إحداثي Z) عن طريق استبدال معادلة الدائرة في معادلة الموجة

من خلال كتابة تظليل مع معادلة الموجة ومعادلة الدائرة ، نحصل على هذا التأثير.

كود شادر
Shader "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" } 



هناك موجات. لكنني أريد أن تبدأ الرسوم المتحركة وتنتهي بطائرة. وظيفة الجيب سوف تساعدنا في هذا. بضرب السعة بواسطة sin (_Timer * PI) ، نحصل على مظهر سلس واختفاء للأمواج. نظرًا لأن _Timer يأخذ القيم من 0 إلى 1 ، والجيب عند الصفر وفي PI يساوي الصفر ، فهذا هو بالضبط ما تحتاجه.


بينما ليس على الإطلاق مثل انخفاض السقوط. المشكلة هي أن طاقة الأمواج تضيع بالتساوي. أضف خاصية _Radius ، والتي ستكون مسؤولة عن نصف قطر التأثير. ونقوم بضرب سعة المشبك (_Radius - rad ، 0 ، 1) ونحصل بالفعل على تأثير أشبه بالحقيقة.


حسنا ، الخطوة الأخيرة. حقيقة أن السعة في كل نقطة فردية تصل إلى الحد الأقصى في وقت يساوي 0.5 ليست صحيحة تمامًا ، فمن الأفضل استبدال هذه الوظيفة.



ثم شعرت بالكسل قليلاً لدرجة أنني لم أعد نحسب ، ولقد ضربت الجيب منذ (1 - _Timer) وحصلت على هذا المنحنى.



ولكن بشكل عام ، من وجهة نظر الرياضيات ، هنا يمكنك أيضًا تحديد المنحنى المرغوب استنادًا إلى المنطق في أي وقت تريد ذروة وشكل تقريبي ، ثم بناء الاستيفاء في هذه النقاط.

والنتيجة هي مثل تظليل وتأثير.

كود شادر
 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" } 




شبكة مش مهم

العودة قليلا إلى موضوع المقال السابق . يتم تنفيذ الموجات بواسطة تظليل قمة الرأس ، وبالتالي فإن شبكة الشبكة تلعب دورًا كبيرًا إلى حد ما. نظرًا لأن طبيعة الحركة معروفة ، فإن المهمة مبسطة ، ولكن بشكل عام ، تعتمد الصورة النهائية على شكل الشبكة. يصبح الفارق ضئيلًا مع تعدد الأضلاع العالي ، ولكن بالنسبة للأداء ، كلما كانت المضلعات أقل ، كان ذلك أفضل. فيما يلي صور توضح الفرق بين الشبكات والمرئيات.

صحيح:



خطأ:



حتى مع وجود ضعف عدد المضلعات ، فإن الشبكة الثانية تعطي الصورة المرئية الخاطئة (يتم إنشاء كلتا الشبكتين باستخدام Triangle.Net ، فقط باستخدام خوارزميات مختلفة).

البصرية النهائية


في إصدار مختلف من التظليل ، تمت إضافة جزء خاص لإنشاء موجات ليس فقط في الوسط ، ولكن في عدة نقاط. كيف يتم تنفيذ ذلك وكيف يمكنك اجتياز هذه المعلمات ، يمكنني أن أخبر في المقالات التالية ، إذا كان الموضوع مثيرًا للاهتمام.

هنا هو تظليل نفسها:

تموج قمة الرأس مع القطب
 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" } 


يمكن العثور على المشروع ككل وكيف يعمل هنا . صحيح ، جزء من الموارد كان لا بد من إزالتها بسبب قيود الوزن من جيثب (تقرير التنمية البشرية skybox والسيارة).

شكرا لاهتمامكم! آمل أن تكون المقالة مفيدة لشخص ما ، وقد أصبح من الواضح قليلاً سبب الحاجة إلى علم المثلثات ، والهندسة التحليلية (كل ما يتعلق بالمنحنيات) والتخصصات الرياضية الأخرى.

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


All Articles