على الرغم من أن الشبكات هي أبسط الطرق وأكثرها تنوعًا لتقديمها ، إلا أن هناك خيارات أخرى لتمثيل الأشكال في الأبعاد ثنائية وثلاثية الأبعاد. إحدى الطرق الشائعة الاستخدام هي حقول المسافة الموقعة (SDF). توفر حقول المسافات الموقَّعة تتبعًا أقل تكلفة للأشعة ، وتسمح للأشكال المختلفة بالتدفق بسلاسة إلى بعضها البعض مع توفير مواد منخفضة الدقة لصور عالية الجودة.
سنبدأ بتوليد علامة على حقول الحقول باستخدام الدالات في بعدين ، ولكننا سنستمر لاحقًا في إنشائها في ثلاثي الأبعاد. سأستخدم إحداثيات الفضاء العالمي بحيث يكون لدينا أقل قدر ممكن من الاعتماد على القياس والإحداثيات فوق البنفسجية ، لذلك إذا كنت لا تفهم كيف يعمل ، فدرس هذا
البرنامج التعليمي على تراكب مسطح ، وهو ما يفسر ما يحدث.
إعداد الأساس
سنقوم بإلغاء الخصائص مؤقتًا من تظليل الغطاء المسطح الأساسي ، لأننا في الوقت الحالي سنهتم بالأساس التقني. ثم نكتب موقع قمة الرأس في العالم مباشرةً إلى هيكل الشظية ، ولن نقوم أولاً بتحويله إلى UV. في المرحلة الأخيرة من الإعداد ، سنكتب وظيفة جديدة تحسب المشهد وتعيد المسافة إلى أقرب سطح. ثم نسميه الوظائف ونستخدم النتيجة كلون.
Shader "Tutorial/034_2D_SDF_Basics"{ SubShader{
سأكتب جميع وظائف حقول المسافة الموقعة في ملف منفصل حتى نتمكن من استخدامها بشكل متكرر. للقيام بذلك ، سوف أقوم بإنشاء ملف جديد. لن نضيف أي شر إلى ذلك ، ثم نضعه ونكمل الشرطية وتشمل الحماية ، ونتحقق أولاً مما إذا كان قد تم تعيين متغير المعالج المسبق. إذا لم يتم تعريفها بعد ، فنحن نحددها ونكمل الشرطية إذا تم إنشاؤها بعد الوظائف التي نريد تضمينها. ميزة ذلك هي أنه إذا أضفنا الملف مرتين (على سبيل المثال ، إذا أضفنا ملفين مختلفين ، لكل منهما الوظائف التي نحتاجها ، وكلاهما يضيفان نفس الملف) ، فسيؤدي ذلك إلى كسر التظليل. إذا كنت متأكدًا من أن هذا لن يحدث أبدًا ، فلا يمكنك إجراء هذا الفحص.
إذا كان ملف التضمين موجودًا في نفس المجلد مثل التظليل الرئيسي ، فيمكننا ببساطة تضمينه باستخدام بنية pragma.
لذلك سنرى سطحًا أسودًا فقط على السطح المقدم ، جاهزًا لعرض المسافة مع وجود علامة عليه.
دائرة
إن أبسط وظيفة لحقل المسافة الموقعة هي وظيفة الدائرة. ستتلقى الوظيفة فقط موضع العينة ونصف قطر الدائرة. نبدأ بالحصول على طول متجه موقف العينة. إذن ، نحصل على نقطة في الموضع (0 ، 0) ، والتي تشبه الدائرة التي يبلغ قطرها 0.
float circle(float2 samplePosition, float radius){ return length(samplePosition); }
ثم يمكنك استدعاء وظيفة الدائرة في وظيفة المشهد وإرجاع المسافة التي تعود إليها.
float scene(float2 position) { float sceneDistance = circle(position, 2); return sceneDistance; }
ثم نضيف نصف القطر إلى الحسابات. يتمثل أحد الجوانب المهمة لوظائف المسافة الموقعة في أنه عندما نكون داخل الكائن ، فإننا نحصل على مسافة سالبة إلى السطح (هذا ما تعنيه الكلمة الموقعة في حقل مسافة التعبير الموقعة). لزيادة الدائرة إلى دائرة نصف قطرها ، نقوم ببساطة بطرح نصف القطر من الطول. وبالتالي ، فإن السطح ، الذي هو في كل مكان حيث ترجع الدالة 0 ، يتحرك للخارج. ما هو في وحدتين من المسافة من السطح لدائرة بحجم 0 ، هو وحدة واحدة فقط من دائرة ذات دائرة نصف قطرها 1 ، ووحدة واحدة داخل الدائرة (القيمة هي -1) لدائرة نصف قطرها 3 ؛
float circle(float2 samplePosition, float radius){ return length(samplePosition) - radius; }
الآن الشيء الوحيد الذي لا يمكننا فعله هو نقل الدائرة من المركز. لإصلاح ذلك ، يمكنك إضافة وسيطة جديدة إلى وظيفة الدائرة لحساب المسافة بين موضع العينة ووسط الدائرة ، وطرح نصف القطر من هذه القيمة لتعريف دائرة. أو يمكنك إعادة تعريف الأصل عن طريق تحريك مساحة نقطة العينة ، ثم الحصول على دائرة في تلك المساحة. يبدو الخيار الثاني أكثر تعقيدًا ، ولكن نظرًا لأن الأجسام المتحركة هي عملية نرغب في استخدامها لجميع الأشكال ، فهي أكثر عالمية ، وبالتالي سأشرحها.
تتحرك
"تحول مساحة نقطة" - يبدو أسوأ بكثير مما هو عليه في الواقع. هذا يعني أننا نقوم بتمرير النقطة إلى الوظيفة ، وتغييرها بحيث لا يزال بإمكاننا استخدامها في المستقبل. في حالة النقل ، نقوم ببساطة بطرح الإزاحة من النقطة. يتم طرح الموضع عندما نريد نقل الأشكال في الاتجاه الإيجابي ، لأن الأشكال التي نعرضها في الفضاء تتحرك في الاتجاه المعاكس لتحريك الفضاء.
على سبيل المثال ، إذا أردنا رسم مجال في الموضع
(3, 4)
، فإننا نحتاج إلى تغيير المسافة بحيث يتحول
(3, 4)
إلى
(0, 0)
، ولهذا نحتاج إلى طرح
(3, 4)
. الآن إذا رسمنا كرة حول نقطة أصل
جديدة ، فستكون نقطة
قديمة (3, 4)
.
float scene(float2 position) { float2 circlePosition = translate(position, float2(3, 2)); float sceneDistance = circle(circlePosition, 2); return sceneDistance; }
المستطيل
شكل بسيط آخر هو مستطيل. بادئ ذي بدء ، فإننا نعتبر المكونات بشكل منفصل. أولاً نحصل على المسافة من المركز ، مع أخذ القيمة المطلقة. بعد ذلك ، على غرار الدائرة ، نطرح نصف الحجم (الذي يشبه نصف قطر المستطيل بشكل أساسي). لإظهار كيف ستبدو النتائج ، سنعود إلى عنصر واحد فقط في الوقت الحالي.
float rectangle(float2 samplePosition, float2 halfSize){ float2 componentWiseEdgeDistance = abs(samplePosition) - halfSize; return componentWiseEdgeDistance.x; }
الآن يمكننا الحصول على نسخة رخيصة من المستطيل ببساطة عن طريق إرجاع أكبر مكون 2. هذا يعمل في كثير من الحالات ، ولكن ليس بشكل صحيح ، لأنه لا يعرض المسافة الصحيحة حول الزوايا.
يمكن الحصول على القيم الصحيحة للمستطيل خارج الشكل عن طريق أخذ الحد الأقصى أولاً بين المسافات إلى الحواف و 0 ، ثم أخذ طوله.
إذا لم نحدد المسافة من الأسفل إلى 0 ، فنحن ببساطة نحسب المسافة إلى الزوايا (حيث تكون المسافات edge
(0, 0)
) ، لكن الإحداثيات بين الزوايا لن تقل عن 0 ، لذلك سيتم استخدام الحافة بأكملها. عيب هذا هو أن 0 يستخدم كمسافة من الحافة لكامل داخل الشكل.
لتصحيح المسافة 0 للجزء الداخلي بأكمله ، تحتاج إلى إنشاء المسافة الداخلية ، ببساطة باستخدام صيغة المستطيل الرخيص (أخذ الحد الأقصى للقيمة من المكون x و y) ، ومن ثم ضمان عدم تجاوزها أبدًا ، مع أخذ الحد الأدنى للقيمة منها إلى 0. ثم نضيف المسافة الخارجية التي لا تقل أبدًا عن 0 والمسافة الداخلية التي لا تتجاوز أبدًا 0 ، ونحصل على وظيفة المسافة النهائية.
float rectangle(float2 samplePosition, float2 halfSize){ float2 componentWiseEdgeDistance = abs(samplePosition) - halfSize; float outsideDistance = length(max(componentWiseEdgeDistance, 0)); float insideDistance = min(max(componentWiseEdgeDistance.x, componentWiseEdgeDistance.y), 0); return outsideDistance + insideDistance; }
نظرًا لأننا سبق أن سجلنا وظيفة النقل في شكل عالمي ، يمكننا الآن استخدامها لنقل مركزها إلى أي مكان.
float scene(float2 position) { float2 circlePosition = translate(position, float2(1, 0)); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; }
بدوره
تدوير الأشكال يشبه الحركة. قبل حساب المسافة إلى الشكل ، نقوم بتدوير الإحداثيات في الاتجاه المعاكس. لتبسيط فهم الدورات قدر الإمكان ، نقوم بمضاعفة الدوران بمقدار 2 * pi للحصول على الزاوية بالراديان. وبالتالي ، فإننا نمرر وظيفة التناوب ، حيث 0.25 هي ربع دورة ، 0.5 هو نصف دورة ، و 1 هو دور كامل (يمكنك إجراء تحويلات بشكل مختلف إذا كان يبدو أكثر طبيعية بالنسبة لك). نحن أيضًا نعكس الدوران ، لأننا نحتاج إلى تدوير الموضع في الاتجاه المعاكس من دوران الشكل لنفس السبب كما هو الحال عند الحركة.
لحساب الإحداثيات الدوارة ، نقوم أولاً بحساب الجيب وجيب التمام بناءً على الزاوية. Hlsl لديه وظيفة sincos التي تحسب كل من هذه القيم بشكل أسرع من عند حسابها بشكل منفصل.
عند إنشاء متجه جديد للمكون x ، نأخذ المكون الأصلي x مضروبًا بالجيب التمامي والمكون y مضروبًا بالجيب. يمكن تذكر ذلك بسهولة إذا كنت تتذكر أن جيب تمام 0 يساوي 1 ، وعندما يتم تدويره بواسطة 0 ، نريد أن يكون المكون x من المتجه الجديد هو نفسه تمامًا كما كان من قبل (أي ، ضرب 1). المكون y ، الذي أشار سابقًا إلى الأعلى ، لم يقدم أي مساهمة في المكون x ، يدور إلى اليمين ، وتبدأ قيمه من 0 ، في البداية يصبح أكبر ، أي أن حركته موصوفة تمامًا بواسطة جيب.
بالنسبة للمكون y للمتجه الجديد ، نقوم بضرب جيب التمام بمكون y للمتجه القديم ونطرح الجيب مضروباً بالمكون القديم x. لفهم لماذا نطرح ، بدلاً من إضافة الجيب ، مضروبًا بالمكون x ، من الأفضل أن نتخيل كيف يتغير المتجه
(1, 0)
عند تشغيله في اتجاه عقارب الساعة. يبدأ المكون y للنتيجة عند 0 ويصبح أقل من 0. وهذا هو عكس الطريقة التي يتصرف بها الجيب ، لذلك نغير العلامة.
float2 rotate(float2 samplePosition, float rotation){ const float PI = 3.14159; float angle = rotation * PI * 2 * -1; float sine, cosine; sincos(angle, sine, cosine); return float2(cosine * samplePosition.x + sine * samplePosition.y, cosine * samplePosition.y - sine * samplePosition.x); }
الآن وقد كتبنا طريقة التدوير ، يمكننا استخدامها مع النقل لنقل وتناوب الشكل.
float scene(float2 position) { float2 circlePosition = position; circlePosition = rotate(circlePosition, _Time.y); circlePosition = translate(circlePosition, float2(2, 0)); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; }
في هذه الحالة ، نقوم أولاً بتدوير الكائن حول مركز المشهد بأكمله ، بحيث يؤثر الدوران أيضًا على النقل. لتدوير رقم بالنسبة إلى مركزه ، تحتاج أولاً إلى تحريكه ، ثم تدويره. بسبب هذا الترتيب الذي تم تغييره بحلول وقت الدوران ، سيصبح مركز الشكل مركز نظام الإحداثيات.
float scene(float2 position) { float2 circlePosition = position; circlePosition = translate(circlePosition, float2(2, 0)); circlePosition = rotate(circlePosition, _Time.y); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; }
التحجيم
التحجيم يعمل بشكل مشابه لطرق أخرى لتحويل الأشكال. نقسم الإحداثيات حسب المقياس ، مما يجعل الشكل في الفضاء بنطاق مخفض ، وفي نظام الإحداثيات الأساسي يصبح حجمه أكبر.
float2 scale(float2 samplePosition, float scale){ return samplePosition / scale; }
float scene(float2 position) { float2 circlePosition = position; circlePosition = translate(circlePosition, float2(0, 0)); circlePosition = rotate(circlePosition, .125); float pulseScale = 1 + 0.5*sin(_Time.y * 3.14); circlePosition = scale(circlePosition, pulseScale); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; }
على الرغم من أن هذا يؤدي إلى القياس بشكل صحيح ، إلا أن المسافة تقيس أيضًا. الميزة الرئيسية لحقل المسافة الموقعة هي أننا نعرف دائمًا المسافة إلى أقرب سطح ، لكن التصغير يدمر هذه الخاصية تمامًا. يمكن إصلاح ذلك بسهولة عن طريق ضرب مجال المسافة الذي تم الحصول عليه من وظيفة مسافة الإشارة (في حالتنا ،
rectangle
) بالمقياس. للسبب نفسه ، لا يمكننا القياس بسهولة بشكل غير متساو (مع مقاييس مختلفة لمحوري x و y).
float scene(float2 position) { float2 circlePosition = position; circlePosition = translate(circlePosition, float2(0, 0)); circlePosition = rotate(circlePosition, .125); float pulseScale = 1 + 0.5*sin(_Time.y * 3.14); circlePosition = scale(circlePosition, pulseScale); float sceneDistance = rectangle(circlePosition, float2(1, 2)) * pulseScale; return sceneDistance; }
التصور
يمكن استخدام حقول المسافات الموقعة لمجموعة متنوعة من الأشياء ، مثل إنشاء الظلال ، وعرض المشاهد ثلاثية الأبعاد ، والفيزياء ، ونص العرض. لكننا لا نريد أن نتعمق في التعقيد ، لذلك سأشرح فقط تقنيتين لتصورهما. الأول هو شكل واضح مع الحواف ، والثاني هو تقديم خطوط اعتمادا على المسافة.
شكل واضح
تشبه هذه الطريقة تلك المستخدمة غالبًا عند تقديم النص ، فهي تنشئ نموذجًا واضحًا. إذا كنا نريد إنشاء حقل مسافة ليس من وظيفة ، ولكن لقراءته من نسيج ، فهذا يتيح لنا استخدام مواد ذات دقة أقل بكثير من المعتاد والحصول على نتائج جيدة. يستخدم TextMesh Pro هذه التقنية لتقديم النص.
لتطبيق هذه التقنية ، نستفيد من حقيقة أن البيانات الموجودة في حقول المسافات موقعة ، ونعرف نقطة الفصل. نبدأ بحساب مدى تغير حقل المسافة إلى البكسل التالي. يجب أن تكون هذه هي نفس قيمة طول الإحداثيات ، ولكن من الأسهل والأكثر موثوقية حساب المسافة بإشارة.
بعد تلقي تغيير المسافة ، يمكننا عمل
خطوة من نصف تغيير المسافة إلى ناقص / زائد نصف تغيير المسافة. سيؤدي هذا إلى إجراء عملية قص بسيطة حول 0 تقريبًا ، ولكن مع تجانس. ثم يمكنك استخدام هذه القيمة الملساء لأي قيمة ثنائية نحتاجها. في هذا المثال ، سوف أقوم بتغيير التظليل إلى تظليل الشفافية واستخدامه لقناة ألفا. أقوم بسلاسة من القيمة الموجبة إلى القيمة السالبة لأننا نريد أن تكون القيمة السلبية لحقل المسافة مرئية. إذا كنت لا تفهم تمامًا كيف يعمل تقديم الشفافية هنا ، فنوصيك قراءة
تعليمي الخاص بعرض الشفافية.
fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); float distanceChange = fwidth(dist) * 0.5; float antialiasedCutoff = smoothstep(distanceChange, -distanceChange, dist); fixed4 col = fixed4(_Color, antialiasedCutoff); return col; }
خطوط الارتفاع
أسلوب شائع آخر لتصور حقول المسافة هو عرض المسافات كخطوط. في تطبيقنا ، سأضيف بضعة خطوط سميكة وبعض الخطوط الرفيعة بينهما. سأقوم أيضًا برسم الشكل الخارجي والخارجي بألوان مختلفة حتى تتمكن من رؤية مكان الكائن.
سنبدأ بعرض الفرق بين الشكل الداخلي والخارجي. يمكن تخصيص الألوان في المادة ، لذلك سنضيف خصائص جديدة ، وكذلك متغيرات التظليل للألوان الداخلية والخارجية للشكل.
Properties{ _InsideColor("Inside Color", Color) = (.5, 0, 0, 1) _OutsideColor("Outside Color", Color) = (0, .5, 0, 1) }
ثم في التظليل الجزئي ، نتحقق من موقع البيكسل ، والذي نعرضه بمقارنة المسافة مع العلامة ب 0 باستخدام دالة
step
. نحن نستخدم هذا المتغير للتحريف من اللون الداخلي إلى اللون الخارجي وجعله على الشاشة.
fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); fixed4 col = lerp(_InsideColor, _OutsideColor, step(0, dist)); return col; }
لتقديم الخطوط ، نحتاج أولاً إلى تحديد عدد مرات عرض الخطوط ، ومدى ثقلها ، وتحديد الخصائص ومتغيرات التظليل المقابلة.
ثم ، لتقديم الخطوط ، سنبدأ بحساب التغيير في المسافة حتى نتمكن من استخدامه لاحقًا للتجانس. لقد قمنا بالفعل بتقسيمه على 2 ، لأننا في وقت لاحق نضيف نصفه ونطرح نصفه لتغطية مسافة التغيير التي تبلغ 1 بكسل.
float distanceChange = fwidth(dist) * 0.5;
ثم نأخذ المسافة ونحولها بحيث يكون لها نفس السلوك في نقاط التكرار. للقيام بذلك ، نقسمها أولاً على المسافة بين السطور ، في حين لن نحصل على أرقام كاملة في كل خطوة أولى ، ولكننا سنحصل على أرقام كاملة فقط على أساس المسافة التي حددناها.
ثم نضيف 0.5 للرقم ونأخذ الجزء الكسري ونطرح 0.5 مرة أخرى. هناك حاجة إلى الجزء الكسري والطرح هنا بحيث يمر الخط من خلال الصفر في نمط التكرار. نضيف 0.5 للحصول على الجزء الكسري من أجل تحييد الطرح الإضافي البالغ 0.5 - الإزاحة ستؤدي إلى حقيقة أن القيم التي يكون فيها الرسم البياني 0 هي 0 ، 1 ، 2 ، إلخ ، وليس 0.5 ، 1.5 ، الخ
الخطوات الأخيرة لتحويل القيمة - نأخذ القيمة المطلقة ونضربها مرة أخرى من خلال المسافة بين السطور. القيمة المطلقة تجعل المساحات قبل وبعد نقاط الخط تبقى كما هي ، مما يسهل إنشاء لقطة للخطوط. هناك حاجة إلى العملية الأخيرة ، حيث نضرب القيمة مرة أخرى من خلال المسافة بين السطور ، لتحييد القسمة في بداية المعادلة ، وبفضلها ، يكون التغيير في القيمة هو نفسه كما في البداية ، ولا يزال التغيير المحسوب مسبقًا في المسافة صحيحًا.
float majorLineDistance = abs(frac(dist / _LineDistance + 0.5) - 0.5) * _LineDistance;
الآن وقد قمنا بحساب المسافة إلى الخطوط بناءً على المسافة إلى الشكل ، يمكننا رسم الخطوط. نحن ننفذ خطوة بخطوة من الخطاب ناقص نصف التغير في المسافة إلى الخط الخيطي بالإضافة إلى نصف التغير في المسافة ونستخدم مسافة الخط المحسوبة كقيمة للمقارنة. بعد حساب هذه القيمة ، نقوم بضربها حسب اللون لإنشاء خطوط سوداء (يمكنك أيضًا التعشيق على لون مختلف إذا كنت بحاجة إلى خطوط متعددة الألوان).
fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); fixed4 col = lerp(_InsideColor, _OutsideColor, step(0, dist)); float distanceChange = fwidth(dist) * 0.5; float majorLineDistance = abs(frac(dist / _LineDistance + 0.5) - 0.5) * _LineDistance; float majorLines = smoothstep(_LineThickness - distanceChange, _LineThickness + distanceChange, majorLineDistance); return col * majorLines; }
نقوم بتنفيذ خطوط رفيعة بين الخطوط السميكة بالطريقة نفسها - نضيف خاصية تحدد عدد الخطوط الرفيعة التي ينبغي أن تكون بين الخطوط السميكة ، ثم نقوم بما فعلناه بالخطوط السميكة ، لكن بسبب المسافة بين الخطوط الرفيعة ، نقسم المسافة بين الخطوط السميكة على عدد الخطوط الرفيعة بين لهم. سنجعل أيضًا عدد الخطوط الرفيعة
IntRange
، وبفضل هذا يمكننا فقط تعيين قيم عدد صحيح وعدم الحصول على خطوط رقيقة لا
IntRange
الخطوط السميكة. بعد حساب الخطوط الرفيعة ، نقوم بضربها بالألوان بنفس طريقة الخطوط السميكة.
fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); fixed4 col = lerp(_InsideColor, _OutsideColor, step(0, dist)); float distanceChange = fwidth(dist) * 0.5; float majorLineDistance = abs(frac(dist / _LineDistance + 0.5) - 0.5) * _LineDistance; float majorLines = smoothstep(_LineThickness - distanceChange, _LineThickness + distanceChange, majorLineDistance); float distanceBetweenSubLines = _LineDistance / _SubLines; float subLineDistance = abs(frac(dist / distanceBetweenSubLines + 0.5) - 0.5) * distanceBetweenSubLines; float subLines = smoothstep(_SubLineThickness - distanceChange, _SubLineThickness + distanceChange, subLineDistance); return col * majorLines * subLines; }
شفرة المصدر
2D SDF الميزات
#ifndef SDF_2D #define SDF_2D float2 rotate(float2 samplePosition, float rotation){ const float PI = 3.14159; float angle = rotation * PI * 2 * -1; float sine, cosine; sincos(angle, sine, cosine); return float2(cosine * samplePosition.x + sine * samplePosition.y, cosine * samplePosition.y - sine * samplePosition.x); } float2 translate(float2 samplePosition, float2 offset){
مثال الدائرة
Shader "Tutorial/034_2D_SDF_Basics/Rectangle"{ SubShader{
مثال مستطيل
Shader "Tutorial/034_2D_SDF_Basics/Rectangle"{ SubShader{
قطع
Shader "Tutorial/034_2D_SDF_Basics/Cutoff"{ Properties{ _Color("Color", Color) = (1,1,1,1) } SubShader{ Tags{ "RenderType"="Transparent" "Queue"="Transparent"} Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Pass{ CGPROGRAM #include "UnityCG.cginc" #include "2D_SDF.cginc" #pragma vertex vert #pragma fragment frag struct appdata{ float4 vertex : POSITION; }; struct v2f{ float4 position : SV_POSITION; float4 worldPos : TEXCOORD0; }; fixed3 _Color; v2f vert(appdata v){ v2f o;
خطوط المسافة
Shader "Tutorial/034_2D_SDF_Basics/DistanceLines"{ Properties{ _InsideColor("Inside Color", Color) = (.5, 0, 0, 1) _OutsideColor("Outside Color", Color) = (0, .5, 0, 1) _LineDistance("Mayor Line Distance", Range(0, 2)) = 1 _LineThickness("Mayor Line Thickness", Range(0, 0.1)) = 0.05 [IntRange]_SubLines("Lines between major lines", Range(1, 10)) = 4 _SubLineThickness("Thickness of inbetween lines", Range(0, 0.05)) = 0.01 } SubShader{
آمل أن أتمكن من شرح أساسيات حقول المسافات بإشارة ، وأنت بالفعل تنتظر بعض البرامج التعليمية الجديدة التي سأتحدث فيها عن طرق أخرى لاستخدامها.