《高级计算机图形学》大作业报告
1 图形学与图形系统简述
计算机图形学是一种使用数学算法将二维或三维图形转化为计算机显示器的栅格形式的科学。它的主要研究内容就是研究如何在计算机中表示图形、以及利用计算机进行图形的计算、处理和显示的相关原理与算法。图形通常由点、线、面、体等几何元素和灰度、色彩、线型、线宽等非几何属性组成。从处理技术上来看,图形主要分为两类,一类是基于线条信息表示的,如工程图、等高线地图、曲面的线框图等,另一类是明暗图,也就是通常所说的真实感图形。
计算机图形学一个主要的目的就是要利用计算机产生令人赏心悦目的真实感图形。为此,必须建立图形所描述的场景的几何表示,再用某种光照模型,计算在假想的光源、纹理、材质属性下的光照明效果。
计算机图形系统主要由硬件与软件两大部分组成,硬件主要有显示器,显卡,鼠标等基本输入输出设备组成,负责显示图形,输入输出图形,软件则对图形进行各种坐标变换,光照处理,消隐处理等,使图形输出,并具有一定的真实感。本实验只是在Visual C++ 2008下用MFC编写的一个小的图形系统的软件部分,基本功能为用线框显示一个立方体和一个球体,然后用Gouraud 光照模型实现光照效果,并添加了部分额外的功能,如:旋转物体,在多个摄像机中显示图形,移动和旋转摄像机等。软件的编写过程按对象建模,坐标变换,可见面判别,光照处理,面的绘制,物体和摄像机操作控制的步骤完成,下面将按编写过程中的步骤分别说明。
2 对象建模
2.1 三维对象表示
一个对象最基本的特征包括:点,线,面。要在建模坐标系中表示一个物体,就要记录该物体的点,线,面的信息,并要建立这三者之间的联系。这里用指向边表的指针来表示三维对象,三者间的关系为:一个对象由多个多边形面组成,每个多边形用指向边表的指针来表示,边表中每条边只存储一次,每条边都包括构成该边的两个顶点,每个顶点只在顶点表V中存储一次。例如要建立如图1所示简单的正四面体,则存储结构如下:
V={V1,V2,V3,V4}={(x1,y1,z1), (x2,y2,z2), (x3,y3,z3), (x4,y4,z4)}
E={E1,E2,E3,E4,E5,E6}={(V1,V2),(V1,V3),(V2,V3),(V1,V4),(V3,V4) ,(V2,V4)}
P={P1, P2, P3, P4}={(E1,E3,E2),(E2,E5,E4),(E1,E4,E6),(E3,E6,E5)}
因此,应该建立顶点类,边类,面类和物体类。类声明分别如下:
//顶点类
class CVertex
{
public:
CVertex(void);
CVertex(const CVertex &v);
CVertex(float x, float y, float z):pworld(x, y, z),position(x , y, z),pedge(NULL){};
~CVertex(void);
//世界坐标
CPoint3d pworld;
//变换坐标
CPoint3d position;
//光照强度
CIntens I;
//与点相关的边表
EdgeLink *pedge;
//转换对象的世界坐标
void trfworld(const CMatrix4d &m);
//变换临时坐标
void trfpos(const CMatrix4d &m);
void operator=( CVertex const &v);
};
//边类
class CEdge
{
public:
CEdge(const CEdge &e);
CEdge(int mh, int mt):head(mh),tail(mt),fp(-1),sp(-1){};
~CEdge(void);
//第一个节点
int head;
//第二个顶点
int tail;
//第一个面
int fp;
//第二个面
int sp;
};
//面类(三角形面)
class CPlane
{
public:
CPlane(const CPlane &p);//拷贝函数
CPlane(int m1,int m2, int m3):ft(m1),sd(m2), thd(m3),show(false){};
~CPlane(void);
int ft;//第一条边
int sd;//第二条边
int thd;//第三条边
bool show;//是否显示
CVector3d norvec;//法向量
};
//三维对象类
class CGrObj
{
public:
CGrObj(void);
~CGrObj(void);
CPoint3d position;//在世界坐标系中的位置
CVector3d mcrdx;//建模坐标系x轴的向量
CVector3d mcrdy;//建模坐标系y 轴的向量
CVector3d mcrdz;//建模坐标系z 轴的向量
vector < CVertex > V;//顶点表
vector E;//边表
vector P;//面表
//float kd;//漫反射系数
// 调整建模坐标系向量
CMatrix4d MatrMtoW;
// 把模型加载到世界坐标
bool Load(void);
//指向下一个物体
CGrObj *pnextobj;
//对对象的pworld进行变换
void transformpword(CMatrix4d &m);
//对对象的position进行变换
void transformptemp(CMatrix4d &m);
//判别可见面
void visable(void);
//转换成投影坐标
void toProject(void);
//显示
void Drow( CDC* pDC,int DrowType);
//CDC* pDC;
//更新坐标,将世界坐标写入临时坐标中
void Updata(void);
//画三角形
void Trangle(CDC * pDc, CPoint * P, CIntens * I);
//画线
void DrowLine(CDC * pDc,int x1, int x2, CIntens & I1, CIntens & I2, int y);
protected:
// 画横线
void DrowLine( CDC* pDC);
//画面
void DrowReal( CDC* pDC);
//加一个顶点
void addVertex( float x, float y, float z);
//加一条边
void addEdge(int v1,int v2);
//加一个三角面
void addPlane(int E1,int E2,int E3);
private:
// 获取每一顶点的颜色
void GetColor(void);
// 调整将建模坐标转换到世界坐标的矩阵
void coordAdjust(void);
//求每个面的法向量
void getNorVect(void);
};
2.2 对象建立
分别建两个CGrObj的子类,class CCube和class CSphere,在其构造函数中建立对象的数据。
建立立方体:由输入的棱长,分别在顶点表中加入八个顶点的位置,在边表中加入边对应的顶点的索引,在面表中加入每个面对应的边的索引。
建立球体:
a、径先求南极和北极的坐标,加入顶点表。
b、由半径,经线数,纬线数,从第一条经线开始求出每条经线上各纬度的点的坐标,加入顶点表。
c、由第一条经线开始,向边表中加入本条经线与下一条经线间的所有边。最后一条经线与第一条经线间的边单独处理。
d、由第一条经线开始,向面表中加入本条经线与下一条经线间的所有的面。最后一条经线与第一条经线间的面单独处理。
3 坐标变换
在第2部分的对象建模中,对象是在模型坐标系中建立的,要把对象放到世界坐标系中,就要将对象从模型坐标系变换到世界坐标系。此功能在CGrObj 类中通过调用 Load( )方法把对象加载到世界坐标系中。
为建立观察坐标系,首先需要在世界坐标系中挑选一点作为观察坐标系的原点,也称为观察参考点。它就类似于相机镜头所在的位置。然后在该点处指定法矢量N,法矢量N即为观察坐标系中的z轴的正向和观察平面的方向。
为了方便计算,将摄像头的位置放在观察坐标系中的原点,观察方向为z轴负向。
要在摄像机中显示对象,则应该把对象坐标从世界坐标系转换到与摄像机对应的观察坐标系中。然后,建立起与观察坐标系中xy平面相平行的观察平面,将观察坐标再投影到观察平面上。
观察坐标投影到观察平面上的过程可以经由以下几个步骤来完成。首先,进行观察空间的规范化变换,然后,在规范化空间内进行图形的三维裁剪。裁剪完后即可作正投影,将裁剪后的图形投影到观察平面上。最后,将观察窗口中的内容在图形输出设备上进行显示。
4 可见面判别
可见面的判别可以在世界坐标系中完成,也可以在规范化观察坐标系中完成,此实验中采用后种方法。在规范化坐标系中,求出每个面的法向量,法向量的z 分量大于0,则该面可见,否则不可见。判断每一个面的可见性之后,在面类的可见属性中修改其可见属性。
如果用线框图来表示物体,在将坐标变换成设备坐标之后,即可对边表中的每条边进行判断,如果一条边的两个关联面都是不可见的,则不画该边;否则,读出该边的两个顶点坐标,在屏幕上的两点间划直线。即可得到如下线框图。
图-2 线框立方体 图-3 线框球体
5 光照处理和面的绘制
5.1 平面处理
最简单的光照处理方法是每个平面为同一种颜色填充,取三个顶点处的光强的平均值作为面的,效果图如下:
图-4 平面绘制的立方体 图-5平面绘制的球体
很容易看出,图片的光照效果并不理想,因为各个面片的边缘处的颜色变化太大。因此要有真实感,必须采用明暗处理方法。
5.2 明暗处理
最常见的简单光照明暗处理算法有Gouraud明暗处理和Phong明暗处理方法,本实验中采用双线性光强插值的Gouraud明暗处理方法。每次物体的世界坐标有变化时,都算出每个面的法向量的坐标,然后由Gouraud算法算出每个顶点处的光强。
双线性光强插值:由顶点的光强插值计算各边的光强,然后由各边的光强插值计算出多边形内部点的光强。如下:
采用双线性光强插值的Gouraud明暗处理方法后,效果图如下:
图-6明暗处理绘制的立方体 图-7明暗处理绘制的球体
5.3 着色优化
当用平面处理的方法填充平面时,如果相邻面的颜色相差比较大,就会产生较大失真效果。当用双线性明暗处理方法填充三角面时,每个三角面都要计算逐个点的颜色,对于三个顶点颜色相差非常小,肉眼几乎无法分辨时,这种方法非常耗时,效率很低,可以对其采用平面处理的方法填充。下面为两种方法下效果的对比图:
图-8 Gouraud明暗处理 图-9明暗处理和平面处理相结合
从上图可以看出,只要控制得好,可以大大提高平面绘制的效率。上图中近40%的三角形填充采用平面填充方法,从而省去了逐点计算光强的时间。
6 物体和摄像机操作控制
6.1 物体旋转
要实现物体的旋转可以在系统中设置一定时器,每隔一断时间将物体在世界坐标系中的各点的坐标旋转一定的角度,然后重绘窗口。下面为相隔一定时间后同一物体旋转后的线框图:
图-10 旋转前的立方体 图-11旋转后的立方体
6.2 摄像机控制
摄像机的平行移动控制可以通过改变摄像机的位置来实现,而摄像机的旋转可以通过旋转观察坐标系的x和y坐标来实现。通过View类响应键盘和鼠标的消息,来控制摄像头的移动和旋转。
键盘上的方向键或ADSW键控制摄像头的平行移动。如果要旋转摄像头,可点击鼠标左键,此时开启了用鼠标控制摄像头的功能,鼠标的移动方向即为摄像头的视野的转动方向。转动完成后,再次点击鼠标左键,关闭用鼠标控制摄像头的功能。如下为旋转前和旋转后的对比图:
图-12 摄像头旋转前 图-13摄像头旋转后
6.3 两个摄像头
在系统中设置两个摄像头,以从不同的角度观察同一个物体。只需在系统中再加一个CCamera 类的对象,并将其位置与x 轴方向重新设定即可。效果如下:
图-14两个摄像头效果图
(前面的图是用Win Live Writer传的,被压缩过,效果太TM烂了,这个自己在网页编辑中传的)
7 总结
接触图形学三维部分已经有近半年的时间了,但半年来基本都是理论层面的学习,几乎没有动手写过三维程序,此次大作业正好是一次绝好的练习机会。只有实践时才会真正的体会到理论与实践的差距,平时学习图形学中的基本原理与算法时,虽然觉得很多算法思想很精妙,但也能理解是什么意思。可是当真正动手编程时,才知道平时有好多东西都是一知半解,例如:学习三维对象的表示时,课本上有三维对象的三种表示方法——显式表示,指向顶点表的指针,指向边表的指针。这几种方法理解起来也不难,很容易就知道第三种是最合适的,但具体在编程实现数据结构时又要考虑到要能够由点找到边,找到面,不能每次都是在各表在进行查找,于是在每个顶点中除了应该保存位置,颜色等信息外,还应加入与之关联的边的指针,在每条边中除了保存两个顶点的指针,还应该保存与之关联的面的指针。诸如此类的问题还有许多,在此不赘述。以后还得加强练习才能真正的学懂图形学。