先看区别:
1, 相机可旋转了,上个例子是固定相机
2, 进行的不是背面消除,而是物体剔除
3, 渲染的不是单个物体,而是多个物体的渲染列表。列表中,是单物体多位置
先看物体剔除操作,
物体剔除是的位置在世界坐标系之后,在背面消除之前,即流水线目前是这样子的:
模型坐标->局部坐标到世界坐标转换->世界坐标->物体消除->背面消除->世界坐标到相机坐标转换->相机坐标->投影坐标->屏幕坐标
其中物体剔除,可以按照包围球测试,判断是否在视景体内,(当然,包围球测试并非一直有效,或许要用包围盒或者其它几何体)
有三种情况
1, 物体完全在视景体之外
2, 物体完全在视景体之内
3, 物体部分在视景体之内
包围球半径是物体包含顶点离物体中心的最远距离,
为计算方便,包围球在相机坐标中转换,相机设定为90度。球取6个点,分别与正负X,正负Y轴,正负Z轴的平行点,测试目的是为了排除整个球体,
判断点p(X,Y,Z)是否位于视景体外的通用规则为
if( (z>far_z ) || ( z<near_z ) || //远近裁剪面
( fabs(x)<z ) || //左右裁剪面
( fabs(y) <z ) ) //上下裁剪面
{
//物体不在视景体内
}
先定义几个剔除标志,根据各个轴
//剔除标志
#define CULL_OBJECT_X_PLANE 0x0001 //根据左右裁剪面进行剔除
#define CULL_OBJECT_Y_PLANE 0x0002 //根据上下裁剪面进行剔除
#define CULL_OBJECT_Z_PLANE 0x0004 //根据远近裁剪面进行剔除
#define CULL_OBJECT_XYZ_PLANES ( CULL_OBJECT_X_PLANE | CULL_OBJECT_Y_PLANE | CULL_OBJECT_Z_PLANE )
int ddraw_liushuixian::Cull_OBJECT4DV1( OBJECT4DV1_PTR obj, CAM4DV1_PTR cam, int cull_flags,ddraw_math math )
{
//将物体包围球球心变换为相机坐标
POINT4D sphere_pos; //用于存储包围球球心变换后的坐标
//对点进行变换
math.Mat_Mul_VECTOR4D_4X4( & obj->world_pos, & cam->mcam, & sphere_pos );
//根据剔除标记对物体执行剔除操作
if (cull_flags & CULL_OBJECT_Z_PLANE)
{
// cull only based on z clipping planes
// test far plane
if ( ((sphere_pos.z - obj->max_radius) > cam->far_clip_z) ||
((sphere_pos.z + obj->max_radius) < cam->near_clip_z) )
{
SET_BIT(obj->state, OBJECT4DV1_STATE_CULLED);
return(1);
} // end if
} // end if
if (cull_flags & CULL_OBJECT_X_PLANE)
{
// cull only based on x clipping planes
// we could use plane equations, but simple similar triangles
// is easier since this is really a 2D problem
// if the view volume is 90 degrees the the problem is trivial
// buts lets assume its not
// test the the right and left clipping planes against the leftmost and rightmost
// points of the bounding sphere
float z_test = (0.5)*cam->viewplane_width*sphere_pos.z/cam->view_dist;
if ( ((sphere_pos.x-obj->max_radius) > z_test) || // right side
((sphere_pos.x+obj->max_radius) < -z_test) ) // left side, note sign change
{
SET_BIT(obj->state, OBJECT4DV1_STATE_CULLED);
return(1);
} // end if
} // end if
if (cull_flags & CULL_OBJECT_Z_PLANE)
{
// cull only based on z clipping planes
// test far plane
if ( ((sphere_pos.z - obj->max_radius) > cam->far_clip_z) ||
((sphere_pos.z + obj->max_radius) < cam->near_clip_z) )
{
SET_BIT(obj->state, OBJECT4DV1_STATE_CULLED);
return(1);
} // end if
} // end if
if (cull_flags & CULL_OBJECT_Y_PLANE)
{
// test the the right and left clipping planes against the leftmost and rightmost
// points of the bounding sphere
float z_test = (0.5)*cam->viewplane_height*sphere_pos.z/cam->view_dist;
if ( ((sphere_pos.y-obj->max_radius) > z_test) || // right side
((sphere_pos.y+obj->max_radius) < -z_test) ) // left side, note sign change
{
SET_BIT(obj->state, OBJECT4DV1_STATE_CULLED);
return(1);
} // end if
} // end if
return ( 0 );
}
现在,可以逐步进行,
首先定义了一行物体的个数和物体间距
// object defines
#define NUM_OBJECTS 2 // number of objects on a row
#define OBJECT_SPACING 250 // spacing between objects
设置摄像机位置
POINT4D cam_pos = {0,200,0,1};
物体不放缩
VECTOR4D vscale={1.0,1.0,1.0,1},
因为要渲染一群坦克,所以用渲染列表
RENDERLIST4DV1 render_list;
在Game_Init()中,初始化发生改变如下:
将远切面改为1000
liushuixian.Init_CAM4DV1( *math, &cam, // the camera object
CAM_MODEL_EULER, // euler camera model
&cam_pos, // initial camera position
&cam_dir, // initial camera angles
NULL, // no initial target
50.0, // near and far clipping planes
1000.0,
90.0, // field of view in degrees
WINDOW_WIDTH, // size of final screen viewport
WINDOW_HEIGHT);
加载坦克模型
liushuixian.Load_OBJECT4DV1_PLG( & obj, "tank1.plg", & vscale, & vpos, & vrot );
设定模型
obj.world_pos.x = 0;
obj.world_pos.y = 0;
obj.world_pos.z = 400;
在Game_Main()中,每帧先重置列表
liushuixian.Reset_RENDERLIST4DV1( &render_list);
重置角度
ang_x = 0;
ang_y = 1;
ang_z = 0;
通过按键控制摄像机朝向
if ( KEYDOWN( VK_DOWN))
{
cam.dir.x += 1;
}
else
if ( KEYDOWN( VK_UP))
{
cam.dir.x -= 1;
}
if ( KEYDOWN( VK_LEFT))
{
cam.dir.y += 1;
}
else
if ( KEYDOWN( VK_RIGHT))
{
cam.dir.y -= 1;
}
旋转角度矩阵
mrot = math->Build_XYZ_Rotation_MATRIX4x4( ang_x, ang_y, ang_z );
上个DEMO,位置是这么设定的
liushuixian.Transform_OBJECT4DV1( &obj, &mrot, TRANSFORM_LOCAL_ONLY, 1, * math );
现在由于是列表,
for ( int x = - NUM_OBJECTS / 2; x < NUM_OBJECTS / 2; x++ )
{
for ( int z = - NUM_OBJECTS / 2; z < NUM_OBJECTS / 2; z++)
{
liushuixian.Reset_OBJECT4DV1( & obj );
//设置物体位置
obj.world_pos.x = x * OBJECT_SPACING + OBJECT_SPACING / 2;
obj.world_pos.y = 0;
obj.world_pos.z = 500 + z * OBJECT_SPACING + OBJECT_SPACING / 2;
//判断是否物体消除,没有的话,则转到世界坐标系,并插入渲染列表
if ( ! liushuixian.Cull_OBJECT4DV1( & obj, & cam, CULL_OBJECT_XYZ_PLANES))
{
liushuixian.Model_To_World_OBJECT4DV1( & obj, * math );
liushuixian.Insert_POLYF4DV1_RENDERLIST4DV1( & render_list, & obj );
}
接下来,改动的,还有个消除列表背面,这里没有,要加上,
void ddraw_liushuixian::Remove_Backfaces_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list, CAM4DV1_PTR cam, ddraw_math math )
{
// process each poly in mesh
for (int poly=0; poly < rend_list->num_polys; poly++)
{
// acquire polygon
POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly];
// is this polygon valid?
// test this polygon if and only if it's not clipped, not culled,
// active, and visible and not 2 sided. Note we test for backface in the event that
// a previous call might have already determined this, so why work
// harder!
if ( ( curr_poly == NULL) ||
!(curr_poly->state & POLY4DV1_STATE_ACTIVE) ||
(curr_poly->state & POLY4DV1_STATE_CLIPPED ) ||
(curr_poly->attr & POLY4DV1_ATTR_2SIDED) ||
(curr_poly->state & POLY4DV1_STATE_BACKFACE) )
{
continue;
}
VECTOR4D u, v, n;
// build u, v
math.VECTOR4D_Build(curr_poly->tvlist[0],curr_poly->tvlist[1], &u);
math.VECTOR4D_Build(curr_poly->tvlist[1],curr_poly->tvlist[2], &v);
// compute cross product
math.VECTOR4D_CROSS(&u, &v, &n);
// now create eye vector to viewpoint
VECTOR4D view;
math.VECTOR4D_Build(&curr_poly->tvlist[0], &cam->pos, &view);
// and finally, compute the dot product
float dp = math.VECTOR4D_DOT(&n, &view);
// if the sign is > 0 then visible, 0 = scathing, < 0 invisible
if (dp <= 0.0 )
SET_BIT(curr_poly->state, POLY4DV1_STATE_BACKFACE);
} // end for poly
fclose(fp);
} // end Remove_Backfaces_OBJECT4DV1
剩下部分都改成列表
liushuixian.Build_CAM4DV1_Matrix_Euler( *math, & cam, CAM_ROT_SEQ_ZYX );
liushuixian.Remove_Backfaces_RENDERLIST4DV1( & render_list, &cam, * math );
liushuixian.World_To_Camera_RENDERLIST4DV1( *math, &render_list, & cam );
liushuixian.Camera_To_Perspective_RENDERLIST4DV1( &render_list, & cam );
liushuixian.Perspective_To_Screen_RENDERLIST4DV1( &render_list, & cam );
ddraw->DDraw_Lock_Back_Surface();
liushuixian.Draw_RENDERLIST4DV1_Wire16( *math, & render_list, ddraw->getbackbuffer(), ddraw->getbacklpitch() );
加上个将物体插入列表中的函数,
int ddraw_liushuixian::Insert_OBJECT4DV1_RENDERLIST4DV1( RENDERLIST4DV1_PTR rend_list, OBJECT4DV1_PTR obj, int insert_local /* = 0 */ )
{
if ( ! ( obj->state & OBJECT4DV1_STATE_ACTIVE ) ||
( obj-> state & OBJECT4DV1_STATE_CULLED ) ||
! ( obj->state & OBJECT4DV1_STATE_VISIBLE))
{
return ( 0 );
}
for ( int poly = 0; poly < obj->num_polys; poly++)
{
POLY4DV1_PTR curr_poly = & obj->plist[poly];
if ( ! ( curr_poly->state & POLY4DV1_STATE_ACTIVE) ||
( curr_poly->state & POLY4DV1_STATE_CLIPPED) ||
( curr_poly->state & POLY4DV1_STATE_BACKFACE ))
{
continue;
}
POINT4D_PTR vlist_old = curr_poly->vlist;
if ( insert_local )
{
curr_poly->vlist = obj->vlist_local;
}
else
{
curr_poly->vlist = obj->vlist_trans;
}
if ( ! Insert_POLYF4DV1_RENDERLIST4DV1( rend_list, curr_poly))
{
curr_poly->vlist = vlist_old;
return ( 0 );
}
curr_poly->vlist = vlist_old;
}
return ( 1 );
}
当然,还要加上添加顶点到列表中,
int ddraw_liushuixian::Insert_POLY4DV1_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list, POLY4DV1_PTR poly, ddraw_math math)
{
if( rend_list->num_polys >= RENDERLIST4DV1_MAX_POLYS )
return ( 0 );
rend_list->poly_ptrs[rend_list->num_polys] = &rend_list->poly_data[rend_list->num_polys];
rend_list->poly_data[rend_list->num_polys].state = poly->state;
rend_list->poly_data[rend_list->num_polys].attr = poly->attr;
rend_list->poly_data[rend_list->num_polys].color = poly->color;
math.VECTOR4D_COPY( & rend_list->poly_data[rend_list->num_polys].tvlist[0], & poly->vlist[poly->vert[0]] );
math.VECTOR4D_COPY( & rend_list->poly_data[rend_list->num_polys].tvlist[1], & poly->vlist[poly->vert[1]] );
math.VECTOR4D_COPY( & rend_list->poly_data[rend_list->num_polys].tvlist[2], & poly->vlist[poly->vert[2]] );
math.VECTOR4D_COPY( & rend_list->poly_data[rend_list->num_polys].vlist[0], & poly->vlist[poly->vert[0]] );
math.VECTOR4D_COPY( & rend_list->poly_data[rend_list->num_polys].vlist[1], & poly->vlist[poly->vert[1]] );
math.VECTOR4D_COPY( & rend_list->poly_data[rend_list->num_polys].vlist[2], & poly->vlist[poly->vert[2]] );
if( rend_list->num_polys == 0 )
{
rend_list->poly_data[0].next = NULL;
rend_list->poly_data[0].prev = NULL;
}
else
{
rend_list->poly_data[rend_list->num_polys].next = NULL;
rend_list->poly_data[rend_list->num_polys].prev = & rend_list->poly_data[rend_list->num_polys-1];
rend_list->poly_data[rend_list->num_polys-1].next = & rend_list->poly_data[rend_list->num_polys];
}
rend_list->num_polys++;
return ( 1 );
}
经过一系列调整,OK了