創建3D圖形引擎(2)
本篇是創建3D圖形引擎(1)的續篇,3D圖形引擎的代碼以創建游戲內核中編寫的代碼為基礎進行開發。
視錐的介紹
視錐(viewing frustum)是6個平面的集合,它從觀察點向外擴展,以確定某個多邊形是否能夠被觀察到。
首先,可以將視錐看作是一個以觀察者開始擴展的金字塔,如下圖所示:
這個金字塔代表了觀察范圍(field of view,FOV),位于觀察范圍內的物體是可見的,而位于觀察范圍外的物體則是不可見的。
三維圖形引擎的每個物體都是由3D點(稱為頂點)構成的,每個視錐包含6個面(前、后、左、右、上、下)。通過一些數學計算,可以確定哪些頂點位于視錐內,哪些頂點位于視錐外。位于視錐內的頂點被渲染,而位于視錐外的頂點則不被渲染。同樣,當渲染多邊形時,只有那些在視錐內的頂點才被渲染。
平面與裁剪
視錐的6個側面被稱為剪切平面(clipping plane),Direct3D使用了一個名為D3DXPLANE的特定對象去包含平面數據,D3DXPLANE包含了4個變量:a、b、c、d,它們都是浮點型數據。
定義好一個平面后,為它指定一個特定的方向,并將它從原點移到相應的位置。事實上,一個平面就是由一條法線以及它與原點的距離進行定義的,如下圖所示,它演示了一個平面在三維空間中的方位。
不需要指定X、Y、Z的具體數值,你只要使用變量A、B、C,同時還有一個數值D來確定平面離原點的距離。為了定義一個平面,將A、B、C設置為法向量的值,這樣就可以使用一個平面去檢測指定的點位于平面的前面或者后面。
為了計算視錐的6個平面,可以將當前的觀察變換矩陣和投影矩陣組合起來,然后使用組合矩陣直接計算每個平面的A、B、C、D。
平面可見性檢測
為了檢測一個頂點位于平面的前方或后方,可以利用點積來計算,通過調用D3DXPlaneCoord函數來實現。
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.
完整視錐的檢測
對于立方體和長方體,需檢測所有的拐角頂點。如果所有的頂點都位于任一平面之后,那么立方體或長方體就位于視錐之外(因而也在視野之外)。如果有任一頂點位于視錐之內,或者說位于任一平面前(也就是說不是所有的頂點都位于任一平面后),那就意味著立方體或者長方體是可見的。至于球體,只要它與每個平面的距離 等于或大于球形的平面,那么它就是可見的。
FRUSTUM類
因為每次使用視錐所涉及的數學運算都是一樣的,所以完全可以創建一個類,讓它處理好數學方面的問題,包括創建視錐以及使用視錐去檢測一個物體是否可見等。
// 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;
每當觀察或投影矩陣發生變化時,請調用construct去構造6個測試平面,如果僅希望最接近的物體能被看到,則可以為遠端剪切平面指定一個新的距離值。
// 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系列函數判斷物體在視錐內是否可見。
// 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;
}
測試代碼:
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();
}
截圖: