在这里,
Ct
指遍历整个节点包含的三角形遍历时间,也就是渲染这一簇三角形的时间;
Cl
指
Split
Plane“
左边”的三角形遍历时间,同理
Cr
是“右边”的遍历时间。引用的一些资料认为,
事实上,
Cost(x)
是两个单调函数
Cl(x)
、
Cr(x)
的线性叠加。我们要尽量找到
Cost(x)
最小的某个空间位置,或者是某一个三角形边界,进而分割场景三角形。
显然,这肯定由三步组成:
1、选择合适的
Split
Plane
,可以有三个维度,并且可以容易推断总共有
2n
个
2、数数看左边有多少个
AABBs
3、数数看右边有多少个
AABBs
下面我们开始讨论
2
种方法:排序与扫描。
Sort
排序
排序很容易理解。沿着轴将所有三角形的
AABB
排序(也就是对三角形索引数组进行排序)。排序后就可以直接把想要得位置和三角形数目带入公式计算,如果绘制为函数图像可以获得最光滑连续的图形。总花费为
O(nlogn)
。
Scan
扫描
扫描有些罗嗦,不过也很容易实现,最重要的是目前已经有成功的应用
[4]
。指定场景中的一个平面,遍历三角形比较大小,记录落在左边和右边的数目,再带入公式。如果要获得多个
Cost
数值,就必须要选择多个位置进行计算。每次都要遍历全部的三角形。经过处理后也可以达到
O(nlogn)
的复杂度,不过结合了采样技术后可以获得近似准确的结果,而且花费小的多。而且,这个过程是可以使用
SIMD
加速的。
还有一些技巧和疑问
[3]
:
1、是否总是计算三个轴中最小的那个位置,还是总是从一个轴计算?
2、树需要细分到何种程度?每个
Leaf
需要有多少个三角形?
现在我们可以做进一步的推测,如果希望用光线跟踪一个动态的场景,那么每当我们变换过矩阵后,都需要重新构造一次
kD
树,所以为了达到实时交互式的速度,必须要对关键的步骤进行优化。而且生成树的质量与遍历的性能关系十分密切。
我们的思路有了,下面可以构思具体的实现过程
A.
准备三角形索引数组
Tris[]
。
B.
获得整个场景的
AABB
V
,其中
V.min[0]
就是场景的左边界,以此类推。
C.
通过三角形索引数组读取
3
个顶点,构造每个三角形的
AABB
,储存到一个容器中
vector<AABB>
AABBs
,同时记录
AABB
究竟属于哪个三角形。
D.
获得三角形
AABBs
的数目
N
,如果大于一个数值比如
1024
就用步骤
FG
计算
3
次,否则只计算一次。获得
AABBs
的开头和末尾指针(迭代器)。
E.
如果选择
Sort
,那么就要先对
AABBs
中的元素排序。这里我们采用
Scan
。
F.
在
(min[AXIS],max[AXIS])
之间找几个固定平面,比如
8
个。
AXIS
指选择的坐标轴。
H.
带入公式计算,选择
Cost
最小的
Split
Position
,生成
Node
。如果已经达到中止条件,那么就生成
Leaf
Node
,否则生成
Inner
Node
。把
v
从
Split
Position
分成两个
Voxel
Vl
与
Vr
。把
AABBs
分割为两个,从与
Split
Position
最近的三角形开始。
G.
遍历
AABBs
容器,比较平面和每个元素的位置,记录
8
对数据。
I.
带入
Vl
与
Vr
分别从
D
开始迭代。
下面是分隔这个模型的输出信息:
TriNum :
16220
Left Voxel has :
12377
Right Voxel has :
384
Left Voxel has :
2912
Right Voxel has :
946
Left Voxel has :
1025
Right Voxel has :
188
Left Voxel has :
415
Right Voxel has :
610
Left Voxel has :
128
Right Voxel has :
287
Left Voxel has :
68
Right Voxel has :
60
Leaf LEVEL :
5
Count :
128
Left Voxel has :
145
Right Voxel has :
142
Left Voxel has :
77
Right Voxel has :
68
Leaf LEVEL :
6
Count :
145
Left Voxel has :
61
Right Voxel has :
81
Leaf LEVEL :
6
Count :
142
Left Voxel has :
218
Right Voxel has :
392
Left Voxel has :
97
Right Voxel has :
121
Leaf LEVEL :
5
Count :
218
Left Voxel has :
211
Right Voxel has :
181
Left Voxel has :
114
Right Voxel has :
97
Leaf LEVEL :
6
Count :
211
Left Voxel has :
82
Right Voxel has :
99
Leaf LEVEL :
6
Count :
181
Left Voxel has :
772
Right Voxel has :
111
Left Voxel has :
373
Right Voxel has :
399
Left Voxel has :
194
Right Voxel has :
179
Left Voxel has :
82
Right Voxel has :
112
Leaf LEVEL :
6
Count :
194
Left Voxel has :
111
Right Voxel has :
68
Leaf LEVEL :
6
Count :
179
Left Voxel has :
190
Right Voxel has :
209
Left Voxel has :
99
Right Voxel has :
91
Leaf LEVEL :
6
Count :
190
Left Voxel has :
63
Right Voxel has :
146
Left Voxel has :
516
Right Voxel has :
599
Left Voxel has :
246
Right Voxel has :
270
Left Voxel has :
124
Right Voxel has :
122
Leaf LEVEL :
6
Count :
246
Left Voxel has :
126
Right Voxel has :
144
Left Voxel has :
285
Right Voxel has :
314
Left Voxel has :
150
Right Voxel has :
135
Left Voxel has :
152
Right Voxel has :
162
Left Voxel has :
4180
Right Voxel has :
528
Left Voxel has :
2220
Right Voxel has :
196
Left Voxel has :
1211
Right Voxel has :
100
Left Voxel has :
502
Right Voxel has :
709
Left Voxel has :
235
Right Voxel has :
267
Left Voxel has :
343
Right Voxel has :
366
Left Voxel has :
471
Right Voxel has :
538
Left Voxel has :
201
Right Voxel has :
270
Left Voxel has :
263
Right Voxel has :
275
Left Voxel has :
924
Right Voxel has :
103
Left Voxel has :
428
Right Voxel has :
496
Left Voxel has :
184
Right Voxel has :
244
Left Voxel has :
286
Right Voxel has :
210
Left Voxel has :
440
Right Voxel has :
596
Left Voxel has :
211
Right Voxel has :
229
Left Voxel has :
310
Right Voxel has :
286
Left Voxel has :
2437
Right Voxel has :
284
Left Voxel has :
1228
Right Voxel has :
120
Left Voxel has :
558
Right Voxel has :
670
Left Voxel has :
303
Right Voxel has :
255
Left Voxel has :
298
Right Voxel has :
372
Left Voxel has :
578
Right Voxel has :
631
Left Voxel has :
295
Right Voxel has :
283
Left Voxel has :
302
Right Voxel has :
329
Left Voxel has :
1466
Right Voxel has :
138
Left Voxel has :
805
Right Voxel has :
661
Left Voxel has :
430
Right Voxel has :
375
Left Voxel has :
346
Right Voxel has :
315
Left Voxel has :
653
Right Voxel has :
729
Left Voxel has :
347
Right Voxel has :
306
Left Voxel has :
367
Right Voxel has :
362
Left Voxel has :
2319
Right Voxel has :
152
Left Voxel has :
1413
Right Voxel has :
906
Left Voxel has :
825
Right Voxel has :
588
Left Voxel has :
437
Right Voxel has :
388
Left Voxel has :
180
Right Voxel has :
257
Left Voxel has :
98
Right Voxel has :
82
Leaf LEVEL :
6
Count :
180
Left Voxel has :
150
Right Voxel has :
107
Left Voxel has :
191
Right Voxel has :
197
Left Voxel has :
90
Right Voxel has :
101
Leaf LEVEL :
6
Count :
191
Left Voxel has :
68
Right Voxel has :
129
Left Voxel has :
294
Right Voxel has :
294
Left Voxel has :
150
Right Voxel has :
144
Left Voxel has :
91
Right Voxel has :
59
Leaf LEVEL :
6
Count :
150
Left Voxel has :
51
Right Voxel has :
93
Leaf LEVEL :
6
Count :
144
Left Voxel has :
153
Right Voxel has :
141
Left Voxel has :
105
Right Voxel has :
48
Leaf LEVEL :
6
Count :
153
Left Voxel has :
49
Right Voxel has :
92
Leaf LEVEL :
6
Count :
141
Left Voxel has :
457
Right Voxel has :
449
Left Voxel has :
214
Right Voxel has :
243
Left Voxel has :
124
Right Voxel has :
90
Leaf LEVEL :
5
Count :
214
Left Voxel has :
102
Right Voxel has :
141
Left Voxel has :
79
Right Voxel has :
62
Leaf LEVEL :
6
Count :
141
Left Voxel has :
196
Right Voxel has :
253
Left Voxel has :
99
Right Voxel has :
97
Leaf LEVEL :
5
Count :
196
Left Voxel has :
133
Right Voxel has :
120
Left Voxel has :
56
Right Voxel has :
77
Leaf LEVEL :
6
Count :
133
Left Voxel has :
791
Right Voxel has :
733
Left Voxel has :
462
Right Voxel has :
329
Left Voxel has :
228
Right Voxel has :
234
Left Voxel has :
129
Right Voxel has :
99
Left Voxel has :
64
Right Voxel has :
65
Leaf LEVEL :
6
Count :
129
Left Voxel has :
117
Right Voxel has :
117
Leaf LEVEL :
5
Count :
234
Left Voxel has :
171
Right Voxel has :
158
Left Voxel has :
90
Right Voxel has :
81
Leaf LEVEL :
5
Count :
171
Left Voxel has :
88
Right Voxel has :
70
Leaf LEVEL :
5
Count :
158
Left Voxel has :
423
Right Voxel has :
310
Left Voxel has :
205
Right Voxel has :
218
Left Voxel has :
114
Right Voxel has :
91
Leaf LEVEL :
5
Count :
205
Left Voxel has :
112
Right Voxel has :
106
Leaf LEVEL :
5
Count :
218
Left Voxel has :
177
Right Voxel has :
133
Left Voxel has :
86
Right Voxel has :
91
Leaf LEVEL :
5
Count :
177
Left Voxel has :
77
Right Voxel has :
56
Leaf LEVEL :
5
Count :
133
其中,
C
可以用
foreach
,
H
可以用
partition
(
stable_partition
)等
STL
的算法。
核心的代码如下:
void
Count(AABB
*
BeginItr,AABB
*
EndItr,AXIS Axis,
float
SplitPos,
ulong
*
Cl,
ulong
*
Cr)
{
for
( AABB
*
itr
=
BeginItr ; itr
!=
EndItr; itr
++
){
(
*
Cl)
+=
( (itr
->
max[Axis]
<
SplitPos )
?
1
:
0
);
(
*
Cr)
+=
( (itr
->
min[Axis]
>
SplitPos )
?
1
:
0
);
}
};
inline
bool
Terminate(
ulong
TriCount,
int
Level)
{
if
( TriCount
<
128
||
Level
>
MAX_LEVEL){
return
true
;
}
return
false
;
};
inline
bool
IsUpboundSmaller(AABB
&
AABB)
{
float
half_width
=
( AABB.max[::gSplitAxis]
-
AABB.min[::gSplitAxis] )
*
0.5f
;
return
(AABB.max[::gSplitAxis]
-
::gSplitPos)
<
half_width
?
true
:
false
;
};
float
SAHCost(AXIS Axis,
ulong
*
Cl,
ulong
*
Cr,AABB
&
V,
float
SplitPos)
{
ulong
Nl
=
*
Cl;
ulong
Nr
=
*
Cr;
float
Length
=
V.max[Axis]
-
V.min[Axis];
float
L
=
SplitPos
-
V.min[Axis],R
=
SplitPos
-
V.max[Axis];
float
Pl
=
L
/
Length;
float
Pr
=
1.0f
-
Pl;
float
Tt
=
TRAVERSAL_TIME;
float
Til
=
Nl
*
INTERSECTION_TIME,Tir
=
Nr
*
INTERSECTION_TIME;
return
Tt
+
Pl
*
Nl
*
Til
+
Pr
*
Nr
*
Tir;
};
void
KDTreeBuild(KDNode
*
Root,AABB V,AABB
*
BeginItr,AABB
*
EndItr,AXIS Axis,
int
Level)
{
ulong
TotalTriCount
=
EndItr
-
BeginItr;
if
( Terminate(TotalTriCount,Level) )
return
;
float
SplitLoc[
7
];
float
Cost[
7
];
ulong
Cl
=
0
,Cr
=
0
;
float
MinLoc
=
V.min[Axis],MaxLoc
=
V.max[Axis];
float
step
=
(MaxLoc
-
MinLoc)
/
8.0f
;
for
(
int
i
=
0
;i
<
7
;i
++
){
SplitLoc[i]
=
MinLoc
+
step
*
float
(i
+
1
);
Count(BeginItr,EndItr,Axis,SplitLoc[i],
&
Cl,
&
Cr);
Cost[i]
=
SAHCost(Axis,
&
Cl,
&
Cr,V,SplitLoc[i]);
Cl
=
Cr
=
0
;
}
float
*
pGoodCostPtr
=
min_element(Cost,Cost
+
7
);
size_t _Pos
=
pGoodCostPtr
-
Cost;
::gSplitPos
=
SplitLoc[_Pos];
::gSplitAxis
=
Axis;
AABB
*
MidItr
=
stable_partition(BeginItr,EndItr,IsUpboundSmaller);
Cl
=
MidItr
-
BeginItr;
Cr
=
EndItr
-
MidItr;
cout
<<
"
Left Voxel has :
"
<<
Cl
<<
"
\tRight Voxel has :
"
<<
Cr
<<
endl;
AABB Vl,Vr;
if
( Cr
>
128
||
Cl
>
128
){
Root
=
new
KDNode;
//
Root
Root
->
Left
=
new
KDNode;
Root
->
Right
=
new
KDNode;
Root
->
tag.SplitPos
=
::gSplitPos;
Root
->
Count
=
Axis;
//
Ulong.MaxSize - Axis 代表坐标 因为Left Right肯定不是0
switch
(Axis){
case
X :
Vl.min[
0
]
=
V.min[
0
];
Vl.min[
1
]
=
V.min[
1
];
Vl.min[
2
]
=
V.min[
2
];
Vl.max[
0
]
=
::gSplitPos;
Vl.max[
1
]
=
V.max[
1
];
Vl.max[
2
]
=
V.max[
2
];
Vr.min[
0
]
=
::gSplitPos;
Vr.min[
1
]
=
V.min[
1
];
Vr.min[
2
]
=
V.min[
2
];
Vr.max[
0
]
=
V.max[
0
];
Vr.max[
1
]
=
V.max[
1
];
Vr.max[
2
]
=
V.max[
2
];
break
;
case
Y :
Vl.min[
0
]
=
V.min[
0
];
Vl.min[
1
]
=
V.min[
1
];
Vl.min[
2
]
=
V.min[
2
];
Vl.max[
0
]
=
V.max[
0
];
Vl.max[
1
]
=
::gSplitPos;
Vl.max[
2
]
=
V.max[
2
];
Vr.min[
0
]
=
V.min[
0
];
Vr.min[
1
]
=
::gSplitPos;
Vr.min[
2
]
=
V.min[
2
];
Vr.max[
0
]
=
V.max[
0
];
Vr.max[
1
]
=
V.max[
1
];
Vr.max[
2
]
=
V.max[
2
];
break
;
case
Z :
Vl.min[
0
]
=
V.min[
0
];
Vl.min[
1
]
=
V.min[
1
];
Vl.min[
2
]
=
V.min[
2
];
Vl.max[
0
]
=
V.max[
0
];
Vl.max[
1
]
=
V.max[
1
];
Vl.max[
2
]
=
::gSplitPos;
Vr.min[
0
]
=
V.min[
0
];
Vr.min[
1
]
=
V.min[
1
];
Vr.min[
2
]
=
::gSplitPos;
Vr.max[
0
]
=
V.max[
0
];
Vr.max[
1
]
=
V.max[
1
];
Vr.max[
2
]
=
V.max[
2
];
break
;
}
}
else
{
Root
=
new
KDNode;
//
Leaf
Root
->
Left
=
0
;Root
->
Right
=
0
;
Root
->
tag.AbsoluteOffset
=
BeginItr
-
::gScenePtr;
Root
->
Count
=
EndItr
-
BeginItr;
//cout
<<
"
Leaf LEVEL :
"
<<
Level
<<
"
\tCount :
"
<<
Root
->
Count
<<
endl;
return
;
}
++
Level;
KDTreeBuild(Root
->
Left,Vl,BeginItr,MidItr,Axis,Level);
KDTreeBuild(Root
->
Right,Vr,MidItr,EndItr,Axis,Level);
};
这样,我们就得到了一颗完整的基于SAH的KD树。下面就是遍历。
由于不同渲染要求的不同,遍历代码也大不相同。不过我们可以知道,kD树及适用于光线跟踪程序。目前在多款开源光线跟踪器,比如blender就采用了kD树作为加速结构。而且必须要提到的是,如果希望在Realtime Rendering的程序中使用光线跟踪技术 —— 虽然说在目前的情况下还不是很现实,不过这毕竟是个趋势,因为只有光线跟踪才能够精确的模拟物理全局光照,光栅化系统先天限制无法达到,虽然说Voxel渲染是一个折中的办法。我引用下面的这张表格[3]作为性能参照。
可是这里又会出现一个问题,就是在实时渲染程序中如何处理多纹理贴图?我们必须要模拟管线的处理过程。
A、如果场景中有运动的模型,标记出来
B、在世界坐标系统中对运动模型作矩阵变换,类似于OpenGL的MATRIX操作
C、把所有,各就各位的模型顶点、向量、纹理坐标、索引整合在一起。每当在这个链表中加入新的模型的时候,都要把现在模型的所有索引加上链表中已经加入的所有顶点的数目,构建一个好像是整体连续的单个模型。同时标记纹理,应该储存一个顶点偏移量以及三角形数目,用来标示这个纹理属于哪个模型。
D、根据视点,遍历kD树,把符合条件的三角形数目放入一个全局队列
E、渲染队列中的所有三角形,作为一个Frame。重复A。
目前由德国萨尔大学计算机科学系所开发的实时光线跟踪硬件与软件已经成功的问世,最令人振奋的莫过于表现了一个全部由光线跟踪引擎实现的Quake3游戏。有兴趣的朋友可以去[4]看看。
[1]Computer
Graphics WS05/06 – kD-Tree and Optimization for Ray Tracing
[2]Lib3ds
http://lib3ds.sf.net/
[3]Fast
kd-tree Construction with an Adaptive Error-Bounded Heuristic
[4]OpenRT http://www.openrt.de/