سيوضح لك هذا البرنامج التعليمي كيفية كتابة تظليل هندسي لإنشاء شفرات من العشب من قمم الشبكة الواردة واستخدام التغطية بالفسيفساء للتحكم في كثافة العشب.
توضح المقالة عملية خطوة بخطوة لكتابة تظليل العشب في الوحدة. يستقبل التظليل الشبكة الواردة ، ومن كل قمة للشبكة تولد شفرة من العشب باستخدام
التظليل الهندسي . من أجل الاهتمام والواقعية ، سيكون لريش العشب
حجم ودوران عشوائيان ، كما ستتأثر
بالرياح . للتحكم في كثافة العشب ، نستخدم
التغطية بالفسيفساء لفصل الشبكة الواردة. العشب سوف تكون قادرة على
يلقي وتلقي الظلال.
تم نشر
المشروع النهائي في نهاية المقال. يحتوي ملف التظليل الذي تم إنشاؤه على عدد كبير من التعليقات التي تجعل الفهم أسهل.
متطلبات
لإكمال هذا البرنامج التعليمي ، ستحتاج إلى معرفة عملية حول محرك الوحدة وفهمًا أوليًا لبناء جملة وظلال تظليل.
قم بتنزيل مسودة المشروع (.zip) .
الحصول على العمل
قم بتنزيل مسودة المشروع وافتحه في محرر الوحدة. افتح المشهد
Main
، ثم افتح تظليل
Grass
في محرر الكود.
يحتوي هذا الملف على تظليل ينتج اللون الأبيض ، وكذلك بعض الوظائف التي سنستخدمها في هذا البرنامج التعليمي. ستلاحظ أن هذه الوظائف جنبًا إلى جنب مع تظليل قمة الرأس يتم تضمينها في كتلة
CGINCLUDE
الموجودة
خارج SubShader
. سيتم
تضمين الكود الموجود في هذه الكتلة
تلقائيًا في جميع عمليات المرور في التظليل ؛ سيكون هذا مفيدًا في وقت لاحق لأن تظليلنا سيكون له عدة تمريرات.
سنبدأ بكتابة
تظليل هندسي يولد مثلثات من كل قمة على سطح شبكتنا.
1. تظليل هندسي
التظليل الهندسي جزء اختياري من خط أنابيب التقديم. يتم تنفيذها
بعد التظليل قمة الرأس (أو تظليل التغطية بالفسيفساء إذا تم استخدام التغطية بالفسيفساء) وقبل أن تتم معالجة الرؤوس لتظليل التجزئة.
خط أنابيب رسومات Direct3D 11. لاحظ أنه في هذا الرسم التخطيطي يسمى تظليل الجزء بكسل تظليل بكسل .يتلقى التظليل الهندسي
بدائية واحدة عند الإدخال ويمكن أن يولد صفرًا أو واحدًا أو أكثر من البدائل. سنبدأ بكتابة تظليل هندسي يتلقى
رأسًا (أو
نقطة ) عند الإدخال ، ويغذي
مثلثًا واحدًا يمثل شفرة من العشب.
تعلن الكود أعلاه عن تظليل هندسي يسمى
geo
مع معلمتين. الأول ،
triangle float4 IN[3]
، يشير إلى أنه سوف يستغرق مثلثًا واحدًا (يتكون من ثلاث نقاط) كمدخلات. والثاني ، مثل
TriangleStream
، يقوم بإعداد تظليل لإخراج دفق من المثلثات بحيث يستخدم كل قمة بنية
geometryOutput
للإخراج لنقل بياناته.
قلنا أعلاه أن التظليل سيحصل على قمة واحدة وينتج شفرة من العشب. لماذا إذن نحصل على مثلث؟سيكون أقل تكلفة لاتخاذ
كمدخلات. ويمكن القيام بذلك على النحو التالي.
void geo(point vertexOutput IN[1], inout TriangleStream<geometryOutput> triStream)
ومع ذلك ، نظرًا لأن شبكتنا الواردة (في هذه الحالة
GrassPlane10x10
، والموجودة في مجلد
Mesh
) لها
طوبولوجيا مثلث ، فإن هذا سيتسبب في عدم تطابق بين طوبولوجيا الشبكات الواردة والبدائية المطلوبة للإدخال. على الرغم من أن هذا
مسموح به في DirectX HLSL ، إلا أنه غير
مسموح به في OpenGL ، لذلك سيتم عرض خطأ.
بالإضافة إلى ذلك ، نضيف المعلمة الأخيرة بين أقواس مربعة أعلى إعلان الدالة:
[maxvertexcount(3)]
. يخبر GPU أننا سوف نخرج (ولكن ليس
مطلوبًا من ذلك)
أكثر من 3 رؤوس. نجعل أيضًا
SubShader
يستخدم
SubShader
هندسيًا بإعلانه داخل
Pass
.
لدينا تظليل هندسي لا تفعل أي شيء حتى الآن. لرسم مثلث ، أضف الكود التالي داخل التظليل الهندسي.
geometryOutput o; o.pos = float4(0.5, 0, 0, 1); triStream.Append(o); o.pos = float4(-0.5, 0, 0, 1); triStream.Append(o); o.pos = float4(0, 1, 0, 1); triStream.Append(o);
أعطى هذا نتائج غريبة جدا. عند تحريك الكاميرا ، يصبح من الواضح أن المثلث يتم تقديمه في
مساحة الشاشة . هذا منطقي: نظرًا لأن التظليل الهندسي يتم تنفيذه مباشرةً قبل معالجة القمم ، فإنه يستبعد من تظليل الرأس مسؤولية عرض القمم في
مساحة الاقتطاع . سوف نقوم بتغيير الكود الخاص بنا ليعكس هذا.
الآن يتم تقديم المثلث لدينا بشكل صحيح في العالم. ومع ذلك ، يبدو أن يتم إنشاء واحد فقط. في الواقع ، يتم
رسم مثلث واحد لكل قمة من شبكتنا ، لكن المواضع المخصصة لرؤوس المثلث
ثابتة - لا تتغير لكل قمة واردة. لذلك ، توجد كل المثلثات واحدة فوق الأخرى.
سنقوم بإصلاح ذلك عن طريق جعل مواضع الذروة الصادرة
تخالفًا بالنسبة إلى النقطة الواردة.
لماذا لا تنشئ بعض القمم مثلثًا؟على الرغم من أننا قررنا أن البدائي القادم سيكون
مثلثًا ، إلا أن شفرة العشب تنتقل فقط من
إحدى نقاط المثلث ، متجاهلة الآخرين. بالطبع ، يمكننا نقل شفرة من العشب من جميع النقاط الثلاثة الواردة ، ولكن هذا سيؤدي إلى حقيقة أن المثلثات المجاورة تنشئ بشكل مفرط شفرات من العشب فوق بعضها البعض.
أو يمكنك حل هذه المشكلة عن طريق أخذ الشبكات التي تحتوي على نوع من
نقاط الطوبولوجيا كشبكات واردة من التظليل الهندسي.
يتم رسم المثلثات الآن بشكل صحيح ، وتقع قاعدتها في ذروة انبعاثها. قبل الانتقال ، اجعل كائن
GrassPlane
غير نشط في المشهد ، وجعل كائن
GrassBall
نشطًا . نريد إنشاء العشب بشكل صحيح على أنواع مختلفة من الأسطح ، لذلك من المهم اختباره على شبكات ذات أشكال مختلفة.
في حين أن جميع المثلثات تنبعث في اتجاه واحد ، وليس الخارج من سطح الكرة. لحل هذه المشكلة ، سنقوم بإنشاء شفرات من العشب في
مساحة الظل .
2. مساحة الظل
من الناحية المثالية ، نود إنشاء شفرات من العشب من خلال تحديد عرض وطول وانحناء ودوران مختلفين ، دون مراعاة زاوية السطح التي تنبعث منها شفرة العشب. ببساطة ، نحدد شفرة من العشب في مساحة
موضعية للرأس التي تنبعث منها ، ثم نحولها بحيث تكون
موضعية للشبكة . هذا الفضاء يسمى
مساحة الظل .
في الفضاء المماسي ، يتم تعريف محاور X و Y و Z بالنسبة إلى الوضع الطبيعي وموضع السطح (في حالتنا ، الرؤوس).مثل أي مساحة أخرى ، يمكننا تحديد مساحة الظل في قمة الرأس بثلاثة متجهات:
اليمين ،
للأمام وللأعلى . باستخدام هذه المتجهات ، يمكننا إنشاء مصفوفة لتحويل شفرة العشب من الظل إلى الفضاء المحلي.
يمكنك الوصول إلى المتجهات بشكل
صحيح وأعلى عن طريق إضافة بيانات قمة إدخال جديدة.
يمكن حساب المتجه الثالث عن طريق أخذ
المنتج المتجه بين اثنين آخرين. إرجاع منتج متجه متجه
عمودي إلى متجهين واردة.
لماذا يتم ضرب ناتج المتجه بإحداثيات المماس؟عند تصدير شبكة من محرر ثلاثي الأبعاد ، عادةً ما تحتوي على نقاط ثنائية (تسمى أيضًا
الظلال بنقطتين ) مخزنة بالفعل في بيانات الشبكة. بدلاً من استيراد هذه الأشكال الثنائية ، تأخذ الوحدة ببساطة اتجاه كل ثنائي الاتجاه وتعيينها إلى إحداثيات الظل. يتيح لك ذلك حفظ الذاكرة ، مع توفير القدرة في نفس الوقت على إعادة إنشاء المعيار الصحيح. يمكن الاطلاع
هنا على مناقشة تفصيلية لهذا الموضوع.
بعد كل المتجهات الثلاثة ، يمكننا إنشاء مصفوفة للتحول بين المماس والمساحات المحلية. سنقوم بضرب كل قمة من شفرة العشب في هذه المصفوفة قبل تمريرها إلى
UnityObjectToClipPos
، والتي تتوقع قمة في الفضاء المحلي.
قبل استخدام المصفوفة ، ننقل رمز إخراج قمة الرأس إلى الوظيفة حتى لا نكتب نفس سطور الشفرة مرارًا وتكرارًا. وهذا ما يسمى
مبدأ DRY ، أو
لا تكرر نفسك .
أخيرًا ، نقوم بضرب رؤوس
tangentToLocal
بمصفوفة
tangentToLocal
،
tangentToLocal
بشكل صحيح مع العادي لنقطة الإدخال الخاصة بهم.
triStream.Append(VertexOutput(pos + mul(tangentToLocal, float3(0.5, 0, 0)))); triStream.Append(VertexOutput(pos + mul(tangentToLocal, float3(-0.5, 0, 0)))); triStream.Append(VertexOutput(pos + mul(tangentToLocal, float3(0, 1, 0))));
هذا أشبه ما نحتاجه ، ولكن ليس صحيحًا تمامًا. المشكلة هنا هي أننا في البداية قمنا بتعيين الاتجاه "لأعلى" (للأعلى) للمحور
Y ؛ ومع ذلك ، في الفضاء المماسي ، يوجد اتجاه الارتفاع عادةً على طول المحور
Z. الآن سنقوم بإجراء هذه التغييرات.
3. ظهور العشب
لجعل مثلثات تبدو وكأنها شفرات العشب ، تحتاج إلى إضافة ألوان وأشكال مختلفة. نبدأ بإضافة
التدرج النزولي من أعلى شفرة العشب.
3.1 لون التدرج
هدفنا هو السماح للفنان بتعيين لونين - من الأعلى والأسفل ، وأن ينقلب بين هذين اللونين إلى قاعدة شفرة العشب. يتم تعريف هذه الألوان بالفعل في ملف التظليل كـ
_TopColor
و
_BottomColor
. لأخذ العينات المناسبة ، تحتاج إلى تمرير
إحداثيات الأشعة فوق البنفسجية لتظليل شظية.
أنشأنا إحداثيات للأشعة فوق البنفسجية لشفرة من العشب على شكل مثلث ، يقع رأسا القاعدة في أسفل اليسار واليمين ، ويوجد رأس الحافة في الوسط في الأعلى.
إحداثيات الأشعة فوق البنفسجية من القمم الثلاثة لريش العشب. على الرغم من أننا نرسم شفرات العشب بتدرج بسيط ، إلا أن وجود ترتيب مماثل للأنسجة يتيح لك تراكب القوام.الآن ، يمكننا
lerp
الألوان العلوية والسفلية في شظية القطعة باستخدام الأشعة فوق البنفسجية ومن ثم
lerp
مع
lerp
. سوف نحتاج أيضًا إلى تعديل معلمات تظليل الأجزاء ، وجعل
geometryOutput
float4
كإدخال ، وليس فقط موضع
float4
.
3.2 اتجاه شفرة عشوائي
لخلق تقلب وإعطاء العشب نظرة أكثر طبيعية ، سنجعل كل شفرة من العشب تبدو في اتجاه عشوائي. للقيام بذلك ، نحتاج إلى إنشاء مصفوفة دوران تدور نصل العشب بكمية عشوائية حول محورها
الأعلى .
هناك وظيفتان في ملف التظليل سيساعداننا على القيام بذلك:
rand
، الذي يولد رقمًا عشوائيًا من المدخلات ثلاثية الأبعاد ، و
AngleAxis3x3
، الذي يستقبل الزاوية (
بالراديان ) ويعيد مصفوفة تدور هذه القيمة حول المحور المحدد. تعمل الوظيفة الأخيرة تمامًا مثل وظيفة C #
AngleAxis3x3
تُرجع
AngleAxis3x3
فقط مصفوفة وليست quaternion).
ترجع الدالة
rand
رقماً في النطاق 0 ... 1 ؛ نقوم بضربه في
2 Pi للحصول على مجموعة كاملة من القيم الزاوية.
نحن نستخدم موقف
pos
الوارد كبذور لدوران عشوائي. نتيجة لهذا ، سيكون لكل نصل من العشب دورته الخاصة ، ثابت في كل إطار.
يمكن تطبيق الدوران على شفرة الحشائش
tangentToLocal
بمصفوفة
tangentToLocal
تم إنشاؤها. لاحظ أن مصفوفة الضرب
ليست تبادلية ؛ ترتيب المعاملات
مهم .
3.3 الانحناء إلى الأمام عشوائي
إذا كانت جميع شفرات العشب محاذاة تمامًا ، فستظهر بنفس الشكل. قد يكون هذا مناسبًا للعشب الذي تم إعداده جيدًا ، على سبيل المثال ، في مروج مزخرفة ، ولكن في الطبيعة لا تنمو العشب من هذا القبيل. سنقوم بإنشاء مصفوفة جديدة لتدوير العشب على طول المحور
X ، وكذلك خاصية للتحكم في هذا الدوران.
مرة أخرى نستخدم موضع شفرة العشب كبذور عشوائية ، هذه المرة عن طريق
الكنس لإنشاء بذرة فريدة من نوعها. سنقوم أيضًا بضرب
UNITY_PI
بمقدار
0.5 ؛ هذا سيعطينا فاصل عشوائي من 0 ... 90 درجة.
نطبق هذه المصفوفة مرة أخرى من خلال التناوب ، ونضرب كل شيء بالترتيب الصحيح.
3.4 العرض والارتفاع
بينما يقتصر حجم شفرة العشب على عرض وحدة واحدة وارتفاع 1 وحدة. سنضيف خصائص للتحكم في الحجم ، بالإضافة إلى خصائص لإضافة أشكال عشوائية.
أصبحت المثلثات الآن أشبه بشفرات الحشائش ، ولكنها أيضًا قليلة جدًا. ببساطة ، لا توجد قمم كافية في الشبكة الواردة لإنشاء انطباع بحقل كثيف النمو.
أحد الحلول هو إنشاء شبكة جديدة أكثر كثافة ، إما باستخدام C # أو في محرر ثلاثي الأبعاد. سوف يعمل هذا ، لكن لن يسمح لنا بالتحكم ديناميكيًا في كثافة العشب. بدلاً من ذلك ، سنقوم بتقسيم الشبكة الواردة باستخدام
التغطية بالفسيفساء .
4. التغطية بالفسيفساء
التغطية بالفسيفساء هي مرحلة اختيارية لخط أنابيب التقديم ، يتم تنفيذها بعد تظليل الرأس وقبل التظليل الهندسي (إن وجد). وتتمثل مهمتها في تقسيم سطح وارد واحد إلى العديد من العناصر البدائية. يتم تطبيق التغطية بالفسيفساء في خطوتين للبرمجة: تظليل
الهيكل والمجال .
بالنسبة لتظليل السطح ، لدى Unity
تطبيق التغطية بالفسيفساء المضمن . ومع ذلك ، نظرًا لأننا
لا نستخدم التظليل السطحي ، فسيتعين علينا تطبيق تظليل النطاق والمجال الخاصة بنا. في هذه المقالة ، لن أناقش تطبيق
CustomTessellation.cginc
بالفسيفساء بالتفصيل ، ونحن ببساطة نستخدم ملف
CustomTessellation.cginc
الموجود. هذا الملف مقتبس من
مقال Catlike Coding ، والذي يعد مصدرًا ممتازًا للمعلومات عن تنفيذ
التغطية بالفسيفساء في Unity.
إذا قمنا بتضمين كائن
TessellationExample
في المشهد ، فسوف نرى أنه يحتوي بالفعل على مواد تنفذ التغطية بالفسيفساء. تغيير خاصية
Tessellation Uniform يوضح تأثير التقسيم الفرعي.
ننفذ التغطية بالفسيفساء في تظليل العشب للتحكم في كثافة الطائرة ، وبالتالي للتحكم في عدد من شفرات العشب المتولدة. تحتاج أولاً إلى إضافة ملف
CustomTessellation.cginc
. سوف نشير إليها من خلال طريقها
النسبي إلى التظليل.
إذا قمت بفتح
CustomTessellation.cginc
، فستلاحظ أن
vertexOutput
و
vertexOutput
، وكذلك تظليل vertex ، معرّفة بالفعل في ذلك. لا حاجة لإعادة تعريفهم في تظليل العشب لدينا ؛ يمكن حذفها.
لاحظ أن تظليل قمة الرأس في
CustomTessellation.cginc
ببساطة المدخلات مباشرة إلى مرحلة
CustomTessellation.cginc
بالفسيفساء ؛
vertexOutput
وظيفة
vertexOutput
، والتي تسمى داخل تظليل المجال ، مهمة إنشاء بنية
vertexOutput
.
الآن يمكننا إضافة تظليل
shell والمجال إلى تظليل العشب. سنضيف أيضًا خاصية
_TessellationUniform
جديدة للتحكم في حجم الوحدة - تم إعلان المتغير المطابق لهذه الخاصية في
CustomTessellation.cginc
.
الآن تغيير خاصية
Tessellation Uniform يتيح لنا التحكم في كثافة العشب. لقد وجدت أنه يتم الحصول على نتائج جيدة بقيمة
5 .
5. الريح
ننفذ الريح عن طريق أخذ عينات من
تشويه الملمس . سيبدو هذا الملمس
كخريطة عادية ، فقط به ستوجد قناتان فقط بدلاً من ثلاث قنوات. سوف نستخدم هاتين القناتين في اتجاهات الرياح على طول
X و
Y.قبل أخذ عينات من نسيج الرياح ، نحتاج إلى إنشاء تنسيق للأشعة فوق البنفسجية. بدلاً من استخدام إحداثيات النسيج المعينة للشبكة ، نطبق موضع النقطة الواردة. بفضل هذا ، إذا كان هناك العديد من الشبكات العشبية في العالم ، سيتم إنشاء الوهم بأنهم جزء من نظام الرياح نفسه. كما نستخدم
_Time
المدمج في shader _Time
لتمرير نسيج الرياح على طول سطح العشب.
نحن نطبق مقياس وإزاحة
_WindDistortionMap
على الموضع ، ثم
_Time.y
إلى
_Time.y
، محجوب إلى
_WindFrequency
. الآن سوف نستخدم هذه الأشعة فوق البنفسجية لأخذ عينات من النسيج وإنشاء خاصية للتحكم في قوة الرياح.
لاحظ أننا نقوم بقياس القيمة التي تم أخذ عينات منها من النسيج من الفاصل الزمني 0 ... 1 إلى الفاصل -1 ... 1. بعد ذلك ، يمكننا إنشاء متجه طبيعي يدل على اتجاه الريح.
الآن يمكننا إنشاء مصفوفة للتدوير حول هذا المتجه وضربه بواسطة
transformationMatrix
بنا.
أخيرًا ، نقوم بنقل نسيج
Wind
(الموجود في جذر المشروع) إلى حقل
Wind Distortion Map من المادة العشبية في محرر Unity. لقد قمنا أيضًا بتعيين المعلمة
Tiling للنسيج على
0.01, 0.01
.
إذا لم يكن العشب متحركًا في نافذة
Scene ، فانقر فوق
Toggle skybox ، الضباب ، ومختلف تأثيرات المؤثرات الأخرى لتمكين المواد المتحركة.
من مسافة بعيدة ، تبدو العشب صحيحة ، لكن إذا نظرنا عن كثب إلى شفرة العشب ، لاحظنا أن شفرة العشب بأكملها تدور ، وهذا هو السبب في أن القاعدة لم تعد متصلة بالأرض.لم تعد قاعدة شفرة العشب متصلة بالأرض ، ولكنها تتقاطع معها (كما هو موضح باللون الأحمر ) ، وتعلق فوق مستوى سطح الأرض (المشار إليه بالخط الأخضر ).سوف نصلح ذلك من خلال تحديد مصفوفة تحويل ثانية ، والتي تنطبق فقط على رأسين من القاعدة. في هذه المصفوفة لن شملت المصفوفة windRotation
و bendRotationMatrix
، وذلك بفضل التي يتم إرفاق قاعدة على سطح العشب.
6. انحناء شفرات العشب
الآن يتم تحديد شفرات العشب الفردية بواسطة مثلث واحد. على مسافات كبيرة ، هذه ليست مشكلة ، ولكن بالقرب من شفرة العشب تبدو جامدة وهندسية للغاية ، بدلاً من العضوية والحيوية. سنصلح ذلك من خلال بناء شفرات من العشب من عدة مثلثات وثنيها على طول المنحنى .سيتم تقسيم كل شفرة من العشب إلى عدة قطاعات . سيكون لكل مقطع شكل مستطيل ويتكون من مثلثين ، باستثناء الجزء العلوي - سيكون مثلثًا واحدًا يدل على طرف شفرة العشب.حتى الآن ، قمنا برسم ثلاثة رؤوس فقط ، وخلق مثلث واحد. كيف ، إذن ، إذا كان هناك مزيد من القمم ، هل يعرف التظليل الهندسي أي منها ينضم ويشكل مثلثات؟ الجواب في بنية البياناتشريط المثلث . تلتف القمم الثلاثة الأولى وتشكل مثلثًا ، وتشكل كل قمة جديدة مثلثًا مع الاثنين السابقتين.شفرة منقسمة من العشب ، ممثلة كشريط مثلث وخلق رأسًا واحدًا في كل مرة. بعد القمم الثلاثة الأولى ، تشكل كل قمة جديدة مثلثًا جديدًا به القمتان السابقتان.هذا ليس فقط أكثر فعالية من حيث استخدام الذاكرة ، ولكنه يسمح لك أيضًا بإنشاء تسلسلات المثلثات في التعليمات البرمجية الخاصة بك بسهولة وسرعة. إذا أردنا إنشاء عدة خطوط من المثلثات ، فيمكننا استدعاء RestartStrip للحصول على TriangleStream
الوظيفة . قبل أن نبدأ في رسم المزيد من القمم من التظليل الهندسي ، نحتاج إلى زيادتها . سنستخدم التصميم للسماح لمؤلف التظليل بالتحكم في عدد القطاعات وحساب عدد الرؤوس المعروضة منه.maxvertexcount
#define
في البداية ، قمنا بتعيين عدد الأجزاء على 3 وتحديثها maxvertexcount
لحساب عدد القمم بناءً على عدد القطاعات.لإنشاء شفرة مجزأة من العشب ، نستخدم دورة for
. وكل التكرار من الحلقة إضافة على قمتين : اليسار و اليمين . بعد الانتهاء من الحافة ، نضيف قمة الرأس الأخيرة على طرف شفرة العشب.قبل القيام بذلك ، سيكون من المفيد نقل جزء من موضع الحوسبة لرؤوس شفرات العشب من الشفرة إلى الوظيفة ، لأننا سنستخدم هذا الرمز عدة مرات داخل الحلقة وخارجها. أضف CGINCLUDE
التالي إلى الكتلة : geometryOutput GenerateGrassVertex(float3 vertexPosition, float width, float height, float2 uv, float3x3 transformMatrix) { float3 tangentPoint = float3(width, 0, height); float3 localPosition = vertexPosition + mul(transformMatrix, tangentPoint); return VertexOutput(localPosition, uv); }
تقوم هذه الوظيفة بنفس المهام لأنها تتجاوز الحجج التي مررنا بها سابقًا VertexOutput
لتوليد رؤوس شفرة العشب. الحصول على الموضع والارتفاع والعرض ، فإنه يحول بشكل صحيح قمة الرأس باستخدام المصفوفة المرسلة ويعين له تنسيق الأشعة فوق البنفسجية. سنقوم بتحديث الكود الموجود حتى تعمل الوظيفة بشكل صحيح.
بدأت الوظيفة تعمل بشكل صحيح ، ونحن على استعداد لنقل رمز توليد قمة الرأس إلى الحلقة for
. أضف ما float width
يلي تحت الخط : for (int i = 0; i < BLADE_SEGMENTS; i++) { float t = i / (float)BLADE_SEGMENTS; }
نعلن عن دورة سيتم تشغيلها مرة واحدة لكل شفرة من قطعة العشب. داخل الحلقة ، أضف متغيرًا t
. سيقوم هذا المتغير بتخزين قيمة في النطاق 0 ... 1 ، مما يشير إلى مدى تحركنا على طول شفرة العشب. نستخدم هذه القيمة لحساب عرض وارتفاع القطعة في كل تكرار للحلقة.
عند تحريك شفرة العشب ، يزداد الارتفاع وينخفض العرض. الآن يمكننا إضافة مكالمات إلى الحلقة GenerateGrassVertex
لإضافة رؤوس إلى تيار المثلثات. سنضيف أيضًا مكالمة واحدة GenerateGrassVertex
خارج الحلقة لإنشاء طرف شفرة العشب.
ألقِ نظرة على السطر المخصص للإعلان float3x3 transformMatrix
- هنا نختار واحدًا من مصفوفات التحويل: نأخذ transformationMatrixFacing
رؤوس القمم transformationMatrix
ولكل المراحل الأخرى.يتم الآن تقسيم شفرات الحشائش إلى عدة قطاعات ، لكن سطح الشفرة لا يزال مسطحًا - لم تشارك المثلثات الجديدة بعد. سوف نقوم بإضافة شفرة من العشب انحناء، وتحويل الموقف من قمة الرأس من لY . أولاً ، نحتاج إلى تعديل الوظيفة GenerateGrassVertex
حتى تحصل على إزاحة في Y ، والتي سوف نسميها forward
.
لحساب إزاحة كل قمة ، فإننا نستبدل pow
القيمة في الوظيفة t
. بعد الوصول t
إلى السلطة ، سيكون تأثيرها على الإزاحة الأمامية غير خطي وتحويل شفرة العشب إلى منحنى.
هذا جزء كبير من الشفرة ، لكن كل العمل يتم بشكل مشابه لما تم القيام به لعرض وارتفاع شفرة العشب. مع انخفاض القيم _BladeForward
، _BladeCurve
وحصلنا على مروج جيد الإعداد ، والقيم الأكبر ستمنح التأثير المعاكس.7. الإضاءة والظلال
كخطوة نهائية لإتمام تظليل أضفنا القدرة على تجاهل و تلقي الظلال. سنضيف أيضًا إضاءة بسيطة من المصدر الرئيسي للضوء الاتجاهي.7.1 صب الظلال
لإلقاء الظلال في الوحدة ، تحتاج إلى إضافة تمريرة ثانية إلى التظليل. سيتم استخدام هذا المقطع من خلال مصادر الإضاءة التي تنشئ الظل في المشهد لتجعل عمق العشب في خريطة الظل الخاصة بهم . هذا يعني أنه يجب إطلاق التظليل الهندسي في ممر الظل ، بحيث تتمكن شفرات العشب من الظلال.نظرًا لأن التظليل الهندسي مكتوب داخل الكتل CGINCLUDE
، فيمكننا استخدامه في أي ممر للملف. قم بإنشاء تمريرة ثانية تستخدم نفس التظليلات مثل الأولى ، باستثناء التظليل الجزئي - سنحدد مرورًا جديدًا سنكتب فيه ماكرو يعالج الإخراج.
بالإضافة إلى إنشاء شظية شظية جديدة ، هناك بعض الاختلافات المهمة في هذا المقطع. التسمية LightMode
لها قيمة ShadowCaster
، وليس ForwardBase
- تقول الوحدة، أن هذه الفقرة ينبغي أن تستخدم لتقديم الكائن في خريطة الظل. هناك أيضا توجيه قبل المعالج هنا multi_compile_shadowcaster
. يضمن أن التظليل يجمع كل الخيارات الضرورية المطلوبة لإلقاء الظلال.اجعل كائن اللعبة Fence
نشطًا في المشهد ؛ حتى نحصل على سطح يمكن لريش العشب أن تلقي بظلاله عليه.7.2 الحصول على الظلال
بعد أن تقوم الوحدة بإظهار خريطة الظل من وجهة نظر مصدر الضوء لإنشاء الظل ، فإنها تطلق ممرًا "يجمع" الظلال في نسيج مساحة الشاشة . لأخذ عينات من هذا الملمس ، سنحتاج إلى حساب مواضع القمم في مساحة الشاشة ونقلها إلى تظليل الأجزاء.
في التظليل الجزئي للممر ، ForwardBase
يمكننا استخدام ماكرو للحصول على قيمة float
تشير إلى ما إذا كان السطح في الظلال أم لا. هذه القيمة في النطاق 0 ... 1 ، حيث 0 عبارة عن تظليل كامل ، 1 هي إضاءة كاملة.لماذا يسمى تنسيق الأشعة فوق البنفسجية من مساحة الشاشة _ShadowCoord؟ هذا لا يتوافق مع اصطلاحات التسمية السابقة.Unity ( ).
SHADOW_ATTENUATION
.
Autolight.cginc
, , .
#define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
- , .
أخيرًا ، نحتاج إلى تكوين التظليل بشكل صحيح لتلقي الظلال. للقيام بذلك ، سنضيف ForwardBase
توجيهًا قبل المعالجة إلى التمريرة بحيث يجمع كل خيارات التظليل اللازمة.
بعد تقريب الكاميرا ، يمكننا ملاحظة القطع الأثرية على سطح شفرات العشب ؛ إنها ناجمة عن حقيقة أن شفرات العشب الفردية تلقي بظلالها على نفسها. يمكننا إصلاح ذلك عن طريق تطبيق نوبة خطية أو تحريك مواضع القمم في مساحة الاقتطاع قليلاً بعيدًا عن الشاشة. سنستخدم ماكرو الوحدة لهذا وسندرجه في التصميم #if
بحيث يتم تنفيذ العملية فقط في مسار الظل.
بعد تطبيق إزاحة الظل الخطية ، تختفي القطع الأثرية للظلال على شكل خطوط من سطح المثلثات.لماذا توجد قطع أثرية على طول حواف شفرات العشب المظللة؟(multisample anti-aliasing
MSAA ) Unity
, . , .
— , ,
Unity . ( );
Unity .
7.3 الإضاءة
سنقوم بتنفيذ الإضاءة باستخدام خوارزمية حساب الإضاءة بسيطة ومشتركة للغاية.... حيث N هو الطبيعي على السطح ، L هو الاتجاه الطبيعي للمصدر الرئيسي للإضاءة الاتجاه ، وأنا الإضاءة المحسوبة. في هذا البرنامج التعليمي ، لن نطبق إضاءة غير مباشرة.في الوقت الحالي ، لا يتم تعيين الحالات الطبيعية إلى رؤوس شفرات العشب. كما هو الحال مع مواضع الذروة ، نقوم أولاً بحساب القيم الطبيعية في مساحة الظل ومن ثم تحويلها إلى محلية.عندما يكون Blade Curvature Amount هو 1 ، يتم توجيه كل شفرات العشب في مساحة الظل في اتجاه واحد: مباشرة مقابل المحور Y. كأول ممر لحلنا ، نقوم بحساب المعدل الطبيعي ، مع افتراض عدم وجود انحناء.
tangentNormal
، المعرّفة على أنها مباشرة مقابل المحور ص ، يتم تحويلها بنفس المصفوفة التي استخدمناها لتحويل نقاط الظل إلى مساحة محلية. الآن يمكننا نقلها إلى وظيفة VertexOutput
، ومن ثم إلى هيكل geometryOutput
.
لاحظ أنه قبل الاستنتاج ، نقوم بتحويل الطبيعي إلى الفضاء العالمي ؛ الوحدة تنقل إلى تظليل اتجاه المصدر الرئيسي للضوء الاتجاه في الفضاء العالمي ، لذلك هذا التحول ضروري.الآن يمكننا تصور الحالات الطبيعية في جزء التظليل ForwardBase
للتحقق من نتيجة عملنا.
منذ Cull
يتم تعيين قيمة في تظليلنا Off
، يتم تقديم كلا الجانبين من شفرة العشب. لكي يتم توجيه الوضع الطبيعي في الاتجاه الصحيح ، نستخدم معلمة مساعدة VFACE
أضفناها إلى تظليل الأجزاء. ستُرجعالوسيطة fixed facing
رقمًا موجبًا إذا عرضنا الوجه الأمامي للسطح ورقم سالب إذا كان عكس ذلك. نستخدم هذا في الكود أعلاه لقلب الوضع الطبيعي إذا لزم الأمر.عندما يكون مقدار Blade Curvature أكبر من 1 ، سيتم تبديل موضع Z المماسك لكل رأس من خلال الكمية التي forward
تم تمريرها إلى الوظيفة GenerateGrassVertex
. سوف نستخدم هذه القيمة لتوسيع نطاق المحور Z بالتناسب .
أخيرًا ، أضف الكود إلى شظية القطعة لتجمع الظلال والإضاءة الاتجاهية والإضاءة المحيطة. أوصي بدراسة معلومات أكثر تفصيلاً حول تنفيذ الإضاءة المخصصة في التظليل في تعليمي على shaders toon .
استنتاج
في هذا البرنامج التعليمي ، يغطي العشب مساحة صغيرة من 10x10 وحدة. لكي يغطي التظليل المساحات المفتوحة الكبيرة مع الحفاظ على الأداء العالي ، يجب تقديم تحسينات. يمكنك تطبيق التغطية بالفسيفساء على أساس المسافة بحيث يتم تقليل عدد أقل من شفرات العشب بعيدًا عن الكاميرا. بالإضافة إلى ذلك ، على مسافات طويلة ، بدلاً من شفرات العشب الفردية ، يمكن رسم مجموعات من شفرات العشب باستخدام رباعي الزوايا مع نسيج متراكب.نسيج عشب مضمن في حزمة الأصول الموحدة لمحرك Unity. يتم رسم العديد من شفرات العشب على رباعي الزوايا ، مما يقلل من عدد المثلثات في المشهد.على الرغم من أننا لا نستطيع أصلاً استخدام التظليل الهندسي مع التظليل السطحي ، لتحسين أو توسيع وظيفة الإضاءة والتظليل ، إذا كنت بحاجة إلى استخدام نموذج الإضاءة Unity القياسي ، يمكنك دراسة مستودع GitHub ، الذي يوضح حل المشكلة عن طريق التأخر في التقديم والتعبئة اليدوية لخزائن G-buffer.شادر شفرة المصدر في مستودع جيثبإضافة: التعاون
بدون التشغيل المتداخل ، قد تبدو التأثيرات الرسومية ثابتة أو هامدة للاعبين. هذا البرنامج التعليمي طويل جدًا بالفعل ، لذلك لم أقم بإضافة قسم عن تفاعل الكائنات العالمية بالعشب.قد يتضمن التطبيق الساذج للأعشاب التفاعلية عنصرين: شيء في عالم اللعبة يمكنه نقل البيانات إلى التظليل لإخباره عن أي جزء من العشب يجري التفاعل معه ، ورمز في التظليل لتفسير هذه البيانات.يتم عرض مثال على كيفية تنفيذ ذلك بالماء هنا . يمكن تكييفها للعمل مع العشب. بدلاً من رسم التموجات في المكان الذي توجد فيه الشخصية ، يمكنك تحويل شفرة العشب لمحاكاة تأثيرات الخطوات.