首頁 >web前端 >js教程 >使用JavaScript構建3D引擎

使用JavaScript構建3D引擎

Joseph Gordon-Levitt
Joseph Gordon-Levitt原創
2025-02-18 11:45:10414瀏覽

使用JavaScript構建3D引擎

>本文由Tim Severien和Simon Codrington進行了同行評審。感謝SitePoint所有的同行評審員製作SitePoint內容的最佳功能! 在網頁中顯示圖像和其他平面形狀非常容易。但是,在顯示3D形狀時,事物變得不那麼容易了,因為3D幾何比2D幾何形狀更為複雜。為此,您可以使用專用的技術和庫,例如WebGl和Thrive.js。 但是,如果您只想顯示一些基本形狀,例如立方體,則這些技術無需這些技術。此外,它們將無法幫助您了解它們的工作方式,以及我們如何在平面上顯示3D形狀。

本教程的目的是解釋如何在沒有WebGL的情況下為Web構建簡單的3D引擎。我們將首先看到如何存儲3D形狀。然後,我們將在兩個不同的視圖中看到如何顯示這些形狀。

>

鑰匙要點

>僅利用JavaScript,就可以創建一個簡單的3D引擎,而無需諸如WebGl之類的高級庫,使其適合呈現基本形狀,例如立方體。 在該發動機中,多面體在3D建模中至關重要,在該發動機中,使用扁平面近似複雜的形狀,類似於2D中的多邊形。 通過存儲對頂點而不是複制它們來實現有效的內存管理,從而大大降低了內存使用情況,因為每個頂點都在多個面上共享。

>通過將更改直接應用於頂點,引擎支持形狀的基本轉換,例如翻譯,該更改會自動更新參考系統所致的相關面。 討論了兩種類型的投影方法:拼字投影,通過忽略深度和透視投影簡化了3D到2D轉換,該轉換是深度的,並提供了更現實的視圖。

>
    渲染是通過將3D坐標的函數處理到2D並在畫布上繪製的函數,該方法使用確保顯示屏上形狀的正確定位和方向的方法。
  • 存儲和轉換3D形狀
  • 所有形狀均為多面體
  • 虛擬世界與真正的一個主要方式不同:沒有什麼是連續的,一切都是離散的。例如,您無法在屏幕上顯示完美的圓圈。您可以通過繪製帶有很多邊緣的常規多邊形來對其進行處理:您的邊緣越多,您的圓圈就越“完美”。
  • 。 在3D中,
  • 是同一件事,每個形狀都必須使用相當於多邊形的3D接近:多面體(一個3D形狀,我們只找到平坦的臉部螞蟻而不是彎曲的邊)。當我們談論已經是多面體的形狀時,這並不奇怪,但是當我們想顯示其他形狀(例如球形)時,這是要牢記的。

    使用JavaScript構建3D引擎

    存儲多面體

    猜猜如何存儲多面體,我們必須記住如何在數學中識別這種東西。在您的學年期間,您肯定已經做了一些基本的幾何形狀。例如,要識別一個正方形,您將其稱為ABCD,用A,B,C和D稱為構成正方形每個角落的頂點。 對於我們的3D發動機,

    將是相同的。我們將首先存儲形狀的每個頂點。然後,此形狀將列出其面孔,每個面都會列出其頂點。 >

    為代表頂點,我們需要正確的結構。在這裡,我們創建一個以存儲頂點的坐標的類

    現在可以像任何其他對像一樣創建頂點:

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

    接下來,我們創建一個代表多面體的類。讓我們以立方體為例。班級的定義在下面,並在此之後進行解釋。

    >
    <span>var A = new Vertex(10, 20, 0.5);
    </span>

    使用此類,我們可以通過指示其中心及其邊緣長度來創建一個虛擬立方體。

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

    >立方體類的構造函數首先從指示中心的位置生成立方體的頂點。模式將更清晰,因此請參閱下面我們生成的八個頂點的位置:>

    <span>var cube = new Cube(new Vertex(0, 0, 0), 200);
    </span>

    然後,我們列出了面孔。每張臉是一個正方形,因此我們需要為每個臉部指出四個頂點。在這裡,我選擇代錶帶有數組的面孔,但是,如果您需要的話,您可以為此創建一個專門的類。

    >

    創建面部時,我們會使用四個頂點。我們不需要再次表示它們的位置,因為它們存儲在the.vertices [i]對像中。這是實用的,但是還有另一個原因。 使用JavaScript構建3D引擎> 默認情況下,JavaScript試圖使用最少的內存數量。為了實現這一目標,它不會復制通過函數參數傳遞的對象,甚至存儲在數組中。就我們的情況而言,這是完美的行為。

    實際上,每個頂點包含三個數字(它們的坐標),以及如果需要添加幾種方法。如果對於每張臉,我們存儲了頂點的副本,我們將使用很多內存,這是沒有用的。在這裡,我們只有參考文獻:坐標(和其他方法)一次存儲一次,只有一次。由於每個頂點由三個不同的面都使用,而不是存儲參考而不是副本,因此我們將所需的內存除以三個(或多或少)!

    >!

    我們需要三角形嗎?

    如果您已經玩過3D(例如,使用Blender之類的軟件,或WebGl等庫),也許您聽說我們應該使用三角形。在這裡,我選擇不使用三角形。

    >

    這個選擇背後的原因是本文是對該主題的介紹,我們將顯示類似立方體的基本形狀。在我們的情況下,使用三角形展示正方形比其他任何事物都更像是並發症。

    > 但是,如果您打算構建一個更完整的渲染器,那麼通常需要知道,三角形是首選的。這樣的主要原因有兩個:

      紋理:要在臉上顯示圖像,我們需要三角形,出於某些數學原因;
    1. 奇怪的面:三個頂點始終在同一平面。但是,您可以添加不在同一平面中的第四個頂點,並且可以創建一個連接這四個頂點的臉。在這種情況下,要繪製它,我們別無選擇:我們必須將其分為兩個三角形(只需嘗試一張紙!)。通過使用三角形,您可以保留控件,然後選擇拆分的位置(謝謝提醒!)。
    2. 作用於多面體

    存儲參考而不是副本還有另一個優勢。當我們要修改多面體時,使用這樣的系統還將將所需的操作數除以三。

    了解為什麼,讓我們再次回憶起我們的數學課。當您想翻譯正方形時,您並沒有真正翻譯它。實際上,您翻譯了這四個頂點,然後加入翻譯。

    >

    >在這裡,我們會做同樣的事情:我們不會碰到面孔。我們在每個頂點上應用所需的操作,我們完成了。當面部使用參考時,面部的坐標會自動更新。例如,查看我們如何翻譯我們先前創建的立方體:

    渲染圖像

    <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>
    我們知道如何存儲3D對像以及如何對它們作用。現在是時候看看如何查看它們了!但是,首先,我們需要該理論的少量背景,以了解我們將要做什麼。

    投影

    >目前,我們存儲3D坐標。但是,屏幕只能顯示2D坐標,因此我們需要一種將3D坐標轉換為2D的方法:這就是我們所謂的數學投影。 3D到2D投影是由稱為虛擬相機的新對象進行的抽像操作。該攝像機將3D對象帶入2D對象,並將其發送到渲染器,以將其顯示在屏幕上。我們將在這裡假設我們的相機位於3D空間的起源(因此其坐標為(0,0,0))。自本文開頭以來,我們已經討論了坐標,由三個數字表示:x,y和z。但是要定義坐標,我們需要一個基礎:z是垂直坐標嗎?它是到頂部還是底部?沒有通用的答案,也沒有慣例,因為您可以選擇自己想要的任何東西。您唯一要記住的是,當您對3D對象採取行動時,您必須保持一致,因為公式會根據它而改變。在本文中,我選擇了您可以在上面的立方體模式中看到的基礎:x從左至右,y從後到正面,從下到頂部。 >

    >

    >現在,我們知道該怎麼做:我們在(x,y,z)基礎上有坐標,並且要顯示它們,我們需要以(x,z)為基礎將它們轉換為坐標:因為它是平面,我們將能夠顯示它們。

    不僅有一個投影。更糟糕的是,存在無限數量的不同預測!在本文中,我們將看到兩種不同類型的投影,在實踐中是最常用的投影。

    如何渲染我們的場景

    >在投影我們的對象之前,讓我們編寫顯示它們的功能。此函數接受為參數,列出了渲染對象的數組,必須用於顯示對象的畫布的上下文,以及在正確位置繪製對象所需的其他詳細信息。

    >

    >數組可以包含幾個以渲染的對象。這些對象必須尊重一件事:擁有一個名為“面孔”的公共屬性,該屬性是一個數組列出對象的所有面(如我們先前創建的Cube)。這些面孔可以是任何東西(如果需要的話,正方形,三角形,甚至是十二桿):它們只需要列出其頂點的數組。

    讓我們看一下功能的代碼,然後說明:

    這個功能值得一些解釋。更確切地說,我們需要解釋該項目()函數是什麼,這些DX和DY參數是什麼。其餘的基本上除了列出對象,然後繪製每個臉。

    >其名稱所建議的是,Project()函數在這裡將3D坐標轉換為2D。它接受3D空間中的頂點,並在2D平面中返回一個頂點,我們可以在下面定義。
    <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>

    >而不是命名坐標x和z我在這裡選擇將z坐標命名為y,而是要維護我們經常在2D幾何形狀中找到的經典慣例,但是如果您喜歡的話,可以保留z。

    project()的確切內容是我們將在下一部分中看到的:這取決於您選擇的投影類型。但是無論是什麼類型,渲染函數都可以按現在保持。

    >

    >一旦我們在飛機上有坐標,我們就可以在畫布上顯示它們,這就是我們要做的……有一個小技巧:我們實際上並沒有真正繪製project()函數返回的實際坐標。
    <span>var A = new Vertex(10, 20, 0.5);
    </span>
    >實際上,project()函數返回虛擬2D平面上的坐標,但具有相同的原點,而不是我們為3D空間定義的坐標。但是,我們希望原點位於畫布的中心,這就是為什麼我們翻譯坐標的原因:頂點(0,0)不在畫布的中心,但是(0 dx,0 dy)是,如果我們是選擇DX並明智地選擇DY 。正如我們想要的(DX,DY)位於畫布的中心一樣,我們實際上沒有選擇,我們定義dx = canvas.width / 2和dy = canvas.height / 2.

    最後,最後一個細節:為什麼我們使用-y而不是直接使用y?答案是我們選擇的基礎:Z軸針對頂部。然後,在我們的場景中,帶有正面Z坐標的Vertice將上升。但是,在畫布上,Y軸定向到底部:帶正Y坐標的佛特(Vertice)將向下移動。這就是為什麼我們需要在畫布上定義畫布的y坐標為我們場景的z坐標的逆轉。

    >現在呈渲染函數很明顯,是時候查看project()。

    正讀視圖

    >讓我們從拼字圖投影開始。因為這是最簡單的,所以了解我們將要做什麼。

    >

    我們有三個坐標,我們只需要兩個。在這種情況下,最簡單的事情是什麼?刪除其中一種坐標。這就是我們在拼字法中所做的。我們將刪除表示深度的坐標:y坐標。

    >您現在可以測試自本文開頭以來我們編寫的所有代碼:它有效!恭喜,您剛剛在平面上顯示了一個3D對象!

    >
    <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>
    此功能在下面的實時示例中實現,您可以通過與鼠標旋轉來與立方體進行交互。

    請參見codepen上的sitepoint(@sitepoint)的筆3D拼字圖。

    有時,我們想要的是拼字圖,因為它具有保留相似之處的優勢。但是,這並不是最自然的觀點:我們的眼睛不像那樣。這就是為什麼我們會看到第二個投影:透視視圖。

    >

    透視圖

    透視視圖比拼字法更複雜,因為我們需要進行一些計算。但是,這些計算並不是那麼複雜,您只需要知道一件事:如何使用攔截定理。

    了解為什麼,讓我們看一個代表拼字圖視圖的模式。我們以正交方式將積分投射到飛機上。

    >

    ,但是,在現實生活中,我們的眼睛更像以下模式。

    >

    使用JavaScript構建3D引擎

    基本上,我們有兩個步驟:>

    1. 我們加入原始頂點和相機的起源;
    2. >
    3. 投影是該線與平面之間的交點。
    >與拼字法視圖相反,這裡的飛機的確切位置很重要:如果將飛機放在遠離相機的位置,則不會獲得與將其放置在接近的情況下相同的效果。在這裡,我們將其放在距相機的距離D。

    >

    從3D空間中的頂點m(x,y,z)開始,我們想計算平面上投影m'的坐標(x',z')。

    使用JavaScript構建3D引擎>猜測我們將如何計算這些坐標,讓我們採取另一個觀點並查看與上面相同的模式,但從頂部看。

    使用JavaScript構建3D引擎>我們可以識別攔截定理中使用的配置。在上面的架構上,我們知道一些值:x,y和d等。我們要計算X',以便應用截距定理並獲得此方程:x'= d / y * x。

    現在,如果您從側面看同一場景,則獲得了類似的模式,允許您獲得Z'的值,這要歸功於Z,Y和D:Z'= D / Y *Z。

    我們現在可以使用透視視圖編寫project()函數:>

    可以在下面的實時示例中測試此功能。再一次,您可以與Cube進行交互。

    >
    <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>
    請參閱codepen上的sitepoint(@sitepoint)的筆3D透視圖。

    閉幕詞

    我們(非常基本的)3D引擎現在可以顯示我們想要的任何3D形狀。您可以做一些事情來增強它。例如,我們看到形狀的每個臉,甚至是背面的臉。要隱藏它們,您可以實現後面扣。

    >

    >另外,我們沒有談論紋理。在這裡,我們所有的形狀都具有相同的顏色。您可以通過例如在對像中添加顏色屬性來更改它,以了解如何繪製它們。您甚至可以選擇每張臉部的一種顏色,而無需更改很多事情。您也可以嘗試在臉上顯示圖像。但是,更加困難,並且詳細說明如何做這樣的事情將花費整篇文章。

    其他事情可以更改。我們將相機放置在空間的原點,但是您可以移動它(在投射頂點之前需要更改基礎)。另外,放置在相機後面的頂點在這裡繪製,這不是我們想要的。剪裁平面可以解決這個問題(易於理解,易於實現)。

    >如您所見,我們在這裡構建的3D引擎還遠遠無法完成,這也是我自己的解釋。您可以在其他類中添加自己的觸摸:例如,三分。 JS使用專用類來管理相機和投影。另外,我們使用基本數學來存儲坐標,但是如果您想創建一個更複雜的應用程序,並且如果需要,例如,在框架期間旋轉許多頂點,您將沒有平穩的體驗。為了優化它,您將需要一些更複雜的數學:均勻的坐標(投射幾何)和四元素。

    >如果您對引擎的改進有想法,或者根據此代碼建立了一些酷的東西,請在下面的評論中告訴我!

    >!

    >在JavaScript中構建3D引擎的常見問題(常見問題解答)

    >在JavaScript中構建3D引擎的先決條件是什麼?熟悉HTML和CSS也是有益的。 3D數學的知識,包括媒介,矩陣和四元素,至關重要。了解計算機圖形的基礎知識,例如渲染管道,著色器和紋理映射,也將很有幫助。

    我如何開始學習3D數學用於JavaScript 3D引擎開發?在線可用的幾種資源可以學習3D數學。像汗學院這樣的網站提供了有關線性代數和矢量微積分的課程,這些課程對於理解3D數學至關重要。諸如“圖形和遊戲開發的3D數學底漆”之類的書也可能會有所幫助。

    >在JavaScript中構建3D引擎的最佳圖書館是什麼?在JavaScript中構建3D引擎的兩個最受歡迎的庫。這兩個庫都為WebGL提供了高級接口,從而更容易創建複雜的3D場景。他們還提供了廣泛的文檔和社區支持。

    >如何優化我的JavaScript 3D引擎以提高性能?

    >

    >有幾種方法可以優化您的3D引擎以提高性能。一種方法是通過使用諸如實例和批處理之類的技術來最大程度地減少呼叫的數量。另一種方法是使用壓縮紋理和幾何形狀減少發送到GPU的數據量。您還可以優化著色器以獲得更好的性能。

    >如何處理JavaScript 3D引擎中的用戶輸入?

    >在JavaScript 3D引擎中處理用戶輸入通常涉及聆聽鍵盤和鼠標事件。您可以使用瀏覽器提供的“ AddEventListener”方法來聆聽這些事件。然後,您可以使用事件數據來控制3D場景中的相機或其他元素。

    >如何將照明添加到我的JavaScript 3D引擎?

    >向JavaScript 3D引擎添加照明涉及創建光源並實現陰影模型。光源可以是定向,點或聚光燈。確定表面如何響應光的陰影模型可以很簡單(例如蘭伯特陰影)或複雜的(例如基於物理的渲染)。

    >如何將紋理添加到我的Javascript 3D Engine?

    >將紋理添加到JavaScript 3D引擎中涉及加載圖像文件,創建紋理對象並將它們映射到幾何形狀上。您可以使用瀏覽器提供的“圖像”對象來加載圖像。然後,您可以使用WebGl提供的“ gl.CreateTexture”方法創建紋理對象。

    >如何將動畫添加到我的javascript 3D引擎?

    >隨著時間的推移對象的屬性。您可以使用瀏覽器提供的“ requestAnimationFrame”方法來創建一個循環,該循環以一致的速率更新對象的屬性。然後,您可以使用插值技術在不同狀態之間平穩過渡。

    >如何處理JavaScript 3D引擎中的碰撞?

    處理JavaScript 3D引擎中的碰撞通常涉及實現碰撞檢測算法檢測。這可以像檢查邊界框之間的重疊一樣簡單,也可以像檢查復雜幾何形狀之間的相交一樣複雜。一旦檢測到碰撞,您就可以通過更改碰撞對象的屬性來做出響應。

    如何調試我的javascript 3D引擎?

    >調試JavaScript 3D引擎可能會很具有挑戰性3D圖形的複雜性。但是,WebGL檢查員和Chrome DevTools之類的工具可能非常有幫助。這些工具允許您檢查WebGL上下文的狀態,查看緩衝區和紋理的內容,並跨過著色器。

以上是使用JavaScript構建3D引擎的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn