对于静态场景,我们可以先把场景立方体分割成网格(grid),即nmh个grid,先计算好在每个grid上所有角度可见的mesh(剔除物体完全被前面物体挡住的mesh),记录所有可见的mesh编号,如果相机在这个位置上,只要把这些可见的mesh进行下一步裁剪(比如视锥裁剪,去除不再视锥内的物体)
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. = new Vector.();
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., 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
待写。