文件以二进制方式打开如果文件中存在二进制的0x00,就不能用fgetc()这样的函数去读取文件!fgetc只能处理文本的!同样的也不能用fputs()写二进制数据!只能用fread和fwrite来读文件和写文件!
你對这个回答的评价是
你对这个回答的评价是?
本课我们来谈谈如何显示文字
OpenGL並没有直接提供显示文字的功能,并且OpenGL也没有自带专门的字库。因此要显示文字,就必须依赖操作系统所提供的功能了
各种流行的圖形操作系统,例如Windows系统和Linux系统都提供了一些功能,以便能够在OpenGL程序中方便的显示文字
最常见的方法就是,我们给出一个字符给出┅个显示列表编号,然后操作系统由把绘制这个字符的OpenGL命令装到指定的显示列表中当需要绘制字符的时候,我们只需要调用这个显示列表即可
不过,Windows系统和Linux系统产生这个显示列表的方法是不同的(虽然大同小异)。作为我个人只在Windows系统中编程,没有使用Linux系统的相关經验所以本课我们仅针对Windows系统。
写完了本课我的感受是:显示文字很简单,显示文字很复杂看似简单的功能,背后却隐藏了深不可測的玄机 呵呵,别一开始就被吓住了让我们先从“Hello, World!”开始。 前面已经说过了要显示字符,就需要通过操作系统把绘制字符的动作裝到显示列表中,然后我们调用显示列表即可绘制字符 假如我们要显示的文字全部是ASCII字符,则总共只有0到127这128种可能因此可以预先把所囿的字符分别装到对应的显示列表中,然后在需要时调用这些显示列表 Windows系统中,可以使用wglUseFontBitmaps函数来批量的产生显示字符用的显示列表函數有四个参数: 第一个参数是HDC,学过Windows GDI的朋友应该会熟悉这个如果没有学过,那也没关系只要知道调用wglGetCurrentDC函数,就可以得到一个HDC了具体嘚情况可以看下面的代码。 第二个参数表示第一个要产生的字符因为我们要产生0到127的字符的显示列表,所以这里填0 第三个参数表示要產生字符的总个数,因为我们要产生0到127的字符的显示列表总共有128个字符,所以这里填128 第四个参数表示第一个字符所对应显示列表的编號。假如这里填1000则第一个字符的绘制命令将被装到第1000号显示列表,第二个字符的绘制命令将被装到第1001号显示列表依次类推。我们可以先用glGenLists申请128个连续的显示列表编号然后把第一个显示列表编号填在这里。 现在让我们来看具体的代码:
显示列表一旦产生就一直存在(除非调用glDeleteLists销毁)所以我们只需要在第一次调用的时候初始化,以后就可以很方便的调用这些显示列表来绘制字符了 |
在产生显示列表前,Windows允许选择字体 我做了一个selectFont函数来实现它,大家可鉯看看代码
最主要的部分就在于那个参数超多的CreateFont函数,学过Windows GDI的朋友应该不会陌生没有学过GDI的朋友,有兴趣的话可以自己翻翻MSDN文档这裏我并不准备仔细讲这些参数了,下面的内容还多着呢:( |
原则上显示中文和显示英文并无不同,同样是把要显示的字符做成显示列表然后进行调用。 但是有一个问题英文字母很少,最多只有几百个为每个字母创建一个显示列表,没有问题但是汉字有非常多個,如果每个汉字都产生一个显示列表这是不切实际的。 我们不能在初始化时就为每个字符建立一个显示列表那就只有在每次绘制字苻时创建它了。当我们需要绘制一个字符时创建对应的显示列表,等绘制完毕后再将它销毁。 这里还经常涉及到中文乱码的问题我對这个问题也不甚了解,但是网上流传的版本中使用了MultiByteToWideChar这个函数的,基本上都没有出现乱码所以我也准备用这个函数:) 通常我们在C语言②进制里面使用的字符串,如果中英文混合的话例如“this is 中文字符.”,则英文字符只占用一个字节而中文字符则占用两个字节。用MultiByteToWideChar函数可以转化为所有的字符都占两个字节(同时解决了前面所说的乱码问题:))。 // 如果是双字节字符的(比如中文字符)两个字节才算一个芓符 // 否则一个字节算一个字符 加上前面所讲到的wglUseFontBitmaps函数,即可显示中文字符了 |
把文字放到纹理中有很多好处,例如可以任意修改字符的夶小(而不必重新指定字体)。 对一面飘动的旗帜使用带有文字的纹理则文字也会随着飘动。这个技术在“三国志”系列游戏中经常用箌比如关羽的部队,旗帜上就飘着个“关”字张飞的部队,旗帜上就飘着个“张”字曹操的大营,旗帜上就飘着个“曹”字三国囚物何其多,不可能为每种姓氏都单独制作一面旗帜纹理如果能够把文字放到纹理上,则可以解决这个问题(参见后面的例子:绘制┅面“曹”字旗) 如何把文字放到纹理中呢?自然的想法就是:“如果前面所用的显示列表可以直接往纹理里面绘制,那就好了”不過,“绘制到纹理”这种技术要涉及的内容可不少足够我们专门拿一课的篇幅来讲解了。这里我们不是直接绘制到纹理而是用简单一點的办法:先把汉字绘制出来,成为像素然后用glCopyTexImage2D把像素复制为纹理。 glCopyTexImage2D与glTexImage2D的用法是类似的(参见第11课)不过前者是直接把绘制好的像素複制到纹理中,后者是从内存传送数据到纹理中要使用到的代码大致如下: 然后,我们就可以像使用普通的纹理一样来做了绘制各种粅体时,指定合适的纹理坐标即可
有一个细节问题需要特别注意。大家看上面的代码指定文字显示的位置,写的是glRasterPos2f(XXX, XXX);这里来讲讲如何计算这个显示坐标
所以,需要把绘制的位置往上移一点具体来说就是移动下面一栏的高度。这个高度是哆少像素呢这个我也不知道有什么好办法来计算,根据我的经验移动整个字符高度的八分之一是比较合适的。例如字符大小为24则移動3个像素。 |
为了方便我使用了glWindowPos2iARB这个扩展函数来指定绘制的位置。如果某个系统中OpenGL没囿支持这个扩展则需要使用较多的代码来实现类似的功能。为了方便的调用这个扩展我使用了GLEE。详细的情形可以看本教程第十四课朂后的那一个例子。GL_ARB_window_pos扩展在OpenGL
1.3版本中已经成为标准的一部分而几乎所有现在还能用的显卡在正确安装驱动后都至少支持OpenGL 1.4,所以不必担心不支持的问题 另外,占用的空间也是需要考虑的问题通常,我们的纹理都是用GL_RGBA格式OpenGL会保存纹理中每个像素的红、绿、蓝、alpha四个值,通瑺一个像素就需要16或32个二进制位才能保存,也就是2个字节或者4个字节才保存一个像素我们的字符只有“绘制”和“不绘制”两种状态,因此一个二进制位就足够了前面用16个或32个,浪费了大量的空间缓解的办法就是使用GL_LUMINANCE4这种格式,它不单独保存红、绿、蓝颜色而是紦这三种颜色合起来称为“亮度”,纹理中只保存这种亮度一个像素只用四个二进制位保存亮度,比原来的16个、32个要节省不少注意这種格式不会保存alpha值,如果要从纹理中取alpha值的话总是返回1.0。 |
应用纹理字体的实例:飘动的旗帜
(提示:这一段需要一些数学知识)有了纹悝只要我们绘制一个正方形,适当的设置纹理坐标就可以轻松的显示纹理图象了(参见第十一课),因为这里纹理图象实际上就是字苻所以我们也就显示出了字符。并且随着正方形大小的变化,字符的大小也会随着变化 直接贴上纹理,太简单了现在我们来点挑戰性的:画一个飘动的曹操军旗帜。效果如下图很酷吧?呵呵 效果是不错,不过它也不是那么容易完成的接下来我们一点一点的讲解。
为了完成上面的效果我们需要具备以下的知识: |
因為要使用光照,法线向量是不可少的这里我们通过不共线的三个点来得到三个点所在平面的法线向量。 从数学的角度看原理很简单。彡个点v1, v2, v3可以用v2减v1,v3减v1得到从v1到v2和从v1到v3的向量s1和s2。然后向量s1和s2进行叉乘得到垂直于s1和s2所在平面的向量,即法线向量 为了方便使用,應该把法线向量缩放至单位长度这个也很简单,计算向量的模然后向量的每个分量都除以这个模即可。 |
好的飘动的旗帜已经做好,現在来看最后的步骤将纹理贴到旗帜上。
细心的朋友可能会想到这样一个问题:明明绘制文字的时候使用的是白色放到纹理中也是白銫,那个“曹”字是如何显示为黄色的呢
这就要说到纹理的使用方法了。大家在看了第十一课“纹理的使用入门”以后难免认为纹理僦是用一幅图片上的像素颜色来替换原来的颜色。其实这只是纹理最简单的一种用法它还可以有其它更复杂但是实用的用法。
这里我们必须提到一个函数:glTexEnv*从OpenGL 1.0到OpenGL 1.5,每个OpenGL版本都对这个函数进行了修改如今它的功能已经变的非常强大(但同时也非常复杂,如果要全部讲解只怕又要花费一整课的篇幅了)。
它指定纹理的使用方式为“代替”即用纹理中的颜色代替原来的颜色。
我们这里使用另一种用法:
其中第二行指定纹理的使用方式为“混合”它与OpenGL的混合功能类似,但源因子和目标因子是固定的无法手工指定。最终产生的颜色为:紋理的颜色*常量颜色 + (1.0-纹理颜色)*原来的颜色常量颜色是由第三行代码指定为黄色。
因为我们的纹理里面装的是文字只有黑、白两种颜色。如果纹理中某个位置是黑色套用上面的公式,发现结果就是原来的颜色没有变化;如果纹理中某个位置是白色,套用上面的公式發现结果就是常量颜色。所以文字的颜色就由常量颜色决定。我们指定常量颜色也就指定了文字的颜色。
主要的知识就是这些了结匼前面课程讲过的视图变换(设置观察点)、光照(设置光源、材质),以及动画飘动的旗帜就算制作完成。
呵呵代码已经比较庞大叻,限于篇幅完整的版本这里就不发上来了,不过附件里面有一份源代码flag.c
走出做完旗帜的喜悦后,让我们回到二维文字的问题上来 湔面说到因为汉字的数目众多,无法在初始化时就为每个汉字都产生一个显示列表不过,如果每次显示汉字时都重新产生显示列表效率上也说不过去。一个好的办法就是把经常使用的汉字的显示列表保存起来,当需要显示汉字时如果这个汉字的显示列表已经保存,則不再需要重新产生如果有很多的汉字都需要产生显示列表,占用容量过多则删除一部分最近没有使用的显示列表,以便释放出一些涳间来容纳新的显示列表 学过操作系统原理的朋友应该想起来了,没错这与内存置换的算法是一样的。内存速度快但是容量小硬盘(虚拟内存)速度慢但是容量大,需要找到一种机制使性能尽可能的达到最高。这就是内存置换算法 常见的内存置换算法有好几种,這里我们选择一种简单的那就是随机选择一个显示列表并且删除,空出一个位置用来装新的显示列表 还要说一下,我们不再直接用显礻列表来显示汉字了改用纹理。因为纹理更加灵活而且根据实验,纹理比显示列表更快一个显示列表只能保存一个字符,但是纹理呮要足够大则可以保存很多的字符。假设字符的高度是32则宽度不超过32,如果纹理是256*256的话就可以保存8行8列,总共64个汉字 1. 缓冲机制的初始化 3. 根据一个文字字符,返回对应的纹理坐标如果字符本身不在纹理中,则应该先把字符加入到纹理中(如果纹理已经装不下了则先删除一个),然后返回纹理坐标 要改进缓冲机制的性能,则应该使用更高效的置换算法不过这个已经远超出OpenGL的范围了。大家如果有涳也可以看看linux源码什么的应该会找到好的置换算法。 即使我们使用最简单的置换算法完整的代码仍然有将近200行,其实这些都是算法基夲功了跟OpenGL关系并不太大。仍然是由于篇幅限制仅在附件中给出,就不贴在这里了文件名为ctbuf.h和ctbuf.c,在使用的时候把这两个文件都加入到笁程中并调用ctbuf.h中声明的函数即可。 这里我们仅仅给出调用部分的代码 注意这里我们是用纹理来实现字符显示的,因此文字的大小会随著窗口大小而变化最初的Hello, World程序就不会有这样的效果,因为它的字体硬性的规定了大小不如纹理来得灵活。 |
有了缓冲机制显示文字的速度会比没有缓冲时快很多,这样我们也可以考虑显示大段的文字了
基本上,前面的ctbuf_drawString函数已经可以快速的显示一个较长的字符串但是咜有两个缺点。
第一个缺点是不会换行一直横向显示到底。
第二个缺点是即使字符在屏幕以外也会尝试在缓冲中查找这个字符,如果沒找到还会重新生成这个字符。
让我们先来看看第一个问题换行。所谓换行其实就是把光标移动到下一行的开头如果知道每一行开頭的位置的话,只需要很短的代码就可以实现
不过,OpenGL显示文字的时候并不会保存每一行开头的位置所以这个需要我们自己动手来做。
苐二个问题是关于性能的如果字符本身不会显示出来,那么为它产生显示列表和纹理就是一种浪费如果为了容纳它的显示列表或者纹悝,而把缓冲区中其它有用的字符的显示列表或者纹理给删除了那就更加得不偿失。
所以判断字符是否会显示也是很重要的。像我们嘚浏览器如果显示一个巨大的网页,其实它也只绘制最必要的部分
为了解决上面两个问题,我们再单独的编写一个模块初始化的时候指定显示区域的大小、每行多少个字符、每列多少个字符,在模块内部判断是否需要换行以及判断每个文字是否真的需要显示。
呃尛小的感慨一下,为什么每当我做好一份代码就发现它实在太长,长到我不想贴出来呢唉……
注意观察就可以发现,歌词分为多行呮有必要的行才会显示,不会从头到尾的显示出来
代码中主要是算法和C语言二进制基本功,跟OpenGL关系并不大还是照旧,把主要的代码放箌附件里文件名为textarea.h和textarea.c,使用时要与前面的ctbuf.h和ctbuf.c一起使用
这里仅给出调用部分的代码。
其实上面我们所讲那么多呮讲了一类字体,即像素字体此外还有轮廓字体。所以这个看似已经很长的课程,其实只讲了“显示文字”这个课题的一半估计大镓已经看不下去了,其实我也写不下去了好长…… 那么,本课就到这里吧有种虎头蛇尾的感觉:( |
本课的内容不可谓不多。列表如下: |
版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。