The terakhir kali kami berbicara tentang grafik dan lintasan untuk animasi stop motion, dan hari ini akan tentang matriks. Kami akan mencari cara untuk membuat transformasi dasar dalam CSS, SVG dan WebGL, membuat tampilan dunia 3D di layar dengan tangan kami sendiri, sepanjang jalan menggambar paralel dengan alat seperti Three.js, dan juga bereksperimen dengan filter untuk foto dan mencari tahu apa karena sihir semacam itu terletak pada intinya.
Izinkan saya mengingatkan Anda bahwa dalam rangkaian artikel ini kita berkenalan dengan berbagai hal dari bidang matematika yang membuat takut desainer layout, namun dapat bermanfaat dalam menyelesaikan masalah pekerjaan. Kami mencoba menghindari teori yang tidak perlu, lebih memilih gambar dan penjelasan dengan jari, dengan penekanan pada aplikasi praktis di frontend. Dalam hal ini, rumusan di beberapa tempat mungkin tidak sepenuhnya akurat dari sudut pandang matematika, atau tidak cukup lengkap. Tujuan artikel ini adalah untuk memberikan gambaran umum tentang apa yang terjadi dan di mana harus memulai jika terjadi sesuatu.
Skrip untuk menghasilkan gambar dengan gaya seri artikel ini ada di GitHub , jadi jika Anda ingin mencari tahu hal yang sama, Anda tahu apa yang harus dilakukan.
Beberapa definisi
Matriks dalam matematika adalah suatu abstraksi, kita dapat mengatakan bahwa itu adalah tipe data dalam arti, dan menulisnya dalam bentuk tabel persegi panjang. Jumlah kolom dan baris bisa apa saja, tetapi di web kita hampir selalu berurusan dengan matriks persegi 2x2, 3x3, 4x4, dan 5x5.
Kami juga membutuhkan definisi seperti vektor. Saya pikir dari geometri sekolah Anda dapat mengingat definisi yang terkait dengan kata "panjang" dan "arah", tetapi secara umum dalam matematika banyak hal yang dapat disebut vektor. Secara khusus, kita akan berbicara tentang vektor sebagai kumpulan nilai yang terurut. Misalnya koordinat bentuk (x, y) atau (x, y, z), atau warna dalam format (r, g, b) atau (h, s, l, a), dll. Bergantung pada berapa banyak elemen yang termasuk dalam himpunan seperti itu, kita akan berbicara tentang vektor satu dimensi atau lainnya: jika dua elemen adalah dua dimensi, tiga adalah tiga dimensi, dll. Selain itu, dalam kerangka topik yang dipertimbangkan, terkadang lebih mudah untuk menganggap vektor sebagai matriks dengan ukuran 1x2, 1x3, 1x4, dll. Secara teknis, kita dapat membatasi diri hanya pada istilah "matriks", tetapi kita masih akan menggunakan kata "vektor" untuk memisahkan kedua konsep ini satu sama lain,setidaknya dalam arti logis.
Untuk matriks, serta untuk vektor, berbagai operasi didefinisikan yang dapat dilakukan dengannya. Secara khusus, perkalian. Kami terus-menerus memperbanyaknya di antara kami sendiri. Algoritma perkalian itu sendiri tidak terlalu rumit, meskipun mungkin tampak sedikit membingungkan:
function multiplyMatrices(a, b) {
const m = new Array(a.length);
for (let row = 0; row < a.length; row++) {
m[row] = new Array(b[0].length);
for (let column = 0; column < b[0].length; column++) {
m[row][column] = 0;
for (let i = 0; i < a[0].length; i++) {
m[row][column] += a[row][i] * b[i][column];
}
}
}
return m;
}
Tetapi bagi kami, pada kenyataannya, tidak begitu penting untuk terus-menerus mengingat prinsip operasinya saat menyelesaikan masalah sehari-hari. Di sini kami menyebutkannya untuk kelengkapan, untuk memberikan konteks untuk contoh lebih lanjut.
Saat bekerja dengan entitas kompleks dalam matematika, abstrak sangat berguna. Seperti di sini - kita akan sering berbicara tentang perkalian, tetapi kita tidak akan memperhatikan operasi aritmatika seperti apa yang terjadi di sana. Kami tahu bahwa perkalian sudah ditentukan - dan itu cukup untuk pekerjaan itu.
Kita hanya akan menggunakan matriks kuadrat dalam rangkaian masalah yang sangat spesifik, jadi sekumpulan aturan sederhana sudah cukup:
- Anda hanya dapat mengalikan matriks dengan dimensi yang sama.
- Kami mengalikan matriks dengan matriks - kami mendapatkan matriks.
- Anda dapat mengalikan matriks dengan vektor - kita mendapatkan vektor.
- Urutan perkalian itu penting.
Kami terutama akan menggunakan perkalian kiri-ke-kanan, karena perkalian ini lebih familiar dan cocok untuk penjelasan, tetapi di beberapa buku atau perpustakaan Anda mungkin menemukan notasi kanan-ke-kiri, dan semua matriks akan dicerminkan secara diagonal. Ini sama sekali tidak mempengaruhi esensi dari manipulasi yang sedang berlangsung, jadi kami tidak akan membahas ini, tetapi jika Anda menyalin dan menempelkan sesuatu, perhatikan.
Juga, untuk pekerjaan selanjutnya, kita membutuhkan konsep seperti matriks identitas. Ini adalah matriks dengan satu diagonal utama dan nol di semua sel lainnya. Algoritma perkalian dibangun sedemikian rupa sehingga mengalikan matriks identitas dengan matriks lain - kita mendapatkan matriks yang sama. Atau vektor, jika kita berbicara tentang vektor. Dengan kata lain, matriks identitas bertindak sebagai satu kesatuan dalam perkalian bilangan biasa. Ini adalah hal netral yang "tidak mempengaruhi apa pun" saat dikalikan.
Dan hal terakhir yang kita butuhkan adalah contoh yang ditunjukkan pada gambar di atas. Ini, seolah-olah, adalah kasus khusus untuk mengalikan matriks dengan vektor, ketika baris terakhir matriks adalah "bagian dari matriks identitas", dan elemen terakhir dari vektor juga sama dengan 1.
Dalam contoh ini, kami menggunakan huruf (x, y), dan seperti yang sudah Anda duga, pembahasan berikut akan berfokus pada koordinat dalam 2D. Tetapi mengapa menambahkan koordinat ketiga dan membiarkannya sebagai satu? - Anda bertanya. Ini semua tentang kenyamanan, atau bahkan lebih baik, keserbagunaan. Kami sangat sering menambahkan koordinat +1 untuk menyederhanakan penghitungan, dan bekerja dengan 2D menggunakan matriks 3x3, bekerja dengan 3D - dengan matriks 4x4, dan bekerja dengan 4D, misalnya, dengan warna dalam format (r, g, b, a) berjalan dengan matriks 5x5. Sekilas, ini tampak seperti ide gila, tetapi nanti kita akan melihat bagaimana itu menyatukan semua operasi. Jika Anda ingin memahami topik ini lebih detail, Anda bisa google ekspresi "koordinat seragam".
Tapi cukup teori, mari kita lanjutkan ke praktik.
I. Transformasi dasar dalam grafik komputer
Mari kita ambil ekspresi dari contoh di atas dan melihatnya sebagaimana adanya, di luar konteks matriks:
newX = a*x + b*y + c
newY = d*x + e*y + f
Anggap saja ini sebagai persamaan parametrik yang kita gambarkan terakhir kali. Apa yang terjadi jika Anda menetapkan ini atau koefisien itu di dalamnya? Mari kita mulai dengan opsi selanjutnya:
newX = 1*x + 0*y + 0 = x
newY = 0*x + 1*y + 0 = y
Tidak ada yang berubah di sini - koordinat baru (x, y) identik dengan yang lama. Jika kita mengganti koefisien ini ke dalam matriks dan melihat lebih dekat, kita akan melihat bahwa kita mendapatkan matriks identitas.
Apa yang terjadi jika Anda menggunakan koefisien lain? Misalnya, ini adalah:
newX = 1*x + 0*y + A = x + A
newY = 0*x + 1*y + 0 = y
Kita akan mendapatkan offset sepanjang sumbu X. Tapi apa lagi yang bisa terjadi di sini? Jika ini tidak jelas bagi Anda, maka lebih baik kembali ke bagian pertama, di mana kita berbicara tentang bagan dan koefisien.
Mengubah 6 koefisien ini - a, b, c, d, e, f - dan mengamati perubahan x dan y, cepat atau lambat kita akan sampai pada empat kombinasinya, yang tampaknya berguna dan nyaman untuk penggunaan praktis. Mari kita tuliskan langsung dalam bentuk matriks, kembali ke contoh semula:
Nama-nama matriks ini berbicara sendiri. Saat mengalikan matriks ini dengan vektor dengan koordinat beberapa titik, objek di tempat kejadian, dll. kami mendapatkan koordinat baru untuk mereka. Selain itu, kami beroperasi dengan transformasi intuitif - gerakan, penskalaan, rotasi, dan kemiringan, dan koefisien menentukan tingkat keparahan transformasi tertentu di sepanjang sumbu yang sesuai.
Seringkali lebih mudah untuk memikirkan matriks sebagai transformasi untuk sesuatu, seperti koordinat. Ini adalah kata lain tentang abstraksi.
Transformasi dapat ditumpuk. Dalam hal matriks, kita akan menggunakan operasi perkalian, yang bisa sedikit membingungkan, tetapi ini adalah overhead bahasa lisan. Jika kita perlu menggeser suatu benda ke samping dan bertambah, maka kita dapat mengambil matriks untuk perpindahan, matriks untuk penskalaan, dan mengalikannya. Hasilnya adalah matriks yang memberikan offset dan penskalaan pada saat yang bersamaan. Itu hanya tersisa untuk mengubah setiap titik objek kita dengan bantuannya.
Transformasi dasar di CSS
Tapi ini semua hanya kata-kata. Mari kita lihat seperti apa tampilan front-end yang sebenarnya. Di CSS, kami (tiba-tiba) memiliki fungsi matriks. Ini terlihat seperti ini dalam konteks kode:
.example {
transform: matrix(1, 0, 0, 1, 0, 0);
}
Banyak pemula yang melihatnya untuk pertama kali dicakup oleh pertanyaan - mengapa ada enam parameter? Ini aneh. Ini akan menjadi 4 atau 16 - masih belum pergi kemana, tapi 6? Apa yang mereka lakukan?
Namun kenyataannya, semuanya sederhana. Keenam parameter ini adalah koefisien yang baru saja kita susun matriks untuk transformasi dasar. Tetapi untuk beberapa alasan mereka diatur dalam urutan yang berbeda:
Juga di CSS terdapat fungsi matrix3d untuk mengatur transformasi 3D menggunakan matriks. Sudah ada 16 parameter, tepatnya untuk membuat matriks 4x4 (jangan lupa tambahkan dimensi +1).
Matriks untuk transformasi 3D dasar dibuat dengan cara yang sama seperti untuk 2D, hanya lebih banyak koefisien yang diperlukan untuk menyusunnya bukan dalam dua koordinat, tetapi dalam tiga. Tapi prinsipnya sama.
Biasanya, setiap kali akan aneh untuk memagari matriks dan memantau penempatan koefisien yang benar saat bekerja dengan transformasi sederhana di CSS. Kami programmer biasanya mencoba membuat hidup kami lebih mudah. Jadi sekarang kami memiliki fungsi pendek di CSS untuk membuat transformasi individual - translateX, translateY, scaleX, dan seterusnya. Biasanya kita menggunakannya, tetapi penting untuk dipahami bahwa di dalamnya mereka membuat matriks yang sama dengan yang kita bicarakan, cukup menyembunyikan proses ini dari kita di balik lapisan abstraksi lainnya.
Transformasi terjemahan, putar, skala, dan kemiringan yang sama, serta fungsi matriks universal untuk menentukan transformasi, ada di SVG. Sintaksnya sedikit berbeda, tetapi intinya sama. Saat bekerja dengan grafik 3D, misalnya dengan WebGL, kami juga akan menggunakan transformasi yang sama. Tetapi lebih dari itu nanti, sekarang penting untuk dipahami bahwa mereka ada di mana-mana, dan mereka bekerja di mana-mana menurut prinsip yang sama.
Subtotal
Mari kita rangkum di atas:
- Matriks dapat digunakan sebagai transformasi untuk vektor, khususnya untuk koordinat beberapa objek pada halaman.
- Kami hampir selalu beroperasi dengan matriks persegi dan menambahkan dimensi +1 untuk menyederhanakan dan menyatukan penghitungan.
- Ada 4 transformasi dasar - terjemahkan, putar, skala, dan kemiringan. Mereka digunakan di mana-mana dari CSS hingga WebGL dan bekerja dengan cara yang sama di mana-mana.
II. Konstruksi adegan 3D DIY
Perkembangan logis dari topik tentang transformasi koordinat akan menjadi konstruksi pemandangan 3D dan menampilkannya di layar. Dalam satu atau lain bentuk, tugas ini biasanya ada di semua kursus grafik komputer, tetapi di kursus front-end biasanya tidak. Kita akan melihat, mungkin sedikit disederhanakan, namun versi lengkap tentang bagaimana Anda dapat membuat kamera dengan sudut pandang yang berbeda, operasi apa yang diperlukan untuk menghitung koordinat semua objek di layar dan membuat gambar, dan juga menggambar paralel dengan Three.js - yang paling populer alat untuk bekerja dengan WebGL.
Sebuah pertanyaan yang masuk akal harus muncul di sini - mengapa? Mengapa belajar melakukan semuanya dengan tangan Anda jika Anda memiliki alat yang sudah jadi? Jawabannya terletak pada masalah kinerja. Anda mungkin pernah mengunjungi situs dengan kontes seperti Awwwards, CSS Design Awards, FWA dan sejenisnya. Ingat bagaimana situs berkinerja berpartisipasi dalam kontes ini? Ya, hampir semua orang melambat, lambat saat memuat dan membuat laptop bersenandung seperti pesawat terbang! Ya, tentu saja, alasan utamanya biasanya shader yang kompleks atau terlalu banyak manipulasi DOM, tetapi yang kedua adalah jumlah skrip yang luar biasa. Ini memiliki efek bencana pada pemuatan situs tersebut. Biasanya semuanya terjadi seperti ini: Anda perlu melakukan sesuatu di WebGL - ambil beberapa jenis mesin 3D (+ 500KB) dan beberapa plugin untuk itu (+ 500KB);Anda perlu membuat sebuah benda jatuh atau sesuatu terbang terpisah - benda tersebut mengambil mesin fisika (+ 1MB, atau bahkan lebih); Anda perlu memperbarui beberapa data di halaman - ya, tambahkan beberapa SPA-framework dengan selusin plugin (+ 500KB), dll. Dan dengan cara ini, beberapa megabyte skrip diketik, yang tidak hanya perlu diunduh oleh klien (dan ini selain gambar besar), tetapi juga browser akan melakukan sesuatu dengannya setelah mengunduh - mereka tidak hanya terbang ke sana. Selain itu, dalam 99% kasus, hingga skrip berfungsi, pengguna tidak akan melihat semua keindahan yang perlu dia tunjukkan sejak awal.apa yang perlu diunduh klien (dan ini adalah tambahan dari gambar-gambar besar), jadi browser akan melakukan sesuatu dengan mereka setelah memuat - mereka tidak hanya datang kepadanya karena suatu alasan. Selain itu, dalam 99% kasus, hingga skrip berfungsi, pengguna tidak akan melihat semua keindahan yang perlu dia tunjukkan sejak awal.apa yang perlu diunduh klien (dan ini adalah tambahan dari gambar-gambar besar), sehingga browser akan melakukan sesuatu dengan mereka setelah memuat - mereka tidak datang begitu saja karena suatu alasan. Selain itu, dalam 99% kasus, hingga skrip berfungsi, pengguna tidak akan melihat semua keindahan yang perlu dia tunjukkan sejak awal.
Keyakinan populer mengatakan bahwa setiap 666KB skrip dalam produksi meningkatkan waktu buka halaman dengan waktu yang cukup bagi pengguna untuk mengirim pengembang situs ke lingkaran neraka berikutnya. Three.js dalam konfigurasi minimum berbobot 628KB ...
Selain itu, seringkali tugas tidak memerlukan koneksi alat yang rumit. Misalnya, untuk menampilkan beberapa bidang dengan tekstur di WebGL dan menambahkan beberapa bayangan sehingga gambarnya menyimpang dalam gelombang, Anda tidak memerlukan keseluruhan Three.js. Dan untuk membuat sebuah benda jatuh, Anda tidak memerlukan mesin fisika yang lengkap. Ya, ini mungkin akan mempercepat pekerjaan Anda, terutama jika Anda sudah terbiasa dengannya, tetapi Anda akan membayarnya sesuai waktu pengguna. Di sini setiap orang memutuskan sendiri apa yang lebih menguntungkan baginya.
Rantai transformasi koordinat
Sebenarnya, inti dari transformasi koordinat untuk membuat pemandangan 3D di layar kami cukup sederhana, tetapi kami akan tetap menganalisisnya dalam beberapa langkah, karena kemungkinan besar bagi banyak desainer tata letak, proses ini akan menjadi sesuatu yang baru.
Jadi begitu. Katakanlah seorang desainer telah menggambar model 3D. Biarkan itu menjadi kubus (dalam contoh kita akan menggunakan konstruksi yang paling sederhana agar tidak memperumit ilustrasi secara tiba-tiba):
Seperti apa model ini? Faktanya, ini adalah sekumpulan titik dalam beberapa sistem koordinat dan satu set hubungan di antara mereka sehingga Anda dapat menentukan di antara titik mana bidang harus ditempatkan. Pertanyaan tentang pesawat dalam konteks WebGL akan berada di pundak browser itu sendiri, dan koordinat penting bagi kami. Anda perlu mencari tahu persis bagaimana mengubahnya.
Modelnya, seperti yang kami katakan, memiliki sistem koordinat. Tapi biasanya kami ingin punya banyak model, kami ingin heboh dengan mereka. Pemandangan, dunia 3D kita, akan memiliki sistem koordinat globalnya sendiri. Jika kita hanya mengartikan koordinat model sebagai koordinat global, maka model kita akan ditempatkan seolah-olah "di tengah dunia". Dengan kata lain, tidak ada yang berubah. Tapi kami ingin menambahkan banyak model ke berbagai tempat di dunia kami, seperti ini:
Apa yang harus dilakukan? Anda perlu mengubah koordinat setiap model menjadi koordinat global. Posisi model di luar angkasa diatur oleh offset, rotasi, dan penskalaan - kita telah melihat transformasi dasar ini. Sekarang kita perlu membuat matriks transformasi untuk setiap model, yang akan menyimpan dengan tepat informasi ini tentang di mana model tersebut dalam hubungannya dengan dunia, dan bagaimana model dirotasi.
Misalnya, untuk kubus akan ada kira-kira matriks berikut:
// .
// « » .
const modelMatrix1 = [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
// , X.
const modelMatrix2 = [
[1, 0, 0, 1.5],
[0, 1, 0, 0 ],
[0, 0, 1, 0 ],
[0, 0, 0, 1 ]
];
// , X .
const modelMatrix3 = [
[1, 0, 0, -1.5],
[0, 1, 0, 0 ],
[0, 0, 1, 0 ],
[0, 0, 0, 1 ]
];
Selanjutnya, kami akan bertindak kira-kira sebagai berikut:
{
= [ ] *
}
Oleh karena itu, setiap model membutuhkan matriksnya sendiri.
Demikian pula, Anda dapat membuat rantai beberapa objek. Jika seekor burung harus mengepakkan sayapnya, maka akan tepat untuk menerjemahkan koordinat titik sayap menjadi koordinat burung, dan kemudian menjadi koordinat dunia global. Akan lebih mudah untuk menebak lintasan sayap secara langsung dalam koordinat global. Tapi begitulah, omong-omong.
Selanjutnya, Anda perlu memutuskan di sisi mana kita akan melihat dunia. Saya butuh kamera.
Kamera adalah abstraksi seperti itu, seperti tiruan dari kamera fisik. Ini memiliki koordinat dan beberapa sudut kemiringan yang menentukan lokasinya dalam koordinat global. Tugas kita adalah mengubah sekumpulan koordinat global sekarang menjadi sistem koordinat kamera. Prinsipnya sama dengan contoh sebelumnya:
{
= [ ] *
}
, , . !
Mari kita lihat pemandangan dari tempat kamera kondisional kita berada:
Sekarang, setelah mengubah semua titik ke sistem koordinat kamera, kita dapat dengan mudah membuang sumbu Z, dan menafsirkan sumbu X dan Y sebagai "horizontal" dan "vertikal". Jika Anda menggambar semua titik model di layar, Anda akan mendapatkan gambar, seperti pada contoh - tanpa perspektif dan sulit untuk memahami bagian mana dari pemandangan yang benar-benar masuk ke dalam bingkai. Kamera itu seperti ukuran yang tak terbatas ke segala arah. Entah bagaimana, kita dapat menyesuaikan semuanya sehingga apa yang kita butuhkan pas dengan layar, tetapi alangkah baiknya memiliki cara universal untuk menentukan bagian mana dari pemandangan yang akan termasuk dalam bidang pandang kamera dan mana yang tidak.
Dengan kamera fisik, kita bisa membicarakan hal seperti sudut pandang. Mengapa tidak menambahkannya di sini juga?
Untuk ini kita membutuhkan matriks lain - matriks proyeksi. Secara umum, itu dapat dibangun dengan berbagai cara. Bergantung pada apa yang diambil sebagai parameter awal, Anda mendapatkan jenis yang sedikit berbeda dari matriks ini, tetapi intinya akan sama. Kami akan mengambil versi yang sedikit disederhanakan berikut ini:
// 90
const s = 1 / (Math.tan(90 * Math.PI / 360));
const n = 0.001;
const f = 10;
const projectionMatrix = [
[s, 0, 0, 0],
[0, s, 0, 0],
[0, 0, -(f)/(f-n), -f*n/(f-n)],
[0, 0, -1, 0]
];
Matriks proyeksi, dengan satu atau lain cara, berisi tiga parameter - ini adalah sudut pandang, serta jarak minimum dan maksimum ke titik tempat Anda bekerja. Ini dapat diekspresikan dengan cara yang berbeda, dapat digunakan dengan cara yang berbeda, tetapi parameter ini akan tetap ada dalam matriks ini.
Saya mengerti bahwa tidak pernah jelas mengapa matriks terlihat persis seperti ini, tetapi untuk memperolehnya dengan penjelasan, Anda memerlukan rumus untuk 2-3 halaman. Ini sekali lagi membawa kita kembali ke gagasan bahwa mengabstraksikan itu berguna - kita dapat beroperasi dengan hasil yang lebih umum, tanpa masuk ke detail kecil di mana tidak perlu memecahkan masalah tertentu.
Sekarang, membuat transformasi yang sudah familiar:
{
= [ ] *
}
Kami mendapatkan bidang visi kami persis seperti yang kami harapkan. Meningkatkan sudut - kita melihat sebagian besar dari semua sisi, mengurangi sudut - kita hanya melihat apa yang lebih dekat ke arah kamera diarahkan. KEUNTUNGAN!
Tapi sebenarnya tidak. Kami lupa tentang perspektifnya. Gambar tanpa harapan dibutuhkan di beberapa tempat, jadi Anda perlu menambahkannya entah bagaimana. Dan di sini, secara tiba-tiba, kami tidak membutuhkan matriks. Tugas ini terlihat sangat sulit, tetapi diselesaikan dengan pembagian dangkal dari koordinat X dan Y oleh W untuk setiap titik:
* Di sini kita telah menggeser kamera ke samping dan menambahkan garis paralel "di lantai" untuk memperjelas di mana perspektif ini muncul.
Dengan memilih koefisien sesuai selera kita, kita akan mendapatkan pilihan perspektif yang berbeda. Dalam arti tertentu, koefisien di sini menentukan jenis lensa, seberapa besar ia “meratakan” ruang di sekitarnya.
Sekarang kami memiliki gambaran yang lengkap. Anda dapat mengambil koordinat X dan Y untuk setiap titik dan menggambarnya di layar sesuka Anda.
Secara umum, ini cukup untuk membangun suasana, tetapi dalam proyek nyata, Anda mungkin juga menemukan transformasi tambahan terkait penskalaan di bagian paling akhir. Idenya adalah bahwa setelah proyeksi kita mendapatkan koordinat (x, y) dalam 1, koordinat yang dinormalisasi, dan kemudian kita mengalikannya dengan ukuran layar atau kanvas, mendapatkan koordinat untuk ditampilkan di layar. Langkah ekstra ini menghilangkan ukuran kanvas dari semua kalkulasi, meninggalkannya hanya di bagian paling akhir. Ini terkadang nyaman.
Di sini Anda mungkin sakit kepala karena banyaknya informasi, jadi mari kita perlambat dan ulangi semua transformasi di satu tempat:
Jika Anda menggabungkan transformasi ini menjadi satu, Anda mendapatkan mesin kecil.
Bagaimana tampilannya di Three.js?
Sekarang setelah kita memahami dari mana mesin kecil ini berasal, mari kita lihat contoh shader vertex default di Three.js yang "tidak melakukan apa-apa":
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
atau lebih lengkap:
void main() {
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}
Apakah dia mengingatkan Anda tentang sesuatu? Ya, inilah mesin khusus ini. Dan "tidak melakukan apa-apa" yang kami maksud adalah bahwa ia hanya melakukan semua pekerjaan penghitungan ulang koordinat, berdasarkan matriks yang diteruskan dengan hati-hati dari Three.js. Tapi tidak ada yang mau membuat matriks ini dengan tangan mereka sendiri, bukan?
Jenis kamera dalam grafik komputer dan Three.js
Topik jenis kamera tidak secara langsung berkaitan dengan matriks, tetapi kami masih akan mencurahkan beberapa menit untuk itu, karena kita masih berbicara tentang Three.js, dan terkadang orang-orang bingung tentang hal ini.
Kamera adalah abstraksi. Ini membantu kita berpikir di dunia 3D dengan cara yang sama seperti yang kita pikirkan di dunia nyata. Seperti yang kami katakan, kamera memiliki posisi dalam ruang, arah yang dilihatnya, dan sudut pandang. Semua ini ditentukan menggunakan dua matriks dan, mungkin, tambahan pembagian koordinat untuk membuat perspektif.
Dalam grafik komputer, kami memiliki dua jenis kamera - "dengan perspektif" dan "tanpa perspektif". Ini adalah dua jenis kamera yang pada dasarnya berbeda dalam istilah teknis, yang membutuhkan tindakan berbeda untuk mendapatkan gambar. Dan itu saja. Tidak ada yang lain. Yang lainnya adalah kombinasi mereka, beberapa abstraksi yang lebih kompleks. Misalnya, Three.js memiliki kamera stereo - ini bukan semacam kamera "tipe khusus" dalam istilah teknis, tetapi hanya abstraksi - dua kamera agak terpisah dalam ruang dan terletak pada suatu sudut:
Untuk setiap setengah layar, kami mengambil kamera kami sendiri dan ternyata gambar stereo. Dan CubeCamera adalah 6 kamera biasa yang terletak di berbagai sisi titik, tidak lebih.
Apa berikutnya?
Langkah selanjutnya, setelah mendapatkan koordinat objek, adalah menentukan objek mana yang akan terlihat dan mana yang akan disembunyikan di belakang objek lainnya. Dalam konteks WebGL, browser akan melakukannya sendiri. Nah, masih akan ada tugas terkait seperti menerapkan tekstur pada mereka, menghitung pencahayaan dengan normal, bayangan, gambar pasca-pemrosesan, dll. Tetapi kami telah melakukan bagian yang paling penting dan sulit untuk dipahami. Itu bagus. Faktanya, banyak hal generatif tidak membutuhkan tekstur dan pencahayaan ini, jadi mungkin saja pengetahuan yang diperoleh sekarang akan cukup untuk bekerja dengannya.
Ngomong-ngomong, melemparkan bayangan dari suatu objek ke bidang tidak lebih dari proyeksi objek ini ke bidang ini pada sudut tertentu, diikuti dengan mencampurkan warna. Prosesnya secara inheren sangat mirip dengan kamera, tetapi juga menambahkan sudut antara bidang proyeksi dan "arah pandang".
Tentang tekstur dan efek untuk gambar di WebGL, termasuk tanpa perpustakaan, telah kita bicarakan di artikel sebelumnya lebih dari satu kali. Anda dapat merujuk ke mereka jika Anda tertarik dengan topik ini. Jadi, dengan menggabungkan semua pengetahuan ini bersama-sama, kita dapat membangun benda-benda 3D penuh warna dengan tangan kita sendiri.
3D- . – - . , Three.js . , , , - , - . , .
Sekarang saatnya untuk meringkas hal di atas sehingga ada ruang di kepala Anda untuk use case matriks selanjutnya.
Begitu:
- Anda dapat membangun dunia 3D dan menghitung koordinat objek di layar dengan tangan Anda sendiri menggunakan kereta dari matriks.
- Dalam dunia 3D, kami beroperasi dengan abstraksi seperti "kamera". Ini memiliki lokasi, arah dan sudut pandang. Semua ini diatur menggunakan matriks yang sama. Dan ada dua tampilan kamera dasar - perspektif dan non-perspektif.
- Dalam konteks WebGL, rendering gambar secara manual di layar atau kalkulasi fisika sering kali dapat menghilangkan ketergantungan yang berat dan mempercepat pemuatan halaman. Tetapi penting untuk menjaga keseimbangan antara skrip Anda, alat yang sudah jadi, dan opsi alternatif untuk memecahkan masalah, tidak hanya memperhatikan kenyamanan Anda, tetapi juga masalah kecepatan unduh dan kinerja terbaik, termasuk pada ponsel.
AKU AKU AKU. Filter untuk gambar
Akhirnya, kita akan melihat area penerapan matriks seperti filter untuk gambar. Jika kita menganggap warna dalam format RGBA sebagai vektor, maka kita dapat berasumsi bahwa di sini kita dapat menerapkan transformasi yang mirip dengan yang kita gunakan dengan koordinat:
Dan terapkan ini pada gambar sesuai dengan prinsip yang jelas:
{
= [ ] *
}
Jika matriks identitas bertindak sebagai matriks, tidak ada yang akan berubah, kita sudah tahu itu. Apa yang terjadi jika Anda menerapkan filter yang mirip dengan transformasi terjemahan dan skala?
OU. Hasilnya adalah filter kecerahan dan kontras. Menarik.
Saat bereksperimen dengan filter semacam itu, Anda harus selalu ingat untuk menyesuaikan nilai agar gambar tidak terlalu terang. Jika Anda mengalikan sesuatu dengan angka besar, kemungkinan besar Anda perlu mengurangi atau membagi sesuatu di suatu tempat. Seperti yang ditunjukkan pada contoh sebelumnya.
Bagaimana cara membuat gambar hitam putih dari yang berwarna? Hal pertama yang terlintas dalam pikiran adalah menambahkan nilai saluran RGB, membagi dengan 3, dan menggunakan nilai yang dihasilkan untuk ketiga saluran. Dalam format matriks, akan terlihat seperti ini:
Dan meskipun kami mendapatkan gambar hitam putih, itu masih dapat ditingkatkan. Mata kita benar-benar merasakan kecerahan warna yang berbeda secara berbeda. Dan untuk menyampaikan hal ini selama desaturasi, kami membuat koefisien yang berbeda untuk setiap saluran RGB dalam matriks ini.
Contoh di bawah ini akan menyajikan nilai yang diterima secara umum untuk koefisien ini, tetapi tidak ada yang mau memainkannya. Secara total, koefisien ini harus memberikan 1, tetapi tergantung pada proporsinya, kita akan mendapatkan gambar hitam putih yang sedikit berbeda. Ini dapat, sampai batas tertentu, mensimulasikan reproduksi warna yang berbeda saat bekerja dengan kamera film.
Dan jika kita juga mengalikan sedikit diagonal utama, maka kita mendapatkan filter saturasi universal:
Ini berfungsi di kedua arah - baik dalam desaturasi (Anda dapat mencapai gambar hitam dan putih sepenuhnya), dan dalam saturasi. Itu semua tergantung pada koefisien yang sesuai.
Secara umum, Anda dapat bermain-main dengan filter untuk waktu yang lama, mendapatkan berbagai hasil:
* Matriks yang digunakan dalam contoh ini dapat dilihat di GitHubjika Anda tiba-tiba membutuhkannya. Untuk dimasukkan ke dalam artikel, volumenya akan berlebihan.
Tapi mari kita masih sedikit memperhatikan di mana ini sebenarnya berlaku. Jelas bahwa gagasan mengganti warna untuk setiap piksel menyarankan shader untuk memproses foto, atau untuk pasca-pemrosesan beberapa adegan 3D, tetapi mungkin itu masih di suatu tempat di frontend?
Filter di CSS
Di CSS, kami memiliki properti filter. Dan di sana, khususnya, ada opsi untuk filter yang terkait dengan warna:
- kecerahan (kami berhasil)
- kontras (selesai)
- invert (sama seperti kontras, hanya koefisien diagonal utama dengan tanda berbeda)
- jenuh (selesai)
- grayscale (seperti yang telah disebutkan, ini adalah kasus saturate khusus)
- sepia (konsep yang sangat kabur, versi sepia yang berbeda diperoleh dengan memainkan koefisien, di mana kami entah bagaimana mengurangi keberadaan warna biru)
Dan filter ini menerima koefisien sebagai masukan, yang kemudian disubstitusikan dalam satu bentuk atau lainnya ke dalam matriks yang kita buat sebelumnya. Sekarang kita tahu bagaimana sihir ini bekerja dari dalam. Dan sekarang sudah jelas bagaimana filter ini digabungkan di dalam perut juru bahasa CSS, karena semua yang ada di sini dibangun menurut prinsip yang sama dengan koordinat: matrik multiply - tambahkan efek. Benar, tidak ada matriks fungsi kustom dalam properti ini di CSS. Tapi dalam SVG!
Filter matriks dalam SVG
Dalam SVG, kami memiliki feColorMatrix, yang digunakan untuk membuat filter untuk gambar. Dan di sini kita sudah memiliki kebebasan penuh - kita bisa membuat matriks sesuai selera kita. Sintaksnya seperti ini:
<filter id=’my-color-filter’>
<feColorMatrix in=’SourceGraphics’
type=’matrix’,
values=’1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
0 0 0 0 1‘
/>
</filter>
Anda juga dapat menerapkan filter SVG ke elemen DOM biasa dalam CSS, ada fungsi url khusus untuk ini ... Tapi saya tidak memberi tahu Anda!
Faktanya, filter SVG dalam CSS masih belum didukung oleh semua browser (tidak menunjuk jari ke IE), tetapi ada rumor bahwa Edge akhirnya pindah ke mesin chromium, dan versi yang lebih lama akan kehilangan dukungan di masa mendatang, jadi sudah waktunya untuk teknologi ini. master, Anda dapat melakukan banyak hal menarik dengannya.
Apa lagi yang terjadi?
Selain efek untuk gambar, yang dibangun di atas prinsip transformasi, ada berbagai hal yang dibangun di atas perpindahan piksel, mencampur warna dan manipulasi lainnya, di mana matriks dapat menjadi format yang baik untuk menyimpan data, yang menurutnya manipulasi ini harus dilakukan.
Matriks kernel
Secara khusus, di frontend, kita bertemu dengan matriks kernel, dan efek yang terkait dengannya. Intinya sederhana - ada matriks persegi, biasanya 3x3 atau 5x5, meskipun mungkin ada lebih banyak, dan koefisien disimpan di dalamnya. Di tengah matriks - untuk piksel "saat ini", di sekitar tengah - untuk piksel tetangga. Jika matriksnya 5x5, maka lapisan lain muncul di sekitar tengah - untuk piksel yang terletak satu dari yang sekarang. Jika 7x7 - lalu lapisan lain, dll. Dengan kata lain, kami menganggap matriks sebagai bidang dua dimensi, tempat Anda dapat menyusun koefisien sesuai kebijaksanaan Anda, tanpa mengacu pada persamaan apa pun. Dan mereka akan diartikan sebagai berikut:
{
=
,
}
Kanvas kosong sangat tidak cocok untuk tugas-tugas seperti itu, tetapi shader sangat merata. Tetapi mudah untuk menebak bahwa semakin besar matriks, semakin banyak piksel tetangga yang akan kita gunakan. Jika matriksnya 3x3 maka kita tambahkan 9 warna, jika 5x5 - 25, jika 7x7 - 49, dst. Lebih banyak operasi - lebih banyak beban pada prosesor atau kartu video. Ini pasti akan mempengaruhi kinerja halaman secara keseluruhan.
Jika memungkinkan, gunakan matriks kecil untuk efek tersebut jika Anda perlu melapisinya di suatu tempat dalam waktu nyata.
Dalam SVG, kami memiliki tag feConvolveMatrix khusus, yang dibuat hanya untuk membuat efek seperti itu:
<filter id=’my-image-filter’>
<feConvolveMatrix
kernelMatrix=’0 0 0
0 1 0
0 0 0’
/>
</filter>
Di sini kita telah membuat filter sederhana untuk gambar yang tidak melakukan apa-apa - warna baru untuk setiap piksel akan sama dengan yang sekarang dikalikan dengan 1, dan nilai untuk warna piksel tetangga akan dikalikan dengan 0.
Perhatikan bahwa browser yang berbeda merender SVG secara berbeda, dan rendering warna juga dapat mengapung sangat luas. Terkadang perbedaannya hanya bencana. Jadi selalu uji filter SVG Anda atau gunakan kanvas, yang lebih dapat diprediksi dalam konteks kami.
Jika kita mulai mengatur angka-angka dalam beberapa lapisan, dari yang lebih besar ke yang lebih kecil, kita mendapatkan blur:
Semakin besar matriks, semakin banyak piksel yang berdekatan yang kita sentuh, semakin banyak gambar yang dicuci. Hal utama di sini adalah jangan lupa untuk menormalkan nilai, jika tidak, gambar akan menyala.
Sekarang, dengan mengetahui cara kerja blur, kita dapat memahami mengapa penggunaan aktifnya pada halaman dalam CSS atau SVG menyebabkan rem - untuk setiap piksel, browser melakukan banyak perhitungan.
Jika Anda mulai bereksperimen dengan mengubah tanda-tanda koefisien dan menyusunnya dalam pola yang berbeda, Anda akan mendapatkan efek penajaman, deteksi tepi dan beberapa lainnya. Cobalah bermain dengan mereka sendiri. Ini mungkin bisa membantu.
Dengan demikian, Anda dapat membuat efek berbeda untuk foto, atau bahkan video, dalam waktu nyata, dan membuatnya bergantung pada tindakan pengguna. Itu semua tergantung imajinasi Anda.
Subtotal
Mari kita rangkum apa yang dikatakan di bagian ini:
- Matriks dapat digunakan tidak hanya untuk transformasi yang terkait dengan koordinat, tetapi juga untuk membuat filter warna. Semuanya dilakukan dengan prinsip yang sama.
- Matriks dapat digunakan sebagai tempat penyimpanan 2D yang nyaman untuk beberapa data, termasuk koefisien yang berbeda untuk efek visual.
Kesimpulan
Jika kita mengabstraksi sedikit dari algoritma yang rumit, maka matriks akan menjadi alat yang terjangkau untuk memecahkan masalah praktis. Dengan bantuan mereka, Anda dapat menghitung transformasi geometris dengan tangan Anda sendiri, termasuk dalam kerangka CSS dan SVG, membuat pemandangan 3D, dan juga membuat semua jenis filter untuk foto atau untuk gambar pasca-pemrosesan dalam kerangka WebGL. Semua topik ini biasanya melampaui frontend klasik dan lebih terkait dengan grafik komputer secara umum, tetapi meskipun Anda tidak menyelesaikan masalah ini secara langsung, mengetahui prinsip pemecahannya akan memungkinkan Anda untuk lebih memahami cara kerja beberapa alat Anda. Itu tidak akan pernah berlebihan.
Saya harap artikel ini membantu Anda memahami topik penggunaan praktis matriks di frontend, atau setidaknya memberi Anda dasar yang dapat Anda gunakan untuk pengembangan lebih lanjut. Jika menurut Anda beberapa topik lain yang berkaitan dengan matematika atau fisika layak mendapat ulasan yang sama dalam konteks tata letak, maka tulis pemikiran Anda di kolom komentar, mungkin salah satu artikel berikutnya akan membahasnya.