下面将渲染列表从世界坐标系—>摄像机坐标系
void ddraw_liushuixian::World_To_Camera_RENDERLIST4DV1( ddraw_math math, RENDERLIST4DV1_PTR rend_list, CAM4DV1_PTR cam)
{
for( int poly = 0; poly < rend_list->num_polys; poly++ )
{
//获得当前多边形
POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly];
//当且仅当多边形没有被剔除或者裁剪掉,同时处于活动状态且可见时,才对其进行变换)
if( ( curr_poly == NULL ) ||
!( curr_poly->state & POLY4DV1_STATE_ACTIVE ) ||
( curr_poly->state & POLY4DV1_STATE_CLIPPED ) ||
( curr_poly->state & POLY4DV1_STATE_BACKFACE ) )
continue; //进入下一个多边形
//满足条件,对其进行变换
for( int vertex = 0; vertex < 3; vertex ++ )
{
//使用相机对象中的矩阵mcma对顶点进行变换
POINT4D presult; //用于存储每次变换的结果
//对顶点进行变换
math.Mat_Mul_VECTOR4D_4X4( &curr_poly->tvlist[vertex], &cam->mcam, &presult );
//将结果存回去
math.VECTOR4D_COPY( & curr_poly->tvlist[vertex], &presult );
}
}
}
在每帧中调用
liushuixian.World_To_Camera_RENDERLIST4DV1( math, &rend_list, &cam );
2014年1月6日星期一(流水线:相机坐标系到投影坐标系变换)
本来,不想多写,只想罗列写代码,不过,发现,我并没有真正搞明白透视这块,有必要加深印象。
视平面和视点之间的距离被称为视距(d)。
从物体的每点与视点发出一条连线,该线与视平面的交点就是投影。然后,在视平面上形成的图像被渲染到计算机屏幕上。
计算时,根据相似三角形,设要投影的点为P(x0,y0,z0),视点为(0,0,0),视距为d,则投影坐标为(x0*d/z0,y0*d/z0,d),当d=0时,无穷大,不能对z=0的顶点进行投影;z为负值时,物体被倒转投影。可以通过指定近裁剪面避免这两种情况。
通过矩阵变换的话,投影矩阵为
T = [1 0 0 0
0 1 0 0
0 0 1 1/d
0 0 0 0]
则P点为
[x0 y0 z0 1] * [1 0 0 0
0 1 0 0
0 0 1 1/d
0 0 0 0]
=[x0 y0 z0 z0/d]
其中w=z0/d
将分量x,y,z都除以w,则坐标为
[ x0*d/z0 y0*d/z0 d 1]
对于d的取值,可以任意。最合适的d值。
有两种选择,
可以选为1,这时候投影坐标变为
[x0/z0 y0/z0 1 1]
投影矩阵变为
T = [1 0 0 0
0 1 0 0
0 0 1 1
0 0 0 0]
在fov为90的时候,坐标x和y都在-1和1之间,在转换到屏幕坐标时,要根据屏幕分辨率宽度*高度进行缩放,使之一致。在书上,宽高比就是x/y,也就是说,变宽的同时,必须变高,才能达到不变形。
另外一种设置d的方法是,根据屏幕坐标范围计算 ,考虑宽高比ar,则投影变换矩阵为
T = [d 0 0 0
0 d*ar 0 0
0 0 1 1
0 0 0 0]
即
Xper = x*d/z
Yper = y *ar*d/z
透视投影将视景体内用相机坐标表示的点变换到视平面上,以便下一步将其变换为屏幕坐标,并可以将棱锥体视景体变成长方体。,以利于裁剪。
裁剪有两种方式,
1, 图像空间裁剪,就是先转换为屏幕坐标后,再用屏幕空间或视口裁剪,但是物体可能会在z<0处,需要在2D空间内对每个物体进行裁剪,像素测试
2, 在物体空间裁剪,在投影变换之前,消除物体 和/或背面,对所有不完全位于视景体的几何体进行裁剪。但是不但要裁剪多边形顶点,还需要裁剪纹理坐标和光照坐标。
投影变换可以将棱锥体变为立方体:将每个点乘以d再除以z坐标即可。
理论终于看完了,看下代码吧。
void ddraw_liushuixian::Camera_To_Perspective_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list, CAM4DV1_PTR cam)
{
for( int poly = 0; poly < rend_list->num_polys; poly++ )
{
//获得当前多边形
POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly];
//当且仅当多边形没有被剔除或者裁剪掉,同时处于活动状态且可见时,才对其进行变换)
if( ( curr_poly == NULL ) ||
!( curr_poly->state & POLY4DV1_STATE_ACTIVE ) ||
( curr_poly->state & POLY4DV1_STATE_CLIPPED ) ||
( curr_poly->state & POLY4DV1_STATE_BACKFACE ) )
continue; //进入下一个多边形
//满足条件,对其进行变换
for( int vertex = 0; vertex < 3; vertex ++ )
{
float z = curr_poly->tvlist[vertex].z;
//根据相机的观察参数对顶点进行变换
curr_poly->tvlist[vertex].x = cam->view_dist * curr_poly->tvlist[vertex].x / z;
curr_poly->tvlist[vertex].y = cam->view_dist * curr_poly->tvlist[vertex].y * cam->aspect_ratio / z;
}
}
}
在game_main()每帧中,
liushuixian.Camera_To_Perspective_RENDERLIST4DV1( &rend_list, & cam );
2014年1月7日星期二(流水线:转换到屏幕坐标)
这里我觉得很简单了,主要是坐标平移(必要时缩放)的问题,并反转Y轴,
void ddraw_liushuixian::Perspective_To_Screen_RENDERLIST4DV1(RENDERLIST4DV1_PTR rend_list, CAM4DV1_PTR cam)
{
for( int poly = 0; poly < rend_list->num_polys; poly++ )
{
//获得当前多边形
POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly];
//当且仅当多边形没有被剔除或者裁剪掉,同时处于活动状态且可见时,才对其进行变换)
if( ( curr_poly == NULL ) ||
!( curr_poly->state & POLY4DV1_STATE_ACTIVE ) ||
( curr_poly->state & POLY4DV1_STATE_CLIPPED ) ||
( curr_poly->state & POLY4DV1_STATE_BACKFACE ) )
continue; //进入下一个多边形
float alpha = ( 0.5 * cam->viewport_width - 0.5 );
float beta = ( 0.5 * cam->viewplane_height - 0.5 );
//满足条件,对其进行变换
for( int vertex = 0; vertex < 3; vertex ++ )
{
//顶点的透视坐标是归一化的,取值范围为-1到,对坐标进行缩放,并反转Y轴
curr_poly->tvlist[vertex].x = alpha + alpha * curr_poly->tvlist[vertex].x;
curr_poly->tvlist[vertex].y = beta - beta * curr_poly->tvlist[vertex].y;
}
}
}
在每帧中,调用
liushuixian.Perspective_To_Screen_RENDERLIST4DV1( &rend_list, & cam );
流水线到这里应该结束了。
继续DEMO7-1,下面进行执行渲染列表,用线框模式和16位RGB颜色模式绘制渲染列表中所有的面,线框模式无需对多边形进行排序。
void ddraw_liushuixian::Draw_RENDERLIST4DV1_Wire16(ddraw_math math, RENDERLIST4DV1_PTR rend_list, UCHAR *video_buffer,int lpitch)
{
for( int poly = 0; poly < rend_list->num_polys; poly++ )
{
//获得当前多边形
POLYF4DV1_PTR curr_poly = rend_list->poly_ptrs[poly];
//当且仅当多边形没有被剔除或者裁剪掉,同时处于活动状态且可见时,才对其进行变换)
if( ( curr_poly == NULL ) ||
!( curr_poly->state & POLY4DV1_STATE_ACTIVE ) ||
( curr_poly->state & POLY4DV1_STATE_CLIPPED ) ||
( curr_poly->state & POLY4DV1_STATE_BACKFACE ) )
continue; //进入下一个多边形
//绘制三角形的边
//2d初始化过程中已经设置好裁剪,位于D屏幕/窗口外的多边形都将被裁剪掉
math.Draw_Clip_Line16( rend_list->poly_ptrs[poly]->tvlist[0].x,
rend_list->poly_ptrs[poly]->tvlist[0].y,
rend_list->poly_ptrs[poly]->tvlist[1].x,
rend_list->poly_ptrs[poly]->tvlist[1].y,
rend_list->poly_ptrs[poly]->color,
video_buffer, lpitch );
math.Draw_Clip_Line16( rend_list->poly_ptrs[poly]->tvlist[1].x,
rend_list->poly_ptrs[poly]->tvlist[1].y,
rend_list->poly_ptrs[poly]->tvlist[2].x,
rend_list->poly_ptrs[poly]->tvlist[2].y,
rend_list->poly_ptrs[poly]->color,
video_buffer, lpitch );
math.Draw_Clip_Line16( rend_list->poly_ptrs[poly]->tvlist[2].x,
rend_list->poly_ptrs[poly]->tvlist[2].y,
rend_list->poly_ptrs[poly]->tvlist[0].x,
rend_list->poly_ptrs[poly]->tvlist[0].y,
rend_list->poly_ptrs[poly]->color,
video_buffer, lpitch );
}
}
在每一帧
ddraw->DDraw_Lock_Back_Surface();
liushuixian.Draw_RENDERLIST4DV1_Wire16( math, & rend_list, ddraw->getbackbuffer(), ddraw->getbacklpitch() );
ddraw->DDraw_Unlock_Back_Surface();
ddraw->DDraw_Flip();
mytool.Wait_Clock( 30 );
结果发现没有三角形。只有黑屏。说明变换中某步或某几步有问题,
2014年1月8日星期三(诊断DEMO7-1)
发现
Insert_POLYF4DV1_RENDERLIST4DV1( & rend_list, &poly1 );
Build_XYZ_Rotation_MATRIX4X4( 0, ang_y, 0, &mrot );
Build_CAM4DV1_Matrix_Euler( & cam, CAM_ROT_SEQ_ZYX );
Perspective_To_Screen_RENDERLIST4DV1( &rend_list, & cam );
有问题,
从后往前看,发现
Perspective_To_Screen_RENDERLIST4DV1( &rend_list, & cam );
拼写错误。
改后,OK
2014年1月13日星期一(继续DEMO7-1)
继续往下纠正,
liushuixian.Insert_POLYF4DV1_RENDERLIST4DV1( & rend_list, &poly1 );
//Insert_POLYF4DV1_RENDERLIST4DV1( & rend_list, &poly1 );
有的话
也是笔误
memcpy( ( void * ) & rend_list->poly_data[rend_list->num_polys], (void * )poly,sizeof( POLYF4DV1 ) );
,改掉后OK,继续往下走
Build_XYZ_Rotation_MATRIX4X4()这个函数也有问题,改造后不动了,
去掉各种SWITCH,只考虑绕Y轴旋转
实际上就是
MATRIX4X4 mx, my, mz,mtmp; // working matrices
float sin_theta=0, cos_theta=0; // used to initialize matrices
cos_theta = Fast_Cos(theta_y);
sin_theta = Fast_Sin(theta_y);
// set the matrix up
Mat_Init_4X4(&my,cos_theta, 0, -sin_theta, 0,
0, 1, 0, 0,
sin_theta, 0, cos_theta, 0,
0, 0, 0, 1);
// that's it, copy to output matrix
MAT_COPY_4X4(&my,mrot);
最后发现是cos_look[]和sin_look[]数组的缘故,有个fast_cos()和fast_sin()用到了,这里去掉这些,OK了,至此,流水线OK了,结果很简单,如下所示,