1、使用MFC实现真实感图形绘制使用MFC实现真实感图形绘制真实感图形绘制是计算机图形学的一个重要组成部分。它综合利用数学、物理学、计算机科学和其他学科知识在计算机图形设备上生成象彩色照片那样的真实感图形。要用计算机图形设备绘制场景的真实感图形,就必须首先在计算机中建立该场景的模型,用这个模型来反映场景的特点和属性。这一模型通常是由一批几何数据及数据之间的拓扑关系来表示的,这就是造型技术,它是真实感图形绘制技术的重要组成部分。有了三维场景的模型,并给定了观察点和观察方向以后,就可以通过几何变换和投影变换在屏幕上显示该三维场景的二维图像。为了使二维图像具有立体感,并尽可能逼真地显示出该物体在现实世界
2、中被观察到的形象,就需要运用适当的光照模型,来模拟场景在现实世界中受到各种光源照射时的效果,这就是真实感图形的画面绘制技术,也就是真实感图形的生成技术。用计算机在图形设备上生成连续色调的真实感图形大致可以分为以下四步:第一步,用数学方法建立所需三维场景的几何描述,并将它们输入至计算机。这部分工作可由三维立体造型或曲面造型系统来完成。场景的几何描述直接影响了图形的复杂性和图形绘制的计算耗费,因此选择合理的、有效的数据表示和输入手段是非常重要的。第二步,将三维几何描述转换为二维投影图。这可以通过对场景的投影变换来完成。第三步,确定场景中的所有可见面,这需要使用隐藏面消除算法将被其他物体遮挡的不可见
3、面消去。第四步,计算场景中可见面的颜色,严格地说,就是根据基于光学物理的光照明模型计算可见面投射到观察者眼中的光亮度大小和色彩分量,并将它转换成适合图形设备的颜色值,从而确定投影画面上每一象素的颜色,最终生成图形。前三步的相关知识在前面已经进行了介绍,本章将重点介绍如何通过MFC编程的方式,利用光照模型计算场景中可见面的光亮度和颜色,并绘制最终的真实感图形。实际上,现在OpenGL和DirectX等图形函数库提供了很多支持真实感图形绘制的函数,使用它们可以更轻松的完成真实感图形绘制。本章仍采用最基本的MFC编程方式来实现真实感图形绘制,是为了让读者可以更好的体会和理解真实感图形绘制中用到的光照
4、模型等相关知识的原理。1演示程序使用的场景造型场景造型又叫几何造型,它是在计算机中建立的用于描述现实场景的几何模型,它是真实感图形生成的一个重要部分。在真实感图形中,一个景物的场景造型体现了该景物的几何特征和景物属性。场景造型的复杂程度直接决定了最终绘制的真实感图形的效果。本章的重点在于光照模型的实现,所以本章中的演示程序没有创建复杂场景,只使用了一种景物球体。演示程序根据球体的函数方程,计算球体表面的参数点坐标,然后按这些参数点对球体表面作三角剖分,最后利用光照模型对剖分得到的三角面片计算光照并进行绘制。1.1球体造型球体表面的函数方程式如下:其中,坐标为球心坐标,而坐标为球面上的参数点坐标
5、,为半径,、分别为经度和纬度参数变量。我们创建一个MFC项目RealityDemo,该应用程序作为本章中的演示程序。在该应用程序中添加一个类CObject3D,其基类为CObject。该类的实例对应场景中的一个景物。为了定义景物,需要定义如下结构体:/三维空间中点struct Point3D double x; double y; double z;/三角面struct TriSurface int no;/所属景物序号 Point3D p1,p2,p3;/三角面的顶点 double xn,yn,zn;/三角面的法向量;/景物光照参数struct Param double krd;/景物表面红
6、色光漫反射率 double kgd;/景物表面绿色光漫反射率 double kbd;/景物表面蓝色光漫反射率 double kra;/景物表面红色光泛光反射率 double kga;/景物表面绿色光泛光反射率 double kba;/景物表面蓝色光泛光反射率 double krs;/景物表面红色光镜面反射率 double kgs;/景物表面绿色光镜面反射率 double kbs;/景物表面蓝光镜面反射率 int n;/景物表面镜面高光指数;Point3D定义了三维空间中的一点。而TriSurface则定义了一个三角面片。结构体Param中的各成员变量指定了景物的光照参数,其具体含义将会在介绍光
7、照模型时说明。我们在CObject3D类中添加如下的成员变量和成员函数:public: /球体表面三角剖分后得到的三角面列表 CArray m_SurfaceList; Param m_Param;/球体表面光照参数 Point3D p3d101101;/球体表面参数点数组 int countx,county;/生成的参数点在经度和纬度上的数量 double bx,by,bz;/圆心坐标public: /创建指定球心和半径的球体 void CreateBall(double x0, double y0, double z0, double r); void SetSurfaceList();/
8、对球体表面进行三角剖分 void SetFVector(TriSurface* surface);/设置三角面的法向量 void SetParam(Param param);/设置球体表面光照参数1.2生成球体表面参数点成员函数CreateBall用于生成球体表面的参数点,并按这些参数点对球体表面进行三角剖分,将剖分生成的三角面片存入到列表m_SurfaceList中,其中三角剖分由成员函数SetSurfaceList完成。CreateBall函数的实现代码如下:/创建一个球体void CObject3D:CreateBall(double x0, double y0, double z0,
9、double r) int i=0,j; double pi = 3.1415926; bx = x0;by = y0;bz = z0; for (double u = -pi/2;upi/2+pi/32;u=u+pi/32) j = 0; for (double v = 0;v2*pi-pi/32;v=v+pi/32) p3dij.x = x0 + r * cos(u) * cos(v); p3dij.y = y0 + r * cos(u) * sin(v); p3dij.z = z0 + r * sin(u); j+; i+; countx = i; county = j; SetSurf
10、aceList();其中u和v的步长决定了产生的球体表面的参数点的数量。countx和county需要在类的构造函数中设置初始值为0。程序中将生成的球体表面参数点存入到p3d数组中,这样做是为了方便对球体表面进行三角剖分。1.3球体表面三角剖分对球体表面进行三角剖分采用如下方法。设球体表面上一个参数点为Pij,则其经度上的下一个参数点为pi+1j,而纬度上的下一个参数点为pij+1,再加上该点在对角线方向上的下一个参数点pi+1j+1,这四个点构成的区域可以剖分成两个三角面片。第一个三角面片的顶点为pij,pi+1j,pi+1j+1,第二个三角面片的顶点为pij,pij+1,pi+1j+1。在
11、生成三角面片的同时需要计算该三角面片的法向量,因为在计算光照的时候需要用到此法向量。平面上两个向量的叉乘积即为平面的法向量,其方向性满足右手定则,计算的时候需要注意法向量的方向应该是指向球外的。执行三角剖分的函数SetSurfaceList和计算法向量的函数SetFVector实现代码如下:/球体表面进行三角剖分void CObject3D:SetSurfaceList() for (int i=0;icountx-1;i+) for (int j=0;jp2.x - surface-p1.x; yu = surface-p2.y - surface-p1.y; zu = surface-p2
12、.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; su
13、rface-yn = (zu * xv - zv * xu) / d; surface-zn = (xu * yv - xv * yu) / d;在计算三角面片的法向量时计算的是单位法向量。在CObject3D类中还有一个函数SetParam用于设置景物的光照参数,其实现非常简单,代码如下:/设置光照参数void CObject3D:SetParam(Param param) m_Param.kra = param.kra; m_Param.kga = param.kga; m_Param.kba = param.kba; m_Param.krd = param.krd; m_Param.kg
14、d = 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函数创建球体景物,其实质是生成该球体的三角剖分面片列表,然后设置该球体的光照参数。此时就可以使用光照模型来计算球体表面的光照并进行绘制了。同样的,我们也可以创建其它形状的景物,其过程大体如下:获得景物表面的参数点,然后进行三角剖分,计算三角面片的法
15、向量,最后设置景物的光照参数。2 局部光照模型为了模拟光源照射在景物表面所产生的光照效果,就需要用到光照模型,光照模型是生成真实感图形的基础。光照模型是根据光学物理的有关定律,计算景物表面上任一点投向观察者眼中的光亮度的大小和色彩组成的公式。光照模型分为局部光照模型和整体光照模型。局部光照模型仅考虑光源直接照射在景物表面所产生的光照效果,景物表面通常被假定为不透明,且具有均匀的反射率。局部光照模型能表现由光源直接照射在漫射表面上形成的连续明暗色调、镜面上的高光以及由于景物相互遮挡而形成的阴影等,具有一定的真实感效果。 而整体光照模型除了考虑上述因素外,还要考虑周围环境对景物表面的影响,如出现在
16、镜面上的其他景物的印象,通过透明面可观察到后面的景物等。本节我们将实现的是局部光照模型。2.1局部光照模型概述从光源发出的光照射到景物表面时,会出现以下四种情形:(1)经景物表面向外反射形成反射光;(2)若景物透明,则入射光会穿透该景物,从而产生透射光;(3)若景物透明,入射光在穿透景物时会产生散射光;(4)部分入射光将被景物吸收而转换成热。显然,刺激人眼产生视觉效果的主要是反射光和透射光,如图9.1所示。物体表面的反射光和透射光的光谱分布决定了景物表面呈现的颜色,反射光和透射光的强弱则决定了景物表面的明暗程度。显然,反射光和透射光决定于入射光的强弱、光谱组成以及景物表面对入射光中不同波长光的
17、吸收程度。例如,当一束白光照射在一个吸收除红光以外所有不同波长光的不透明景物表面上时,景物呈红色。但若用一束绿光或蓝光照射该景物,则它将呈黑色。若已知入射光在每一波长上的强弱和物体表面在各波长上对光的吸收率,即给出入射光的光谱分布以及物体表面的反射率和透射率的光谱分布,我们就能根据环境的光源布置和景物表面的材料属性正确计算出景物表面反射光和透射光的光强和颜色。物体表面的反射光可分为漫反射光和镜面反射光。漫反射光可以认为是光穿过物体表面层被部分吸收后,重新发射出来的光。因此,漫反射光均匀地散布在各个方向,观察者不论站在哪一方位上,他所观察到的漫射光的强度均相等。镜面反射光则是物体的外表面对入射光
18、的直接反射。镜面反射光亮度沿镜面反射主方向最强,在该主方向四周则逐渐衰减,形成一定的空间分布,因而,观察者只有位于一定方向上,才能看到明亮的镜面反射光。以上就是构成局部光照模型的基本思想。根据这个思想,可以得到基本的光照模型。局部光照模型通常假定光源为点光源,物体为非透明体。因此,透射光和散射光可忽略不记。计算光照时只需计算反射光。常用的局部模型有Lamber漫反射光照模型和Phong局部光照模型。2.2Lambert漫反射光照模型概述自然界的绝大多数景物为理想漫反射体,Lambert余弦定律总结了一个理想漫反射物体在点光源照射下的反射规律,这就是Lambert漫反射模型。根据Lambert定
19、律,一个理想漫反射物体表面上反射出来的漫反射光的强度同入射光与物体表面法向量之间的余弦成正比,即:其中,I为景物表面在被照射点P处的漫反射光的光亮度,为点光源所发出的入射光亮度,为景物表面的漫反射率,为入射光与表面法向量之间的夹角,如图9.2所示。若景物表面在被照射点P处的单位法向量为N,P到点光源的单位向量为L,则上式可表达为如下的向量形式:显然,当点光源离被照射表面很远时,上式中的向量L变化很小,因而可将L看作为一常向量。我们称此时的点光源为方向光,它由一向量完全确定。可知,当入射角大于时,光源位于物体背面,因而该光源对被照射点的光亮度贡献为零,而当入射角为零度时,光源垂直照射在景物表面上
20、,此时反射光的强度最大。由不同材料构成的景物表面具有不同的漫反射率,漫反射率的大小标志了景物表面向四周反射光线的能力的大小。在入射光强度相同的情况下,漫反射率越大的景物,看上去越亮。在实际场景中,物体不仅接收光源发出的光,还会接收到从周围环境投射来的光,如房间的墙壁、天空等。在图形学中,称这部分光为环境光或泛光。环境光是一种分布光源,精确模拟它非常耗时。在局部光照模型中,常假定环境反射光是均匀入射的漫反射光,并用一常数来表示其强度。这样,Lambert漫反射光照模型可写成:其中,为入射的泛光光强,为景物表面对泛光的漫反射系数。在上面的公式里没有反映出光的距离衰减效应。光的传播以距离平方衰减,即
21、某处入射光的强度与该点和光源间的距离平方成反比。为了模拟光的距离衰减效应,可以采用线形衰减模型:其中,d为光源到景物的距离,k为一任意常数。通常可采用以下的Lambert漫反射模型来模拟光的各种距离衰减效果: 其中,为光源强度衰减因子,、和为用户确定的常数。上述模型可以推广到多光源情形,只需将这些点光源对景物表面的光亮度贡献逐个累加起来。此时,Lambert漫反射模型为:其中,分别为第个光源强度衰减因子、光强和单位入射方向的向量。2.3Lambert漫反射光照模型的实现在应用程序项目中添加一个新类CReality,我们在该类中实现光照模型。为了定义光源的相关属性,需要定义如下结构:/光源参数s
22、truct LightParam double m_distance;/光源距离 double c1;/距离衰减系数c1 double c2;/距离衰减系数c2 double c3;/距离衰减系数c3 double irl;/入射光红颜色分量强度 double igl;/入射光绿颜色分量强度 double ibl;/入射光蓝颜色分量强度 double xn,yn,zn;/入射单位方向向量 double f;/距离衰减;该结构中的各个成员变量决定了光源的相关属性。为了实现Lambert漫反射模型,需要在CReality类中添加如下的成员变量和成员函数:public: CArray m_Surfa
23、ceList;/景物表面三角面列表 CArray m_ParamList;/景物表面参数列表 CArray m_LightList;/光源参数列表 double lxn,lyn,lzn;/视点向量 int m_count;/景物数量 double ira;/泛光中红色光分量的强度 double iga;/泛光中绿色光分量的强度 double iba;/泛光中蓝色光分量的强度public: void AddObject3D(CObject3D* pObject);/添加景物 void AddLight(LightParam lightParam);/添加光源 /设置泛光强度 void SetIA
24、(double r, double g, double b); /计算传入两个向量夹角余弦值 double GetVectorPM(double xn1, double yn1, double zn1, double xn2, double yn2, double zn2); /计算投影点 CPoint Projection(Point3D point3d); /Lambert漫反射光照模型实现函数 void Lambert(CDC* pDC); void Clear();/清除当前景物和光源成员变量和成员函数的含义可以看相应的注释。下面分别介绍每个成员函数的实现。函数AddObject3D用
25、于添加要计算光照的景物,其实现代码如下:void CReality:AddObject3D(CObject3D* pObject) m_count+; for (int i=0;im_SurfaceList.GetSize();i+) TriSurface surface = (TriSurface)pObject-m_SurfaceList.GetAt(i); /背向视点方向的面不可见 if (GetVectorPM(surface.xn,surface.yn,surface.zn,lxn,lyn,lzn) = 0) surface.no = m_count;/设置当前面对应的光照参数 m_
26、SurfaceList.Add(surface); /设置景物参数 m_ParamList.Add(pObject-m_Param);实现代码中需要说明的是,对于景物表面进行三角剖分得到的三角面片来说,如果该面片的单位法向量与视线的单位方向向量的夹角余弦值小于0,表示该面片背向于观察者,所以不用计算其光照。函数AddLight用于添加光源,其实现代码如下:void CReality:AddLight(LightParam lightParam) /计算距离衰减 double f = 1.0/(lightParam.c1 + lightParam.c2 * lightParam.m_distan
27、ce + lightParam.c3 * lightParam.m_distance * lightParam.m_distance); if (f 1) f = 1.0; lightParam.f = f; /添加到光源列表中 m_LightList.Add(lightParam);在添加光源的同时,将光源的距离衰减值计算了出来。函数SetIA用于设置泛光强度,其三个参数代表了泛光中三个颜色分量的强度,其实现代码如下:void CReality:SetIA(double r, double g, double b) ira = r;iga = g; iba = b;在计算光照时需要计算向量夹
28、角余弦值,函数GetVectorPM实现此计算,其实现代码如下:double CReality:GetVectorPM(double xn1, double yn1, double zn1, double xn2, double yn2, double zn2) return (xn1 * xn2 + yn1 * yn2 + zn1 * zn2) / (sqrt(xn1 * xn1 + yn1 * yn1 + zn1 * zn1) * sqrt(xn2 * xn2 + yn2 * yn2 + zn2 * zn2);函数Projection用于计算传入的三维坐标点的投影点,其实现代码如下:CPoint CReality:Projection(Point3D point3d) CPoint point; point.x = (long)(point3d.x + point3d.z * 0.5 * cos(0); point.y = 800-(long)(point3d.y + point3d.z * 0.5 * sin(0); return point;我们采用的投影是斜
copyright@ 2008-2023 冰点文库 网站版权所有
经营许可证编号:鄂ICP备19020893号-2