[unity] 仿造一个枭龙战机的HUD
我希望在自制的游戏中实现一个简单的HUD(Head-up display 抬头显示器),使其看起来“像那么回事”;DCS游戏中的座舱制作的算比较精致的,鉴于财力问题,我们这次能够“抄作业“的最好对象就是JF-17”枭龙“战机的HUD了:JF-17的HUD
枭龙模组的HUD非常复杂,我们本次尝试只选择实现这些内容:
[*]反射式成像的效果:图像中心的光线始终与载机轴线平行,看起来始终保持在正前方;图像看起来在无限远处,成像大小仅和视角有关,前后移动,并不会有近大远小的效果
[*]速度、高度、航向指示器,且刻度有滚动效果
[*]姿态仪,能够正确显示俯仰、偏航、滚转角度
[*]速度矢量符号、机头指向
[*]其他纯文字内容:攻角、过载、历史最大过载、马赫数、HUD模式等
反射式瞄具准星始终保持在正前方
贴图绘制
实现HUD的整体思路是将其中动态的部分拆解成独立的图片,来进行移动、旋转操作。通过一些取巧的手段,本次实现的内容都不需要动态生成mesh就能够完成,这样工作量就少了很多。
HUD中的图像除了文字就是一些线条,虽然不太复杂但是对位置的精准性要求比较高,所以我使用了手写svg的方式来绘制HUD。这种方法后续调整参数比较方便,同时不需要专业的软件,直接用文本编辑器就可以了。我构造了一个简单的工作流:首先写出要绘制内容的jinja2模板,然后通过python脚本填入参数生成.svg文件,再使用inkscape将其渲染为.png文件。
以左侧的速度表的滚动刻度为例,它的jinja2模板长这个样子:
<?xml version=&#34;1.0&#34; encoding=&#34;UTF-8&#34;?>
{% set height = scale_height * (scales|length * line_widths|length) %}
{% set x_mid = width/2 %}
{% set y_mid = height %}
<svg width=&#34;{{width}}&#34; height=&#34;{{height}}&#34;>
{# background #}
<g stroke=&#34;#00ff00&#34; stroke-width=&#34;{{line_stroke_width}}&#34;>
{% for i, _ in scales %}
{% for j, line_width in line_widths %}
{% set x = 0 if side==&#39;left&#39; else width-line_width %}
{% set dy = (j + i * line_widths|length) * scale_height + line_stroke_width%}
<line x1=&#34;{{x}}&#34; x2=&#34;{{x + line_width}}&#34;
y1=&#34;{{height - dy}}&#34; y2=&#34;{{height - dy}}&#34; />
{% endfor %}
{% endfor %}
</g>
</svg>生成的svg图长这个样子
<?xml version=&#34;1.0&#34; encoding=&#34;utf-8&#34;?>
<svg height=&#34;125&#34; width=&#34;20&#34;>
<g stroke=&#34;#00ff00&#34; stroke-width=&#34;1.5&#34;>
<line x1=&#34;10&#34; x2=&#34;20&#34; y1=&#34;123.5&#34; y2=&#34;123.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;118.5&#34; y2=&#34;118.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;113.5&#34; y2=&#34;113.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;108.5&#34; y2=&#34;108.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;103.5&#34; y2=&#34;103.5&#34;/>
<line x1=&#34;10&#34; x2=&#34;20&#34; y1=&#34;98.5&#34; y2=&#34;98.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;93.5&#34; y2=&#34;93.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;88.5&#34; y2=&#34;88.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;83.5&#34; y2=&#34;83.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;78.5&#34; y2=&#34;78.5&#34;/>
<line x1=&#34;10&#34; x2=&#34;20&#34; y1=&#34;73.5&#34; y2=&#34;73.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;68.5&#34; y2=&#34;68.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;63.5&#34; y2=&#34;63.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;58.5&#34; y2=&#34;58.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;53.5&#34; y2=&#34;53.5&#34;/>
<line x1=&#34;10&#34; x2=&#34;20&#34; y1=&#34;48.5&#34; y2=&#34;48.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;43.5&#34; y2=&#34;43.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;38.5&#34; y2=&#34;38.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;33.5&#34; y2=&#34;33.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;28.5&#34; y2=&#34;28.5&#34;/>
<line x1=&#34;10&#34; x2=&#34;20&#34; y1=&#34;23.5&#34; y2=&#34;23.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;18.5&#34; y2=&#34;18.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;13.5&#34; y2=&#34;13.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;8.5&#34; y2=&#34;8.5&#34;/>
<line x1=&#34;14&#34; x2=&#34;20&#34; y1=&#34;3.5&#34; y2=&#34;3.5&#34;/>
</g>
</svg>字体
我没有找到JF-17中使用的字体,但是找到了一个开源的航空字体:B612,据说是空客在使用的字体。在unity中,可以使用TextMeshPro来生成与渲染我们自己选择的字体;另外,还支持一些超文本标记,后续扩展HUD中的其他文字显示会比较方便,比如左右对齐、居中、删除线之类的。
SDF着色
在初期的测试中,我发现图像的显示质量十分感人,即使把分辨率拉满,线条边缘也和抠图没扣干净一样,有一些残留的白色。参考TextMeshPro的字体显示方案,解决这个问题的方法就是采用SDF(Signed Distance Field)贴图来进行着色,参考这个blog。使用SDF后,线条的边缘在各种缩放下都能一直保持清晰,不过在贴图分辨率不高的情况下,转角处会更圆滑。
SDF的原理很多地方都有了。简单来说问题的原因是图像渲染时的插值,在同一个色块的内部,插值工作的很好,但是在(字体/线条)的边缘处,颜色发生了突变,插值的结果就不科学了,会产生模糊的效果。对于字体、线条来说,这个问题就很严重,因此要采用一个适合插值的东西作为贴图的值输入着色器。由此产生了SDF的概念,它描述的是像素点与纹样边缘的距离,插值后也很科学(不完全对,比如字体转角处就会变圆滑),在着色器中,最后将这个距离输出为图像的alpha值,就可以获得一个清晰的边缘了,参考这个shader:Image-SDF。
在github上,已经有工具能够将.png图像转化为SDF贴图了,我做了一点小改动:SDF-Generator;只写入alpha通道,不需要其他颜色信息了(基色直接在材质里设置)。这样在unity中导入贴图时可以选择alpha8格式,节省一点空间。
滚动刻度
滚动刻度的范围可以非常大,比如速度表,其展示的范围从0~1000+都有可能(我采用了公制单位,就是任性),直接输出到一张图片中就是又细又长,体积太大。所以需要输出可变的刻度数字,再通过上下移动较小尺寸的组合对象来实现滚动的效果,当偏移超过+-0.5时,改变数字内容,再重置偏移量;多余的部分使用遮罩去除即可。
遮蔽前
遮蔽后
姿态仪与速度矢量符号
姿态仪也通过一整张图片实现,目前使用了均匀的刻度分划,方便进行平移,在大角度时效果不是太好,不过问题不大。
平面上的等角刻度分划不是均匀的
观察JF-17的HUD,可以发现姿态仪是如何展示滚转、俯仰、偏航、攻角数据的,特别地,速度矢量符号总是处在姿态仪的中心垂线上:
姿态仪数据示意
滚转角和速度矢量符号的存在让姿态仪图像的平移稍微麻烦了亿点,需要整点三角函数才能计算出最终的平移量。
反射式成像效果
图像中心的光线始终与载机轴线平行:这个实现起来比较简单,只要让HUD的xy坐标随着观察者一起移动即可。
图像看起来在无限远处,成像大小仅和视角有关:可以让HUD始终保持在观察者前方的固定距离上,也可以让HUD随着观察者z坐标进行缩放。但是后者在观察者距离HUD平面很近的时候,缩放值会非常小,由于数值计算精度的原因,图像会抖动起来,效果非常糟糕。
当姿态仪图像的大小确定后,我们实际上已经固定了视角-HUD中距离的换算关系了,否则姿态仪的虚拟地平线就不能和真正的地平线重合了。假设1弧度对应于HUD中2单位的距离,我们将HUD平面固定在观察者前3单位的位置上,那么此时HUD的缩放大小应该是1*3/2=1.5,即放大1.5倍。
最后整出来的效果就是这样:【超级鱼窝】从零开始的飞行模拟游戏 - HUD测试,看起来海星
页:
[1]