游戏裁剪

来源:互联网 发布:windows loader 和kms 编辑:程序博客网 时间:2024/05/22 06:49

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

PVS

对于静态场景,我们可以先把场景立方体分割成网格(grid),即n*m*h个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);            }        }

视锥裁剪

如何判断一个点在平面侧

这里写图片描述
给定一个平面
ax+by+cz+d=0
平面外的一个点(x0,y0,z0)
平面的法向量为v=(a,b,c)T
平面到点(x0,y0,z0)的向量为w=(x0x,y0y,z0z)T
wv上的投影为
Distance=|projectvw| =|vw||v| =|a(x0x)+b(y0y)+c(z0z)|a2+b2+c2 =|ax0+by0+cz0+d|a2+b2+c2
如果不加入绝对值即

Distance=ax0+by0+cz0+da2+b2+c2

正号表示与法向量同一侧
负号表示与法向量方向一侧
假设v为单位向量n,上公式可写成
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

待写。


原创粉丝点击