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

使用JavaScript构建3D引擎

Joseph Gordon-Levitt
Joseph Gordon-Levitt原创
2025-02-18 11:45:10400浏览

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