هذا البرنامج التعليمي يدور حول
الخرائط التفاعلية وإنشاءها في الوحدة باستخدام التظليل.
يمكن أن يكون هذا التأثير بمثابة أساس لتقنيات أكثر تعقيدًا ، مثل الإسقاط المجسم أو حتى طاولة رملية من فيلم "النمر الأسود".
مصدر إلهام لهذا البرنامج التعليمي هو سقسقة من
باران كاهيو أوغلو ، والتي تعرض مثالًا على ما
يخلقه لـ
Mapbox .
تم التقاط المشهد (باستثناء الخريطة) من العرض التوضيحي الخاص بـ Unity Visual Effect Graph Spaceship (انظر أدناه) ، والذي يمكن تنزيله من
هنا .
الجزء 1. تعويض فيرتكس
تشريح التأثير
أول ما يمكنك ملاحظته على الفور هو أن الخرائط الجغرافية
مسطحة : إذا تم استخدامها كقوام ، فإنها تفتقر إلى الأبعاد الثلاثة التي سيحتوي عليها نموذج ثلاثي الأبعاد حقيقي لمنطقة الخريطة المقابلة.
يمكنك تطبيق هذا الحل: قم بإنشاء نموذج ثلاثي الأبعاد للمنطقة المطلوبة في اللعبة ، ثم قم بتطبيق نسيج من الخريطة عليه. سيساعد هذا في حل المشكلة ، لكنه يستغرق الكثير من الوقت ولن يسمح بإدراك تأثير "التمرير" من الفيديو Baran Kahyaoglu.
من الواضح أن النهج التقني أكثر هو الأفضل. لحسن الحظ ، يمكن استخدام تظليل لتغيير هندسة نموذج ثلاثي الأبعاد. بمساعدتهم ، يمكنك تحويل أي طائرة إلى وديان وجبال المنطقة التي نحتاجها.
في هذا البرنامج التعليمي نستخدم خريطة
Chillot ، Chilli ، التي تشتهر بالتلال المميزة. تُظهر الصورة أدناه نسيج المنطقة المرسوم على شبكة مستديرة.
على الرغم من أننا نرى التلال والجبال ، فهي لا تزال مسطحة تماما. هذا يدمر وهم الواقعية.
القذف الطبيعي
الخطوة الأولى لاستخدام التظليل لتغيير الشكل الهندسي هي تقنية تسمى
البثق العادي . إنها تحتاج
إلى معدّل قمة الرأس : دالة يمكنها التعامل مع الرؤوس الفردية لنموذج ثلاثي الأبعاد.
تعتمد طريقة استخدام مُعدِّل الرأس على نوع التظليل المستخدم. في هذا البرنامج التعليمي ، سنقوم بتغيير
Surface Standard Shader - أحد أنواع الظلال التي يمكنك إنشاؤها في Unity.
هناك العديد من الطرق لمعالجة رؤوس نموذج ثلاثي الأبعاد. إحدى الطرق الأولى الموصوفة في معظم برامج تعليمي تظليل قمة الرأس هي
البثق العادي . يتكون ذلك من دفع كل قمة "للخارج" (
البثق ) ، مما يعطي النموذج ثلاثي الأبعاد مظهرًا أكثر انتفاخًا. "خارج" يعني أن كل قمة تتحرك على طول الاتجاه الطبيعي.
بالنسبة للأسطح الملساء ، يعمل هذا بشكل جيد للغاية ، ولكن في الطُرز ذات التوصيلات السيئة للقمل ، يمكن لهذه الطريقة أن تُنتج قطعًا غريبة. تم توضيح هذا التأثير جيدًا في أحد برامجي التعليمية الأولى:
مقدمة لطيفة لـ Shaders ، حيث أوضحت كيفية
إخراج نموذج ثلاثي الأبعاد
وتطفله .
إن إضافة القواعد الطبيعية المبثوقة لتظليل السطح أمر سهل للغاية. كل تظليل السطح لديه
#pragma
، والذي يستخدم لنقل معلومات وأوامر إضافية. أحد هذه الأوامر هو
vert
، مما يعني أنه سيتم استخدام وظيفة
vert
لمعالجة كل قمة من نموذج ثلاثي الأبعاد.
التظليل المحرر كما يلي:
#pragma surface surf Standard fullforwardshadows addshadow vertex:vert ... float _Amount; ... void vert(inout appdata_base v) { v.vertex.xyz += v.normal * _Amount; }
نظرًا لأننا نغير موضع القمم ، نحتاج أيضًا إلى استخدام
addshadow
إذا أردنا أن يلقي النموذج بظلاله على نفسه بشكل صحيح.
ما هو appdata_base؟كما ترون ، لقد أضفنا وظيفة مُعدِّل الرؤوس (
vert
) ، والتي تأخذ كمعلمة
بنية تسمى
appdata_base
. يخزن هذا الهيكل معلومات حول كل قمة منفردة للنموذج الثلاثي الأبعاد. لا يحتوي فقط
على موضع vertex (
v.vertex
) ، ولكن أيضًا على حقول أخرى ، على سبيل المثال
، الاتجاه الطبيعي (
v.normal
)
ومعلومات النسيج المرتبطة بـ vertex (
v.texcoord
).
في بعض الحالات ، لا يكفي هذا وقد نحتاج إلى خصائص أخرى ، مثل
لون الرأس (
v.color
)
واتجاه الظل (
v.tangent
). يمكن تحديد مُعدِّلات Vertex باستخدام مجموعة متنوعة من هياكل
appdata_tan
الأخرى ، بما في ذلك
appdata_tan
و
appdata_full
، اللذين يوفران مزيدًا من المعلومات بتكلفة منخفضة
appdata_full
. يمكنك قراءة المزيد حول
appdata
(ومتغيراته) في
Unity3D wiki .
كيف يتم إرجاع القيم من فير؟الوظيفة العليا لا يوجد لديه قيمة الإرجاع. إذا كنت معتادًا على لغة C # ، فيجب أن تعلم أن الهياكل يتم تمريرها حسب القيمة ، أي عندما تتغير v.vertex
يؤثر هذا فقط على نسخة v
، التي يقتصر نطاقها على نص الوظيفة.
ومع ذلك ، v
إعلان v
أيضًا على أنها inout
، مما يعني أنه يستخدم لكل من المدخلات والمخرجات. أي تغييرات تقوم بها تغير المتغير نفسه ، الذي vert
إلى vert
. غالبًا ما تستخدم الكلمات الرئيسية out
في رسومات الكمبيوتر ، ويمكن ربطها تقريبًا ref
out
في C #.
البثق المعتادة مع القوام
الشفرة التي استخدمناها أعلاه تعمل بشكل صحيح ، لكنها أبعد ما تكون عن التأثير الذي نريد تحقيقه. السبب هو أننا لا نريد أن نخرج كل القمم بنفس المقدار. نريد أن يطابق سطح النموذج ثلاثي الأبعاد الوديان والجبال في المنطقة الجغرافية المقابلة. أولاً ، نحتاج بطريقة أو بأخرى إلى تخزين واسترجاع المعلومات حول مقدار رفع كل نقطة على الخريطة. نريد أن يتأثر البثق بالنسيج الذي يتم ترميز مرتفعات المناظر الطبيعية فيه. غالبًا ما تسمى هذه القوام
خرائط الارتفاع ، ولكن غالبًا ما تسمى أيضًا
خرائط العمق ، اعتمادًا على السياق. بعد تلقي معلومات حول الارتفاعات ، سنكون قادرين على تعديل قذف الطائرة بناءً على خريطة الارتفاع. كما هو موضح في الرسم البياني ، سيتيح لنا ذلك التحكم في رفع وخفض المناطق.
من السهل جدًا العثور على صورة القمر الصناعي للمنطقة الجغرافية التي تهتم بها وخريطة الارتفاع المرتبطة بها. يوجد أدناه خريطة القمر الصناعي المريخ (أعلاه) وخريطة الارتفاع (أدناه) التي استخدمت في هذا البرنامج التعليمي:
تحدثت بالتفصيل عن مفهوم خريطة العمق في سلسلة أخرى من البرامج التعليمية تسمى
"صور ثلاثية الأبعاد لفيسبوك من الداخل: تظليل المنظر" [
الترجمة إلى هابري].
في هذا البرنامج التعليمي ، سوف نفترض أن خريطة الارتفاع يتم تخزينها كصورة بتدرج الرمادي ، حيث يتطابق الأسود والأبيض مع المرتفعات الأدنى والأعلى. نحتاج أيضًا إلى
قياس هذه القيم
خطيًا ، أي اختلاف اللون ، على سبيل المثال ، عند
0.1
يقابل فرق الارتفاع بين
0
و
0.1
أو بين
0.9
و
1.0
. بالنسبة للخرائط العميقة ، هذا ليس صحيحًا دائمًا ، لأن الكثير منهم يخزنون معلومات العمق على
مقياس لوغاريتمي .
لأخذ عينات من نسيج ، هناك حاجة إلى عنصرين من المعلومات: النسيج نفسه
وإحداثيات الأشعة فوق البنفسجية للنقطة التي نريد
أخذ عينات منها. يمكن الوصول إلى الأخير من خلال حقل
texcoord
، المخزن في بنية
appdata_base
. هذا هو تنسيق الأشعة فوق البنفسجية المرتبط بالرأس الحالي الذي تتم معالجته. يتم أخذ عينات الملمس في
وظيفة السطح باستخدام
tex2D
، ولكن عندما نكون في
، فإن
tex2Dlod
مطلوب.
في مقتطف الشفرة أدناه ،
_HeightMap
استخدام نسيج يسمى
_HeightMap
لتعديل قيمة البثق التي يتم تنفيذها لكل قمة:
sampler2D _HeightMap; ... void vert(inout appdata_base v) { fixed height = tex2Dlod(_HeightMap, float4(v.texcoord.xy, 0, 0)).r; vertex.xyz += v.normal * height * _Amount; }
لماذا لا يمكن استخدام tex2D كدالة فيرتكس؟
إذا نظرت إلى الكود الذي تنشئه Unity لـ Standard Surface Shader ، ستلاحظ أنه يحتوي بالفعل على مثال لكيفية أخذ عينات من القوام. على وجه الخصوص ، فإنه يقوم
_MainTex
المادة
الرئيسية (تسمى
_MainTex
) في
وظيفة السطح (وتسمى
surf
) باستخدام دالة
tex2D
المضمنة.
وفي الحقيقة ،
tex2D
استخدام
tex2D
عينات من البكسل من نسيج ، بغض النظر عن ما يتم تخزينه فيه ، اللون أو الارتفاع. ومع ذلك ، قد تلاحظ أنه لا يمكن استخدام
tex2D
في وظيفة قمة الرأس.
السبب هو أن
tex2D
لا يقرأ
فقط البكسل من النسيج. تقرر أيضًا أي إصدار من الملمس يتم استخدامه ، اعتمادًا على المسافة إلى الكاميرا. هذه التقنية تسمى
mipmapping : فهي تتيح لك الحصول على إصدارات أصغر من نسيج واحد يمكن استخدامها تلقائيًا على مسافات مختلفة.
في وظيفة السطح ، يعرف التظليل بالفعل نوع
MIP المراد استخدامه. قد لا تتوفر هذه المعلومات بعد في وظيفة vertex ، وبالتالي لا يمكن استخدام
tex2D
بثقة كاملة. على عكس ذلك ، يمكن تمرير وظيفة
tex2Dlod
معلمتين إضافيتين ، والتي في هذا البرنامج التعليمي يمكن أن يكون لها قيمة صفرية.
النتيجة واضحة للعيان في الصور أدناه.
في هذه الحالة ، يمكن إجراء تبسيط بسيط واحد. الرمز الذي استعرضناه سابقًا يمكن أن يعمل مع أي هندسة. ومع ذلك ، يمكننا أن نفترض أن السطح مسطح للغاية. في الواقع ، نريد حقًا تطبيق هذا التأثير على الطائرة.
لذلك ، يمكنك إزالة
v.normal
واستبدالها بـ
float3(0, 1, 0)
:
void vert(inout appdata_base v) { float3 normal = float3(0, 1, 0); fixed height = tex2Dlod(_HeightMap, float4(v.texcoord.xy, 0, 0)).r; vertex.xyz += normal * height * _Amount; }
يمكننا القيام بذلك لأن كل الإحداثيات في
appdata_base
يتم تخزينها في
مساحة النموذج ، أي أنها تم تعيينها بالنسبة لمركز واتجاه النموذج الثلاثي الأبعاد. الانتقال والتناوب والقياس مع
التحويل في الوحدة يغير موضع الكائن وتدويره وحجمه ، لكن لا يؤثر على النموذج ثلاثي الأبعاد الأصلي.
الجزء 2. تأثير التمرير
كل ما فعلناه أعلاه يعمل بشكل جيد. قبل المتابعة ، سنقوم باستخراج الشفرة اللازمة لحساب ارتفاع قمة الرأس الجديد في دالة
getVertex
منفصلة:
float4 getVertex(float4 vertex, float2 texcoord) { float3 normal = float3(0, 1, 0); fixed height = tex2Dlod(_HeightMap, float4(texcoord, 0, 0)).r; vertex.xyz += normal * height * _Amount; return vertex; }
عندئذ يكون للوظيفة برمتها الشكل:
void vert(inout appdata_base v) { vertex = getVertex(v.vertex, v.texcoord.xy); }
لقد فعلنا ذلك لأنه في الأسفل نحتاج إلى حساب ارتفاع عدة نقاط. نظرًا لحقيقة أن هذه الوظيفة ستكون في وظيفتها المنفصلة الخاصة بها ، فسيصبح الرمز أكثر بساطة.
الأشعة فوق البنفسجية تنسيق حساب
ومع ذلك ، هذا يقودنا إلى مشكلة أخرى. تعتمد وظيفة
getVertex
ليس فقط على موضع قمة الرأس الحالية (v.vertex) ، ولكن أيضًا على إحداثيات الأشعة فوق البنفسجية (
v.texcoord
).
عندما نرغب في حساب إزاحة ارتفاع قمة الرأس التي تقوم دالة
vert
appdata_base
حاليًا ، يكون كلا عنصري البيانات متاحين في بنية
appdata_base
. ومع ذلك ، ماذا يحدث إذا كنا بحاجة إلى أخذ عينات من نقطة مجاورة؟ في هذه الحالة ، يمكننا معرفة موضع xyz في
مساحة النموذج ، لكن ليس لدينا إمكانية الوصول إلى إحداثيات الأشعة فوق البنفسجية.
هذا يعني أن النظام الحالي قادر على حساب إزاحة الارتفاع للقمة الحالية فقط. لن يسمح لنا هذا التقييد بالمضي قدماً ، لذلك نحن بحاجة إلى إيجاد حل.
أسهل طريقة هي العثور على طريقة لحساب إحداثيات الأشعة فوق البنفسجية لكائن ثلاثي الأبعاد ، ومعرفة موضع قمة الرأس. هذه مهمة صعبة للغاية ، وهناك العديد من التقنيات لحلها (واحدة من أكثرها شعبية هي
الإسقاط الثلاثي ). ولكن في هذه الحالة بالذات ، لا نحتاج إلى مطابقة الأشعة فوق البنفسجية مع الهندسة. إذا افترضنا أن التظليل سيتم تطبيقه دائمًا على الشبكة المسطحة ، تصبح المهمة تافهة.
يمكننا حساب
إحداثيات الأشعة فوق البنفسجية (الصورة السفلية) من
مواضع القمم (الصورة العلوية) نظرًا لحقيقة أن كليهما يتم تثبيتهما خطيًا على شبكة مسطحة.
هذا يعني أنه من أجل حل مشكلتنا ، نحتاج إلى تحويل
المكونات XZ لموضع الرأس إلى
إحداثيات UV المقابلة.
هذا الإجراء يسمى
الاستيفاء الخطي . تمت مناقشته بالتفصيل على موقع الويب الخاص بي (على سبيل المثال:
The Secrets Of Color Interpolation ).
في معظم الحالات ، تكون قيم الأشعة فوق البنفسجية في المدى من
إلى
. إحداثيات كل قمة ، في المقابل ، يحتمل أن تكون غير محدودة. من وجهة نظر الرياضيات ، للتحويل من XZ إلى الأشعة فوق البنفسجية ، نحتاج فقط إلى قيمها الحدية:
- .
- .
- .
- .
والتي تظهر أدناه:
هذه القيم تختلف تبعا للشبكة المستخدمة. على مستوى الوحدة ، تقع
إحداثيات الأشعة فوق البنفسجية في النطاق من
إلى
،
وإحداثيات القمم في النطاق من
إلى
.
معادلات تحويل XZ إلى UV هي:
(1)
كيف يتم عرضها؟إذا لم تكن معتادًا على مفهوم الاستيفاء الخطي ، فقد تبدو هذه المعادلات مخيفة إلى حد كبير.
ومع ذلك ، يتم عرضها ببساطة شديدة. لنلقِ نظرة على مثال.
. لدينا فواصل زمنية: واحدة من القيم من
إلى
آخر من
إلى
. البيانات الواردة للتنسيق
هو تنسيق قمة الرأس الحالية التي تتم معالجتها ، وسوف يكون الناتج هو تنسيق
تستخدم لأخذ عينات من الملمس.
نحن بحاجة إلى الحفاظ على خصائص التناسب بين
و الفاصل ، و
والفاصل الزمني لها. على سبيل المثال ، إذا
يهم 25 ٪ من فاصلها ثم
سوف يهم أيضا 25 ٪ من الفاصل الزمني لها.
كل هذا مبين في الشكل التالي:
من هذا ، يمكننا أن نستنتج أن النسبة المكوّنة من القطعة الحمراء فيما يتعلق بالوردي يجب أن تكون هي نفسها النسبة بين القطعة الزرقاء والزرقاء:
(2)
الآن يمكننا تحويل المعادلة الموضحة أعلاه للحصول عليها
:
وهذه المعادلة لها بالضبط نفس الشكل الموضح أعلاه (1).
يمكن تنفيذ هذه المعادلات في الكود كما يلي:
float2 _VertexMin; float2 _VertexMax; float2 _UVMin; float2 _UVMax; float2 vertexToUV(float4 vertex) { return (vertex.xz - _VertexMin) / (_VertexMax - _VertexMin) * (_UVMax - _UVMin) + _UVMin; }
الآن يمكننا استدعاء دالة
getVertex
دون الحاجة إلى تمرير
v.texcoord
:
float4 getVertex(float4 vertex) { float3 normal = float3(0, 1, 0); float2 texcoord = vertexToUV(vertex); fixed height = tex2Dlod(_HeightMap, float4(texcoord, 0, 0)).r; vertex.xyz += normal * height * _Amount; return vertex; }
ثم تأخذ الدالة
vert
بأكملها الشكل:
void vert(inout appdata_base v) { v.vertex = getVertex(v.vertex); }
تأثير التمرير
بفضل الرمز الذي كتبناه ، يتم عرض الخريطة بأكملها على الشبكة. إذا كنا نريد تحسين العرض ، فعلينا إجراء تغييرات.
دعونا إضفاء الطابع الرسمي على رمز أكثر قليلا. أولاً ، قد نحتاج إلى تكبير جزء منفصل من الخريطة ، بدلاً من النظر إليه بالكامل.
يمكن تعريف هذه المنطقة بقيمتين: حجمها (
_CropSize
) وموقعها على الخريطة (
_CropOffset
) ، المقاسة في
مساحة الرأس (من
_VertexMin
إلى
_VertexMax
).
بعد تلقي هاتين القيمتين ، يمكننا مرة أخرى استخدام الاستيفاء الخطي بحيث لا
getVertex
استدعاء
getVertex
الحالي لأعلى النموذج ثلاثي الأبعاد ، ولكن
getVertex
.
رمز ذات الصلة:
void vert(inout appdata_base v) { float2 croppedMin = _CropOffset; float2 croppedMax = croppedMin + _CropSize;
إذا كنا نريد التمرير ، فستكون كافية لتحديث
_CropOffset
خلال البرنامج النصي. نتيجة لهذا ، ستتحرك منطقة الاقتطاع ، وتمريرها فعليًا عبر المناظر الطبيعية.
public class MoveMap : MonoBehaviour { public Material Material; public Vector2 Speed; public Vector2 Offset; private int CropOffsetID; void Start () { CropOffsetID = Shader.PropertyToID("_CropOffset"); } void Update () { Material.SetVector(CropOffsetID, Speed * Time.time + Offset); } }
لكي ينجح هذا ، من المهم للغاية ضبط
وضع الالتفاف لجميع القوام على
التكرار . إذا لم يتم ذلك ، فلن نكون قادرين على حلقة الملمس.
بالنسبة لتأثير التكبير / التصغير ، فإن مجرد تغيير
_CropSize
.
الجزء 3. التضاريس التظليل
تظليل مسطح
كل الكود الذي كتبناه يعمل ، لكن لديه مشكلة خطيرة. تظليل النموذج غريب إلى حد ما. السطح منحني بشكل صحيح ، لكنه يتفاعل مع الضوء كما لو كان مسطحًا.
هذا واضح جدا في الصور أدناه. الصورة العليا تظهر تظليل القائمة. يظهر الجزء السفلي كيف يعمل في الواقع.
يمكن أن يكون حل هذه المشكلة تحديا كبيرا. لكن أولاً ، نحن بحاجة إلى معرفة الخطأ.
غيرت عملية البثق العادية الهندسة العامة للطائرة التي استخدمناها في البداية. ومع ذلك ، فإن الوحدة غيرت فقط موضع القمم ، ولكن ليس اتجاهاتها العادية.
اتجاه الرأس
العادي ، كما يوحي الاسم ، هو ناقل طول الوحدة (
الاتجاه ) يشير عموديًا على السطح.
تعتبر القواعد الأساسية ضرورية لأنها تلعب دورًا مهمًا في تظليل نموذج ثلاثي الأبعاد. يتم استخدامها من قبل جميع التظليل السطحي لحساب كيف ينبغي أن ينعكس الضوء من كل مثلث من نموذج ثلاثي الأبعاد. عادةً ما يكون هذا ضروريًا لتحسين الأبعاد الثلاثة للنموذج ، على سبيل المثال ، يؤدي إلى ارتداد الضوء عن سطح مستو تمامًا مثلما ينتعش من سطح منحني. غالبًا ما تستخدم هذه الخدعة لجعل الأسطح منخفضة بولي تبدو أكثر سلاسة مما هي عليه بالفعل (انظر أدناه).
ومع ذلك ، في حالتنا يحدث العكس. الشكل الهندسي منحني وسلس ، ولكن بما أن جميع القواعد الطبيعية موجهة لأعلى ، فإن الضوء ينعكس من النموذج كما لو كان مسطحًا (انظر أدناه):
يمكنك قراءة المزيد حول دور الحالات الطبيعية في تظليل الكائنات في مقالة "
التعيين العادي" (تعيين التعتيم) ، حيث تبدو الأسطوانات المتطابقة مختلفة تمامًا ، على الرغم من نفس النموذج ثلاثي الأبعاد ، بسبب طرق مختلفة لحساب القيم الطبيعية الرأسية (انظر أدناه).
لسوء الحظ ، لا يوجد لدى الوحدة ولا لغة إنشاء تظليل حل مدمج لإعادة حساب القيم الطبيعية تلقائيًا. هذا يعني أنه يجب عليك تغييرها يدويًا وفقًا للهندسة المحلية للنموذج ثلاثي الأبعاد.
حساب عادي
الطريقة الوحيدة لحل مشكلة التظليل هي حساب القيم الطبيعية وفقًا لهندسة السطح يدويًا. نوقشت مشكلة مماثلة في
المنشور Vertex Displacement - Melting Shader Part 1 ، حيث تم استخدامه لمحاكاة ذوبان النماذج ثلاثية الأبعاد في لعبة
Cone Wars .
على الرغم من أن الشفرة النهائية ستضطر إلى العمل في الإحداثيات ثلاثية الأبعاد ، فلنقتصر المهمة على بعدين فقط في الوقت الحالي. تخيل أنك بحاجة إلى حساب
اتجاه المعدل الطبيعي المقابل للنقطة على منحنى ثنائي الأبعاد (السهم الأزرق الكبير في الرسم البياني أدناه).
من وجهة نظر هندسية ، يكون
اتجاه السهم
العادي (السهم الأزرق الكبير) متجهًا عموديًا إلى
المماس الذي يمر عبر نقطة الاهتمام بالنسبة لنا (خط أزرق رقيق). يمكن تمثيل
الظل كخط موجود على انحناء النموذج.
متجه المماس هو
متجه الوحدة الذي يقع على المماس.
هذا يعني أنه لحساب المعدل الطبيعي ، تحتاج إلى اتخاذ خطوتين: أولاً ، ابحث عن الخط
المماس إلى النقطة المطلوبة ؛ ثم احسب المتجه بشكل عمودي عليه (والذي سيكون
الاتجاه الضروري
للعادي ).
حساب الظل
للحصول على
الوضع الطبيعي ، نحتاج أولاً إلى حساب
الظل . يمكن تقريبها عن طريق أخذ عينات من نقطة قريبة واستخدامها لبناء خط بالقرب من قمة الرأس. أصغر الخط ، وأكثر دقة القيمة.
ثلاث خطوات مطلوبة:
- المرحلة 1. حرك كمية صغيرة على سطح مستو
- الخطوة 2. احسب ارتفاع النقطة الجديدة.
- الخطوة 3. استخدم ارتفاع النقطة الحالية لحساب الظل
كل هذا يمكن رؤيته في الصورة أدناه:
لكي ينجح هذا ، نحتاج إلى حساب ارتفاعات نقطتين ، وليس نقطة واحدة. لحسن الحظ ، نحن نعرف بالفعل كيفية القيام بذلك. في الجزء السابق من البرنامج التعليمي ، أنشأنا وظيفة تقوم باختبار ارتفاع المشهد الطبيعي على أساس نقطة شبكة. أطلقنا عليه
getVertex
.
يمكننا أن نأخذ قيمة قمة الرأس الجديدة عند النقطة الحالية ، ثم عند نقطتين أخريين. واحد سيكون للماس ، والآخر للماس في نقطتين. بمساعدتهم ، نحصل على الوضع الطبيعي. إذا كانت الشبكة الأصلية المستخدمة لإنشاء التأثير مستوية (وفي حالتنا هي) ، فلن نحتاج إلى الوصول إلى
v.normal
ويمكننا فقط استخدام
float3(0, 0, 1)
إلى نقطتين ، على التوالي
float3(0, 0, 1)
و
float3(1, 0, 0)
. إذا أردنا أن نفعل الشيء نفسه ، ولكن ، على سبيل المثال ، بالنسبة للكرة ، فإن العثور على نقطتين مناسبتين لحساب الظل والماس إلى نقطتين سيكون أكثر صعوبة.
ناقل العمل الفني
بعد الحصول على ناقلات الظل والماس المناسبة إلى نقطتين ، يمكننا حساب المعدل الطبيعي باستخدام عملية تسمى
منتج المتجه . هناك العديد من التعريفات والتفسيرات للعمل المتجه وما يفعله.
يستقبل المنتج المتجه متجهين ويعيد واحدًا جديدًا. إذا كان هناك متجهان أوليان وحدة (طولهما يساوي الوحدة) ، وكانا يقعان بزاوية 90 ، فسيكون المتجه الناتج عند 90 درجة بالنسبة لكليهما.
في البداية ، يمكن أن يكون هذا مربكًا ، ولكن من الناحية الرسومية ، يمكن تمثيله على النحو التالي: المنتج المتجه ذو المحورين يخلق ثالثًا. هذا هو
لكن ايضا
و هكذا.
إذا اتخذنا خطوة صغيرة بما فيه الكفاية (في الكود ، يتم
offset
هذا) ، فإن متجهات المماس والظل إلى نقطتين ستكون بزاوية 90 درجة.
جنبا إلى جنب مع ناقلات طبيعية ، فإنها تشكل ثلاثة محاور عمودية موجهة على طول سطح النموذج.مع العلم بذلك ، يمكننا كتابة جميع الكود اللازم لحساب وتحديث المتجه العادي. void vert(inout appdata_base v) { float3 bitangent = float3(1, 0, 0); float3 tangent = float3(0, 0, 1); float offset = 0.01; float4 vertexBitangent = getVertex(v.vertex + float4(bitangent * offset, 0) ); float4 vertex = getVertex(v.vertex); float4 vertexTangent = getVertex(v.vertex + float4(tangent * offset, 0) ); float3 newBitangent = (vertexBitangent - vertex).xyz; float3 newTangent = (vertexTangent - vertex).xyz; v.normal = cross(newTangent, newBitangent); v.vertex.y = vertex.y; }
وضع كل ذلك معا
الآن بعد أن أصبح كل شيء يعمل ، يمكننا إرجاع تأثير التمرير. void vert(inout appdata_base v) {
وعلى هذا اكتمال تأثيرنا في النهاية.إلى أين أذهب بعد ذلك
يمكن أن يصبح هذا البرنامج التعليمي أساسًا لتأثيرات أكثر تعقيدًا ، على سبيل المثال ، الإسقاط المجسم أو حتى نسخة من الطاولة الرملية من فيلم "النمر الأسود".حزمة الوحدة
يمكن تنزيل الحزمة الكاملة لهذا البرنامج التعليمي على Patreon ، حيث تحتوي على جميع الأصول اللازمة لتشغيل التأثير الموصوف.