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{
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
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.
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.
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.
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
.
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
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{