Unreal Engine地形系统辨析(二)
不是每一个Landscape Component中都拥有一张独立的Heightmap(系统会把高度和法线信息都打包到同一个纹素中)。根据最大的heightmap纹理尺寸(#define MAX_HEIGHTMAP_TEXTURE_SIZE 512),邻接的几个component可能共享同一张heightmap,但weightmap是每一个component独享的,它的尺寸与lod0的顶点数量保持一致(int32 WeightmapSize = (SubsectionSizeQuads + 1) * NumSubsections;)。weightmap专门用来做多层纹理的混合,一种是基于权重的,另一种是基于高度的,还有一种是基于alpha的。基于权重的混合较为常见,每个layer的颜色直接乘以该层权重值,然后再依次把所有算好的颜色值叠加一起,而这些权重在每次编辑后会自动的进行归一化处理。基于height的混合方法稍微复杂点,在每层原始权重的基础上再叠加上一个来自与颜色图匹配的高度图的灰度值,后面会做个重新的归一化,这样就能保证输出的颜色亮度不会超过颜色纹理中的最高亮度。通过高度图把后期涂抹的权重进行强化,让当前层里的一些纹路凸显出来。最后基于alpha的混合就比较简单了,它们会依照顺序逐层进行线性插值,所以这些基于alpha的混合层会被单独提取出来,等非alpha混合结束后再进行基于alpha的混合计算。编辑器中在对weightmap修改后,系统会做一个简单的判断,如果发现该层的权重值都变为零时,就把当前层移除掉。接着它会更新这个component的材质实例,根据当前的layer的使用情况生成对应的shader变体,假如有新的一层添加进来也会做同样的处理。这样做保证shader不会进行冗余的纹理采样和相应的混合计算。unity的weightmap是整个地形共享的,因为它根节点的patch需要覆盖terrain的全部区域。这对于texture streaming不是那么友好,除非用的是virtual texture技术(可以把局部区域映射到物理内存上)。而且还有一个问题,不是地形的所有区域都需要混合全部的layer,那么一些layer所对应的混合权重可能全都为零。因此weightmap上的部分存储空间是完全浪费的,不过这也没有办法,由于普通纹理作为一个整体,不能表达稀疏的特性。另外unity的地形渲染为了兼容老式的硬件,所以限定了采样的纹理层数,包含diffusemap和normalmap,一次drawcall只能处理8张纹理,当然还要加上一张weightmap。那如果超过四层纹理需要混合怎么办?很简单,它会通过增加一个额外的pass,将另外四层纹理的混合结果叠加到上一次输出的结果上(通过RenderToTarget的方式,同一个mesh在同一个位置绘制多次),以此类推,理论上它能够支持无数张纹理的混合计算,前提是增加新的drawcall。这种方式对于那些原本就可以支持更多纹理采样的硬件来说不是那么友好,因为把四层纹理混合后的结果通过alpha blending叠加到上次的混合结果上会明显增加rop的开销,而且多走一次管线也是不划算的,相当于顶点变换,光栅化以及深度测试还要再做一次。unity为此做了一些简单的优化,那就是在addpass里面判断四个权重值都为零时,这个像素会被直接丢弃掉,不采样颜色纹理,同时也不把结果输出到rendertarget中。但是由于它多了一个动态的shader条件分支,所以增加了warp中发生指令分歧的概率。除此之外,unity的地形系统还生成了一张预计算的basemap,它会在远离摄像机的地块材质中使用(把所有需要混合的纹理都离线处理好,放入到一张纹理中,因此分辨率不必设置得太高),主要是为了降低纹理的采样数量,从而提升渲染效率,这种优化手段ue4是不具备的。
前面说到ue4地形的weightmap的分辨率只能与顶点数量保持一致,这就限制它了混合细节度的表现力,更细微的过渡边缘很难被勾勒出来。反观unity,它倒可以改变alphamap的分辨率,但是由于硬件的约束,也只能设置到8k。不过这些问题并不会对实际项目造成太大的影响,因为ue4有基于高度的混合,这其实也是从另一个方面补充了内部混合的细节,虽然这张高度图跟颜色图一样都是tiling的模式。unity则可以通过放置多个terrain到同一场景中让alphamap覆盖的区域不至于那么大,从而维持了一个合理的混合纹素密度。
页:
[1]