注:对《添加模型》一文的一点补充,参考
http://blog.csdn.net/jk276993857/archive/2010/10/19/5951738.aspx
由于浮点数精度方面的问题,创建超大连续游戏世界或地理信息系统数字地球的开发者总是不可避免地陷入到麻烦中。几乎每个三维平台渲染矩阵都使用32位浮点数来表示坐标,然而当坐标值较大而同时要求不能有明显的精度错误时,浮点数就无法表示比第一人称视角射击游戏的场景更大的区域,也无法表达在全球范围下的城市级别地物。此前有《解决大型游戏世界坐标中的精度问题》和《虚拟城市中世界坐标的精度问题及解决方法》采用了基于基片(Segment)和偏移(Offset)的方法来解决此类问题。本文介绍了World Wind 1.4版本中解决世界精度问题的技术。
一、问题描述
在单精度32位IEEE浮点数格式中,1位是符号位,接下来8位是指数部分(E),剩下23位是规一化数(或数的科学表示法形式)的小数部分(F)。对于32位浮点数来说,精度等于其尾数最低位的位值或粒度。由于 第一位的位值是2^E(表示2的E次方)并有23位的小数,LBS的位值就是2^(E-23)。例如,非常接近于1的数(E=0,2^0=1),粒度就是2^(-23),约为0.0000001192。接近1000的数(E=8,2^8=1024),精度增加到0.000061(2^(-13)),接近100000(E=17,2^17=131072)的值,精度就是0.0078125(2^(-7))。用实际的术语来讲:以米为单位,我们的世界是边长为100公里的正方形,在我们世界的最远角落,32位浮点数仅能让我们来表示粒度为7.8毫米的空间。我们要表示的坐标系越大,在最远的范围内精度就越低。要表示全球的区域,地球半径为6370多公里,那将限于半米级别的精度。
精度误差的征兆:
1、 无法将相邻对象对齐
2、 网格中的裂缝
3、 动画抖动
4、 碰撞误差
所有这些误差,只要它们在较大坐标中出现,都可能被发现。如果你在很大的坐标上看到角色发生抖动,但在坐标原点上却一点问题都 没有,那几乎可以确定渲染出现了浮点精度问题。
在WW中,添加城市模型,在近距离观看时,就会出现动画抖动现象。
有必要说明的是:
1、 单位的选择并不影响精度误差。换句话说,无论是选择1公里、1米或者1毫米为单位,和出现精度误差问题时的比例无关。例如你用米来度量游戏世界在10000米处遇到的误差,和你选择公里作为度量单位 而在10公里遇到的误差是相同的。
2、 减小鼠标操作的最小粒度误差也不会修正此项精度误差。有的人通过减小鼠标操作中最小粒度误差能使动画抖动现象减轻,那是因为鼠标操作误差大于精度误差,使精度误差不明显,当鼠标误差小于精度误差时,无论做任何鼠标操作误差的优化,都不能消除由浮点精度产生的误差。
二、可能的解决方式
1、 使用高精度浮点类型
精度解决的最理想方式就是简单地采用较大浮点类型,如64位浮点数(double)使用了52位尾数,超过单精度浮点数多出的29位则提高 了相当于536870912倍精度。然而,在较低精度中的运算有明显的性能好处。再者,大多数图形API只处理单精度浮点数矩阵,图形硬件可能在更低精度中做内部操作。即使在整个变换坐标系中全部使用双精度浮点数,但只要在图形硬件还使用较低精度的转换,精度问题就仍然会出现。
目前WW中在计算过程中,均采用双精度计算,以减小由计算产生的误差传递效应,但是在使用D3D的API投影矩阵中,仍需要转换成单精度浮点类型。
2、 消除全局空间变换
大多数图形流水线包含了应用于顶点的变换序列,如下:
WorldMatrix->ViewMatrix->ProjectionMatrix
其中全局变换(WorldMatrix)从模型空间改变坐标,其中顶点是相对于模型本地原址而定义的,对于全局空间来说,顶点是相对于同一场景所有对象的原点而定义的。基本上,全局变换将模型放入World中,如其名字所示。视图变换(ViewMatrix)在全局空间中定位观察者,将顶点变换到照相空间。视图矩阵在camera位置周围重新定位整个世界中的对象和方向。在大型坐标中,WorldMatrix和ViewMatrix都可能有精度误差。
大多数引擎会将WorldMatrix、ViewMatrix、ProjectionMatrix单独地递交给渲染流水线,或是把ViewMatrix和ProjectionMatrix先连接,再与WorldMatrix连接,都会首先精度误差。但是,如果将WorldMatrix和ViewMatrix尽可能早地相连,而后与ProjectionMatrix连接的话,就可能跳过许多精度问题,这特别适用于解决动画抖动问题。
【WorldMatrix * ViewMatrix 】->ProjectionMatrix
虽然这可以减轻一些动画中比较明显的视觉问题,也可以作为解决精度问题的保留招数,但它无法解决基本问题,简单来讲,就是没有足够的精度来表示游戏世界中的位置。此外,只要视图矩阵改变(基本上就是每次camera移动的时候),它就会在每个网格引入额外的CPU级的转换。
3、 偏移位置
通常由三个浮点数组成的位置矢量,增加了代表浮点原点的额外整型数组件,由于该数据表达的方式类似于分段地址扩展早期32位处理器地址空间,组件用segment和offset来命名,其结合体叫做偏移位置。由于浮点数的精度是数量级相关的,离原点越远,粒度越大,重新定位原点,可以使它靠近我们需要度量的点,减少度量的粒度。偏移位置让原点能够动态定位,使得有效精度最大化。
在游戏世界模型中,高度是用Y轴表示,那么原点只需要在xz平面上重新定位,因为高度往往不会超出精度范围。而如果在三维数字地球的球面上,则无法简单的采用重新定位平面原点的方法来使得Y轴不超出精度范围,而只能考虑当观察区可被视为平面时采用。
三、World Wind中解决世界坐标精度问题的方法
1、 原理说明
World Wind中解决世界坐标精度问题的方法主要是基于以上说的第二种方法,通过将WorldMatrix和ViewMatrix的结合消除全局空间变换带来的误差,其中也用到了偏移位置的思想,采用相对中心的概念将原点移至球面。
首先,让我们看下World Wind 在未采用此方法时计算WorldMatrix、ViewMatrix、ProjectionMatrix的方法。如下图1绝对坐标系所示。
在世界坐标系中,以球心为原点,相机(camera)的坐标为(Xe1,Ye1,Ze1),观察点在球面上的坐标为(Xe0,Ye0,Ze0)。采用右手坐标系构建WorldMatrix、ViewMatrix、ProjectionMatrix。
float aspectRatio = (float)viewPort.Width / viewPort.Height;
float zNear = (float)this._altitude*0.1f;
double distToCenterOfPlanet = (this._altitude + this.WorldRadius);
double tangentalDistance = Math.Sqrt( distToCenterOfPlanet*distToCenterOfPlanet - _worldRadius*_worldRadius);
m_absoluteProjectionMatrix = Matrix.PerspectiveFovRH( (float)_fov.Radians, aspectRatio, zNear, (float)tangentalDistance );
m_absoluteViewMatrix = Matrix.LookAtRH(
MathEngine.SphericalToCartesian(
_latitude.Degrees,
_longitude.Degrees,
WorldRadius),
Vector3.Empty,
new Vector3(0,0,1));
m_absoluteViewMatrix *= Matrix.RotationYawPitchRoll(
0,
(float)-_tilt.Radians,
(float)this._heading.Radians);
m_absoluteViewMatrix *= Matrix.Translation(0,0,(float)(-this._distance - curCameraElevation));
m_absoluteViewMatrix *= Matrix.RotationZ((float)this._bank.Radians);
}
其中WorldMatrix为单位矩阵,即全局坐标均以球心坐标原点为参照,不进行偏移。
ViewMatrix构建的是从相机坐标(Xe1,Ye1,Ze1)到观察点(Xe0,Ye0,Ze0)方向的视图矩阵,其中(Xe1,Ye1,Ze1)和(Xe0,Ye0,Ze0)的原点均为球心。当相机不断靠近观察点时,这样(Xe1,Ye1,Ze1)走近于(Xe0,Ye0,Ze0),相当于要表示在地球半径距离的物体,如问题描述中所示,若采用32位浮点数精度只能表示半米级的精度,即在渲染矩阵上ViewMatrix就产生了精度误差,会产生严重的动画抖动现象。
同时,在表示地表物体时,例如在(Xe0,Ye0,Ze0)附近的地块,其坐标值与(Xe0,Ye0,Ze0)相近,此处也将造成地物坐标的精度误差。
接下来,我们来看采用相对中心如何解决精度误差的问题。如图2所示,令(Xe0,Ye0,Ze0)为相对中心原点(0,0,0),则相机坐标由原来的(Xe1,Ye1,Ze1)变为了(Xr1,Yr1,Zr1)。此时进行手动构建视图矩阵,如下:
// this constitutes a local tri-frame hovering above the sphere
Point3d zAxis = LookFrom.normalize(); // on sphere the normal vector and position vector are the same
Point3d xAxis = Point3d.cross(cameraUpVector,zAxis).normalize();
Point3d yAxis = Point3d.cross(zAxis, xAxis);
ReferenceCenter = MathEngine.SphericalToCartesianD(
Angle.FromRadians(Convert.ToSingle(_latitude.Radians)