65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (20投票s)

2013年7月30日

CPOL

7分钟阅读

viewsIcon

47138

downloadIcon

843

本文介绍了 i3DML 项目,并演示了 XML 和 JavaScript 如何帮助您构建交互式 3D 世界。

A simple Earth model - Created using i3DML

引言

为了简化 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 relations

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 页面 上找到)

以上代码效果图

Step 1 Result - The Space

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

Step 2 Result -  The Sun

完成!太阳已经准备好了,它将照耀我们将在下一节中添加的行星!

第 3 步 - 行星

行星的绘制略有不同,因为一些行星是多个网格的组合,例如土星和天王星,它们的球体周围有一个环,所以如果您改变其中一个网格的位置或旋转,您应该根据第一个变化对其他网格应用相应的更改。Place 是解决这个问题的方案。正如我在上一节中所说,Place 是容器元素,所以您可以将您的网格一起放在一个 Place 中,以创建您想要的模型,其中每个网格的位置和旋转都依赖于您的 Place 的位置和旋转。在这种情况下,您应该将每颗行星的球体和环放在一个单独的 Place 中。

Step 3 Result - Planets

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

结果

Step 4 Result - Rings of Saturn and Uranus

第 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 实现了一个简单的太阳系模拟器!

Step 5 Result - Planets movements

最终代码

<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 实现更多的东西!

在 GitHub 上查看该项目!

i3DML 官方网站

历史

项目名称已更改为 i3DML

© . All rights reserved.