《异度神剑X》无缝地图的工作流程
演讲者: 稲葉道彦 @ 株式会社モノリスソフト (Monolith Software Inc.)
译者: SakuraNeko
原文: ゼノブレイドクロスのシームレスマップのワークフロー
《Xenoblade X》是Monolith Soft 开发,任天堂发行的一款专门针对 WiiU 的无缝开放世界角色扮演游戏 。
游戏以广阔的场地作为冒险的舞台。
你可以在可见的地形上移动,骑着一个叫 “娃娃”的机器人,在场地上自由移动。
这种移动的自由是通过在地图上的各个地点之间无缝切换而实现的,没有中间的地图跳转。
对于这种无缝切换方法,有必要开发一个无缝地图功能。
这个演讲的内容将是关于我们如何为《Xenoblade X》创建无缝地图。
我们将解释《Xenoblade X》的无缝地图的工作流程,数据是如何划分的,以及它在实际设备端是如何表现的。
我们希望能与你分享我们的方法。
一、地图的构成
" srcset="/img/loading.gif
首先,我们先来介绍一下《Xenoblade X》的基本地图结构。
如果你已经听过我关于绘画的讲座,就当做是一次总结吧。
《Xenoblade X》的大世界系统由基础地貌(Basemap)、实例地图(Instancemap)和草地地图(Grassmap)三部分组成。
" srcset="/img/loading.gif
基础地貌(Basemap)由地形结构数据构成,不包含细致的地貌等特征。
" srcset="/img/loading.gif
实例地图(Instancemap)是将模型与坐标分开的数据。
地图构成了上部物体,用于树木、岩石等。
关于模型,LOD可以单独设置。
" srcset="/img/loading.gif
草地地图(Grassmap)是一个用于显示大量模型的系统。
顾名思义,它是用来种草的。
该工具使用专用的碰撞数据,在实际机器上进行放置,并根据放置的结果显示模型。
也可以在工具中进行放置计算,将结果存储为数据,并使用放置数据来绘制模型。
二、基本结构
" srcset="/img/loading.gif
接下来将解释无缝地图的基本结构。
" srcset="/img/loading.gif
它由一个无缝地图中的两个数据组成。
一个是资产数据。
这是一个巨大的文件,由1.6G的数据组成。
这包含了绘图所需的数据,如顶点和纹理数据。
当加载一个资产文件时,不是以文件名,而是以偏移量加载。即要加载文件的位置,从这个位置指定并加载要加载的大小。
另一个是头文件,大小为1.5M,是构建一个无缝地图所需的最小数据。
当无缝地图启动时,它总是存在于内存中。
" srcset="/img/loading.gif
从内存中分配192M作为数据区,绘图所需的资产数据被加载并存储在这个数据区。
16M用作程序区,用于绘制命令缓冲区、无缝地图工作。
" srcset="/img/loading.gif
如何管理下列地图数据。
在《Xenoblade X》中,管理是以称为区域的单位进行的。
一个区域只是一个图片周围环境发生明显变化的区域。
这些图像的位置是完全不同的地图。
" srcset="/img/loading.gif
基于区域的方法的原因是,它对设计师来说更容易管理。
易于管理的原因是,设计者在逐个区域的基础上调整模型、位置等。
地图数据是在MAYA中管理的。
" srcset="/img/loading.gif
然而,例如地图模型是在不同的MAYA中对每个模型进行管理,而不是按区域管理。
实例图模型是以这种方式管理的,因为它被用于各个领域。
这意味着,如果你为某个特定的实例地图更新模型,所有的区域都会反映这个模型。
注意,只有坐标数据是由实例地图按区域管理的。
" srcset="/img/loading.gif
创建无缝地图的第一步就是要从MAYA输出一个中间文件
这将根据规则在一个指定的文件夹中输出。
在所有地区的中间文件输出完成后,创建无缝地图的工具被激活,无缝地图就完成了。
从MAYA到中间文件的转换大约需要两个小时,而从中间文件的转换大约需要一个小时。
中间文件是用一个内部开发的工具输出的,该工具可以自动转换所有的文件。
" srcset="/img/loading.gif
首先,无缝地图控制真的有必要吗?而且即使你这样做了,也要决定你应该走多远。
关于实际的《Xenoblade X》地图,我们在图片上看到在存在脸部图标的坐标处所需的模型数量。
" srcset="/img/loading.gif
在图像位置,有三个区域模型。
左边的图片是一个叫做原始荒野的模型。
中间的图片是一个名为NLA的模型。
在右边图片是“遗忘峡谷”的模型,这个模型有三个区域。
" srcset="/img/loading.gif
如果这三个模型没有被无缝映射,并且都被载入了,它的容量会达到550M。
数据区域为192米,所以从这一点可以看出,创建一个无缝的地图是非常必要的,有必要开发一个机制,只彻底加载必要的数据。
三、基础地貌(Basemap)的划分
" srcset="/img/loading.gif
我将直接进入主题,谈谈实际的数据分割。
关于地图规格,它们是由各种控制装置驱动的。
出于这个原因,我们没有在一个共同的控件中对它们进行分割,而是为所有的控件创建了一个专门的分割方法来应对这种情况。
首先我们要讲的是基础地貌的划分。
" srcset="/img/loading.gif
首先,让我解释一下基础地貌在数据分区方面的一些特点:
该图像是一个平原的基础地貌,是原始的荒野,它由一个巨大的模型组成。
而且顶点很大。
纹理也非常大。
由于缺乏内存空间,我们不得不处理只在内存中放置适当的数据。
" srcset="/img/loading.gif
首先,由于数据的特性,顶点和纹理被从基图中切出,作为第一个分区操作
这种切割出来的数据被称为基图信息。
这些数据被储存在资产数据中。
" srcset="/img/loading.gif
基础地貌信息是绘制基地图所需的最小数据。
包含用于绘制的数据、着色器文件等。
因此,在绘制基础地貌时,会首先加载基础地貌信息。
" srcset="/img/loading.gif
要加载基础地貌信息,您需要有关要加载的区域的信息
此加载区域分析和创建底图顶点数据。
具体来说,顶点的最大和最小位置被确定,这些信息被建立为一个加载区域。
" srcset="/img/loading.gif
实际的基础地貌信息加载区域被创建为一个方框区域,如该图所示。
图像显示整个基础地貌信息的区域。
" srcset="/img/loading.gif
现在,让我们来了解一下实际基础地貌信息的加载控制。
将此网格视为加载区域。
" srcset="/img/loading.gif
首先,在加载区和指定尺寸的球体之间做出接触判定。
这个球体的中心点是负载坐标。
" srcset="/img/loading.gif
颜色发生变化的地方是已经接触到的负载区。
对于这个接触过的地区来说,基础地貌信息是由这个控件加载的。
" srcset="/img/loading.gif
基础地貌信息是由这个控件加载的。
" srcset="/img/loading.gif
负载区被存储在头文件中。
如果资产数据被存储在资产数据中,资产数据在初始阶段将不存在于内存中,因此无法加载基础地貌信息。
决策球的大小是可变的。
这样做的原因是,例如,如果在某一地区没有足够的内存,可以缩小这一规模,以便将存在于远处的基础地图信息储存在内存中。
存在于远处的基础地貌信息将很难加载。
这就腾出了一块内存区域,可以储存其他信息。
当然,如果它离开负载区,就会被从内存中丢弃。
" srcset="/img/loading.gif
接下来,我将解释一下我是如何就刚才切出的顶点数据进行控制的。
首先,基础地貌的顶点容量很大!
将所有这些数据放在内存中会增加内存的使用量。
" srcset="/img/loading.gif
因此,我们增加了精简地貌(LOWMAP)的概念。
精简地貌是用于绘制大面积的数据,顶点和纹理较少。
通过绘制大面积的精简地貌,即使内存中没有大面积的基础贴图顶点数据,也不会有问题。
" srcset="/img/loading.gif
根据前面描述的精简地貌的规则,我们将裁剪基础地貌的顶点。
首先,创建一个三维网格。
图片显示的是一个二维网格,但在现实中把它看成是一个三维网格。
从与基础地貌信息的加载区域相同的最大和最小区域中划分并创建指定数量的片段。
在图片中,网格被分为四个部分。
然后计算该网格中的多边形数量。
" srcset="/img/loading.gif
如果网格中的多边形数量少于指定数量,则从基础地貌的顶点裁剪,裁剪的数据将被登记在资产数据中。
" srcset="/img/loading.gif
如果裁剪失败,进一步细化网格,继续使用相同的控制,继续裁剪数据。
" srcset="/img/loading.gif
请注意,这个控制有一个最小的网格尺寸,超过这个尺寸就会进行强制切断和分割。
最小网格大小为200米。
" srcset="/img/loading.gif
之所以规定分割数,是因为要根据顶点的形状进行适当的裁剪。
例如,如果顶点是正方形、水平或垂直的,则按图中所示进行不同的划分。
" srcset="/img/loading.gif
改变分割数的原因是,如果把一张细长的地图分成四个2*2的分割,如图所示,它被分割成一个细长的形状。
这种形状将被加载和低效地裁剪。
为了避免这种情况的发生,分部被调整为尽可能的立方体。
" srcset="/img/loading.gif
接下来,需要一个加载区域来加载刚刚被分割的顶点。
对于负荷面积,计算出划分的网格中顶点的最大和最小值,并作为负荷面积。
图片显示的是实际面积。
这个区域的数据被储存在基础地貌信息中。
对于这个负载区域,与球体进行接触判定,并加载适当的顶点。
与基础地貌信息中的原因相同,球体的半径是根据面积来改变的。
" srcset="/img/loading.gif
接下来,关于广域渲染的精简地貌,数据没有被打散。
把顶点、纹理和其他信息看成是一个单一的数据。
然而,模型同样是根据基础地貌的顶点数据的分割信息进行分割和处理。
这是为了在没有加载基础地貌顶点数据的地方显示精简地貌模型。
" srcset="/img/loading.gif
精简地貌被设定为从精简地貌顶点的最大值和最小值的加载区域,并存储在一个头文件中。
精简地貌是从这个区域和球体以及基图信息中加载的。
球体的大小要比基础地貌的信息宽,因为我们想画出比基础地貌更宽的区域。
" srcset="/img/loading.gif
至于实际的顶点加载行为。
首先假设有顶点被划分为一个网格,如图所示。
" srcset="/img/loading.gif
然后找出与球体接触的负荷面积。
" srcset="/img/loading.gif
对于这个接触到的区域,基础地貌的顶点被加载并在加载后显示出来 。
" srcset="/img/loading.gif
对于没有基图顶点的位置,会绘制一张精简地貌。
精简地貌模型已经被提前分解,以配合基底地图的顶点划分,这样就可以为没有装载顶点的区域绘制精简地貌。
对于没有加载顶点的区域,可以绘制精简地貌。
这就是基础地貌顶点的控制方式。
" srcset="/img/loading.gif
接下来,让我们谈谈纹理问题。
像顶点一样,模型管理着所有的纹理。
自然,加载所有的纹理是一个内存问题。
有必要根据需要加载适当的纹理。
" srcset="/img/loading.gif
作为第一种方法。
首先,被显示的材料从加载的顶点中提取出来。
从该材料中,必要的纹理被加载,加载后,纹理被反射和绘制。
使用这种方法时,遇到了两个问题。
" srcset="/img/loading.gif
首先,存在于远处的模型可以有一个降低的纹理分辨率。
然而,目前的方法是加载必要的纹理,所以在远处显示的多边形的纹理也以相同的分辨率存在于内存中。
这不可避免地增加了内存的使用量。
" srcset="/img/loading.gif
其次,在低容量纹理方面也存在加载问题。
低容量的纹理大量存在。
为了加载这些数据,要多次进行加载控制,导致加载延迟问题。
这些都可以通过批量加载数据来解决。
" srcset="/img/loading.gif
下一节将介绍用于解决这两个问题的方法。
首先,工具中自动生成了三种类型的纹理。
具体来说,从一个单一的纹理,该工具自动生成的纹理分为高分辨率、中等分辨率和低分辨率。
" srcset="/img/loading.gif
关于高分辨率,纹理是由设计者调整的现成的分辨率纹理。
Mipmaps并不存在。
注:Mipmaps是MIP map的另一种写法。
MIP map是一种电脑图形图像技术,用于在三维图像的二维代替物中达到立体感效应。
MIP = multum in parvo,意为在一个小空间里的多数。
" srcset="/img/loading.gif
中等分辨率的纹理是具有原始纹理的四分之一的分辨率,一半的高度和一半的宽度的数据。
这个数据存在Mipmaps。
" srcset="/img/loading.gif
而低分辨率的纹理是非常低容量的图像数据。
只有反照率地图才能创造出这种图像。
" srcset="/img/loading.gif
接下来,使用了两种储存纹理的方法。
一个是纹理列表。
纹理列表在一块数据中包含多个纹理数据。
另一个是单一纹理。
顾名思义,单个纹理被管理在一块数据中。
" srcset="/img/loading.gif
如果中等分辨率的纹理小于指定的尺寸,纹理列表将被存储。
如果没有,它将存储低分辨率纹理的数据,如果它们存在的话
。
这种数据是按地区管理的,不能用于不同地区。
例如,平原地图的纹理列表不能用于荒野地图,因为荒野地图是一个不同的区域。
这个纹理列表的一个特点是,它是一起加载的,这加快了加载速度。
" srcset="/img/loading.gif
接下来,关于单一项目的纹理
单一纹理是没有存储在纹理列表中的纹理数据。
它被储存在资产数据中,以一个纹理为单位。
这可以用在不同的地方,所以没有内存浪费,但也有一个问题,那就是由于数据是作为一个单项读入的,所以加载的次数会增加。
高分辨率的纹理总是作为一个单一的纹理来管理。
" srcset="/img/loading.gif
这些条件被用来解释负载的控制。
首先,纹理列表总是在加载基图信息之后才加载。
只要有基地图信息,纹理列表就会一直保存在内存中。
" srcset="/img/loading.gif
纹理列表是显示模型所需的最小纹理。
在加载顶点后显示模型时,第一步总是参考纹理列表中的纹理并绘制它们。
这使得模型可以在顶点加载后的任何时间被绘制。
" srcset="/img/loading.gif
在浏览纹理列表时,有一些纹理因为条件不匹配而没有被存储。
对于这些,法线贴图将始终反映正面的纹理,所有其他纹理将反映0的纹理。
这些纹理常驻在内存中。
" srcset="/img/loading.gif
在纹理列表中,接下来是对加载单个纹理的解释。
首先,把图像看成是网格单位中的一个加载顶点。
正在显示的材料是从这个加载的顶点中提取的。
" srcset="/img/loading.gif
从先前提取的材料中加载所需的单一纹理。
要加载的纹理是一个中等分辨率的纹理,加载后将被改变为纹理列表中的纹理,并反映在模型中。
" srcset="/img/loading.gif
在加载中分辨率纹理后,相对于近似模型加载一个高分辨率的纹理。
与指定尺寸的球体接触的网格被用作近似模型。
这个指定的尺寸在每个区域都会像以前一样发生变化。
" srcset="/img/loading.gif
加载后,这种高分辨率的纹理在近距离反映在模型上。
" srcset="/img/loading.gif
然而,在反映高分辨率纹理时,中等分辨率的纹理被用于mipmaps。
这确保了中等分辨率的纹理的使用不会造成浪费。
这一连串的事件控制着基础地貌。
四、实例地图(Instancemap)的划分
" srcset="/img/loading.gif
接下来,我们谈一谈实例地图的划分。
" srcset="/img/loading.gif
首先,对实例地图在数据分区方面的特点进行了描述
一个实例地图基本上由多个模型和放置坐标组成。
还有大量的坐标:一个地区,大约放置了2至15万个坐标。
而且它的容量比基础地貌大。
" srcset="/img/loading.gif
基础地貌较大的原因是有几个模型的顶点和纹理,这增加了地图的整体尺寸。
" srcset="/img/loading.gif
由于这些数据的性质,自然不可能将所有的实例地图都放在内存中。
因此,与基础地貌一样,首先要把数据切出来。
首先,具有较大体积的坐标、顶点和纹理被切割出来。
这种切出的数据被称为实例地图信息。
" srcset="/img/loading.gif
然而,在创建实例地图信息之前,有一些事情要先做。
实例地图中有一个混合的模型,在近距离、中距离和远距离显示。
" srcset="/img/loading.gif
这张图片中用红色围起来的区域是按区域创建的实例地图。
从该图像中摄像机的位置来看,红色围成的区域的实例地图只需要显示远距离的信息,而不需要近距离和中距离的模型或坐标数据。
" srcset="/img/loading.gif
在创建实例地图信息之前,实例地图按坐标和模型进行分解,以便在短距离、中距离和长距离上显示,因为在内存中只应保留尽可能少的信息量。
换句话说,实例地图事先被分成了三张地图。
将实例图分解成三个部分后,为每个部分创建实例图信息。
" srcset="/img/loading.gif
关于实例地图信息,需要加载它的信息以及基图信息。
这是由坐标创建的。
对刚刚被分解的实例图所持有的所有坐标进行分析,找出最大和最小坐标,并将这些信息作为加载区域。
" srcset="/img/loading.gif
加载区域和基图信息一样,都存储在头文件中。
当加载区域和球体的指定尺寸接触时,实例地图信息就会被加载。
与基图信息一样,指定的尺寸也会根据区域的不同而改变,但指定的尺寸总是按照长距离、中距离、短距离的顺序增加。
使用这种顺序是因为长距离的实例地图需要从长距离绘制。
" srcset="/img/loading.gif
在实例地图信息旁边,关于坐标数据
首先,有大量的坐标数据
其中一个特别大的数据量是在近距离显示模型的坐标,90%的坐标数据是这种近距离的坐标数据。
" srcset="/img/loading.gif
而对于短距离的坐标,除非你离坐标很近,否则没有必要在内存中存储坐标,因为模型显示距离很短。
" srcset="/img/loading.gif
基于这些,坐标数据被分解成三维网格并存储在资产数据中。
网格大小是固定的,以600米为单位进行分解。
中距离和远距离的坐标没有被分解并存储在资产数据中,因为坐标的数量比短距离的要少。
" srcset="/img/loading.gif
要加载这个坐标数据,和以前一样,你需要一个区域来加载它。
把图像中的蓝色网格看作是你刚刚分割的网格之一。
在这个网格内,为坐标数据创建一个负载区。
" srcset="/img/loading.gif
首先,根据网格中的坐标和模型的显示范围创建绘图区域。
把图像中的点看作是坐标,把圆形区域看作是模型在坐标上的显示范围。
" srcset="/img/loading.gif
然后在之前创建的绘图区域周围创建一个网格区域,这就是负载区域。
这是为所有网格创建的。
" srcset="/img/loading.gif
创建的信息被存储在实例地图信息中。
如果加载坐标进入加载区域,加载控制就会加载坐标。
由于实例图在近、中、远距离上的事先分离,负载区域没有明显变化。
这意味着如果50米和1000米的模型混合在模型显示区,1000米的绘图区将受到影响,加载区将更宽,这将导致加载控制不佳。
这也是为什么事先将实例地图按距离分开的原因。
" srcset="/img/loading.gif
在坐标之后,我们谈一谈顶点。
实例图中的每个顶点都是以模型为单位进行管理的。
它们由独立的顶点管理,例如树木、岩石、鼓等,如图所示。
" srcset="/img/loading.gif
这些模型中的每一个都包含价值为LOD的顶点数据。
" srcset="/img/loading.gif
其次,在近距离显示的模型基本上有较大的顶点容量。
这是因为显示的是具有最高级别LOD的模型,所以顶点的数量自然会更多。
" srcset="/img/loading.gif
相反,在远处显示的模型具有较小的顶点容量。
这是因为显示的是LOD级别最低的模型,而且顶点数量基本上很少。
如果每个模型的顶点被分割,那么即使在远处显示,也会保留近处模型的顶点数据。
这种情况是在浪费内存。
" srcset="/img/loading.gif
基于这些条件,顶点数据被分解。
首先,顶点被分解为模型单元。
接下来,这个分解的结果被进一步分解成两个顶点数据,一个是远距离,另一个是另一个,并登记在资产数据中。
通过这种方式的分解,近距离和中距离的顶点在显示远距离时不会被保留,也不会浪费内存。
" srcset="/img/loading.gif
接下来,关于纹理。
纹理的分解方式与基底图的分解方式相同。
它被分解成高分辨率、中分辨率和低分辨率的纹理,同时还创建了一个纹理列表和一个单一的纹理。
纹理列表总是在加载实例贴图信息之后加载。
" srcset="/img/loading.gif
现在我们来谈谈实际的顶点和纹理加载控制。
首先,从模型的坐标和显示区域中检测出载荷坐标接触的模型,如图所示。
让我们假设这个面孔图标是负载坐标。
" srcset="/img/loading.gif
检测后,模型加载所需的顶点数据。
" srcset="/img/loading.gif
顶点加载完毕后,加载模型所需的单一纹理。
对于这个模型,和基础地图一样,最初使用纹理列表中的纹理,然后反映已经加载的单一纹理。
" srcset="/img/loading.gif
然而,这种方法也有问题。
由于大量的坐标数据,提取要加载的信息的负荷非常大。
虽然通过空间分割等方式实现了提速,但也有一个限度。
由于一些地方存在大量的模型类型,向纹理提取信息的负荷也是很可笑的。
根据不同的位置,在每秒30帧的控制下,负载控制的负荷接近20%。
这必须以某种方式加以解决。
" srcset="/img/loading.gif
为了避免这些,加载实例地图的控制已经被调整。
首先,坐标,即模型的显示区域,被用来在其周围创建一个三维网格区域。
类似于前面描述的坐标的加载区域的控制被应用于整个区域的坐标。
" srcset="/img/loading.gif
然后将得到的面积进行分解
网格被分解成特定数量的碎片。
指定的数字受基础地貌的形状以及顶点的影响,并被分解成一个尽可能立体的网格。
" srcset="/img/loading.gif
接下来,找到网格中存在的纹理和顶点。
其方法是找到与模型的坐标和显示范围相联系的网格。
一旦接触,模型就会在网格中存储必要的顶点和纹理加载信息。
这些系列的控制是针对所有网格进行的。
" srcset="/img/loading.gif
在创建负载信息后,为每个网格构建进一步细分的网格。
这些被称为子网格,被分割的上层网格被称为父网格。
" srcset="/img/loading.gif
接下来,与前面描述的父网格相同的控件被用来构建负载信息。
建成后,超过四分之三的子网格所持有的负载信息(也就是纹理、顶点等的负载信息)。
为此,子网格抛弃了保留。
如果子网格丢弃了它,就没有问题,因为父网格保留了同样的信息。
相比之下,对于其他小区域使用的负载信息,子网格继续保留,而父网格则将其丢弃。
在图像中,蓝色区域被定义为用于四分之三的区域,这是一个宽阔的区域。 在更广泛的地区使用。
这一信息由父网格保留。
相比之下,对于橙色区域,它被用于较窄的区域,并被子网格所保留子网格被定义为父网格的面积,在前三分钟内使用。
" srcset="/img/loading.gif
这些控制一直持续到不再有从父到子的传递。
" srcset="/img/loading.gif
实际的装载是用这个网格信息来控制的。
首先,最上面的网格位置是由负载坐标决定的
在图片中,加载坐标是脸部图标的位置。
然后根据这个网格所持有的加载信息来加载顶点和纹理。
加载完成后,用户会移动到这个网格的子网格。
" srcset="/img/loading.gif
移动到子网格后,重复前面描述的加载控制,直到最深的网格。
" srcset="/img/loading.gif
这些控制措施使支票处理量最小,CPU速度更快
以前负荷接近20%,现在已经降低到2%。
注意,对于高分辨率的纹理,不再执行这个控制,而只是在显示LOD的最高质量模型时才加载。
网格加载信息现在被存储在实例地图信息中。
关于实例地图的讨论到此结束。
五、草地地图(Grassmap)的划分
" srcset="/img/loading.gif
接下来,我们将讨论草地地图(Grassmap)的划分。
" srcset="/img/loading.gif
首先,从数据分区的角度对草地地图的特点进行了描述
一个草地地图由几个模型、放置坐标和碰撞组成。
其中,纹理、顶点、坐标和碰撞的容量最大。
" srcset="/img/loading.gif
根据这些数据的特点,以与基底图和实例图相同的方式切出初步信息。
首先,只有坐标和碰撞数据被剪切出来。
裁剪后的数据被称为草地地图信息。
" srcset="/img/loading.gif
接下来,关于草地地图模型。
使用草地地图模型,并在大范围内绘制,如图所示。
" srcset="/img/loading.gif
由于这个模型的性质,草地地图的顶点和纹理没有被分割。
这是因为该模型总是在一个广泛的区域内使用,因此与常驻状态没有区别。
草地地图的顶点和纹理也比基础地图和实例地图的小。
基于这两个原因,顶点和纹理被保留在草地地图信息侧。
" srcset="/img/loading.gif
草地地图信息也需要一个加载区。
对于这些信息,最大和最小的位置是由草地地图的碰撞和坐标决定的,这些被用作加载区域。
" srcset="/img/loading.gif
和以前一样,载荷区被存储在一个头文件中,通过确定与球体的接触来加载。
加载草地地图的球体尺寸很小。
这是因为该模型的显示距离很小,没有必要在大面积上读取。
因此,在大多数地方只有一张草地地图,只有在该地区的边界附近才有两张。
" srcset="/img/loading.gif
第二,关于坐标数据
有大量的坐标数据
在一些地区,有超过50万个坐标。
对于这些,我们不希望一直保留它们,因为这给记忆带来压力。
" srcset="/img/loading.gif
因此,草地地图的坐标数据被分割。
关于坐标数据的划分,它被划分在XZ轴上一个固定的600米网格上。
到目前为止,它一直是一个三维网格,但只有草地地图是在XZ轴上。
这是因为XZ轴不像实例地图坐标那样复杂,所以XZ轴没有问题。
" srcset="/img/loading.gif
分解完草地地图的坐标后,必须确定加载区域。
显示区域是由网格中的坐标和模型的显示距离创建的,如图所示。
圆形区域被认为是显示区域。
" srcset="/img/loading.gif
然后,从前面得到的显示区域,得到一个围绕它的网格,然后将其作为加载区域。
这与实例地图坐标的方法相同。
" srcset="/img/loading.gif
生成的负载区被存储在草地地图信息中。
如果加载坐标进入加载区域,坐标就会被加载,完成后会显示坐标位置的模型。
" srcset="/img/loading.gif
接下来,关于碰撞数据。
碰撞数据是在实际机器上放置的数据,由三角形的多边形组成。
它有一定的容量,有超过10万个多边形。
这种碰撞数据只用于草地地图,是专门用于放置的。
与坐标一样,我们不想在内存中保留所有的碰撞数据。
" srcset="/img/loading.gif
碰撞后的数据也和以前一样被打散。
与坐标一样,它们被划分在一个固定为600M XZ 轴的网格上。
对于这里的装载区,网格的大小被用作装载区,因为它是这样的。
" srcset="/img/loading.gif
接下来,在碰撞的加载控制之前,称为草地地图的加载半径的信息被创建。
负载半径保持着与碰撞所放置的模型有最宽显示距离的信息。
如图所示,每个模型的显示范围被设定,最广泛的信息被采集。
" srcset="/img/loading.gif
碰撞的装载区域被储存在草地地图信息中
装载面积和球体是用来确定球体的半径的,也就是前面得到的装载半径。
如果联系到这一点,碰撞被加载,加载后的坐标被放置,模型被显示。
这就是草地地图的流程。
六、内存管理
" srcset="/img/loading.gif
最后,本节涉及到内存管理。
" srcset="/img/loading.gif
自然,有必要使用无缝地图的内存而不浪费它。
事实上,关于《Xenoblade X》中的无缝地图,在许多地方,数据区往往几乎完全被堵塞了。
为了处理这个问题,有效的内存管理是必须的。
由于很难在不浪费内存的情况下使用内存,我们开发了一个专门的内存管理系统。
" srcset="/img/loading.gif
让我们先解释一下为什么要精简地使用内存。
我相信大多数程序员都知道这个故事,但让我们从碎片化开始。
在上面的图片中,内存中充满了数据。
从那里,不再需要的数据被删除。
如下面的图片所示。
" srcset="/img/loading.gif
删除后,会产生一个自由区域。
当额外的数据不能被放置在自由空间时,就会出现这种现象。
虽然总体自由空间大于你想增加的容量,但由于没有连续的自由空间,你无法放置数据。
这种现象被称为碎片化。
" srcset="/img/loading.gif
解决这个问题的方法很简单,只要不断监测内存,并在必要时重新安排。
你可以简单地重新排列数据,如上图所示。
" srcset="/img/loading.gif
那么问题出在哪里呢?
最重要的事情只是努力创建一个程序来组织记忆。
这包括实际移动数据的控制,以及在数据被移动后重组内存信息的控制。
其他问题是多核心和GPU问题
首先,让我们来谈谈多核心问题。
" srcset="/img/loading.gif
并行运行的CPU和GPU在同一时间引用同一内存是很常见的。
" srcset="/img/loading.gif
如果CPU0在这种情况下移动数据,CPU1和GPU将参考空内存。
" srcset="/img/loading.gif
如果新的数据被加载到这个空白区域,它将参考有关数据。
" srcset="/img/loading.gif
为了处理这个问题,首先进行了复制而不是移动。
在图像中,CPU0复制数据。
来自复制源的信息仍然存在于内存中,并被保证为信息。
" srcset="/img/loading.gif
在复制之后,如果能保证在复制之前没有人参考过这些信息,那么这些数据就会被销毁。
通过继续这样做,多核心组织的问题就解决了。
" srcset="/img/loading.gif
然而,这些都有问题
如果目的地的容量小于要复制的数据,就不能复制。
如果目标存储器比要复制的数据小,如图所示,要复制的数据就不能被复制。
" srcset="/img/loading.gif
为了解决这个问题,我们已经准备了一个临时区域
关于无缝地图的数据区域,存在192M,但14M被用于临时区域。
作为一种实际控制,如果目的地的容量不足,则在临时区域进行复制控制。
" srcset="/img/loading.gif
如果这时能保证它不会像以前那样被引用,那么它就会从内存中被丢弃。
" srcset="/img/loading.gif
然后再从临时区域复制到正常数据区。
" srcset="/img/loading.gif
如果能保证它不会像以前那样被引用,那么它就会被销毁。
我们通过这一系列的步骤来处理这个问题。
" srcset="/img/loading.gif
关于实际的内存控制,一个叫做MemObj的类负责所有的内存控制。
然后,需要数据的对象,如顶点和纹理,由MemObj不断监控。
MemObj会通知它们内存位置的变化、破坏等,基于此,每个对象都会进行适当的处理。
从这个解释中,你可能已经明白了,结果是,编程工作无法避免,由一个简单的、真正的糊涂的过程来处理!
在这方面,当考虑到处理速度加快、多核问题等时,我们无法拿出适当的回应,结果,回应是我们已经尽力了。
" srcset="/img/loading.gif
关于内存移动区,负载太高,以至于不是所有的移动控制都能一次完成。
具体而言,它被控制在每帧1M字节以内。
然而,在地图跳转过程中,没有进行绘图,所以在负载方面有一定的余量,所以在14M字节以内进行了内存控制。
对于不被其他CPU或GPU访问、不需要考虑多核的数据,不使用以下内容。
到现在为止的一系列控制都没有进行,直接移动记忆来应付情况。
这种内存组织只在数据区进行,没有在程序区使用。
关于14M的临时区域,高分辨率的纹理在不被用于内存移动等情况下被存储起来。
这是因为高分辨率的纹理是唯一可以在需要组织内存的临时区域时被销毁的数据。
尾声:总结与感想
" srcset="/img/loading.gif
支持无缝地图增加了控制绘图所需的努力。
首先,在检查模型数据时,每次都要将其转换为无缝地图,非常耗时。
因此,我们开发了一个专门的绘图控件来处理这个问题。
当一个系统以这种方式创建时,通过使用通用对象简化了代码,但开发两个系统仍然需要时间和精力。
然而,我们仍然不得不开发两个系统。
另一点是,数据越是被分解,就越是要考虑到加载的问题。
如果你有足够的内存,我个人认为没有必要拆解这么多。
无缝的功能使我们无法确定模型数据的容量。
在正常游戏的情况下,大多数时候内存图是固定的,并且这个内存图的容量被设定为某个水平。
有了无缝地图,只有必要的区域被加载,所以你就没有这种问题了。
然而,结果是出现了在数据创建之前不知道可以读到什么程度的问题!
在大多数地区,这个问题没有发生,但当它发生时,大量的数据已经完成,并且要确定哪些信息要简化是非常困难的。
因此,开发了一个专门的系统来处理这个问题,即如果信息进入一个特定的区域,就会被丢弃在另一个特定区域。
这是关于组织内存和处理压缩的问题,最好是尽快对此采取行动!
在《Xenoblade X》中,我们在某种程度上完成了无缝地图功能后开始处理,但这非常耗时。
代码的设计没有考虑到内存的整理,所以有很多工作要解决。
这是我们觉得如果先做的话会更容易的领域之一,我们可以通过先适当地设计这个区域来节省大量的时间和精力。
而如果你正在开发一个无缝地图,我个人认为整理内存的功能是一个必不可少的功能。
如果你有充足的内存,我认为你不需要处理这个问题,但我认为我们大多数人没有那么多的剩余容量。
在这种情况下,碎片化的问题将一直存在,通过整理内存,你可以最大限度地使用内存而不浪费。
从这个意义上说,我认为这是构建无缝地图的一个基本功能。
在开发无缝地图时,我们不得不考虑一切,因为我们无法准确找到它,因为我们没有文件。
这是很难用低调的方式做到的事情。
我们不得不用手来完成整个过程,试图弄清楚什么是正确的答案,它是否会成功,问题会如何出现,等等。
当我们第一次开始开发无缝地图时,我们不知道该从哪里开始。
我们知道有大量的工作要做,但我们不知道首先从哪里开始。
然而,游戏软件是在假设无缝地图会完成的情况下进行的。
在开发渲染引擎的同时开发无缝地图,已经让我们承受了很大的压力。
当我创作一个节目时,我把工作分解成几个部分,然后逐一处理每个部分,但这次我不能这样做。
因为我不知道该怎么做。
所以我又回到了绘图板上,通过试验和错误来处理它。
起初,我们阅读了每个地区所有必要的模型,并制作了一张无缝地图。
然后,由于内存问题,我们只分解了纹理。
接下来,顶点被分解为
我们被要求在更大的范围内绘制模型,因此我们创造了精简地貌的概念。
我们以这种方式处理问题和要求,相应地增加功能。
我获得的经验越多,就越倾向于不喜欢这种方法,但最后,我意识到这是创造新东西的唯一途径。
这一部分让我再次想到,这是创造新事物的唯一途径。
现在就这些了。
非常感谢你。
【全文完】
译者后记
明天就是《异度神剑3》发布的日子,这篇《<异度神剑 X>无缝地图的工作流程》的中文翻译工作也算是在紧赶慢赶之下大功告成了。
首先在此感谢演讲者稲葉道彦先生进行的精彩演示,让笔者能了解到在《异度神剑 X》开放世界技术背后的实现细节。
此外,感谢 @bluerose ,在笔者翻译的过程中提供了大量的支持。
最后祝大家在艾欧尼翁玩得开心!
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 协议 ,转载请注明出处!