Terrain Splatting
spalatting技术指的是在terrain中给指定的tile贴纹理,并且所纹理之间是非线性过渡的.这种技术基本上不耗费CPU,通过显卡多次渲染同一个三角形,在帧缓存中合成纹理.
基础Splatting
把terrain分成若干个块,对每个块,找出影响它的所有贴图,即块中所有单元(一个或多个tile,取决于你的粒度)所用的贴图和紧邻这个块的单元所使用的贴图.这就是所谓的"splat块"和"splat纹理".为每个块创建顶点缓存,优化.
对当前块中的每个splat纹理,取所有使用该纹理或与该纹理相邻的单元,创建三角形列表,优化.每个三角形列表就称为一个splat.每个splat块就由若干个相互交叠的splat组成.
为每个块生成alpha值,让它沿着边界淡出,可以使用顶点的alpha值,但这样要修改顶点缓存,所以通过生成一张alpha纹理来完成.为每个splat创建一张覆盖整个块的纹理,精度为每个单元2*2像素,对每个像素,找出splat纹理对应的权重.此权重由周围9个单元的纹理影响计算得出,每个texel对权重的影响为从对应单元的中心点按距离递减,到1.75个单元时为0,公式为: Weight(pt1, pt2) = 1 - DistanceSquared(pt1, pt2) / (1.75)^2)(可以设计自己的权重计算公式).计算出所有权重后把它们归一化,使它们相加为1.计算结果为每个splat一张alpha map.
现在从splat纹理中取颜色,alpha map中取alpha值来渲染splat.可以利用multi-texturing: 背景纹理为Stage 0,alpha map为Stage 1.D3D设置如下:
device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
// Stage 0 alpha: nada
device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
device->SetTextureStageState(0, D3DTSS_ALPHAARG1, DECTA_CURRENT);
// Stage 1 coloring: nada
device->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
device->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
// Stage 1 alpha: texture1
device->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
device->SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
注意每个splat的uv不同,在创建顶点缓存时使用标准网格的uv,然后为每个splat使用纹理矩阵把网格映射到对应的tile上,具体说来,Stage 0(splat纹理)的uv每几个单元用一个,Stage 1(alpha map)uv拉伸到整个块.
对不支持multi-texturing的硬件:
对每个splat,首先用alpha map做为纹理进行渲染,在帧缓存中写入alpha值(32位帧缓存),然后用splat纹理再渲染一遍,取帧缓存中的alpha值(destination alpha)来做混合.
以上方法有一个细节问题,在两个splat相交处,第一个splat以50%的alpha和帧缓存颜色混合,第二个splat又以50%和当前颜色混合,所以最终结果的25%会来自以前帧缓存中的颜色.最简单的修正办法是先以terrain中最常用的纹理渲染一遍整个terrain,之后的步骤中就不用再构造这个纹理的splat.
这样所完成的splatting,纹理之间的过渡是线性的(其实是二次的,但非常规则),这种方法得到的纹理有很高的细节.注意要设置splat之间的顺序,比如给每种纹理设置一个优先级,不同顺序渲染出来的效果不一样.
增强Splatting: 1.基础Pass和淡出
可以制作一个"基础纹理(base texture)"来修补splat之间的空洞.通过一个pass来生成这个基础纹理,大小为每个单元4*4 texels.把所有splat纹理用权重(跟上面的alpha map计算方法一样)混合起来得到基础纹理,基础纹理的精度很低,所以使用splat纹理的4*4 mip生成就够了.这样就得到了一张精度很低的terrain贴图.这次就不需要用前面的方法了,在splat过渡的地方会显示基础纹理.
有了这个基础纹理,就产生了另一种可能,在远处只使用基础纹理就足够了,不需要splat.所以我们让splat淡出,到一个指定的距离时关闭.跟制作细节纹理(details texture)的方法相同: 使用顶点alpha,或者把淡出加入到alpha map的mips中.即对alpha map过滤,使产生的mips越来越透明,到4*4 mip(或指定距离对应的mip)时完全透明.这种技巧只有在需要绘制很远的地方时才有优势.
基础纹理和alpha map都会有通常的块状问题,即在块的边界处由于过滤产生的问题.可以通过沿边界复制像素和把uv起点向纹理内移动半个像素来解决.
增强Splatting: 2.调整alpha值
有许多方法可以调整alpha map中的alpha值.第一种很简单:把权重的和归一化到比1大的值,然后截断到1.这样就能让基础纹理显示得少点高层纹理显示得多些.
另一种方法是基于所在的splat层偏移alpha值.增大底层的alpha,减少顶层的alpha.方法是对于每个单元,生成alpha map时检查所有相邻的splat,就可以得到当前纹理所在的层.算出下面和上面的层数,然后把alpha值乘以(number_above + 1)/(number_below + number_above + 1).
最后还可以给alpha增加一点随机值来改进效果.
增加Noise
增加noise可以让splatting纹理的过渡看起来不规则.这里的noise是splat纹理的alpha通道.在绘制时纹理和alpha通道一起绘制,在noise pass中使用.生成基础纹理时忽略alpha通道.
noise pass简单地重新渲染一遍splat,把这个alpha混合进去.D3D设置:
device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
// Stage 0 alpha
device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
// Stage 1 coloring
device->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
device->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
// Stage 1 alpha
device->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
device->SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_CURRENT);
device->SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_TEXTURE);
noise alpha通道通常最大值在200左右,不会完全不透明.在splat的中间部分,noise pass不起作用,只是重画一遍像素,在过渡区域,会使得纹理产生不规则的影响.因为不用渲染基础纹理,noise pass增加的三角形数量会少于一倍.noise也可以和splat一样淡出,通常会比splat淡出得更快.因为渲染的是同一个顶点缓存所以noise alpha效率很高.