Rumah >hujung hadapan web >tutorial js >Membina enjin 3D dengan JavaScript
Tujuan tutorial ini adalah untuk menerangkan bagaimana kita boleh membina enjin 3D mudah untuk web, tanpa WebGL. Kami akan terlebih dahulu melihat bagaimana kami dapat menyimpan bentuk 3D. Kemudian, kita akan melihat cara memaparkan bentuk ini, dalam dua pandangan yang berbeza.
Takeaways Key
Untuk enjin 3D kami, ia akan sama. Kami akan bermula dengan menyimpan setiap puncak bentuk kami. Kemudian, bentuk ini akan menyenaraikan wajahnya, dan setiap wajah akan menyenaraikan simpangnya.
Untuk mewakili puncak, kita memerlukan struktur yang betul. Di sini kita membuat kelas untuk menyimpan koordinat puncak.
<span>var <span>Vertex</span> = function(x<span>, y, z</span>) { </span> <span>this.x = parseFloat(x); </span> <span>this.y = parseFloat(y); </span> <span>this.z = parseFloat(z); </span><span>}; </span>sekarang titik vertex boleh dibuat seperti objek lain:
<span>var A = new Vertex(10, 20, 0.5); </span>Seterusnya, kami membuat kelas yang mewakili polyhedron kami. Mari kita ambil kiub sebagai contoh. Takrif kelas adalah di bawah, dengan penjelasan selepas.
<span>var <span>Cube</span> = function(center<span>, size</span>) { </span> <span>// Generate the vertices </span> <span>var d = size / 2; </span> <span>this.vertices = [ </span> <span>new Vertex(center.x - d, center.y - d, center.z + d), </span> <span>new Vertex(center.x - d, center.y - d, center.z - d), </span> <span>new Vertex(center.x + d, center.y - d, center.z - d), </span> <span>new Vertex(center.x + d, center.y - d, center.z + d), </span> <span>new Vertex(center.x + d, center.y + d, center.z + d), </span> <span>new Vertex(center.x + d, center.y + d, center.z - d), </span> <span>new Vertex(center.x - d, center.y + d, center.z - d), </span> <span>new Vertex(center.x - d, center.y + d, center.z + d) </span> <span>]; </span> <span>// Generate the faces </span> <span>this.faces = [ </span> <span>[this.vertices[0], this.vertices[1], this.vertices[2], this.vertices[3]], </span> <span>[this.vertices[3], this.vertices[2], this.vertices[5], this.vertices[4]], </span> <span>[this.vertices[4], this.vertices[5], this.vertices[6], this.vertices[7]], </span> <span>[this.vertices[7], this.vertices[6], this.vertices[1], this.vertices[0]], </span> <span>[this.vertices[7], this.vertices[0], this.vertices[3], this.vertices[4]], </span> <span>[this.vertices[1], this.vertices[6], this.vertices[5], this.vertices[2]] </span> <span>]; </span><span>}; </span>Menggunakan kelas ini, kita boleh membuat kiub maya dengan menunjukkan pusatnya dan panjang tepinya.
<span>var cube = new Cube(new Vertex(0, 0, 0), 200); </span>Pembina kelas kiub bermula dengan menghasilkan simpul kiub, dikira dari kedudukan pusat yang ditunjukkan. Skema akan lebih jelas, jadi lihat di bawah kedudukan lapan simpang yang kita hasilkan:
Kemudian, kami menyenaraikan wajah. Setiap muka adalah persegi, jadi kita perlu menunjukkan empat simpang untuk setiap muka. Di sini saya memilih untuk mewakili wajah dengan array tetapi, jika anda memerlukan, anda boleh membuat kelas khusus untuk itu.
Apabila kita membuat muka, kita menggunakan empat simpang. Kami tidak perlu menunjukkan kedudukan mereka sekali lagi, kerana ia disimpan dalam objek ini. [I]. Ia praktikal, tetapi ada sebab lain mengapa kami melakukannya.
Secara lalai, JavaScript cuba menggunakan jumlah memori yang paling sedikit. Untuk mencapai itu, ia tidak menyalin objek yang diluluskan sebagai hujah fungsi atau bahkan disimpan ke dalam tatasusunan. Untuk kes kami, tingkah laku yang sempurna.
Malah, setiap puncak mengandungi tiga nombor (koordinat mereka), ditambah beberapa kaedah jika kita perlu menambahnya. Jika, untuk setiap muka, kami menyimpan salinan puncak, kami akan menggunakan banyak memori, yang tidak berguna. Di sini, semua yang kita ada adalah rujukan: koordinat (dan kaedah lain) disimpan sekali, dan hanya sekali. Oleh kerana setiap titik digunakan oleh tiga muka yang berbeza, dengan menyimpan rujukan dan bukan salinan, kita membahagikan memori yang diperlukan oleh tiga (lebih kurang)!
adakah kita memerlukan segitiga?
Sebab di sebalik pilihan ini ialah artikel ini adalah pengenalan kepada topik ini dan kami akan memaparkan bentuk asas seperti kiub. Menggunakan segitiga untuk memaparkan dataran akan lebih komplikasi daripada apa -apa lagi dalam kes kita.
untuk memahami mengapa, ingat sekali lagi kelas matematik kami. Apabila anda ingin menterjemahkan persegi, anda tidak benar -benar menerjemahkannya. Malah, anda menerjemahkan empat simpang, dan anda menyertai terjemahan.
di sini, kita akan melakukan perkara yang sama: kita tidak akan menyentuh wajah. Kami menggunakan operasi yang dikehendaki pada setiap puncak dan kami sudah selesai. Sebagai wajah menggunakan rujukan, koordinat wajah dikemas kini secara automatik. Sebagai contoh, lihat bagaimana kita boleh menterjemahkan kiub kami yang telah dibuat sebelum ini:
<span>var <span>Vertex</span> = function(x<span>, y, z</span>) { </span> <span>this.x = parseFloat(x); </span> <span>this.y = parseFloat(y); </span> <span>this.z = parseFloat(z); </span><span>}; </span>Memberi imej
unjuran
Sejak permulaan artikel ini kita telah bercakap tentang koordinat, yang diwakili oleh tiga nombor: x, y dan z. Tetapi untuk menentukan koordinat, kita memerlukan asas: adakah z koordinat menegak? Adakah ia pergi ke bahagian atas atau ke bawah? Tidak ada jawapan sejagat, dan tiada konvensyen, kerana hakikatnya anda boleh memilih apa sahaja yang anda mahukan. Satu -satunya perkara yang perlu anda ingat ialah apabila anda bertindak pada objek 3D, anda perlu konsisten, kerana formula akan berubah bergantung padanya. Dalam artikel ini, saya memilih asas yang dapat anda lihat dalam skema kiub di atas: x dari kiri ke kanan, Y dari belakang ke depan dan z dari bawah ke atas.
Sekarang, kita tahu apa yang perlu dilakukan: kita mempunyai koordinat dalam asas (x, y, z) dan, untuk memaparkannya, kita perlu mengubahnya menjadi koordinat dalam asas (x, z): kerana ia adalah pesawat , kami akan dapat memaparkannya.
tidak hanya satu unjuran. Lebih buruk lagi, terdapat bilangan unjuran yang tidak terhingga! Dalam artikel ini kita akan melihat dua jenis unjuran, yang dalam praktiknya yang paling banyak digunakan.
Sebelum memproyeksikan objek kami, mari tulis fungsi yang akan memaparkannya. Fungsi ini menerima sebagai parameter array yang menyenaraikan objek untuk diberikan, konteks kanvas yang mesti digunakan untuk memaparkan objek, dan butiran lain yang diperlukan untuk menarik objek di tempat yang betul.
Arahan boleh mengandungi beberapa objek untuk diberikan. Objek -objek ini harus menghormati satu perkara: mempunyai harta awam bernama wajah yang merupakan penyenaraian array semua wajah objek (seperti kiub kami yang telah dibuat sebelumnya). Wajah ini boleh menjadi apa -apa (persegi, segitiga, atau bahkan dodecagon, jika anda mahu): mereka hanya perlu menjadi array yang menyenaraikan simpang mereka.
Mari lihat kod untuk fungsi, diikuti dengan penjelasan:
<span>var <span>Vertex</span> = function(x<span>, y, z</span>) { </span> <span>this.x = parseFloat(x); </span> <span>this.y = parseFloat(y); </span> <span>this.z = parseFloat(z); </span><span>}; </span>
Fungsi ini layak mendapat penjelasan. Lebih tepat lagi kita perlu menjelaskan apa fungsi projek ini (), dan apakah argumen DX dan DY ini. Selebihnya pada dasarnya tidak lain daripada menyenaraikan objek, kemudian lukis setiap muka.
seperti namanya, fungsi projek () di sini untuk menukar koordinat 3D menjadi 2D yang. Ia menerima puncak dalam ruang 3D dan mengembalikan puncak dalam satah 2D yang dapat kita tentukan seperti di bawah.
<span>var A = new Vertex(10, 20, 0.5); </span>
Daripada menamakan koordinat x dan z yang saya pilih di sini untuk menamakan semula z koordinat ke Y, untuk mengekalkan konvensyen klasik yang sering kita temukan dalam geometri 2D, tetapi anda boleh menyimpan z jika anda lebih suka.
Kandungan tepat projek () adalah apa yang akan kita lihat di bahagian seterusnya: ia bergantung kepada jenis unjuran yang anda pilih. Tetapi apa jua jenis ini, fungsi render () boleh disimpan seperti sekarang.
Sebaik sahaja kita mempunyai koordinat di atas kapal terbang, kita boleh memaparkannya di atas kanvas, dan itulah yang kita lakukan ... dengan sedikit tipu muslihat: kita tidak benar -benar menarik koordinat sebenar yang dikembalikan oleh fungsi projek ().
Malah, projek () fungsi mengembalikan koordinat pada satah 2D maya, tetapi dengan asal yang sama daripada yang kami tentukan untuk ruang 3D kami. Walau bagaimanapun, kami mahu asalnya berada di tengah kanvas kami, itulah sebabnya kami menerjemahkan koordinat: puncak (0,0) tidak berada di tengah kanvas, tetapi (0 dx, 0 dy) adalah, jika kami Pilih dx dan dengan bijak. Seperti yang kita mahu (dx, dy) berada di tengah kanvas, kita tidak mempunyai pilihan dan kita menentukan dx = canvas.width / 2 dan dy = canvas.height / 2.
Akhirnya, terperinci terakhir: Mengapa kita menggunakan -y dan bukan y secara langsung? Jawapannya adalah dalam pilihan kami: paksi z diarahkan ke bahagian atas. Kemudian, dalam adegan kami, vertis dengan koordinat Z positif akan bergerak. Walau bagaimanapun, pada kanvas, paksi Y diarahkan ke bahagian bawah: vertis dengan koordinat Y positif akan bergerak ke bawah. Itulah sebabnya kita perlu menentukan kanvas 'koordinat pada kanvas sebagai terbalik dari koordinat Z dari adegan kita.
Sekarang fungsi render () jelas, sudah tiba masanya untuk melihat projek ().
mari kita mulakan dengan unjuran ortografi. Kerana ia adalah yang paling mudah, ia adalah sempurna untuk memahami apa yang akan kita lakukan.
Kami mempunyai tiga koordinat, dan kami hanya mahu dua. Apakah perkara paling mudah dilakukan dalam kes sedemikian? Keluarkan salah satu koordinat. Dan itulah yang kita lakukan dalam pandangan ortografi. Kami akan mengeluarkan koordinat yang mewakili kedalaman: koordinat y.
<span>var <span>Vertex</span> = function(x<span>, y, z</span>) { </span> <span>this.x = parseFloat(x); </span> <span>this.y = parseFloat(y); </span> <span>this.z = parseFloat(z); </span><span>}; </span>
Anda kini boleh menguji semua kod yang kami tulis sejak permulaan artikel ini: Ia berfungsi! Tahniah, anda hanya memaparkan objek 3D pada skrin rata!
Fungsi ini dilaksanakan dalam contoh hidup di bawah, di mana anda boleh berinteraksi dengan kiub dengan memutarnya dengan tetikus anda.
lihat pandangan pen 3D orthographic pena oleh SitePoint (@SitePoint) pada codepen.
Kadang -kadang, pandangan ortografi adalah apa yang kita mahu, kerana ia mempunyai kelebihan memelihara paralel. Walau bagaimanapun, ia bukan pandangan yang paling semula jadi: mata kita tidak melihat seperti itu. Itulah sebabnya kita akan melihat unjuran kedua: pandangan perspektif.
Pandangan perspektif adalah sedikit lebih kompleks daripada ortografi, seperti yang kita perlukan untuk melakukan beberapa pengiraan. Walau bagaimanapun, pengiraan ini tidak begitu rumit dan anda hanya perlu tahu satu perkara: Cara menggunakan teorem intercept.
Untuk memahami mengapa, mari kita lihat skema yang mewakili pandangan ortografi. Kami memproyeksikan mata kami di atas kapal terbang dengan cara ortogonal.
pada dasarnya kita mempunyai dua langkah:
Bertentangan dengan pandangan ortografi, lokasi tepat pesawat di sini adalah penting: jika anda meletakkan pesawat jauh dari kamera, anda tidak akan mendapat kesan yang sama daripada jika anda meletakkannya dekat dengannya. Di sini kita meletakkannya di jarak d dari kamera.
Bermula dari puncak m (x, y, z) di ruang 3D, kami ingin mengira koordinat (x ', z') dari unjuran m 'pada satah.
kita dapat mengenali konfigurasi yang digunakan dalam teorem memintas. Mengenai skema di atas, kita tahu beberapa nilai: x, y dan d, antara lain. Kami mahu mengira x 'jadi kami menggunakan teorem memintas dan mendapatkan persamaan ini: x' = d / y * x.
Sekarang, jika anda melihat adegan yang sama dari sisi, anda mendapat skema yang sama, yang membolehkan anda mendapatkan nilai z 'terima kasih kepada z, y dan d: z' = d / y * z.
sekarang kita boleh menulis fungsi projek () menggunakan pandangan perspektif:
Fungsi ini boleh diuji dalam contoh langsung di bawah. Sekali lagi, anda boleh berinteraksi dengan kiub.
<span>var <span>Vertex</span> = function(x<span>, y, z</span>) { </span> <span>this.x = parseFloat(x); </span> <span>this.y = parseFloat(y); </span> <span>this.z = parseFloat(z); </span><span>}; </span>
Lihat Paparan Perspektif Pen 3D Pena oleh SitePoint (@SitePoint) pada Codepen.
Perkataan penutup
Juga, kami tidak bercakap mengenai tekstur. Di sini, semua bentuk kami berkongsi warna yang sama. Anda boleh mengubahnya dengan, sebagai contoh, menambah harta warna dalam objek anda, untuk mengetahui cara menariknya. Anda juga boleh memilih satu warna setiap muka tanpa mengubah banyak perkara. Anda juga boleh cuba memaparkan imej di muka. Walau bagaimanapun, ia lebih sukar dan terperinci bagaimana melakukan perkara sedemikian akan mengambil artikel keseluruhan.
perkara lain boleh diubah. Kami meletakkan kamera di asal ruang, tetapi anda boleh memindahkannya (perubahan asas akan diperlukan sebelum memproyeksikan simpang). Juga, simpang yang diletakkan di belakang kamera di sini ditarik, dan itu bukan perkara yang kita mahu. Pesawat kliping boleh membetulkannya (mudah difahami, kurang mudah dilaksanakan).
seperti yang anda lihat, enjin 3D yang kami bina di sini jauh untuk lengkap, dan ia juga tafsiran saya sendiri. Anda boleh menambah sentuhan anda sendiri dengan kelas lain: contohnya, tiga.js menggunakan kelas khusus untuk menguruskan kamera dan unjuran. Juga, kami menggunakan matematik asas untuk menyimpan koordinat, tetapi jika anda ingin membuat aplikasi yang lebih kompleks dan jika anda memerlukan, misalnya, untuk memutar banyak simpul semasa bingkai, anda tidak akan mempunyai pengalaman yang lancar. Untuk mengoptimumkannya, anda memerlukan beberapa matematik yang lebih kompleks: koordinat homogen (geometri proyektif) dan quaternions.
Jika anda mempunyai idea untuk penambahbaikan anda sendiri pada enjin, atau telah membina sesuatu yang sejuk berdasarkan kod ini, sila beritahu saya dalam komen di bawah!
Apakah perpustakaan terbaik untuk membina enjin 3D di JavaScript? Dua perpustakaan yang paling popular untuk membina enjin 3D di JavaScript. Kedua-dua perpustakaan menyediakan antara muka peringkat tinggi ke WebGL, menjadikannya lebih mudah untuk mewujudkan adegan 3D yang kompleks. Mereka juga menawarkan dokumentasi dan sokongan komuniti yang luas. Salah satu cara adalah untuk meminimumkan bilangan panggilan menarik dengan menggunakan teknik seperti instancing dan batching. Cara lain adalah untuk mengurangkan jumlah data yang dihantar ke GPU dengan menggunakan tekstur dan geometri termampat. Anda juga boleh mengoptimumkan shaders anda untuk prestasi yang lebih baik.
Atas ialah kandungan terperinci Membina enjin 3D dengan JavaScript. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!