ホームページ >ウェブフロントエンド >jsチュートリアル >JavaScriptで3Dエンジンを構築します

JavaScriptで3Dエンジンを構築します

Joseph Gordon-Levitt
Joseph Gordon-Levittオリジナル
2025-02-18 11:45:10414ブラウズ

JavaScriptで3Dエンジンを構築します

この記事は、Tim SeverienとSimon Codringtonによって査読されました。 SetePointコンテンツを最高にしてくれたSitePointのすべてのピアレビュアーに感謝します!Webページに画像やその他のフラットシェイプを表示するのは非常に簡単です。ただし、3Dジオメトリは2Dジオメトリよりも複雑であるため、3Dシェイプを表示すると、物事はそれほど簡単ではなくなります。そのためには、Webglや3.jsなどの専用のテクノロジーやライブラリを使用できます。 ただし、キューブのような基本的な形状を表示したい場合、これらのテクノロジーは必要ありません。さらに、彼らはあなたが彼らがどのように機能するか、そして私たちがフラットスクリーンに3D形状をどのように表示できるかを理解するのを助けません。

このチュートリアルの目的は、webglなしでWeb用のシンプルな3Dエンジンをどのように構築できるかを説明することです。まず、3Dシェイプを保存する方法を確認します。次に、これらの形状を2つの異なるビューで表示する方法を確認します。

キーテイクアウト

JavaScriptのみを利用して、WebGLなどの高度なライブラリを必要とせずにシンプルな3Dエンジンを作成でき、キューブなどの基本的な形状をレンダリングするのに適しています。 このエンジン内の3Dモデリングには、ポリヘドロンが不可欠です。このエンジンでは、2Dのポリゴンと同様に、平らな面を使用して複雑な形状が近似されます。

効率的なメモリ管理は、頂点を複製するのではなく、頂点への参照を保存することによって達成されます。これにより、各頂点が複数の面で共有されるため、メモリの使用量が大幅に削減されます。

エンジンは、変化を頂点に直接適用することにより、翻訳などの形状の基本的な変換をサポートします。これにより、参照システムにより関連面を自動的に更新します。

2種類の投影方法について説明します。深さを無視することにより3Dから2Dの変換を簡素化する正書法投影と、深さを説明し、より現実的なビューを提供する視点投影。
    レンダリングは、3D座標を2Dに投影し、キャンバスに描画する関数を介して処理されます。
  • 3Dシェイプの保存と変換
  • すべての形状はポリヘドロン
  • です
  • 仮想世界は、実際の世界とは1つの主要な方法で異なります。継続的なものはなく、すべてが離散的です。たとえば、画面に完璧な円を表示することはできません。多くのエッジを備えた通常のポリゴンを描くことでアプローチできます。エッジが多いほど、「完璧」が多くなります。 3Dでは、それは同じものであり、ポリゴンに相当する3D相当ですべての形状に近づく必要があります。すでにキューブのようなポリヘドロンである形状について話すのは驚くことではありませんが、球体のように他の形状を表示したいときは留意してください。

    JavaScriptで3Dエンジンを構築します

    多面体の保存

    多面体を保存する方法を推測するには、数学でそのようなことをどのように識別できるかを覚えておく必要があります。あなたは確かにあなたの学年の間にすでにいくつかの基本的な幾何学をしました。たとえば、正方形を識別するには、a、b、c、およびdでabcdと呼び、正方形の各角を構成する頂点を指します。

    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>

キューブクラスのコンストラクターは、指定された中心の位置から計算されたキューブの頂点を生成することから始まります。スキーマはより明確になるので、生成する8つの頂点の位置を参照してください。

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

次に、顔をリストします。各顔は正方形なので、各顔に4つの頂点を示す必要があります。ここで私は配列で顔を表現することを選びましたが、必要に応じて、そのための専用クラスを作成することができます。
<span>var cube = new Cube(new Vertex(0, 0, 0), 200);
</span>
顔を作成するとき、4つの頂点を使用します。それらはthis.vertices [i]オブジェクトに保存されているため、それらの位置を再度示す必要はありません。それは実用的ですが、私たちがそれをしたもう一つの理由があります。

デフォルトでは、JavaScriptは可能な限り最小のメモリの使用を試みます。それを達成するために、関数の引数として渡されたり、アレイに保存されたりするオブジェクトをコピーしません。私たちの場合、それは完璧な動作です。

JavaScriptで3Dエンジンを構築します実際、各頂点には3つの数値(座標)に加えて、追加する必要がある場合はいくつかの方法が含まれています。顔ごとに頂点のコピーを保存すると、多くのメモリを使用しますが、これは役に立ちません。ここで、私たちが持っているのは参照です。座標(およびその他の方法)は一度だけ保存されます。各頂点はコピーではなく参照を保存することにより、3つの異なる面で使用されているため、必要なメモリを3つ(多かれ少なかれ)!

三角形が必要ですか?

既に3D(たとえばBlenderなどのソフトウェア、またはWebGLのようなライブラリを使用して)を使用している場合、三角形を使用する必要があると聞いたことがあるかもしれません。ここでは、三角形を使用しないことを選択しました

この選択の背後にある理由は、この記事がトピックの紹介であり、キューブのような基本的な形状を表示することです。三角形を使用して正方形を表示することは、私たちの場合の他の何よりも複雑なものです。 ただし、より完全なレンダラーを構築する予定がある場合は、一般に、三角形が望ましいことを知る必要があります。これには2つの主な理由があります:

テクスチャ:顔に画像を表示するには、いくつかの数学的な理由で三角形が必要です;
  1. 奇妙な顔:3つの頂点は常に同じ平面にあります。ただし、同じ平面にない4番目の頂点を追加することができ、これらの4つの頂点に参加する顔を作成できます。そのような場合、それを描くためには、選択肢がありません。2つの三角形に分割する必要があります(紙で試してみてください!)。三角形を使用することで、コントロールを保持し、分割が発生する場所を選択します(ティムにリマインダーに感謝します!)。
  2. 多面体に作用します
コピーの代わりに参照を保存することには別の利点があります。多面体を変更したい場合、そのようなシステムを使用すると、必要な数の操作を3つだけ除算します。

理由を理解するには、もう一度数学のクラスを思い出しましょう。正方形を翻訳したいときは、実際に翻訳しません。実際、4つの頂点を翻訳し、翻訳に参加します。

ここで、同じことをします。顔に触れません。各頂点に必要な操作を適用し、完了です。顔が参照を使用すると、面の座標が自動的に更新されます。たとえば、以前に作成したキューブをどのように翻訳できるかを参照してください:

画像のレンダリング

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>

現在、3D座標を保存しています。ただし、画面は2D座標のみを表示できるため、3D座標を2Dの座標に変換する方法が必要です。これが数学の投影と呼ばれるものです。 3Dから2Dプロジェクションは、仮想カメラと呼ばれる新しいオブジェクトによって作成された抽象的な操作です。このカメラは3Dオブジェクトを取り、座標を2Dオブジェクトに変換して、画面に表示するレンダラーに送信します。ここでは、カメラが3Dスペースの原点に配置されていると仮定します(その座標は(0,0,0))。この記事の冒頭以来、X、Y、Zの3つの数字で表される座標について説明しました。しかし、座標を定義するには、基礎が必要です。Zは垂直座標ですか?上部または下部に行きますか?普遍的な答えも慣習もありません。事実は、あなたが望むものを選ぶことができるということです。留意しなければならない唯一のことは、3Dオブジェクトで行動する場合、式に応じて式が変わるため、一貫性がある必要があるということです。この記事では、上記のキューブのスキーマで見ることができる基礎を選びました:X x左から右へ、y、y y、y y、yはbotterからbottirまでz

今、私たちは何をすべきかを知っています:(x、y、z)ベースに座標があり、それらを表示するには、(x、z)ベースでそれらを座標に変換する必要があります:平面であるため、それらを表示することができます。

1つの投影だけではありません。さらに悪いことに、さまざまな投影が無限に存在します!この記事では、実際に最も使用されているものである2つの異なる種類の投影が表示されます。

シーンをレンダリングする方法

オブジェクトを投影する前に、それらを表示する関数を書きましょう。この関数は、レンダリングするオブジェクトをリストする配列、オブジェクトを表示するために使用する必要があるキャンバスのコンテキスト、および正しい場所でオブジェクトを描画するために必要なその他の詳細をパラメーターとして受け入れます。

配列には、レンダリングするいくつかのオブジェクトを含めることができます。これらのオブジェクトは、1つのことを尊重する必要があります。オブジェクトのすべての顔をリストする配列であるFacesという名前の公共プロパティを持つことです(以前に作成したキューブのように)。これらの顔は何でもかまいません(正方形、三角形、またはdodecagonでさえ、必要に応じて):頂点をリストする配列が必要です。

関数のコードを見て、それに続いて説明:

この関数は説明に値します。より正確には、このプロジェクト()機能とは何か、そしてこれらのDXおよびDYの引数は何であるかを説明する必要があります。残りは基本的にオブジェクトをリストしてから各顔を描くこと以外は何もありません。

その名前が示すように、プロジェクト()関数は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を維持できます。

プロジェクト()の正確なコンテンツは、次のセクションで表示されるものです。選択したプロジェクションのタイプに依存します。しかし、このタイプが何であれ、render()関数は今のように保持できます。

飛行機に座標があると、キャンバスにそれらを表示できます。それが私たちがしていることです...少しのトリックで:プロジェクト()関数によって返された実際の座標を実際に描画しません。

実際、プロジェクト()関数は仮想2D平面で座標を返しますが、3Dスペースで定義したものと同じ起源を持っています。ただし、起源がキャンバスの中心にあることを望んでいるため、座標を翻訳するのはそのためです。頂点(0,0)はキャンバスの中心にありませんが、(0 dx、0 dy)は、賢明にdxとdyを選択してください。 (DX、DY)がキャンバスの中心にあることを望んでいるように、私たちは本当に選択肢がなく、DX = canvas.width / 2およびdy = canvas.height / 2を定義します。

最後に、最後の詳細:なぜyを使用していて、直接yを使用しているのですか?答えは私たちの選択です。Z軸は上部に向けられています。次に、私たちのシーンでは、正のz座標を持つ頂点が上がります。ただし、キャンバスでは、y軸は底に向けられます。正のy座標を持つ頂点が下に移動します。だからこそ、キャンバスのY座標をキャンバス上のZ座標の逆の逆として定義する必要があります。

render()関数が明確になったので、project()を見る時が来ました。

正書法のビュー

正書法の投影から始めましょう。最も簡単なので、私たちが何をするかを理解するのに最適です。

3つの座標があり、2つしか必要です。そのような場合に最も簡単なことは何ですか?座標の1つを削除します。そして、それが私たちが正書法の見方で行うことです。深さを表す座標を削除します: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正書法ビューを参照してください。

時々、類似点を維持するという利点があるため、正書法の見解が私たちが望むものです。しかし、それは最も自然な見方ではありません。私たちの目はそのように見えません。そのため、2番目の予測が表示されます。

パースペクティブビュー

いくつかの計算を行う必要があるため、パースペクティブビューは正書法のものよりも少し複雑です。ただし、これらの計算はそれほど複雑ではなく、1つのことを知る必要があります。インターセプト定理の使用方法。

理由を理解するには、正書法の見解を表すスキーマを見てみましょう。私たちは直交する方法で飛行機にポイントを投影しました。

しかし、実生活では、私たちの目は次のスキーマに似ています。

基本的に、2つのステップがあります

  1. 元の頂点とカメラの起源に加わります;
  2. 投影は、この線と平面の交差点です。

正書法の見解に反して、ここの飛行機の正確な位置が重要です。カメラから遠く離れた飛行機を配置すると、それを近くに置く場合と同じ効果が得られません。ここでは、カメラから距離dに配置します。

3D空間の頂点m(x、y、z)から始まると、平面上の投影m 'の座標(x'、z ')を計算したい。

JavaScriptで3Dエンジンを構築しますこれらの座標を計算する方法を推測するために、別の視点を取り、上から同じスキーマを見てみましょう。

インターセプト定理で使用される構成を認識できます。上記のスキーマでは、とりわけ、x、y、dなどのいくつかの値がわかります。 x 'を計算したいので、インターセプト定理を適用して、この方程式を取得します:x' = d / y * x。JavaScriptで3Dエンジンを構築します

さあ、同じシーンを横から見ると、同様のスキーマが得られ、z、y、d:z '= d / y * zのおかげでz'の値を取得できます。 >

パースペクティブビューを使用してプロジェクト()関数を書くことができます:

この関数は、以下のライブ例でテストできます。繰り返しになりますが、キューブと対話できます。

codepenのSitePoint(@sitepoint)によるペン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>
単語を閉じます

(非常に基本的な)3Dエンジンは、必要な3D形状を表示する準備ができました。それを強化するためにできることがいくつかあります。たとえば、私たちは形のすべての顔、後ろの顔さえも見ます。それらを隠すために、バックフェイスのcullingを実装できます。

また、テクスチャについては話しませんでした。ここで、私たちのすべての形状は同じ色を共有しています。たとえば、オブジェクトにカラープロパティを追加して、それらを描く方法を知ることで変更できます。多くのことを変えることなく、顔ごとに1色を選択することもできます。また、顔に画像を表示することもできます。ただし、そのようなことを行う方法を詳細に説明することはより困難であり、記事全体を撮るでしょう。

他のものを変更できます。カメラをスペースの原点に配置しましたが、移動できます(頂点を投影する前に、基礎の変更が必要になります)。また、カメラの後ろに配置された頂点がここに描かれていますが、それは私たちが望むものではありません。クリッピングプレーンはそれを修正できます(理解しやすく、実装しやすい)。

ここで構築した3Dエンジンは、完全になるまで遠くにあり、それは私自身の解釈でもあります。他のクラスに独自のタッチを追加できます。たとえば、Three.jsは専用のクラスを使用してカメラとプロジェクションを管理します。また、基本的な数学を使用して座標を保存しましたが、より複雑なアプリケーションを作成したい場合、たとえばフレーム中に多くの頂点を回転させる必要がある場合は、スムーズな体験がありません。それを最適化するには、より複雑な数学が必要になります:均質座標(射影幾何学)とQuaternions。

エンジンの独自の改善のアイデアがある場合、またはこのコードに基づいてクールなものを構築した場合は、以下のコメントでお知らせください!

JavaScriptで3Dエンジンの構築に関するよくある質問(FAQ) JavaScriptで3Dエンジンを構築するための前提条件は何ですか?

JavaScriptで3Dエンジンを構築するには、JavaScriptとその概念を確実に理解する必要があります。 HTMLとCSSに精通していることも有益です。ベクトル、マトリックス、四項を含む3D数学の知識が重要です。パイプライン、シェーダー、テクスチャマッピングのレンダリングなど、コンピューターグラフィックの基本を理解することも役立ちます。 3D数学を学ぶためにオンラインで利用可能ないくつかのリソース。 Khan AcademyのようなWebサイトは、3D数学を理解するための基本的な線形代数とベクター計算に関するコースを提供しています。 「グラフィックスとゲーム開発のための3D Math Primer」などの本も役立ちます。 JavaScriptで3Dエンジンを構築するための最も人気のあるライブラリの2つ。どちらのライブラリもWebGLに高レベルのインターフェイスを提供し、複雑な3Dシーンを簡単に作成できるようにします。また、広範なドキュメントとコミュニティのサポートを提供しています。

JavaScript 3Dエンジンを最適化してパフォーマンスを向上させるにはどうすればよいですか? 1つの方法は、インスタンスやバッチなどの手法を使用して、抽選コールの数を最小限に抑えることです。別の方法は、圧縮されたテクスチャとジオメトリを使用してGPUに送信されるデータの量を減らすことです。パフォーマンスを向上させるためにシェーダーを最適化することもできます。 。ブラウザが提供する「AddEventListener」メソッドを使用して、これらのイベントをリッスンできます。その後、イベントデータを使用して、3Dシーンのカメラまたはその他の要素を制御できます。

javaScript 3Dエンジンに照明を追加するにはどうすればよいですか?

JavaScript 3Dエンジンに照明を追加するには、光源の作成とシェーディングモデルの実装が含まれます。光源は、方向性、ポイント、またはスポットライトにすることができます。表面が光にどのように反応するかを決定するシェーディングモデルは、単純な(ランバートシェーディングなど)または複雑な(物理ベースのレンダリングなど)。

JavaScript 3Dエンジンにテクスチャを追加するには、画像ファイルのロード、テクスチャオブジェクトの作成、ジオメトリへのマッピングが含まれます。ブラウザが提供する「画像」オブジェクトを使用して画像をロードできます。次に、WebGLが提供する「GL.CreatEtexture」メソッドを使用してテクスチャオブジェクトを作成できます。時間の経過に伴うオブジェクトのプロパティ。ブラウザが提供する「requestAnimationFrame」メソッドを使用して、一貫したレートでオブジェクトのプロパティを更新するループを作成できます。その後、補間技術を使用して、異なる状態間でスムーズに移行できます。 。これは、境界ボックス間のオーバーラップをチェックするのと同じくらい簡単です。複雑なジオメトリ間の交差点をチェックするのと同じくらい複雑です。衝突が検出されたら、衝突オブジェクトのプロパティを変更することで応答できます。 3Dグラフィックスの複雑さ。ただし、WebGLインスペクターやChrome Devtoolsなどのツールは非常に役立ちます。これらのツールを使用すると、WebGLコンテキストの状態を検査し、バッファーとテクスチャの内容を表示し、シェーダーを踏むことができます。

以上がJavaScriptで3Dエンジンを構築しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。