本篇是創(chuàng)建3D圖形引擎(1)的續(xù)篇,3D圖形引擎的代碼以創(chuàng)建游戲內(nèi)核中編寫(xiě)的代碼為基礎(chǔ)進(jìn)行開(kāi)發(fā)。
下載源碼和工程
視錐的介紹
視錐(viewing frustum)是6個(gè)平面的集合,它從觀察點(diǎn)向外擴(kuò)展,以確定某個(gè)多邊形是否能夠被觀察到。
首先,可以將視錐看作是一個(gè)以觀察者開(kāi)始擴(kuò)展的金字塔,如下圖所示:

這個(gè)金字塔代表了觀察范圍(field of view,FOV),位于觀察范圍內(nèi)的物體是可見(jiàn)的,而位于觀察范圍外的物體則是不可見(jiàn)的。
三維圖形引擎的每個(gè)物體都是由3D點(diǎn)(稱為頂點(diǎn))構(gòu)成的,每個(gè)視錐包含6個(gè)面(前、后、左、右、上、下)。通過(guò)一些數(shù)學(xué)計(jì)算,可以確定哪些頂點(diǎn)位于視錐內(nèi),哪些頂點(diǎn)位于視錐外。位于視錐內(nèi)的頂點(diǎn)被渲染,而位于視錐外的頂點(diǎn)則不被渲染。同樣,當(dāng)渲染多邊形時(shí),只有那些在視錐內(nèi)的頂點(diǎn)才被渲染。
平面與裁剪
視錐的6個(gè)側(cè)面被稱為剪切平面(clipping plane),Direct3D使用了一個(gè)名為D3DXPLANE的特定對(duì)象去包含平面數(shù)據(jù),D3DXPLANE包含了4個(gè)變量:a、b、c、d,它們都是浮點(diǎn)型數(shù)據(jù)。
定義好一個(gè)平面后,為它指定一個(gè)特定的方向,并將它從原點(diǎn)移到相應(yīng)的位置。事實(shí)上,一個(gè)平面就是由一條法線以及它與原點(diǎn)的距離進(jìn)行定義的,如下圖所示,它演示了一個(gè)平面在三維空間中的方位。

不需要指定X、Y、Z的具體數(shù)值,你只要使用變量A、B、C,同時(shí)還有一個(gè)數(shù)值D來(lái)確定平面離原點(diǎn)的距離。為了定義一個(gè)平面,將A、B、C設(shè)置為法向量的值,這樣就可以使用一個(gè)平面去檢測(cè)指定的點(diǎn)位于平面的前面或者后面。
為了計(jì)算視錐的6個(gè)平面,可以將當(dāng)前的觀察變換矩陣和投影矩陣組合起來(lái),然后使用組合矩陣直接計(jì)算每個(gè)平面的A、B、C、D。
平面可見(jiàn)性檢測(cè)
為了檢測(cè)一個(gè)頂點(diǎn)位于平面的前方或后方,可以利用點(diǎn)積來(lái)計(jì)算,通過(guò)調(diào)用D3DXPlaneCoord函數(shù)來(lái)實(shí)現(xiàn)。
Computes the dot product of a plane and a 3D vector. The w parameter of the
vector is assumed to be 1.
FLOAT D3DXPlaneDotCoord(
CONST D3DXPLANE * pP,
CONST D3DXVECTOR3 * pV
);
Parameters
- pP
- [in] Pointer to a source D3DXPLANE structure.
- pV
- [in] Pointer to a source D3DXVECTOR3 structure.
Return Values
The dot product of the plane and 3D vector.
Remarks
Given a plane (a, b, c, d) and a 3D vector (x, y, z) the return value of this
function is a*x + b*y + c*z + d*1. The D3DXPlaneDotCoord function is
useful for determining the plane's relationship with a coordinate in 3D space.
完整視錐的檢測(cè)
對(duì)于立方體和長(zhǎng)方體,需檢測(cè)所有的拐角頂點(diǎn)。如果所有的頂點(diǎn)都位于任一平面之后,那么立方體或長(zhǎng)方體就位于視錐之外(因而也在視野之外)。如果有任一頂點(diǎn)位于視錐之內(nèi),或者說(shuō)位于任一平面前(也就是說(shuō)不是所有的頂點(diǎn)都位于任一平面后),那就意味著立方體或者長(zhǎng)方體是可見(jiàn)的。至于球體,只要它與每個(gè)平面的距離
等于或大于球形的平面,那么它就是可見(jiàn)的。
FRUSTUM類
因?yàn)槊看问褂靡曞F所涉及的數(shù)學(xué)運(yùn)算都是一樣的,所以完全可以創(chuàng)建一個(gè)類,讓它處理好數(shù)學(xué)方面的問(wèn)題,包括創(chuàng)建視錐以及使用視錐去檢測(cè)一個(gè)物體是否可見(jiàn)等。
//==============================================================================
// This class encapsulate for frustum, judge whether other object is in frustum.
//==============================================================================
typedef class FRUSTUM
{
public:
// Construct the six planes from current view and projection.
// Can override the default depth value.
BOOL construct(GRAPHICS_PTR graphics, float z_distance = 0.0f);
// The following functions check a single point, cube, rectangle, and sphere if
// contained in the frustum. A return value of TRUE means visible, FALSE not visible.
// When checking cubes or rectangles, you can supply a BOOL variable that determines
// if all the points are in the frustum.
BOOL check_point(float x_pos, float y_pos, float z_pos);
BOOL check_cube(float x_center, float y_center, float z_center,
float radius,
BOOL* completely_contained = NULL);
BOOL check_rectangle(float x_center, float y_center, float z_center,
float x_radius, float y_radius, float z_radius,
BOOL* completely_contained = NULL);
BOOL check_sphere(float x_center, float y_center, float z_center,
float radius);
private:
D3DXPLANE _planes[6]; // the frustum planes
} *FRUSTUM_PTR;
每當(dāng)觀察或投影矩陣發(fā)生變化時(shí),請(qǐng)調(diào)用
construct去構(gòu)造6個(gè)測(cè)試平面,如果僅希望最接近的物體能被看到,則可以為遠(yuǎn)端剪切平面指定一個(gè)新的距離值。
//----------------------------------------------------------------------------
// Construct frustum.
//----------------------------------------------------------------------------
BOOL FRUSTUM::construct(GRAPHICS_PTR graphics, float z_distance)
{
D3DXMATRIX matrix, mat_view, mat_proj;
// error checking
if(graphics == NULL)
return FALSE;
// calculate FOV data
graphics->get_device_com()->GetTransform(D3DTS_PROJECTION, &mat_proj);
if(! float_equal(z_distance, 0.0f))
{
// Calculate new projection matrix based on distance provided.
//
// projection matrix is:
//
// | xScale 0 0 0 |
// | 0 yScale 0 0 |
// | 0 0 zf/(zf-zn) 1 |
// | 0 0 -zn*zf/(zf-zn) 0 |
//
// where:
// yScale = cot(fovY/2)
// xScale = yScale / aspect ratio
float z_min = -mat_proj._43 / mat_proj._33;
float q = z_distance / (z_distance - z_min);
mat_proj._33 = q;
mat_proj._43 = -q * z_min;
}
graphics->get_device_com()->GetTransform(D3DTS_VIEW, &mat_view);
D3DXMatrixMultiply(&matrix, &mat_view, &mat_proj);
// calculate the planes
_planes[0].a = matrix._14 + matrix._13; // Near
_planes[0].b = matrix._24 + matrix._23;
_planes[0].c = matrix._34 + matrix._33;
_planes[0].d = matrix._44 + matrix._43;
D3DXPlaneNormalize(&_planes[0], &_planes[0]);
_planes[1].a = matrix._14 - matrix._13; // Far
_planes[1].b = matrix._24 - matrix._23;
_planes[1].c = matrix._34 - matrix._33;
_planes[1].d = matrix._44 - matrix._43;
D3DXPlaneNormalize(&_planes[1], &_planes[1]);
_planes[2].a = matrix._14 + matrix._11; // Left
_planes[2].b = matrix._24 + matrix._21;
_planes[2].c = matrix._34 + matrix._31;
_planes[2].d = matrix._44 + matrix._41;
D3DXPlaneNormalize(&_planes[2], &_planes[2]);
_planes[3].a = matrix._14 - matrix._11; // Right
_planes[3].b = matrix._24 - matrix._21;
_planes[3].c = matrix._34 - matrix._31;
_planes[3].d = matrix._44 - matrix._41;
D3DXPlaneNormalize(&_planes[3], &_planes[3]);
_planes[4].a = matrix._14 - matrix._12; // Top
_planes[4].b = matrix._24 - matrix._22;
_planes[4].c = matrix._34 - matrix._32;
_planes[4].d = matrix._44 - matrix._42;
D3DXPlaneNormalize(&_planes[4], &_planes[4]);
_planes[5].a = matrix._14 + matrix._12; // Bottom
_planes[5].b = matrix._24 + matrix._22;
_planes[5].c = matrix._34 + matrix._32;
_planes[5].d = matrix._44 + matrix._42;
D3DXPlaneNormalize(&_planes[5], &_planes[5]);
return TRUE;
}
使用
check系列函數(shù)判斷物體在視錐內(nèi)是否可見(jiàn)。
//----------------------------------------------------------------------------
// Check one point whether in frustum.
//----------------------------------------------------------------------------
BOOL FRUSTUM::check_point(float x_pos, float y_pos, float z_pos)
{
// make sure point is in frustum
for(short i = 0; i < 6; i++)
{
if(D3DXPlaneDotCoord(&_planes[i], &D3DXVECTOR3(x_pos, y_pos, z_pos)) < 0.0f)
return FALSE;
}
return TRUE;
}
//----------------------------------------------------------------------------
// Check whether a cube in frustum, if total cube in frustum then
// completely_contained will be set TRUE.
//----------------------------------------------------------------------------
BOOL FRUSTUM::check_cube(float x_center, float y_center, float z_center,
float radius,
BOOL* completely_contained)
{
DWORD num_points_in_frustum = 0;
// count the number of points inside the frustum
for(short i = 0; i < 6; i++)
{
DWORD count = 8;
BOOL in_all_planes = TRUE;
// test all eight points against plane
if(D3DXPlaneDotCoord(&_planes[i], &D3DXVECTOR3(x_center - radius, y_center - radius, z_center - radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&_planes[i], &D3DXVECTOR3(x_center + radius, y_center - radius, z_center - radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&_planes[i], &D3DXVECTOR3(x_center - radius, y_center + radius, z_center - radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&_planes[i], &D3DXVECTOR3(x_center + radius, y_center + radius, z_center - radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&_planes[i], &D3DXVECTOR3(x_center - radius, y_center - radius, z_center + radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&_planes[i], &D3DXVECTOR3(x_center + radius, y_center - radius, z_center + radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&_planes[i], &D3DXVECTOR3(x_center - radius, y_center + radius, z_center + radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&_planes[i], &D3DXVECTOR3(x_center + radius, y_center + radius, z_center + radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
// if none contained, return FALSE.
if(count == 0)
return FALSE;
// update counter if they were all in front of plane.
if(in_all_planes)
++num_points_in_frustum;
}
// store BOOL flag if completely contained
if(completely_contained)
*completely_contained = (num_points_in_frustum == 6);
return TRUE;
}
//----------------------------------------------------------------------------
// Check whether a rectangle is in frustum, if total in then completely_contained
// will be set TRUE.
//----------------------------------------------------------------------------
BOOL FRUSTUM::check_rectangle(float x_center, float y_center, float z_center,
float x_radius, float y_radius, float z_radius,
BOOL* completely_contained)
{
DWORD num_points_in_frustum = 0;
// count the number of points inside the frustum
for(short i = 0; i < 6; i++)
{
DWORD count = 8;
BOOL in_all_planes = TRUE;
// Test all eight points against plane
if(D3DXPlaneDotCoord(&_planes[i],
&D3DXVECTOR3(x_center - x_radius, y_center - y_radius, z_center - z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&_planes[i],
&D3DXVECTOR3(x_center + x_radius, y_center - y_radius, z_center - z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&_planes[i],
&D3DXVECTOR3(x_center - x_radius, y_center + y_radius, z_center - z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&_planes[i],
&D3DXVECTOR3(x_center + x_radius, y_center + y_radius, z_center - z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&_planes[i],
&D3DXVECTOR3(x_center - x_radius, y_center - y_radius, z_center + z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&_planes[i],
&D3DXVECTOR3(x_center + x_radius, y_center - y_radius, z_center + z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&_planes[i],
&D3DXVECTOR3(x_center - x_radius, y_center + y_radius, z_center + z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&_planes[i],
&D3DXVECTOR3(x_center + x_radius, y_center + y_radius, z_center + z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
// If none contained, return FALSE
if(count == 0)
return FALSE;
// Update counter if they were all in front of plane
if(in_all_planes)
++num_points_in_frustum;
}
// Store BOOL flag if completely contained
if(completely_contained)
*completely_contained = (num_points_in_frustum == 6);
return TRUE;
}
//----------------------------------------------------------------------------
// Check whether a sphere is in frustum.
//----------------------------------------------------------------------------
BOOL FRUSTUM::check_sphere(float x_center, float y_center, float z_center,
float radius)
{
// make sure radius is in frustum
for(short i = 0; i < 6; i++)
{
if(D3DXPlaneDotCoord(&_planes[i], &D3DXVECTOR3(x_center, y_center, z_center)) < -radius)
return FALSE;
}
return TRUE;
}
測(cè)試代碼:
/************************************************************************************
PURPOSE:
frustum test.
************************************************************************************/
#include "core_global.h"
#include "frustum.h"
#define MAX_OBJECTS 256
class APP : public APPLICATION
{
public:
APP()
{
_width = 640;
_height = 480;
APPLICATION::_x_pos = (get_screen_width() - _width) / 2;
APPLICATION::_y_pos = (get_screen_height() - _height) / 4;
_style = WS_BORDER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU;
strcpy(_class_name, "object clipping class");
strcpy(_caption, "object clipping demo");
}
BOOL init()
{
// initialize graphics and set display mode
_graphics.init();
_graphics.set_mode(get_hwnd(), TRUE, TRUE);
_graphics.set_perspective(D3DX_PI / 4, 1.3333f, 1.0f, 10000.0f);
// create a font
_font.create(&_graphics, "Arial", 16, TRUE, FALSE);
// initialize input and input device
_input.init(get_hwnd(), get_inst());
_mouse.create(&_input, MOUSE, TRUE);
// load mesh
if(! _mesh.load(&_graphics, "..\\Data\\Yodan.x", "..\\Data\\"))
return FALSE;
for(short i = 0; i < MAX_OBJECTS; i++)
{
_objects[i].create(&_graphics, &_mesh);
_objects[i].move((float) (rand() % 4000) - 2000.0f, 0.0f, (float) (rand() % 4000) - 2000.0f);
}
return TRUE;
}
BOOL frame()
{
// read mouse data
_mouse.read();
// position camera and rotate based on mouse position
_camera.move(0.0f, 100.0f, 0.0f);
// _mouse.get_y_delta():
// get mouse's relative x movement coordinate.
//
// _mouse.get_x_delta():
// get mouse's relative y movement coordinate.
_camera.rotate_rel(_mouse.get_y_delta() / 200.0f, _mouse.get_x_delta() / 200.0f, 0.0f);
// set camera
_graphics.set_camera(&_camera);
// render everything
_graphics.clear(D3DCOLOR_RGBA(0, 64, 128, 255));
// begin render now
if(_graphics.begin_scene())
{
FRUSTUM frustum;
frustum.construct(&_graphics);
long num_drawn = 0;
// render each object in frustums
for(short i = 0; i < MAX_OBJECTS; i++)
{
float radius;
_objects[i].get_bounds(NULL, NULL, NULL, NULL, NULL, NULL, &radius);
if(frustum.check_sphere(_objects[i].get_x_pos(), _objects[i].get_y_pos(), _objects[i].get_z_pos(),
radius))
{
_objects[i].render();
num_drawn++;
}
}
char stats[128];
// display statistics
sprintf(stats, "%lu of 256 objects drawn.", num_drawn);
_font.print(stats, 0, 0, 400, 100);
_graphics.end_scene();
}
_graphics.display();
return TRUE;
}
BOOL shutdown()
{
return TRUE;
}
private:
GRAPHICS _graphics;
CAMERA _camera;
FONT _font;
INPUT _input;
INPUT_DEVICE _mouse;
MESH _mesh;
OBJECT _objects[MAX_OBJECTS];
};
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
APP app;
return app.run();
}
截圖:
