使用MFC实现真实感图形绘制真实感图形绘制是计算机图形学的一个重要组成部分。它综合利用数学、物理学、计算机科学和其他学科知识在计算机图形设备上生成象彩色照片那样的真实感图形。要用计算机图形设备绘制场景的真实感图形,就必须首先在计算机中建立该场景的模型,用这个模型来反映场景的特点和属性。这一模型通常是由一批几何数据及数据之间的拓扑关系来表示的,这就是造型技术,它是真实感图形绘制技术的重要组成部分。有了三维场景的模型,并给定了观察点和观察方向以后,就可以通过几何变换和投影变换在屏幕上显示该三维场景的二维图像。为了使二维图像具有立体感,并尽可能逼真地显示出该物体在现实世界中被观察到的形象,就需要运用适当的光照模型,来模拟场景在现实世界中受到各种光源照射时的效果,这就是真实感图形的画面绘制技术,也就是真实感图形的生成技术。用计算机在图形设备上生成连续色调的真实感图形大致可以分为以下四步:第一步,用数学方法建立所需三维场景的几何描述,并将它们输入至计算机。这部分工作可由三维立体造型或曲面造型系统来完成。场景的几何描述直接影响了图形的复杂性和图形绘制的计算耗费,因此选择合理的、有效的数据表示和输入手段是非常重要的。第二步,将三维几何描述转换为二维投影图。这可以通过对场景的投影变换来完成。第三步,确定场景中的所有可见面,这需要使用隐藏面消除算法将被其他物体遮挡的不可见面消去。第四步,计算场景中可见面的颜色,严格地说,就是根据基于光学物理的光照明模型计算可见面投射到观察者眼中的光亮度大小和色彩分量,并将它转换成适合图形设备的颜色值,从而确定投影画面上每一象素的颜色,最终生成图形。前三步的相关知识在前面已经进行了介绍,本章将重点介绍如何通过MFC编程的方式,利用光照模型计算场景中可见面的光亮度和颜色,并绘制最终的真实感图形。实际上,现在OpenGL和DirectX等图形函数库提供了很多支持真实感图形绘制的函数,使用它们可以更轻松的完成真实感图形绘制。本章仍采用最基本的MFC编程方式来实现真实感图形绘制,是为了让读者可以更好的体会和理解真实感图形绘制中用到的光照模型等相关知识的原理。1演示程序使用的场景造型场景造型又叫几何造型,它是在计算机中建立的用于描述现实场景的几何模型,它是真实感图形生成的一个重要部分。在真实感图形中,一个景物的场景造型体现了该景物的几何特征和景物属性。场景造型的复杂程度直接决定了最终绘制的真实感图形的效果。本章的重点在于光照模型的实现,所以本章中的演示程序没有创建复杂场景,只使用了一种景物——球体。演示程序根据球体的函数方程,计算球体表面的参数点坐标,然后按这些参数点对球体表面作三角剖分,最后利用光照模型对剖分得到的三角面片计算光照并进行绘制。1.1球体造型球体表面的函数方程式如下:000coscos[,]22cossin[0,2]sinxxruwuyyruwwzzru其中,坐标),,(000zyx为球心坐标,而坐标),,(zyx为球面上的参数点坐标,r为半径,u、w分别为经度和纬度参数变量。我们创建一个MFC项目RealityDemo,该应用程序作为本章中的演示程序。在该应用程序中添加一个类CObject3D,其基类为CObject。该类的实例对应场景中的一个景物。为了定义景物,需要定义如下结构体://三维空间中点structPoint3D{doublex;doubley;doublez;};//三角面structTriSurface{intno;//所属景物序号Point3Dp1,p2,p3;//三角面的顶点doublexn,yn,zn;//三角面的法向量};//景物光照参数structParam{doublekrd;//景物表面红色光漫反射率doublekgd;//景物表面绿色光漫反射率doublekbd;//景物表面蓝色光漫反射率doublekra;//景物表面红色光泛光反射率doublekga;//景物表面绿色光泛光反射率doublekba;//景物表面蓝色光泛光反射率doublekrs;//景物表面红色光镜面反射率doublekgs;//景物表面绿色光镜面反射率doublekbs;//景物表面蓝光镜面反射率intn;//景物表面镜面高光指数};Point3D定义了三维空间中的一点。而TriSurface则定义了一个三角面片。结构体Param中的各成员变量指定了景物的光照参数,其具体含义将会在介绍光照模型时说明。我们在CObject3D类中添加如下的成员变量和成员函数:public://球体表面三角剖分后得到的三角面列表CArrayTriSurface,TriSurfacem_SurfaceList;Paramm_Param;//球体表面光照参数Point3Dp3d[101][101];//球体表面参数点数组intcountx,county;//生成的参数点在经度和纬度上的数量doublebx,by,bz;//圆心坐标public://创建指定球心和半径的球体voidCreateBall(doublex0,doubley0,doublez0,doubler);voidSetSurfaceList();//对球体表面进行三角剖分voidSetFVector(TriSurface*surface);//设置三角面的法向量voidSetParam(Paramparam);//设置球体表面光照参数1.2生成球体表面参数点成员函数CreateBall用于生成球体表面的参数点,并按这些参数点对球体表面进行三角剖分,将剖分生成的三角面片存入到列表m_SurfaceList中,其中三角剖分由成员函数SetSurfaceList完成。CreateBall函数的实现代码如下://创建一个球体voidCObject3D::CreateBall(doublex0,doubley0,doublez0,doubler){inti=0,j;doublepi=3.1415926;bx=x0;by=y0;bz=z0;for(doubleu=-pi/2;upi/2+pi/32;u=u+pi/32){j=0;for(doublev=0;v2*pi-pi/32;v=v+pi/32){p3d[i][j].x=x0+r*cos(u)*cos(v);p3d[i][j].y=y0+r*cos(u)*sin(v);p3d[i][j].z=z0+r*sin(u);j++;}i++;}countx=i;county=j;SetSurfaceList();}其中u和v的步长决定了产生的球体表面的参数点的数量。countx和county需要在类的构造函数中设置初始值为0。程序中将生成的球体表面参数点存入到p3d数组中,这样做是为了方便对球体表面进行三角剖分。1.3球体表面三角剖分对球体表面进行三角剖分采用如下方法。设球体表面上一个参数点为P[i][j],则其经度上的下一个参数点为p[i+1][j],而纬度上的下一个参数点为p[i][j+1],再加上该点在对角线方向上的下一个参数点p[i+1][j+1],这四个点构成的区域可以剖分成两个三角面片。第一个三角面片的顶点为p[i][j],p[i+1][j],p[i+1][j+1],第二个三角面片的顶点为p[i][j],p[i][j+1],p[i+1][j+1]。在生成三角面片的同时需要计算该三角面片的法向量,因为在计算光照的时候需要用到此法向量。平面上两个向量的叉乘积即为平面的法向量,其方向性满足右手定则,计算的时候需要注意法向量的方向应该是指向球外的。执行三角剖分的函数SetSurfaceList和计算法向量的函数SetFVector实现代码如下://球体表面进行三角剖分voidCObject3D::SetSurfaceList(){for(inti=0;icountx-1;i++){for(intj=0;jcounty;j++){TriSurfacesurface1;surface1.p1=p3d[i][j];surface1.p2=p3d[(i+1)%countx][(j+1)%county];surface1.p3=p3d[(i+1)%countx][j];SetFVector(&surface1);m_SurfaceList.Add(surface1);TriSurfacesurface2;surface2.p1=p3d[i][j];surface2.p2=p3d[i][(j+1)%county];surface2.p3=p3d[(i+1)%countx][(j+1)%county];SetFVector(&surface2);m_SurfaceList.Add(surface2);}}}//计算三角面的法向量voidCObject3D::SetFVector(TriSurface*surface){doublexu,yu,zu,xv,yv,zv,d;xu=surface-p2.x-surface-p1.x;yu=surface-p2.y-surface-p1.y;zu=surface-p2.z-surface-p1.z;xv=surface-p3.x-surface-p1.x;yv=surface-p3.y-surface-p1.y;zv=surface-p3.z-surface-p1.z;d=sqrt((yu*zv-yv*zu)*(yu*zv-yv*zu)+(zu*xv-zv*xu)*(zu*xv-zv*xu)+(xu*yv-xv*yu)*(xu*yv-xv*yu));surface-xn=(yu*zv-yv*zu)/d;surface-yn=(zu*xv-zv*xu)/d;surface-zn=(xu*yv-xv*yu)/d;}在计算三角面片的法向量时计算的是单位法向量。在CObject3D类中还有一个函数SetParam用于设置景物的光照参数,其实现非常简单,代码如下://设置光照参数voidCObject3D::SetParam(Paramparam){m_Param.kra=param.kra;m_Param.kga=param.kga;m_Param.kba=param.kba;m_Param.krd=param.krd;m_Param.kgd=param.kgd;m_Param.kbd=param.kbd;m_Param.krs=param.krs;m_Param.kgs=param.kgs;m_Param.kbs=param.kbs;m_Param.n=param.n;}我们创建景物时,首先需要实例化CObject3D对象,然后调用CreateBall函数创建球体景物,其实质是生成该球体的三角剖分面片列表,然后设置该球体的光照参数。此时就可以使用光照模型来计算球体表面的光照并进行绘制了。同样的,我们也可以创建其它形状的景物,其过程大体如下:获得景物表面的参数点,然后进行三角剖分,计算三角面片的法向量,最后设置景物的光照参数。2局部光照模型为了模拟光源照射在景物表面所产生的光照效果,就需要用到光照模型,光照模型是生成真实感图形的基础。光照模型是根据光学物理的有关定律,计算景物表面上任一点投向观察者眼中的光亮度的大小和色彩组成的公式。光照模型分为局部光照模型和整体光照模型。局部光照模型仅考虑光源直接照射在景物表面所产生的光照效果,景物表面通常被假定为不透明,且具有均匀的反射率。局部光照模型能表现由光源直接照射在漫射表面上形成的连续明暗色调、镜面上的高光以及由于景物相互遮挡而形成的阴影等,具有一定的真实感效果。而整体光照模型除了考虑上述因素外,还要考虑周围环境对景物表面的影响,如出现在镜面上的其他景物的印象,通过透明面可观察到后面的景物等。本节我们将实现的是局部光照模型。2.1局部光照模型概述从光源发出的光照射到景物表面时,会出现以下四种情形:(1)经景物表面向外反射形成反射光;(2)若景物透明,则入射光会穿透该景物,从而