原文链接:http://www.nicemxp.com/articles/23
物体从局部坐标最后渲染到屏幕上我们已经基本了解了,但是在3d流水线中我们不能把世界中所有的物体和物体所有的面都渲染出来,这样在视景体中物体的背面或者不在视景体中的物体,对他们进行计算都是做无用功,所以有了物体的背面消除和物体剔除。
背面消除
物体的背面消除很简单,在世界坐标中进行,即相机坐标转换操作之前,物体的面法向量与面到视点的向量的夹角不超过90度时,这个面就是对视点可见的,所以根据向量的点积,当两个向量的点积为负数时将面标记为背面,在3d流水线中忽略掉该背面,如图所示:
下面是我在vs中测试用例的截图,左侧是消除背面后的样子:
物体剔除
当世界中有许多物体时,我们要将不再视景体中的物体进行标记,在渲染阶段避免计算,节省大量时间,这里介绍的是包围球测试,以物体最远顶点到中心点距离为半径,画一个球将物体包围,通过计算这个球体是否在视景体的外侧,如果在视景体外侧则完全忽略该物体。如图所示:
图中是一个坐标系的俯视图,根据图示我们可以很容易在z方向判断出是否剔除物体,只要物体中心坐标z大于远裁剪面加球体半径,或者小于近裁剪面减去球体半径,就可以简单剔除掉物体。
现在我们考虑上下左右裁剪面,还是如上面的俯视图我们考虑球体是否与左右裁剪面相交,如果我们精确的计算需要计算物体中心点到左右裁剪面的距离,2d平面中可以使用点到直线的公式来计算,但是这样会很麻烦,所以这里我们只做简单的计算,当物体中心坐标分量x大于右裁剪面加上物体到右裁剪面的距离或者小于左裁剪面减去物体到左裁剪面的距离时,物体被剔除。下面是测试用例截图:
背面消除源码:
//物体背面消除
void RemoveObjBackface(OBJECT4DV1_PTR obj, POINT4D_PTR cam_pos)
{
//计算多边形面法线
VECTOR4D u, v, n;
//指向视点的向量
VECTOR4D view;
for (int poly = 0; poly < obj->num_polys; poly++)
{
POLY4DV1_PTR curpoly = &obj->plist[poly];
//取顶点
int vertex_idx1 = curpoly->vert[0];
int vertex_idx2 = curpoly->vert[1];
int vertex_idx3 = curpoly->vert[2];
//取u和v
VECTOR4D_SUB(&curpoly->vlist[vertex_idx2], &curpoly->vlist[vertex_idx1], &u);
VECTOR4D_SUB(&curpoly->vlist[vertex_idx3], &curpoly->vlist[vertex_idx2], &v);
//求法线n
VECTOR4D_CROSS(&u, &v, &n);
//指向视点
VECTOR4D_SUB(cam_pos, &curpoly->vlist[vertex_idx1], &view);
//计算点积
float dp = VECTOR4D_DOT(&n, &view);
if (dp <= 0.0)
SET_BIT(curpoly->state, POLY4DV1_STATE_BACKFACE);
}
}
物体剔除源码:
//剔除物体
int CullObj(OBJECT4DV1_PTR obj, CAM4DV1_PTR cam, int cull_flags)
{
POINT4D sphere_pos; //包围球坐标
//将包围球转换相机坐标
Mat_Mul_VECTOR4D_4X4(&obj->world_pos, &cam->mcam, &sphere_pos);
if (cull_flags & CULL_OBJECT_Z_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;
}
}
if (cull_flags & CULL_OBJECT_X_PLANE)
{
float z_test = (cam->viewplane_width / 2) * sphere_pos.z / cam->view_dist;
if ((sphere_pos.x - obj->max_radius > z_test)
|| (sphere_pos.x + obj->max_radius < -z_test))
{
SET_BIT(obj->state, OBJECT4DV1_STATE_CULLED);
return 1;
}
}
if (cull_flags & CULL_OBJECT_Y_PLANE)
{
float z_test = (cam->viewplane_height / 2) * sphere_pos.z / cam->view_dist;
if ((sphere_pos.y - obj->max_radius > z_test)
|| (sphere_pos.y + obj->max_radius < -z_test))
{
SET_BIT(obj->state, OBJECT4DV1_STATE_CULLED);
return 1;
}
}
return 0;
}