渲染一个高精度的虚拟地球模型之前我们需要一个椭球体对地球进行表达,这章节我们将介绍这样一个表达的目的和数学知识,重点建立起一个可重构的椭球Ellipsoid类,它需要包含计算表面法线的函数、多种坐标系之间的转换函数、计算椭球表面上的曲线函数等等。
本章节与其他章节不同的地方就在于本章节包含了大量的数学知识和派生知识,而其他章节则是真正地介绍渲染引擎和渲染算法。你并不需要花大量时间去记忆这些用于构造一个虚拟地球的派生知识概念,只需要去对这些如何表达椭球体方法的数学知识有一个高度的认识和理解。
2.1 虚拟地球坐标系统
任何渲染图新引擎都包含一个或多个坐标系统,虚拟地球也不例外。虚拟地球集中于两种坐标系统:地理坐标系(用于描述全球表面上或者相对表面上的坐标点位置)、笛卡尔坐标系(用于渲染)。
2.1.1 地理坐标系
2.1.2 WGS84坐标系
2.2 椭球体基础
2.2.1 WGS84椭球体
WGS84椭球使用非常广泛,STK和Insight3D中使用它来描述很多虚拟球体,此外也有很多游戏使用它,如微软的飞行模拟器。在程序中处理全球形状最灵活的方式就是使用一个可以允许用户自定义半径参数的通用椭球类,这样就使得程序不仅可以支持WGS84椭球,还可以支持其他椭球体模型,如:月球、火星等等。在OpenGlobe中,椭球Ellipsoid类就是这样一个类。
2.2.2 椭球体表面法线
计算一个椭球体表面上某点的朝外表面法线有很多作用,如:阴影计算以及精确地定义地理坐标系的高度值(地表以上还是以下)。针对一个球体表面上的点而言,它的表面法线就是它本身点位坐标单位化后的向量值。同样地可以通过这种方法获得椭球体表面的某点法线,该法线称之为地心表面法线。之所以称之为地心表面法线,因为它是一个由地心点出发到椭球体表面上的某个点的向量。如果这个椭球体不完美(不接近圆球体,偏扁),这个向量则并不能准确表达椭球体表面上大部分点的法线。
另一方面,大地表面法线则是一种椭球体表面法线的准确表达。下图三种类型下的椭球体分别可以表示出上述两种法线的不同情况,椭球体越扁,二者差异越大,而后者很明显表达更准确些。
大地表面法线仅仅在计算上比地心表面法线更复杂些,计算公式如下:
实际中,这样一个元组是预先就计算好的,并随着椭球体一起存储好。从而大地表面法线的计算就简单地变为了一种上述预先参数值和具体点坐标的分量相乘,紧接着将其单位化即可。
在表单2.5中展示了一个相似的GLSL函数,这个预先参数向量通过一个oneOverEllipsoidRadiiSquared提供给着色器,而这个向量值则是一个事先由CPU一次性计算好的uniform值提供,于是接下来就可以用来在GPU作多次计算。一般来说,我们时刻寻找类似这种预先计算好的数值用来优化性能,特别是这种预先计算好的参数值只需较小的内存用于存储。
示例程序:运行示例程序Chapter02EllipsoidSurfaceNormals,如下图结果:
核心代码如下:
//! \ brief _oneOverRadiiSquared构造函数中计算好
public Ellipsoid(Vector3D radii)
{
if ((radii.X <= 0.0) || (radii.Y <= 0.0) || (radii.Z <= 0.0))
{
throw new ArgumentOutOfRangeException("radii");
}
_radii = radii;
_radiiSquared = new Vector3D(
radii.X * radii.X,
radii.Y * radii.Y,
radii.Z * radii.Z);
_radiiToTheFourth = new Vector3D(
_radiiSquared.X * _radiiSquared.X,
_radiiSquared.Y * _radiiSquared.Y,
_radiiSquared.Z * _radiiSquared.Z);
_oneOverRadiiSquared = new Vector3D(
1.0 / (radii.X * radii.X),
1.0 / (radii.Y * radii.Y),
1.0 / (radii.Z * radii.Z));
}
//! \ brief 分量方式相乘求取大地表面法线
public Vector3D GeodeticSurfaceNormal(Vector3D positionOnEllipsoid)
{
return (positionOnEllipsoid.MultiplyComponents(_oneOverRadiiSquared)).Normalize();
}
//! \ brief 分别计算大地表面法线和地心表面法线 Vector3D p = _globeShape.ToVector3D(new Geodetic3D(0, Trig.ToRadians(45), 0)); Vector3D deticNormal = _globeShape.GeodeticSurfaceNormal(p); Vector3D centricNormal = Ellipsoid.CentricSurfaceNormal(p);