خلق تظليل العشب في محرك الوحدة


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

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

تم نشر المشروع النهائي في نهاية المقال. يحتوي ملف التظليل الذي تم إنشاؤه على عدد كبير من التعليقات التي تجعل الفهم أسهل.

متطلبات


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

قم بتنزيل مسودة المشروع (.zip) .

الحصول على العمل


قم بتنزيل مسودة المشروع وافتحه في محرر الوحدة. افتح المشهد Main ، ثم افتح تظليل Grass في محرر الكود.

يحتوي هذا الملف على تظليل ينتج اللون الأبيض ، وكذلك بعض الوظائف التي سنستخدمها في هذا البرنامج التعليمي. ستلاحظ أن هذه الوظائف جنبًا إلى جنب مع تظليل قمة الرأس يتم تضمينها في كتلة CGINCLUDE الموجودة خارج SubShader . سيتم تضمين الكود الموجود في هذه الكتلة تلقائيًا في جميع عمليات المرور في التظليل ؛ سيكون هذا مفيدًا في وقت لاحق لأن تظليلنا سيكون له عدة تمريرات.

سنبدأ بكتابة تظليل هندسي يولد مثلثات من كل قمة على سطح شبكتنا.

1. تظليل هندسي


التظليل الهندسي جزء اختياري من خط أنابيب التقديم. يتم تنفيذها بعد التظليل قمة الرأس (أو تظليل التغطية بالفسيفساء إذا تم استخدام التغطية بالفسيفساء) وقبل أن تتم معالجة الرؤوس لتظليل التجزئة.


خط أنابيب رسومات Direct3D 11. لاحظ أنه في هذا الرسم التخطيطي يسمى تظليل الجزء بكسل تظليل بكسل .

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

 // Add inside the CGINCLUDE block. struct geometryOutput { float4 pos : SV_POSITION; }; [maxvertexcount(3)] void geo(triangle float4 IN[3] : SV_POSITION, inout TriangleStream<geometryOutput> triStream) { } … // Add inside the SubShader Pass, just below the #pragma fragment frag line. #pragma geometry geo 

تعلن الكود أعلاه عن تظليل هندسي يسمى 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); 


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

 // Update the return call in the vertex shader. //return UnityObjectToClipPos(vertex); return vertex; … // Update each assignment of o.pos in the geometry shader. o.pos = UnityObjectToClipPos(float4(0.5, 0, 0, 1)); … o.pos = UnityObjectToClipPos(float4(-0.5, 0, 0, 1)); … o.pos = UnityObjectToClipPos(float4(0, 1, 0, 1)); 


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

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

 // Add to the top of the geometry shader. float3 pos = IN[0]; … // Update each assignment of o.pos. o.pos = UnityObjectToClipPos(pos + float3(0.5, 0, 0)); … o.pos = UnityObjectToClipPos(pos + float3(-0.5, 0, 0)); … o.pos = UnityObjectToClipPos(pos + float3(0, 1, 0)); 


لماذا لا تنشئ بعض القمم مثلثًا؟

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

أو يمكنك حل هذه المشكلة عن طريق أخذ الشبكات التي تحتوي على نوع من نقاط الطوبولوجيا كشبكات واردة من التظليل الهندسي.

يتم رسم المثلثات الآن بشكل صحيح ، وتقع قاعدتها في ذروة انبعاثها. قبل الانتقال ، اجعل كائن GrassPlane غير نشط في المشهد ، وجعل كائن GrassBall نشطًا . نريد إنشاء العشب بشكل صحيح على أنواع مختلفة من الأسطح ، لذلك من المهم اختباره على شبكات ذات أشكال مختلفة.


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

2. مساحة الظل


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


في الفضاء المماسي ، يتم تعريف محاور X و Y و Z بالنسبة إلى الوضع الطبيعي وموضع السطح (في حالتنا ، الرؤوس).

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

يمكنك الوصول إلى المتجهات بشكل صحيح وأعلى عن طريق إضافة بيانات قمة إدخال جديدة.

 // Add to the CGINCLUDE block. struct vertexInput { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; }; struct vertexOutput { float4 vertex : SV_POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; }; … // Modify the vertex shader. vertexOutput vert(vertexInput v) { vertexOutput o; o.vertex = v.vertex; o.normal = v.normal; o.tangent = v.tangent; return o; } … // Modify the input for the geometry shader. Note that the SV_POSITION semantic is removed. void geo(triangle vertexOutput IN[3], inout TriangleStream<geometryOutput> triStream) … // Modify the existing line declaring pos. float3 pos = IN[0].vertex; 

يمكن حساب المتجه الثالث عن طريق أخذ المنتج المتجه بين اثنين آخرين. إرجاع منتج متجه متجه عمودي إلى متجهين واردة.

 // Place in the geometry shader, below the line declaring float3 pos. float3 vNormal = IN[0].normal; float4 vTangent = IN[0].tangent; float3 vBinormal = cross(vNormal, vTangent) * vTangent.w; 

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

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

 // Add below the lines declaring the three vectors. float3x3 tangentToLocal = float3x3( vTangent.x, vBinormal.x, vNormal.x, vTangent.y, vBinormal.y, vNormal.y, vTangent.z, vBinormal.z, vNormal.z ); 

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

 // Add to the CGINCLUDE block. geometryOutput VertexOutput(float3 pos) { geometryOutput o; o.pos = UnityObjectToClipPos(pos); return o; } … // Remove the following from the geometry shader. //geometryOutput o; //o.pos = UnityObjectToClipPos(pos + float3(0.5, 0, 0)); //triStream.Append(o); //o.pos = UnityObjectToClipPos(pos + float3(-0.5, 0, 0)); //triStream.Append(o); //o.pos = UnityObjectToClipPos(pos + float3(0, 1, 0)); //triStream.Append(o); // ...and replace it with the code below. triStream.Append(VertexOutput(pos + float3(0.5, 0, 0))); triStream.Append(VertexOutput(pos + float3(-0.5, 0, 0))); triStream.Append(VertexOutput(pos + float3(0, 1, 0))); 

أخيرًا ، نقوم بضرب رؤوس 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. الآن سنقوم بإجراء هذه التغييرات.

 // Modify the position of the third vertex being emitted. triStream.Append(VertexOutput(pos + mul(tangentToLocal, float3(0, 0, 1)))); 


3. ظهور العشب


لجعل مثلثات تبدو وكأنها شفرات العشب ، تحتاج إلى إضافة ألوان وأشكال مختلفة. نبدأ بإضافة التدرج النزولي من أعلى شفرة العشب.

3.1 لون التدرج


هدفنا هو السماح للفنان بتعيين لونين - من الأعلى والأسفل ، وأن ينقلب بين هذين اللونين إلى قاعدة شفرة العشب. يتم تعريف هذه الألوان بالفعل في ملف التظليل كـ _TopColor و _BottomColor . لأخذ العينات المناسبة ، تحتاج إلى تمرير إحداثيات الأشعة فوق البنفسجية لتظليل شظية.

 // Add to the geometryOutput struct. float2 uv : TEXCOORD0; … // Modify the VertexOutput function signature. geometryOutput VertexOutput(float3 pos, float2 uv) … // Add to VertexOutput, just below the line assigning o.pos. o.uv = uv; … // Modify the existing lines in the geometry shader. triStream.Append(VertexOutput(pos + mul(tangentToLocal, float3(0.5, 0, 0)), float2(0, 0))); triStream.Append(VertexOutput(pos + mul(tangentToLocal, float3(-0.5, 0, 0)), float2(1, 0))); triStream.Append(VertexOutput(pos + mul(tangentToLocal, float3(0, 0, 1)), float2(0.5, 1))); 

أنشأنا إحداثيات للأشعة فوق البنفسجية لشفرة من العشب على شكل مثلث ، يقع رأسا القاعدة في أسفل اليسار واليمين ، ويوجد رأس الحافة في الوسط في الأعلى.


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

الآن ، يمكننا lerp الألوان العلوية والسفلية في شظية القطعة باستخدام الأشعة فوق البنفسجية ومن ثم lerp مع lerp . سوف نحتاج أيضًا إلى تعديل معلمات تظليل الأجزاء ، وجعل geometryOutput float4 كإدخال ، وليس فقط موضع float4 .

 // Modify the function signature of the fragment shader. float4 frag (geometryOutput i, fixed facing : VFACE) : SV_Target … // Replace the existing return call. return float4(1, 1, 1, 1); return lerp(_BottomColor, _TopColor, i.uv.y); 


3.2 اتجاه شفرة عشوائي


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

هناك وظيفتان في ملف التظليل سيساعداننا على القيام بذلك: rand ، الذي يولد رقمًا عشوائيًا من المدخلات ثلاثية الأبعاد ، و AngleAxis3x3 ، الذي يستقبل الزاوية ( بالراديان ) ويعيد مصفوفة تدور هذه القيمة حول المحور المحدد. تعمل الوظيفة الأخيرة تمامًا مثل وظيفة C # AngleAxis3x3 تُرجع AngleAxis3x3 فقط مصفوفة وليست quaternion).

ترجع الدالة rand رقماً في النطاق 0 ... 1 ؛ نقوم بضربه في 2 Pi للحصول على مجموعة كاملة من القيم الزاوية.

 // Add below the line declaring the tangentToLocal matrix. float3x3 facingRotationMatrix = AngleAxis3x3(rand(pos) * UNITY_TWO_PI, float3(0, 0, 1)); 

نحن نستخدم موقف pos الوارد كبذور لدوران عشوائي. نتيجة لهذا ، سيكون لكل نصل من العشب دورته الخاصة ، ثابت في كل إطار.

يمكن تطبيق الدوران على شفرة الحشائش tangentToLocal بمصفوفة tangentToLocal تم إنشاؤها. لاحظ أن مصفوفة الضرب ليست تبادلية ؛ ترتيب المعاملات مهم .

 // Add below the line declaring facingRotationMatrix. float3x3 transformationMatrix = mul(tangentToLocal, facingRotationMatrix); … // Replace the multiplication matrix operand with our new transformationMatrix. triStream.Append(VertexOutput(pos + mul(transformationMatrix, float3(0.5, 0, 0)), float2(0, 0))); triStream.Append(VertexOutput(pos + mul(transformationMatrix, float3(-0.5, 0, 0)), float2(1, 0))); triStream.Append(VertexOutput(pos + mul(transformationMatrix, float3(0, 0, 1)), float2(0.5, 1))); 


3.3 الانحناء إلى الأمام عشوائي


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

 // Add as a new property. _BendRotationRandom("Bend Rotation Random", Range(0, 1)) = 0.2 … // Add to the CGINCLUDE block. float _BendRotationRandom; … // Add to the geometry shader, below the line declaring facingRotationMatrix. float3x3 bendRotationMatrix = AngleAxis3x3(rand(pos.zzx) * _BendRotationRandom * UNITY_PI * 0.5, float3(-1, 0, 0)); 

مرة أخرى نستخدم موضع شفرة العشب كبذور عشوائية ، هذه المرة عن طريق الكنس لإنشاء بذرة فريدة من نوعها. سنقوم أيضًا بضرب UNITY_PI بمقدار 0.5 ؛ هذا سيعطينا فاصل عشوائي من 0 ... 90 درجة.

نطبق هذه المصفوفة مرة أخرى من خلال التناوب ، ونضرب كل شيء بالترتيب الصحيح.

 // Modify the existing line. float3x3 transformationMatrix = mul(mul(tangentToLocal, facingRotationMatrix), bendRotationMatrix); 


3.4 العرض والارتفاع


بينما يقتصر حجم شفرة العشب على عرض وحدة واحدة وارتفاع 1 وحدة. سنضيف خصائص للتحكم في الحجم ، بالإضافة إلى خصائص لإضافة أشكال عشوائية.

 // Add as new properties. _BladeWidth("Blade Width", Float) = 0.05 _BladeWidthRandom("Blade Width Random", Float) = 0.02 _BladeHeight("Blade Height", Float) = 0.5 _BladeHeightRandom("Blade Height Random", Float) = 0.3 … // Add to the CGINCLUDE block. float _BladeHeight; float _BladeHeightRandom; float _BladeWidth; float _BladeWidthRandom; … // Add to the geometry shader, above the triStream.Append calls. float height = (rand(pos.zyx) * 2 - 1) * _BladeHeightRandom + _BladeHeight; float width = (rand(pos.xzy) * 2 - 1) * _BladeWidthRandom + _BladeWidth; … // Modify the existing positions with our new height and width. triStream.Append(VertexOutput(pos + mul(transformationMatrix, float3(width, 0, 0)), float2(0, 0))); triStream.Append(VertexOutput(pos + mul(transformationMatrix, float3(-width, 0, 0)), float2(1, 0))); triStream.Append(VertexOutput(pos + mul(transformationMatrix, float3(0, 0, height)), float2(0.5, 1))); 


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

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

4. التغطية بالفسيفساء


التغطية بالفسيفساء هي مرحلة اختيارية لخط أنابيب التقديم ، يتم تنفيذها بعد تظليل الرأس وقبل التظليل الهندسي (إن وجد). وتتمثل مهمتها في تقسيم سطح وارد واحد إلى العديد من العناصر البدائية. يتم تطبيق التغطية بالفسيفساء في خطوتين للبرمجة: تظليل الهيكل والمجال .

بالنسبة لتظليل السطح ، لدى Unity تطبيق التغطية بالفسيفساء المضمن . ومع ذلك ، نظرًا لأننا لا نستخدم التظليل السطحي ، فسيتعين علينا تطبيق تظليل النطاق والمجال الخاصة بنا. في هذه المقالة ، لن أناقش تطبيق CustomTessellation.cginc بالفسيفساء بالتفصيل ، ونحن ببساطة نستخدم ملف CustomTessellation.cginc الموجود. هذا الملف مقتبس من مقال Catlike Coding ، والذي يعد مصدرًا ممتازًا للمعلومات عن تنفيذ التغطية بالفسيفساء في Unity.

إذا قمنا بتضمين كائن TessellationExample في المشهد ، فسوف نرى أنه يحتوي بالفعل على مواد تنفذ التغطية بالفسيفساء. تغيير خاصية Tessellation Uniform يوضح تأثير التقسيم الفرعي.


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

 // Add inside the CGINCLUDE block, below the other #include statements. #include "Shaders/CustomTessellation.cginc" 

إذا قمت بفتح CustomTessellation.cginc ، فستلاحظ أن vertexOutput و vertexOutput ، وكذلك تظليل vertex ، معرّفة بالفعل في ذلك. لا حاجة لإعادة تعريفهم في تظليل العشب لدينا ؛ يمكن حذفها.

 /*struct vertexInput { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; }; struct vertexOutput { float4 vertex : SV_POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; }; vertexOutput vert(vertexInput v) { vertexOutput o; o.vertex = v.vertex; o.normal = v.normal; o.tangent = v.tangent; return o; }*/ 

لاحظ أن تظليل قمة الرأس في CustomTessellation.cginc ببساطة المدخلات مباشرة إلى مرحلة CustomTessellation.cginc بالفسيفساء ؛ vertexOutput وظيفة vertexOutput ، والتي تسمى داخل تظليل المجال ، مهمة إنشاء بنية vertexOutput .

الآن يمكننا إضافة تظليل shell والمجال إلى تظليل العشب. سنضيف أيضًا خاصية _TessellationUniform جديدة للتحكم في حجم الوحدة - تم إعلان المتغير المطابق لهذه الخاصية في CustomTessellation.cginc .

 // Add as a new property. _TessellationUniform("Tessellation Uniform", Range(1, 64)) = 1 … // Add below the other #pragma statements in the SubShader Pass. #pragma hull hull #pragma domain domain 

الآن تغيير خاصية Tessellation Uniform يتيح لنا التحكم في كثافة العشب. لقد وجدت أنه يتم الحصول على نتائج جيدة بقيمة 5 .


5. الريح


ننفذ الريح عن طريق أخذ عينات من تشويه الملمس . سيبدو هذا الملمس كخريطة عادية ، فقط به ستوجد قناتان فقط بدلاً من ثلاث قنوات. سوف نستخدم هاتين القناتين في اتجاهات الرياح على طول X و Y.


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

 // Add as new properties. _WindDistortionMap("Wind Distortion Map", 2D) = "white" {} _WindFrequency("Wind Frequency", Vector) = (0.05, 0.05, 0, 0) … // Add to the CGINCLUDE block. sampler2D _WindDistortionMap; float4 _WindDistortionMap_ST; float2 _WindFrequency; … // Add to the geometry shader, just above the line declaring the transformationMatrix. float2 uv = pos.xz * _WindDistortionMap_ST.xy + _WindDistortionMap_ST.zw + _WindFrequency * _Time.y; 

نحن نطبق مقياس وإزاحة _WindDistortionMap على الموضع ، ثم _Time.y إلى _Time.y ، محجوب إلى _WindFrequency . الآن سوف نستخدم هذه الأشعة فوق البنفسجية لأخذ عينات من النسيج وإنشاء خاصية للتحكم في قوة الرياح.

 // Add as a new property. _WindStrength("Wind Strength", Float) = 1 … // Add to the CGINCLUDE block. float _WindStrength; … // Add below the line declaring float2 uv. float2 windSample = (tex2Dlod(_WindDistortionMap, float4(uv, 0, 0)).xy * 2 - 1) * _WindStrength; 

لاحظ أننا نقوم بقياس القيمة التي تم أخذ عينات منها من النسيج من الفاصل الزمني 0 ... 1 إلى الفاصل -1 ... 1. بعد ذلك ، يمكننا إنشاء متجه طبيعي يدل على اتجاه الريح.

 // Add below the line declaring float2 windSample. float3 wind = normalize(float3(windSample.x, windSample.y, 0)); 

الآن يمكننا إنشاء مصفوفة للتدوير حول هذا المتجه وضربه بواسطة transformationMatrix بنا.

 // Add below the line declaring float3 wind. float3x3 windRotation = AngleAxis3x3(UNITY_PI * windSample, wind); … // Modify the existing line. float3x3 transformationMatrix = mul(mul(mul(tangentToLocal, windRotation), facingRotationMatrix), bendRotationMatrix); 

أخيرًا ، نقوم بنقل نسيج Wind (الموجود في جذر المشروع) إلى حقل Wind Distortion Map من المادة العشبية في محرر Unity. لقد قمنا أيضًا بتعيين المعلمة Tiling للنسيج على 0.01, 0.01 .


إذا لم يكن العشب متحركًا في نافذة Scene ، فانقر فوق Toggle skybox ، الضباب ، ومختلف تأثيرات المؤثرات الأخرى لتمكين المواد المتحركة.

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


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

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

 // Add below the line declaring float3x3 transformationMatrix. float3x3 transformationMatrixFacing = mul(tangentToLocal, facingRotationMatrix); … // Modify the existing lines outputting the base vertex positions. triStream.Append(VertexOutput(pos + mul(transformationMatrixFacing, float3(width, 0, 0)), float2(0, 0))); triStream.Append(VertexOutput(pos + mul(transformationMatrixFacing, float3(-width, 0, 0)), float2(1, 0))); 

6. انحناء شفرات العشب


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

سيتم تقسيم كل شفرة من العشب إلى عدة قطاعات . سيكون لكل مقطع شكل مستطيل ويتكون من مثلثين ، باستثناء الجزء العلوي - سيكون مثلثًا واحدًا يدل على طرف شفرة العشب.

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


شفرة منقسمة من العشب ، ممثلة كشريط مثلث وخلق رأسًا واحدًا في كل مرة. بعد القمم الثلاثة الأولى ، تشكل كل قمة جديدة مثلثًا جديدًا به القمتان السابقتان.

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

maxvertexcount#define

 // Add to the CGINCLUDE block. #define BLADE_SEGMENTS 3 … // Modify the existing line defining the maxvertexcount. [maxvertexcount(BLADE_SEGMENTS * 2 + 1)] 

في البداية ، قمنا بتعيين عدد الأجزاء على 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لتوليد رؤوس شفرة العشب. الحصول على الموضع والارتفاع والعرض ، فإنه يحول بشكل صحيح قمة الرأس باستخدام المصفوفة المرسلة ويعين له تنسيق الأشعة فوق البنفسجية. سنقوم بتحديث الكود الموجود حتى تعمل الوظيفة بشكل صحيح.

 // Update the existing code outputting the vertices. triStream.Append(GenerateGrassVertex(pos, width, 0, float2(0, 0), transformationMatrixFacing)); triStream.Append(GenerateGrassVertex(pos, -width, 0, float2(1, 0), transformationMatrixFacing)); triStream.Append(GenerateGrassVertex(pos, 0, height, float2(0.5, 1), transformationMatrix)); 

بدأت الوظيفة تعمل بشكل صحيح ، ونحن على استعداد لنقل رمز توليد قمة الرأس إلى الحلقة for. أضف ما float widthيلي تحت الخط :

 for (int i = 0; i < BLADE_SEGMENTS; i++) { float t = i / (float)BLADE_SEGMENTS; } 

نعلن عن دورة سيتم تشغيلها مرة واحدة لكل شفرة من قطعة العشب. داخل الحلقة ، أضف متغيرًا t. سيقوم هذا المتغير بتخزين قيمة في النطاق 0 ... 1 ، مما يشير إلى مدى تحركنا على طول شفرة العشب. نستخدم هذه القيمة لحساب عرض وارتفاع القطعة في كل تكرار للحلقة.

 // Add below the line declaring float t. float segmentHeight = height * t; float segmentWidth = width * (1 - t); 

عند تحريك شفرة العشب ، يزداد الارتفاع وينخفض ​​العرض. الآن يمكننا إضافة مكالمات إلى الحلقة GenerateGrassVertexلإضافة رؤوس إلى تيار المثلثات. سنضيف أيضًا مكالمة واحدة GenerateGrassVertexخارج الحلقة لإنشاء طرف شفرة العشب.

 // Add below the line declaring float segmentWidth. float3x3 transformMatrix = i == 0 ? transformationMatrixFacing : transformationMatrix; triStream.Append(GenerateGrassVertex(pos, segmentWidth, segmentHeight, float2(0, t), transformMatrix)); triStream.Append(GenerateGrassVertex(pos, -segmentWidth, segmentHeight, float2(1, t), transformMatrix)); … // Add just below the loop to insert the vertex at the tip of the blade. triStream.Append(GenerateGrassVertex(pos, 0, height, float2(0.5, 1), transformationMatrix)); … // Remove the existing calls to triStream.Append. //triStream.Append(GenerateGrassVertex(pos, width, 0, float2(0, 0), transformationMatrixFacing)); //triStream.Append(GenerateGrassVertex(pos, -width, 0, float2(1, 0), transformationMatrixFacing)); //triStream.Append(GenerateGrassVertex(pos, 0, height, float2(0.5, 1), transformationMatrix)); 

ألقِ نظرة على السطر المخصص للإعلان float3x3 transformMatrix- هنا نختار واحدًا من مصفوفات التحويل: نأخذ transformationMatrixFacingرؤوس القمم transformationMatrixولكل المراحل الأخرى.


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

 // Update the function signature of GenerateGrassVertex. geometryOutput GenerateGrassVertex(float3 vertexPosition, float width, float height, float forward, float2 uv, float3x3 transformMatrix) … // Modify the Y coordinate assignment of tangentPoint. float3 tangentPoint = float3(width, forward, height); 

لحساب إزاحة كل قمة ، فإننا نستبدل powالقيمة في الوظيفة t. بعد الوصول tإلى السلطة ، سيكون تأثيرها على الإزاحة الأمامية غير خطي وتحويل شفرة العشب إلى منحنى.

 // Add as new properties. _BladeForward("Blade Forward Amount", Float) = 0.38 _BladeCurve("Blade Curvature Amount", Range(1, 4)) = 2 … // Add to the CGINCLUDE block. float _BladeForward; float _BladeCurve; … // Add inside the geometry shader, below the line declaring float width. float forward = rand(pos.yyz) * _BladeForward; … // Add inside the loop, below the line declaring segmentWidth. float segmentForward = pow(t, _BladeCurve) * forward; … // Modify the GenerateGrassVertex calls inside the loop. triStream.Append(GenerateGrassVertex(pos, segmentWidth, segmentHeight, segmentForward, float2(0, t), transformMatrix)); triStream.Append(GenerateGrassVertex(pos, -segmentWidth, segmentHeight, segmentForward, float2(1, t), transformMatrix)); … // Modify the GenerateGrassVertex calls outside the loop. triStream.Append(GenerateGrassVertex(pos, 0, height, forward, float2(0.5, 1), transformationMatrix)); 

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


7. الإضاءة والظلال


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

7.1 صب الظلال


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

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

 // Add below the existing Pass. Pass { Tags { "LightMode" = "ShadowCaster" } CGPROGRAM #pragma vertex vert #pragma geometry geo #pragma fragment frag #pragma hull hull #pragma domain domain #pragma target 4.6 #pragma multi_compile_shadowcaster float4 frag(geometryOutput i) : SV_Target { SHADOW_CASTER_FRAGMENT(i) } ENDCG } 

بالإضافة إلى إنشاء شظية شظية جديدة ، هناك بعض الاختلافات المهمة في هذا المقطع. التسمية LightModeلها قيمة ShadowCaster، وليس ForwardBase- تقول الوحدة، أن هذه الفقرة ينبغي أن تستخدم لتقديم الكائن في خريطة الظل. هناك أيضا توجيه قبل المعالج هنا multi_compile_shadowcaster. يضمن أن التظليل يجمع كل الخيارات الضرورية المطلوبة لإلقاء الظلال.

اجعل كائن اللعبة Fence نشطًا في المشهد ؛ حتى نحصل على سطح يمكن لريش العشب أن تلقي بظلاله عليه.


7.2 الحصول على الظلال


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

 // Add to the geometryOutput struct. unityShadowCoord4 _ShadowCoord : TEXCOORD1; … // Add to the VertexOutput function, just above the return call. o._ShadowCoord = ComputeScreenPos(o.pos); 

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

لماذا يسمى تنسيق الأشعة فوق البنفسجية من مساحة الشاشة _ShadowCoord؟ هذا لا يتوافق مع اصطلاحات التسمية السابقة.
Unity ( ). SHADOW_ATTENUATION . Autolight.cginc , , .

 #define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord) 

- , .

 // Add to the ForwardBase pass's fragment shader, replacing the existing return call. return SHADOW_ATTENUATION(i); //return lerp(_BottomColor, _TopColor, i.uv.y); 

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

 // Add to the ForwardBase pass's preprocessor directives, below #pragma target 4.6. #pragma multi_compile_fwdbase 


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

 // Add at the end of the VertexOutput function, just above the return call. #if UNITY_PASS_SHADOWCASTER // Applying the bias prevents artifacts from appearing on the surface. o.pos = UnityApplyLinearShadowBias(o.pos); #endif 


بعد تطبيق إزاحة الظل الخطية ، تختفي القطع الأثرية للظلال على شكل خطوط من سطح المثلثات.

لماذا توجد قطع أثرية على طول حواف شفرات العشب المظللة؟

(multisample anti-aliasing MSAA ) Unity , . , .

— , , Unity . ( ); Unity .

7.3 الإضاءة


سنقوم بتنفيذ الإضاءة باستخدام خوارزمية حساب الإضاءة بسيطة ومشتركة للغاية.


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

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

عندما يكون Blade Curvature Amount هو 1 ، يتم توجيه كل شفرات العشب في مساحة الظل في اتجاه واحد: مباشرة مقابل المحور Y. كأول ممر لحلنا ، نقوم بحساب المعدل الطبيعي ، مع افتراض عدم وجود انحناء.

 // Add to the GenerateGrassVertex function, belowing the line declaring tangentPoint. float3 tangentNormal = float3(0, -1, 0); float3 localNormal = mul(transformMatrix, tangentNormal); 

tangentNormal، المعرّفة على أنها مباشرة مقابل المحور ص ، يتم تحويلها بنفس المصفوفة التي استخدمناها لتحويل نقاط الظل إلى مساحة محلية. الآن يمكننا نقلها إلى وظيفة VertexOutput، ومن ثم إلى هيكل geometryOutput.

 // Modify the return call in GenerateGrassVertex. return VertexOutput(localPosition, uv, localNormal); … // Add to the geometryOutput struct. float3 normal : NORMAL; … // Modify the existing function signature. geometryOutput VertexOutput(float3 pos, float2 uv, float3 normal) … // Add to the VertexOutput function to pass the normal through to the fragment shader. o.normal = UnityObjectToWorldNormal(normal); 

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

الآن يمكننا تصور الحالات الطبيعية في جزء التظليل ForwardBaseللتحقق من نتيجة عملنا.

 // Add to the ForwardBase fragment shader. float3 normal = facing > 0 ? i.normal : -i.normal; return float4(normal * 0.5 + 0.5, 1); // Remove the existing return call. //return SHADOW_ATTENUATION(i); 

منذ Cullيتم تعيين قيمة في تظليلنا Off، يتم تقديم كلا الجانبين من شفرة العشب. لكي يتم توجيه الوضع الطبيعي في الاتجاه الصحيح ، نستخدم معلمة مساعدة VFACEأضفناها إلى تظليل الأجزاء. ستُرجع

الوسيطة fixed facingرقمًا موجبًا إذا عرضنا الوجه الأمامي للسطح ورقم سالب إذا كان عكس ذلك. نستخدم هذا في الكود أعلاه لقلب الوضع الطبيعي إذا لزم الأمر.


عندما يكون مقدار Blade Curvature أكبر من 1 ، سيتم تبديل موضع Z المماسك لكل رأس من خلال الكمية التي forwardتم تمريرها إلى الوظيفة GenerateGrassVertex. سوف نستخدم هذه القيمة لتوسيع نطاق المحور Z بالتناسب .

 // Modify the existing line in GenerateGrassVertex. float3 tangentNormal = normalize(float3(0, -1, forward)); 

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

 // Add to the ForwardBase fragment shader, below the line declaring float3 normal. float shadow = SHADOW_ATTENUATION(i); float NdotL = saturate(saturate(dot(normal, _WorldSpaceLightPos0)) + _TranslucentGain) * shadow; float3 ambient = ShadeSH9(float4(normal, 1)); float4 lightIntensity = NdotL * _LightColor0 + float4(ambient, 1); float4 col = lerp(_BottomColor, _TopColor * lightIntensity, i.uv.y); return col; // Remove the existing return call. //return float4(normal * 0.5 + 0.5, 1); 


استنتاج


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


نسيج عشب مضمن في حزمة الأصول الموحدة لمحرك Unity. يتم رسم العديد من شفرات العشب على رباعي الزوايا ، مما يقلل من عدد المثلثات في المشهد.

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

شادر شفرة المصدر في مستودع جيثب

إضافة: التعاون


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

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

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

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


All Articles