Dasar-dasar Bidang Jarak yang Ditandatangani dalam 2D

Meskipun jerat adalah cara paling sederhana dan paling serbaguna untuk menyajikan, ada opsi lain untuk mewakili bentuk dalam 2d dan 3d. Salah satu metode yang umum digunakan adalah bidang jarak ditandatangani (SDF). Bidang jarak yang ditandatangani menyediakan penelusuran sinar yang lebih murah, memungkinkan berbagai bentuk mengalir dengan lancar satu sama lain dan menghemat tekstur resolusi rendah untuk gambar berkualitas tinggi.

Kami akan mulai dengan membuat tanda bidang jarak menggunakan fungsi dalam dua dimensi, tetapi nanti kami akan terus menghasilkannya dalam 3D. Saya akan menggunakan koordinat ruang dunia sehingga kami memiliki sedikit ketergantungan pada penskalaan dan koordinat UV mungkin, jadi jika Anda tidak mengerti cara kerjanya, maka pelajari tutorial ini pada hamparan datar , yang menjelaskan apa yang terjadi.


Persiapan fondasi


Kami akan membuang sementara properti dari shader overlay flat base, karena untuk saat ini kami akan mengurus basis teknis. Kemudian kami menulis posisi vertex di dunia langsung ke struktur fragmen, dan kami tidak akan mengubahnya terlebih dahulu menjadi UV. Pada tahap persiapan terakhir, kita akan menulis fungsi baru yang menghitung adegan dan mengembalikan jarak ke permukaan terdekat. Kemudian kita memanggil fungsi dan menggunakan hasilnya sebagai warna.

Shader "Tutorial/034_2D_SDF_Basics"{ SubShader{ //           Tags{ "RenderType"="Opaque" "Queue"="Geometry"} Pass{ CGPROGRAM #include "UnityCG.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; //         o.position = UnityObjectToClipPos(v.vertex); //     o.worldPos = mul(unity_ObjectToWorld, v.vertex); return o; } float scene(float2 position) { //      return 0; } fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); fixed4 col = fixed4(dist, dist, dist, 1); return col; } ENDCG } } FallBack "Standard" //fallback   ,       } 

Saya akan menulis semua fungsi untuk bidang jarak yang ditandatangani dalam file terpisah sehingga kami dapat menggunakannya berulang kali. Untuk melakukan ini, saya akan membuat file baru. Kami tidak akan menambahkan kejahatan padanya, kemudian kami mengaturnya dan menyelesaikan perlindungan include bersyarat, memeriksa terlebih dahulu apakah variabel preprosesor diatur. Jika belum didefinisikan, maka kita mendefinisikannya dan menyelesaikan konstruksi kondisional jika setelah fungsi yang ingin kita sertakan. Keuntungan dari ini adalah bahwa jika kita menambahkan file dua kali (misalnya, jika kita menambahkan dua file yang berbeda, masing-masing memiliki fungsi yang kita butuhkan, dan mereka berdua menambahkan file yang sama), maka ini akan memecah shader. Jika Anda yakin ini tidak akan pernah terjadi, maka Anda tidak dapat melakukan pemeriksaan ini.

 // in include file // include guards that keep the functions from being included more than once #ifndef SDF_2D #define SDF_2D // functions #endif 

Jika file include terletak di folder yang sama dengan shader utama, kita cukup memasukkannya menggunakan pragma construct.

 // in main shader #include "2D_SDF.cginc" 

Jadi kita hanya akan melihat permukaan hitam di permukaan yang diberikan, siap untuk menampilkan jarak dengan tanda di atasnya.


Lingkaran


Fungsi paling sederhana dari bidang jarak yang ditandatangani adalah fungsi lingkaran. Fungsi hanya akan menerima posisi sampel dan jari-jari lingkaran. Kami mulai dengan mendapatkan panjang vektor posisi sampel. Jadi kita mendapatkan titik di posisi (0, 0), yang mirip dengan lingkaran dengan jari-jari 0.

 float circle(float2 samplePosition, float radius){ return length(samplePosition); } 

Kemudian Anda dapat memanggil fungsi lingkaran dalam fungsi pemandangan dan mengembalikan jarak yang dikembalikannya.

 float scene(float2 position) { float sceneDistance = circle(position, 2); return sceneDistance; } 


Lalu kita tambahkan radius ke perhitungan. Aspek penting dari fungsi jarak yang ditandatangani adalah bahwa ketika kita berada di dalam objek, kita mendapatkan jarak negatif ke permukaan (ini adalah apa yang berarti kata yang ditandatangani berarti dalam bidang ekspresi jarak yang ditandatangani). Untuk meningkatkan lingkaran menjadi jari-jari, kita cukup kurangi jari-jari dari panjangnya. Jadi permukaan, yang ada di mana-mana di mana fungsi mengembalikan 0, bergerak ke luar. Apa yang ada dalam dua unit jarak dari permukaan untuk lingkaran dengan ukuran 0, hanya satu unit dari lingkaran dengan jari-jari 1, dan satu unit di dalam lingkaran (nilainya -1) untuk lingkaran dengan jari-jari 3;

 float circle(float2 samplePosition, float radius){ return length(samplePosition) - radius; } 


Sekarang satu-satunya hal yang tidak bisa kita lakukan adalah memindahkan lingkaran dari pusat. Untuk memperbaiki ini, Anda bisa menambahkan argumen baru ke fungsi lingkaran untuk menghitung jarak antara posisi sampel dan pusat lingkaran, dan kurangi jari-jari dari nilai ini untuk menentukan lingkaran. Atau, Anda bisa mendefinisikan kembali asal dengan memindahkan ruang titik sampel, dan lalu mendapatkan lingkaran di ruang itu. Opsi kedua terlihat jauh lebih rumit, tetapi karena benda bergerak adalah operasi yang ingin kita gunakan untuk semua gambar, itu jauh lebih universal, dan karena itu saya akan menjelaskannya.

Bergerak


"Transformasi ruang dari suatu titik" - terdengar jauh lebih buruk daripada yang sebenarnya. Ini berarti bahwa kita melewatkan titik ke fungsi, dan fungsi mengubahnya sehingga kita masih bisa menggunakannya di masa depan. Dalam hal transfer, kami cukup mengurangi offset dari titik. Posisi dikurangi ketika kita ingin memindahkan bentuk ke arah positif, karena bentuk yang kita render dalam ruang bergerak ke arah yang berlawanan dengan memindahkan ruang.

Misalnya, jika kita ingin menggambar bola di posisi (3, 4) , maka kita perlu mengubah spasi sehingga (3, 4) berubah menjadi (0, 0) , dan untuk ini kita perlu mengurangi (3, 4) . Sekarang jika kita menggambar bola di sekitar titik asal baru, itu akan menjadi titik lama (3, 4) .

 // in sdf functions include file float2 translate(float2 samplePosition, float2 offset){ return samplePosition - offset; } 

 float scene(float2 position) { float2 circlePosition = translate(position, float2(3, 2)); float sceneDistance = circle(circlePosition, 2); return sceneDistance; } 


Persegi panjang


Bentuk sederhana lainnya adalah persegi panjang. Untuk mulai dengan, kami mempertimbangkan komponen secara terpisah. Pertama kita mendapatkan jarak dari pusat, dengan mengambil nilai absolut. Kemudian, mirip dengan lingkaran, kita kurangi setengah ukuran (yang pada dasarnya menyerupai jari-jari persegi panjang). Untuk menunjukkan bagaimana hasilnya nanti, kami hanya akan mengembalikan satu komponen untuk saat ini.

 float rectangle(float2 samplePosition, float2 halfSize){ float2 componentWiseEdgeDistance = abs(samplePosition) - halfSize; return componentWiseEdgeDistance.x; } 


Sekarang kita bisa mendapatkan versi murah dari persegi panjang hanya dengan mengembalikan komponen terbesar 2. Ini berfungsi dalam banyak kasus, tetapi tidak benar, karena tidak menampilkan jarak yang tepat di sudut-sudut.


Nilai yang benar untuk persegi panjang di luar gambar dapat diperoleh dengan pertama-tama mengambil maksimum antara jarak ke tepi dan 0, dan kemudian mengambil panjangnya.

Jika kita tidak membatasi jarak dari bawah ke 0, maka kita cukup menghitung jarak ke sudut (di mana edgeDistances berada (0, 0) ), tetapi koordinat antara sudut tidak akan jatuh di bawah 0, sehingga seluruh tepi akan digunakan. Kerugiannya adalah 0 digunakan sebagai jarak dari tepi untuk seluruh bagian dalam gambar.

Untuk memperbaiki jarak 0 untuk seluruh bagian dalam, Anda perlu menghasilkan jarak internal, cukup menggunakan rumus persegi panjang yang murah (mengambil nilai maksimum dari komponen x dan y), dan kemudian menjamin bahwa itu tidak akan pernah melebihi 0, mengambil nilai minimum dari itu menjadi 0. Kemudian kami menambahkan jarak eksternal, yang tidak pernah lebih rendah dari 0, dan jarak internal, yang tidak pernah melebihi 0, dan kami mendapatkan fungsi jarak selesai.

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

Karena kami sebelumnya mencatat fungsi transfer dalam bentuk universal, sekarang kami juga dapat menggunakannya untuk memindahkan pusatnya ke tempat mana pun.

 float scene(float2 position) { float2 circlePosition = translate(position, float2(1, 0)); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; } 


Putar


Bentuk berputar mirip dengan bergerak. Sebelum menghitung jarak ke gambar, kami memutar koordinat dalam arah yang berlawanan. Untuk menyederhanakan pemahaman rotasi sebanyak mungkin, kami mengalikan rotasi dengan 2 * pi untuk mendapatkan sudut dalam radian. Jadi, kami meneruskan rotasi ke fungsi, di mana 0,25 adalah seperempat putaran, 0,5 adalah setengah putaran, dan 1 adalah putaran penuh (Anda dapat melakukan konversi secara berbeda jika tampaknya lebih alami bagi Anda). Kami juga membalikkan rotasi, karena kami perlu memutar posisi dalam arah yang berlawanan dari rotasi gambar untuk alasan yang sama seperti ketika bergerak.

Untuk menghitung koordinat yang diputar, pertama-tama kita menghitung sinus dan cosinus berdasarkan sudut. Hlsl memiliki fungsi sincos yang menghitung kedua nilai ini lebih cepat daripada ketika dihitung secara terpisah.

Ketika membuat vektor baru untuk komponen x, kita mengambil komponen asli x dikalikan dengan kosinus dan komponen y dikalikan dengan sinus. Ini dapat dengan mudah diingat jika Anda ingat bahwa cosinus 0 adalah 1, dan ketika diputar oleh 0, kami ingin komponen x dari vektor baru sama persis seperti sebelumnya (yaitu, kalikan dengan 1). Komponen y, yang sebelumnya menunjuk ke atas, tidak memberikan kontribusi pada komponen x, berputar ke kanan, dan nilainya dimulai pada 0, pada awalnya menjadi lebih besar, yaitu, gerakannya sepenuhnya dijelaskan oleh sinus.

Untuk komponen y dari vektor baru, kita mengalikan kosinus dengan komponen y dari vektor lama dan mengurangi sinus dikalikan dengan komponen lama x. Untuk memahami mengapa kita mengurangi, daripada menambahkan sinus, dikalikan dengan komponen x, yang terbaik adalah membayangkan bagaimana vektor (1, 0) berubah ketika diputar searah jarum jam. Komponen y dari hasil dimulai dari 0 dan kemudian menjadi kurang dari 0. Ini adalah kebalikan dari bagaimana sinus berperilaku, jadi kami mengubah tanda.

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

Sekarang kita telah menulis metode rotasi, kita dapat menggunakannya dalam kombinasi dengan transfer untuk memindahkan dan memutar gambar.

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


Dalam hal ini, pertama-tama kita memutar objek di sekitar pusat seluruh adegan, sehingga rotasi juga mempengaruhi transfer. Untuk memutar angka relatif ke pusatnya sendiri, pertama-tama Anda harus memindahkannya, dan kemudian memutarnya. Karena urutan perubahan ini pada saat rotasi, pusat gambar akan menjadi pusat sistem koordinat.

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


Scaling


Penskalaan bekerja sama dengan cara lain untuk mengubah bentuk. Kami membagi koordinat berdasarkan skala, menampilkan gambar di ruang angkasa dengan skala yang dikurangi, dan dalam sistem koordinat basis, mereka menjadi lebih besar.

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


Meskipun ini melakukan penskalaan dengan benar, jarak juga skala. Keuntungan utama dari bidang jarak yang ditandatangani adalah bahwa kita selalu mengetahui jarak ke permukaan terdekat, tetapi memperkecil sepenuhnya menghancurkan properti ini. Ini dapat dengan mudah diperbaiki dengan mengalikan bidang jarak yang diperoleh dari fungsi jarak tanda (dalam kasus kami, rectangle ) dengan skala. Untuk alasan yang sama, kita tidak dapat dengan mudah mengukur secara tidak rata (dengan skala yang berbeda untuk sumbu x dan 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; } 


Visualisasi


Bidang jarak yang ditandatangani dapat digunakan untuk berbagai hal, seperti membuat bayangan, merender adegan 3D, fisika, dan merender teks. Tetapi kami tidak ingin masuk jauh ke dalam kompleksitas, oleh karena itu saya hanya akan menjelaskan dua teknik visualisasi mereka. Yang pertama adalah bentuk yang jelas dengan antialiasing, yang kedua adalah rendering garis tergantung pada jarak.

Bentuk yang jelas


Metode ini mirip dengan yang sering digunakan saat merender teks, ini menciptakan bentuk yang jelas. Jika kita ingin menghasilkan bidang jarak bukan dari fungsi, tetapi untuk membacanya dari tekstur, ini memungkinkan kita untuk menggunakan tekstur dengan resolusi yang jauh lebih rendah dari biasanya dan mendapatkan hasil yang baik. TextMesh Pro menggunakan teknik ini untuk membuat teks.

Untuk menerapkan teknik ini, kami mengambil keuntungan dari fakta bahwa data dalam bidang jarak ditandatangani, dan kami tahu titik cut-off. Kami mulai dengan menghitung seberapa jauh bidang jarak berubah ke piksel berikutnya. Ini harus nilai yang sama dengan panjang perubahan koordinat, tetapi lebih mudah dan lebih dapat diandalkan untuk menghitung jarak dengan tanda.

Setelah menerima perubahan jarak, kita dapat membuat langkah mulus dari setengah perubahan jarak menjadi minus / plus setengah perubahan jarak. Ini akan melakukan guntingan sederhana sekitar 0, tetapi dengan smoothing. Kemudian Anda dapat menggunakan nilai ini untuk nilai biner apa pun yang kami butuhkan. Dalam contoh ini, saya akan mengubah shader menjadi shader transparansi dan menggunakannya untuk saluran alpha. Saya melakukan langkah mulus dari nilai positif ke nilai negatif karena kami ingin nilai negatif dari bidang jarak terlihat. Jika Anda tidak mengerti cara kerja rendering transparansi di sini, maka saya sarankan membaca tutorial rendering transparansi saya .

 //properties Properties{ _Color("Color", Color) = (1,1,1,1) } 

 //in subshader outside of pass Tags{ "RenderType"="Transparent" "Queue"="Transparent"} Blend SrcAlpha OneMinusSrcAlpha ZWrite Off 

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


Garis elevasi


Teknik umum lainnya untuk memvisualisasikan bidang jarak adalah untuk menampilkan jarak sebagai garis. Dalam implementasi kami, saya akan menambahkan beberapa garis tebal dan beberapa garis tipis di antaranya. Saya juga akan mengecat bagian dalam dan luar gambar dengan warna yang berbeda sehingga Anda dapat melihat di mana objek itu berada.

Kami akan mulai dengan menampilkan perbedaan antara bagian dalam dan luar gambar. Warna dapat disesuaikan dalam materi, jadi kami akan menambahkan properti baru, serta variabel shader untuk warna internal dan eksternal dari gambar tersebut.

 Properties{ _InsideColor("Inside Color", Color) = (.5, 0, 0, 1) _OutsideColor("Outside Color", Color) = (0, .5, 0, 1) } 

 //global shader variables float4 _InsideColor; float4 _OutsideColor; 

Kemudian dalam fragmen shader kami memeriksa di mana piksel berada, yang kami render dengan membandingkan jarak dengan tanda dengan 0 menggunakan fungsi step . Kami menggunakan variabel ini untuk menginterpolasi dari warna dalam ke luar dan menampilkannya di layar.

 fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); fixed4 col = lerp(_InsideColor, _OutsideColor, step(0, dist)); return col; } 


Untuk merender baris, pertama-tama kita perlu menentukan seberapa sering kita akan merender garis, dan seberapa tebal mereka, mengatur properti dan variabel shader yang sesuai.

 //Properties _LineDistance("Mayor Line Distance", Range(0, 2)) = 1 _LineThickness("Mayor Line Thickness", Range(0, 0.1)) = 0.05 

 //shader variables float _LineDistance; float _LineThickness; 

Kemudian, untuk merender garis, kita akan mulai dengan menghitung perubahan jarak sehingga kita bisa menggunakannya nanti untuk menghaluskan. Kami juga membaginya dengan 2, karena nanti kami tambahkan setengahnya dan kurangi setengahnya untuk menutupi jarak perubahan 1 piksel.

 float distanceChange = fwidth(dist) * 0.5; 

Kemudian kita mengambil jarak dan mengubahnya sehingga memiliki perilaku yang sama di titik yang berulang. Untuk melakukan ini, pertama-tama kita membaginya dengan jarak antara garis, sementara kita tidak akan mendapatkan angka penuh pada setiap langkah pertama, tetapi angka penuh hanya berdasarkan jarak yang kita tentukan.

Kemudian kita tambahkan 0,5 ke angka, ambil bagian pecahan dan kurangi 0,5 lagi. Bagian fraksional dan pengurangan diperlukan di sini sehingga garis melewati nol dalam pola berulang. Kami menambahkan 0,5 untuk mendapatkan bagian fraksional untuk menetralkan pengurangan lebih lanjut dari 0,5 - offset akan mengarah pada fakta bahwa nilai-nilai di mana grafik 0 berada pada 0, 1, 2, dll, dan bukan pada 0,5, 1,5, dll.

Langkah terakhir untuk mengonversi nilai - kami mengambil nilai absolut dan mengalikannya dengan jarak di antara garis. Nilai absolut membuat area sebelum dan sesudah titik garis tetap sama, yang membuatnya lebih mudah untuk membuat kliping untuk garis. Operasi terakhir, di mana kita kembali mengalikan nilai dengan jarak antara garis, diperlukan untuk menetralkan pembagian di awal persamaan, berkat itu, perubahan nilainya lagi sama seperti di awal, dan perubahan jarak yang dihitung sebelumnya masih benar.


 float majorLineDistance = abs(frac(dist / _LineDistance + 0.5) - 0.5) * _LineDistance; 

Sekarang kita telah menghitung jarak ke garis berdasarkan jarak ke gambar, kita bisa menggambar garis. Kami melakukan langkah mulus dari linethickness dikurangi setengah perubahan jarak ke linethickness plus setengah perubahan jarak dan menggunakan jarak garis yang baru dihitung sebagai nilai untuk perbandingan. Setelah menghitung nilai ini, kami mengalikannya dengan warna untuk membuat garis hitam (Anda juga dapat menggunakan warna yang berbeda jika Anda membutuhkan garis multi-warna).

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


Kami menerapkan garis tipis antara yang tebal dengan cara yang sama - kami menambahkan properti yang menentukan berapa banyak garis tipis harus antara yang tebal, dan kemudian kami melakukan apa yang kami lakukan dengan yang tebal, tetapi karena jarak antara garis tipis kami membagi jarak antara yang tebal dengan jumlah garis tipis antara mereka. Kami juga akan membuat jumlah garis tipis IntRange , berkat ini kami hanya dapat menetapkan nilai integer dan tidak mendapatkan garis tipis yang tidak IntRange tebal. Setelah menghitung garis tipis, kami mengalikannya dengan warna dengan cara yang sama dengan yang tebal.

 //properties [IntRange]_SubLines("Lines between major lines", Range(1, 10)) = 4 _SubLineThickness("Thickness of inbetween lines", Range(0, 0.05)) = 0.01 

 //shader variables 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; } 


Kode sumber


Fitur SDF 2D



 #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){ //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; } 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 

Contoh lingkaran



 Shader "Tutorial/034_2D_SDF_Basics/Rectangle"{ 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) { float2 circlePosition = position; circlePosition = rotate(circlePosition, _Time.y * 0.5); circlePosition = translate(circlePosition, float2(2, 0)); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; } fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); fixed4 col = fixed4(dist, dist, dist, 1); return col; } ENDCG } } FallBack "Standard" //fallback adds a shadow pass so we get shadows on other objects } 

Contoh segi empat



 Shader "Tutorial/034_2D_SDF_Basics/Rectangle"{ 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) { float2 circlePosition = position; circlePosition = rotate(circlePosition, _Time.y * 0.5); circlePosition = translate(circlePosition, float2(2, 0)); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; } fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); fixed4 col = fixed4(dist, dist, dist, 1); return col; } ENDCG } } FallBack "Standard" //fallback adds a shadow pass so we get shadows on other objects } 

Cutoff



 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; //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) { float2 circlePosition = position; circlePosition = rotate(circlePosition, _Time.y * 0.5); circlePosition = translate(circlePosition, float2(2, 0)); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; } 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; } ENDCG } } FallBack "Standard" //fallback adds a shadow pass so we get shadows on other objects } 

Garis jarak



 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{ //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) { float2 circlePosition = position; circlePosition = rotate(circlePosition, _Time.y * 0.2); circlePosition = translate(circlePosition, float2(2, 0)); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; } 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 } 

Saya harap saya berhasil menjelaskan dasar-dasar bidang jarak dengan tanda, dan Anda sudah menunggu beberapa tutorial baru di mana saya akan berbicara tentang cara lain untuk menggunakannya.

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


All Articles