الجمع بين الحقول مسافة موقعة في 2D

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


التحضير


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

يظهر التظليل الرئيسي الذي سنبدأ به على النحو التالي:

Shader "Tutorial/035_2D_SDF_Combinations/Champfer Union"{ 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{ //the material is completely non-transparent and is rendered at the same time as the other opaque geometry Tags{ "RenderType"="Opaque" "Queue"="Geometry"} 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; }; v2f vert(appdata v){ v2f o; //calculate the position in clip space to render the object o.position = UnityObjectToClipPos(v.vertex); //calculate world position of vertex o.worldPos = mul(unity_ObjectToWorld, v.vertex); return o; } float scene(float2 position) { const float PI = 3.14159; float2 squarePosition = position; squarePosition = translate(squarePosition, float2(1, 0)); squarePosition = rotate(squarePosition, .125); float squareShape = rectangle(squarePosition, float2(2, 2)); float2 circlePosition = position; circlePosition = translate(circlePosition, float2(-1.5, 0)); float circleShape = circle(circlePosition, 2.5); float combination = combination_function(circleShape, squareShape); return combination; } float4 _InsideColor; float4 _OutsideColor; float _LineDistance; float _LineThickness; float _SubLines; float _SubLineThickness; 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; } ENDCG } } FallBack "Standard" //fallback adds a shadow pass so we get shadows on other objects } 

و 2 D_SDF.cginc تعمل في نفس المجلد مع التظليل ، والتي سنقوم بتوسيعها ، في البداية تبدو كما يلي:

 #ifndef SDF_2D #define SDF_2D //transforms 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){ //move samplepoint in the opposite direction that we want to move shapes in return samplePosition - offset; } float2 scale(float2 samplePosition, float scale){ return samplePosition / scale; } //shapes float circle(float2 samplePosition, float radius){ //get distance from center and grow it according to radius return length(samplePosition) - radius; } 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; } #endif 

مجموعات بسيطة


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

الاقتران


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

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

سأذكر هنا وظيفة إنشاء الاقتران "دمج" ، جزئيًا لأننا ندمجهم ، جزئيًا لأن الكلمة الأساسية للاتحاد في hlsl محجوزة ، لذلك لا يمكن استخدامها كاسم الوظيفة.

 //in 2D_SDF.cginc include file float merge(float shape1, float shape2){ return min(shape1, shape2); } 

 //in scene function in shader float combination = merge(circleShape, squareShape); 




تقاطع


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

 //in 2D_SDF.cginc include file float intersect(float shape1, float shape2){ return max(shape1, shape2); } 

 //in scene function in shader float combination = intersect(circleShape, squareShape); 


الطرح


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

 //in 2D_SDF.cginc include file float subtract(float base, float subtraction){ return intersect(base, -subtraction); } 

 //in scene function in shader float combination = subtract(squareShape, circleShape); 


الاستيفاء


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

 //in 2D_SDF.cginc include file float interpolate(float shape1, float shape2, float amount){ return lerp(shape1, shape2, amount); } 

 //in scene function in shader float pulse = sin(_Time.y) * 0.5 + 0.5; float combination = interpolate(circleShape, pulse); 


مركبات أخرى


بعد تلقي اتصالات بسيطة ، لدينا بالفعل كل ما هو ضروري لمزيج بسيط من الأشكال ، لكن الخاصية المذهلة لحقول الإشارة عن بعد هي أنه لا يمكننا أن نقتصر على ذلك ، فهناك العديد من الطرق المختلفة لدمج الأشكال وتنفيذ الإجراءات المثيرة للاهتمام في أماكن اتصالهم. سأشرح هنا بعضًا من هذه التقنيات مرة أخرى فقط ، ولكن يمكنك العثور على العديد من التقنيات الأخرى في مكتبة http://mercury.sexy/hg_sdf (اكتب لي إذا كنت تعرف مكتبات SDF المفيدة الأخرى).

التقريب


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

 float round_merge(float shape1, float shape2, float radius){ float2 intersectionSpace = float2(shape1, shape2); intersectionSpace = min(intersectionSpace, 0); return length(intersectionSpace); } 


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

 float radius = max(sin(_Time.y * 5) * 0.5 + 0.4, 0); float combination = round_intersect(squareShape, circleShape, radius); 

 float round_merge(float shape1, float shape2, float radius){ float2 intersectionSpace = float2(shape1 - radius, shape2 - radius); intersectionSpace = min(intersectionSpace, 0); return length(intersectionSpace); } 


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

 float round_merge(float shape1, float shape2, float radius){ float2 intersectionSpace = float2(shape1 - radius, shape2 - radius); intersectionSpace = min(intersectionSpace, 0); return length(intersectionSpace) - radius; } 


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

 float round_merge(float shape1, float shape2, float radius){ float2 intersectionSpace = float2(shape1 - radius, shape2 - radius); intersectionSpace = min(intersectionSpace, 0); float insideDistance = -length(intersectionSpace); float simpleUnion = merge(shape1, shape2); float outsideDistance = max(simpleUnion, radius); return insideDistance + outsideDistance; } 


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

 float round_intersect(float shape1, float shape2, float radius){ float2 intersectionSpace = float2(shape1 + radius, shape2 + radius); intersectionSpace = max(intersectionSpace, 0); float outsideDistance = length(intersectionSpace); float simpleIntersection = intersect(shape1, shape2); float insideDistance = min(simpleIntersection, -radius); return outsideDistance + insideDistance; } 


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

 float round_subtract(float base, float subtraction, float radius){ round_intersect(base, -subtraction, radius); } 


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

شطبة


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


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

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

 float champferSize = sin(_Time.y * 5) * 0.3 + 0.3; float combination = champfer_merge(circleShape, squareShape, champferSize); 

 float champfer_merge(float shape1, float shape2, float champferSize){ const float SQRT_05 = 0.70710678118; float simpleMerge = merge(shape1, shape2); float champfer = (shape1 + shape2) * SQRT_05; champfer = champfer - champferSize; return merge(simpleMerge, champfer); } 


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

 float champfer_intersect(float shape1, float shape2, float champferSize){ const float SQRT_05 = 0.70710678118; float simpleIntersect = intersect(shape1, shape2); float champfer = (shape1 + shape2) * SQRT_05; champfer = champfer + champferSize; return intersect(simpleIntersect, champfer); } 


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

 float champfer_subtract(float base, float subtraction, float champferSize){ return champfer_intersect(base, -subtraction, champferSize); } 


تقاطع مستدير


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

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

 float round_border(float shape1, float shape2, float radius){ float2 position = float2(shape1, shape2); float distanceFromBorderIntersection = length(position); return distanceFromBorderIntersection - radius; } 


الشق الحدودي


آخر شيء سأشرحه هو طريقة إنشاء درجة في شكل ما عند موضع حد لشكل آخر.

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

 float depth = max(sin(_Time.y * 5) * 0.5 + 0.4, 0); float combination = groove_border(squareShape, circleShape, .3, depth); 

 float groove_border(float base, float groove, float width, float depth){ float circleBorder = abs(groove) - width; return circleBorder; } 


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

 float groove_border(float base, float groove, float width, float depth){ float circleBorder = abs(groove) - width; float grooveShape = subtract(circleBorder, base + depth); return grooveShape; } 


الخطوة الأخيرة هي طرح الشق من الشكل الأساسي وإرجاع النتيجة.

 float groove_border(float base, float groove, float width, float depth){ float circleBorder = abs(groove) - width; float grooveShape = subtract(circleBorder, base + depth); return subtract(base, grooveShape); } 


شفرة المصدر


المكتبة



 #ifndef SDF_2D #define SDF_2D //transforms 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){ //move samplepoint in the opposite direction that we want to move shapes in return samplePosition - offset; } float2 scale(float2 samplePosition, float scale){ return samplePosition / scale; } //combinations ///basic float merge(float shape1, float shape2){ return min(shape1, shape2); } float intersect(float shape1, float shape2){ return max(shape1, shape2); } float subtract(float base, float subtraction){ return intersect(base, -subtraction); } float interpolate(float shape1, float shape2, float amount){ return lerp(shape1, shape2, amount); } /// round float round_merge(float shape1, float shape2, float radius){ float2 intersectionSpace = float2(shape1 - radius, shape2 - radius); intersectionSpace = min(intersectionSpace, 0); float insideDistance = -length(intersectionSpace); float simpleUnion = merge(shape1, shape2); float outsideDistance = max(simpleUnion, radius); return insideDistance + outsideDistance; } float round_intersect(float shape1, float shape2, float radius){ float2 intersectionSpace = float2(shape1 + radius, shape2 + radius); intersectionSpace = max(intersectionSpace, 0); float outsideDistance = length(intersectionSpace); float simpleIntersection = intersect(shape1, shape2); float insideDistance = min(simpleIntersection, -radius); return outsideDistance + insideDistance; } float round_subtract(float base, float subtraction, float radius){ return round_intersect(base, -subtraction, radius); } ///champfer float champfer_merge(float shape1, float shape2, float champferSize){ const float SQRT_05 = 0.70710678118; float simpleMerge = merge(shape1, shape2); float champfer = (shape1 + shape2) * SQRT_05; champfer = champfer - champferSize; return merge(simpleMerge, champfer); } float champfer_intersect(float shape1, float shape2, float champferSize){ const float SQRT_05 = 0.70710678118; float simpleIntersect = intersect(shape1, shape2); float champfer = (shape1 + shape2) * SQRT_05; champfer = champfer + champferSize; return intersect(simpleIntersect, champfer); } float champfer_subtract(float base, float subtraction, float champferSize){ return champfer_intersect(base, -subtraction, champferSize); } /// round border intersection float round_border(float shape1, float shape2, float radius){ float2 position = float2(shape1, shape2); float distanceFromBorderIntersection = length(position); return distanceFromBorderIntersection - radius; } float groove_border(float base, float groove, float width, float depth){ float circleBorder = abs(groove) - width; float grooveShape = subtract(circleBorder, base + depth); return subtract(base, grooveShape); } //shapes float circle(float2 samplePosition, float radius){ //get distance from center and grow it according to radius return length(samplePosition) - radius; } 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; } #endif 

قاعدة شادر



 Shader "Tutorial/035_2D_SDF_Combinations/Round"{ 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{ //the material is completely non-transparent and is rendered at the same time as the other opaque geometry Tags{ "RenderType"="Opaque" "Queue"="Geometry"} 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; }; v2f vert(appdata v){ v2f o; //calculate the position in clip space to render the object o.position = UnityObjectToClipPos(v.vertex); //calculate world position of vertex o.worldPos = mul(unity_ObjectToWorld, v.vertex); return o; } float scene(float2 position) { const float PI = 3.14159; float2 squarePosition = position; squarePosition = translate(squarePosition, float2(1, 0)); squarePosition = rotate(squarePosition, .125); float squareShape = rectangle(squarePosition, float2(2, 2)); float2 circlePosition = position; circlePosition = translate(circlePosition, float2(-1.5, 0)); float circleShape = circle(circlePosition, 2.5); float combination = /* combination calculation here */; return combination; } float4 _InsideColor; float4 _OutsideColor; float _LineDistance; float _LineThickness; float _SubLines; float _SubLineThickness; 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; } ENDCG } } FallBack "Standard" //fallback adds a shadow pass so we get shadows on other objects } 

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


All Articles