裁剪能够极大提高渲染效率。
PVS
对于静态场景,我们可以先把场景立方体分割成网格(grid),即nmh个grid,先计算好在每个grid上所有角度可见的mesh(剔除物体完全被前面物体挡住的mesh),记录所有可见的mesh编号,如果相机在这个位置上,只要把这些可见的mesh进行下一步裁剪(比如视锥裁剪,去除不再视锥内的物体)
八叉树(Octree)
在进入视锥裁剪前,可以把空间进行分割。比如把空间分为8份。
OctreeNode结构很简单,如下:
public class OctreeNode extends NodeBase
{
private var _centerX:Number;
private var _centerY:Number;
private var _centerZ:Number;
private var _minX:Number;
private var _minY:Number;
private var _minZ:Number;
private var _maxX:Number;
private var _maxY:Number;
private var _maxZ:Number;
private var _quadSize:Number;
private var _depth:Number;
private var _leaf:Boolean;
private var _rightTopFar:OctreeNode;
private var _leftTopFar:OctreeNode;
private var _rightBottomFar:OctreeNode;
private var _leftBottomFar:OctreeNode;
private var _rightTopNear:OctreeNode;
private var _leftTopNear:OctreeNode;
private var _rightBottomNear:OctreeNode;
private var _leftBottomNear:OctreeNode;
//private var _entityWorldBounds : Vector.<Number> = new Vector.<Number>();
private var _halfExtent:Number;
}
八叉树生成如下:
/**
* @param maxDepth 八叉树的深度
* @param size 大小
* @param centerX 中心点
* @param depth 当前深度
*/
public function OctreeNode(maxDepth:int = 5, size:Number = 10000, centerX:Number = 0, centerY:Number = 0, centerZ:Number = 0, depth:int = 0)
{
init(size, centerX, centerY, centerZ, depth, maxDepth);
}
private function init(size:Number, centerX:Number, centerY:Number, centerZ:Number, depth:int, maxDepth:int):void
{
_halfExtent = size*.5;
_centerX = centerX;
_centerY = centerY;
_centerZ = centerZ;
_quadSize = size;
_depth = depth;
_minX = centerX - _halfExtent;
_minY = centerY - _halfExtent;
_minZ = centerZ - _halfExtent;
_maxX = centerX + _halfExtent;
_maxY = centerY + _halfExtent;
_maxZ = centerZ + _halfExtent;
_leaf = depth == maxDepth;
if (!_leaf) {
var hhs:Number = _halfExtent*.5;
addNode(_leftTopNear = new OctreeNode(maxDepth, _halfExtent, centerX - hhs, centerY + hhs, centerZ - hhs, depth + 1));
addNode(_rightTopNear = new OctreeNode(maxDepth, _halfExtent, centerX + hhs, centerY + hhs, centerZ - hhs, depth + 1));
addNode(_leftBottomNear = new OctreeNode(maxDepth, _halfExtent, centerX - hhs, centerY - hhs, centerZ - hhs, depth + 1));
addNode(_rightBottomNear = new OctreeNode(maxDepth, _halfExtent, centerX + hhs, centerY - hhs, centerZ - hhs, depth + 1));
addNode(_leftTopFar = new OctreeNode(maxDepth, _halfExtent, centerX - hhs, centerY + hhs, centerZ + hhs, depth + 1));
addNode(_rightTopFar = new OctreeNode(maxDepth, _halfExtent, centerX + hhs, centerY + hhs, centerZ + hhs, depth + 1));
addNode(_leftBottomFar = new OctreeNode(maxDepth, _halfExtent, centerX - hhs, centerY - hhs, centerZ + hhs, depth + 1));
addNode(_rightBottomFar = new OctreeNode(maxDepth, _halfExtent, centerX + hhs, centerY - hhs, centerZ + hhs, depth + 1));
}
}
如何放置一个mesh,已知mesh的AABB盒,从Octree的根节点开始查找代码如下:
// TODO: this can be done quicker through inversion
private function findPartitionForBounds(minX:Number, minY:Number, minZ:Number, maxX:Number, maxY:Number, maxZ:Number):OctreeNode
{
var left:Boolean, right:Boolean;
var far:Boolean, near:Boolean;
var top:Boolean, bottom:Boolean;
if (_leaf)
return this;
right = maxX > _centerX;
left = minX < _centerX;
top = maxY > _centerY;
bottom = minY < _centerY;
far = maxZ > _centerZ;
near = minZ < _centerZ;
if ((left && right) || (far && near))
return this;
if (top) {
if (bottom)
return this;
if (near) {
if (left)
return _leftTopNear.findPartitionForBounds(minX, minY, minZ, maxX, maxY, maxZ);
else
return _rightTopNear.findPartitionForBounds(minX, minY, minZ, maxX, maxY, maxZ);
} else {
if (left)
return _leftTopFar.findPartitionForBounds(minX, minY, minZ, maxX, maxY, maxZ);
else
return _rightTopFar.findPartitionForBounds(minX, minY, minZ, maxX, maxY, maxZ);
}
} else {
if (near) {
if (left)
return _leftBottomNear.findPartitionForBounds(minX, minY, minZ, maxX, maxY, maxZ);
else
return _rightBottomNear.findPartitionForBounds(minX, minY, minZ, maxX, maxY, maxZ);
} else {
if (left)
return _leftBottomFar.findPartitionForBounds(minX, minY, minZ, maxX, maxY, maxZ);
else
return _rightBottomFar.findPartitionForBounds(minX, minY, minZ, maxX, maxY, maxZ);
}
}
}
把mesh放在这个Octree下即可。
渲染的时候从根节点开始遍历。
先判断Octree节点的包围盒是否在视锥内,如果是,遍历他的8个子Octree。
/**
* Allows the traverser to visit the current node. If the traverser's enterNode method returns true, the
* traverser will be sent down the child nodes of the tree.
* This method should be overridden if the order of traversal is important (such as for BSP trees) - or if static
* child nodes are not added using addNode, but are linked to separately.
*
* @param traverser The traverser visiting the node.
*
* @see away3d.core.traverse.PartitionTraverser
*/
public function acceptTraverser(traverser:PartitionTraverser):void
{
if (_numEntities == 0 && !_debugPrimitive)
return;
if (traverser.enterNode(this)) {
var i:uint;
while (i < _numChildNodes)
_childNodes[i++].acceptTraverser(traverser);
if (_debugPrimitive)
traverser.applyRenderable(_debugPrimitive);
}
}
视锥裁剪
如何判断一个点在平面侧
给定一个平面
a
x
+
b
y
+
c
z
+
d
=
0
ax+by+cz+d=0
ax+by+cz+d=0
平面外的一个点
(
x
0
,
y
0
,
z
0
)
(x_0,y_0,z_0)
(x0,y0,z0)
平面的法向量为
v
=
(
a
,
b
,
c
)
T
\textbf{v}=(a,b,c)^T
v=(a,b,c)T
平面到点
(
x
0
,
y
0
,
z
0
)
(x_0,y_0,z_0)
(x0,y0,z0)的向量为
w
=
(
x
0
−
x
,
y
0
−
y
,
z
0
−
z
)
T
\textbf{w}=(x_0-x,y_0-y,z_0-z)^T
w=(x0−x,y0−y,z0−z)T
w
\textbf{w}
w在
v
\textbf{v}
v上的投影为
D
i
s
t
a
n
c
e
=
∣
p
r
o
j
e
c
t
v
w
∣
=
∣
v
⋅
w
∣
∣
v
∣
=
∣
a
(
x
0
−
x
)
+
b
(
y
0
−
y
)
+
c
(
z
0
−
z
)
∣
a
2
+
b
2
+
c
2
=
∣
a
x
0
+
b
y
0
+
c
z
0
+
d
∣
a
2
+
b
2
+
c
2
Distance=|project_{\textbf{v}}\textbf{w}|\\ =\dfrac{|\textbf{v}\cdot \textbf{w}|}{|\textbf{v}|}\\ =\dfrac{|a(x_0-x)+b(y_0-y)+c(z_0-z)|}{\sqrt{a^2+b^2+c^2}}\\ =\dfrac{|ax_0+by_0+cz_0+d|}{\sqrt{a^2+b^2+c^2}}
Distance=∣projectvw∣=∣v∣∣v⋅w∣=a2+b2+c2∣a(x0−x)+b(y0−y)+c(z0−z)∣=a2+b2+c2∣ax0+by0+cz0+d∣
如果不加入绝对值即
D
i
s
t
a
n
c
e
=
a
x
0
+
b
y
0
+
c
z
0
+
d
a
2
+
b
2
+
c
2
Distance=\dfrac{ax_0+by_0+cz_0+d}{\sqrt{a^2+b^2+c^2}}
Distance=a2+b2+c2ax0+by0+cz0+d
正号表示与法向量同一侧
负号表示与法向量方向一侧
假设
v
\textbf{v}
v为单位向量
n
\textbf{n}
n,上公式可写成
D
i
s
t
a
n
c
e
=
a
x
0
+
b
y
0
+
c
z
0
+
d
=
n
⋅
p
0
+
d
Distance=ax_0+by_0+cz_0+d=\textbf{n}\cdot \textbf{p}_0 + d
Distance=ax0+by0+cz0+d=n⋅p0+d
判断一个AABB包围盒是否在视锥(6个面组成)内,代码如下:
public function isInFrustum(planes:Vector.<Plane3D>, numPlanes:int):Boolean
{
//return true;
for (var i:uint = 0; i < numPlanes; ++i)
{
var plane:Plane3D = planes[i];
var flippedExtentX:Number = plane.a < 0 ? -_halfExtentsX : _halfExtentsX;
var flippedExtentY:Number = plane.b < 0 ? -_halfExtentsY : _halfExtentsY;
var flippedExtentZ:Number = plane.c < 0 ? -_halfExtentsZ : _halfExtentsZ;
var projDist:Number = plane.a * (_centerX + flippedExtentX) + plane.b * (_centerY + flippedExtentY) + plane.c * (_centerZ + flippedExtentZ) + plane.d;
if (projDist < 0)
{
return false;
}
}
return true;
}
如何快速提取视锥平面
参考Fast Extraction of Viewing Frustum Planes from the World-View-Projection Matrix
待写。