理想情况下,我们只要绘制场景中进入我们视野的物体就够了~
我们在3D场景中的视野范围由Direct3D流水线中的摄影变换和投影变换共同决定。它的形状就好象一个被截去了顶端部分的四棱锥,因此,我们称其为“视截体”或“视域体”。
就如下图所示:
假如物体的某一部分进入这个视截体当中,我们就认为该物体已经进入了我们的视野,而后对其绘制即可。
但是如何来判断物体的某一部分是否与视截体相交呢?当然,遍历物体的所有顶点加以判断是最精确的做法,但不可行,因为性能上会受不了。
这里我们引入外接体的概念:外接体是一种具备标准形状且刚好可以容纳目标物体的最小体积单位。常见的有外接盒(长方体)、外接球、圆柱外接体、胶囊外接体等。其中最为常用的是外接盒和外接球。
它们的形状如下图所示:
通过判断外接体是否位于视野中从而断定物体是否可见。这种做法可以极大的改善性能,且在精确度上不会出现太大的偏差,是一种十分可行的方案。
下面,我们来看如何构造D3D中的外接体对象。
首先声明一个外接体基类CBoundingVolume:
/*------------------------------------- 代码清单:BoundingVolume.h 来自:http://www.cnblogs.com/kenkao -------------------------------------*/ #include "D3DInit.h" #pragma _disibledevent=> // 外接体基类 class CBoundingVolume { public: CBoundingVolume(void); virtual ~CBoundingVolume(void); public: virtual bool IsPointInside(D3DXVECTOR3 pos) = 0; // 单点检测 };
/*------------------------------------- 代码清单:BoundingVolume.cpp 来自:http://www.cnblogs.com/kenkao -------------------------------------*/ #include "StdAfx.h" #include "BoundingVolume.h" CBoundingVolume::CBoundingVolume(void) { } CBoundingVolume::~CBoundingVolume(void) { }
它只是一种标准,且提供了一些规则以供子类重写。之后我们就可以在其基础上派上具体形状的子类了。
首先来看外接盒:
/*------------------------------------- 代码清单:BoundingBox.h 来自:http://www.cnblogs.com/kenkao -------------------------------------*/ #pragma _disibledevent=> #include "boundingvolume.h" class CBoundingBox : public CBoundingVolume { public: CBoundingBox(void); CBoundingBox(D3DXVECTOR3 min, // 外接盒最小坐标 D3DXVECTOR3 max); // 外接盒最大坐标 ~CBoundingBox(void); public: D3DXVECTOR3 GetMin(){return m_Min;} void SetMin(D3DXVECTOR3 min){m_Min = min;} D3DXVECTOR3 GetMax(){return m_Max;} void SetMax(D3DXVECTOR3 max){m_Max = max;} public: virtual bool IsPointInside(D3DXVECTOR3 pos); // 单点检测 static bool ComputeBoundingBox(CBoundingBox* pOut, D3DXVECTOR3* pVertexBuffer, DWORD NumVertices, DWORD dwStride); //计算外接盒(静态、通用) private: D3DXVECTOR3 m_Min; D3DXVECTOR3 m_Max; };
/*------------------------------------- 代码清单:BoundingBox.cpp 来自:http://www.cnblogs.com/kenkao -------------------------------------*/ #include "StdAfx.h" #include "BoundingBox.h" CBoundingBox::CBoundingBox(void) : m_Min(D3DXVECTOR3_ZERO), m_Max(D3DXVECTOR3_ZERO) { } CBoundingBox::CBoundingBox(D3DXVECTOR3 min, D3DXVECTOR3 max) : m_Min(min), m_Max(max) { } CBoundingBox::~CBoundingBox(void) { } bool CBoundingBox::ComputeBoundingBox(CBoundingBox* pOut, D3DXVECTOR3* pVertexBuffer, DWORD NumVertices, DWORD dwStride) { D3DXVECTOR3 min = D3DXVECTOR3_ZERO; D3DXVECTOR3 max = D3DXVECTOR3_ZERO; if(FAILED(D3DXComputeBoundingBox(pVertexBuffer, NumVertices, dwStride, &min, &max))) return false; pOut = new CBoundingBox(min,max); return true; } bool CBoundingBox::IsPointInside(D3DXVECTOR3 pos) { return pos.x >= m_Min.x && pos.y >= m_Min.y && pos.z >= m_Min.z && pos.x <= m_Max.x && pos.y <= m_Max.y && pos.z <= m_Max.z; }
对于一个长方体,我们没有必要知道全部8个顶点的坐标,而只需要知道其中的两个就可以了——xyz最小和最大的最小坐标和最大坐标。至于其他点,高等数学中的组合即可求得~
CBoundingBox重写了基类的单点检测方法,同时对外提供两种构造外接盒的方法:第一种直接传入两个关键坐标即可获得;第二种通过调用D3DAPI来实现,只需令其获知某物体的所有顶点信息即可,无需受制于某种专有的数据格式,便捷、通用。具体可参看代码。
然后是外接球的构建:
/*------------------------------------- 代码清单:BoundingSphere.h 来自:http://www.cnblogs.com/kenkao -------------------------------------*/ #pragma _disibledevent=> #include "boundingvolume.h" class CBoundingSphere : public CBoundingVolume { public: CBoundingSphere(void); CBoundingSphere(D3DXVECTOR3 center, // 外接球球心 float radius); // 外接球半径 ~CBoundingSphere(void); public: D3DXVECTOR3 GetCenter(){return m_Center;} void SetCenter(D3DXVECTOR3 center){m_Center = center;} float GetRadius(){return m_Radius;} void SetRadius(float radius){m_Radius = radius;} public: virtual bool IsPointInside(D3DXVECTOR3 pos); // 单点检测 static bool ComputeBoundingSphere(CBoundingSphere* pOut, D3DXVECTOR3* pVertexBuffer, DWORD NumVertices, DWORD dwStride); // 计算外接球(静态、通用) private: D3DXVECTOR3 m_Center; float m_Radius; };
/*------------------------------------- 代码清单:BoundingSphere.cpp 来自:http://www.cnblogs.com/kenkao -------------------------------------*/ #include "StdAfx.h" #include "BoundingSphere.h" CBoundingSphere::CBoundingSphere(void) : m_Center(D3DXVECTOR3_ZERO), m_Radius(0.0f) { } CBoundingSphere::CBoundingSphere(D3DXVECTOR3 center, float radius) : m_Center(center), m_Radius(radius) { } CBoundingSphere::~CBoundingSphere(void) { } bool CBoundingSphere::ComputeBoundingSphere(CBoundingSphere* pOut, D3DXVECTOR3 *pVertexBuffer, DWORD NumVertices, DWORD dwStride) { D3DXVECTOR3 center = D3DXVECTOR3_ZERO; float radius = 0.0f; if(FAILED(D3DXComputeBoundingSphere(pVertexBuffer, NumVertices, dwStride, ¢er, &radius))) return false; pOut = new CBoundingSphere(center,radius); return true; } bool CBoundingSphere::IsPointInside(D3DXVECTOR3 pos) { D3DXVECTOR3 distance = this->m_Center - pos; return sqrt(pow(distance.x,2) + pow(distance.y,2) + pow(distance.z,2)) <= this->m_Radius; }
同样的,我们只需知道球心和半径即可描述该外接球。
CBoundingSphere同样提供单点检测和两种构造外接球的方法,同CBoundingBox类似,原理非常简单。
有了具体的外接盒、外接球,接下来我们来看视截体对象的构建方法。视截体可以理解为外接体的一种特殊类型,因此我们令CBoundingFrustum继承自CBoundingVolume即可:
/*------------------------------------- 代码清单:BoundingFrustum.h 来自:http://www.cnblogs.com/kenkao -------------------------------------*/ #pragma _disibledevent=> #include "boundingvolume.h" #include "BoundingBox.h" #include "BoundingSphere.h" class CBoundingFrustum : public CBoundingVolume { public: CBoundingFrustum(void); ~CBoundingFrustum(void); public: void Update(D3DXMATRIX Matrix); // 更新视截体 void Release(){}; // 释放视截体(可以不执行) public: bool Contains(CBoundingBox* pBoundingBox); // 判断外接盒是否被视截体包含 bool Contains(CBoundingSphere* pBoundingSphere); // 判断外接球是否被视截体包含 virtual bool IsPointInside(D3DXVECTOR3 pos); // 判断某一点是否位于视截体内 public: D3DXPLANE GetNear() {return m_planes[0];} // 获得近、远、左、右、上、下六个平面 D3DXPLANE GetFar() {return m_planes[1];} D3DXPLANE GetLeft() {return m_planes[2];} D3DXPLANE GetRight() {return m_planes[3];} D3DXPLANE GetTop() {return m_planes[4];} D3DXPLANE GetButtom(){return m_planes[5];} D3DXMATRIX GetMatrix(){return m_Matrix;} // 获得视截体摄影·投影变换矩阵 private: D3DXPLANE m_planes[6]; // 视截体六个平面 D3DXMATRIX m_Matrix; // 视截体摄影·投影变换矩阵 };
BoundingFrustum.cpp /*------------------------------------- 代码清单:BoundingFrustum.cpp 来自:http://www.cnblogs.com/kenkao -------------------------------------*/ #include "StdAfx.h" #include "BoundingFrustum.h" CBoundingFrustum::CBoundingFrustum(void) { } CBoundingFrustum::~CBoundingFrustum(void) { } bool CBoundingFrustum::IsPointInside(D3DXVECTOR3 pos) { for(int i=0;i<6;i++) { // 如果该点位于视截体六个面中任何一个面外,则判定为不被包含 if( D3DXPlaneDotCoord(&m_planes[i], &pos) < 0.0f ) return FALSE; } return TRUE; } bool CBoundingFrustum::Contains(CBoundingBox* pBoundingBox) { D3DXVECTOR3 vertex[8]; D3DXVECTOR3 min = pBoundingBox->GetMin(); D3DXVECTOR3 max = pBoundingBox->GetMax(); vertex[0] = min; vertex[1] = D3DXVECTOR3(max.x,min.y,min.z); vertex[2] = D3DXVECTOR3(min.x,max.y,min.z); vertex[3] = D3DXVECTOR3(min.x,min.y,max.z); vertex[4] = D3DXVECTOR3(max.x,max.y,min.z); vertex[5] = D3DXVECTOR3(min.x,max.y,max.z); vertex[6] = D3DXVECTOR3(max.x,min.y,max.z); vertex[7] = max; for (int i=0;i<8;i++) { // 如果外接盒的任何一个顶点位于视截体中,则判定为被包含 if(IsPointInside(vertex[i])) return true; } return false; } bool CBoundingFrustum::Contains(CBoundingSphere* pBoundingSphere) { for(int i=0;i<6;i++) { // 如果外接球球心位于视截体六个面中任何一个面外且其距离大于半径,则判定为不被包含 if( D3DXPlaneDotCoord(&m_planes[i],&pBoundingSphere->GetCenter()) < -pBoundingSphere->GetRadius()) return FALSE; } return TRUE; } void CBoundingFrustum::Update(D3DXMATRIX Matrix) { // 更新视截体六个平面 m_Matrix = Matrix; // 近平面 m_planes[0].a = Matrix._14 + Matrix._13; m_planes[0].b = Matrix._24 + Matrix._23; m_planes[0].c = Matrix._34 + Matrix._33; m_planes[0].d = Matrix._44 + Matrix._43; D3DXPlaneNormalize(&m_planes[0], &m_planes[0]); //所得平面要执行单位化,以利于后期计算 // 远平面 m_planes[1].a = Matrix._14 - Matrix._13; m_planes[1].b = Matrix._24 - Matrix._23; m_planes[1].c = Matrix._34 - Matrix._33; m_planes[1].d = Matrix._44 - Matrix._43; D3DXPlaneNormalize(&m_planes[1], &m_planes[1]); // 左平面 m_planes[2].a = Matrix._14 - Matrix._11; m_planes[2].b = Matrix._24 - Matrix._21; m_planes[2].c = Matrix._34 - Matrix._31; m_planes[2].d = Matrix._44 - Matrix._41; D3DXPlaneNormalize(&m_planes[2], &m_planes[2]); // 右平面 m_planes[3].a = Matrix._14 + Matrix._11; m_planes[3].b = Matrix._24 + Matrix._21; m_planes[3].c = Matrix._34 + Matrix._31; m_planes[3].d = Matrix._44 + Matrix._41; D3DXPlaneNormalize(&m_planes[3], &m_planes[3]); // 上平面 m_planes[4].a = Matrix._14 - Matrix._12; m_planes[4].b = Matrix._24 - Matrix._22; m_planes[4].c = Matrix._34 - Matrix._32; m_planes[4].d = Matrix._44 - Matrix._42; D3DXPlaneNormalize(&m_planes[4], &m_planes[4]); // 下平面 m_planes[5].a = Matrix._14 + Matrix._12; m_planes[5].b = Matrix._24 + Matrix._22; m_planes[5].c = Matrix._34 + Matrix._32; m_planes[5].d = Matrix._44 + Matrix._42; D3DXPlaneNormalize(&m_planes[5], &m_planes[5]); }
与外接球及外接盒不同,我们需要六个平面来描述一个视截体。
我们通过传入摄影*投影矩阵来Update视截体的六个平面,除单点检测之外,同时提供重载的Contains函数判断其与外接盒或者外接球的包容关系,你也可以自行重载该方法,从而提供针对于其他形状外接体的判别机制。
其中的原理细节,大家可参看网友 laizhishen 的原创文章:http://hi.baidu.com/laizhishen/blog/item/3d206d209cca9c54ac34de46.html
至此我们仅需获得某物体对应的外接体,就可利用视截体进一步判断该物体是否可见、是否有必要绘制。
为观察可视化效果,我们来丰富CSimpleXMesh类的功能,使其具备加载并渲染.X model外接体的能力:
/*------------------------------------- 代码清单:SimpleXMesh.h 来自:http://www.cnblogs.com/kenkao -------------------------------------*/ #include "D3DInit.h" #include "BoundingSphere.h" #include "BoundingBox.h" #pragma _disibledevent=> class CSimpleXMesh { public: CSimpleXMesh(void); ~CSimpleXMesh(void); public: bool LoadXMesh(TCHAR* szXFileName); // 加载.X网格 void DrawXMesh(); // 绘制.X网格 void DrawXMesh(D3DXVECTOR3 pos); void DrawXMesh(D3DXMATRIX trans); void DrawXMeshSubset(int index); // 绘制.X网格子集 void Release(); // 释放.X网格 public: bool LoadXMeshWithBoundingSphere(TCHAR* szXFileName); // 加载.X网格(携带外接球) bool LoadXMeshWithBoundingBox(TCHAR* szXFileName); // 加载.X网格(携带外接盒) void DrawXMeshWithBoundingBox(); // 绘制.X网格(携带外接盒) void DrawXMeshWithBoundingSphere(); // 绘制.X网格(携带外接球) public: CBoundingBox* GetBoundingBox() {return m_pBoundingBox;} // 获得外接盒 CBoundingSphere* GetBoundingSphere(){return m_pBoundingSphere;} // 获得外接球 public: DWORD GetMaterialNum() {return m_dwMaterials;} // 获得网格材质数 D3DMATERIAL9 GetMaterial(int index) {return m_pD3DMaterialArray[index];} // 获得网格材质 IDirect3DTexture9* GetTexture(int index){return m_ppDirect3DTextureArray[index];} // 获得网格纹理 private: bool ComputeBoundingSphere(); // 计算外接球 bool ComputeBoundingBox(); // 计算外接盒 private: ID3DXBuffer* m_pAdjacencyBuffer; // 邻接三角形信息缓冲区 ID3DXBuffer* m_pMaterialBuffer; // 材质缓冲区 D3DMATERIAL9 *m_pD3DMaterialArray; // 材质数组 IDirect3DTexture9 **m_ppDirect3DTextureArray; // 纹理数组 DWORD m_dwMaterials; // 材质数 ID3DXMesh* m_pD3DXMesh; // .X网格对象指针 private: CBoundingBox* m_pBoundingBox; // 外接盒 CBoundingSphere* m_pBoundingSphere; // 外接球 ID3DXMesh* m_pBoundingBoxMesh; // 外接盒网格 ID3DXMesh* m_pBoundingSphereMesh; // 外接球网格 };
SimpleXMesh.cpp /*------------------------------------- 代码清单:SimpleXMesh.cpp 来自:http://www.cnblogs.com/kenkao -------------------------------------*/ #include "StdAfx.h" #include "SimpleXMesh.h" #include "D3DGame.h" extern IDirect3DDevice9 *g_pD3DDevice; CSimpleXMesh::CSimpleXMesh(void):m_pAdjacencyBuffer(NULL), m_pMaterialBuffer(NULL), m_pD3DMaterialArray(NULL), m_ppDirect3DTextureArray(NULL), m_dwMaterials(0), m_pD3DXMesh(NULL), m_pBoundingBox(NULL), m_pBoundingSphere(NULL), m_pBoundingBoxMesh(NULL), m_pBoundingSphereMesh(NULL) { } CSimpleXMesh::~CSimpleXMesh(void) { } bool CSimpleXMesh::LoadXMesh(TCHAR* szXFileName) { // 加载X网格 if(FAILED(D3DXLoadMeshFromX( szXFileName, //.X文件名 D3DXMESH_MANAGED, //内存托管模式 g_pD3DDevice, //Direct3D设备 &m_pAdjacencyBuffer, //邻接三角形信息缓冲区指针 &m_pMaterialBuffer, //材质缓冲区指针 0, //特效缓冲区指针,由于没有用到特效,我们在这里置0即可 &m_dwMaterials, //材质数 &m_pD3DXMesh //得到的X网格 ))){ return false; } // 错误判断 if(m_pMaterialBuffer==NULL || m_dwMaterials==0) return false; // 获得材质缓冲区指针 D3DXMATERIAL* pD3DXMaterial=(D3DXMATERIAL*)m_pMaterialBuffer->GetBufferPointer(); if(pD3DXMaterial!=NULL){ // 初始化材质数组 m_pD3DMaterialArray=new D3DMATERIAL9[m_dwMaterials]; // 初始化纹理数组 m_ppDirect3DTextureArray=new IDirect3DTexture9*[m_dwMaterials]; // 遍历材质缓冲区,填充材质及纹理数组 for(DWORD i=0;i
D3DGame.cpp /*------------------------------------- 代码清单:D3DGame.cpp 来自:http://www.cnblogs.com/kenkao -------------------------------------*/ #include "StdAfx.h" #include "D3DGame.h" #include "D3DSprite.h" #include "SpriteBatch.h" #include "D3DFont.h" #include "D3DCamera.h" #include "BaseTerrain.h" #include "SimpleXMesh.h" #include "BoundingFrustum.h" #include "BoundingSphere.h" #include
以上,谢谢 ^ ^
最新评论