总湮灭在我心中有一个特别的地方,因为它是我玩的第一个RTS; 这是“ Command&Conquer”和“ 星际争霸”,是90年代末发布的最好的RTS之一。
10年后 - 在2007年 - 其继任者被释放:最高指挥官。
随着克里斯·泰勒作为设计师,乔纳森·马弗 负责引擎编程和杰里米·索尔的作曲(一些背后的原横扫千军的主要人物),球迷的期望很高。
最高指挥官原来被评论家和球员高度赞扬,具有诸如“战略放大”或物理上现实的弹道之类的好功能。
所以让我们来看看Moho,引擎为SupCom提供动力,呈现出一个游戏框架!
由于RenderDoc不支持DirectX 9游戏,因此使用旧的PIX进行逆向工程。
地形结构
在我们挖掘框架渲染之前,首先要谈谈SupCom中如何构建地形以及使用哪种技术。
以下是“Finn's Revenge”的概述,1对1地图。
左侧是整个地图的顶视图,就像在迷你地图上出现的游戏中一样。
下面是从另一个角度看的相同的地图:
首先,从高度图计算地形的几何。
高度图描述了地形的高程。白色表示高空,黑色表示低空。
对于我们的地图,使用513x513单通道图像,它代表了游戏中10x10公里的地形。SupCom支持更大的地图,高达81x81公里。
| |
所以我们有一个代表我们地形的网格。
然后,游戏应用与正常纹理结合的反照率纹理来覆盖所有这些多边形。
对于每个地图,海平面也被指定,所以游戏调制海面下的像素的反照率颜色给它们一个蓝色的色调。
好的,所以有高度的纹理是很好的,但它的限制相当快。
我们如何在地图上添加更多细节和变体?
所使用的技术称为“ 纹理拼接 ”:游戏画一系列附加的反照率+正常纹理。每个步骤都将所谓的“层”添加到地形。
我们已经有层次0:具有原始反照率+颜色纹理的地形。
要应用下一层,我们需要一些额外的信息:一个“splat map”,告诉我们在哪里绘制新的反照率+正常,更重要的是不绘制的地方!没有这样一个“地图”也称为阿尔法地图,应用一个新的层次将完全覆盖以前的层次。反照率和正常纹理在应用于网格时都具有自己的缩放因子。
所以我们应用层次1,2,3和4,每个依赖于3个独立的纹理。反照率和正常纹理每个使用3个通道(RGB),但是拼图仅使用一个通道。
因此,作为优化,将4个拼贴图组合成单个RGBA纹理。
|
好的,所以我们的地形有更多的纹理变化。它从远处看起来不错,但如果你放大你快速注意到缺乏高频率的细节。
这是在贴花时使用的:这些像是在本地修改反照率颜色和像素法线的小精灵。这个地形有861个实例,有21个独特的贴花。
好多了,一些植被呢?
下一步是添加到地形引擎称之为“道具”:树或摇滚模型。对于这张地图,有23个独特模型的6026个实例。
现在最后一触:海面。它是几个正常的地图和沿着不同方向的UV滚动的组合,一个用于靠近岸边的波浪的反射和精灵的环境地图。
我们的地形现在已经准备好了。
为地图设计师创建好的高度图和拼图可能是具有挑战性的,但幸运的是有几个工具可以帮助您完成任务:官方的“Supcom Map Editor”或World Machine具有更高级的功能。
所以现在我们知道SupCom地形背后的理论,我们来看一下实际的游戏框架。
框架细分
这是我们将剖析的游戏框架:
果肉剔除
游戏在RAM中是从高度图创建的地形网格,由CPU镶嵌,每个顶点的位置是已知的。当缩放级别变化时,CPU会重新计算地形的镶嵌。
我们的相机专注于靠近岸边的场景。渲染整个地形将是浪费计算,因此引擎提取整个地形的子网,仅提供播放器可见的部分,并将该小部分提供给GPU进行渲染。
正常地图
首先,只计算法线。
第一遍计算由5个层(5个法线贴图和4个拼贴图)的组合产生的法线。
不同的法线贴图混合在一起,所有的操作都是在切线空间中完成的。
| |
这是在一个具有6个纹理提取的单个绘图调用中完成的。你会注意到结果是相当“淡黄色”,与其他普通的地图往往是蓝色相反。事实上:这里的蓝色通道根本就不用,只有红色和绿色。
但是等一下,一个正常的是一个3分量的矢量,怎么只能存储2个组件?它实际上是一种压缩技术(在帖子结尾解释)。
现在让我们假设红色和绿色通道包含我们所需要的关于法线的所有信息。
地层已经完成了,现在是贴花的轮到:添加地形贴花和建筑贴花来调节地层法线。
我们还没有使用我们的渲染目标的蓝色和Alpha通道。
所以游戏从512x512纹理中读取,代表地形的全部法线(从原始高度图中烘焙),并使用双三次插值计算每个像素的正常值。结果存储在蓝色和Alpha通道中。
| 存储在蓝色和阿尔法的双三次插值。 | |
然后,游戏将这两组法线(地层/贴花法线和地形法线)组合到用于计算照明的最终法线中。
这次没有压缩:法线使用3个RGB通道,每个组件一个。
它可能看起来很绿,但这是因为场景相当平坦,结果是正确的:您可以通过做任何像素并计算其法线矢量colorRGB * 2.0 - 1.0
,还可以检查矢量的范数为1。
阴影贴图
用于渲染阴影的技术是“Light Space Perspective Shadow Maps”或LiSPSM技术。
在这里,我们只是把太阳当成一个方向的光。渲染场景的每个网格,并将其与太阳的距离存储在1024×1024纹理的红色通道中。LiSPSM技术计算最佳投影空间以最大化阴影贴图的精度。
如果我们停在这里,我们只能画出阴影。当渲染单位时,游戏实际上会尝试通过使用一些PCF采样来平滑阴影的边缘。
但即使是PCF,也没有办法获得我们在屏幕截图上看到的美丽的平滑阴影,特别是地面上建筑物的平滑轮廓......那么这样做是如何实现的呢?
即使在游戏开发过程的最后部分,似乎阴影的实施仍然是一个持续的努力。这是Jonathan Mavor在游戏公开发布前11个月所说的话:
在宣布一个月后,出现了一个新的开创性的阴影贴图技术:差异阴影贴图或VSM。它能够非常有效地呈现华丽的柔和阴影。
似乎SupCom团队试图尝试这种新技术:反编译D3D字节码,揭示了对DepthToVariancePS()
计算阴影贴图的模糊版本的函数的引用。在VSM发明之前,阴影贴图不能也不会模糊。
这里SupCom执行影子贴图的5x5 高斯模糊(水平和垂直通过)。
| 高斯模糊 | |
然而在D3D字节码中,没有关于存储深度和平方深度(VSM技术所需的信息)的指令。它似乎只是一个部分实现:也许没有时间来完善技术在开发的最后阶段,但无论如何,代码原样可以产生好的结果。
注意,伪VSM映射仅用于在地面上产生软阴影。
当阴影必须被绘制到一个单元上时,它是通过使用PCF采样的LiSPSM图进行的。您可以在下面的截图中看到差异(PCF在阴影边框上有块状工件):
阴影地形
由于生成的正常地图和阴影贴图,可以最终开始渲染地形:带有照明和阴影的纹理网格。
| | | | |
贴花
绘制贴花的反照率分量,使用正常信息计算照明方程。
水反射
我们在我们现场的右边有海,所以如果机器人坐在水中,我们应该能够看到它在海面上的反射。
渲染表面的反射是一个经典的技巧:执行一个附加的过程,在应用相机变换之前,垂直轴被缩放-1,所以整个场景就水面而言是对称的(就像一个镜像),这正是渲染反射所需的转换。SupCom使用这种技术,并将所有镜像单元网格渲染成反射图。
网格渲染
所有单位然后逐一呈现。对于植被,几何实例化用于在一个绘图调用中渲染多个树。大海使用单个四面体渲染,像素着色器提取几个法线贴图,折射图(直到现在呈现的场景),反射图(刚刚生成的)和一个天空盒进行额外的反射。
在最后一幅图像中注意到屏幕边界附近的小黑色文物:这是因为水面的取样被打乱,造成运动错觉。有时,中断会在视口内从视口外部导出纹素:但是这样的信息不存在,因此黑色区域。
在游戏过程中,UI实际上会隐藏在覆盖视口边缘的薄边框下的这些工件。
网格结构
SupCom中的每个单元都在单个绘图调用中呈现。模型由一组纹理定义:
- 反照率地图
- 一个正常的地图
- 一个“镜面地图”实际上包含比镜面更多的信息。这是一个RGBA纹理:
- 红色:反思 反映出多少环境地图。
- 绿色:高光。关于阳光。
- 蓝色:亮度 以后用于控制绽放。
- Alpha:团队颜色 它根据团队颜色调整单位反照率。
| |
粒子
然后渲染所有颗粒,并且还添加每个单元的健康条。
盛开
时间让事情闪耀!但是,由于我们使用LDR缓冲区,我们如何获得“亮度信息” ?
亮度图实际上包含在Alpha通道中,它同时被绘制在先前的网格上。
创建帧的缩小版本,使用alpha通道仅使亮区域突出,并执行两个连续的高斯模糊。
| |
然后将模糊的缓冲区用添加剂混合画在原始场景的顶部。
用户界面
我们完成了主要场景。UI终于被渲染,它被精美优化:单次绘制调用来渲染整个界面。1158个三角形立即被推送到GPU。
像素着色器从单个1024x1024纹理读取,其效果类似于纹理图集。当选择另一个单位时,UI被修改,纹理图集将被即时重新生成,以包装一组新的精灵。
我们完成了框架!
补充笔记
详细程度
由于SupCom支持缩放级别的巨大变化,所以依赖于细节级别或LOD。
如果玩家从地图上缩放,可见单元的数量将迅速增加,为了跟上GPU负载的增加,需要渲染更简单的几何图形和更小的纹理。由于机组距离很远,发动机可以随之消失:机型更换为低多模型,细节更少,但在屏幕上显示的很小,玩家几乎看不到与高分辨率楷模。
LOD不仅关注单位:阴影,贴花和道具停止超出一定距离。
战争迷雾
由于战争之雾,每个单位都有一条视线,只有靠近这个单位的地方才是完全可见的。不存在单位的区域是灰色(以前访问过)或黑色(尚未开发)。
游戏将雾信息存储在128x128单声道纹理中,其定义了雾的强度:1表示无可见度,而0表示完全可见。
正常压缩
如所承诺的,这里是关于SupCom中用来压缩法线的技巧的简短解释。
法线通常是三分量矢量,但是在切线空间中,矢量相对于表面切线表示:X和Y在切线平面内,而Z分量总是远离表面。
默认情况下,正常情况(0, 0, 1)
是为什么当正常情况不受干扰时,大多数正常地图显示为蓝色。
所以假设正常是一个单位矢量,它的长度是一个X² + Y² + Z² = 1
。
如果你知道X和Y的值,那么Z只能有两个可能的值:Z = ±√(1 - X² - Y²)
。
但是,由于Z总是偏离表面,所以必须是积极的,所以我们有Z = √(1 - X² - Y²)
。
这就是为什么只有在红色和绿色通道中存储X和Y就足够了,Z可以从它们派生出来。在本文中可以找到更详细(更好)的解释。
正常混合
由于我们正在谈论法线,SupCom 在法线贴图之间使用splatmaps作为因素来执行某种类型的语言。实际上,有两种方法可以混合两个普通的地图,不同的结果,这不是一个简单的问题,就像这篇文章解释一样。
更多链接
- Jonathan Mavor 的博客有很多技术见解和关于TA Graphics Engine的非常好的帖子。
- TA发展背后的故事。非常有趣的阅读从1998年,存档在Wayback机。
- 有关SupCom地图编辑和修改的详细信息。
关于这个主题的更多讨论:Slashdot, Hacker News, Reddit。
此外,本文的打印副本已由Hacker Monthly提供。