游戏裁剪


裁剪能够极大提高渲染效率。

PVS

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

八叉树(Octree)

在进入视锥裁剪前,可以把空间进行分割。比如把空间分为8份。
Octree

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=(x0x,y0y,z0z)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=vvw=a2+b2+c2 a(x0x)+b(y0y)+c(z0z)=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+c2 ax0+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=np0+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

待写。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值