在这种依赖关系中,对于任意一个顶点,最多有四个孩子,如上图Level 1中右边所示,中点的四个孩子分别为周围的四个边上的中点(而对于在整个网格上的边界上的点,就会出现孩子不足四个的情况);一个点,最多有两个父亲,如Level 1中的左边所示,中点的两个父亲分别为对角线上的两个点(同样不足的情况在网格的边界上会出现)。
首先对于一个顶点,他有一个自身的Error,这个Error的产生来源于该顶点保留和被Removed掉之间的垂直距离。
如图:
红色部分即为这个Error。
检测一个顶点是保留还是移除
记这个Err_w代表这个图中的Error,这个时候Err_w是在World Space中的。
这个方法的大致过程为:计算Err_w在屏幕空间的Err_s,在检测这个Err_s是否超出给定的一个Threshold,如果超出,表示误差过大,需要保留该点,否则表示该点可以移除。
具体过程:
记:
View Space的基向量为 : EVx, EVy, EVz
Eye Position : EPos = (ex, ey, ez)
Vertex Position : VPos = (vx, vy, vz)
总是假设Vertex Position就是当前的Camera看出去的中心,并且记
V+ = VPos+(0, Err_w/2, 0)
V- = VPos-(0, Err_w/2, 0)
(这里Height Map以Y轴向上,因此Error产生的方向在Y方向)
Ve+和Ve-为V+和V-对应在View Space中的点
则建立模型
首先计算出在View Space中需要计算的距离,记为De
Ve+-Ve- = (V+-V-)*ViewMatrix
根据假设
EVz_new = Normalize(EPos-VPos)
ViewMatrix = | EVx.x EVy.x EVz_new.x 0 |
| EVx.y EVy.y EVz_new.y 0 |
| EVx.z EVy.z EVz_new.z 0 |
| -Epos*EVx -EPos*EVy -EPos*EVz_new 1 |
则
Ve+-Ve- = (0, Err_w, 0)*ViewMatrix = Err_w*(EVx.y, EVy.y, EVz_new.y)
计算出Ve+-Ve-在View Space中的xy平面上的投影,然后根据比例即可得到De
则
De = Near*Project_XY(Ve+-Ve-)/|EPos-VPos|
这里
Project_XY(Ve+-Ve-)为在View Space中XY平面上的投影距离即
Project_XY(Ve+-Ve-) = Sqrt((Err_w*EVx.y)^2+(Err_w*EVy.y)^2) = Err_w*Sqrt(EVx.y^2+EVy.y^2)
Near为近平面距离
而ViewMatrix的左上角的3x3子矩阵为标准正交阵则:
(EVx.y, EVy.y, EVz_new.y)为单位向量
De = Near*Err_w*Sqrt(1-EVz_new.z^2)/|EPos-VPos|
有了De,要计算出在屏幕空间的距离,记为Ds,只要知道在View Space中在近平面上一个单位距离有多少像素点即可,记这个值为L
L = Wscreen/(2*Near*tan(Fov/2)), 这实际上是一个常量
其中:
Wscreen : 屏幕宽度
Fov : Camera张角
Ds = De*L = L*Err_w*Near*Sqrt(1-EVz_new.z^2)/|EPos-VPos|
其中 EVz_new = Normalize(EPos-VPos)
这就是最后的表达式
后面需要比较的就是Ds 与 给定的在屏幕空间的一个Threshold。
可见最后的结果只涉及到了EPos和VPos,其他都是常量。
对上面过程的说明:
上面整个过程都是基于一个假设,这个点是Camera看出去的中心,也就是说当这个点真正的处在了假设的位置,Ds才是正确的。而越是偏离的越远,这个计算的Ds就会与真实值偏差越大。
并且根据最后的结果表达式看,只涉及到了EPos和VPos,而与Camera的观察方向没有关系。
所以两者的比较
则假设当中计算出来的值可能偏大也可能偏小,根据原文当中的话“an artifact that is often acceptable as human visual perception degrades toward the periphery”。
该文中介绍的方法来自于:
P. Lindstrom, D. Koller, W. Ribarsky, L. F. Hodges, N. Faust,and G. A. Turner. “Real-time, continuous level of detail rendering of height fields”
Construct Enabled Dependency Graph By Bottom-Up Algorithm
对于在整个Height Map上的所有点,如果一个顶点
1:经过Error Detection没有被移除,那么这个顶点将被Enable
2:这个顶点有一个孩子是Enabled的,那么这个顶点被Enable
对于整个Height Map, 划分为多个Block, 每一个Block的宽度为2^n+1, 因此一个Block的最大Level为n, 整个Height Map只有一个Dependency Graph, Enable Dependency Graph构建在其上。
构建的算法
对每一个可见的Block执行下面的算法
1:以当前选择的LOD Level作为当前Level
2:对当前的Level上的每一个Quad,从左到右,从上到下的顺序遍历Quad,执行如下算法
1:计算出Quad的Non-Persistent Vertex
2:对4个Edge-Middle Vertices,如果该顶点没有Enable,进行Error Detection,如果没有通过Error Detection,则执行Enable操作;否则对该点无操作。
3:对Center执行2中同样的操作
3:如果已经是Lowest Level,停止;否者Level -= 1, 转2
Enable操作
1:该点Enable标示置为true
2:如果Left Parent没有Enable,对Left Parent执行Enable操作
3:如果Right Parent没有Enable,对Right Parent执行Enable操作
注:
1:这里的Left Parent和Right Parent实际上就是一个顶点的两个Parent,Left和Right只是一个规则
2:Enable操作是一个扩散的过程,本Block会将Enable扩散到其他的Block上去,所以就避免了Block之间的Crack的出现
对于上面的算法,遍历Level的时候是从下往上的遍历,最坏情况是每一个点都Enable,即每一个顶点做了一次Enable操作,所以复杂度是O(n),n为顶点数目。
LOD设计
首先对于一个Block中,一个Level上所有顶点的Error的最大值为该Level的最大Error。
我们定义在每一个Level
1:当前Level可往下看多少层,如果为k,则需要取出下k层中最大的Error,作为当前Level的PeekError
2:当前Level可允许的差距Level值,该值用于在Triangulate该Block时候和实际到达最大深度与当前LODLevel的差值进行比较
LOD过程(在每一帧中检测)
1:LOD_TAG记为NO_OP
2:遍历一个Level的Quad时候,如果当前Level为LOD Level,进行下面的过程
1:NeedLower = true, NeedHigher = false
2:取出LOD Level的PeekError
3:对每一个顶点,如果该顶点需要Error Detection,则:1,如果该点没有通过Detection,则NeedLower=false;2,以PeekError作为该点的Error进行Detection,如果没有通过Detection时,则NeedHigher = true
4:如果NeedHigher为true,则LOD_TAG = NEED_HIGHER;否则,如果NeedLower为true,则LOD_TAG = NEED_LOWER
3:Triangulate该Block的时候,如果LOD_TAG!=NEED_HIGHER,则取出当前LOD Level的可容许Level差值,记为k,在Triangulate过程中,如果发现存在一个三角形,他所在的Level-LOD_Level的结果大于k,则LOD_TAG = NEED_HIGHER
4:如果LOD_TAG为NEED_HIGHER则,++LOD;否则如果为NEED_LOWER,--LOD。
Triangulate操作
首先在一个Quad中
将会出现上面的八种三角形
Top --> LB + RB
Right --> LU + RU
Bottom --> ...
Left --> ...
这四种划分是属于同一个Level上的,所以上面图中的三角形是属于同一个Level上的
LB --> Top + Right
RB --> Left + Top
RU --> ...
LU --> ...
这四种划分是一级Level向高一级Level的
定义:在一个三角形中,定义这个三角形的斜边上的中点为这个三角形的Base Vertex。
对于一个三角形,如果他的Base Vertex是Enable的,那么这个三角形需要分裂,否则直接选择这个三角形。
因此对于整个Block的Triangulation,只需要以这个Block的两个对角三角形开始,从上往下进行Base Vertex判定。同样只对于可见的Block执行这个过程。
因为使用Vertex Dependency Graph,这可以很好的避免了临接Block上的Crack的问题。
Reference :
P. Lindstrom, D. Koller, W. Ribarsky, L. F. Hodges, N. Faust,and G. A. Turner. "Real-time, continuous level of detail rendering of height fields"
Renato Pajarola, "Large Scale Terrain Visualization Using The Restricted Quadtree Triangulation"
Samples
使用513x513的Height Map,65x65的Block。从该Sample可以看出,在Screen Error Threshold变大的过程中,注意远处的三角形,和对于复杂地表部分的三角形的变化。
使用513x513的Height Map,65x65的Block。该Sample,可以看到在Screen Error Threshold变大的时候,各个Block的LOD的变化(左边红色部分)