Menggabungkan Bidang Jarak yang Ditandatangani dalam 2D

Dalam tutorial sebelumnya, kami belajar cara membuat dan memindahkan bentuk sederhana menggunakan fungsi jarak yang ditandatangani. Pada artikel ini, kita akan belajar cara menggabungkan beberapa bentuk untuk membuat bidang jarak yang lebih kompleks. Sebagian besar teknik yang dijelaskan di sini saya pelajari dari perpustakaan fungsi jarak dengan tanda glsl, yang dapat ditemukan di sini . Ada juga beberapa cara untuk menggabungkan bentuk, yang tidak saya bahas di sini.


Persiapan


Untuk visualisasi bidang jarak yang ditandatangani (bidang jarak yang ditandatangani, SDF) kami akan menggunakan satu konfigurasi sederhana, dan kemudian menerapkan operator ke sana. Untuk menampilkan bidang jarak, itu akan menggunakan visualisasi garis jarak dari tutorial pertama. Demi kesederhanaan, kami akan mengatur semua parameter kecuali untuk parameter visualisasi dalam kode, tetapi Anda dapat mengganti nilai apa pun dengan properti untuk membuatnya dapat disesuaikan.

Shader utama yang akan kita mulai dengan tampilan seperti ini:

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 } 

Dan fungsi 2D_SDF.cginc dalam folder yang sama dengan shader, yang akan kita kembangkan, pada awalnya terlihat seperti ini:

 #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 

Kombinasi sederhana


Kami akan mulai dengan beberapa cara sederhana untuk menggabungkan dua bentuk untuk membuat satu bentuk besar, konjugasi, persimpangan, dan pengurangan, serta cara untuk mengubah satu bentuk menjadi yang lain.

Pemasangan


Operator paling sederhana adalah pemasangan. Dengan itu, kita bisa menyatukan kedua sosok dan mendapatkan jarak dengan tanda dari sosok yang terhubung. Ketika kita memiliki jarak dengan tanda dua angka, kita dapat menggabungkannya dengan mengambil yang lebih kecil dari keduanya menggunakan fungsi min .

Karena pilihan yang lebih kecil dari dua nilai, angka akhir akan berada di bawah 0 (terlihat) di mana salah satu dari dua angka yang masuk memiliki jarak ke tepi kurang dari 0; hal yang sama berlaku untuk semua nilai jarak lainnya, menunjukkan kombinasi dua angka.

Di sini saya akan memberi nama fungsi untuk membuat konjugasi "menggabungkan", sebagian karena kita menggabungkannya, sebagian karena kata kunci gabungan di hlsl dicadangkan, sehingga tidak dapat digunakan sebagai nama fungsi.

 //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); 




Persimpangan


Cara umum lain untuk menghubungkan bentuk adalah dengan menggunakan area di mana dua bentuk tumpang tindih. Untuk melakukan ini, kami mengambil nilai maksimum dari jarak dua angka yang ingin kami gabungkan. Saat menggunakan yang terbesar dari dua nilai, kami mendapatkan nilai yang lebih besar dari 0 (di luar gambar), ketika salah satu jarak ke dua angka berada di luar angka, dan jarak lain juga disejajarkan dengan cara yang sama.

 //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); 


Pengurangan


Namun, seringkali kami tidak ingin memproses kedua bentuk dengan cara yang sama, dan kami perlu mengurangi yang lain dari satu bentuk. Ini cukup mudah dilakukan dengan memotong antara bentuk yang ingin kita ubah dan semua kecuali bentuk yang ingin kita kurangi. Kami mendapatkan nilai yang berlawanan untuk bagian dalam dan luar gambar, membalikkan jarak dengan tanda. Apa yang 1 unit di luar gambar sekarang 1 unit di dalam.

 //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); 


Interpolasi


Cara yang tidak jelas untuk menggabungkan dua angka adalah dengan menyisipkan di antara mereka. Mungkin juga sampai batas tertentu untuk jerat poligon dengan bentuk campuran, tetapi jauh lebih terbatas daripada apa yang bisa kita lakukan dengan bidang jarak yang ditandatangani. Dengan interpolasi sederhana antara jarak dua angka, kami mencapai aliran yang mulus dari satu ke yang lain. Untuk interpolasi, Anda cukup menggunakan metode 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); 


Senyawa lainnya


Setelah menerima koneksi sederhana, kami sudah memiliki semua yang diperlukan untuk kombinasi angka yang sederhana, tetapi properti bidang tanda jarak jauh yang menakjubkan adalah bahwa kami tidak dapat dibatasi dengan ini, ada banyak cara berbeda untuk menggabungkan angka dan melakukan tindakan menarik di tempat-tempat koneksi mereka. Di sini saya akan menjelaskan hanya beberapa teknik ini lagi, tetapi Anda dapat menemukan banyak lainnya di perpustakaan http://mercury.sexy/hg_sdf (menulis kepada saya jika Anda tahu perpustakaan SDF berguna lainnya).

Pembulatan


Kita dapat menginterpretasikan permukaan dari dua angka penggabungan sebagai sumbu x dan sumbu y dari posisi dalam sistem koordinat, dan kemudian menghitung jarak ke asal koordinat posisi ini. Jika kita melakukan ini, kita akan mendapatkan angka yang sangat aneh, tetapi jika kita membatasi sumbu pada nilai di bawah 0, kita mendapatkan sesuatu yang menyerupai konjugasi halus jarak internal dua angka.

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


Ini indah, tetapi kami tidak dapat menggunakan ini untuk mengubah garis di mana jaraknya 0, sehingga operasi ini tidak lebih berharga daripada pemasangan biasa. Tapi sebelum kita menghubungkan kedua angka itu, kita bisa menambahnya sedikit. Dengan cara yang sama seperti kita membuat lingkaran, untuk memperbesar gambar, kita kurangi dari jaraknya untuk mendorong keluar garis lebih jauh, di mana jarak dengan tanda adalah 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); } 


Itu hanya memperbesar angka dan memastikan transisi yang mulus di dalam, tetapi kami tidak ingin menambah angka, kami hanya perlu transisi yang mulus. Solusinya adalah dengan mengurangi jari-jari lagi setelah menghitung panjangnya. Sebagian besar bagian akan terlihat sama seperti sebelumnya, kecuali untuk transisi antara angka-angka, yang indah dihaluskan sesuai dengan jari-jari. Kami akan mengabaikan bagian luar dari gambar untuk saat ini.

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


Tahap terakhir adalah koreksi bagian luar gambar. Selain itu, sementara bagian dalam gambar berwarna hijau, dan kami menggunakan warna ini untuk bagian luar. Langkah pertama adalah menukar bagian eksternal dan internal, cukup dengan membalikkan jarak mereka dengan tanda. Kemudian kami mengganti bagian di mana jari-jari dikurangi. Pertama kita mengubahnya dari pengurangan menjadi penambahan. Ini perlu, karena sebelum menggabungkan dengan jari-jari, kita menggambar jarak vektor, oleh karena itu, sesuai dengan ini, kita perlu membalikkan operasi matematika yang digunakan. Kemudian kita akan mengganti jari-jari dengan pasangan biasa, yang akan memberi kita nilai yang benar di luar gambar, tetapi tidak dekat dengan tepi dan di dalam gambar. Untuk menghindari hal ini, kita mengambil maksimum antara nilai dan jari-jari, sehingga memperoleh nilai positif dari nilai yang benar di luar gambar, serta penambahan jari-jari yang kita butuhkan di dalam gambar.

 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; } 


Untuk membuat persimpangan, kita perlu melakukan yang sebaliknya - kurangi angka dengan jari-jari, pastikan semua komponen vektor lebih besar dari 0, ambil panjangnya dan jangan ubah tandanya. Jadi kita akan membuat bagian luar dari gambar tersebut. Kemudian, untuk membuat bagian dalam, kami mengambil persimpangan biasa dan memastikan bahwa tidak kurang dari minus jari-jari. Kemudian, seperti sebelumnya, kami menambahkan nilai-nilai internal dan eksternal.

 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; } 


Dan sebagai poin terakhir, pengurangan dapat kembali digambarkan sebagai perpotongan antara angka dasar dan segalanya kecuali angka yang kita kurangi.

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


Di sini, dan terutama ketika mengurangi, Anda dapat melihat artefak yang muncul dari asumsi bahwa kita dapat menggunakan dua angka sebagai koordinat, tetapi untuk sebagian besar aplikasi, bidang jarak masih cukup baik.

Bevel


Kita juga dapat memotong transisi untuk memberikan sudut seperti talang. Untuk mencapai efek ini, pertama-tama kita membuat bentuk baru dengan menambahkan dua yang sudah ada. Jika kita kembali berasumsi bahwa titik di mana dua angka bertemu adalah ortogonal, maka operasi ini akan memberi kita garis diagonal yang melewati titik pertemuan dua permukaan.


Karena kita hanya menambahkan dua komponen, jarak dengan tanda garis baru ini memiliki skala yang salah, tetapi kita dapat memperbaikinya dengan membaginya dengan diagonal dari satuan bujur sangkar, yaitu, akar kuadrat dari 2. Pembagian dengan akar 2 sama dengan mengalikan dengan akar kuadrat dari 0,5, dan kita cukup menuliskan nilai ini ke kode agar tidak menghitung akar yang sama setiap waktu.

Sekarang kita memiliki bentuk yang memiliki bentuk bevel yang diinginkan, kita akan mengembangkannya sehingga bevel melampaui batas-batas gambar. Dengan cara yang sama seperti sebelumnya, kita kurangi nilai yang kita butuhkan untuk menambah angka. Kemudian kami menggabungkan bentuk bevel dengan output dari penggabungan biasa, menghasilkan transisi miring.

 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); } 


Untuk mendapatkan bevel silang, kita, seperti sebelumnya, menambahkan dua angka, tetapi kemudian kita mengurangi angka dengan menambahkan bevel dan berpotongan dengan gambar crossed yang biasa.

 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); } 


Dan mirip dengan pengurangan sebelumnya, kita juga bisa melakukan persimpangan dengan gambar kedua terbalik di sini.

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


Persimpangan bulat


Sejauh ini, kami hanya menggunakan operator Boolean (kecuali untuk interpolasi). Tapi kita bisa menggabungkan bentuk dengan cara lain, misalnya, dengan membuat bentuk bulat baru di tempat-tempat di mana batas dari dua bentuk tumpang tindih.

Untuk melakukan ini, kita lagi perlu menafsirkan dua angka sebagai sumbu x dan sumbu y dari titik. Kemudian kita cukup menghitung jarak dari titik ini ke titik asal. Jika batas kedua angka bertumpang tindih, jarak ke kedua angka tersebut adalah 0, yang memberi kita jarak 0 ke titik asal sistem koordinat imajiner kita. Kemudian, jika kita memiliki jarak ke titik asal, kita dapat melakukan operasi yang sama dengannya seperti untuk lingkaran dan kurangi jari-jarinya.

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


Takik batas


Hal terakhir yang akan saya jelaskan adalah cara membuat takik dalam satu bentuk di posisi tepi bentuk lainnya.

Kita mulai dengan menghitung bentuk batas lingkaran. Ini dapat dilakukan dengan memperoleh nilai absolut dari jarak gambar pertama, sedangkan bagian dalam dan luar akan dianggap sebagai bagian dalam gambar, tetapi perbatasan masih memiliki nilai 0. Jika kita menambah angka ini dengan mengurangi lebar takik, kita akan mendapatkan gambar di sepanjang perbatasan gambar sebelumnya .

 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; } 


Sekarang kita perlu batas lingkaran untuk masuk lebih dalam hanya dengan nilai yang kita tentukan. Untuk melakukan ini, kita kurangi dari itu versi pengurangan dari angka dasar. Jumlah reduksi pada bentuk dasar adalah kedalaman takikan.

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


Langkah terakhir adalah mengurangi takik dari bentuk dasar dan mengembalikan hasilnya.

 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); } 


Kode sumber


Perpustakaan



 #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 

Basis shader



 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/id438954/


All Articles