三维投影总结:数学原理、投影几何、OpenGL教程、我的方法

http://blog.csdn.net/wishchin/article/details/15816253


  如果要得到pose视图,除非有精密的测量方法,否则进行大量的样本采集时很耗时耗力的。可以采取一些取巧的方法,正如A Survey on Partial of 3d shapes,描述的,可以利用已得到的3D模型,利用投影的方法 (page10-透视投影或者正射投影),自动得到精确的3D单向视图。

        其中的遇到了好几个难题:透视投影的视角问题;单侧面的曲面补全问题(曲面插值问题);pose特征的描述性问题

        一篇文章看完视觉及相关通略。


先普及一下基础知识:

一:图像处理、计算机图形学、计算机视觉和模式识别领域的区别和联系

        三者之间既有区别,又有联系。 原文链接:http://blog.csdn.net/qianmianyuan/article/details/9130805

        计算机图形学是给定关于景象结构、表面反射特性、光源配置及相机模型的信息,生成图像。 而计算机视觉是给定图象,推断景象特性实现的是从模型到图像的变换,也就是说从图象数据提取信息,包括景象的三维结构,运动检测,识别物体等。 模式识别则是从特征空间到类别空间的变换。研究内容包括特征提取( PCA,LDA,LFA,Kernel,Mean Shift,SIFT,ISOMAP,LLE);特征选择;分类器设计(SVM,AdaBoost)等。 

       总之,计算机图形学是计算机视觉的逆问题,两者从最初相互独立的平行发展到最近的融合是一大趋势。图像模式的分类是计算机视觉中的一个重要问题,模式识别中的许多方法可以应用于计算机视觉中。

       图形学讲的是图形,也就是图形的构造方式,是一种从无到有的概念,从数据得到图像。数字图像处理是对已有的图像进行变换、分析、重构,得到的仍是图像。PR本质就是分类,根据常识或样本或二者结合进行分类,可以对图像进行分类,从图像得到数据。

        

       Computer Graphics和Computer Vision是同一过程的两个方向。Computer Graphics将抽象的语义信息转化成图像,Computer Vision从图像中提取抽象的语义信息。Image Processing探索的是从一个图像或者一组图像之间的互相转化和关系,与语义信息无关。

先说区别
        Computer Graphics,简称 CG 。输入的是对虚拟场景的描述,通常为多边形数组,而每个多边形由三个顶点组成,每个顶点包括三维坐标、贴图坐标、rgb颜色等。输出的是图像,即二维像素数组。
        Computer Vision,简称 CV。输入的是图像或图像序列,通常来自相机或usb摄像头。输出的是对于图像序列对应的真实世界的理解,比如检测人脸、识别车牌。
       Digital Image Processing,简称 DIP。输入的是图像,输出的也是图像。Photoshop中对一副图像应用滤镜就是典型的一种图像处理。常见操作有模糊、灰度化、增强对比度等。

        可以简单地理解为:图像学更侧重于图像的字母表和语法部分;而计算机视觉更侧重于图像从语法到语义的部分;而数字图像处理侧重于图像的状态空间转化过程及总结其一般规律。


再说联系:
         CG 中也会用到 DIP,现今的三维游戏为了增加表现力都会叠加全屏的后期特效,原理就是 DIP,只是将计算量放在了显卡端。CV 更是大量依赖 DIP 来打杂活,比如对需要识别的照片进行预处理。最后还要提到近年来的热点——增强现实(AR),它既需要 CG,又需要 CV,当然也不会漏掉 DIP。它用 DIP 进行预处理,用 CV 进行跟踪物体的识别与姿态获取,用 CG 进行虚拟三维物体的叠加。

          简单点说吧,1 计算机视觉,里面人工智能的东西更多一些,不仅仅是图像处理的知识,还涵盖了人工智能,机器学习等领域知识;2,计算机图形学,主要涉及图形成像及游戏类开发,如opengl等,还有就是视频渲染等;3,图像处理,这个主要针对图像图像的基本处理,如图像检索或则图像识别,压缩,复原等等操作。以上只是本人字面理解


         计算机图形学和数字图像处理是比较老的技术。计算机视觉要迟几十年才提出。计算机图形学和数字图像处理的区别在于图形和图像。
         图形是矢量的、纯数字式的。图像常常由来自现实世界的信号产生,有时也包括图形。而图像和图形都是数据的简单堆积,计算机视觉要从图像中整理出一些信息或统计数据,也就是说要对计算机图像作进一步的分析。
以上是它们的区别,下面来说联系:
       计算机图形学的研究成果可以用于产生数字图像处理所需要的素材,计算机视觉需要以数字图像处理作为基础。计算机视觉与数字图像处理的这种关系类似于物理学和数学的关系。

       你可以多看看浙大周昆的文章,周昆是计算机图形学这个领域的领军人物另外,你如果不是浙江大学的或者中科院计算所的,不建议做这个方向,难度太大(图形比图像虽然表面上只高一维,但实际上工作量大了好多倍;其次,图像,国内外差距目前已经很小,好发重要期刊;图形,除上面两个单位和微软外,国内外差距很大,不好发重要期刊)

        数字图像处理主要是对已有的图像,比如说可见光的图像、红外图像、雷达成像进行噪声滤除、边缘检测、图像恢复等处理,就像用ps 处理照片一样的。人脸识别啊、指纹识别啊、运动物体跟踪啊,都属于图像处理。去噪有各种滤波算法;其他的有各种时频变化算法,如傅里叶变化,小波变换等,有很多这方面的书籍。
        图形学主要研究如何生成图形的,像用autoCAD作图,就是图形学中算法的应用。各种动漫软件中图形算法的生成等。



二:数学的三大核心领域数学的三大核心领域——代数学、几何学、分析学

   原文链接:http://blog.sciencenet.cn/blog-81613-682181.html

    数学发展到现在,已经成为科学世界中拥有100多个主要分支学科的庞大的共和国。大体说来,数学中研究数的部分属于代数学的范畴;研究形的部分,属于几何学的范筹;沟通形与数且涉及极限运算的部分,属于分析学的范围。这三大类数学构成了整个数学的本体与核心。在这一核心的周围,由于数学通过数与形这两个概念,与其它科学互相渗透,而出现了许多边缘学科和交叉学科。本章简要介绍数学三大核心领域中十几门主要分支学科的有关历史发展情况。

一、代数学范畴


1、算术
   
算术有两种含义,一种是从中国传下来的,相当于一般所说的数学,如《九章算术》等。另一种是从欧洲数学翻译过来的,源自希腊语,有计算技术之意。现在一般所说的算术,往往指自然数的四则运算;如果是在高等数学中,则有数论的含义。作为现代小学课程内容的算术,主要讲的是自然数、正分数以及它们的四则运算,并通过由计数和度量而引起的一些最简单的应用题加以巩固。
  算术是数学中最古老的一个分支,它的一些结论是在长达数千年的时间里,缓慢而逐渐地建立起来的。它们反映了在许多世纪中积累起来,并不断凝固在人们意识中的经验。
  自然数:是在对于对象的有限集合进行计算的过程中,产生的抽象概念。日常生活中要求人们不仅要计算单个的对象,还要计算各种量,例如长度、重量和时间。为了满足这些简单的量度需要,就要用到分数。
  现代初等算术运算方法的发展,起源于印度,时间可能在10世纪或11世纪。它后来被阿拉伯人采用,之后传到西欧。15世纪,它被改造成现在的形式。在印度算术的后面,明显地存在着我国古代的影响。
  19世纪中叶,格拉斯曼第一次成功地挑选出一个基本公理体系,来定义加法与乘法运算;而算术的其它命题,可以作为逻辑的结果,从这一体系中被推导出来。后来,皮亚诺进一步完善了格拉斯曼的体系。
  算术的基本概念和逻辑推论法则,以人类的实践活动为基础,深刻地反映了世界的客观规律性。尽管它是高度抽象的,但由于它概括的原始材料是如此广泛,因此我们几乎离不开它。同时,它又构成了数学其它分支的最坚实的基础。

2、初等代数
  作为中学数学课程主要内容的初等代数,其中心内容是方程理论。代数一词的拉丁文原意是归位。代数方程理论在初等代数中是由一元一次方程向两个方面扩展的:其一是增加未知数的个数,考察由有几个未知数的若干个方程所构成的二元或三元方程组(主要是一次方程组);其二是增高未知量的次数,考察一元二次方程或准二次方程。初等代数的主要内容在16世纪便已基本上发展完备了
  古巴比伦(公元前19世纪~前17世纪)解决了一次和二次方程问题,欧几里得的《原本》(公元前4世纪)中就有用几何形式解二次方程的方法。我国的《九章算术》(公元1世纪)中有三次方程和一次联立方程组的解法,并运用了负数。3世纪的丢番图用有理数求一次、二次不定方程的解。13世纪我国出现的天元术(李冶《测圆海镜》)是有关一元高次方程的数值解法。16世纪意大利数学家发现了三次和四次方程的解法。
  代数学符号发展的历史,可分为三个阶段。第一个阶段为三世纪之前,对问题的解不用缩写和符号,而是写成一篇论文,称为文字叙述代数。第二个阶段为三世纪至16世纪,对某些较常出现的量和运算采用了缩写的方法,称为简化代数。三世纪的丢番图的杰出贡献之一,就是把希腊代数学简化,开创了简化代数。然而此后文字叙述代数,在除了印度以外的世界其它地方,还十分普通地存在了好几百年,尤其在西欧一直到15世纪。第三个阶段为16世纪以后,对问题的解多半表现为由符号组成的数学速记,这些符号与所表现的内容没有什么明显的联系,称为符号代数。16世纪韦达的名著《分析方法入门》,对符号代数的发展有不少贡献。16世纪末,维叶特开创符号代数,经笛卡尔改进后成为现代的形式。
  号第一次在数学书中出现,是1489年魏德曼的著作。不过正式为大家所公认,作为加、减法运算的符号,那是从1514年由荷伊克开始的。1540年,雷科德开始使用现在使用。到1591年,韦达在著作中大量使用后,才逐渐为人们所接受。1600年哈里奥特创用大于号和小于号1631年,奥屈特给出“×”“÷”作为乘除运算符。1637年,笛卡尔第一次使用了根号,并引进用字母表中头前的字母表示已知数、后面的字母表示未知数的习惯做法。至于“≮”“≯”“≠”这三个符号的出现,那是近代的事了。
  数的概念的拓广,在历史上并不全是由解代数方程所引起的,但习惯上仍把它放在初等代数里,以求与这门课程的安排相一致。公元前4世纪,古希腊人发现无理数。公元前2世纪(西汉时期),我国开始应用负数。1545年,意大利的卡尔达诺开始使用虚数。1614年,英国的耐普尔发明对数。17世纪末,一般的实数指数概念才逐步形成。

3、高等代数-多项式方程
   在高等代数中,一次方程组(即线性方程组)发展成为线性代数理论;而、二次方程发展成为多项式理论。前者是向量空间、线性变换、型论、不变量论和张量代数等内容的一门近世代数分支学科,而后者是研究只含有一个未知量的任意次方程的一门近世代数分支学科。作为大学课程的高等代数,只研究它们的基础。
  1683年关孝和(日本人)最早引入行列式概念。关于行列式理论最系统的论述,则是雅可比1841年的《论行列式的形成与性质》一书。在逻辑上,矩阵的概念先于行列式的概念;而在历史上,次序正相反。凯雷在1855年引入了矩阵的概念,在1858年发表了关于这个课题的第一篇重要文章《矩阵论的研究报告》。
  19世纪,行列式和矩阵受到人们极大的关注,出现了千余篇关于这两个课题的文章。但是,它们在数学上并不是大的改革,而是速记的一种表达式。不过已经证明它们是高度有用的工具。
  多项式代数的研究始于对34次方程求根公式的探索。1515年,菲洛解决了被简化为缺2次项的3次方程的求解问题。1540年,费尔拉里成功地发现了一般4次方程的代数解法。人们继续寻求5次、6次或更高次方程的求根公式,但这些努力在200多年中付诸东流。
  1746年,达朗贝尔首先给出了代数学基本定理的证明(有不完善之处)。这个定理断言:每一个实系数或复系数的n次代数方程,至少有一个实根或复根。因此,一般地说,n次代数方程应当有n个根。1799年,22岁的高斯在写博士论文中,给出了这个定理的第一个严格的证明。1824年,22岁的阿贝尔证明了:高于4次的一般方程的全部系数组成的根式,不可能是它的根。1828年,年仅17岁的伽罗华创立了伽罗华理论,包含了方程能用根号解出的充分必要条件。

4、数论
  以正整数作为研究对象的数论,可以看作是算术的一部分,但它不是以运算的观点,而是以数的结构的观点,即一个数可用性质较简单的其它数来表达的观点来研究数的。因此可以说,数论是研究由整数按一定形式构成的数系的科学。
  早在公元前3世纪,欧几里得的《原本》讨论了整数的一些性质。他证明素数的个数是无穷的,他还给出了求两个数的公约数的辗转相除法。这与我国《九章算术》中的更相减损法是相同的。埃拉托色尼则给出了寻找不大于给定的自然数N的全部素数的筛法:在写出从1N的全部整数的纸草上,依次挖去2357……的倍数(各自的2倍,3倍,……)以及1,在这筛子般的纸草上留下的便全是素数了。
  当两个整数之差能被正整数m除尽时,便称这两个数对于”m同余。我国《孙子算经》(公元4世纪)中计算一次同余式组的求一术,有中国剩余定理之称。13世纪,秦九韶已建立了比较完整的同余式理论——“大衍求一术,这是数论研究的内容之一。
  丢番图的《算术》中给出了求x?y?z?所有整数解的方法。费尔马指出x^ny^nz^nn3时无整数解,对于该问题的研究产生了19世纪的数论。之后高斯的《数论研究》(1801)形成了系统的数论。
   数论的古典内容基本上不借助于其它数学分支的方法,称为初等数论。17世纪中叶以后,曾受数论影响而发展起来的代数、几何、分析、概率等数学分支,又反过来促进了数论的发展,出现了代数数论(研究整系数多项式的根—“代数数”)、几何数论(研究直线坐标系中坐标均为整数的全部整点”—“空间格网”)19世纪后半期出现了解析数论,用分析方法研究素数的分布。二十世纪出现了完备的数论理论。

5、抽象代数-近世代数
   1843年,哈密顿发明了一种乘法交换律不成立的代数——四元数代数。第二年,格拉斯曼推演出更有一般性的几类代数。1857年,凯雷设计出另一种不可交换的代数——矩阵代数。他们的研究打开了抽象代数(也叫近世代数)的大门。实际上,减弱或删去普通代数的某些假定,或将某些假定代之以别的假定(与其余假定是相容的),就能研究出许多种代数体系。
   1870年,克隆尼克给出了有限阿贝尔群的抽象定义;狄德金开始使用的说法,并研究了代数体;1893年,韦伯定义了抽象的体;1910年,施坦尼茨展开了体的一般抽象理论;狄德金和克隆尼克创立了环论;1910年,施坦尼茨总结了包括群、代数、域等在内的代数体系的研究,开创了抽象代数学。
   1926年,诺特完成了理想()理论;1930年,毕尔霍夫建立格论,它源于1847年的布尔代数;第二次世界大战后,出现了各种代数系统的理论和布尔巴基学派;1955年,嘉当、格洛辛狄克和爱伦伯克建立了同调代数理论。
   到现在为止,数学家们已经研究过200多种这样的代数结构,其中最主要德若当代数和李代数是不服从结合律的代数的例子。这些工作的绝大部分属于20世纪,它们使一般化和抽象化的思想在现代数学中得到了充分的反映。
   抽象代数是研究各种抽象的公理化代数系统的数学学科。典型的代数系统有群、环、域等,它们主要起源于19世纪的群论,包含有群论、环论、伽罗华理论、格论、线性代数等许多分支,并与数学其它分支相结合产生了代数几何、代数数论、代数拓扑、拓扑群等新的数学学科。抽象代数已经成了当代大部分数学的通用语言。(其中李群机器学习方法称为机器学习方法的一个重要分支)
  现在,可以笼统地把代数学解释为关于字母计算的学说,但字母的含义是在不断地拓广的。在初等代数中,字母表示数;而在高等代数和抽象代数中,字母则表示向量(n元有序数组)、矩阵、张量、旋量、超复数等各种形式的量。可以说,代数已经发展成为一门关于形式运算的一般学说了。


二、几何学范畴
1、初等几何-测量几何
  在希腊语中,几何学是由测量合并而来的,本来有测量土地的含义,意译就是测地术几何学这个名词,系我国明代数学家根据读音译出的,沿用至今。
  现在的初等几何主要是指欧几里得几何,它是讨论图形(点、线、面、角、圆等)在运动下的不变性质的科学。例如,欧氏几何中的两点之间的距离,两条直线相交的交角大小,半径是r的某一圆的面积等都是一些运动不变量。
  初等几何作为一门课程来讲,安排在初等代数之后;然而在历史上,几何学的发展曾优先于代数学,它主要被认为是古希腊人的贡献。
  几何学舍弃了物质所有的其它性质,只保留了空间形式和关系作为自己研究的对象,因此它是抽象的。这种抽象决定了几何的思维方法,就是必须用推理的方法,从一些结论导出另一些新结论。定理是用演绎的方式来证明的,这种论证几何学的代表作,便是公元前三世纪欧几里得的《原本》,它从定义与公理出发,演绎出各种几何定理。
  现在中学《平面三角》中关于三角函数的理论是15世纪才发展完善起来的,但是它的一些最基本的概念,却早在古代研究直角三角形时便己形成。因此,可把三角学划在初等几何这一标题下。
  古代埃及、巴比伦、中国、希腊都研究过有关球面三角的知识。公元前2世纪,希帕恰斯制作了弦表,可以说是三角的创始人。后来印度人制作了正弦表;阿拉伯的阿尔·巴塔尼用计算sinθ值的方法来解方程,他还与阿布尔·沃法共同导出了正切、余切、正割、余割的概念;赖蒂库斯作了较精确的正弦表,并把三角函数与圆弧联系起来。
  由于直角三角形是最简单的直线形,又具有很重要的实用价值,所以各文明古国都极重视它的研究。我国《周髀算经》一开始就记载了周朝初年(约公元前1100年左右)的周公与学者商高的对话,其中就谈到勾三股四弦五,即勾股定理的特殊形式;还记载了在周公之后的陈子,曾用勾股定理和相似图形的比例关系,推算过地球与太阳的距离和太阳的直径,同时为勾股定理作的图注达几十种之多。在国外,传统称勾股定理为毕达哥拉斯定理,认为它的第一个一致性的证明源于毕氏学派(公元前6世纪),虽然巴比伦人在此以前1000多年就发现了这个定理。到现在人们对勾股定理已经至少提供了370种证明。
  19世纪以来,人们对于关于三角形和圆的初等综合几何,又进行了深入的研究。至今这一研究领域仍然没有到头,不少资料已引申到四面体及伴随的点、线、面、球。

2、射影几何-变换几何
  射影几何学是一门讨论在把点射影到直线或平面上的时候,图形的不变性质的一门几何学。幻灯片上的点、线,经过幻灯机的照射投影,在银幕上的图画中都有相对应的点线,这样一组图形经过有限次透视以后,变成另一组图形,这在数学上就叫做射影对应。射影几何学在航空、摄影和测量等方面都有广泛的应用。
  射影几何是迪沙格和帕斯卡在1639年开辟的。迪沙格发表了本关于圆维曲线的很有独创性的小册子,从开普勒的连续性原理开始,导出了许多关于对合、调和变程、透射、极轴、极点以及透视的基本原理,这些课题是今天学习射影几何这门课程的人所熟悉的。年仅16岁的帕斯卡得出了一些新的、深奥的定理,并于9年后写了一份内容很丰富的手稿。18世纪后期,蒙日提出了二维平面上的适当投影表达三维对象的方法,因而从提供的数据能快速算出炮兵阵地的位置,避开了冗长的、麻烦的算术运算。
  射影几何真正独立的研究是由彭赛勒开创的。1822年,他发表了《论图形的射影性质》一文,给该领域的研究以巨大的推动作用。他的许多概念被斯坦纳进一步发展。1847年,斯陶特发表了《位置几何学》一书,使射影几何最终从测量基础中解脱出来。
  后来证明,采用度量适当的射影定义,能在射影几何的范围内研究度量几何学。将一个不变二次曲线添加到平面上的射影几何中,就能得到传统的非欧几何学。在19世纪晚期和20世纪初期,对射影几何学作了多种公设处理,并且有限射影几何也被发现。事实证明,逐渐地增添和改变公设,就能从射影几何过渡到欧几里得几何,其间经历了许多其它重要的几何学。
 
3解析几何-代数几何化
   解析几何即坐标几何,包括平面解析几何和立体解析几何两部分。解析几何通过平面直角坐标系和空间直角坐标系,建立点与实数对之间的一一对应关系,从而建立起曲线或曲面与方程之间的一一对应关系,因而就能用代数方法研究几何问题,或用几何方法研究代数问题。
   在初等数学中,几何与代数是彼此独立的两个分支;在方法上,它们也基本是互不相关的。解析几何的建立,不仅由于在内容上引入了变量的研究而开创了变量数学,而且在方法上也使几何方法与代数方法结合起来。
   在迪沙格和帕斯卡开辟了射影几何的同时,笛卡儿和费尔马开始构思现代解析几何的概念。这两项研究之间存在一个根本区别:前者是几何学的一个分支,后者是几何学的一种方法。
   1637年,笛卡儿发表了《方法论》及其三个附录,他对解析几何的贡献,就在第三个附录《几何学》中,他提出了几种由机械运动生成的新曲线。在《平面和立体轨迹导论》中,费尔马解析地定义了许多新的曲线。在很大程度上,笛卡儿从轨迹开始,然后求它的方程;费尔马则从方程出发,然后来研究轨迹。这正是解析几何基本原则的两个相反的方面,解析几何的名称是以后才定下来的。
   这门课程达到现在课本中熟悉的形式,是100多年以后的事。象今天这样使用坐标、横坐标、纵坐标这几个术语,是莱布尼兹于1692年提出的。1733年,年仅18岁的克雷洛出版了《关于双重曲率曲线的研究》一书,这是最早的一部空间解析几何著作。1748年,欧拉写的《无穷分析概要》,可以说是符合现代意义的第一部解析几何学教程。1788年,拉格朗日开始研究有向线段的理论。1844年,格拉斯曼提出了多维空间的概念,并引入向量的记号。于是多维解析几何出现了。
   解析几何在近代的发展,产生了无穷维解析几何和代数几何等一些分支。普通解析几何只不过是代数几何的一部分,而代数几何的发展同抽象代数有着密切的联系。

4非欧几何-广义空间几何
  非欧几何有三种不同的含义:狭义的,单指罗氏(罗巴切夫斯基)几何;广义的,泛指一切和欧氏(欧几里得)几何不同的几何;通常意义的,指罗氏几何和黎曼几何。
  欧几里得的第5公设(平行公设)在数学史上占有特殊的地位,它与前4条公设相比,性质显得太复杂了。它在《原本》中第一次应用是在证明第29个定理时,而且此后似乎总是尽量避免使用它。因此人们怀疑第五公设的公理地位,并探索用其它公理来证明它,以使它变为一条定理。在三千多年的时间中,进行这种探索并有案可查的就达两千人以上,其中包括许多知名的数学家,但他们都失败了。
  罗巴契夫斯基于1826年,鲍耶于1832年发表了划时代的研究结果,开创了非欧几何。在这种几何中,他们假设过不在已知直线上的一点,可以引至少两条直线平行于已知直线,用以代替第五公设,同时保留了欧氏几何的其它公设。
  1854年,黎曼推出了另一种非欧几何。在这种几何中,他假设过已知直线外一点,没有和已知直线平行的直线可引,用以代替第5公设,同时保留了欧氏几何的其它公设。1871年,克莱因把这3种几何:罗巴契夫斯基鲍耶的、欧几里得的和黎曼的分别定名为双曲几何、抛物几何和椭圆几何。
  非欧几何的发现不仅最终解决了平行公设的问题——平行公设被证明是独立于欧氏几何的其它公设的,而且把几何学从其传统模型中解放出来,创造了许多不同体系的几何的道路被打开了。
  1854年,黎曼发表了关于作为几何学基础的假设的讲演。他指出:每种不同的(两个无限靠近的点的)距离公式决定了最终产生的空间和几何的性质。1872年,克莱因建立了各种几何系统按照不同变换群不变量的分类方法。
  19世纪以后,几何空间概念发展的另一方向,是按照所研究流形的微分几何原则的分类,每一种几何都对应着一种定理系统。1899年,希尔伯特发表了《几何基础》一书,提出了完备的几何公理体系,建立了欧氏几何的严密的基础,并给出了证明一个公理体系的相容性(无矛盾性)、独立性和完备性的普遍原则。按照他的观点,不同的几何空间乃是从属于不同几何公理要求的元素集合。欧氏几何和非欧几何,在大量的几何系统中,只不过是极其特殊的情形罢了。

5拓扑学-代数拓扑和几何拓扑
  1736年,欧拉发表论文,讨论哥尼斯堡七桥问题。他还提出球面三角形剖分图形顶点、边、面之间关系的欧拉公式,这可以说是拓扑学的开端。
  庞加莱于18951904年建立了拓扑学,采用代数组合的方法研究拓扑性质。他把欧拉公式推广为欧拉庞加莱公式,与此有关的理论现在称为同调理论和同伦理论。以后的拓扑学主要按照庞加莱的设想发展。
  拓扑学开始是几何学的一个分支,在二十世纪它得到了极大的推广。1906年,弗雷歇发表博士论文,把函数作为一个来看,把函数收敛描绘成点的收敛,这就把康托的点集论和分析学的抽象化联系起来了。他在函数所构成的集合中引入距离的概念,构成距离空间,展开了线性距离空间的理论。在这个基础上,产生了点集拓扑学。在豪斯道夫的《点集论纲要》一书中,出现了更一般的点集拓扑学的完整想法。第二次世界大战后,把分析引进拓扑,发展了微分拓扑。
  现在的拓扑学可以粗略地定义为对于连续性的数学研究。任何事物的集合都能在某种意义上构成拓扑空间,拓扑学的概念和理论已基本完组成为数学的基础理论之一,渗入到各个分支,并且成功地应用于电磁学和物理学的研究。


三、分析学范畴


1、微积分
  微积分学是微分学和积分学的统称,它是研究函数的导数、积分的性质和应用的一门数学分支学科。
  微积分的出现具有划时代意义,时至今日,它不仅成了学习高等数学各个分支必不可少的基础,而且是学习近代任何一门自然科学和工程技术的必备工具。现在的微积分学的教程,通常的讲授次序是先极限、再微分、后积分,这与历史顺序正好相反。
  在微积分历史中,最初的问题是涉及计算面积、体积和弧长的。阿基米得(公元前3世纪)的方法最接近于现行的积分法。在17世纪探索微积分的至少有十几位大数学家和几十位小数学家。牛顿和莱布尼茨分别进行了创造性的工作,各自独立地跑完了微积分这场接力赛的最后一棒
  1609年,开普勒为了计算行星运动第二定律中包含的面积,和在他的论文中讨论的酒桶的体积,而借助了某种积分方法。1635年,卡瓦列利发表了一篇阐述不可分元法的论文,提出卡瓦列利原理,它是计算面积和体积的有价值的工具。1650年,沃利斯把卡瓦列利的方法系统化,并作了推广。
  微分起源于作曲线的切线和求函数的极大值或极小值问题。虽然可以追溯到古希腊,但是第一个真正值得注意的先驱工作,是费尔马1629年陈述的概念。1669年,巴罗对微分理论作出了重要的贡献,他用了微分三角形,很接近现代微分法。一般认为,他是充分地认识到微分法为积分法的逆运算的第一个人。

  至此,还有什么要做的呢?首要的是,创造一般的符号和一整套形式的解析规则,形成可以应用的微积分学,这项工作是由牛顿和莱布尼兹彼此独立地做出的。接着的工作是在可接受的严格的基础上,重新推导基本理论,这必须等到此课题想到多方面应用之后。柯西和他的后继者们完成了这一工作。
  牛顿早在1665年才23岁时,就创造了流数法(微分学),并发展到能求曲线上任意一点的切线和曲率半径。他的《流数法》写于1671年,但直到死后9年的1736年才发表。牛顿考虑了两种类型的问题,等价于现在的微分和解微分方程。他定义了流数(导数)、极大值、极小值、曲线的切线、曲率、拐点、凸性和凹性,并把它的理论应用于许多求积问题和曲线的求长问题。
  牛顿创立的微积分原理是同他的力学研究分不开的,他借此发现、并研究了力学三大定律和万有引力定律,1687年出版了名著《自然哲学的数学原理》。这本书是研究天体力学的,包括了微积分的一些基本概念和原理。
  莱布尼茨是在1673年到1676年之间,从几何学观点上独立发现微积分的。1676年,他第一次用长写字母表示积分符号,象今天这样写微分和微商。1684年~1686年,他发表了一系列微积分著作,力图找到普遍的方法来解决问题。今天课本中的许多微分的基本原则就是他推导出来的,如求两个函数乘积的n阶导数的法则,现在仍称作菜布尼兹法则。莱布尼兹的另一最大功绩是创造了反映事物本质的数字符号,数学分析中的基本概念的记号,例如微分dx,二级微分dx?,积分∫ydx,导数dy/dx等都是他提出来的,并且沿用至今,非常方便。
  牛顿与莱布尼茨的创造性工作有很大的不同。主要差别是牛顿把xy的无穷小增量作为求导数的手段,当增量越来越小的时候,导数实际上就是增量比的极限,而莱布尼兹却直接用xy的无穷小增量(就是微分)求出它们之间的关系。
  这个差别反映了他们研究方向的不同,在牛顿的物理学方向中,速度之类是中心概念;而在莱布尼兹的几何学方向中,却着眼于面积体积的计算。其它差别是,牛顿自由地用级数表示函数,采用经验的、具体和谨慎的工作方式,认为用什么记号无关紧要;而莱布尼兹则宁愿用有限的形式来表示函数,采用富于想象的、喜欢推广的、大胆的工作方式,花费很多时间来选择富有提示性的符号。
  到1700年,现在大学且学习的大部分微积分内容已经建立起来。第一部微积分课本出版于1696年,是洛比达写的。1769年,欧拉论述了二重积分。1773年,拉格朗日考察了三重积分。1837年,波尔查诺给出了级数的现代定义。19世纪分析学的严谨化,是由柯西奠基的。现在课本中的极限、连续性定义、把导数看作差商的极限、把定积分看做和的权限等等,实质上都是柯西给出的。进一步完成这一工作的是威尔斯特拉斯,他给出了现在使用的精确的极限定义,并同狄德金、康托于19世纪70年代建立了严格的实数理论,使微积分有了坚固可靠的逻辑基础。

2、微分方程
  凡是表示未知函数和未知函数的导数以及自变量之间的关系的方程,就叫做微分方程。如果未知函数是一元函数,则称为常微分方程,如果未知函数是多元函数,则称为偏微分方积。微分方程的基本问题是在一定条件下,从所给出的微分方程解出未知函数。
  微分方程几乎是与微积分同时发展起来的,由于它与力学、物理学的渊源很深,所以在13世纪便已自成一门独立的学科了。两个多世纪来,这一学科已发展得相当完善。
  1676年,莱布尼兹在致牛顿的信中,首先提出了微分方程这个名称。在他们两人的著作中,都包含了许多微分方程的实例。早期的研究侧重于探讨各类一阶方程的解法,并由此导致了方程的分类。18世纪,欧拉解决了全微分方程和欧拉方程”(一类高阶变系数线性微分方程),提出了通解和特解的概念,指出了n阶线性方程通解的结构。其后,泰勒得到了方程的奇解;拉格朗日推导了非齐次线性方程的常数交易法。
  对于微分方程组的研究,始于达朗贝尔。19世纪前半叶,柯西开始研究解的存在性和唯一性。19世纪后半叶,数学家们开始利用群论来研究微分方程,由此建立连续群和李群的新理论。庞加莱引入了极限环的概念,李雅普诺夫引入了微分方程组解的稳定性概念。他们的方法都不必直接求解,称为定性理论。1927年,毕尔霍夫建立了动力系统的一段定性理论。
  一阶偏微分方程的研究首先是从几何学问题开始的。拉格朗日指出,解一阶线性偏微分方程的技巧,在于把它们化为常微分方程。一阶非线性偏微分方程的研究,始于欧拉和拉格朗日,蒙日为偏微分方程的几何理论奠定了基础。到18世纪末叶,在引入奇解、通解、全积分、通积分、特积分等概念之后,偏微分方程已形成一门独立的学科。
  二阶偏微分方程的研究,始于18世纪的弦振动理论。通常见的二阶偏微分方程均来自物理或力学的实际问题,它们构成了这门学科中一个独立的系统数学物理方程。
  积分方程源于阿贝尔1826年的工作,但是直到1888年杜·波阿·雷蒙的著作中,才正式提出了积分方程这个名词。1896年开始,伏特拉给出了两类积分方程的一般理论;不久,弗雷德荷姆大体上完成了一类重要的线性积分方程理论。由于这类积分方程常出现在一些物理问题中,因此积分方程论常被包含在数学物理方程内。
  现代科学技术,如空间技术、现代物理学、力学等,都有许多问题需要用微分方程来求解,甚至在化学、生物学、医药学、经济学等方面,微分方程的应用也越来越多。

3、微分几何
  微分几何这门分支学科主要研究三维欧氏空间中曲线和曲面的内在性质,所谓内在性质就是同几何对象在空间中的位置无关的性质。它以微积分、微分方程这些分支学科的理论为研究工具。或简单地说,微分几何就是用分析方法研究几何性质。
  微分几何的发端可见于1731年克莱洛的著作中。蒙日1809年的著作包含了这一学科的雏型;欧拉研究了曲面的一般理论;高斯1827年的《关于曲面的一般研究》一书,论述了曲面理论,创立了内蕴几何学,奠定了曲面微分几何的基础。18871896年,达布的《曲面一般理论的讲义》集曲线和曲面微分几何之大成。
  变换理论对于微分几何的影响,产生了射影微分几何、仿射微分几何等分支。二十世纪初,出现了对非充分光滑曲线和曲面以及曲线曲面的整体问题的研究,形成现代微分几何。1923年,嘉当提出了一般联络的理论。1945年,陈省身建立了代数拓扑和微分几何的联系,他又是纤维丛概念的创建人之一。

4、函数论
  函数论包括复变函数论和实变函数论,但有时也单指复变函数论(或复分析)而言。
  复数概念出现于16世纪,但对它的全面掌握和广泛运用,却迟至18世纪。自变量是复数的函数,叫做复变函数。如果复变函数在某一区域内除了可能有有限个例外点之外,处处有导数,那么这个伏辩函数叫做在这个区域内的解析函数;例外点叫做奇点。复变函数论主要研究解析函数的性质。
  复变函数的研究是从18世纪开始的。3040年代,欧拉利用幂级数详细讨论了初等复变函数的性质。达朗贝尔于1752年得出复变函数可微的必要条件(柯西黎曼条件”)。拉普拉斯也考虑过复变函数的积分。
  复变函数的全面发展是在19世纪。1825年,柯西讨论了虚限定积分,1831年他实质上推出了柯西积分公式,并在此基础上建立了一整套复变函数微分和积分的理论。黎曼1851年的博士论文《复变函数论的基础》,奠定了复变函数论的基础。他推广了单位解析函数到多位解析函数;引入了黎曼曲面的重要概念,确立了复变因数的几何理论基础;证明了保角映射基本定理。威尔斯特拉斯完全摆脱了几何直观,以幂级数为工具,用严密的纯解析推理展开了函数论。定义解析函数是可以展开为幂级数的函数,围绕着奇点研究函数的性质。近几十年来,复变函数论又有很大的推进。
  复变函数论是解决工程技术问题的有力工具,飞机飞行理论、热运动理论、流体力学理论、电场和弹性理论等中的很多问题。
  实变函数的发展较晚,其中积分论是它的重要组成部分。容度和测度是线段长度概念的推广,是为了推广积分的概念而建立起来的。1893年,约当给出了约当容度的概念,并用于讨论积分。1894年,斯提捷首先推广了积分概念,得到了斯提捷积分1898年,波莱尔改进了容度的概念,他称之为测度。下一步决定性的进展是1902年勒贝格改进了测度理论,建立了勒贝格测度勒贝格积分等概念。1904年,他完全解决了黎曼可积性的问题。后来,数学家们对积分的概念又作了种种推广和探索。
  实变函数的另一个领域是函数构造论。1885年,威尔斯特拉斯证明:连续函数必可表示为一致收敛的多项式级数。这一结果和切比雪夫斯基最佳逼近论,是函数构造论的开端。近年来,这个方向的研究十分活跃。

5、泛函分析
  本世纪初,出现了一个广阔的新领域——泛函分析,它是古典分析观点的推广。近几十年来,由于分析学中许多新分支的形成,从而发现在代数、几何、分析中不同领域之间的某些方面的类似。其次,几何与集合论的结合产生了抽象空间的理论,将函数看成函数空间中的点。再加上实变函数论以及近世代数的感念和方法的影响,就产生了泛画分析。它综合函数论,几何和代数的观点,研究无穷维向量空间上的函数、算子和极限理论。
  19世纪末,弗尔太拉和二十世纪初阿达玛的著作中已出现泛函分析的萌芽。随后希尔伯特、海令哲开创了希尔伯将空间的研究,黎斯、冯·诺伊曼等人在这方面都有重要的建树。


  未完待续..............................




三:OpenGL图像学基础

一、OpenGL与3D图形世界

          OpenGL是基于图元建模的典范,粒子建模计算太复杂以至于现在还仅仅是停留在学术领域,作为未来的方向继续发展.

1.1、OpenGL使人们进入三维图形世界
    我们生活在一个充满三维物体的三维世界中,为了使计算机能精确地再现这些物体,我们必须能在三维空间描绘这些物体。我们又生活在一个充满信息的世界中,能否尽快地理解并运用这些信息将直接影响事业的成败,所以我们需要用一种最直接的形式来表示这些信息。
    最近几年计算机图形学的发展使得三维表现技术得以形成,这些三维表现技术使我们能够再现三维世界中的物体,能够用三维形体来表示复杂的信息,这种技术就是可视化(Visualization)技术。可视化技术使人能够在三维图形世界中直接对具有形体的信息进行操作,和计算机直接交流。这种技术已经把人和机器的力量以一种直觉而自然的方式加以统一,这种革命性的变化无疑将极大地提高人们的工作效率。可视化技术赋予人们一种仿真的、三维的并且具有实时交互的能力,这样人们可以在三维图形世界中用以前不可想象的手段来获取信息或发挥自己创造性的思维。机械工程师可以从二维平面图中得以解放直接进入三维世界,从而很快得到自己设计的三维机械零件模型。医生可以从病人的三维扫描图象分析病人的病灶。军事指挥员可以面对用三维图形技术生成的战场地形,指挥具有真实感的三维飞机、军舰、坦克向目标开进并分析战斗方案的效果。
   更令人惊奇的是目前正在发展的虚拟现实技术,它能使人们进入一个三维的、多媒体的虚拟世界,人们可以游历远古时代的城堡,也可以遨游浩翰的太空。所有这些都依赖于计算机图形学、计算机可视化技术的发展。人们对计算机可视化技术的研究已经历了一个很长的历程,而且形成了许多可视化工具,其中SGI公司推出的GL三维图形库表现突出,易于使用而且功能强大。利用GL开发出来的三维应用软件颇受许多专业技术人员的喜爱,这些三维应用软件已涉及建筑、产品设计、医学、地球科学、流体力学等领域。随着计算机技术的继续发展,GL已经进一步发展成为OpenGL,OpenGL已被认为是高性能图形和交互式视景处理的标准,目前包括ATT公司UNIX软件实验室、IBM公司、DEC公司、SUN公司、HP公司、Microsoft公司和 SGI公司在内的几家在计算机市场占领导地位的大公司都采用了OpenGL图形标准。
   值得一提的是,由于Microsoft公司在 Windows NT中提供OpenGL图形标准,OpenGL将在微机中广泛应用,尤其是OpenGL三维图形加速卡和微机图形工作站的推出,人们可以在微机上实现三维图形应用,如CAD设计、仿真模拟、三维游戏等,从而更有机会、更方便地使用OpenGL及其应用软件来建立自己的三维图形世界。
1.2、OpenGL提供直观的三维图形开发环境
   OpenGL实际上是一种图形与硬件的接口。它包括了120个图形函数,开发者可以用这些函数来建立三维模型和进行三维实时交互。与其他图形程序设计接口不同,OpenGL提供了十分清晰明了的图形函数,因此初学的程序设计员也能利用OpenGL的图形处理能力和1670万种色彩的调色板很快地设计出三维图形以及三维交互软件。
   OpenGL强有力的图形函数不要求开发者把三维物体模型的数据写成固定的数据格式,这样开发者不但可以直接使用自己的数据,而且可以利用其他不同格式的数据源。这种灵活性极大地节省了开发者的时间,提高了软件开发效益。
   长期以来,从事三维图形开发的技术人员都不得不在自己的程序中编写矩阵变换、外部设备访问等函数,这样为调制这些与自己的软件开发目标关系并不十分密切的函数费脑筋,而OpenGL正是提供一种直观的编程环境,它提供的一系列函数大大地简化了三维图形程序。例如:

  •  OpenGL提供一系列的三维图形单元供开发者调用。
  • OpenGL提供一系列的图形变换函数。
  • OpenGL提供一系列的外部设备访问函数,使开发者可以方便地访问鼠标、键盘、空间球、数据手套等这种直观的三维图形开发环境体现了OpenGL的技术优势,这也是许多三维图形开发者热衷于OpenGL的缘由所在。
1.3、OpenGL成为目前三维图形开发标准
  OpenGL成为目前三维图形开发标准在计算机发展初期,人们就开始从事计算机图形的开发。直到计算机硬软件和计算机图形学高度发达的九十年代,人们发现复杂的数据以视觉的形式表现时是最易理解的,因而三维图形得以迅猛发展,于是各种三维图形工具软件包相继推出,如PHIGS、PEX、 RenderMan等。这些三维图形工具软件包有些侧重于使用方便,有些侧重于渲染效果或与应用软件的连接,但没有一种三维工具软件包在交互式三维图形建模能力、外部设备管理以及编程方便程度上能够OpenGL相比拟。
  OpenGL经过对GL的进一步发展,实现二维和三维的高级图形技术,在性能上表现得异常优越,它包括建模、变换、光线处理、色彩处理、动画以及更先进的能力,如纹理影射、物体运动模糊等。OpenGL的这些能力为实现逼真的三维渲染效果、建立交互的三维景观提供了优秀的软件工具。OpenGL在硬件、窗口、操作系统方面是相互独立的。
  许多计算机公司已经把 OpenGL集成到各种窗口和操作系统中,其中操作系统包括UNIX、Windows NT、DOS等,窗口系统有X窗口、Windows等。为了实现一个完整功能的图形处理系统,设计一个与OpenGL相关的系统结构为:其最底层是图形硬件,第二层为操作系统,第三层为窗口系统,第四层为OpenGL,第五层为应用软件。OpenGL是网络透明的,在客户 — 服务器(Client-Server)体系结构中,OpenGL允许本地和远程绘图。所以在网络系统中,OpenGL在X窗口、Windows或其它窗口系统下都可以以一个独立的图形窗口出现。
  OpenGL作为一个性能优越的图形应用程序设计界面(API)而适合于广泛的计算环境,从个人计算机到工作站和超级计算机,OpenGL都能实现高性能的三维图形功能。由于许多在计算机界具有领导地位的计算机公司纷纷采用OpenGL作为三维图形应用程序设计界面,OpenGL应用程序具有广泛的移植性。因此,OpenGL已成为目前的三维图形开发标准,是从事三维图形开发工作的技术人员所必须掌握的开发工具。

二、OpenGL概念建立

<>function StorePage(){
d=document;
t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus(); 
2.1、OpenGL基本理解
  OpenGL是一个与硬件图形发生器的软件接口,它包括了100多个图形操作函数,开发者可以利用这些函数来构造景物模型、进行三维图形交互软件的开发。正如上一章所述,OpenGL是一个高性能的图形开发软件包。OpenGL支持网络,在网络系统中用户可以在不同的图形终端上运行程序显示图形。 OpenGL作为一个与硬件独立的图形接口,它不提供与硬件密切相关的设备操作函数,同时,它也不提供描述类似于飞机、汽车、分子形状等复杂形体的图形操作函数。用户必须从点、线、面等最基本的图形单元开始构造自己的三维模型。当然,象OpenInventor那样更高一级的基于OpenGL的三维图形建模开发软件包将提供方便的工具。因此OpenGL的图形操作函数十分基本、灵活。例如OpenGL中的模型绘制过程就多种多样,内容十分丰富,OpenGL提供了以下的对三维物体的绘制方式:
  • 网格线绘图方式wireframe
    这种方式仅绘制三维物体的网格轮廓线。
  • 深度优先网格线绘图方式depth_cued
    用网格线方式绘图,增加模拟人眼看物体一样,远处的物体比近处的物体要暗些。
  • 反走样网格线绘图方式antialiased
    用网格线方式绘图,绘图时采用反走样技术以减少图形线条的参差不齐。
  • 平面消隐绘图方式flat_shade
    对模型的隐藏面进行消隐,对模型的平面单元按光照程度进行着色但不进行光滑处理。
  • 光滑消隐绘图方式smooth_shade
    对模型进行消隐按光照渲染着色的过程中再进行光滑处理,这种方式更接近于现实。
  • 加阴影和纹理的绘图方式shadows、textures
    在模型表面贴上纹理甚至于加上光照阴影,使得三维景观象照片一样。
  • 运动模糊的绘图方式motion-blured
    模拟物体运动时人眼观察所感觉的动感现象。
  • 大气环境效果atmosphere-effects
    在三维景观中加入如雾等大气环境效果,使人身临其境。
  • 深度域效果depth-of-effects
    类似于照相机镜头效果,模型在聚焦点处清晰,反之则模糊。
  这些三维物体绘图和特殊效果处理方式,说明OpenGL已经能够模拟比较复杂的三维物体或自然景观,这就是我们所面对的OpenGL。

2.2、OpenGL工作流程
  整个OpenGL的基本工作流程如下图:
        
  其中几何顶点数据包括模型的顶点集、线集、多边形集,这些数据经过流程图的上部,包括运算器、逐个顶点操作等;图像数据包括象素集、影像集、位图集等,图像象素数据的处理方式与几何顶点数据的处理方式是不同的,但它们都经过光栅化、逐个片元(Fragment)处理直至把最后的光栅数据写入帧缓冲器。在OpenGL中的所有数据包括几何顶点数据和象素数据都可以被存储在显示列表中或者立即可以得到处理。OpenGL中,显示列表技术是一项重要的技术。
  OpenGL要求把所有的几何图形单元都用顶点来描述,这样运算器和逐个顶点计算操作都可以针对每个顶点进行计算和操作,然后进行光栅化形成图形碎片;对于象素数据,象素操作结果被存储在纹理组装用的内存中,再象几何顶点操作一样光栅化形成图形片元。
  整个流程操作的最后,图形片元都要进行一系列的逐个片元操作,这样最后的象素值BZ送入帧缓冲器实现图形的显示。
2.3、OpenGL图形操作步骤
  在上一节中说明了OpenGL的基本工作流程,根据这个流程可以归纳出在OpenGL中进行主要的图形操作直至在计算机屏幕上渲染绘制出三维图形景观的基本步骤:
  1)根据基本图形单元建立景物模型,并且对所建立的模型进行数学描述(OpenGL中把:点、线、多边形、图像和位图都作为基本图形单元)。
  2)把景物模型放在三维空间中的合适的位置,并且设置视点(viewpoint)以观察所感兴趣的景观。
  3)计算模型中所有物体的色彩,其中的色彩根据应用要求来确定,同时确定光照条件、纹理粘贴方式等。
  4)把景物模型的数学描述及其色彩信息转换至计算机屏幕上的象素,这个过程也就是光栅化(rasterization)。
  在这些步骤的执行过程中,OpenGL可能执行其他的一些操作,例如自动消隐处理等。另外,景物光栅化之后被送入帧缓冲器之前还可以根据需要对象素数据进行操作。

三、WindowsNT下的OpenGL
3.1、Windows NT下的OpenGL函数
  如前面的章节所述,Windows NT下的OpenGL同样包含100多个库函数,这些函数都按一定的格式来命名,即每个函数都以gl开头。Windows NT下的OpenGL除了具有基本的OpenGL函数外,还支持其他四类函数:
相应函数具体说明
OpenGL实用库43个函数,每个函数以glu开头。
OpenGL辅助库31个函数,每个函数以aux开头。
Windows专用库函数(WGL6个函数,每个函数以wgl开头。
Win32 API函数5个函数,函数前面没有专用前缀。

  在OpenGL中有115个核心函数,这些函数是最基本的,它们可以在任何OpenGL的工作平台上应用。这些函数用于建立各种各样的形体,产生光照效果,进行反走样以及进行纹理映射,进行投影变换等等。由于这些核心函数有许多种形式并能够接受不同类型的参数,实际上这些函数可以派生出300 多个函数。
  OpenGL的实用函数是比OpenGL核心函数更高一层的函数,这些函数是通过调用核心函数来起作用的。这些函数提供了十分简单的用法,从而减轻了开发者的编程负担。OpenGL的实用函数包括纹理映射、坐标变换、多边形分化、绘制一些如椭球、圆柱、茶壶等简单多边形实体(本指南将详细讲述这些函数的具体用法)等。这部分函数象核心函数一样在任何OpenGL平台都可以应用。
  OpenGL的辅助库是一些特殊的函数,这些函数本来是用于初学者做简单的练习之用,因此这些函数不能在所有的OpenGL平台上使用,在Windows NT环境下可以使用这些函数。这些函数使用简单,它们可以用于窗口管理、输入输出处理以及绘制一些简单的三维形体。为了使OpenGL的应用程序具有良好的移植性,在使用OpenGL辅助库的时候应谨慎。
  6个WGL函数是用于连接OpenGL与Windows NT的,这些函数用于在Windows NT环境下的OpenGL窗口能够进行渲染着色,在窗口内绘制位图字体以及把文本放在窗口的某一位置等。这些函数把Windows与OpenGL揉合在一起。最后的5个Win32函数用于处理象素存储格式和双缓冲区,显然这些函数仅仅能够用于Win32系统而不能用于其它OpenGL平台。

3.2、OpenGL基本功能
  OpenGL能够对整个三维模型进行渲染着色,从而绘制出与客观世界十分类似的三维景象。另外OpenGL还可以进行三维交互、动作模拟等。具体的功能主要有以下这些内容。
  • 模型绘制
    OpenGL能够绘制点、线和多边形。应用这些基本的形体,我们可以构造出几乎所有的三维模型。OpenGL通常用模型的多边形的顶点来描述三维模型。如何通过多边形及其顶点来描述三维模型,在指南的在后续章节会有详细的介绍。
  • 模型观察
    在建立了三维景物模型后,就需要用OpenGL描述如何观察所建立的三维模型。观察三维模型是通过一系列的坐标变换进行的。模型的坐标变换在使观察者能够在视点位置观察与视点相适应的三维模型景观。在整个三维模型的观察过程中,投影变换的类型决定观察三维模型的观察方式,不同的投影变换得到的三维模型的景象也是不同的。最后的视窗变换则对模型的景象进行裁剪缩放,即决定整个三维模型在屏幕上的图象。

  • 颜色模式的指定
    OpenGL 应用了一些专门的函数来指定三维模型的颜色。程序员可以选择二个颜色模式,即RGBA模式和颜色表模式。在RGBA模式中,颜色直接由RGB值来指定;在颜色表模式中,颜色值则由颜色表中的一个颜色索引值来指定。程序员还可以选择平面着色和光滑着色二种着色方式对整个三维景观进行着色。
  • 光照应用
    用OpenGL绘制的三维模型必须加上光照才能更加与客观物体相似。OpenGL提供了管理四种光(辐射光、环境光、镜面光和漫反射光)的方法,另外还可以指定模型表面的反射特性。
  • 图象效果增强
    OpenGL提供了一系列的增强三维景观的图象效果的函数,这些函数通过反走样、混合和雾化来增强图象的效果。反走样用于改善图象中线段图形的锯齿而更平滑,混合用于处理模型的半透明效果,雾使得影像从视点到远处逐渐褪色,更接近于真实。
  • 位图和图象处理
    OpenGL还提供了专门对位图和图象进行操作的函数。
  • 纹理映射
    三维景物因缺少景物的具体细节而显得不够真实,为了更加逼真地表现三维景物,OpenGL提供了纹理映射的功能。OpenGL提供的一系列纹理映射函数使得开发者可以十分方便地把真实图象贴到景物的多边形上,从而可以在视窗内绘制逼真的三维景观。
  • 实时动画
    为了获得平滑的动画效果,需要先在内存中生成下一幅图象,然后把已经生成的图象从内存拷贝到屏幕上,这就是OpenGL的双缓存技术(double buffer)。OpenGL提供了双缓存技术的一系列函数。
  • 交互技术
    目前有许多图形应用需要人机交互,OpenGL提供了方便的三维图形人机交互接口,用户可以选择修改三维景观中的物体。
3.3、Windows NT下OpenGL的结构
  OpenGL的作用机制是客户(client)/服务器(sever)机制,即客户(用OpenGL绘制景物的应用程序)向服务器(即OpenGL内核)发布OpenGL命令,服务器则解释这些命令。大多数情况下,客户和服务器在同一机器上运行。正是OpenGL的这种客户/服务器机制,OpenGL可以十分方便地在网络环境下使用。因此Windows NT下的OpenGL是网络透明的。正象Windows的图形设备接口(GDI)把图形函数库封装在一个动态链接库(Windows NT下的GDI32.DLL)内一样,OpenGL图形库也被封装在一个动态链接库内(OPENGL32.DLL)。受客户应用程序调用的OpenGL函数都先在OPENGL32.DLL中处理,然后传给服务器WINSRV.DLL。OpenGL的命令再次得到处理并且直接传给Win32的设备驱动接口(Device Drive Interface,DDI),这样就把经过处理的图形命令送给视频显示驱动程序。下图简要说明这个过程:
       
图3-1 OpenGL在Windows NT下运行机制

  在三维图形加速卡的GLINT图形加速芯片的加速支持下,二个附加的驱动程序被加入这个过程中。一个OpenGL可安装客户驱动程序(Installable Client Driver,ICD)被加在客户这一边,一个硬件指定DDI(Hardware-specific DDI)被加在服务器这边,这个驱动程序与Wind32 DDI是同一级别的。

       
       图3-2 在三维图形加速下OpenGL运行机制

四、OpenGL基础程序结构
  用OpenGL编写的程序结构类似于用其他语言编写的程序。实际上,OpenGL是一个丰富的三维图形函数库,编写OpenGL程序并非难事,只需在基本C语言中调用这些函数,用法同Turbo C、Microsoft C等类似,但也有许多不同之处。
  本指南所有的程序都是在Windows NT的Microsoft Visual C++集成环境下编译连接的,其中有部分头文件和函数是为这个环境所用的,例如判别操作系统的头文件“glos.h”。此外,为便于各类读者同时快速入门,在短时间内掌握OpenGL编程的基本方法和技巧,指南中例子尽量采用标准ANSI C调用OpenGL函数来编写,而且所有例程都只采用OpenGL附带的辅助库中的窗口系统。此外,这样也便于程序在各平台间移植,尤其往工作站UNIX 操作系统移植时,也只需改动头文件等很少很少的部分。下面列出一个简单的OpenGL程序:

  例4-1 OpenGL简单例程(Simple.c
[cpp]  view plain  copy
  1. <span style="font-size:12px;"> #include <GL/gl.h>  
  2.   #include <GL/glaux.h>  
  3.   #include "glos.h"  
  4.   
  5.   void main(void)  
  6.   {  
  7.     auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);  
  8.     auxInitPosition(0,0,500,500);  
  9.     auxInitWindow("simple");  
  10.   
  11.     glClearColor(0.0,0.0,0.0,0.0);  
  12.     glClear(GL_COLOR_BUFFER_BIT);  
  13.   
  14.      glColor3f(1.0,0.0,0.0);  
  15.     glRectf(-0.5,-0.5,0.5,0.5);  
  16.   
  17.      glFlush();  
  18.     _sleep(1000);  
  19.   }</span>  

  这个程序运行结果是在屏幕窗口内画一个红色的方块。
  下面具体分析整个程序结构:首先,在程序最开始处是OpenGL头文件:<GL/gl.h>、<GL/glaux.h>。前一个是gl库的头文件,后一个是辅助库的头文件。此外,在以后的几章中还将说明OpenGL的另外两个头文件,一个是<GL/glu.h>实用库的头文件,另一个是<GL/glx.h>X窗口扩充库的头文件(这个常用在工作站上)。接下来是主函数main()的定义:一般的程序结构是先定义一个窗口:
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);  
  2.   auxInitPosition(0,0,500,500);  
  3.   auxInitWindow("simple");</span>  
  auxInitDisplayMode(AUX_SINGLE|AUX_RGBA)设置窗口显示模式为RGBA方式,即彩色方式,并且图形缓存为单缓存(SINGLE BUFFER)。 auxInitPosition(0, 0, 500, 500)定义窗口的初始位置,前两个参数(0, 0)为窗口的左上角点的屏幕坐标,后两个参数(500,500)为窗口的宽度和高度。auxInitWindow("simple")是窗口初始化,字符参数是窗口名称。
  然后是窗口内清屏:
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  glClearColor(0.0,0.0,0.0,0.0);  
  2.     glClear(GL_COLOR_BUFFER_BIT);</span>  
  第一句将窗口清为黑色,第二句将颜色缓冲区清为glClearColor(0.0, 0.0, 0.0, 0.0)命令所设置的颜色,即同窗口背景颜色一致。
  再接着是在窗口内画一个物体:
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  glColor3f(1.0,0.0,0.0);  
  2.   glRectf(-0.5,-0.5,0.5,0.5);</span>  
  很明显,第一句设置物体颜色,函数中前三个参数分别为R、G、B值,最后一个参数是Alpha值,范围都从0至1;第二句绘制一个二维矩形。注意:OpenGL是针对三维图形而言,因此用作OpenGL编程绘制物体必须意识到任何一个物体都是三维的,具有空间性,而显示于屏幕上的物体都是三维物体在二维平面上的投影。
  从表面上看,上述程序代码很简单,实际上已经用到了缺省的投影形式(正射投影)。再看glFlush()函数,表示强制绘图完成。最后一句_sleep(1000),参数单位为毫秒,整句意思是保持现有状况一秒钟,然后结束程序运行。这个函数是VC++的库函数。
  总而言之,OpenGL程序基本结构为定义窗口、清理窗口、绘制物体、结束运行。 

五、OpenGL的数据类型和函数名
OpenGL的数据类型定义可以与其它语言一致,但建议在ANSI C下最好使用以下定义的数据类型,例如GLint、GLfloat等。具体类型见表5-1。
前缀    数据类型                    相应C语言类型        OpenGL类型 
================================================================
b       8-bit integer            signed char         GLbyte 
s       16-bit integer           short               GLshort 
i       32-bit integer           long                        GLint,GLsizei 
f       32-bit floating-point    float                       GLfloat,GLclampf 
d       64-bit floating-point    double                      GLdouble,GLclampd 
ub      8-bit unsigned integer   unsigned char               GLubyte,GLboolean 
us      16-bit unsigned integer  unsigned short      GLushort 
ui      32-bit unsigned integer  unsigned long       GLuint,GLenum,GLbitfield 
表5-1 命令前缀和参数数据类型

  OpenGL的库函数命名方式很有规律,了解这种规律后阅读和编写程序都比较容易方便。
  首先,每个库函数有前缀gl、glu、glx或aux,表示此函数分属于基本库、实用库、X窗口扩充库或辅助库,其后的函数名头字母大写,后缀是参数类型的简写,取i、f,参见表5-1。例:
  
glVertex2i(2,4);
  glVertex3f(2.0,4.0,5.0);
注意:有的函数参数类型后缀前带有数字2、3、4。2代表二维,3代表三维,4代表alpha值(以后介绍)。
  有些OpenGL函数最后带一个字母v,表示函数参数可用一个指针指向一个向量(或数组)来替代一系列单个参数值。下面两种格式都表示设置当前颜色为红色,二者等价。
   glColor3f(1.0,0.0,0.0);
  float color_array[]={1.0,0.0,0.0};
  glColor3fv(color_array);

  除了以上基本命名方式外,还有一种带“*”星号的表示方法,例如glColor*(),它表示可以用函数的各种方式来设置当前颜色。同理,glVertex*v()表示用一个指针指向所有类型的向量来定义一系列顶点坐标值。
  最后,OpenGL也定义GLvoid类型,如果用C语言编写,可以用它替代void类型。

六、OpenGL辅组库的基本使用
  OpenGL是一个开放的系统,它是独立于任何窗口系统或操作系统的。尽管它包含了许多图形函数,但它却没有窗口函数,也没有从键盘和鼠标读取事件的函数,所以要初学者写出一个完整的图形程序是相当困难的。另外,OpenGL图形函数中只提供基本的几何原形:点、线、多边形,因此要创建基本的三维几何体如球、锥体等,也很不容易。而OpenGL辅助库就是为解决这些基本问题专门设计的,它提供了一些基本的窗口管理函数和三维图形绘制函数,能帮助初学者尽快进入OpenGL世界,掌握关键的三维图形技术,体会其中奇妙的乐趣。但是,对于复杂的应用,这些函数远远不够,只能作为参考。
       6.1、辅助库函数分类
  这一节内容可以作为手册查阅,初学者不必深究。
  辅助库函数大致分为六类:

  6.1.1 窗口初始化和退出
  相关函数有三个,它们在第一章已提到,这里将详细介绍:

  void auxInitWindow(GLbyte *titleString)

  打开一个由auxInitDisplayMode()和auxInitPosition()指定的窗口。函数参数是窗口标题,窗口背景缺省颜色是RGBA下的黑色或颜色表(color_index)下的0号调色板的颜色。按下Escape键可以完成关掉窗口、结束程序、全部清屏三项功能。

  void auxInitDisplayMode(GLbitfield mask)
  设置窗口显示模式。基本模式有RGBA或颜色表、单或双缓存,也可指定其他附加模式:深度、模板或累积缓存(depth,stencil,and/or accumulation buffer)。参数mask是一组位标志的联合(取或),AUX_RGBA或AUX_INDEX、AUX_SINGLE或AUX_DOUBLE,以及其它有效标志AUX_DEPTH、AUX_STENCIL或AUX_ACCUM。

  void auxInitPosition(GLint x,GLint y,GLsizei width,GLsizei height)
  设置窗口位置及大小。参数(x, y)为窗口的左上角点的屏幕坐标,参数(width, height)为窗口的宽度和高度,单位为象素,缺省值为(0, 0, 100, 100)。

  6.1.2 窗口处理和事件输入
  当窗口创建后,且在进入主函数循环之前,应当登记以下列出的回调函数(callback function):

  void auxReshapeFunc(void(*function)(GLsizei,GLsizei))
  定义窗口改变时形状重定函数。参数function是一个函数指针,这个函数带有两个参数,即窗口改变后的新宽度和新高度。通常,function是 glViewport(),显示裁减后的新尺寸,重定义投影矩阵,以便使投影后图像的比例与视点匹配,避免比例失调。若不调用 auxReshapeFunc(),缺省重定物体形状的函数功能是调用一个二维的正射投影矩阵。运用辅助库,窗口将在每个事件改变后自动重新绘制。

  void auxKeyFunction(GLint key,void(*function)(void))
  定义键盘响应函数。参数function就是当按下key键时所调用的函数指针,辅助库为参数key定义了几个常量:AUX_0至AUX_9、 AUX_A至AUX_Z、AUX_a至AUX_z、AUX_LEFT、AUX_RIGHT、AUX_UP、AUX_DOWN(方向键)、 AUX_ESCAPE、AUX_SPACE或AUX_RETURN。

  void auxMouseFunc(GLint button,Glint mode,void(*function)(AUX_EVENTREC *))
  定义鼠标响应函数。参数function就是当鼠标以mode方式作用于button时所调用的函数。参数button有 AUX_LEFTBUTTON、AUX_MIDDLEBUTTON或AUX_RIGHTBUTTON(以右手为标准)。参数mode代表鼠标触击状态,击中时为AUX_MOUSEDOWN,释放时为AUX_MOUSEUP。参数function必须带一个参数,它是指向结构AUX_EVENNTREC的指针。当函数auxMouseFunc()被调用时将为这个结构分配相应的内存。通常用法类似如下:
[cpp]  view plain  copy
  1. void function(AUX_EVENTREC *event)  
  2. {  
  3.   GLint x,y;  
  4.   x=event->data[AUX_MOUSEX];  
  5.   y=event->data[AUX_MOUSEY];  
  6.   ...  
  7. }  
  6.1.3 颜色表装入
  因为OpenGL本身没有窗口系统,所以依赖于窗口系统的颜色映射就没法装入颜色查找表。如果采用颜色表模式,就要用到辅助库提供的用RGB值定义的单个颜色索引函数:
   void auxSetOneColor(GLint index,GLfloat red,GLfloat green,GLfloat blue)
  设置自定义颜色的索引。参数index即索引号,参数red、green、blue分别为红、绿、蓝值,范围在(0~1)内。
  6.1.4 三维物体绘制
  每组三维物体包括两种形式:网状体( wire)和实心体( solid)。网状体没有平面法向,而实心体有,能进行光影计算,有光照时采用实心体模型。下面这些函数的 参数都是定义物体大小的,可以改变。

功能函数
绘制球void auxWireSphere(GLdouble radius)
void auxSolidSphere(GLdouble radius)
绘制立方体void auxWireCube(GLdouble size)
void auxSolidCube(GLdouble size)
绘制长方体void auxWireBox(GLdouble width,GLdouble height,GLdouble depth)
void auxSolidBox(GLdouble width,GLdouble height,GLdouble depth)
绘制环形圆纹面void auxWireTorus(GLdouble innerRadius,GLdouble outerRadius)
void auxSolidTorus(GLdouble innerRadius,GLdouble outerRadius)
绘制圆柱void auxWireCylinder(GLdouble radius,GLdouble height)
void auxSolidCylinder(GLdouble radius,GLdouble height)
绘制二十面体void auxWireIcosahedron(GLdouble radius)
void auxSolidIcosahedron(GLdouble radius)
绘制八面体void auxWireOctahedron(GLdouble radius)
void auxSolidOctahedron(GLdouble radius)
绘制四面体void auxWireTetrahedron(GLdouble radius)
void auxSolidTetrahedron(GLdouble radius)
绘制十二面体void auxWireDodecahedron(GLdouble radius)
void auxSolidDodecahedron(GLdouble radius)
绘制圆锥void auxWireCone(GLdouble radius,GLdouble height)
void auxSolidCone(GLdouble radius,GLdouble height)
绘制茶壶void auxWireTeapot(GLdouble size)
void aucSolidTeapot(GLdouble size)
表6-1

  以上物体均以各自中心为原点绘制,所有坐标都已单位化,可以缩放。

  6.1.5 背景过程管理
  void auxIdleFunc(void *func)
  定义空闲状态执行函数。参数func是一个指针,指向所要执行的函数功能。当它为零时,func执行无效。

  6.1.6 程序运行
  void auxMainLoop(void(*displayFunc)(void))
  定义场景绘制循环函数。displayFunc指针指向场景绘制函数。当窗口需要更新或场景发生改变时,程序便调用它所指的函数,重新绘制场景。

6.2、辅助库应用示例
  下面举一个辅助库的应用例子,testaux.c:

  例6-1 辅助库应用例程 testaux.c
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  #include "glos.h"  
  2.   #include <GL/gl.h>  
  3.   #include <GL/glaux.h>  
  4.   void myinit(void);  
  5.   void CALLBACK myReshape(GLsizei w,GLsizei h);  
  6.   void CALLBACK display(void);  
  7.   
  8.   void myinit(void)  
  9.   {  
  10.     glClearColor(0.0,0.0,0.0,0.0);  
  11.     glClear(GL_COLOR_BUFFER_BIT);  
  12.   }  
  13.   
  14.   void CALLBACK myReshape(GLsizei w,GLsizei h)  
  15.   {  
  16.     glViewport(0,0,w,h);  
  17.     glMatrixMode(GL_PROJECTION);  
  18.     glLoadIdentity();  
  19.     if(w<=h)  
  20.       glOrtho(-1.5,1.5,-1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w,-10.0,10.0);  
  21.     else  
  22.       glOrtho(-1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w,-1.5,1.5,-10.0,10.0);  
  23.     glMatrixMode(GL_MODELVIEW);  
  24.     glLoadIdentity();  
  25.   }  
  26.   
  27.   void CALLBACK display(void)  
  28.   {  
  29.     glColor3f(1.0,1.0,0.0);  
  30.     auxWireSphere(1.0);  
  31.     glFlush();  
  32.   }  
  33.   
  34.   void main(void)  
  35.   {  
  36.     auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);  
  37.     auxInitPosition(0,0,500,500);  
  38.     auxInitWindow("AUX_SAMPLE");  
  39.     myinit();  
  40.     auxReshapeFunc(myReshape);  
  41.     auxMainLoop(display);  
  42.   }</span>  

图6-1 网状球体

  以上程序运行结果是在屏幕窗口内绘制一个黄色的网状球体,这个程序充分体现了辅助库的基本应用方法。
  首先,在主函数中用辅助库函数定义一个窗口auxInitWindow(),然后初始化颜色myinit(),这些在第一章中已说明。接下来是两个十分重要的函数 auxReshapeFunc()和auxMainLoop(),参数都是一个函数指针,指向的都是回调函数(回调函数定义用CALLBACK说明)。
  前者是窗口形状重定函数,参数指针指向函数myReshape(),它的两个参数就是窗口的新宽度和新高度。然后用glViewport(0, 0, w, h)重定视口,并且在新视口内重新定义投影矩阵,
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  glMatrixMode(GL_PROJECTION);  
  2.   glLoadIdentity();  
  3.   if(w<=h)  
  4.     glOrtho(-1.5,1.5,-1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w,-10.0,10.0);  
  5.   else  
  6.     glOrtho(-1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w,-1.5,1.5,-10.0,10.0);</span>  

  即先用glMatrixMode()说明当前矩阵操作与投影有关GL_PROJECTION,再用glLoadIdentity()将矩阵清为单位矩阵,避免受其它矩阵操作的干扰;然后调用glOrtho()对物体进行正射投影,并且用判断语句给出了两种情况,使投影后图像的比例与视点匹配,避免比例失调。
  再下来调用glMatrixMode()将矩阵操作改为对观察物体有关的方式GL_MODELVIEW,同样用 glLoadIdentity()清矩阵。后者是主函数循环函数,参数指针指向函数display(),即绘制物体。当窗口需要更新或物体发生改变时,程序便调用它重新绘制。以上例子是辅助库的最基本应用,复杂的应用将在后续的章节中详细介绍。 

七、OpenGL建模

  OpenGL基本库提供了大量绘制各种类型图元的方法,辅助库也提供了不少描述复杂三维图形的函数。这一章主要介绍基本图元,如点、线、多边形,有了这些图元,就可以建立比较复杂的模型了。

7.1、描述图元
  OpenGL是三维图形的函数库,它所定义的点、线、多边形等图元与一般的定义不太一样,存在一定的差别。对编程者来说,能否理解二者之间的差别十分重要。一种差别源于基于计算机计算的限制。OpenGL中所有浮点计算精度有限,故点、线、多边形的坐标值存在一定的误差。另一种差别源于位图显示的限制。以这种方式显示图形,最小的显示图元是一个象素,尽管每个象素宽度很小,但它们仍然比数学上所定义的点或线宽要大得多。当用OpenGL 进行计算时,虽然是用一系列浮点值定义点串,但每个点仍然是用单个象素显示,只是近似拟合。
  OpenGL图元是抽象的几何概念,不是真实世界中的物体,因此须用相关的数学模型来描述。

 7.1.1 齐次坐标(Homogeneous Coordinate
  在空间直角坐标系中,任意一点可用一个三维坐标矩阵[x y z]表示。如果将该点用一个四维坐标的矩阵[Hx Hy Hz H]表示时,则称为齐次坐标表示方法。在齐次坐标中,最后一维坐标H称为比例因子。
  在OpenGL中,二维坐标点全看作三维坐标点,所有的点都用齐次坐标来描述,统一作为三维齐次点来处理。每个齐次点用一个向量(x, y, z, w)表示,其中四个元素全不为零。齐次点具有下列几个性质:
  1)如果实数a非零,则(x, y, x, w)和(ax, ay, az, aw)表示同一个点,类似于x/y = (ax)/( ay)。
  2)三维空间点(x, y, z)的齐次点坐标为(x, y, z, 1.0),二维平面点(x,y)的齐次坐标为(x, y, 0.0, 1.0)。
  3)当w不为零时,齐次点坐标(x, y, z, w)即三维空间点坐标(x/w, y/w, z/w);当w为零时,齐次点(x, y, z, 0.0)表示此点位于某方向的无穷远处。
  注意:OpenGL中指定w大于或等于0.0。

  7.1.2 点(Point)
  用浮点值表示的点称为顶点(Vertex)。所有顶点在OpenGL内部计算时都作为三维点处理,用二维坐标(x, y)定义的点在OpenGL中默认z值为0。所有顶点坐标用齐次坐标(x, y, z, w) 表示,如果w不为0.0,这些齐次坐标表示的顶点即为三维空间点(x/w, y/w, z/w)。编程者可以自己指定w值,但很少这样做。一般来说,w缺省为1.0。

  7.1.3 线(Line)
  在OpenGL中,线代表线段(Line Segment),不是数学意义上的那种沿轴两个方向无限延伸的线。这里的线由一系列顶点顺次连结而成,有闭合和不闭合两种。见图7-1所示。

图7-1 线段的两种连结方式

  7.1.4 多边形(Polygon)
  OpenGL中定义的多边形是由一系列线段依次连结而成的封闭区域。这些线段不能交叉,区域内不能有空洞,多边形必须在凸多边形,否则不能被OpenGL函数接受。合法和非法多边形图示见图7-2。

图7-2 合法和非法多边形
  OpenGL多边形可以是平面多边形,即所有顶点在一个平面上,也可以是空间多边形。更复杂的多边形将在提高篇中介绍

7.2、绘制图元

  7.2.1 定义顶点
  在OpenGL中,所有几何物体最终都由有一定顺序的顶点集来描述。
  函数glVertex{234}{sifd}[v](TYPE coords)可以用二维、三维或齐次坐标定义顶点。举例如下:
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  glVertex2s(2,3);  
  2.   glVertex3d(0.0,1.0,3.1414926535);  
  3.   glVertex4f(2.4,1.0,-2.2,2.0);  
  4.   GLfloat pp[3]={5.0,2.0,10.2};  
  5.   glVertex3fv(pp);</span>  

  第一例子表示一个空间顶点(2, 3, 0),第二个例子表示用双精度浮点数定义一个顶点,第三个例子表示用齐次坐标定义一个顶点,其真实坐标为(1.2, 0.5, -1.1),最后一个例子表示用一个指针(或数组)定义顶点。

  7.2.2 构造几何图元
  在实际应用中,通常用一组相关的顶点序列以一定的方式组织起来定义某个几何图元,而不采用单独定义多个顶点来构造几何图元。在OpenGL中,所有被定义的顶点必须放在glBegain()和glEnd()两个函数之间才能正确表达一个几何图元或物体,否则,glVertex*()不完成任何操作。如:
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  glBegin(GL_POLYGON);  
  2.     glVertex2f(0.0,0.0);  
  3.     glVertex2f(0.0,3.0);  
  4.     glVertex2f(3.0,3.0);  
  5.     glVertex2f(4.0,1.5);  
  6.     glVertex2f(3.0,0.0);  
  7.   glEnd();</span>  

  以上这段程序定义了一个多边形,如果将glBegin()中的参数GL_POLYGON改为GL_POINTS,则图形变为一组顶点(5个),见图7-3所示。
  
图7-3 绘制多边形或一组顶点

  点函数glBegin(GLenum mode)标志描述一个几何图元的顶点列表的开始,其参数mode表示几何图元的描述类型。所有类型及说明见表7-1所示,相应的图示见图7-4。

类型说明
GL_POINTS单个顶点集
GL_LINES多组双顶点线段
GL_POLYGON单个简单填充凸多边形
GL_TRAINGLES多组独立填充三角形
GL_QUADS多组独立填充四边形
GL_LINE_STRIP不闭合折线
GL_LINE_LOOP闭合折线
GL_TRAINGLE_STRIP线型连续填充三角形串
GL_TRAINGLE_FAN扇形连续填充三角形串
GL_QUAD_STRIP连续填充四边形串
表7-1 几何图元类型和说明

图7-4 几何图元类型

  函数glEnd()标志顶点列表的结束。
  从图7-4中可看出,可以采用许多方法构造几何图元,这些方法仅仅依赖于所给的顶点数据。
  在glBegin()和glEnd()之间最重要的信息就是由函数glVertex*()定义的顶点,必要时也可为每个顶点指定颜色、法向、纹理坐标或其他,即调用相关的函数,见表7-2所示,具体用法以后会逐步介绍。

函数函数意义
glVertex*()设置顶点坐标
glColor*()设置当前颜色
glIndex*()设置当前颜色表
glNormal*()设置法向坐标
glEvalCoord*()产生坐标
glCallList(),glCallLists()执行显示列表
glTexCoord*()设置纹理坐标
glEdgeFlag*()控制边界绘制
glMaterial*()设置材质
表7-2 在glBegin()和glEnd()之间可调用的函数

  看如下几句:
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  glBegin(GL_POINTS);  
  2.     glColor3f(1.0,0.0,0.0); /* red color */  
  3.     glVertex(...);  
  4.     glColor3f(0.0,1.0,0.0); /* green color */  
  5.     glColor3f(0.0,0.0,1.0); /* blue color */  
  6.     glVertex(...);  
  7.     glVertex(...);  
  8.   glEnd();</span>  

  颜色等的设置只对当前点或后续点有效。上一例中第一个点是红色,第二个点和第三个点都是蓝色。其中设置绿色时,之后没有顶点操作,而是设置蓝色,故只有当前蓝色对紧跟其后的两个顶点有效。
  为了更好地理解构造几何图元函数的用法,下面举一个简单的例子:

  例7-3 几何图元构造例程(drawgeom.c
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  #include "glos.h"  
  2.   #include<GL/gl.h>  
  3.   #include<GL/glaux.h>  
  4.   
  5.   void myinit(void);  
  6.   void DrawMyObjects(void);  
  7.   void CALLBACK myReshape(GLsizei w,GLsizei h);  
  8.   void CALLBACK display(void);  
  9.   
  10.   void myinit(void)  
  11.   {  
  12.     glClearColor(0.0,0.0,0.0,0.0);  
  13.     glClear(GL_COLOR_BUFFER_BIT);  
  14.     glShadeModel(GL_FLAT);  
  15.   }  
  16.   
  17.   void CALLBACK myReshape(GLsizei w,GLsizei h)  
  18.   {  
  19.     glViewport(0,0,w,h);  
  20.     glMatrixMode(GL_PROJECTION);  
  21.     glLoadIdentity();  
  22.   
  23.     if(w<=h)  
  24.       glOrtho(-20.0,20.0,-20.0*(GLfloat)h/(GLfloat)w, 20.0*(GLfloat)h/(GLfloat)w,-50.0,50.0);  
  25.     else  
  26.       glOrtho(-20.0*(GLfloat)h/(GLfloat)w, 20.0*(GLfloat)h/(GLfloat)w,-20.0,20.0,-50.0,50.0);  
  27.     glMatrixMode(GL_MODELVIEW);  
  28.     glLoadIdentity();  
  29.   }  
  30.   
  31.   void CALLBACK display(void)  
  32.   {  
  33.     glColor3f(1.0,1.0,0.0);  
  34.     DrawMyObjects();  
  35.     glFlush();  
  36.   }  
  37.   
  38.   void DrawMyObjects(void)  
  39.   {  
  40.     /* draw some points */  
  41.     glBegin(GL_POINTS);  
  42.       glColor3f(1.0,0.0,0.0);  
  43.       glVertex2f(-10.0,11.0);  
  44.       glColor3f(1.0,1.0,0.0);  
  45.       glVertex2f(-9.0,10.0);  
  46.       glColor3f(0.0,1.0,1.0);  
  47.       glVertex2f(-8.0,12.0);  
  48.     glEnd();  
  49.   
  50.     /* draw some line_segments */  
  51.     glBegin(GL_LINES);  
  52.       glColor3f(1.0,1.0,0.0);  
  53.       glVertex2f(-11.0,8.0);  
  54.       glVertex2f(-7.0,7.0);  
  55.       glColor3f(1.0,0.0,1.0);  
  56.       glVertex2f(-11.0,9.0);  
  57.       glVertex2f(-8.0,6.0);  
  58.     glEnd();  
  59.   
  60.     /* draw one opened_line */  
  61.     glBegin(GL_LINE_STRIP);  
  62.       glColor3f(0.0,1.0,0.0);  
  63.       glVertex2f(-3.0,9.0);  
  64.       glVertex2f(2.0,6.0);  
  65.       glVertex2f(3.0,8.0);  
  66.       glVertex2f(-2.5,6.5);  
  67.     glEnd();  
  68.   
  69.     /* draw one closed_line */  
  70.     glBegin(GL_LINE_LOOP);  
  71.       glColor3f(0.0,1.0,1.0);  
  72.       glVertex2f(7.0,7.0);  
  73.       glVertex2f(8.0,8.0);  
  74.       glVertex2f(9.0,6.5);  
  75.       glVertex2f(10.3,7.5);  
  76.       glVertex2f(11.5,6.0);  
  77.       glVertex2f(7.5,6.0);  
  78.     glEnd();  
  79.   
  80.     /* draw one filled_polygon */  
  81.     glBegin(GL_POLYGON);  
  82.       glColor3f(0.5,0.3,0.7);  
  83.       glVertex2f(-7.0,2.0);  
  84.       glVertex2f(-8.0,3.0);  
  85.       glVertex2f(-10.3,0.5);  
  86.       glVertex2f(-7.5,-2.0);  
  87.       glVertex2f(-6.0,-1.0);  
  88.     glEnd();  
  89.   
  90.     /* draw some filled_quandrangles */  
  91.     glBegin(GL_QUADS);  
  92.       glColor3f(0.7,0.5,0.2);  
  93.       glVertex2f(0.0,2.0);  
  94.       glVertex2f(-1.0,3.0);  
  95.       glVertex2f(-3.3,0.5);  
  96.       glVertex2f(-0.5,-1.0);  
  97.       glColor3f(0.5,0.7,0.2);  
  98.       glVertex2f(3.0,2.0);  
  99.       glVertex2f(2.0,3.0);  
  100.       glVertex2f(0.0,0.5);  
  101.       glVertex2f(2.5,-1.0);  
  102.     glEnd();  
  103.   
  104.     /* draw some filled_strip_quandrangles */  
  105.     glBegin(GL_QUAD_STRIP);  
  106.       glVertex2f(6.0,-2.0);  
  107.       glVertex2f(5.5,1.0);  
  108.       glVertex2f(8.0,-1.0);  
  109.       glColor3f(0.8,0.0,0.0);  
  110.       glVertex2f(9.0,2.0);  
  111.       glVertex2f(11.0,-2.0);  
  112.       glColor3f(0.0,0.0,0.8);  
  113.       glVertex2f(11.0,2.0);  
  114.       glVertex2f(13.0,-1.0);  
  115.       glColor3f(0.0,0.8,0.0);  
  116.       glVertex2f(14.0,1.0);  
  117.     glEnd();  
  118.   
  119.     /* draw some filled_triangles */  
  120.     glBegin(GL_TRIANGLES);  
  121.       glColor3f(0.2,0.5,0.7);  
  122.       glVertex2f(-10.0,-5.0);  
  123.       glVertex2f(-12.3,-7.5);  
  124.       glVertex2f(-8.5,-6.0);  
  125.       glColor3f(0.2,0.7,0.5);  
  126.       glVertex2f(-8.0,-7.0);  
  127.       glVertex2f(-7.0,-4.5);  
  128.       glVertex2f(-5.5,-9.0);  
  129.     glEnd();  
  130.   
  131.     /* draw some filled_strip_triangles */  
  132.     glBegin(GL_TRIANGLE_STRIP);  
  133.       glVertex2f(-1.0,-8.0);  
  134.       glVertex2f(-2.5,-5.0);  
  135.       glColor3f(0.8,0.8,0.0);  
  136.       glVertex2f(1.0,-7.0);  
  137.       glColor3f(0.0,0.8,0.8);  
  138.       glVertex2f(2.0,-4.0);  
  139.       glColor3f(0.8,0.0,0.8);  
  140.       glVertex2f(4.0,-6.0);  
  141.     glEnd();  
  142.   
  143.     /* draw some filled_fan_triangles */  
  144.     glBegin(GL_TRIANGLE_FAN);  
  145.       glVertex2f(8.0,-6.0);  
  146.       glVertex2f(10.0,-3.0);  
  147.       glColor3f(0.8,0.2,0.5);  
  148.       glVertex2f(12.5,-4.5);  
  149.       glColor3f(0.2,0.5,0.8);  
  150.       glVertex2f(13.0,-7.5);  
  151.       glColor3f(0.8,0.5,0.2);  
  152.       glVertex2f(10.5,-9.0);  
  153.     glEnd();  
  154.   }  
  155.   
  156.   void main(void)  
  157.   {  
  158.     auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);  
  159.     auxInitPosition(0,0,500,500);  
  160.     auxInitWindow("Geometric Primitive Types");  
  161.     myinit();  
  162.     auxReshapeFunc(myReshape);  
  163.     auxMainLoop(display);  
  164.   }</span>  

  以上程序运行结果就是图7-4所示的内容,这个例子很好地说明了几何图元的类型及颜色等函数的用法。希望读者自己仔细分析每个物体的绘制方法,体会其中的关键之处,达到举一反三的效果。当然,还可利用上一章辅助库中提供的基本三维图元构造比较复杂的物体,你不妨也试一试。


八、OpenGL变换

  OpenGL变换是本篇的重点内容,它包括计算机图形学中最基本的三维变换,即几何变换、投影变换、裁剪变换、视口变换,以及针对OpenGL的特殊变换概念理解和用法,如相机模拟、矩阵堆栈等。学好了这章,才开始真正走进三维世界。

8.1、从三维空间到二维平面

  8.1.1 相机模拟
  在真实世界里,所有的物体都是三维的。但是,这些三维物体在计算机世界中却必须以二维平面物体的形式表现出来。那么,这些物体是怎样从三维变换到二维的呢?下面我们采用相机(Camera)模拟的方式来讲述这个概念,如图8-1所示。

图8-1 相机模拟

  实际上,从三维空间到二维平面,就如同用相机拍照一样,通常都要经历以下几个步骤 (括号内表示的是相应的图形学概念):
  第一步,将相机置于三角架上,让它对准三维景物(视点变换,Viewing Transformation)。
  第二步,将三维物体放在适当的位置(模型变换,Modeling Transformation)。
  第三步,选择相机镜头并调焦,使三维物体投影在二维胶片上(投影变换,Projection Transformation)。
  第四步,决定二维像片的大小(视口变换,Viewport Transformation)。
  这样,一个三维空间里的物体就可以用相应的二维平面物体表示了,也就能在二维的电脑屏幕上正确显示了。

  8.1.2 三维图形显示流程
  运用相机模拟的方式比较通俗地讲解了三维图形显示的基本过程,但在具体应用OpenGL函数库编程时,还必须了解三维图形世界中的几个特殊坐标系的概念,以及用这些概念表达的三维图形显示流程。
  计算机本身只能处理数字,图形在计算机内也是以数字的形式进行加工和处理的。大家都知道,坐标建立了图形和数字之间的联系。为了使被显示的物体数字化,要在被显示的物体所在的空间中定义一个坐标系。这个坐标系的长度单位和坐标轴的方向要适合对被显示物体的描述,这个坐标系称为世界坐标系。
  计算机对数字化的显示物体作了加工处理后,要在图形显示器上显示,这就要在图形显示器屏幕上定义一个二维直角坐标系,这个坐标系称为屏幕坐标系。这个坐标系坐标轴的方向通常取成平行于屏幕的边缘,坐标原点取在左下角,长度单位常取成一个象素的长度,大小可以是整型数。
  为了使显示的物体能以合适的位置、大小和方向显示出来,必须要通过投影。投影的方法有两种,即正射投影和透视投影。
  有时为了突出图形的一部分,只把图形的某一部分显示出来,这时可以定义一个三维视景体(Viewing Volume)。正射投影时一般是一个长方体的视景体,透视投影时一般是一个棱台似的视景体。只有视景体内的物体能被投影在显示平面上,其他部分则不能。在屏幕窗口内可以定义一个矩形,称为视口(Viewport),视景体投影后的图形就在视口内显示。
  为了适应物理设备坐标和视口所在坐标的差别,还要作一适应物理坐标的变换。这个坐标系称为物理设备坐标系。根据上面所述,三维图形的显示流程应如图8-2所示。

图8-2 三维图形的显示流程

  8.1.3 基本变换简单分析
  下面举一个简单的变换例子,cube.c:

  例8-4 简单变换例程(cube.c
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  #include "glos.h"  
  2.   #include <GL/gl.h>  
  3.   #include <GL/glu.h>  
  4.   #include <GL/glaux.h>  
  5.   
  6.   void myinit(void);  
  7.   void CALLBACK myReshape(GLsizei w, GLsizei h);  
  8.   void CALLBACK display(void);  
  9.   
  10.   void CALLBACK display (void)  
  11.   {  
  12.     glClear(GL_COLOR_BUFFER_BIT);  
  13.     glColor3f (1.0, 1.0, 1.0);  
  14.     glLoadIdentity (); /* clear the matrix */  
  15.     glTranslatef (0.0, 0.0, -5.0); /* viewing transformation */  
  16.     glScalef (1.0, 2.0, 1.0); /* modeling transformation */  
  17.     auxWireCube(1.0); /* draw the cube */  
  18.     glFlush();  
  19.   }  
  20.   
  21.   void myinit (void)  
  22.   {  
  23.     glShadeModel (GL_FLAT);  
  24.   }  
  25.   
  26.   void CALLBACK myReshape(GLsizei w, GLsizei h)  
  27.   {  
  28.     glMatrixMode (GL_PROJECTION); /* prepare for and then */  
  29.     glLoadIdentity (); /* define the projection */  
  30.     glFrustum (-1.0, 1.0, -1.0, 1.0, 1.5, 20.0); /* transformation */  
  31.     glMatrixMode (GL_MODELVIEW); /* back to modelview matrix */  
  32.     glViewport (0, 0, w, h); /* define the viewport */  
  33.   }  
  34.   
  35.   void main(void)  
  36.   {  
  37.     auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);  
  38.     auxInitPosition (0, 0, 500, 500);  
  39.     auxInitWindow ("Perspective 3-D Cube");  
  40.     myinit ();  
  41.     auxReshapeFunc (myReshape);  
  42.     auxMainLoop(display);  
  43.   }</span>  

  以上程序运行结果就是绘制一个三维的正面透视立方体。其中已经用到了相机模拟中提到的四种基本变换,即视点变换、模型变换、投影变换和视口变换。


图8-3 三维的正面透视立方体

  下面简单分析一下整个程序过程:
  1)视点变换。视点变换是在视点坐标系中进行的。视点坐标系于一般的物体所在的世界坐标系不同,它遵循左手法则,即左手大拇指指向Z正轴,与之垂直的四个手指指向X正轴,四指弯曲90度的方向是Y正轴。而世界坐标系遵循右手法则的。如图8-4所示。当矩阵初始化glLoadIdentity()后,调用glTranslatef()作视点变换。函数参数(x, y, z)表示视点或相机在视点坐标系中移动的位置,这里z=-5.0,意思是将相机沿Z负轴移动5个单位。
  通常相机位置缺省值同场景中的物体一样,都在原点处,而且相机初始方向都指向Z负轴。
  这里相机移走后,仍然对准立方体。如果相机需要指向另一方向,则调用glRotatef()可以改变。

图8-4 视点坐标系与世界坐标系

  2)模型变换。模型变换是在世界坐标系中进行的。在这个坐标系中,可以对物体实施平移 glTranslatef()、旋转glRotatef()和放大缩小glScalef()。例子里只对物体进行比例变换,glScalef(sx, sy, sz)的三个参数分别是X、Y、Z轴向的比例变换因子。缺省时都为1.0,即物体没变化。程序中物体Y轴比例为2.0,其余都为1.0,就是说将立方体变成长方体。
  3)投影变换。投影变换类似于选择相机的镜头。本例中调用了一个透视投影函数 glFrustum(),在调用它之前先要用glMatrixMode()说明当前矩阵方式是投影GL_PROJECTION。这个投影函数一共有六个参数,由它们可以定义一个棱台似的视景体。即视景体内的部分可见,视景体外的部分不可见,这也就包含了三维裁剪变换。
  4)视口变换。视口变换就是将视景体内投影的物体显示在二维的视口平面上。通常,都调用函数glViewport()来定义一个视口,这个过程类似于将照片放大或缩小。
  总而言之,一旦所有必要的变换矩阵被指定后,场景中物体的每一个顶点都要按照被指定的变换矩阵序列逐一进行变换。注意:OpenGL 中的物体坐标一律采用齐次坐标,即(x, y, z, w),故所有变换矩阵都采用4X4矩阵。一般说来,每个顶点先要经过视点变换和模型变换,然后进行指定的投影,如果它位于视景体外,则被裁剪掉。最后,余下的已经变换过的顶点x、y、z坐标值都用比例因子w除,即x/w、y/w、z/w,再映射到视口区域内,这样才能显示在屏幕上。

8.2、几何变换
  实际上,上述所说的视点变换和模型变换本质上都是一回事,即图形学中的几何变换。
  只是视点变换一般只有平移和旋转,没有比例变换。当视点进行平移或旋转时,视点坐标系中的物体就相当于在世界坐标系中作反方向的平移或旋转。因此,从某种意义上讲,二者可以统一,只是各自出发点不一样而已。读者可以根据具体情况,选择其中一个角度去考虑,这样便于理解。

  8.2.1 两个矩阵函数解释
  这里先解释两个基本OpenGL矩阵操作函数,便于以后章节的讲述。函数解释如下:

  void glLoadMatrix{fd}(const TYPE *m)

  设置当前矩阵中的元素值。函数参数*m是一个指向16个元素(m0, m1, ..., m15)的指针,这16个元素就是当前矩阵M中的元素,其排列方式如下:

M =| m0 m4 m8 m12 |
| m1 m5 m9 m13 |
| m2 m6 m10 m14 |
| m3 m7 m11 M15 |

  void glMultMatrix{fd}(const TYPE *m)

  用当前矩阵去乘*m所指定的矩阵,并将结果存放于*m中。当前矩阵可以是用glLoadMatrix() 指定的矩阵,也可以是其它矩阵变换函数的综合结果。
  当几何变换时,调用OpenGL的三个变换函数glTranslate*()、glRotate*()和glScale*(),实质上相当于产生了一个近似的平移、旋转和比例矩阵,然后调用glMultMatrix()与当前矩阵相乘。但是直接调用这三个函数程序运行得快一些,因OpenGL自动能计算矩阵。

  8.2.2 平移
  平移变换函数如下:

  void glTranslate{fd}(TYPE x,TYPE y,TYPE z)

  三个函数参数就是目标分别沿三个轴向平移的偏移量。这个函数表示用这三个偏移量生成的矩阵乘以当前矩阵。当参数是(0.0,0.0,0.0)时,表示对函数glTranslate*()的操作是单位矩阵,也就是对物体没有影响。平移示意如图8-5所示。

图8-5 平移示意图

  8.2.3 旋转
  旋转变换函数如下:

  void glRotate{fd}(TYPE angle,TYPE x,TYPE y,TYPE z)

  函数中第一个参数是表示目标沿从点(x, y, z)到原点的方向逆时针旋转的角度,后三个参数是旋转的方向点坐标。这个函数表示用这四个参数生成的矩阵乘以当前矩阵。当角度参数是0.0时,表示对物体没有影响。旋转示意如图8-6所示。

图8-6 旋转示意图

  8.2.3 缩放和反射
  缩放和反射变换函数如下:

  void glScale{fd}(TYPE x,TYPE y,TYPE z)

  三个函数参数值就是目标分别沿三个轴向缩放的比例因子。这个函数表示用这三个比例因子生成的矩阵乘以当前矩阵。这个函数能完成沿相应的轴对目标进行拉伸、压缩和反射三项功能。当参数是(1.0, 1.0, 1.0)时,表示对函数glScale*()操作是单位矩阵,也就是对物体没有影响。当其中某个参数为负值时,表示将对目标进行相应轴的反射变换,且这个参数不为1.0,则还要进行相应轴的缩放变换。最好不要令三个参数值都为零,这将导致目标沿三轴都缩为零。缩放和反射示意如图8-7所示。

图8-7 缩放和反射示意图

  8.2.5 几何变换举例
  以上介绍了三个基本几何变换函数,下面举一个简单的例子进一步说明它们的用法。程序如下:

  例 8-5 几何变换例程(geomtrsf.c
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  #include "glos.h"  
  2.   #include <GL/gl.h>  
  3.   #include <GL/glu.h>  
  4.   #include <GL/glaux.h>  
  5.   
  6.   void myinit(void);  
  7.   void draw_triangle(void);  
  8.   void CALLBACK display(void);  
  9.   void CALLBACK myReshape(GLsizei w, GLsizei h);  
  10.   
  11.   void draw_triangle(void)  
  12.   {  
  13.     glBegin(GL_LINE_LOOP);  
  14.       glVertex2f(0.0, 25.0);  
  15.       glVertex2f(25.0, -25.0);  
  16.       glVertex2f(-25.0, -25.0);  
  17.     glEnd();  
  18.   }  
  19.   
  20.   void CALLBACK display(void)  
  21.   {  
  22.     glClearColor (0.0, 0.0, 0.0, 1.0);  
  23.     glClear (GL_COLOR_BUFFER_BIT);   
  24.   
  25.     /* draw an original triangle */  
  26.     glLoadIdentity ();  
  27.     glColor3f (1.0, 1.0, 1.0);  /* white */  
  28.     draw_triangle ();  
  29.   
  30.     /* translating a triangle along X_axis */  
  31.     glLoadIdentity ();  
  32.     glTranslatef (-20.0, 0.0, 0.0);  
  33.     glColor3f(1.0,0.0,0.0);   /* red */  
  34.     draw_triangle ();  
  35.   
  36.     /* scaling a triangle along X_axis by 1.5 and along Y_axis by 0.5 */  
  37.     glLoadIdentity();  
  38.     glScalef (1.5, 0.5, 1.0);  
  39.     glColor3f(0.0,1.0,0.0);   /* green */  
  40.     draw_triangle ();  
  41.   
  42.     /* rotating a triangle in a counterclockwise direction about Z_axis */  
  43.     glLoadIdentity ();  
  44.     glRotatef (90.0, 0.0, 0.0, 1.0);  
  45.     glColor3f(0.0,0.0,1.0);   /* blue */  
  46.     draw_triangle ();  
  47.   
  48.     /* scaling a triangle along Y_axis and reflecting it about Y_axis */  
  49.     glLoadIdentity();  
  50.     glScalef (1.0, -0.5, 1.0);  
  51.     glColor3f(1.0,1.0,0.0);   /* yellow */  
  52.     draw_triangle ();  
  53.   
  54.     glFlush();  
  55.   }  
  56.   
  57.   void myinit (void)  
  58.   {  
  59.     glShadeModel (GL_FLAT);  
  60.   }  
  61.   
  62.   void CALLBACK myReshape(GLsizei w, GLsizei h)  
  63.   {  
  64.     glViewport(0, 0, w, h);  
  65.     glMatrixMode(GL_PROJECTION);  
  66.     glLoadIdentity();  
  67.     if (w <= h)  
  68.       glOrtho(-50.0, 50.0, -50.0*(GLfloat)h/(GLfloat)w, 50.0*(GLfloat)h/(GLfloat)w,-1.0,1.0);   
  69.     else  
  70.       glOrtho(-50.0*(GLfloat)w/(GLfloat)h, 50.0*(GLfloat)w/(GLfloat)h, -50.0, 50.0,-1.0,1.0);   
  71.     glMatrixMode(GL_MODELVIEW);  
  72.   }  
  73.   
  74.   void main(void)  
  75.   {  
  76.     auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);  
  77.     auxInitPosition (0, 0, 500, 500);  
  78.     auxInitWindow ("Geometric Transformations");  
  79.     myinit ();  
  80.     auxReshapeFunc (myReshape);  
  81.     auxMainLoop(display);  
  82.   }</span>  

  以上程序运行结果:第一个白色三角形是原始三角形,第二个红色三角形是白三角沿X 负轴平移后的三角形,第三个绿色三角形是白三角分别沿X轴和Y轴比例变换后的三角形,第四个蓝色三角形是白三角绕Z正轴逆时针转90度后的三角形,第五个黄色三角形是白三角沿Y轴方向缩小一倍且相对于X轴作反射后形成的三角形。

图8-8 三角形的几何变换

8.3、投影变换
  投影变换是一种很关键的图形变换,OpenGL中只提供了两种投影方式,一种是正射投影,另一种是透视投影。不管是调用哪种投影函数,为了避免不必要的变换,其前面必须加上以下两句:

  glMAtrixMode(GL_PROJECTION);
  glLoadIdentity();

  事实上,投影变换的目的就是定义一个视景体,使得视景体外多余的部分裁剪掉,最终图像只是视景体内的有关部分。本指南将详细讲述投影变换的概念以及用法。

  8.3.1 正射投影(Orthographic Projection
  正射投影,又叫平行投影。这种投影的视景体是一个矩形的平行管道,也就是一个长方体,如图8-9所示。正射投影的最大一个特点是无论物体距离相机多远,投影后的物体大小尺寸不变。这种投影通常用在建筑蓝图绘制和计算机辅助设计等方面,这些行业要求投影后的物体尺寸及相互间的角度不变,以便施工或制造时物体比例大小正确。

图8-9 正射投影视景体

  OpenGL正射投影函数共有两个,这在前面几个例子中已用过。一个函数是:

  void glOrtho(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top,
      GLdouble near,GLdouble far)

  它创建一个平行视景体。实际上这个函数的操作是创建一个正射投影矩阵,并且用这个矩阵乘以当前矩阵。其中近裁剪平面是一个矩形,矩形左下角点三维空间坐标是(left,bottom,-near),右上角点是(right,top,-near);远裁剪平面也是一个矩形,左下角点空间坐标是(left,bottom,-far),右上角点是(right,top,-far)。所有的near和far值同时为正或同时为负。如果没有其他变换,正射投影的方向平行于Z轴,且视点朝向Z负轴。
  这意味着物体在视点前面时far和near都为负值,物体在视点后面时far和near都为正值。另一个函数是:

  void gluOrtho2D(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top)

  它是一个特殊的正射投影函数,主要用于二维图像到二维屏幕上的投影。它的near和far缺省值分别为-1.0和1.0,所有二维物体的Z坐标都为0.0。因此它的裁剪面是一个左下角点为(left,bottom)、右上角点为(right,top)的矩形。

  8.3.2 透视投影(Perspective Projection
  透视投影符合人们心理习惯,即离视点近的物体大,离视点远的物体小,远到极点即为消失,成为灭点。它的视景体类似于一个顶部和底部都被切除掉的棱椎,也就是棱台。这个投影通常用于动画、视觉仿真以及其它许多具有真实性反映的方面。
  OpenGL透视投影函数也有两个,其中函数glFrustum()在8.1.3节中提到过,它所形成的视景体如图8-10所示。 

图8-10 函数glFrustum()透视投影视景体

  这个函数原型为:

  void glFrustum(GLdouble left,GLdouble Right,GLdouble bottom,GLdouble top,
      GLdouble near,GLdouble far);


  它创建一个透视视景体。其操作是创建一个透视投影矩阵,并且用这个矩阵乘以当前矩阵。这个函数的参数只定义近裁剪平面的左下角点和右上角点的三维空间坐标,即(left,bottom,-near)和(right,top,-near);最后一个参数far是远裁剪平面的Z负值,其左下角点和右上角点空间坐标由函数根据透视投影原理自动生成。near和far表示离视点的远近,它们总为正值。
  另一个函数是:

  void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear, GLdouble zFar);

  它也创建一个对称透视视景体,但它的参数定义于前面的不同,如图8-11所示。其操作是创建一个对称的透视投影矩阵,并且用这个矩阵乘以当前矩阵。参数 fovy定义视野在X-Z平面的角度,范围是[0.0, 180.0];参数aspect是投影平面宽度与高度的比率;参数zNear和Far分别是远近裁剪面沿Z负轴到视点的距离,它们总为正值。

图8-11 函数gluPerspective()透视投影视景体

  以上两个函数缺省时,视点都在原点,视线沿Z轴指向负方向。二者的应用实例将在后续章节中介绍。

8.4、裁剪变换
  在OpenGL中,空间物体的三维裁剪变换包括两个部分:视景体裁剪和附加平面裁剪。视景体裁剪已经包含在投影变换里,前面已述,这里不再重复。下面简单讲一下平面裁剪函数的用法。
  除了视景体定义的六个裁剪平面(上、下、左、右、前、后)外,用户还可自己再定义一个或多个附加裁剪平面,以去掉场景中无关的目标,如图8-12所示。

图8-12 附加裁剪平面和视景体

  附加平面裁剪函数为:

  void glClipPlane(GLenum plane,Const GLdouble *equation);

  函数定义一个附加的裁剪平面。其中参数equation指向一个拥有四个系数值的数组,这四个系数分别是裁剪平面Ax+By+Cz+D=0的A、B、 C、D值。因此,由这四个系数就能确定一个裁剪平面。参数plane是GL_CLIP_PLANEi(i=0,1,...),指定裁剪面号。
  在调用附加裁剪函数之前,必须先启动glEnable(GL_CLIP_PLANEi),使得当前所定义的裁剪平面有效;当不再调用某个附加裁剪平面时,可用glDisable(GL_CLIP_PLANEi)关闭相应的附加裁剪功能。
  下面这个例子不仅说明了附加裁剪函数的用法,而且调用了gluPerspective()透视投影函数,读者可以细细体会其中的用法。例程如下:

  例8-6 裁剪变换例程(clipball.c)
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  #include "glos.h"  
  2.   #include <GL/gl.h>  
  3.   #include <GL/glu.h>  
  4.   #include <GL/glaux.h>  
  5.   
  6.   void myinit(void);  
  7.   void CALLBACK myReshape(GLsizei w, GLsizei h);  
  8.   void CALLBACK display(void);  
  9.   
  10.   void CALLBACK display(void)  
  11.   {  
  12.     GLdouble eqn[4] = {1.0, 0.0, 0.0, 0.0};  
  13.   
  14.     glClear(GL_COLOR_BUFFER_BIT);  
  15.   
  16.     glColor3f (1.0, 0.0, 1.0);  
  17.     glPushMatrix();  
  18.     glTranslatef (0.0, 0.0, -5.0);  
  19.   
  20.     /* clip the left part of wire_sphere : x<0 */  
  21.     glClipPlane (GL_CLIP_PLANE0, eqn);  
  22.     glEnable (GL_CLIP_PLANE0);  
  23.     glRotatef (-90.0, 1.0, 0.0, 0.0);  
  24.     auxWireSphere(1.0);  
  25.     glPopMatrix();  
  26.     glFlush();  
  27.   }  
  28.   
  29.   void myinit (void)  
  30.   {  
  31.     glShadeModel (GL_FLAT);  
  32.   }  
  33.   
  34.   void CALLBACK myReshape(GLsizei w, GLsizei h)  
  35.   {  
  36.     glViewport(0, 0, w, h);  
  37.     glMatrixMode(GL_PROJECTION);  
  38.     glLoadIdentity();  
  39.     gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);  
  40.     glMatrixMode(GL_MODELVIEW);  
  41.   }  
  42.   
  43.   void main(void)  
  44.   {  
  45.     auxInitDisplayMode (AUX_SINGLE | AUX_RGB);  
  46.     auxInitPosition (0, 0, 500, 500);  
  47.     auxInitWindow ("Arbitrary Clipping Planes");  
  48.     myinit ();  
  49.     auxReshapeFunc (myReshape);  
  50.     auxMainLoop(display);  
  51.   }</span>  



图8-13 剪取后的网状半球体

8.5、视口变换
  在前面几节内容中已相继提到过视口变换,这一节将针对OpenGL来讲述视口变换的原理及其相关函数的用法。运用相机模拟方式,我们很容易理解视口变换就是类似于照片的放大与缩小。在计算机图形学中,它的定义是将经过几何变换、投影变换和裁剪变换后的物体显示于屏幕窗口内指定的区域内,这个区域通常为矩形,称为视口。OpenGL中相关函数是:

  glViewport(GLint x,GLint y,GLsizei width, GLsizei height);

  这个函数定义一个视口。函数参数(x, y)是视口在屏幕窗口坐标系中的左下角点坐标,参数width和height分别是视口的宽度和高度。缺省时,参数值即(0, 0, winWidth, winHeight) 指的是屏幕窗口的实际尺寸大小。所有这些值都是以象素为单位,全为整型数。
  注意:在实际应用中,视口的长宽比率总是等于视景体裁剪面的长宽比率。如果两个比率不相等,那么投影后的图像显示于视口内时会发生变形,如图8-14所示。另外,屏幕窗口的改变一般不明显影响视口的大小。因此,在调用这个函数时,最好实时检测窗口尺寸,及时修正视口的大小,保证视口内的图像能随窗口的变化而变化,且不变形。

图8-14 视景体到视口的映射

8.6 矩阵堆栈
  学过计算机的人也许都知道这个使用频率极高的名词 — “堆栈”。顾名思义,堆栈指的是一个顶部打开底部封闭的柱状物体,通常用来存放常用的东西。这些东西从顶部依次放入,但取出时也只能从顶部取出,即“先进后出,后进先出”。在计算机中,它常指在内存中开辟的一块存放某些变量的连续区域。因此,OpenGL的矩阵堆栈指的就是内存中专门用来存放矩阵数据的某块特殊区域。
  实际上,在创建、装入、相乘模型变换和投影变换矩阵时,都已用到堆栈操作。一般说来,矩阵堆栈常用于构造具有继承性的模型,即由一些简单目标构成的复杂模型。例如,一辆自行车就是由两个轮子、一个三角架及其它一些零部件构成的。它的继承性表现在当自行车往前走时,首先是前轮旋转,然后整个车身向前平移,接着是后轮旋转,然后整个车身向前平移,如此进行下去,这样自行车就往前走了。
  矩阵堆栈对复杂模型运动过程中的多个变换操作之间的联系与独立十分有利。因为所有矩阵操作函数如glLoadMatrix()、glMultMatrix()、 glLoadIdentity()等只处理当前矩阵或堆栈顶部矩阵,这样堆栈中下面的其它矩阵就不受影响。堆栈操作函数有以下两个:

  void glPushMatrix(void);
  void glPopMatrix(void);

  第一个函数表示将所有矩阵依次压入堆栈中,顶部矩阵是第二个矩阵的备份;压入的矩阵数不能太多,否则出错。第二个函数表示弹出堆栈顶部的矩阵,令原第二个矩阵成为顶部矩阵,接受当前操作,故原顶部矩阵被破坏;当堆栈中仅存一个矩阵时,不能进行弹出操作,否则出错。由此看出,矩阵堆栈操作与压入矩阵的顺序刚好相反,编程时要特别注意矩阵操作的顺序。
  为了更好地理解这两个函数,我们可以形象地认为glPushMatrix()就是“记住自己在哪”,glPopMatrix()就是“返回自己原来所在地”。请看下面一例:

  例8-7 堆栈操作例程(arm.c
[cpp]  view plain  copy
  1. <span style="font-size:12px;">#include "glos.h"  
  2.   #include <GL/gl.h>  
  3.   #include <GL/glu.h>  
  4.   #include <GL/glaux.h>  
  5.   
  6.   void myinit(void);  
  7.   void drawPlane(void);  
  8.   void CALLBACK elbowAdd (void);  
  9.   void CALLBACK elbowSubtract (void);  
  10.   void CALLBACK shoulderAdd (void);  
  11.   void CALLBACK shoulderSubtract (void);  
  12.   void CALLBACK display(void);  
  13.   void CALLBACK myReshape(GLsizei w, GLsizei h);  
  14.   
  15.   static int shoulder = 0, elbow = 0;  
  16.   
  17.   void CALLBACK elbowAdd (void)  
  18.   {  
  19.     elbow = (elbow + 5) % 360;  
  20.   }  
  21.   
  22.   void CALLBACK elbowSubtract (void)  
  23.   {  
  24.     elbow = (elbow - 5) % 360;  
  25.   }  
  26.   
  27.   void CALLBACK shoulderAdd (void)  
  28.   {  
  29.     shoulder = (shoulder + 5) % 360;  
  30.   }  
  31.   
  32.   void CALLBACK shoulderSubtract (void)  
  33.   {  
  34.     shoulder = (shoulder - 5) % 360;  
  35.   }  
  36.   
  37.   void CALLBACK display(void)  
  38.   {  
  39.     glClear(GL_COLOR_BUFFER_BIT);  
  40.     glColor3f(0.0, 1.0, 1.0);  
  41.   
  42.     glPushMatrix();  
  43.     glTranslatef (-0.5, 0.0, 0.0);  
  44.     glRotatef ((GLfloat)  
  45.     shoulder, 0.0, 0.0, 1.0);  
  46.     glTranslatef (1.0, 0.0, 0.0);  
  47.     auxWireBox(2.0, 0.2, 0.5);  
  48.   
  49.     glTranslatef (1.0, 0.0, 0.0);  
  50.     glRotatef ((GLfloat) elbow, 0.0, 0.0, 1.0);  
  51.     glTranslatef (0.8, 0.0, 0.0);  
  52.     auxWireBox(1.6, 0.2, 0.5);  
  53.   
  54.     glPopMatrix();  
  55.     glFlush();  
  56.   }  
  57.   
  58.   void myinit (void)  
  59.   {  
  60.     glShadeModel (GL_FLAT);  
  61.   }  
  62.   
  63.   void CALLBACK myReshape(GLsizei w, GLsizei h)  
  64.   {  
  65.     glViewport(0, 0, w, h);  
  66.     glMatrixMode(GL_PROJECTION);  
  67.     glLoadIdentity();  
  68.     gluPerspective(65.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);  
  69.     glMatrixMode(GL_MODELVIEW);  
  70.     glLoadIdentity(); glTranslatef (0.0, 0.0, -5.0); /* viewing transform */  
  71.   }  
  72.   
  73.   void main(void)  
  74.   {  
  75.     auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);  
  76.     auxInitPosition (0, 0, 400, 400);  
  77.     auxInitWindow ("Composite Modeling Transformations");  
  78.   
  79.     myinit ();  
  80.   
  81.     auxKeyFunc (AUX_LEFT, shoulderSubtract);  
  82.     auxKeyFunc (AUX_RIGHT, shoulderAdd);  
  83.     auxKeyFunc (AUX_UP, elbowAdd);  
  84.     auxKeyFunc (AUX_DOWN, elbowSubtract);  
  85.     auxReshapeFunc (myReshape);  
  86.     auxMainLoop(display);  
  87.   }  
  88. </span>  

  从以上例程可以看出,复杂的机械手臂是由两个简单的长方体依据一定的继承关系构成的,而这个继承关系是由矩阵堆栈的顺序决定的。

图8-15 简单机械手臂的符合运动



九、OpenGL颜色

  几乎所有OpenGL应用目的都是在屏幕窗口内绘制彩色图形,所以颜色在OpenGL编程中占有很重要的地位。这里的颜色与绘画中的颜色概念不一样,它属于RGB颜色空间,只在监视器屏幕上显示。另外,屏幕窗口坐标是以象素为单位,因此组成图形的每个象素都有自己的颜色,而这种颜色值是通过对一系列OpenGL函数命令的处理最终计算出来的。本章将讲述计算机颜色的概念以及OpenGL的颜色模式、颜色定义和两种模式应用场合等内容,若掌握好颜色的应用,你就能走进缤纷绚丽的色彩世界,从中享受无穷的乐趣。

9.1、计算机颜色

  9.1.1 颜色生成原理
  计算机颜色不同于绘画或印刷中的颜色,显示于计算机屏幕上每一个点的颜色都是由监视器内部的电子枪激发的三束不同颜色的光(红、绿、蓝)混合而成,因此,计算机颜色通 常用R(Red)、G(Green)、B(Blue)三个值来表示,这三个值又称为颜色分量。颜色生成原理 示意图见图9-1所示。

图9-1 计算机颜色生成原理

  9.1.2 RGB色立体(RGB Color Cube
  所有监视器屏幕的颜色都属于RGB颜色空间,如果用一个立方体形象地表示RGB颜色组成关系,那么就称这个立方体为RGB色立体,如图9-2所示。

图9-2 RGB色立体

  在图中,R、G、B三值的范围都是从0.0到1.0。如果某颜色分量越大,则表示对应的颜色分量越亮,也就是它在此点所贡献的颜色成分越多;反之,则越暗或越少。当R、G、B三个值都为0.0时,此点颜色为黑色(Black);当三者都为1.0时,此点颜色为白色(White);当三个颜色分量值相等时,表示三者贡献一样,因此呈现灰色(Grey),在图中表现为从黑色顶点到白色顶点的那条对角线;当R=1.0、G=1.0、B=0.0时,此点颜色为黄色(Yellow);同理,R=1.0、G=0.0、B=1.0时为洋红色,也叫品色(Magenta);R=0.0、G=1.0、B=1.0时为青色(Cyan)。

9.2、颜色模式
  OpenGL颜色模式一共有两个:RGB(RGBA)模式和颜色表模式。在RGB模式下,所有的颜色定义全用R、G、B三个值来表示,有时也加上 Alpha值(与透明度有关),即RGBA模式。在颜色表模式下,每一个象素的颜色是用颜色表中的某个颜色索引值表示,而这个索引值指向了相应的R、G、 B值。这样的一个表成为颜色映射(Color Map)。

  9.2.1 RGBA模式(RGBA Mode
  在RGBA模式下,可以用glColor*()来定义当前颜色。其函数形式为:

  void glColor3{b s i f d ub us ui}(TYPE r,TYPE g,TYPE b);
  void glColor4{b s i f d ub us ui}(TYPE r,TYPE g,TYPE b,TYPE a);
  void glColor3{b s i f d ub us ui}v(TYPE *v);
  void glColor4{b s i f d ub us ui}v(TYPE *v);


  设置当前R、G、B和A值。这个函数有3和4两种方式,在前一种方式下,a值缺省为1.0,后一种Alpha值由用户自己设定,范围从0.0到1.0。同样,它也可用指针传递参数。另外,函数的第二个后缀的不同使用,其相应的参数值及范围不同,见下表9-1所示。虽然这些参数值不同,但实际上 OpenGL已自动将它们映射在0.0到1.0或-1.0或范围之内。因此,灵活使用这些后缀,会给你编程带来很大的方便。

后缀数据类型最小值最小值映射最大值最大值映射
b1字节整型数-128-1.01271.0
s2字节整型数-32,768-1.032,7671.0
i4字节整型数-2,147,483,648-1.02,147,483,6471.0
ub1字节无符号整型数00.02551.0
us2字节无符号整型数00.065,5351.0
ui4字节无符号整型数00.04,294,967,2951.0
表9-1 整型颜色值到浮点数的转换

  9.2.2 颜色表模式(Color_Index Mode
  在颜色表方式下,可以调用glIndex*()函数从颜色表中选取当前颜色。其函数形式为:

  void glIndex{sifd}(TYPE c);
  void glIndex{sifd}v(TYPE *c);


  设置当前颜色索引值,即调色板号。若值大于颜色位面数时则取模。

  9.2.3 两种模式应用场合
  在大多情况下,采用RGBA模式比颜色表模式的要多,尤其许多效果处理,如阴影、光照、雾、反走样、混合等,采用RGBA模式效果会更好些;另外,纹理映射只能在RGBA模式下进行。下面提供几种运用颜色表模式的情况(仅供参考):
  1)若原来应用程序采用的是颜色表模式则转到OpenGL上来时最好仍保持这种模式,便于移植。
  2)若所用颜色不在缺省提供的颜色许可范围之内,则采用颜色表模式。
  3)在其它许多特殊处理,如颜色动画,采用这种模式会出现奇异的效果。

9.3、颜色应用举例
  颜色是一个极具吸引力的应用,在前面几章中已经逐步介绍了RGBA模式的应用方式,这里就不再多述。下面着重说一下颜色表模式的应用方法,请看例程:

  例9-1 颜色表应用例程(cindex.c
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  #include "glos.h"  
  2.   #include <GL/gl.h>  
  3.   #include <GL/glaux.h>  
  4.   
  5.   void myinit(void);  
  6.   void InitPalette(void);  
  7.   void DrawColorFans(void);  
  8.   void CALLBACK myReshape(GLsizei w,GLsizei h);  
  9.   void CALLBACK display(void);  
  10.   
  11.   void myinit(void)  
  12.   {  
  13.     glClearColor(0.0,0.0,0.0,0.0);  
  14.     glClear(GL_COLOR_BUFFER_BIT);  
  15.     glShadeModel(GL_FLAT);  
  16.   }  
  17.   
  18.   void InitPalette(void)  
  19.   {  
  20.     GLint j;  
  21.     static GLfloat rgb[][3]={  
  22.       {1.0,0.0,0.0},{1.0,0.0,0.5},{1.0,0.0,1.0},{0.0,0.0,1.0},  
  23.       {0.0,1.0,1.0},{0.0,1.0,0.0},{1.0,1.0,0.0},{1.0,0.5,0.0}};  
  24.   
  25.     for(j=0;j<8;j++)  
  26.       auxSetOneColor(j+1,rgb[j][0],rgb[j][1],rgb[j][2]);  
  27.   }  
  28.   
  29.   void CALLBACK myReshape(GLsizei w,GLsizei h)  
  30.   {  
  31.     glViewport(0,0,w,h);  
  32.     glMatrixMode(GL_PROJECTION);  
  33.     glLoadIdentity();  
  34.     if(w<=h)  
  35.       glOrtho(-12.0,12.0,-12.0*(GLfloat)h/(GLfloat)w, 12.0*(GLfloat)h/(GLfloat)w,-30.0,30.0);  
  36.     else  
  37.       glOrtho(-12.0*(GLfloat)h/(GLfloat)w, 12.0*(GLfloat)h/(GLfloat)w,-12.0,12.0,-30.0,30.0);   
  38.     glMatrixMode(GL_MODELVIEW);  
  39.     glLoadIdentity();  
  40.   }  
  41.   
  42.   void CALLBACK display(void)  
  43.   {  
  44.     InitPalette();  
  45.     DrawColorFans();  
  46.     glFlush();  
  47.   }  
  48.   
  49.   void DrawColorFans(void)  
  50.   {  
  51.     GLint n;  
  52.     GLfloat pp[8][2]={  
  53.       {7.0,-7.0},{0.0,-10.0},{-7.0,-7.0},{-10.0,0.0},  
  54.       {-7.0,7.0}, {0.0,10.0},{7.0,7.0},{10.0,0.0}};  
  55.   
  56.     /* draw some filled_fan_triangles */  
  57.     glBegin(GL_TRIANGLE_FAN);  
  58.       glVertex2f(0.0,0.0);  
  59.       glVertex2f(10.0,0.0);  
  60.       for(n=0;n<8;n++)  
  61.       {  
  62.         glIndexi(n+1);  
  63.         glVertex2fv(pp[n]);  
  64.       }  
  65.     glEnd();  
  66.   }  
  67.   
  68.   void main(void)  
  69.   {  
  70.     auxInitDisplayMode(AUX_SINGLE|AUX_INDEX);  
  71.     auxInitPosition(0,0,500,500);  
  72.     auxInitWindow("Color Index");  
  73.     myinit();  
  74.     auxReshapeFunc(myReshape);  
  75.     auxMainLoop(display);  
  76.   }  
  77. </span>  


  这个程序运行结果是在屏幕上显示八个连成扇形的不同颜色的三角形,每个三角形的颜色定义采用颜色表模式。其中,调用了辅助库函数auxSetOneColor()来装载颜色映射表,即调色板。因为将某个颜色装载到颜色查找表(color lookup table)中的过程必须依赖窗口系统,而OpenGL函数与窗口系统无关,所以这里就调用辅助库的函数来完成这个过程,然后才调用OpenGL自己的函数glIndex()设置当前的颜色号。

图9-3 自定义调色板



十、OpenGL光照

10.1、真实感图形基本概念
  真实感图形绘制是计算机图形学的一个重要组成部分,它综合利用数学、物理学、计算机科学和其它科学知识在计算机图形设备上生成象彩色照片那样的具有真实感的图形。一般说来,用计算机在图形设备上生成真实感图形必须完成以下四个步骤:一是用建模,即用一定的数学方法建立所需三维场景的几何描述,场景的几何描述直接影响图形的复杂性和图形绘制的计算耗费;二是将三维几何模型经过一定变换转为二维平面透视投影图;三是确定场景中所有可见面,运用隐藏面消隐算法将视域外或被遮挡住的不可见面消去;四是计算场景中可见面的颜色,即根据基于光学物理的光照模型计算可见面投射到观察者眼中的光亮度大小和颜色分量,并将它转换成适合图形设备的颜色值,从而确定投影画面上每一象素的颜色,最终生成图形。
  由于真实感图形是通过景物表面的颜色和明暗色调来表现景物的几何形状、空间位置以及表面材料的,而一个物体表面所呈现的颜色是由表面向视线方向辐射的光能决定的。在计算机图形学中,常采用一个既能表示光能大小又能表示其颜色组成的物理量即光亮度(luminance)或光强(intensity of light)来描述物体表面朝某方向辐射光能的颜色。采用这个物理量可以正确描述光在物体表面的反射、透射和吸收现象,因而可以正确计算处物体表面在空间给定方向上的光能颜色。
  物体表面向空间给定方向辐射的光强可应用光照模型进行计算。简单的光照模型通常假定物体表面是光滑的且由理想材料构成,因此只考虑光源照射在物体表面产生的反射光,所生成的图形可以模拟处不透明物体表面的明暗过渡,具有一定的真实感效果。复杂的光照模型除了考虑上述因素外,还要考虑周围环境的光对物体表面的影响。如光亮平滑的物体表面会将环境中其它物体映像在表面上,而通过透明物体也可看到其后的环境景象。这类光照模型称为整体光照模型,它能模拟出镜面映像、透明等较精致的光照效果。为了更真实的绘制图形,还要考虑物体表面的细节纹理,这通常使用一种称为“纹理映射”(texture mapping)的技术把已有的平面花纹图案映射到物体表面上,并在应用光照模型时将这些花纹的颜色考虑进去,物体表面细节的模拟使绘制的图形更接近自然景物。
  以上内容中,真实感图形绘制的四大步骤前两步在前面的章节已经详细介绍过,这里不再重复,第三步OpenGL将自动完成所有消隐过程,第四步下面几节详述。另外,部分复杂光照模型应用将在后续章节里介绍。

10.2、光照模型

  10.2.1 简单光照模型
  当光照射到一个物体表面上时,会出现三种情形。首先,光可以通过物体表面向空间反射,产生反射光。其次,对于透明体,光可以穿透该物体并从另一端射出,产生透射光。最后,部分光将被物体表面吸收而转换成热。在上述三部分光中,仅仅是透射光和反射光能够进入人眼产生视觉效果。这里介绍的简单光照模型只考虑被照明物体表面的反射光影响,假定物体表面光滑不透明且由理想材料构成,环境假设为由白光照明。
  一般来说,反射光可以分成三个分量,即环境反射、漫反射和镜面反射。环境反射分量假定入射光均匀地从周围环境入射至景物表面并等量地向各个方向反射出去,通常物体表面还会受到从周围环境来的反射光(如来自地面、天空、墙壁等的反射光)的照射,这些光常统称为环境光(Ambient Light);漫反射分量表示特定光源在景物表面的反射光中那些向空间各方向均匀反射出去的光,这些光常称为漫射光(Diffuse Light);镜面反射光为朝一定方向的反射光,如一个点光源照射一个金属球时会在球面上形成一块特别亮的区域,呈现所谓“高光(Highlight)”,它是光源在金属球面上产生的镜面反射光(Specular Light)。对于较光滑物体,其镜面反射光的高光区域小而亮;相反,粗糙表面的镜面反射光呈发散状态,其高光区域大而不亮。下面先看一个简单的光照例程。

  例10-1 简单光照例程(light0.c
[cpp]  view plain  copy
  1. <span style="font-size:12px;"> #include "glos.h"  
  2.   #include <GL/gl.h>  
  3.   #include <GL/glu.h>  
  4.   #include <GL/glaux.h>  
  5.   
  6.   void myinit(void);  
  7.   void CALLBACK myReshape(GLsizei w, GLsizei h);  
  8.   void CALLBACK display(void);  
  9.   
  10.   void myinit(void)  
  11.   {  
  12.     GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };  
  13.   
  14.     glLightfv(GL_LIGHT0, GL_POSITION, light_position);  
  15.   
  16.     glEnable(GL_LIGHTING);  
  17.     glEnable(GL_LIGHT0);  
  18.     glDepthFunc(GL_LESS);  
  19.     glEnable(GL_DEPTH_TEST);  
  20.   }  
  21.   
  22.   void CALLBACK display(void)  
  23.   {  
  24.     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
  25.     auxSolidSphere(1.0);  
  26.     glFlush();  
  27.   }  
  28.   
  29.   void CALLBACK myReshape(GLsizei w, GLsizei h)  
  30.   {  
  31.     glViewport(0, 0, w, h);  
  32.     glMatrixMode(GL_PROJECTION);  
  33.     glLoadIdentity();  
  34.     if (w <= h)  
  35.       glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);  
  36.     else  
  37.       glOrtho (-1.5*(GLfloat)w/(GLfloat)h, 1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);  
  38.     glMatrixMode(GL_MODELVIEW);  
  39.     glLoadIdentity();  
  40.   }  
  41.   
  42.   void main(void)  
  43.   {  
  44.     auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);  
  45.     auxInitPosition (0, 0, 500, 500);  
  46.     auxInitWindow ("Simple Lighting");  
  47.     myinit();  
  48.     auxReshapeFunc (myReshape);  
  49.     auxMainLoop(display);  
  50.   }</span>  

  以上程序运行结果是显示一个具有灰色光影的球。其中函数myinit()中包含了关键的设定光源位置、启动光照等几句,而其它程序语言几乎与以前的没有多大区别,但效果却完全不一样。下面几个小节将详细介绍有关函数的用法。

图10-1 带光影的灰色球体

  10.2.2 OpenGL光组成
  在OpenGL简单光照模型中的几种光分为:辐射光(Emitted Light)、环境光(Ambient Light)、漫射光(Diffuse Light)、镜面光(Specular Light)。
  辐射光是最简单的一种光,它直接从物体发出并且不受任何光源影响。
  环境光是由光源发出经环境多次散射而无法确定其方向的光,即似乎来自所有方向。一般说来,房间里的环境光成分要多些,户外的相反要少得多,因为大部分光按相同方向照射,而且在户外很少有其他物体反射的光。当环境光照到曲面上时,它在各个方向上均等地发散(类似于无影灯光)。
  漫射光来自一个方向,它垂直于物体时比倾斜时更明亮。一旦它照射到物体上,则在各个方向上均匀地发散出去。于是,无论视点在哪里它都一样亮。来自特定位置和特定方向的任何光,都可能有散射成分。
  镜面光来自特定方向并沿另一方向反射出去,一个平行激光束在高质量的镜面上产生100%的镜面反射。光亮的金属和塑料具有很高非反射成分,而象粉笔和地毯等几乎没有反射成分。因此,三某种意义上讲,物体的反射程度等同于其上的光强(或光亮度)。

  10.2.3 创建光源(Light Source
  光源有许多特性,如颜色、位置、方向等。选择不同的特性值,则对应的光源作用在物体上的效果也不一样,这在以后的章节中会逐步介绍的。下面详细讲述定义光源特性的函数glLight*():

  void glLight{if}[v](GLenum light , GLenum pname, TYPE param)

  创建具有某种特性的光源。其中第一个参数light指定所创建的光源号,如GL_LIGHT0、GL_LIGHT1、...、GL_LIGHT7。第二个参数pname指定光源特性,这个参数的辅助信息见表10-1所示。最后一个参数设置相应的光源特性值。

pname 参数名缺省值说明
GL_AMBIENT(0.0, 0.0, 0.0, 1.0)RGBA模式下环境光
GL_DIFFUSE(1.0, 1.0, 1.0, 1.0)RGBA模式下漫反射光
GL_SPECULAR(1.0,1.0,1.0,1.0)RGBA模式下镜面光
GL_POSITION(0.0,0.0,1.0,0.0)光源位置齐次坐标(x,y,z,w)
GL_SPOT_DIRECTION(0.0,0.0,-1.0)点光源聚光方向矢量(x,y,z)
GL_SPOT_EXPONENT0.0点光源聚光指数
GL_SPOT_CUTOFF180.0点光源聚光截止角
GL_CONSTANT_ATTENUATION1.0常数衰减因子
GL_LINER_ATTENUATION0.0线性衰减因子
GL_QUADRATIC_ATTENUATION0.0平方衰减因子
表10-1 函数glLight*()参数pname说明

   注意:以上列出的GL_DIFFUSE和GL_SPECULAR的缺省值只能用于GL_LIGHT0,其他几个光源的GL_DIFFUSE和GL_SPECULAR缺省值为(0.0,0.0,0.0,1.0)。另外,表中后六个参数的应用放在下一篇中介绍。在上面例程中,光源的创建为:

  GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
  glLightfv(GL_LIGHT0, GL_POSITION, light_position);


  其中light_position是一个指针,指向定义的光源位置齐次坐标数组。其它几个光源特性都为缺省值。同样,我们也可用类似的方式定义光源的其他几个特性值,例如:

  GLfloat light_ambient [] = { 0.0, 0.0, 0.0, 1.0 };
  GLfloat light_diffuse [] = { 1.0, 1.0, 1.0, 1.0 };
  GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
  glLightfv(GL_LIGHT0, GL_AMBIENT , light_ambient );
  glLightfv(GL_LIGHT0, GL_DIFFUSE , light_diffuse );
  glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);


  10.2.4 启动光照
  在OpenGL中,必须明确指出光照是否有效或无效。如果光照无效,则只是简单地将当前颜色映射到当前顶点上去,不进行法向、光源、材质等复杂计算,那么显示的图形就没有真实感,如前几章例程运行结果显示。要使光照有效,首先得启动光照,即:

  glEnable(GL_LIGHTING);

  若使光照无效,则调用gDisable(GL_LIGHTING)可关闭当前光照。然后,必须使所定义的每个光源有效,例light0.c中只用了一个光源,即:

  glEnable(GL_LIGHT0);

  其它光源类似,只是光源号不同而已。

10.3、明暗处理
  在计算机图形学中,光滑的曲面表面常用多边形予以逼近和表示,而每个小多边形轮廓(或内部)就用单一的颜色或许多不同的颜色来勾画(或填充),这种处理方式就称为明暗处理。在OpenGL中,用单一颜色处理的称为平面明暗处理(Flat Shading),用许多不同颜色处理的称为光滑明暗处理(Smooth Shading),也称为Gourand明暗处理(Gourand Shading)。设置明暗处理模式的函数为:

  void glShadeModel(GLenum mode);

  函数参数为GL_FLAT或GL_SMOOTH,分别表示平面明暗处理和光滑明暗处理。
  应用平面明暗处理模式时,多边形内每个点的法向一致,且颜色也一致;应用光滑明暗处理模式时,多边形所有点的法向是由内插生成的,具有一定的连续性,因此每个点的颜色也相应内插,故呈现不同色。这种模式下,插值方法采用的是双线性插值法,如图10-2所示。

图10-2 Gouraud明暗处理

  Gouraud明暗处理通常算法为:先用多边形顶点的光强线性插值出当前扫描线与多边形边交点处的光强,然后再用交点的光强线插值处扫描线位于多边形内区段上每一象素处的光强值。图中显示出一条扫描线与多边形相交,交线的端点是A点和B点,P点是扫描线上位于多边形内的任一点,多边形三个顶点的光强分别为I1、I2和I3.取A点的光强Ia为I1和I2的线性插值,B点的光强Ib为I1和I3的线性插值,P点的光强Ip则为Ia和Ib的线性插值。采用Gouraud明暗处理不但可以使用多边形表示的曲面光强连续,而且计算量很小。这种算法还可以以增量的形式改进,且能用硬件直接实现算法,从而广泛用于计算机实时图形生成。请看下面光滑明暗处理的例程:

  例10-2 明暗处理例程(Shading.c
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  #include "glos.h"  
  2.   #include <GL/gl.h>  
  3.   #include <GL/glu.h>  
  4.   #include <GL/glaux.h>  
  5.   
  6.   void myinit(void);  
  7.   void object(void);  
  8.   void CALLBACK display(void);  
  9.   void CALLBACK myReshape(GLsizei w, GLsizei h);  
  10.   
  11.   /* GL_SMOOTH is actually the default shading model. */  
  12.   void myinit (void)  
  13.   {  
  14.     glShadeModel (GL_SMOOTH);  
  15.   }  
  16.   
  17.   void object(void)  
  18.   {  
  19.     glBegin (GL_POLYGON);  
  20.       glColor3f (1.0, 0.0, 0.0);  
  21.         glVertex2f (4.0, 4.0);  
  22.       glColor3f(1.0,1.0,1.0);  
  23.         glVertex2f (12.0, 4.0);  
  24.       glColor3f(0.0,0.0,1.0);  
  25.         glVertex2f (12.0, 12.0);  
  26.       glColor3f(0.0,1.0,0.0);  
  27.         glVertex2f (4.0, 12.0);  
  28.     glEnd ();  
  29.   }  
  30.   
  31.   void CALLBACK display(void)  
  32.   {  
  33.     glClear (GL_COLOR_BUFFER_BIT);  
  34.     object ();  
  35.     glFlush ();  
  36.   }  
  37.   
  38.   void CALLBACK myReshape(GLsizei w, GLsizei h)  
  39.   {  
  40.     glViewport(0, 0, w, h);  
  41.     glMatrixMode(GL_PROJECTION);  
  42.     glLoadIdentity();  
  43.     if (w <= h)  
  44.       gluOrtho2D (0.0, 16.0, 0.0, 16.0 * (GLfloat) h/(GLfloat) w);  
  45.     else  
  46.       gluOrtho2D (0.0, 16.0 * (GLfloat) w/(GLfloat) h, 0.0, 16.0);  
  47.     glMatrixMode(GL_MODELVIEW);  
  48.   }  
  49.   
  50.   void main(void)  
  51.   {  
  52.     auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);  
  53.     auxInitPosition (0, 0, 500, 500);  
  54.     auxInitWindow ("Smooth Shading");  
  55.     myinit();  
  56.     auxReshapeFunc (myReshape);  
  57.     auxMainLoop(display);  
  58.   }</span>  


  以上程序运行结果是在屏幕上显示一个色彩连续变化的三角形。这个程序是用的RGBA显示模式,若改用颜色表模式,则颜色内插实际上是颜色表的内插,因此呈现的颜色可能不连续。网友不妨自己试试。
  另外,若在light0.c程序中加上一句定义GL_FLAT明暗处理模式,则又会出现怎样的情形呢?读者可以仔细比较一下。

图10-3 高氏明暗处理的正方形

10.4、材质

  10.4.1 材质颜色
  OpenGL用材料对光的红、绿、蓝三原色的反射率来近似定义材料的颜色。象光源一样,材料颜色也分成环境、漫反射和镜面反射成分,它们决定了材料对环境光、漫反射光和镜面反射光的反射程度。在进行光照计算时,材料对环境光的反射率与每个进入光源的环境光结合,对漫反射光的反射率与每个进入光源的漫反射光结合,对镜面光的反射率与每个进入光源的镜面反射光结合。对环境光与漫反射光的反射程度决定了材料的颜色,并且它们很相似。对镜面反射光的反射率通常是白色或灰色(即对镜面反射光中红、绿、蓝的反射率相同)。镜面反射高光最亮的地方将变成具有光源镜面光强度的颜色。例如一个光亮的红色塑料球,球的大部分表现为红色,光亮的高光将是白色的。

  10.4.2 材质定义
  材质的定义与光源的定义类似。其函数为:

  void glMaterial{if}[v](GLenum face,GLenum pname,TYPE param);

  定义光照计算中用到的当前材质。face可以是GL_FRONT、GL_BACK、GL_FRONT_AND_BACK,它表明当前材质应该应用到物体的哪一个面上;pname说明一个特定的材质;param是材质的具体数值,若函数为向量形式,则param是一组值的指针,反之为参数值本身。非向量形式仅用于设置GL_SHINESS。pname参数值具体内容见表10-1。另外,参数GL_AMBIENT_AND_DIFFUSE表示可以用相同的 RGB值设置环境光颜色和漫反射光颜色。

参数名缺省值说明
GL_AMBIENT(0.2, 0.2, 0.2, 1.0)材料的环境光颜色
GL_DIFFUSE(0.8, 0.8, 0.8, 1.0)材料的漫反射光颜色
GL_AMBIENT_AND_DIFFUSE 材料的环境光和漫反射光颜色
GL_SPECULAR(0.0, 0.0, 0.0, 1.0)材料的镜面反射光颜色
GL_SHINESS0.0镜面指数(光亮度)
GL_EMISSION(0.0, 0.0, 0.0, 1.0)材料的辐射光颜色
GL_COLOR_INDEXES(0, 1, 1)材料的环境光、漫反射光和镜面光颜色
表10-2 函数glMaterial*()参数pname的缺省值

  例10-3 材质定义例程(light1.c
[cpp]  view plain  copy
  1. <span style="font-size:12px;">    #include "glos.h"  
  2.   #include <GL/gl.h>  
  3.   #include <GL/glaux.h>  
  4.   
  5.   void myinit(void);  
  6.   void CALLBACK myReshape(GLsizei w, GLsizei h);  
  7.   
  8.   void CALLBACK display(void); void myinit(void)  
  9.   {  
  10.     /* 设置材质的各种光的颜色成分反射比率 */  
  11.     GLfloat mat_ambient[]={0.8,0.8,0.8,1.0};  
  12.     GLfloat mat_diffuse[]={0.8,0.0,0.8,1.0};     /* 紫色 */  
  13.     GLfloat mat_specular[] = { 1.0, 0.0, 1.0, 1.0 }; /* 亮紫色 */  
  14.     GLfloat mat_shininess[] = { 50.0 };  
  15.   
  16.     GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };  
  17.   
  18.     glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);  
  19.     glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  
  20.     glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);  
  21.     glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);  
  22.   
  23.     glLightfv(GL_LIGHT0, GL_POSITION, light_position);  
  24.   
  25.     glEnable(GL_LIGHTING);  
  26.     glEnable(GL_LIGHT0);  
  27.     glDepthFunc(GL_LESS);  
  28.     glEnable(GL_DEPTH_TEST);  
  29.   }  
  30.   
  31.   void CALLBACK display(void)  
  32.   {  
  33.     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
  34.     auxSolidSphere(1.0);  
  35.     glFlush();  
  36.   }  
  37.   
  38.   void CALLBACK myReshape(GLsizei w, GLsizei h)  
  39.   {  
  40.     glViewport(0, 0, w, h);  
  41.     glMatrixMode(GL_PROJECTION);  
  42.     glLoadIdentity();  
  43.     if (w <= h)  
  44.       glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);  
  45.     else  
  46.       glOrtho (-1.5*(GLfloat)w/(GLfloat)h, 1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);  
  47.     glMatrixMode(GL_MODELVIEW);   
  48.     glLoadIdentity();  
  49.   }  
  50.   
  51.   void main(void)  
  52.   {  
  53.     auxInitDisplayMode (AUX_SINGLE | AUX_RGBA | AUX_DEPTH16);  
  54.     auxInitPosition (0, 0, 500, 500);  
  55.     auxInitWindow ("Lighting_1 ");  
  56.     myinit();  
  57.     auxReshapeFunc (myReshape);  
  58.     auxMainLoop(display);  
  59.   }</span>  


  以上程序运行结果是一个紫色的球。在函数myinit()中定义了球的材质颜色,光源的定义仍延用light0.c中的,而light.c物体的光源定义为缺省形式。从例子中明显地看出,物体的材质颜色定义与光源颜色定义几乎一样,物体反射到眼中的颜色与二者都有关系,具体关系请看下一小节。

  10.4.3 材质RGB值和光源RGB值的关系
  材质的颜色与光源的颜色有些不同。对于光源,R、G、B值等于R、G、B对其最大强度的百分比。若光源颜色的R、G、B值都是1.0,则是最强的白光;若值变为0.5,颜色仍为白色,但强度为原来的一半,于是表现为灰色;若R=G=1.0,B=0.0,则光源为黄色。对于材质,R、G、B值为材质对光的 R、G、B成分的反射率。比如,一种材质的R=1.0、G=0.5、B=0.0,则材质反射全部的红色成分,一半的绿色成分,不反射蓝色成分。也就是说,若OpenGL的光源颜色为(LR、LG、LB),材质颜色为(MR、MG、MB),那么,在忽略所有其他反射效果的情况下,最终到达眼睛的光的颜色为(LR*MR、LG*MG、LB*MB)。
  同样,如果有两束光,相应的值分别为(R1、G1、B1)和(R2、G2、B2),则OpenGL 将各个颜色成分相加,得到(R1+R2、G1+G2、B1+B2),若任一成分的和值大于1(超出了设备所能显示的亮度)则约简到1.0。下面一例程就说明了二者之间的关系。

  例10-4 材质与光源的RGB关系例程(light2.c
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  #include "glos.h"  
  2.   #include <GL/gl.h>  
  3.   #include <GL/glaux.h>  
  4.   
  5.   void myinit(void);  
  6.   void CALLBACK myReshape(GLsizei w, GLsizei h);  
  7.   void CALLBACK display(void);  
  8.   
  9.   void myinit(void)  
  10.   {  
  11.     GLfloat mat_ambient[]= { 0.8, 0.8, 0.8, 1.0 };  
  12.     GLfloat mat_diffuse[]= { 0.8, 0.0, 0.8, 1.0 }; /* 紫色 */  
  13.     GLfloat mat_specular[] = { 1.0, 0.0, 1.0, 1.0 };  
  14.     GLfloat mat_shininess[] = { 50.0 };  
  15.   
  16.     GLfloat light_diffuse[]= { 0.0, 0.0, 1.0, 1.0}; /* 蓝色 */  
  17.     GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };  
  18.   
  19.     glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);  
  20.     glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  
  21.     glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);  
  22.     glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);  
  23.   
  24.     glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);  
  25.     glLightfv(GL_LIGHT0, GL_POSITION, light_position);  
  26.   
  27.     glEnable(GL_LIGHTING);  
  28.     glEnable(GL_LIGHT0);  
  29.     glDepthFunc(GL_LESS);  
  30.     glEnable(GL_DEPTH_TEST);  
  31.   }  
  32.   
  33.   void CALLBACK display(void)  
  34.   {  
  35.     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
  36.     auxSolidSphere(1.0);  
  37.     glFlush();  
  38.   }  
  39.   
  40.   void CALLBACK myReshape(GLsizei w, GLsizei h)  
  41.   {  
  42.     glViewport(0, 0, w, h);  
  43.     glMatrixMode(GL_PROJECTION);  
  44.     glLoadIdentity();  
  45.     if (w <= h)  
  46.       glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);  
  47.     else  
  48.       glOrtho (-1.5*(GLfloat)w/(GLfloat)h, 1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);  
  49.     glMatrixMode(GL_MODELVIEW);  
  50.     glLoadIdentity();  
  51.   }  
  52.   
  53.   void main(void)  
  54.   {  
  55.     auxInitDisplayMode (AUX_SINGLE | AUX_RGBA | AUX_DEPTH16);  
  56.     auxInitPosition (0, 0, 500, 500);  
  57.     auxInitWindow ("Lighting_2 ");  
  58.     myinit();  
  59.     auxReshapeFunc (myReshape);  
  60.     auxMainLoop(display);  
  61.   }</span>  


  以上程序运行结果是一个蓝色的球,其中高光部分仍为上一例的亮紫色。从上可看出,球漫反射光的结果是mat_diffuse[]与 light_diffuse[]中的三个颜色分量值相乘,即 (0.0*1.0,0.0*1.0,0.8*1.0,1.0*1.0)=(0.0,0.0,0.8,1.0),所以球大部分呈现蓝色。

图10-4 光照蓝色球高光为红色

  10.4.4 材质改变
  在实际应用的许多情况下,不同的物体或同一物体的不同部分都有可能设置不同的材质,OpenGL函数库提供了两种方式实现这种要求。下面一例程采用的是设置矩阵堆栈来保存不同物体的材质信息:

  例10-5 矩阵堆栈改变材质例程(chgmat1.c
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  #include "glos.h"  
  2.   #include <GL/gl.h>  
  3.   #include <GL/glu.h>  
  4.   #include <GL/glaux.h>  
  5.   
  6.   void myinit(void);  
  7.   void CALLBACK display(void);  
  8.   void CALLBACK myReshape(GLsizei w, GLsizei h);  
  9.   
  10.   /* 初始化z-buffer、光源和光照模型,在此不具体定义材质。*/  
  11.   void myinit(void)  
  12.   {  
  13.     GLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };  
  14.     GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };  
  15.     GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };  
  16.     GLfloat position[] = { 0.0, 3.0, 2.0, 0.0 };  
  17.     GLfloat lmodel_ambient[] = { 0.4, 0.4, 0.4, 1.0 };  
  18.   
  19.     glEnable(GL_DEPTH_TEST);  
  20.     glDepthFunc(GL_LESS);  
  21.   
  22.     glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);  
  23.     glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);  
  24.     glLightfv(GL_LIGHT0, GL_POSITION, position);  
  25.   
  26.     glEnable(GL_LIGHTING);  
  27.     glEnable(GL_LIGHT0);  
  28.   
  29.     glClearColor(0.0, 0.1, 0.1, 0.0);  
  30.   }  
  31.   
  32.   void CALLBACK display(void)  
  33.   {  
  34.     GLfloat no_mat[] = { 0.0, 0.0, 0.0, 1.0 };  
  35.     GLfloat mat_ambient[] = { 0.7, 0.7, 0.7, 1.0 };  
  36.     GLfloat mat_ambient_color[] = { 0.8, 0.8, 0.2, 1.0 };  
  37.     GLfloat mat_diffuse[] = { 0.1, 0.5, 0.8, 1.0 };  
  38.     GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };  
  39.     GLfloat no_shininess[] = { 0.0 };  
  40.     GLfloat low_shininess[] = { 5.0 };  
  41.     GLfloat high_shininess[] = { 100.0 };  
  42.     GLfloat mat_emission[] = {0.3, 0.2, 0.2, 0.0};  
  43.   
  44.     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
  45.   
  46.     /* 第一行第一列绘制的球仅有漫反射光而无环境光和镜面光。*/  
  47.     glPushMatrix();  
  48.     glTranslatef (-3.75, 3.0, 0.0);  
  49.     glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);  
  50.     glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  
  51.     glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);  
  52.     glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);  
  53.     glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);  
  54.     auxSolidSphere(1.0);  
  55.     glPopMatrix();  
  56.   
  57.     /* 第一行第二列绘制的球有漫反射光和镜面光,并有低高光,而无环境光 。*/  
  58.     glPushMatrix();  
  59.     glTranslatef (-1.25, 3.0, 0.0);  
  60.     glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);  
  61.     glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  
  62.     glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);  
  63.     glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);  
  64.     glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);  
  65.     auxSolidSphere(1.0);  
  66.     glPopMatrix();  
  67.   
  68.     /* 第一行第三列绘制的球有漫反射光和镜面光,并有很亮的高光,而无环境光 。*/  
  69.     glPushMatrix();  
  70.     glTranslatef (1.25, 3.0, 0.0);  
  71.     glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);  
  72.     glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  
  73.     glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);  
  74.     glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);  
  75.     glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);  
  76.     auxSolidSphere(1.0);  
  77.     glPopMatrix();  
  78.   
  79.     /* 第一行第四列绘制的球有漫反射光和辐射光,而无环境和镜面反射光。*/  
  80.     glPushMatrix();  
  81.     glTranslatef (3.75, 3.0, 0.0);  
  82.     glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);  
  83.     glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  
  84.     glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);  
  85.     glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);  
  86.     glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);  
  87.     auxSolidSphere(1.0);  
  88.     glPopMatrix();  
  89.   
  90.     /* 第二行第一列绘制的球有漫反射光和环境光,而镜面反射光。*/  
  91.     glPushMatrix();  
  92.     glTranslatef (-3.75, 0.0, 0.0);  
  93.     glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);  
  94.     glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  
  95.     glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);  
  96.     glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);  
  97.     glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);  
  98.     auxSolidSphere(1.0);  
  99.     glPopMatrix();  
  100.   
  101.     /* 第二行第二列绘制的球有漫反射光、环境光和镜面光,且有低高光。*/  
  102.     glPushMatrix();  
  103.     glTranslatef (-1.25, 0.0, 0.0);  
  104.     glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);  
  105.     glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  
  106.     glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);  
  107.     glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);  
  108.     glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);  
  109.     auxSolidSphere(1.0);  
  110.     glPopMatrix();  
  111.   
  112.     /* 第二行第三列绘制的球有漫反射光、环境光和镜面光,且有很亮的高光。*/  
  113.     glPushMatrix();  
  114.     glTranslatef (1.25, 0.0, 0.0);  
  115.     glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);  
  116.     glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  
  117.     glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);  
  118.     glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);  
  119.     glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);  
  120.     auxSolidSphere(1.0);  
  121.     glPopMatrix();  
  122.   
  123.     /* 第二行第四列绘制的球有漫反射光、环境光和辐射光,而无镜面光。*/  
  124.     glPushMatrix();  
  125.     glTranslatef (3.75, 0.0, 0.0);  
  126.     glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);  
  127.     glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  
  128.     glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);  
  129.     glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);  
  130.     glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);  
  131.     auxSolidSphere(1.0); glPopMatrix();  
  132.   
  133.     /* 第三行第一列绘制的球有漫反射光和有颜色的环境光,而无镜面光。*/  
  134.     glPushMatrix();  
  135.     glTranslatef (-3.75, -3.0, 0.0);  
  136.     glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);  
  137.     glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  
  138.     glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);  
  139.     glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);  
  140.     glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);  
  141.     auxSolidSphere(1.0);  
  142.     glPopMatrix();  
  143.   
  144.     /* 第三行第二列绘制的球有漫反射光和有颜色的环境光以及镜面光,且有低高光。*/  
  145.     glPushMatrix();  
  146.     glTranslatef (-1.25, -3.0, 0.0);  
  147.     glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);  
  148.     glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  
  149.     glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);  
  150.     glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);  
  151.     glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);  
  152.     auxSolidSphere(1.0);  
  153.     glPopMatrix();  
  154.   
  155.     /* 第三行第三列绘制的球有漫反射光和有颜色的环境光以及镜面光,且有很亮的高光。*/  
  156.     glPushMatrix();  
  157.     glTranslatef (1.25, -3.0, 0.0);  
  158.     glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);  
  159.     glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  
  160.     glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);  
  161.     glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);  
  162.     glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);  
  163.     auxSolidSphere(1.0);  
  164.     glPopMatrix();  
  165.   
  166.     /* 第三行第四列绘制的球有漫反射光和有颜色的环境光以及辐射光,而无镜面光。*/  
  167.     glPushMatrix();  
  168.     glTranslatef (3.75, -3.0, 0.0);  
  169.     glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);  
  170.     glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);  
  171.     glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);  
  172.     glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);  
  173.     glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);  
  174.     auxSolidSphere(1.0);  
  175.     glPopMatrix();  
  176.   
  177.     glFlush();  
  178.   }  
  179.   
  180.   void CALLBACK myReshape(GLsizei w, GLsizei h)  
  181.   {  
  182.     glViewport(0, 0, w, h);  
  183.     glMatrixMode(GL_PROJECTION);  
  184.     glLoadIdentity();  
  185.     if (w <= (h * 2))  
  186.       glOrtho (-6.0, 6.0, -3.0*((GLfloat)h*2)/(GLfloat)w,  
  187.         3.0*((GLfloat)h*2)/(GLfloat)w, -10.0, 10.0);  
  188.     else  
  189.       glOrtho (-6.0*(GLfloat)w/((GLfloat)h*2),  
  190.         6.0*(GLfloat)w/((GLfloat)h*2), -3.0, 3.0, -10.0, 10.0);  
  191.     glMatrixMode(GL_MODELVIEW);  
  192.   }  
  193.   
  194.   void main(void)  
  195.   {  
  196.     auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);  
  197.     auxInitPosition (0, 0, 600, 450);  
  198.     auxInitWindow ("Material");  
  199.     myinit();  
  200.     auxReshapeFunc (myReshape);  
  201.     auxMainLoop(display);  
  202.   }</span>  



图10-5 多种光和材质的变化效果

  以上程序运行结果是绘制12个球(3行4列)。第一行的球材质都没有环境反射光,第二行的都有一定的环境反射光,第三行的都有某种颜色的环境光。而第一列的球材质仅有蓝色的漫反射光;第二列的不仅有蓝漫反射光,而且还有镜面反射光,较低的高光;第三列的不仅有蓝漫反射光,而且还有镜面反射光,很亮的高光;第四列的还包括辐射光,但无镜面光。
  这个程序运用矩阵堆栈多次调用glMaterialfv()来设置每个球的材质,也就是改变同一场景中的不同物体的颜色。但由于这个函数的应用有个性能开销,因此建议最好尽可能少的改变材质,以减少改变材质时所带来的性能开销,可采用另一种方式即改变材质颜色,相应函数为glColorMaterial(),说明如下:

  void glColorMaterial(GLenum face,GLenum mode);

  函数参数face指定面,值有GL_FRONT、GL_BACK或GL_FRONT_AND_BACK(缺省值)。mode指定材质成分,值有 GL_AMBIENT、GL_DIFFUSE、GL_AMBIENT_AND_DIFFUSE(缺省值)、GL_SPECULAR或 GLEMISSION。
  注意:这个函数说明了两个独立的值,第一个参数说明哪一个面和哪些面被修改,而第二个参数说明这些面的哪一个或哪些材质成分要被修改。OpenGL并不为每一种face保持独立的mode变量。在调用glColorMterial() 以后,首先需要用GL_COLOR_MATERIAL作为参数调用glEnable()来启动颜色材质,然后在绘图时调用glColor*()来改变当前颜色,或用glMaterial()来改变材质成分。当不用这种方式来改变材质时,可调用glDisable(GL_COLOR_MATERIAL)来关闭取消。如下面一段代码:

  glColorMaterial(GL_FRONT,GL_DIFFUSE);
  glEnable(GL_COLOR_MATERIAL);
  glColor3f(0.3,0.5,0.7);
  /* draw some objects here. */
  glcolor3f(0.0,1.0,0.0);
  /* draw other objects here.*/
  glDisable(GL_COLOR_MATERIAL);

  当需要改变场景中大部分方面的单个材质时,最好调用glColorMaterial();当需要修改不止一个材质参数时,最好调用glMaterial*()。注意,当不需要颜色材质时一定要关闭它,以避免相应的开销。下面来看一个颜色材质的具体应用例子:

  例10-6 颜色定义改变材质例程(chgmat2.c
[cpp]  view plain  copy
  1. <span style="font-size:12px;">  #include "glos.h"   
  2.   #include <GL/gl.h>  
  3.   #include <GL/glu.h>  
  4.   #include <GL/glaux.h>  
  5.   
  6.   void myinit(void);  
  7.   void CALLBACK myReshape(GLsizei w, GLsizei h);  
  8.   void CALLBACK display(void);  
  9.   
  10.   void myinit(void)  
  11.   {  
  12.     GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };  
  13.   
  14.     glLightfv(GL_LIGHT0, GL_POSITION, light_position);  
  15.   
  16.     glEnable(GL_LIGHTING);  
  17.     glEnable(GL_LIGHT0);  
  18.     glDepthFunc(GL_LESS);  
  19.     glEnable(GL_DEPTH_TEST);  
  20.   
  21.     glColorMaterial(GL_FRONT, GL_DIFFUSE);  
  22.     glEnable(GL_COLOR_MATERIAL);  
  23.   }  
  24.   
  25.   void CALLBACK display(void)  
  26.   {  
  27.     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
  28.   
  29.     /* draw one yellow ball */  
  30.     glLoadIdentity();  
  31.     glTranslatef(-0.7,0.0,0.0);  
  32.     glColor3f(1.0,1.0,0.0);  
  33.     auxSolidSphere(0.5);  
  34.   
  35.     /* draw one red cone */  
  36.     glLoadIdentity();  
  37.     glRotatef(-65.0,1.0,0.0,0.0);  
  38.     glTranslatef(0.7,0.0,0.0);  
  39.     glColor3f(1.0,0.0,0.0);  
  40.     auxSolidCone(0.4,0.6);  
  41.   
  42.     glFlush();  
  43.   }  
  44.   
  45.   void CALLBACK myReshape(GLsizei w, GLsizei h)  
  46.   {  
  47.     glViewport(0, 0, w, h);  
  48.     glMatrixMode(GL_PROJECTION);  
  49.     glLoadIdentity();  
  50.     if (w <= h)  
  51.       glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);  
  52.     else  
  53.       glOrtho (-1.5*(GLfloat)w/(GLfloat)h, 1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);  
  54.     glMatrixMode(GL_MODELVIEW);  
  55.     glLoadIdentity();  
  56.   }  
  57.   
  58.   void main(void)  
  59.   {  
  60.     auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);  
  61.     auxInitPosition (0, 0, 500, 500);  
  62.     auxInitWindow ("ColorMaterial Mode");  
  63.     myinit();  
  64.     auxReshapeFunc (myReshape);  
  65.     auxMainLoop(display);  
  66.   }</span>  


  以上程序改变的是漫反射颜色。场景中显示了一个黄色的球和一个红色的锥体。

图10-6 漫反射材质改变



(4):我的解法

过程 :

    进行视角变换;

    进行整数化;

   进行邻域填补(也叫膨胀算法);

   进行正射投影;

   检索二维矩阵;

   逆向分析,根据相机参数和透视关系,还原单侧视图;


算法代码:


 未完待续............................


已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页