使用 i3DML 设计交互式 3D 世界





5.00/5 (20投票s)
本文介绍了 i3DML 项目,并演示了 XML 和 JavaScript 如何帮助您构建交互式 3D 世界。
引言
为了简化 3D 应用程序的设计过程,我开发了一种新的基于 XML 的标记语言,就像 HTML 一样,但专门用于设计 3D 场景而非 2D 网页。在本文中,我将介绍 i3DML 项目,并演示 XML 和 JavaScript 如何帮助您用更少的代码构建交互式 3D 世界!
其核心思想是通过在一个纯文本 XML 文件中声明 3D 对象及其行为来创建 3D 场景。这使我们能够用更小的文件传输大量的 3D 图元和网格,因此也允许我们通过网络传输它们。它还允许开发人员在不具备 3D 计算机图形学高级技术知识的情况下构建 3D 应用程序。
在本文中,我们将回顾 i3DML 应用程序的基础知识和执行过程,然后,我们将使用它的一些基本功能实现一个简单的 i3DML 应用程序。请注意,阅读本文需要您对基础计算机图形学有一定的了解。
*运行浏览器应用程序的要求*
- MS Windows 7+
- .Net Framework 4.0+
- Xna 4.0 运行时组件
使用代码
在开始之前,让我们先看一下 i3DML 应用程序的执行过程。一个 i3DML 应用程序是一个简单的 Xml 文档,存储在后缀为 .i3DML 的文件中。该文件可以托管在本地存储中,也可以存储在网络上。客户端向服务器请求 i3DML 文件并下载托管的文件(托管文件可以动态创建!)。下一步,i3DML 解析器为我们解析文档。解析后,i3DML 引擎将运行应用程序,并应用应用程序元素、脚本和资源之间的关系。它还与图形设备交互,并在屏幕上渲染生成的图元。您可以在下图中看到 i3DML 应用程序的执行过程。(我们使用 Direct3D 作为渲染图元的图形 API)
i3DML 浏览器同时包含 i3DML 解析器和 i3DML 引擎,为浏览 i3DML 文件创建了一个灵活的环境。正如我所说,i3DML 是一种基于 XML 的语言,这意味着您可以使用一组标签和属性来设计您的世界。以下代码块是最简单的 i3DML 应用程序,您可以通过 i3DML 编辑器运行它(已附在软件包中)。
<World xmlns="http://www.i3dml.org/i3DML2014">
<World.Script>
log("Hello World!");
</World.Script>
<!-- Elements -->
</World>
上述代码没有任何可绘制的元素,所以您应该会看到一个空的黑色窗口,以及控制台窗口中显示的 "Hello World" 文本。
与所有 XML 文档一样,每个 i3DML 文档都有一个根元素。每个 i3DML 文档的根元素都是一个容器元素。在 i3DML 中,容器元素是一种不会在屏幕上渲染的元素,它仅用于包含(如其名)其他元素。<World>
标签是 i3DML 中最抽象的容器元素,它是每个 i3DML 应用程序的根元素,包含了我们希望在应用程序中使用的所有元素。您可以在 <World>
标签中设置最通用的渲染选项,并且您应该在 <World>
标签内声明应用程序的通用脚本和元素。正如您在上面的代码中所见,您应该将应用程序脚本放在 <World.Script>
标签内。我们将使用 JavaScript 语言为我们的应用程序编写脚本。
在下一节中,我们将使用 i3DML 提供的最简单的元素,用 i3DML 实现一个简单的项目。
用 i3DML 模拟太阳系!
让我们用 i3DML 构建一个更高级的世界!现在我们想用 i3DML 模拟太阳系,这是一个您可以用 i3DML 完成的简单项目。我们应该在 <World>
标签中定义一些球体作为我们的行星,并在 <World.Script>
标签中用几行代码模拟它们的公转和自转。(请注意,我们不打算构建一个高级的太阳系模拟器,行星之间的距离和它们的大小可能不是那么精确!)
第 1 步 - 一个广阔的太空!
很明显,我们必须在一个有大量星星的太空中绘制我们的行星。但我们不能为太空中的每一颗星星都创建一个独立的球体!在这种情况下,我们可以使用天空贴图技术。在这种方法中,您将所有视觉元素虚拟地投影到一个带纹理的球形区域上!所以山脉、云、太阳、星星和月亮并非真实存在,它们只是图片。这种技术为您提供了高水平的细节和良好的性能。所以我们应该在我们的世界中定义一个非常大的球形区域,并用球形天空贴图纹理进行贴图。在本例中,是一个布满星星的纹理。
<World xmlns="http://www.i3dml.org/i3DML2014">
<World.Script>
<!-- Planets Spinning -->
</World.Script>
<Sphere Density="50" Name="SkymapSphere"
Size="100000" Lighting="false" TextureSource=".\Stars.png"/>
</World>
在上面的代码中,我们在 <World>
标签中定义了一个球体作为我们的太空,并将其 TextureSource
属性设置为我们的天空贴图纹理的 URL。因为光源不应该影响到显示我们太空的球体,我们将其 Lighting
属性设置为 false
。(请注意,本文不涵盖 i3DML 中所有可用标签和属性的描述。完整的 i3DML 元素列表可以在 该项目的 GitHub 页面 上找到)
以上代码效果图
第 2 步 - 太阳
和上一步一样,您应该在您的世界中定义带有纹理源的太阳球体,但重要的是我们想将太阳用作光源,所以像之前的代码一样,我们应该将 Lighting 属性设置为 false,这样我们的光源就无法影响它。
<World xmlns="http://www.i3dml.org/i3DML2014"
CameraPosition="0,0,-2000" CameraRotation="0,0,0">
<World.Script>
<!-- Planets Spinning -->
</World.Script>
<PointLight Position="0"/> <!-- We also defined a point light source in the center of our sun sphere. -->
<Sphere Density="50" Name="SkymapSphere" IsArea="true"
Size="100000" Lighting="false" TextureSource=".\Stars.png"/>
<Sphere Position="0,0,0" Density="50" Name="SunSphere" Lighting="false"
Ambient="0.8" Size="1000" TextureSource=".\Sun.jpg"/>
</World>
完成!太阳已经准备好了,它将照耀我们将在下一节中添加的行星!
第 3 步 - 行星
行星的绘制略有不同,因为一些行星是多个网格的组合,例如土星和天王星,它们的球体周围有一个环,所以如果您改变其中一个网格的位置或旋转,您应该根据第一个变化对其他网格应用相应的更改。Place 是解决这个问题的方案。正如我在上一节中所说,Place 是容器元素,所以您可以将您的网格一起放在一个 Place 中,以创建您想要的模型,其中每个网格的位置和旋转都依赖于您的 Place 的位置和旋转。在这种情况下,您应该将每颗行星的球体和环放在一个单独的 Place 中。
<World xmlns="http://www.i3dml.org/i3DML2014"
CameraPosition="0,0,-2000" CameraRotation="0,0,0" >
<World.Script>
<!-- Planets Spinning -->
</World.Script>
<PointLight Position="0"/>
<Sphere Density="50" Name="SkymapSphere" IsArea="true"
Size="100000" Lighting="false" TextureSource=".\Stars.jpg"/>
<Sphere Position="0,0,0" Density="50" Name="SunSphere" Lighting="false"
Ambient="0.8" Size="1800" TextureSource=".\Sun.jpg"/>
<!-- Each place contains all of the meshes of its planet -->
<Place Name="Mercury" Position="1200,0,0">
<Sphere Density="50" Size="100"
TextureSource=".\Mercury.jpg"/>
</Place>
<Place Name="Venus" Position="1400,0,0">
<Sphere Density="50" Size="100"
TextureSource=".\Venus.jpg"/>
</Place>
<Place Name="Earth" Position="1600,0,0">
<Sphere Density="50" Size="100"
TextureSource=".\Earth.jpg"/>
</Place>
<Place Name="Mars" Position="1800,0,0">
<Sphere Density="50" Size="100"
TextureSource=".\Mars.jpg"/>
</Place>
<Place Name="Saturn" Position="2000,0,0">
<Sphere Density="50" Size="100"
TextureSource=".\Saturn.jpg"/>
</Place>
<Place Name="Jupiter" Position="2200,0,0">
<Sphere Density="50" Size="100"
TextureSource=".\Jupiter.jpg"/>
</Place>
<Place Name="Uranus" Position="2400,0,0">
<Sphere Density="50" Size="100"
TextureSource=".\Uranus.jpg"/>
</Place>
<Place Name="Neptune" Position="2600,0,0">
<Sphere Density="50" Size="100"
TextureSource=".\Neptune.jpg"/>
</Place>
</World>
第 4 步 - 建造行星环!(土星和天王星)
既然我们已经使用 Place 来容纳我们行星的网格,我们可以简单地为我们行星的 Place 添加一个环形网格!
在 i3DML 中,您可以通过将 <Torus>
标签的 TubeDensity
设置为“2”来构建一个环。
对于土星
<Place Name="Saturn" Position="2200,0,0" Rotation="-20,45,0">
<Sphere Density="50" Size="100"
TextureSource=".\Saturn.jpg"/>
<Torus TubeDensity="2" Density="50"
TextureSource=".\SaturnRing.jpg" Size="225,0,225,30"/>
</Place>
以及天王星
<Place Name="Uranus" Position="2400,0,0" Rotation="-80,45,0">
<Sphere Density="50" Size="100"
TextureSource=".\Uranus.jpg"/>
<Torus TubeDensity="2" Density="50"
TextureSource=".\UranusRing.jpg" Size="225,0,225,30"/>
</Place>
结果
第 5 步 - 让它动起来!
一切准备就绪!我们只需要添加一些脚本来实现动画。在 i3DML 应用程序中,通用脚本应存储在 World 标签的 Script
属性中。在编写脚本时,您需要访问 World 标签内的元素。在 <Script>
标签中有一个名为 getElementByName()
的内置函数,它会返回您希望通过其名称访问的元素。您可以通过设置元素的 Name
属性来为元素命名。
所以我们首先在代码中获取我们的元素
<World.Script>
var sun=getElementByName("SunSphere");
var mercury=getElementByName("Mercury");
var venus=getElementByName("Venus");
var earth=getElementByName("Earth");
var mars=getElementByName("Mars");
var saturn=getElementByName("Saturn");
var jupiter=getElementByName("Jupiter");
var uranus=getElementByName("Uranus");
var neptune=getElementByName("Neptune");
</World.Script>
现在我们想定义一些数组,为每个行星的运动提供所需的信息。我们定义一个名为 arr
的数组来表示行星元素,另一个名为 rotarr
的数组来表示行星围绕太阳的 Y 轴旋转,以及一个名为 rotspeedarr
的数组来保存每个行星的自转速度。
<World.Script>
// Getting the planets...
var arr=[mercury,venus,earth,mars,saturn,jupiter,uranus,neptune];
var rotarr=[0,0,0,0,0,0,0,0];
var rotspeedarr=[1.1,2.3,3.2,1.3,1.7,2.1,3.2,0.9];
</World.Script>
<World>
标签有一个名为 OnUpdate
的事件属性,它包含一个 JavaScript 语句,并在每一帧更新时执行它。我们应该在帧更新中进行旋转更改,所以我们可以声明一个名为 Update
的函数来执行旋转效果,并在 OnUpdate
属性中调用它。就像这样
<World xmlns="http://www.i3dml.org/i3DML2014">
CameraPosition="0,0,-2000" CameraRotation="0,0,0" OnUpdate="Update();">
以及在代码中
<World.Script>
// Initializations
function Update()
{
// This function will be called in every frame update.
}
</World.Script>
通过一些数学运算
<World.Script>
// Initializations
function Update()
{
sun.Rotation.Y+=0.05; // Rotate the sun
for(var i=0;i<arr.length;i++)
{
rotarr[i]+=rotspeedarr[i]/400; // Change the planet rotation over the sun
arr[i].Rotation.Y+=0.1; // Rotate the planet
var rad=Math.sqrt(Math.pow(arr[i].Position.X,2)
+Math.pow(arr[i].Position.Z,2)); // Calculating radius (Distance from sun)
arr[i].Position.X=Math.cos(rotarr[i])*rad; // Rotating over the sun
arr[i].Position.Z=Math.sin(rotarr[i])*rad; // Rotating over the sun
}
}
</World.Script>
恭喜!您已经用 i3DML 实现了一个简单的太阳系模拟器!
最终代码
<World xmlns="http://www.i3dml.org/i3DML2014"
CameraPosition="0,1000,-4000" CameraRotation="15,0,0" OnUpdate="Update();" >
<World.Script>
var sun=getElementByName("SunSphere");
var mercury=getElementByName("Mercury");
var venus=getElementByName("Venus");
var earth=getElementByName("Earth");
var mars=getElementByName("Mars");
var saturn=getElementByName("Saturn");
var jupiter=getElementByName("Jupiter");
var uranus=getElementByName("Uranus");
var neptune=getElementByName("Neptune");
var arr=[mercury,venus,earth,mars,saturn,jupiter,uranus,neptune];
var rotarr=[0,0,0,0,0,0,0,0];
var rotspeedarr=[1.1,2.3,3.2,1.3,1.7,2.1,3.2,0.9];
function Update()
{
sun.Rotation.Y+=0.05; // Rotate the sun
for(var i=0;i<arr.length;i++)
{
rotarr[i]+=rotspeedarr[i]/400; // Change the planet rotation over the sun
arr[i].Rotation.Y+=0.1; // Rotate the planet
var rad=Math.sqrt(Math.pow(arr[i].Position.X,2)
+Math.pow(arr[i].Position.Z,2)); // Calculating radius (Distance from sun)
arr[i].Position.X=Math.cos(rotarr[i])*rad; // Rotating over the sun
arr[i].Position.Z=Math.sin(rotarr[i])*rad; // Rotating over the sun
}
}
</World.Script>
<PointLight Position="0"/>
<Sphere Density="50" Name="SkymapSphere" IsArea="true"
Size="100000" Lighting="false" TextureSource=".\Stars.jpg"/>
<Sphere Position="0,0,0" Density="50" Name="SunSphere" Lighting="false"
Size="1800" TextureSource=".\Sun.jpg"/>
<!-- Each place contains all of the meshes of its planet -->
<Place Name="Mercury" Position="1200,0,0">
<Sphere Density="50" Size="100"
TextureSource=".\Mercury.jpg"/>
</Place>
<Place Name="Venus" Position="1400,0,0">
<Sphere Density="50" Size="100"
TextureSource=".\Venus.jpg"/>
</Place>
<Place Name="Earth" Position="1600,0,0">
<Sphere Density="50" Size="100"
TextureSource=".\Earth.jpg"/>
</Place>
<Place Name="Mars" Position="1800,0,0">
<Sphere Density="50" Size="100"
TextureSource=".\Mars.jpg"/>
</Place>
<Place Name="Saturn" Position="2000,0,0" Rotation="-15,45,10">
<Sphere Density="50" Size="100"
TextureSource=".\Saturn.jpg"/>
<Torus TubeDensity="2" Density="50"
TextureSource=".\SaturnRing.jpg" Size="225,0,225,30"/>
</Place>
<Place Name="Jupiter" Position="2200,0,0">
<Sphere Density="50" Size="100"
TextureSource=".\Jupiter.jpg"/>
</Place>
<Place Name="Uranus" Position="2400,0,0" Rotation="-85,-45,15">
<Sphere Density="50" Size="100"
TextureSource=".\Uranus.jpg"/>
<Torus TubeDensity="2" Density="50"
TextureSource=".\UranusRing.jpg" Size="225,0,225,30"/>
</Place>
<Place Name="Neptune" Position="2600,0,0">
<Sphere Density="50" Size="100"
TextureSource=".\Neptune.jpg"/>
</Place>
</World>
关注点
在 i3DML 中进行设计并不局限于本文内容,您可以用 i3DML 实现更多的东西!
历史
项目名称已更改为 i3DML。