引擎设计跟踪(九.10) Max插件更新,地形问题备忘
最近没有大的更新. 最近本来要做max的骨骼/动画导出, 看导出插件代码的时候, 突然想起之前tagent space导出的疑问, 于是确认了一下.
http://www.cnblogs.com/crazii/archive/2013/04/03/2997601.html
同时在网上找到了一个很明显的带有镜像UV的模型, 进行了测试.
使用该模型顺便发现了float16 (IEEE 754-2008 16 bit floating point format)即半精度浮点数 (http://en.wikipedia.org/wiki/Half_precision), 我的实现方式的bug: 由于使用了简单的位运算,
没有处理overflow和underflow的情况, 导致float32 => float16转换过程中数值溢出.
遇到的情况是一个很小的数(比如1e-6)转到float16时underflow, 正确值应该转为0, 但是由于没有处理这种情况, 导致转换的结果为-300多.
本来导出的模型有部分顶点被拉伸错位, 当时没有想到是这个原因, 在导出插件里加了很多isnan()检测和AABB越界检查, 以及flaot16跟float32的转换后比较, 最后发现是以上的原因.
最后干脆使用OpenEXR的half (https://github.com/openexr/openexr/blob/master/IlmBase/Half/half.h),
问题解决. OpenEXR的half实现是用了内建表加速转换, 具体细节没怎么看, 只看到对于half=>single presicon使用了65536个表项的表(静态数组)做全值映射.
导出的tangent space使用quaternion保存,这个之前测过没有问题. 但镜像贴图仍然有缝, crytek的pdf说tangent space的mirror模式缝很难避免, 只能通过好的tangent space生成算法来尽量改进.
这个模型是Gear of War3里面的模型, 除了面部没有使用镜像UV, 其他地方都用了. 可以看到中间是有缝的, 脖子下面有个倒L行的裂缝最明显(镜像UV到面部非镜像的过渡), 这个裂缝在贴了base map以后还可以看到(这个在MAX 2013 shaded模式里面也能看到), 而其他地方的缝, 带上贴图的时候不是特别明显, 除了离的非常近才能看到一点. 试了crytek(ShaderX4中)的方法(http://www.shaderx4.com/TangentSpaceCalculation.h),效果还是没太大改进. 打算暂时先这样吧, 以后找时间专门研究下.
然后无意间在地形渲染里发现一个严重的问题(可能上次地形更新后没有测这些-_-!), 原因已经找到, 但是不太好改:
由于上次的地形更新(http://www.cnblogs.com/crazii/archive/2013/06/01/3085449.html),
使用了layermap带替代per vertex layer buffer, 这个时候blendmap要跟layermap的纹素精确的的匹配.
如果有3张贴图,分别为A(红色贴图),B(白色贴图),C(绿色贴图) 的时候, 索引为 0, 1, 2; (蓝色半透部分为画刷)
2张贴图A,C的时候 索引为0, 2;
blendmap的混合通道为: Red, Green, Blue. Red对应第一张图即底图.
内圆有三张贴图的部分, Green通道权重为0(不显示贴图B). Blue通道为1对应贴图C,
外圆有两张贴图的部分, Green通道权重为1(全显示),对应贴图C. 所以Green通道在边缘处对应的两张贴图不一样, 通过blend weight的线性差值后, 内圆边缘处Green通道本来应该不显示的贴图B的, 但差值后中间有一部分不是0,导致边缘有缝隙.
layermap使用pointer sampling, 因为layermap保存的是atlas贴图的索引, 不能做线性插值, 否则索引就坏掉了. 如果blendmap改用point过滤(layermap, blendmap的精确匹配), 可以解决这个问题, 但是混合效果太丑, 有马赛克, 无法接受.
想了一下, 上次更新之前没,还没有问题, 是因为per vertex layer buffer实际上是per block(chunk), 即一个block上所有的vertex的layer都一样(有点浪费), 只有block之间的接缝处才有这样的问题, 而且之前blendmap的uv是预先计算的顶点数据, 在block边缘做了半个像素的偏移, 避免采样到邻接block, 所以没有缝.
现在layermap不是per block了,而是精确到每一个纹理元素, 而且没有规律, 不能做像素偏移. 即便把layermap改成per-block的精度, 但现在blendmap的UV已经没有vertex buffer, 而是在shader里面根据位置计算算, 两个block衔接处的顶点位置是一样的, 不知道是哪个block的哪一边, 确定不了偏移方式.所以没有办法在shader里做半像素的uv偏移. 除非这部分退回以前的方式, 使用vertex uv buffer保存blend map uv.
这个问题很恶心, 主要是使用了atlas以后, 还想支持一个draw call的batch合并, 产生的各种问题, 为了渲染正确, 已经做了不少work around了, 现在感觉很难处理.
目前打算把layer map做成per block,跟以前一样, 这样缝的问题只出现在block边缘, 然后的处理方式就比较多. 一种方法跟以前一样, 处理blendmap uv的偏移, 需要占用额外的内存/现存. 另一个方法是像WOW一样, blendmap也是per block, 这样就采样不到邻接block, 但是改动可能有点大, 而且这样block就不能合并draw call了. 魔兽世界地形的chunk是可以(根据情况)合并的因为他没有用atlas, 我这里用了atlas图集, 并且要支持batch合并.
还有一个方法是把blendweight放到vertex buffer里面, 由于每个block之间没有共享顶点, 不会有blend weight插值. 但这样纹理混合的精度有所损失,而且没有之前那样,精度可控(只要调整blend map的大小即可).
需要先考虑清楚有没有更好的办法, 再动手改. 后面可能先做骨骼动画的导出(mile stone 2的key feature, 拖了一年了), 也有可能先把这个问题修好.
更新:
尝试在shader里面使用bilinear采样, 代替硬件采样, 这个时候可以做一些特殊处理, 但效果不是特别好, 仍然有不明显的缝存在.
突然想起来, 如果layermap是per block的话, 没必要对一个block按顶点来保存一大块一模一样的数据, 把这一样的数据块合成一个像素, 采样出来的效果一样, 这样起码没有浪费.
更新2: 目前使用shader里面手动bilinear采样, 代替硬件采样的方式, 通过调整一些参数, 勉强可以了. 但是pixel shader多了20条指令(目前-O3已经达到190条左右), 其中有五条tex2D纹理采样. 低材质LOD的直接用点采样吧, 虽然能看到马赛克, 但是已经有点远了, 应该不是特别需要处理, 如果需要的话也用这种方式.
经过简单对比, 感觉效率比以前低了,之前的debug版, 一个512x512的tile, 在380-400FPS左右, 现在大约300-340FPS, 降低了50-60FPS的样子.
如果退回跟以前类似的的方式, 顺便把layermap处理一下, 按最大的tile大小512, 按最小的block大小为16, 而一个block对应一个像素, 则layermap 最大为 64*64*16(R4G4B4A4) 字节, 比以前小很多.blendmap的uv要加vertex stream并且预处理边界, 或者不添加顶点数据, 在现有的顶点数据里存一个标记, 在shader里面处理. 但现在不是很想改, 主要是因为那样改动太大, 而现在的改法, 改动很小. 所以目前暂时先这样吧, 也许对速度的影响不是特别大, 这次先记录下来, 以后如果真的不行, 可以再改.