Terrain 读书笔记:Chapter 5

<Real Time 3D Terrain Engines Using C++ And DX9>

Chapter 5. Fundamental 3D Objects

经过了漫长的前戏(rpwt -_-|||),终于到了讲核心技术的时候了。这是最后一章基础课了(基础课不是在Part I的时候都讲完了么?),讲的就是3D里面非常基础的一个话题——空间分割,果然是基础。

空间分割就是把world分成若干个部分,然后确定出哪些部分引擎应该去渲染,而哪些部分不需要。而且还可以用来确定每个部分的细节(就是该用什么级别的细节去渲染)。在Gaia中,空间分割是用四叉树来实现的。


the Motivation behind Scene Organization

任何render pipeline的第一步都是要确定哪些物体需要被渲染,这就是我们做空间分割的动机。查看一个物体是否可见,就是查看这个物体是否在视锥(frustum)当中。上次有人在bbs上问:那个三角形的东西是什么?我立即ft了。

物体只要有任何一部分在frustum中,就是可见的。比较简单的方法是用一个边界的box装住物体,那么一个物体只要测6次就可以知道它是否可见了(box的6个面)。

视锥中的每个面都可以看成是空间中的平面。每个都用一个法线(指向视锥内部)来表示正的半平面。如果一个物体的某个部分都是在这些半平面的正半面方向,那就是可见了。

有种简化的方法:把一个大空间分成小矩形,叫sector。那么只需要确定sector是否可见,如果可见,那么sector里面所有的object都要送去渲染。

这种方法获得了效率,却丢掉了精度。因为很多可见的sector中的object也是不可见的。那么有个改进方法:所有和frustum边界相交的sector都要再进一步的测试其中的每个object。

如果物体足够分散的话,其实还是相当于test了所有的object,这一点儿也没有效率上的提高。那么继续改进:把sector分成大块,然后再细分,变成一棵树。如果双亲不可见,那么孩子一定不可见。如果双亲可见,就继续test他们的孩子,递归下去。


the Basic Quadtree

四叉树和八叉树都是用树来表示空间关系。四叉树用于2D,将一个矩形分成4等分,每个可以继续细分。八叉树用于3D,把每个立方体分成8等分,然后继续细分。

因为Gaia中的垂直高度比较低,所以可以看成是一个平面,这也是不用octree而用quadtree的原因。不过以后我们可以对quadtree扩展,让它变成一个伪3D的树。

理论上quadtree中的节点数目是最小化的,就是说,如果一个分支上没有物体,它就是null了,它下面的节点也都被忽略了。但是这样做虽然节省空间,但是却缺乏动态效率。一旦有运动物体的话,增加删除节点的操作就太频繁了。

为了避免这种情况,Gaia中使用的是表示全部节点的quadtree,哪怕是null节点也要表示出来。这样就不用考虑增删节点了,不过得耗不少内存。

其实quadtree应该可以看成是一个2维的线段树。查找一个物体的algo如下:

Step 1. 查看当前节点所有的孩子。如果没孩子,goto Step 3
Step 2. 如果该物体被其中一个孩子节点完全包住,那么把这个孩子节点作为当前节点,然后goto Step 1
Step 3. 这个物体就是属于这个节点了,添加,然后Exit


Enhancing the Quadtree

因为查找添加节点的过程太耗时间了,如果是一个动态性很强的游戏的话,那么大部分的时间都要用于查找节点。所以,如果有一种直接能够确定节点插入位置的方法就好了。所幸的是Matt Pritchard给我们提供了一个这样的方法。(为了弄明白这个我还特意去看的<Game Programming Gems 2>)

如果一个四叉树每一层都可以用一个二维数组来表示的话,那么每个节点应该这么表示:

Node[Level][X][Y]

另外,对于这种方法有两个限制,不过Pritchard说这些都不算限制:

1)坐标轴必须是2的幂,比如256x256。如果不是的话是不是就不能用呢?哎,缩放一下就好了啊。

2)树的层数要提前确定。这个对于很多情况下都是提前确定了的。记最大层数为MaxLevel。

对于一个物体,如何确定它的Level和XY的值呢?比如我们的坐标是256x256的,MaxLevel=7,有一个矩形物体,左上角是(190,110),右下角是(195,125)。那么我们需要计算两个量,一个是它在X轴上的两端的值取异或,一个是它在Y轴上的两端的值取异或。如下:

     10111110 (190)
Xor  11000011 (195)
--------------------
     01111101

     01101110 (110)
Xor  01111101 (125)
--------------------
     00010011

这时候,查看他们的最高位所在的位置,然后用最大层数去减,确定在特定轴上的层数:

LevelX = MaxLevel - HighBitSet(1111101) = 7 - 6 = 1
LevelY = MaxLevel - HighBitSet(10011)   = 7 - 4 = 3

然后这个物体的层数是他们的较小者:

Level = MIN( LevelX, LevelY ) = 1

这样就确定了它的层数。下面就需要确定它的位置了。位置是使用物体的任意一个点作右移,右移的位数应为:

ShiftBit = MaxLevel - Level + 1 = 7 - 1 + 1 = 7

然后,分别计算XY的值(取的任意点为物体左上角的点,X1=190,Y1=110):

X = X1>>ShiftBit = 1
Y = Y1>>ShiftBit = 0

则这个物体所属的节点就是Node[1][1][0]了。

都是位运算,看上去都觉得有效率了……


Adding Another Dimension to the Quadtree

如果想给空间加上Z轴的话,就要使用octree了。但是这样的话效率就大打折扣了(quadtree总比octree复杂度低吧)。那么,采用一个比较折衷的方法来建立一个伪Z轴,就是在Z轴上设32个层,用32bit来描述。如果一个物体在第4567层的话,它的高度就是11110000(前24位都是0)。

那么如果插入一个物体的话,就用OR一下节点的高度域。如果测试物体是否属于特定层的话,就AND一下(得到非零值就是有了)。

又是位运算,看来引擎的确是个追求效率的东西。


Fast Quadtree Searches

想查找一个3D的体积,就得把它转化为一个2D的Shape和一个32bits的高度,然后用Shape和这个高度查找。

比如有个给定的体积,那么就先查根。如果这个节点的z包括了这个体积的z,那么就查它的四个孩子。这样一直查下去,如果这个节点的某个object和这个体积相交了,就把这个节点加到一个Link List后面。当查找完成的时候,这个Link List就是最后的结果了。

另外,如果一个节点的z-mask改变了的话,它的祖先也要相应进行调整。


Slow Quadtree Searches

有快的为啥要慢的?慢的意味着更多的比较和操作。其实,用这个的主要原因就是用来查找视锥中的物体集合的。

视锥的形状不规矩,是一个锥形的。那么你可以用一个box来包住视锥,然后用box + Fast Search进行查找,这样的话效率是有了,但是会错误的查到很多本不属于视锥但是却属于box的物体。所以,为了避免这种情况,就直接用视锥去查了。

这个是可选的。如果使用快速渲染的话,就可以用Fast Search,因为多出来的物体就算渲染了也费不了太多时间。但是如果是使用一大堆Shader作高级渲染的话,还是采用Slow Search的好,因为这样会节省更多的渲染时间。


学过了这章,至少明白了什么是Quadtree,虽然还没有机会使用它,却已经感受到了它的强大。本来这章不需要看这么久的,只是这周忽然莫名其妙的大病了一场,所以耽误了许久,今天终于把剩下的一点点看完。基础知识都学完了,以后就要进入真正的地形学习了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值