Manipulasi Spasial 2D dengan Bidang Jarak Bertanda

Saat bekerja dengan aset poligon, Anda hanya dapat menggambar satu objek pada satu waktu (jika Anda tidak memperhitungkan teknik seperti batching dan instancing), tetapi jika Anda menggunakan bidang jarak dengan tanda (bidang jarak yang ditandatangani, SDF), maka kami tidak terbatas pada hal ini. Jika dua posisi memiliki koordinat yang sama, maka fungsi jarak yang ditandatangani akan mengembalikan nilai yang sama, dan dalam satu perhitungan kita bisa mendapatkan beberapa angka. Untuk memahami cara mengubah ruang yang digunakan untuk menghasilkan bidang jarak yang ditandatangani, saya sarankan Anda mencari cara membuat bentuk menggunakan fungsi jarak yang ditandatangani dan menggabungkan bentuk sdf .


Konfigurasi


Untuk tutorial ini, saya memodifikasi pasangan antara kuadrat dan lingkaran, tetapi Anda dapat menggunakannya untuk bentuk lainnya. Ini mirip dengan konfigurasi untuk tutorial sebelumnya .

Penting di sini bahwa bagian yang dapat dimodifikasi adalah sebelum menggunakan posisi untuk menghasilkan angka.

Shader "Tutorial/036_SDF_Space_Manpulation/Type"{ 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) { // manipulate position with cool methods here! float2 squarePosition = position; squarePosition = translate(squarePosition, float2(2, 2)); squarePosition = rotate(squarePosition, .125); float squareShape = rectangle(squarePosition, float2(1, 1)); float2 circlePosition = position; circlePosition = translate(circlePosition, float2(1, 1.5)); float circleShape = circle(circlePosition, 1); float combination = merge(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" } 

Dan fungsi 2D_SDF.cginc terletak di 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; } //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 


Pengulangan ruang


Refleksi cermin


Salah satu operasi paling sederhana adalah mencerminkan dunia tentang sumbu. Untuk memantulkannya di sekitar sumbu y, kita mengambil nilai absolut dari komponen x posisi kita. Dengan demikian, koordinat ke kanan dan kiri poros akan sama. (-1, 1) berubah menjadi (1, 1) , dan ternyata berada di dalam lingkaran menggunakan (1, 1) sebagai asal koordinat dan dengan jari-jari lebih besar dari 0.

Paling sering, kode yang menggunakan fungsi ini akan terlihat seperti position = mirror(position); jadi kita bisa menyederhanakannya sedikit. Kami hanya akan mendeklarasikan argumen posisi sebagai inout. Jadi, saat menulis ke argumen, itu juga akan mengubah variabel yang kita lewatkan ke fungsi. Nilai kembalian dapat berupa tipe void, karena kita masih belum menggunakan nilai kembalian.

 //in 2D_SDF.cginc void mirror(inout float2 position){ position.x = abs(position.x); } 

 //in shader function mirror(position); 


Ternyata sudah cukup bagus, tapi dengan cara ini kita hanya mendapatkan satu sumbu untuk mirroring. Kita dapat memperluas fungsi dengan memutar ruang seperti yang kita lakukan saat memutar angka. Pertama, Anda perlu memutar ruang, lalu cermin, dan lalu balikkan. Dengan cara ini kita dapat melakukan mirroring dengan memperhatikan sudut mana pun. Hal yang sama dimungkinkan ketika mentransfer ruang dan melakukan transfer balik setelah mirroring. (Jika Anda melakukan kedua operasi, maka sebelum mirroring, jangan lupa untuk melakukan transfer terlebih dahulu, dan kemudian berbelok, setelah itu belok pertama.)

 //in shader function float rotation = _Time.y * 0.25; position = rotate(position, rotation); mirror(position); position = rotate(position, -rotation); 


Sel


Jika Anda tahu cara kerja noise , maka Anda memahami bahwa untuk generasi prosedural kita sering mengulangi posisi dan mendapatkan sel-sel kecil yang pada dasarnya sama, hanya berbeda dalam parameter yang tidak signifikan. Kita dapat melakukan hal yang sama untuk bidang jarak.

Karena fungsi fmod (serta menggunakan% untuk membagi dengan sisanya) memberi kita sisanya, bukan definisi sisanya, kita harus menggunakan trik. Pertama, kita mengambil sisa pembagian integer dengan fungsi fmod. Untuk bilangan positif, ini adalah persis apa yang kita butuhkan, dan untuk bilangan negatif ini adalah hasil yang kita butuhkan dikurangi periode. Anda dapat memperbaikinya dengan menambahkan periode dan sekali lagi mengambil sisa divisi. Menambahkan periode akan memberikan hasil yang diinginkan untuk nilai input negatif, dan untuk nilai input positif, nilainya satu periode lebih tinggi. Sisa kedua divisi ini tidak akan melakukan apa pun dengan nilai-nilai untuk nilai input negatif, karena mereka sudah dalam kisaran dari 0 hingga periode, dan untuk nilai input positif kami pada dasarnya mengurangi satu periode.

 //in 2D_SDF.cginc void cells(inout float2 position, float2 period){ position = fmod(position, period); //negative positions lead to negative modulo position += period; //negative positions now have correct cell coordinates, positive input positions too high position = fmod(position, period); //second mod doesn't change values between 0 and period, but brings down values that are above period. } 

 //in shader function cells(position, float2(3, 3)); 


Masalah dengan sel adalah bahwa kita kehilangan kontinuitas yang kita suka bidang jarak. Ini tidak buruk jika bentuknya hanya di tengah sel, tetapi dalam contoh yang ditunjukkan di atas ini dapat menyebabkan artefak yang signifikan yang harus dihindari ketika bidang jarak digunakan untuk banyak tugas di mana bidang jarak biasanya dapat diterapkan.

Ada satu solusi yang tidak bekerja di setiap kasus, tetapi ketika berhasil, itu luar biasa: untuk mencerminkan setiap sel lainnya. Untuk melakukan ini, kita memerlukan indeks sel piksel, tetapi kita masih belum memiliki nilai balik dalam fungsinya, jadi kita bisa menggunakannya untuk mengembalikan indeks sel.

Untuk menghitung indeks sel, kami membagi posisi berdasarkan periode. Jadi, 0-1 adalah sel pertama, 1-2 adalah yang kedua, dan seterusnya ... dan kita dapat dengan mudah mendiskresikannya. Untuk mendapatkan indeks sel, kita cukup membulatkan nilainya ke bawah dan mengembalikan hasilnya. Yang penting adalah kita menghitung indeks sel sebelum membaginya dengan sisanya untuk mengulangi sel; jika tidak, kita akan mendapatkan indeks 0 di mana-mana, karena posisinya tidak dapat melebihi periode.

 //in 2D_SDF.cginc float2 cells(inout float2 position, float2 period){ position = fmod(position, period); //negative positions lead to negative modulo position += period; //negative positions now have correct cell coordinates, positive input positions too high position = fmod(position, period); //second mod doesn't change values between 0 and period, but brings down values that are above period. float2 cellIndex = position / period; cellIndex = floor(cellIndex); return cellIndex; } 

Dengan informasi ini, kita dapat membalik sel. Untuk memahami apakah akan membalik atau tidak, kita membagi modulo indeks sel 2. Hasil operasi ini adalah bergantian 0 dan 1 atau -1 setiap sel kedua. Untuk menjadikan perubahan lebih permanen, kami mengambil nilai absolut dan mendapatkan nilai yang beralih antara 0 dan 1.

Untuk menggunakan nilai ini untuk beralih antara posisi normal dan terbalik, kita memerlukan fungsi yang tidak melakukan apa pun untuk nilai 0, dan kurangi posisi dari periode di mana flipping adalah 1. Artinya, kami melakukan interpolasi linier dari normal ke posisi membalik menggunakan variabel flip. . Karena variabel flip adalah vektor 2d, komponen-komponennya dibalik satu per satu.

 //in shader function float2 period = 3; float2 cell = cells(position, period); float2 flip = abs(fmod(cell, 2)); position = lerp(position, period - position, flip); 


Sel radial


Fitur hebat lainnya adalah pengulangan ruang dalam pola radial.

Untuk mendapatkan efek ini, pertama-tama kita menghitung posisi radial. Untuk melakukan ini, kami mengkodekan sudut relatif ke pusat sumbu x dan jarak dari pusat sepanjang sumbu y.

 float2 radialPosition = float2(atan2(position.x, position.y), length(position)); 

Lalu kami ulangi sudutnya. Karena mentransmisikan jumlah pengulangan jauh lebih mudah daripada sudut setiap potongan, pertama-tama kita menghitung ukuran setiap potongan. Seluruh lingkaran adalah 2 * pi, jadi untuk mendapatkan bagian yang tepat, kami membagi 2 * pi dengan ukuran sel.

 const float PI = 3.14159; float cellSize = PI * 2 / cells; 

Dengan informasi ini, kita dapat mengulangi komponen x posisi radial setiap unit cellSize. Kami melakukan pengulangan dengan pembagian dengan sisanya, oleh karena itu, seperti sebelumnya, kami mendapatkan masalah dengan angka negatif, yang dapat dihilangkan dengan bantuan dua fungsi pembagian dengan sisanya.

 radialPosition.x = fmod(fmod(radialPosition.x, cellSize) + cellSize, cellSize); 

Maka Anda perlu memindahkan posisi baru kembali ke koordinat xy yang biasa. Di sini kita menggunakan fungsi sincos dengan komponen x posisi radial sebagai sudut untuk menulis sinus ke koordinat x posisi dan cosinus ke koordinat y. Dengan langkah ini kita mendapatkan posisi yang dinormalisasi. Untuk mendapatkan arah yang benar dari pusat, Anda perlu mengalikannya dengan komponen y dari posisi radial, yang berarti panjangnya.

 //in 2D_SDF.cginc void radial_cells(inout float2 position, float cells){ const float PI = 3.14159; float cellSize = PI * 2 / cells; float2 radialPosition = float2(atan2(position.x, position.y), length(position)); radialPosition.x = fmod(fmod(radialPosition.x, cellSize) + cellSize, cellSize); sincos(radialPosition.x, position.x, position.y); position = position * radialPosition.y; } 

 //in shader function float2 period = 6; radial_cells(position, period, false); 


Kemudian kita juga bisa menambahkan indeks sel dan mirroring, seperti yang kita lakukan dengan sel biasa.

Penting untuk menghitung indeks sel setelah menghitung posisi radial, tetapi sebelum menerima sisanya dari divisi. Kami mendapatkannya dengan membagi komponen x dari posisi radial dan membulatkan hasilnya ke bawah. Dalam hal ini, indeks juga bisa negatif, dan ini merupakan masalah jika jumlah selnya ganjil. Misalnya, dengan 3 sel, kita mendapatkan 1 sel dengan indeks 0, 1 sel dengan indeks -1 dan 2 sel setengah dengan indeks 1 dan -2. Untuk mengatasi masalah ini, kami menambahkan jumlah sel ke variabel dibulatkan ke bawah variabel, dan kemudian dibagi dengan ukuran sel dengan sisanya.

 //in 2D_SDF.cginc float cellIndex = fmod(floor(radialPosition.x / cellSize) + cells, cells); //at the end of the function: return cellIndex; 

Untuk mencerminkan ini, kita perlu koordinat yang ditentukan dalam radian, jadi untuk menghindari penghitungan ulang koordinat radial di luar fungsi, kita akan menambahkan opsi untuk itu menggunakan argumen bool. Biasanya dalam shader, percabangan (jika konstruksi) tidak diterima, tetapi dalam hal ini semua piksel pada layar akan mengikuti jalur yang sama, jadi ini normal.

Mirroring harus terjadi setelah koordinat radial dilingkarkan, tetapi sebelum dikonversi kembali ke posisi normal. Kami mencari tahu apakah kami perlu membalik sel saat ini dengan membagi indeks sel dengan 2 dengan sisanya. Biasanya ini akan memberi kita nol dan satu, tetapi dalam kasus saya beberapa pasangan muncul, yang aneh, namun kita dapat menanganinya. Untuk menghilangkan deuces, kita cukup mengurangi 1 dari variabel flip, dan kemudian mengambil nilai absolut. Jadi, nol dan deuces menjadi satu, dan satuan menjadi nol, seperti yang kita butuhkan, hanya dalam urutan terbalik.

Karena nol dan yang berada di urutan yang salah, kami melakukan interpolasi linier dari versi terbalik ke versi terbalik, dan bukan sebaliknya, seperti sebelumnya. Untuk membalikkan koordinat, kita cukup kurangi posisi dari ukuran sel.

 //in 2D_SDF.cginc float radial_cells(inout float2 position, float cells, bool mirrorEverySecondCell = false){ const float PI = 3.14159; float cellSize = PI * 2 / cells; float2 radialPosition = float2(atan2(position.x, position.y), length(position)); float cellIndex = fmod(floor(radialPosition.x / cellSize) + cells, cells); radialPosition.x = fmod(fmod(radialPosition.x, cellSize) + cellSize, cellSize); if(mirrorEverySecondCell){ float flip = fmod(cellIndex, 2); flip = abs(flip-1); radialPosition.x = lerp(cellSize - radialPosition.x, radialPosition.x, flip); } sincos(radialPosition.x, position.x, position.y); position = position * radialPosition.y; return cellIndex; } 

 //in shader function float2 period = 6; radial_cells(position, period, true); 


Ruang berayun


Tetapi untuk mengubah ruang tidak perlu mengulanginya. Misalnya, dalam tutorial tentang dasar-dasar, kami memutar, memindahkan, dan menskalanya. Anda juga dapat melakukan hal berikut: pindahkan setiap sumbu berdasarkan yang lain dengan gelombang sinus. Ini akan membuat jarak dari fungsi jarak yang ditandatangani kurang akurat, tetapi sampai mereka terlalu banyak berayun, semuanya akan baik-baik saja.

Pertama, kita menghitung besarnya perubahan posisi dengan membalik komponen x dan y, dan kemudian mengalikannya dengan frekuensi goyangan. Lalu kita ambil sinus dari nilai ini dan kalikan dengan jumlah goyangan yang ingin kita tambahkan. Setelah itu, kita cukup menambahkan faktor goyangan ini ke posisi dan lagi menerapkan hasilnya ke posisi.

 //in 2D_SDF.cginc void wobble(inout float2 position, float2 frequency, float2 amount){ float2 wobble = sin(position.yx * frequency) * amount; position = position + wobble; } 

 //in shader function wobble(position, 5, .05); 


Kami juga dapat menghidupkan gelombang ini, mengubah posisinya, menerapkan gerakan melambai pada posisi offset dan mengembalikan ruang kembali. Agar angka floating point tidak menjadi terlalu besar, saya melakukan pembagian dengan sisa pi * 2 dengan frekuensi goyangan, ini berkorelasi dengan goyangan (sinusoid mengulangi setiap pi * 2 unit), jadi kami menghindari lompatan dan offset yang terlalu besar.

 //in shader function const float PI = 3.14159; float frequency = 5; float offset = _Time.y; offset = fmod(offset, PI * 2 / frequency); position = translate(position, offset); wobble(position, 5, .05); position = translate(position, -offset); 


Kode sumber


Perpustakaan SDF 2D



 #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); } // space repetition void mirror(inout float2 position){ position.x = abs(position.x); } float2 cells(inout float2 position, float2 period){ //find cell index float2 cellIndex = position / period; cellIndex = floor(cellIndex); //negative positions lead to negative modulo position = fmod(position, period); //negative positions now have correct cell coordinates, positive input positions too high position += period; //second mod doesn't change values between 0 and period, but brings down values that are above period. position = fmod(position, period); return cellIndex; } float radial_cells(inout float2 position, float cells, bool mirrorEverySecondCell = false){ const float PI = 3.14159; float cellSize = PI * 2 / cells; float2 radialPosition = float2(atan2(position.x, position.y), length(position)); float cellIndex = fmod(floor(radialPosition.x / cellSize) + cells, cells); radialPosition.x = fmod(fmod(radialPosition.x, cellSize) + cellSize, cellSize); if(mirrorEverySecondCell){ float flip = fmod(cellIndex, 2); flip = abs(flip-1); radialPosition.x = lerp(cellSize - radialPosition.x, radialPosition.x, flip); } sincos(radialPosition.x, position.x, position.y); position = position * radialPosition.y; return cellIndex; } void wobble(inout float2 position, float2 frequency, float2 amount){ float2 wobble = sin(position.yx * frequency) * amount; position = position + wobble; } //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 

Demo shader dasar



 Shader "Tutorial/036_SDF_Space_Manpulation/Mirror"{ 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) { // modify position here! float2 squarePosition = position; squarePosition = translate(squarePosition, float2(2, 2)); squarePosition = rotate(squarePosition, .125); float squareShape = rectangle(squarePosition, float2(1, 1)); float2 circlePosition = position; circlePosition = translate(circlePosition, float2(1, 1.5)); float circleShape = circle(circlePosition, 1); float combination = merge(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 } 

Sekarang Anda tahu semua dasar-dasar fungsi jarak tanda yang bisa saya ingat. Dalam tutorial berikutnya, saya akan mencoba melakukan sesuatu yang menarik dengan mereka.

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


All Articles