<?xml version="1.0" standalone="yes"?>
<?xml-stylesheet type="text/xsl" href="css/rss.xslt"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>ZwqXin - OpenGL技术</title><link>http://www.zwqxin.com/</link><description>    一起学习OPENGL吧 - </description><generator>RainbowSoft Studio Z-Blog 1.8 Arwen Build 90619</generator><language>zh-CN</language><copyright>Copyright 2008-2010 ZwqXin. Some Rights Reserved. Theme edited from ipati. </copyright><pubDate>Fri, 10 Sep 2010 13:08:18 +0800</pubDate><item><title>软阴影的实现尝试Ⅱ</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html</link><pubDate>Sat, 13 Feb 2010 10:29:39 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html</guid><description><![CDATA[<p>软阴影与反锯齿有着深刻的关系，PCF在Graphics上原本就是一种反锯齿技术，而它在阴影算法上往往作为简捷的软阴影达成途径。VSM（Variance Shadow Map）同样旨在解决这一问题，但它并非像以往一样对阴影图的后处理，而是重新定义阴影图。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p><em><strong>1. 锯齿与反锯齿</strong></em></p><p>锯齿感是在Casting的时候产生的。我们确实是相当于把一张阴影图（深度图）贴在场景上了，但具体的操作依然是一条深度比较指令。譬如，传统的SM中，根据场景物体像素在光源视觉下的深度得到的阴影图，在Casting阶段对应相机视觉中的像素判断是否属于阴影图中的像素&mdash;&mdash;这本来就是一个TRUE OR FALSE的判断，若TRUE证明非阴影，像素颜色值不变；若FALSE则涂黑，以表明该处阴影。</p><p style="text-align: center">NewValue = OldValue * (isShadow? 0 : 1)</p><p>即使把0替换成shadowMapValue或是运用距离值判断，也改变不了这里的根源：把视场景分割成阴影区和非阴影区。双方强烈的撕扯总会造成霹雳的裂痕。我们一般使用的纹理在贴在奇形怪状的表面上时产生的扭曲同样会导致锯齿，可为什么却不那么难接受呢？原因是filtering。初学纹理贴图的时候，一定会接触到Texture Filtering，尤其在发生欠采样和过采样的时候，纹理映射的策略。在OpenGL 1.x中，GL_LINEAR和GL_NEAREST应该是最为熟悉的，尤其是线性插值GL_LINEAR，让我们贴纹理的时候能得到比较好的性效比：</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span>glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT,&nbsp;GL_TEXTURE_MIN_FILTER,&nbsp;GL_LINEAR); </span></span></li>    <li><span>glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT,&nbsp;GL_TEXTURE_MAG_FILTER,&nbsp;GL_LINEAR);</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>    glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR);    glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>贴纹理过程的采样能通过简单的线性插值完成，再把插出来的值映射到恰当的位置去&mdash;&mdash;想想对于深度图这会是什么情况：低分辨率的阴影图上一个4X4格子，对应场景中9X9大小区域，恰好区域覆盖阴影边界，深度插值后某像素获得了表示&ldquo;半影&rdquo;的灰度数值。但是要&ldquo;贴&rdquo;到场景时，需要经过一个TRUE OR FALSE过滤器，这个&ldquo;半影&rdquo;像素依然要选择：自己究竟属于影子，还是非影子。一旦整个边界的像素选择完成，由于阴影判断式对所有像素等效，它们会发现自己&ldquo;整齐&rdquo;地划出了一条边界线，锯齿边界线。</p><p>那么，拯救这条边界线最直接的方法也可看出：让这些&ldquo;半影&rdquo;像素随机地选择自己的所属。这样就会产生不整齐、不和谐&mdash;&mdash;这正是半影所需要的。在GPU GEMS 2里就介绍了这样一种做法，效果应该会很好关键是效率，3D纹理的使用就不是那么让人愿意接近了（不过以后我或许要尝试一下哈）。那么，还有别的拯救方法吗？</p><p><em><strong>2. Variance Shadow Map</strong></em></p><p>VSM（Variance Shadow Map）的提出正基于此。其实它本质也是一种基于概率的方法，把以上的TRUE OR FALSE过滤器过程改作一个可见性估计过程&hellip;&hellip;嘛，你也知道概率学上的东西有很多微妙的地方&hellip;&hellip;让texture filtering作用于每个像素的方差（Variance）而非其本身的值，于此建立的切比雪夫不等式作为这个可见性估计式。<a href="http://www.zwqxin.com/">ZwqXin</a>本身概率论学得不深，为了避免误人子弟，这许概率数学原理我就PASS了。</p><p style="text-align: center"><img alt="" src="http://http.developer.nvidia.com/GPUGems3/elementLinks/f162equ03.jpg" /></p><p>每个像素经过这样折腾一下，取切比雪夫不等式的上限（UpperBound，即上式p<sub>max</sub>(t)）为结果，它大概揭示了当前像素属于&ldquo;非阴影区&rdquo;的程度（当这个UpperBound越接近0时代表此像素越可能&ldquo;属于阴影&rdquo;）。UpperBound计算式的三个参数，t是像素的实际深度（根据GPU GEMS3，这里取的是像素距离光源的距离distToLight），&mu;是当前像素深度的期望E(x)（取的是直接在阴影图上采样得到的[插值后的]深度，好吧，囧），&sigma;<sup>2</sup>是方差(&sigma;<sup>2</sup> = E(x<sup>2</sup>)- [E(x)]<sup>2</sup>)。</p><p>首先查查什么是&ldquo;切比雪夫不等式&rdquo;（ Chebyshev's inequality ）。是的，上过概率课，所以至少听过这个名词（听说，那是因为期末考后潜意识对自身的遗忘魔法）：in any data sample or <font color="#002bb8">probability distribution</font>, &quot;nearly all&quot; the values are &quot;close to&quot; the <font color="#002bb8">mean value</font> &mdash; the precise statement being that no more than 1/<i>k</i><sup>2</sup> of the distribution's values can be more than <i>k</i> <font color="#002bb8">standard deviations</font>away from the mean. [WIKI]。</p><p>上面式子应该是另一种表达形式吧（吧？）。在我看来，切比雪夫不等式的上限是关于 t的函数式，描述了一个具体的量t在随机集合X上的位置的概率范围。Casting过程中，一个像素往深度图Shadow Map上采样，姑且认为这是一个随机选择的过程；像素若是根据其纹理坐标（光源投影矩阵合理所得）进行的采样，纹理上应该只有唯一的一个点与之对应&mdash;&mdash;当然，即使取隔壁的点也不是不可，不过它作为准确值的概率没有那个&ldquo;唯一的点&rdquo;大&mdash;&mdash;把可能取的值作为集合X，那X就是一个以&ldquo;唯一的点&rdquo;为中心的圆，使该点作为集合的期望值&mu;。（期望的计算式子E(X) = x<sub>1 </sub>* p(x<sub>1</sub>) + x<sub>2</sub> * p(x<sub>2</sub>) + &hellip;&hellip;，稍微考虑一下就明白）。问题是t，它作为实际值，在我们的纹理采样过程中是不可能出现的，甚至我觉得它根本无法得出，为什么取&ldquo;像素距离光源的距离distToLight&rdquo;呢？它的计算不存在于纹理采样过程中，但也不可能就说它就是&ldquo;真实值&rdquo;啊&mdash;&mdash;哪怕表义如此。对此，可看看GPU GEMS3 8.1中作者的解释，不过我是没有太理解而已（尽管没有更好的）。</p><p>于是，既然摆脱的TRUE OR FALSE的抑制，我们完全可以动用texture filtering去取那个&ldquo;唯一的点&rdquo;&mdash;&mdash;期望值E(x)]了。这不，为了计算方差，我们还需要一个E(x<sup>2</sup>)&mdash;&mdash;类似地，我们只需要在ShadowMap Generating阶段把像素深度值的平方也保存起来就可以了&mdash;&mdash;这就是Variance Shadow Map，需要一张纹理的两个通道，分别存储深度和深度平方。CASTING阶段把各值传入切比雪夫不等式计算上限值&mdash;&mdash;这不是确切的&ldquo;属于非阴影区的概率&rdquo;，但它是唯一明确能得到的值，这个不等式误差是VSM致命弱点的根源。这个上限值概率作为&ldquo;阴影因子&rdquo;乘在场景像素上，造出了肉眼可见的&ldquo;半影&rdquo;区，同时大大地增强了阴影的边缘的轮廓感（难看的锯齿减少）。</p><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html"><img height="431" alt="http://www.zwqxin.com  Soft Shadow" width="463" src="http://lh3.ggpht.com/_lYWT-2PnV0s/S3bJhumSkOI/AAAAAAAABOw/AsjYWJ85w_w/s800/Snap320100213.jpg" /></a><br />（CSM一般效果，没加PCF模糊，注意看的是阴影外形）<br /><a target="_self" href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html"><img alt="http://www.zwqxin.com  Soft Shadow" src="http://lh5.ggpht.com/_lYWT-2PnV0s/S3bJhNka80I/AAAAAAAABOs/SL9qiEjE2_c/s800/Snap120100213.jpg" /></a><br />(对比。CSM+VSM 可见没有了突兀的轮廓，filter的效果显著)</p><p><br /><a target="_self" href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html"><img style="width: 262px; height: 417px" height="424" alt="http://www.zwqxin.com  Soft Shadow" width="272" src="http://lh5.ggpht.com/_lYWT-2PnV0s/S3bJidvd9oI/AAAAAAAABO0/k-T3VyIZo7g/s512/Snap520100213.jpg" /></a><a target="_self" href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html"><img style="width: 272px; height: 415px" height="480" alt="http://www.zwqxin.com  Soft Shadow" width="215" src="http://lh3.ggpht.com/_lYWT-2PnV0s/S3bJin6-55I/AAAAAAAABO4/f85xYfkYrPk/s512/Snap220100213.jpg" /></a><br />(对比。没有VSM效果时，远观时(splitNum = 4)轮廓已经很糟糕了，但VSM下能显示完好轮廓)</p><p>&nbsp;</p>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=85</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=85&amp;key=e174063a</trackback:ping></item><item><title>软阴影的实现尝试Ⅰ</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html</link><pubDate>Thu, 11 Feb 2010 10:51:26 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html</guid><description><![CDATA[<p>软阴影（Soft - Shadow），并非一种图形学算法或技术的代名词。怎么说呢，它是图形学大师们孜孜不倦地追求的更逼近真实的阴影 效果 。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>想起来，第一次接触阴影算法，是一年前这个博客刚刚成立不久的时候吧。阴影锥，阴影帖图，还有那个宝贵的大三寒假。好了，停止无缘由的感伤，给自己编一个可以重新站起来的理由？</p><p>有一点很不能原谅自己的是，这篇小文，本应该是去年10月发出的&hellip;&hellip;</p><p>在已有的阴影DEMO上作改进，是当时很自然的想法。而选择了[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/shadow-map-demo-2.html">Shadow Map Demo2</a>] ，不知道，算不算是一个失策。Cascaded ShadowMap技术，从本质上说，只是分区间地<strong>调整投影矩阵</strong>，根据视距离使用不同分辨率的阴影图，促使效率与效果平衡而已。这是Shadow Map改进算法的一条路，但并非唯一。回想[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/shadow-map-3.html">Shadow Map阴影贴图技术之探Ⅲ</a>] ，里面我简单地用乒乓式的采样，实现了一下3X3的阴影图模糊&mdash;&mdash;PCF。虽然结果看上去很那个，但这昭示着Shadow Map改进算法的另一条路&mdash;&mdash;图形学中的图像处理，<strong>调整阴影图</strong>。</p><p>在我的上一个阴影DEMO中，主要的shader只有一个，它是在Shadow - Casting阶段完成场景阴影图的分层次贴附。在CPU上完成的是ShadowMap - Generating阶段，因为那里没有涉及像素层面，完全不需要GPU的并行计算能力。但如果要对一张贴图的内容进行像素级的处理&hellip;&hellip;恩，这是通常的想法。但当时脑中另一个突然一闪的想法，让我决定先行对其进行捕获&mdash;&mdash;场景后处理。</p><p>一般说的DefferedShading并不等同于这个意义，但就字面来说它们也有那么一点共通。恩，题外话先免了，先暗自打个小算盘：</p><p>场景后处理，我是想在最终画面上动手脚。在OpenGL流水线的尾部，无论是否双缓冲，必然是把显存上每个像素格的值传向显示器屏幕上的每个&ldquo;真&middot;像素&rdquo;上，表示成颜色（譬如LCD是用彩色滤光片等结构对此转换的，可以理解为每个像素上又有红绿蓝三色子像素balabala&hellip;&hellip;嘛，其实我也不怎么理解啊这些硬件的- -，呃离题了，回来~）。如果把这个覆盖屏幕的&ldquo;结果&rdquo;当作贴在一个Screen-Aligned-Quad（屏幕大矩形）的一张纹理，我们在<strong>显存-屏幕</strong>这个过程之前进行拦截，获得并处理这个&ldquo;纹理&rdquo;后再让它传向屏幕。</p><p>有这方面基础的朋友都知道，FBO又该出来表演了[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/learn-fbo.html">学一学，FBO</a>] 。把Shadow - Casting的结果写入FBO绑定的一个纹理中，然后自己画一个Screen-Aligned-Quad，贴这个纹理&mdash;&mdash;对这个过程启用shader处理。最后虽然屏幕上本质只有这么一个矩形，但是众多本该直接映射在屏幕上的场景元素已经完美地&ldquo;被代表&rdquo;了。看上去丝毫没有河蟹爬过的痕迹&hellip;&hellip;完全没有。</p><p>依然是模糊处理。高斯模糊。</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="comment">// </span></span></li>    <li><span>uniform&nbsp;</span><span class="keyword">int</span><span>&nbsp;BSceneWidth; </span></li>    <li class="alt"><span>uniform&nbsp;</span><span class="keyword">int</span><span>&nbsp;BSceneHeight; </span></li>    <li><span>uniform&nbsp;</span><span class="keyword">float</span><span>&nbsp;ishoriz; </span></li>    <li class="alt">&nbsp;</li>    <li><span>uniform&nbsp;</span><span class="keyword">int</span><span>&nbsp;samplecount; </span></li>    <li class="alt"><span>uniform&nbsp;</span><span class="keyword">float</span><span>&nbsp;weights; </span></li>    <li><span>uniform&nbsp;</span><span class="keyword">float</span><span>&nbsp;sigma; </span></li>    <li class="alt">&nbsp;</li>    <li><span>uniform&nbsp;sampler2D&nbsp;&nbsp;BScenemap; </span></li>    <li class="alt">&nbsp;</li>    <li><span class="keyword">void</span><span>&nbsp;main() </span></li>    <li class="alt"><span>{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;gl_FragData[0]&nbsp;=&nbsp;BlurFilter(BScenemap,&nbsp;gl_TexCoord[0].xy)&nbsp;&nbsp;; </span></li>    <li class="alt"><span>} </span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//uniform int BSceneWidth;uniform int BSceneHeight;uniform float ishoriz;uniform int samplecount;uniform float weights;uniform float sigma;uniform sampler2D  BScenemap;void main(){    gl_FragData[0] = BlurFilter(BScenemap, gl_TexCoord[0].xy)  ;}</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>其中BlurFilter函数是对当前像素，在水平或垂直方向上采样（samplecount），采样值乘以一个高斯分布值而已（公式也就网上常见的，sigma为参数），跟PCF也就差不多那回事。很明显，我们需要在水平方向和垂直方向都模糊一次，这里用2个PASS。我们只需要模糊阴影，因此把阴影部分和场景部分分开写入纹理(MRT)，最后再统一。</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span>&nbsp;</span><span class="comment">//&nbsp;CastingShader,&nbsp;render&nbsp;to&nbsp;two&nbsp;textures&nbsp;&nbsp;(MRT) </span></span></li>    <li><span>gl_FragData[0]&nbsp;=&nbsp;vec4(gl_Color.rgb&nbsp;*&nbsp;diffuse.rgb&nbsp;&nbsp;*&nbsp;texColor.rgb,&nbsp;1.0); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li><span>gl_FragData[1]&nbsp;=&nbsp;shadeFactor;</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre> // CastingShader, render to two textures  (MRT)gl_FragData[0] = vec4(gl_Color.rgb * diffuse.rgb  * texColor.rgb, 1.0);   gl_FragData[1] = shadeFactor;</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="comment">//PostProcessingShader&nbsp;,&nbsp;as&nbsp;a&nbsp;texture&nbsp;to&nbsp;the&nbsp;screen-aligned&nbsp;quad </span></span></li>    <li><span>uniform&nbsp;sampler2D&nbsp;&nbsp;Scenemap; </span></li>    <li class="alt"><span>uniform&nbsp;sampler2D&nbsp;&nbsp;Shademap; </span></li>    <li>&nbsp;</li>    <li class="alt"><span class="keyword">void</span><span>&nbsp;main() </span></li>    <li><span>{&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;vec4&nbsp;shadeFact&nbsp;=&nbsp;texture2D(Shademap,&nbsp;gl_TexCoord[1].xy);&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;gl_FragColor&nbsp;=&nbsp;texture2D(Scenemap,&nbsp;gl_TexCoord[0].xy)&nbsp;*&nbsp;shadeFact; </span></li>    <li class="alt"><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//PostProcessingShader , as a texture to the screen-aligned quaduniform sampler2D  Scenemap;uniform sampler2D  Shademap;void main(){       vec4 shadeFact = texture2D(Shademap, gl_TexCoord[1].xy);    gl_FragColor = texture2D(Scenemap, gl_TexCoord[0].xy) * shadeFact;}</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>写到FBO纹理后，屏幕矩形绑定之：</p><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html"><img alt="http://www.zwqxin.com  Soft Shadow" src="http://lh4.ggpht.com/_lYWT-2PnV0s/S3PExu-eYCI/AAAAAAAABL8/jnnQjEDcNMg/s800/20091011Snap1-1.jpg" /></a></p><p>软是软了，就边界效果而言是很好的（这里图片压缩了看上去糟糕而已）但有几个问题：1.很明显的边界问题，阴影与场景分离了，SampleNum越大分离得越明显；2..随着SampleNum增加帧率狂降。效果和效率都不行。</p><p>首先，这种高采样带宽的东西本来就是帧率杀手，更不用说高斯分布权数的计算实在够呛。其次因为场景和阴影分离，而模糊的只是对于相机&ldquo;可见&rdquo;的那部分阴影，所以图中间隔线是活生生被当作阴影边界了。</p><p>后来也考虑过再加入图像处理中的&ldquo;膨胀&rdquo;运算[<a target="_blank" href="http://www.zwqxin.com/archives/image-processing/morphologic-process.html">形态学运算小结</a>] ，但是有点吃力不讨好的感觉，无论膨胀多少次，还是能依稀见到狭缝，加上效率问题，宣告此法的破产。</p><p><br /><a target="_self" href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html"><img style="width: 267px; height: 203px" height="199" alt="http://www.zwqxin.com  Soft Shadow" width="266" src="http://lh5.ggpht.com/_lYWT-2PnV0s/S3PEy1sikDI/AAAAAAAABME/d-oMYIy9wDY/s800/32p4p4pass1Snap091015-1.jpg" /></a><a target="_self" href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html"><img style="width: 267px; height: 207px" height="248" alt="http://www.zwqxin.com  Soft Shadow" width="313" src="http://lh4.ggpht.com/_lYWT-2PnV0s/S3PEzo7WcKI/AAAAAAAABMI/DKdSGkPFzdQ/s800/32p2p2pass1Snap091015-1.jpg" /></a><br /><a target="_self" href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html"><img height="203" alt="http://www.zwqxin.com  Soft Shadow" width="267" src="http://lh3.ggpht.com/_lYWT-2PnV0s/S3PE0dsmZQI/AAAAAAAABMM/HDmKqdwpCwg/s800/32p2p2pass2Snap091015-1.jpg" /></a><a target="_self" href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html"><img height="203" alt="http://www.zwqxin.com  Soft Shadow" width="267" src="http://lh6.ggpht.com/_lYWT-2PnV0s/S3PE0wOvFHI/AAAAAAAABMQ/5ldFeIWtxMw/s800/64p2p2pass1Snap091015-1.jpg" /></a><br /><font color="#0000ff"><font color="#000000"><font color="#0000ff"><font color="#000000">左-右，上-下依次 32p4p4pass1</font>, </font>32p2p2pass1, 32p2p2pass2, 64p2p2pass1（自己猜，我什么都忘记了哈）</font></font></p><p>&nbsp;</p><p>还是好好用别的方法吧~<br /><a target="_blank" href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html">软阴影的实现尝试Ⅱ</a></p>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=84</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=84&amp;key=d993ddcb</trackback:ping></item><item><title>SwapBuffers的等待，虚伪的FPS</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/swapbuffers-fps-vsync.html</link><pubDate>Fri, 04 Sep 2009 19:47:14 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/swapbuffers-fps-vsync.html</guid><description><![CDATA[<p>&nbsp;FPS在实时渲染中扮演着一个重要的角色，也许你会去笑一个不懂FPS是什么的游戏新手，但也许，这只是五十步笑一百步罢了。你能读懂SwapBuffers的深情等待吗？&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p><a name="ghr"></a>frames per second（FPS, 帧率），作为渲染效率的一种衡量，反映的是整个程序在当前的一个渲染状态下平均每秒所能容纳的&ldquo;渲染循环&rdquo;执行次数，也表征了平均每个&ldquo;渲染循环&rdquo;（帧）所用的时间。就表面来看，很多人觉得只要在一个每帧运行一次的函数内设置一个计数器，这样计算出一秒内的记数，便可作为该1s瞬间的FPS了；同样也很容易能求出平均FPS。</p><p>我一直都是这么做的。但是我一直以来有个难解的地方：为什么我的程序的FPS基本不会大于75呢？恩，以前做的DEMO，如果是有计算FPS的，都是这样：场景复杂的时候它可以降低到1或者直接0掉，但是对于简单的场景它永远只有72左右的FPS；再简单，也是72左右；再再简单，还是72左右&hellip;&hellip;可以说是&ldquo;封顶&rdquo;了。今天我（终于？）想要弄清楚是什么原因了，有以下可能：1.所用的程序框架的某处给它限FPS了；2.初始化过程中沾染了些什么会约束渲染效率的东西；3.渲染循环体内有一个&ldquo;每个程序都会用到的函数&rdquo;是&ldquo;每个程序的瓶颈&rdquo;，4.FPS计算的算法有问题；5.其他（啥？）。</p><p>于是我把整个渲染流程核查一次，先把消息处理和循环部分弄成一般WIN32的形式，貌似无关&hellip;&hellip;然后又检查像素格式，直接把最佳像素格式设置为10，（某些程序DEMO中设计者会轮巡所有像素格式找出最佳匹配的，于是我也容易知道对我最&ldquo;友好&rdquo;的像素格式是10号&hellip;&hellip;）貌似也没啥。另外，检查FPS计算的式子，没发现异常。我又把渲染函数RenderGLScene内的东西一个个注释&hellip;&hellip;才发现，只有当我注释了SwapBuffers时，屏幕显示死掉了，但FPS上到了3000以上&hellip;&hellip;对比72的FPS我还是比较相信这个&mdash;&mdash;所以，是双缓冲的关系么。在像素设置中改<a target="_blank" href="http://www.zwqxin.com/archives/MFC/3-nehe-frame-vc6.html.html">像素描述器</a>的PFD_DOUBLEBUFFER为PFD_SWAP_COPY、PFD_SWAP_EXCHANGE之类的，这下FPS也是很高，但是屏幕猛闪~ - - 。google吧，步小心发现了一个东西：vsync。诶？甘熟噶？</p><p>在学Irrlicht引擎的时候，建立DEVICE的时候有一个参数选项：vsync，<strong>vertical syncronisation</strong>，API解释得不清楚，就知道跟屏幕有关&hellip;&hellip;屏幕？屏幕刷新率？我突然想起我做图像处理的WIN32程序时的一个窘况：release出来的程序在人家面前演示，发现图片/视频出不来，后来明白是屏幕刷新率的问题&mdash;&mdash;屏幕刷新率太高会把WIN32循环对屏幕窗口所做的东西&ldquo;消灭&rdquo;掉。那么，这里呢？好，我去看文章了：<strong><a target="_blank" href="http://steinsoft.net/index.php?site=Programming/Code%20Snippets/OpenGL/no7&amp;p">vertical syncronisation</a>。</strong></p><p>&nbsp;垂直同步。原来，程序每一帧的函数全部执行完之后，还要等屏幕刷新了才去执行下一帧啊！一查屏幕刷新率&mdash;&mdash;难怪是72左右呀，正因为偶电脑上设置的屏幕刷新率是72！恩，这里头肯定有个像sleep那样有等待功能的函数做帮凶&mdash;&mdash;噢，亲爱的SwapBuffers，是你呀~</p><p>曾经有人说SwapBuffers比glut库的glutSwapBuffers效率低不少（见<a target="_blank" href="http://www.cnblogs.com/cgwolver/archive/2009/04/01/1427317.html">这里</a>），但这里不是这个问题。无论是SwapBuffers还是glutSwapBuffers还是wglSwapBuffers，它们位于渲染体的末端，都有这么个&ldquo;功能&rdquo;：当显卡的垂直同步功能vsyn被置为TRUE时，每一帧渲染完后来到SwapBuffers，都要悲情地等待下一次屏幕刷新的时刻到来，再交换前后缓冲并开始执行下一帧&hellip;&hellip;上面那篇文章用到了WGL_EXT_swap_control扩展，利用wglSwapIntervalEXT来设置&ldquo;等待的间隔&rdquo;。间隔是指一个屏幕刷新的周期，譬如屏幕刷新率是72Hz，那么一个周期就是（1/72）s， wglSwapIntervalEXT（1）表明要让SwapBuffers类函数开始执行后要等到下一个屏幕刷新时才返回&mdash;&mdash;然后继续下一帧的执行。wglSwapIntervalEXT（2）就是等下个再下个屏幕刷新了。这么说，wglSwapIntervalEXT（0），哈，就是不用等&mdash;&mdash;关闭垂直同步Vsync。</p><p>这里安插一个问题，那么如果程序完成一帧本来就可能大于这个屏幕刷新间隔（1/72s）呢？譬如设开始时刻屏幕刚刷新而某帧也同时开始，均设为0，那么程序的函数集将在第一次刷新之后才完结，进入SwapBuffers。如果没理解错，按照该扩展的spec[<a target="_blank" href="http://www.opengl.org/registry/specs/EXT/wgl_swap_control.txt">EXT_swap_control</a>]所说的话，之后这个SwapBuffers要一直等待到第二次刷新才返回并交换前后缓冲，开始第二帧&mdash;&mdash;即相当于一帧的&ldquo;理论的完结&rdquo;需要两个刷新周期（2/72s = 1/36s），就是说一帧用时稍微比屏幕刷新周期长一点点，也会导致FPS减小一半&hellip;&hellip;而实际上这种不连续性在我以前的&ldquo;低帧率&rdquo;程序中没有体现过。究竟是怎样的呢？留个疑问。</p><p>在我的程序中应用此拓展，在程序初始化阶段就关闭Vsync。哈，很高的FPS。文章<strong><a target="_blank" href="http://steinsoft.net/index.php?site=Programming/Code%20Snippets/OpenGL/no7&amp;p">vertical syncronisation</a></strong>末尾也提供了该作者写的helper类，很容易理解和应用。好了，这样&hellip;&hellip;等待的结束？还没呢&mdash;&mdash;虚伪的FPS。</p><p>是的，这个FPS很虚伪。不是说它太高了（因为我本身对这种大数值的没概念），而是它本身就不是FPS，它不符合我们所想的FPS概念（<a href="#ghr">本文开始处</a>）。更确切点，以上所说的&ldquo;帧&rdquo;的概念与FPS中的帧的概念不一样。</p><p>我们调用函数控制GPU做事，并不是CPU上这种步步执行-返回-下句的形式。我们是按顺序把函数一个一个（作为地址）输送到显卡某个缓存区内，积累一定量后再让显卡按此顺序执行。所以，<strong>代码的执行完成并不代表显卡上相应的硬件功能执行完成</strong>。（其实要是显卡看一个执行一个，流水线不就坏掉了么。）也就这个原因，可以认为两者是不同步的，有延误等等。那么之前讨论中提及的&ldquo;帧&rdquo;就是前者的完成时间，而我们最想要的应该是后者的完成时间&mdash;&mdash;包括CPU向GPU传递函数所用时间和GPU上实际执行函数功能的用时，可以想象两者应该有很大部分的用时是互相重合的，且后者无论如何还是会在函数全部传递完成后仍然需要额外时间来完成。这部分额外时间取决于之前传入的是哪类函数、GPU具体执行的是什么功能&mdash;&mdash;如果直接关掉垂直同步Vsync得出FPS，这个FPS就没有包含这些额外的处理时间，仅仅最多能表示两个U之间数据传输的速度。</p><p>所以要让FPS反映真实的<span style="color: #ff0000"><strong>每帧</strong></span>&mdash;&mdash;包括数据传输和实际执行、绘图&mdash;&mdash;的用时的话，不能用此法。在<a target="_blank" href="http://www.programmer-club.com.tw/ShowSameTitleN/opengl/1324.html">glut 教學 - 計算 frame rate 的正確方法 &mdash;&mdash; 程序设计俱乐部</a>一文中，作者<a onclick="JavaScript:OpenWindowx('/member2007.asp?uid=ma_hty'); return false" none="" href="http://www.programmer-club.com.tw/ShowSameTitleN/opengl/1324.html#"><!--<font color="#D80404">-->ma_hty(白老鼠(Gary))</a>提供了一种方法，在计算帧率时，用glFinish代替SwapBuffers。它没有SwapBuffers那种等待的耐心，强制告诉程序和GPU：要结束了，快把剩余的所有函数指令执行完，然后换下一帧。这样的一帧正是&ldquo;帧&rdquo;的正确含义。但是这样将失去双缓冲，屏幕是不会正常显示绘制的东西的，所以不可能用做实时的FPS计算，只用于某时刻用一下，指示当前的<strong>真实FPS</strong>。</p>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/swapbuffers-fps-vsync.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=73</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=73&amp;key=c991c7fe</trackback:ping></item><item><title>水效果Ⅲ - 抖动波</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/water-simulation-3.html</link><pubDate>Sun, 30 Aug 2009 15:39:30 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/water-simulation-3.html</guid><description><![CDATA[<p>这个名字是我私取的。所谓抖动波，是通过在某点&ldquo;人为地&rdquo;搅动而产生向四周传播波，如同握绳子某点摇动而产生的物理波。回顾其实现方式，以及把算法移植到shader，让GPU高速运算。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>&nbsp;[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/water-simulation-1.html">水效果Ⅰ - 水池</a>]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [<a target="_blank" href="http://www.zwqxin.com/archives/opengl/water-simulation-2.html">水效果Ⅱ - 涟漪</a>]</p><p>涟漪在物理意义上也就是这么一个抖动波，但是在上篇[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/water-simulation-2.html">水效果Ⅱ - 涟漪</a>]中，涟漪的实现不是采用波的传播方式，而是采用一种类似电场般迅速扑开的&ldquo;场&rdquo;，把整个水面纳入计算，根据距波源的距离、时间等模拟出某一时刻的顶点高度。所以说，它体现出传播的特点其实只是波源公式里各个衰减因子的功效而已。对于抖动波，我们就&ldquo;无法&rdquo;控制每个顶点的高度，在给予了最初的一个抖动后就应该&ldquo;任由其发展&rdquo;。说是最初的抖动，其实不过给予波源点一个向上或向下的速度罢了。假设执行波的传播计算的是RenderWave这么一个每帧执行的函数，它的功能就是在网格中带动一个连锁反应：波源点的上升会带动它所连接的四个相邻点上升，然后这四个相邻点的每一个又带动它的四个相邻点（包括带动它的波源点）&hellip;&hellip;这样就把波传递下去了（传到网格边缘则从对边继续）。之后，再保证波源点具有向上速度的同时具有向下的加速度（即每帧速度都在减小），一个物理波的传播模型便形成了。波源在速度变为0的时候到达最高点并在反向速度的拉力下回落，继续扯动相邻点&hellip;&hellip;</p><p>实现的算法的获得应该追溯到一年前，课程三大DEMO之一的完善需要一种简单的海水运动模拟，于是上网找算法找水运动的DEMO，并找到了一个日本人写的这个抖动波算法实现。比较单纯的正弦余弦波结合的方法，这种抖动波能产生更加让人信服的缓波浪。现在再次运用这种算法，本是希望模拟池水轻微的表面浮动，但是结果不理想，一来要等到整个&ldquo;造浪&rdquo;过程到达平缓阶段需要好一段时间，如上所述，抖动传播的过程是无法人为干扰的，改变传播速度也没办法，且最初波源中心的突兀而起是很显然的；二来因为没有一个精确的物理式子描述，所以在大波浪运动之下，组成的每个顶点相连并不光滑，造成很多小触点，尽管在海水中这种触点能恰好加强了波的细节描述，但在池水这种水面一般需要很很光滑的场合这样就不好了。</p><p>在给出代码之前说一下，如果目的是造成网格各处起伏的波浪的话，波源最好不要位于网格中心，或者网格不要是个正方形。因为波到达边缘要回弹或者从对边继续开始同向拂来，如果网格和波源位置过于&ldquo;工整&rdquo;则只会造成波波抵消之类的效果，最初给的抖动（或者说，能量）没办法分散，所以始终会表现为一种重复的运动&mdash;&mdash;波源引导最大振幅的波动。</p><p>以下代码是我对原始代码简化后的版本。在DEMO中为了简捷我还是取规正网格和中心波源，没所谓了 - -</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="comment">//波动开始的时刻 </span></span></li>    <li><span>mCurrentWaveDelta[0]&nbsp;=&nbsp;2.0；</span><span class="comment">//假设0索引是波源点，设定初速 </span></li>    <li class="alt">&nbsp;</li>    <li><span class="comment">//渲染过程： </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">for</span><span>(z&nbsp;=&nbsp;1;&nbsp;z&nbsp;&lt;&nbsp;mGridScaleZ&nbsp;-&nbsp;1;&nbsp;++z) </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">for</span><span>(x&nbsp;=&nbsp;1;&nbsp;x&nbsp;&lt;&nbsp;mGridScaleX&nbsp;-&nbsp;1;&nbsp;++x) </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;factor.</span><span class="keyword">set</span><span>(0.0,&nbsp;0.0,&nbsp;0.0); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;index&nbsp;=&nbsp;x&nbsp;+&nbsp;z&nbsp;*&nbsp;mGridScaleX; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;deltaVec&nbsp;=&nbsp;mOldCurrentOriGrid[index]&nbsp;-&nbsp;mOldCurrentOriGrid[index&nbsp;+&nbsp;1]; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vecDis&nbsp;=&nbsp;deltaVec.abs()&nbsp;*&nbsp;&nbsp;NormalLengthPerGridFactor; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;factor&nbsp;+=&nbsp;deltaVec.normalize()&nbsp;*&nbsp;(1.0&nbsp;-&nbsp;vecDis); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;deltaVec&nbsp;=&nbsp;mOldCurrentOriGrid[index]&nbsp;-&nbsp;mOldCurrentOriGrid[index&nbsp;-&nbsp;1]; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vecDis&nbsp;=&nbsp;deltaVec.abs()&nbsp;*&nbsp;&nbsp;NormalLengthPerGridFactor; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;factor&nbsp;+=&nbsp;deltaVec.normalize()&nbsp;*&nbsp;(1.0&nbsp;-&nbsp;vecDis); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;deltaVec&nbsp;=&nbsp;mOldCurrentOriGrid[index]&nbsp;-&nbsp;mOldCurrentOriGrid[index&nbsp;+&nbsp;mGridScaleX]; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vecDis&nbsp;=&nbsp;deltaVec.abs()&nbsp;*&nbsp;&nbsp;NormalLengthPerGridFactor; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;factor&nbsp;+=&nbsp;deltaVec.normalize()&nbsp;*&nbsp;(1.0&nbsp;-&nbsp;vecDis); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;deltaVec&nbsp;=&nbsp;mOldCurrentOriGrid[index]&nbsp;-&nbsp;mOldCurrentOriGrid[index&nbsp;-&nbsp;mGridScaleX]; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vecDis&nbsp;=&nbsp;deltaVec.abs()&nbsp;*&nbsp;&nbsp;NormalLengthPerGridFactor; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;factor&nbsp;+=&nbsp;deltaVec.normalize()&nbsp;*&nbsp;(1.0&nbsp;-&nbsp;vecDis); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mCurrentWaveDelta[index]&nbsp;+=&nbsp;factor&nbsp;*&nbsp;0.2f&nbsp;*&nbsp;velocity; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mCurrentOriGrid[index]&nbsp;+=&nbsp;mCurrentWaveDelta[index]; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//波动开始的时刻mCurrentWaveDelta[0] = 2.0；//假设0索引是波源点，设定初速//渲染过程：	 for(z = 1; z &lt; mGridScaleZ - 1; ++z)	  for(x = 1; x &lt; mGridScaleX - 1; ++x)	  {		  factor.set(0.0, 0.0, 0.0);          index = x + z * mGridScaleX;		  deltaVec = mOldCurrentOriGrid[index] - mOldCurrentOriGrid[index + 1];		  vecDis = deltaVec.abs() *  NormalLengthPerGridFactor;		  factor += deltaVec.normalize() * (1.0 - vecDis);		  deltaVec = mOldCurrentOriGrid[index] - mOldCurrentOriGrid[index - 1];		  vecDis = deltaVec.abs() *  NormalLengthPerGridFactor;		  factor += deltaVec.normalize() * (1.0 - vecDis);		  deltaVec = mOldCurrentOriGrid[index] - mOldCurrentOriGrid[index + mGridScaleX];		  vecDis = deltaVec.abs() *  NormalLengthPerGridFactor;		  factor += deltaVec.normalize() * (1.0 - vecDis);		  deltaVec = mOldCurrentOriGrid[index] - mOldCurrentOriGrid[index - mGridScaleX];		  vecDis = deltaVec.abs() *  NormalLengthPerGridFactor;		  factor += deltaVec.normalize() * (1.0 - vecDis);		  mCurrentWaveDelta[index] += factor * 0.2f * velocity;	      mCurrentOriGrid[index] += mCurrentWaveDelta[index];	  }</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>mOldCurrentOriGrid是用于计算的当前时刻的网格高度，deltaVec是相邻网格顶点之间的向量；NormalLengthPerGridFactor是一小网格的边长之倒数，是为了规范化vecDis到一个与1靠近的值；factor收集了四相邻点的扯动效果，影响该点速度（mCurrentWaveDelta）方向，是正扯反扯就看vecDis与1.0比的大小以及deltaVec方向&mdash;&mdash;看官可以回忆高中学的机械波传播过程，速度，加速度，位置变化等信息。事实上我觉得自己还是不怎么能完全理解的，所以解释的话就如此从简了。</p><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/opengl/water-simulation-3.html"><img alt="http://ww.zwqxin.com 水效" src="http://lh3.ggpht.com/_lYWT-2PnV0s/SpLLPQxvvSI/AAAAAAAAA30/x-wMrqH6aR0/s800/rtuu824.jpg" /></a><br /><a target="_self" href="http://www.zwqxin.com/archives/opengl/water-simulation-3.html"><img alt="http://ww.zwqxin.com 水效" src="http://lh5.ggpht.com/_lYWT-2PnV0s/SpLLP4tXfZI/AAAAAAAAA34/8w2T--KO8O4/s800/tyyy824.jpg" /></a><br /><a target="_self" href="http://www.zwqxin.com/archives/opengl/water-simulation-3.html"><img alt="http://ww.zwqxin.com 水效" src="http://lh5.ggpht.com/_lYWT-2PnV0s/SpLLQSfZjQI/AAAAAAAAA38/FYMlUfoDZ8o/s800/jgjgj824.jpg" /></a></p><p>由截图可见波是从波源一直传播开来并扩散的，最后达到算是比较和缓的状态。这就是CPU上实现的抖动波。</p><p>接下来是GPU上实现的抖动波。整整折磨了我暑假里不止一个星期啊。</p><p>把算法搬到shader上，但是这里每个顶点都要知道相邻点的情况，所以不能交给并行处理顶点的vertex shader来做。方法是：</p><p>1.独立设一个shader对象，把全部顶点打包成数组（也就是纹理，见[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/glsl-vertex-texture-fetch.html">Vertex Texture Fetch 顶点纹理拾取</a>] ，每个纹素包含一个顶点信息，如果有N*N个顶点，就弄一张N*N大小的纹理好了），传入该fragment shader；</p><p>2.把上面的算法应用到这张纹理上（这是难点，就是这里搞死我的就是这里！），注意纹理可以随便检索，因此通过纹理坐标的偏移可以获得相邻像素里的对应的顶点信息，结果（代表每个顶点的位置）作为gl_FragData渲染到应用程序的一张空纹理上（FBO的应用，见[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/learn-fbo.html">学一学，FBO</a>] ）；</p><p>3.最后是处理网格顶点的shader对象的vertex shader对此处理过的纹理进行VTF（顶点纹理获取，同见[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/glsl-vertex-texture-fetch.html">Vertex Texture Fetch 顶点纹理拾取</a>] ），把得到的顶点位置数据作为对应顶点的结果。</p><p>当然，编程细节可多了。不可能一一在此阐述，但大体思路就是这样。作为效果演示，以下DEMO的抖动波效果是夸张了点，但是它能让你感性了解什么是抖动波。（众：喂，这个名词不是你自己乱取的咩~）</p><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/opengl/water-simulation-3.html"><img alt="http://ww.zwqxin.com 水效" src="http://lh6.ggpht.com/_lYWT-2PnV0s/SpLahfYS5aI/AAAAAAAAA4c/mecn7okM1Ew/s800/eerwr824.jpg" /></a><br /><a target="_self" href="http://www.zwqxin.com/archives/opengl/water-simulation-3.html"><img alt="http://ww.zwqxin.com 水效" src="http://lh3.ggpht.com/_lYWT-2PnV0s/SpLahEDA2CI/AAAAAAAAA4Y/ZYKIb48xA3k/s800/yrwr824.jpg" /></a><br />(截图太囧，还是动态的好~)</p><p>下篇预告：[<a target="_self" href="http://www.zwqxin.com/archives/opengl/water-simulation-4.html">水效果Ⅳ - GPU水面波</a>]</p><p>DEMO提示：可按提示文字操作，右键是涟漪效果（基于CPU计算的）[注意别连续按]；D键是抖动波演示[注意别多按]。</p><p>本DEMO用了不少OpenGL技术和GPU技术，所以对显卡的要求颇高的，(另外，ATI显卡目前连VTF都不一定支持，汗~)我只能保证NV 9 series以上大概能正常观看了。</p><p>下载点My Google Code - Downloads:&nbsp;&nbsp; 1. Water-Simulation-<b style="color: black; background-color: #ffff66">抖动波</b><br /><a target="_blank" href="http://code.google.com/p/zwqxindemos/downloads/list">http://code.google.com/p/zwqxindemos/downloads/list</a></p>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/water-simulation-3.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=72</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=72&amp;key=b1ddeff4</trackback:ping></item><item><title>索引顶点的VBO与多重纹理下的VBO</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/indexed-vbo-and-multitex-vbo.html</link><pubDate>Tue, 25 Aug 2009 09:46:00 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/indexed-vbo-and-multitex-vbo.html</guid><description><![CDATA[<p>VBO的核心是顶点数组的使用，因此顶点索引数组和多重顶点纹理坐标数组的设置才是本文的核心。当然还有的是，它们在VBO下是什么样子的。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.xom</p><p>紧接上篇：[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/learn-vbo.html">学一学，VBO</a>]</p><p>1.索引顶点的VBO</p><p>在VA（顶点数组）下，索引数据被放入一个名为索引数组的&ldquo;容器&rdquo;里，在绘制的时候跟其他顶点属性一样，要先用glEnableClientState(GL_INDEX_ARRAY)，再用glIndexPointer指定该容器&hellip;&hellip;这一切在VBO中都是不需要的。因为它与glDrawElements函数必定一起出现，所以VBO通过同一标志位GL_ELEMENT_ARRAY_BUFFER把它们的关系&ldquo;确定&rdquo;下来。只需要创建一个储存索引数据索引的VBO，渲染时绑定，调用glDrawElements时，第二参数是索引数据的大小，足矣。</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="comment">//初始化部分 </span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glGenBuffers(1,&nbsp;&amp;mIndexBufferObject); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,&nbsp;mIndexBufferObject); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBufferData(GL_ELEMENT_ARRAY_BUFFER,&nbsp;IndexDataSize,&nbsp;IndexData,&nbsp;GL_STREAM_DRAW); </span></li>    <li class="alt">&nbsp;</li>    <li><span class="comment">//渲染部分 </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(GL_ARRAY_BUFFER,&nbsp;mVertexBufferObject); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glEnableClientState(GL_VERTEX_ARRAY); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glVertexPointer(3,&nbsp;GL_FLOAT,&nbsp;0,&nbsp;0); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,&nbsp;mIndexBufferObject); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glDrawElements(GL_QUADS,&nbsp;IndexSize,&nbsp;GL_UNSIGNED_INT,&nbsp;0); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisableClientState(GL_VERTEX_ARRAY); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,&nbsp;0);</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//初始化部分	glGenBuffers(1, &amp;mIndexBufferObject);	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBufferObject);	glBufferData(GL_ELEMENT_ARRAY_BUFFER, IndexDataSize, IndexData, GL_STREAM_DRAW);//渲染部分	glBindBuffer(GL_ARRAY_BUFFER, mVertexBufferObject);	glEnableClientState(GL_VERTEX_ARRAY);	glVertexPointer(3, GL_FLOAT, 0, 0);	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBufferObject);	glDrawElements(GL_QUADS, IndexSize, GL_UNSIGNED_INT, 0);	glDisableClientState(GL_VERTEX_ARRAY);	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>现在我要用VBO技术画一个网格。网格看起来是这样的：</p><p style="text-align: center"><img alt="www.zwqxin.com   VBO索引" src="http://lh3.ggpht.com/_lYWT-2PnV0s/SpH8wyJ-38I/AAAAAAAAA24/QAM3NASfTK4/s800/ryryry.JPG" /></p><p>其中1，2，3，4......是放入顶点数组（GL_VERTEX_ARRAY）里的点的顺序。因为这种存放方式（行扫描）最方便。但是绘制的时候不能按这种顺序绘制（即不能直接用glDrawArrays），应该是一小块一小快矩形来画：1-8-9-2，2-9-10-3，3-10-11-4......6-13-14-7，8-15-16-9........而这个就是所谓的顶点索引，它决定了调用顶点的先后次序，取出顶点数据中相应索引的数据作画。结合顶点位置数据VBO的glVertexPointer对数据的解析（3个FLOAT数据为一组决定一个顶点），以及glDrawElements对数据的解析（4个顶点一组成为QUAD），整个流程就是这样了。注意索引的数据格式是GL_UNSIGNED_INT，我没尝试过其他（听说是不行的）。</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt">&nbsp;</li>    <li><span class="comment">//初始化时，设定索引数据的过程 </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">int</span><span>&nbsp;IndexSize&nbsp;=&nbsp;(ZSCALE&nbsp;-&nbsp;1)&nbsp;*&nbsp;(XSCALE&nbsp;-&nbsp;1)&nbsp;*&nbsp;4; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;GLsizeiptr&nbsp;IndexDataSize&nbsp;=&nbsp;IndexSize&nbsp;*&nbsp;</span><span class="keyword">sizeof</span><span>(GLuint); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;GLuint&nbsp;*IndexData&nbsp;=&nbsp;</span><span class="keyword">new</span><span>&nbsp;GLuint[IndexSize]; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;i&nbsp;=&nbsp;0; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">for</span><span>(z&nbsp;=&nbsp;0;&nbsp;z&nbsp;&lt;&nbsp;ZSCALE&nbsp;-&nbsp;1;&nbsp;++z)&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">for</span><span>(x&nbsp;=&nbsp;0;&nbsp;x&nbsp;&lt;&nbsp;XSCALE&nbsp;-&nbsp;1;&nbsp;++x) </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;index&nbsp;=&nbsp;x&nbsp;+&nbsp;z&nbsp;*&nbsp;XSCALE; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IndexData[i]&nbsp;&nbsp;&nbsp;=&nbsp;index; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IndexData[i+1]&nbsp;=&nbsp;index&nbsp;+&nbsp;XSCALE; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IndexData[i+2]&nbsp;=&nbsp;index&nbsp;+&nbsp;1&nbsp;+&nbsp;XSCALE; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IndexData[i+3]&nbsp;=&nbsp;index&nbsp;+&nbsp;1; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i&nbsp;+=&nbsp;4; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//初始化时，设定索引数据的过程    int IndexSize = (ZSCALE - 1) * (XSCALE - 1) * 4;    GLsizeiptr IndexDataSize = IndexSize * sizeof(GLuint);	GLuint *IndexData = new GLuint[IndexSize];	i = 0;	for(z = 0; z &lt; ZSCALE - 1; ++z) 	  for(x = 0; x &lt; XSCALE - 1; ++x)	  {		index = x + z * XSCALE;		IndexData[i]   = index;		IndexData[i+1] = index + XSCALE;		IndexData[i+2] = index + 1 + XSCALE;		IndexData[i+3] = index + 1;		i += 4;	  }</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/opengl/indexed-vbo-and-multitex-vbo.html"><img alt="VBO WWW.ZWQXIN.COM " src="http://lh3.ggpht.com/_lYWT-2PnV0s/SpJYl1ylETI/AAAAAAAAA3U/w1Ixi373jJI/s512/gjgjgj824.jpg" /></a></p><p>2.多重纹理下的VBO</p><p>其实多重纹理的VBO不是很复杂，但是网上介绍VBO的文章很少涉及这方面，加上网上外国论坛的帖子里的解释内容又不清不楚，所以我还是靠自己不断尝试，不断调整语句的顺序&mdash;&mdash;花了差不多一晚才弄清楚的。</p><p>初始化的时候其实与其他顶点属性是一样的，也没必要设多个VBO，一个就可以了：</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="comment">// </span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;GLsizeiptr&nbsp;TexCoordDataSize&nbsp;=&nbsp;XSCALE&nbsp;*&nbsp;ZSCALE&nbsp;*&nbsp;</span><span class="keyword">sizeof</span><span>(TexCoord); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glGenBuffers(1,&nbsp;&amp;mTexCoordBufferObject); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(GL_ARRAY_BUFFER,&nbsp;mTexCoordBufferObject); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glBufferData(GL_ARRAY_BUFFER,&nbsp;TexCoordDataSize,&nbsp;Watertex,&nbsp;GL_STREAM_DRAW); </span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//   GLsizeiptr TexCoordDataSize = XSCALE * ZSCALE * sizeof(TexCoord);	glGenBuffers(1, &amp;mTexCoordBufferObject);	glBindBuffer(GL_ARRAY_BUFFER, mTexCoordBufferObject);	glBufferData(GL_ARRAY_BUFFER, TexCoordDataSize, Watertex, GL_STREAM_DRAW);</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>渲染的时候，注意，以前的glActiveTexture不要扔掉！不要。glEnable(GL_TEXTURE_2D)也不要扔掉。我们弄的是纹理坐标而不是纹理。前者可以作为客户端状态后者不能[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/learn-vbo.html">学一学，VBO</a>] ，我们还是需要通知服务端启用纹理与否的。</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="comment">// </span></span></li>    <li><span>&nbsp;&nbsp;glActiveTexture(GL_TEXTURE0); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glEnable(GL_TEXTURE_2D); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindTexture(GL_TEXTURE_2D,&nbsp;Tex0); </span></li>    <li class="alt">&nbsp;</li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glActiveTexture(GL_TEXTURE1); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glEnable(GL_TEXTURE_2D); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindTexture(GL_TEXTURE_2D,&nbsp;Tex1); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glActiveTexture(GL_TEXTURE2); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glEnable(GL_TEXTURE_2D); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindTexture(GL_TEXTURE_2D,&nbsp;Tex2); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>.......... </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(GL_ARRAY_BUFFER,&nbsp;mTexCoordBufferObject); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glClientActiveTexture(GL_TEXTURE0); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glEnableClientState(GL_TEXTURE_COORD_ARRAY); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glTexCoordPointer(2,&nbsp;GL_FLOAT,&nbsp;0,&nbsp;0); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glClientActiveTexture(GL_TEXTURE1); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glEnableClientState(GL_TEXTURE_COORD_ARRAY); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glTexCoordPointer(2,&nbsp;GL_FLOAT,&nbsp;0,&nbsp;0); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glClientActiveTexture(GL_TEXTURE2); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glEnableClientState(GL_TEXTURE_COORD_ARRAY); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glTexCoordPointer(2,&nbsp;GL_FLOAT,&nbsp;0,&nbsp;0); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;.............. </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glDarwArrays&nbsp;/&nbsp;glDrawElements </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;.............. </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glClientActiveTexture(GL_TEXTURE0); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisableClientState(GL_TEXTURE_COORD_ARRAY); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glClientActiveTexture(GL_TEXTURE1); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisableClientState(GL_TEXTURE_COORD_ARRAY); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glClientActiveTexture(GL_TEXTURE2); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisableClientState(GL_TEXTURE_COORD_ARRAY); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glActiveTexture(GL_TEXTURE2); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisable(GL_TEXTURE_2D); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glActiveTexture(GL_TEXTURE1); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisable(GL_TEXTURE_2D); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glActiveTexture(GL_TEXTURE0); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisable(GL_TEXTURE_2D);</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//  glActiveTexture(GL_TEXTURE0);	glEnable(GL_TEXTURE_2D);    glBindTexture(GL_TEXTURE_2D, Tex0);    glActiveTexture(GL_TEXTURE1);	glEnable(GL_TEXTURE_2D);    glBindTexture(GL_TEXTURE_2D, Tex1);    glActiveTexture(GL_TEXTURE2);	glEnable(GL_TEXTURE_2D);    glBindTexture(GL_TEXTURE_2D, Tex2);..........	glBindBuffer(GL_ARRAY_BUFFER, mTexCoordBufferObject);	glClientActiveTexture(GL_TEXTURE0);	glEnableClientState(GL_TEXTURE_COORD_ARRAY);	glTexCoordPointer(2, GL_FLOAT, 0, 0);	glClientActiveTexture(GL_TEXTURE1);	glEnableClientState(GL_TEXTURE_COORD_ARRAY);	glTexCoordPointer(2, GL_FLOAT, 0, 0);	glClientActiveTexture(GL_TEXTURE2);	glEnableClientState(GL_TEXTURE_COORD_ARRAY);	glTexCoordPointer(2, GL_FLOAT, 0, 0);    ..............                    glDarwArrays / glDrawElements    ..............	glClientActiveTexture(GL_TEXTURE0);	glDisableClientState(GL_TEXTURE_COORD_ARRAY);	glClientActiveTexture(GL_TEXTURE1);	glDisableClientState(GL_TEXTURE_COORD_ARRAY);	glClientActiveTexture(GL_TEXTURE2);	glDisableClientState(GL_TEXTURE_COORD_ARRAY);    glActiveTexture(GL_TEXTURE2);	glDisable(GL_TEXTURE_2D);	glActiveTexture(GL_TEXTURE1);	glDisable(GL_TEXTURE_2D);	glActiveTexture(GL_TEXTURE0);	glDisable(GL_TEXTURE_2D);</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>多重纹理坐标VBO的整个过程跟多重纹理的设置过程是差不多的。但它体现起来是更改客户端状态（虽然VBO不经过客户端作二次传送，但是因为是继承VA.....）。glClientActiveTexture和glActiveTexture类似，都是用来指定当前使用的纹理句柄的，之后是glEnableClientState进行激活动作，再用glTexCoordPointer指定VBO内的数据的格式。再让阁下注意，这都是针对纹理坐标VBO的。</p><p>3.Interleaved arrays &amp; Serialized arrays</p><p>参考此文<a target="_blank" href="http://www.ozone3d.net/tutorials/opengl_vbo_p2.php">OpenGL Vertex Buffer Objects -&nbsp; PRACTICE</a>。</p><p>Interleaved arrays可以进一步减少函数调用的次数，主要是减少VBO的数量&hellip;&hellip;但总觉得这样有点矫枉过正了。</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="comment">// </span></span></li>    <li><span>#pragma&nbsp;pack(push,&nbsp;1) </span></li>    <li class="alt"><span class="keyword">struct</span><span>&nbsp;SVertex </span></li>    <li><span>{ </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;GLubyte&nbsp;r; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;GLubyte&nbsp;g; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;GLubyte&nbsp;b; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;GLfloat&nbsp;x; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;GLfloat&nbsp;y; </span></li>    <li><span>}; </span></li>    <li class="alt"><span>#pragma&nbsp;pack(pop) </span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//#pragma pack(push, 1)struct SVertex{    GLubyte r;    GLubyte g;    GLubyte b;    GLfloat x;    GLfloat y;};#pragma pack(pop)</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>譬如这样把两类数据装一起，就只要一个VBO来存储数据就可了。但渲染时glXXXPointer还得设置stride间距值（如果用glInterleavedArrays反而更不好，参考文说的）&hellip;&hellip;。另外pragma&nbsp;pack可以切除编译器４字节对宽的优化，不过不给优化不就＂减速＂了嘛&hellip;&hellip;</p><p>参考文也这么认为。所以倡议同样减少函数调用次数，减少VBO个数的Serialized arrays。其实就是把两种或多种顶点属性的数据前后拼一起放入一个VBO里而已。然后更新的时候用glBufferSubData[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/learn-vbo.html">学一学，VBO</a>] 只更新数据里头需要被更新的那一段。glBufferSubData第二个参数是OFFSET了。确实这样比Interleaved arrays的做法好不少。</p>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/indexed-vbo-and-multitex-vbo.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=71</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=71&amp;key=4f82bd35</trackback:ping></item><item><title>学一学，VBO</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/learn-vbo.html</link><pubDate>Sun, 23 Aug 2009 22:33:18 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/learn-vbo.html</guid><description><![CDATA[<p>&nbsp;VBO，全称Vertex Buffer Object，与FBO，PBO并称，但它实际上老不少。就某种意义来说，它就是VA（Vertex Array）的升级版。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>事实上，对VBO的接触可以追溯到当初接触FBO之后[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/learn-fbo.html">学一学，FBO</a>] 。那时候还在做Shadow Volume吧，见识到FBO的强大之后，就想把VBO也学了&mdash;&mdash;于是不觉得有什么难理解和难应用的地方，就把构造Volume（多侧面锥体）的任务交给它了&mdash;&mdash;谁知道（应该说，想当然知道），失败了。结果大概忘记了，反正不会不糟糕。因为想把时间继续放在Shadow Volume算法学习上[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/shadow-volume-5.html">Shadow Volume 阴影锥技术之探Ⅴ</a>] ，就放弃使用VBO了。 而如今再次拿起来，想学的成分和不甘心的成分都有吧，用于最近做的水效DEMO[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/water-simulation-1.html">水效果Ⅰ - 水池</a>] 上&nbsp;，构造网格看看吧。</p><p>1.&ldquo;客户端状态&rdquo;和&ldquo;服务端状态&rdquo;</p><p>从VBO的作用说起吧。VBO出现的背景是人们发现VA还有不让人满足的地方。一般，在OpenGL里，提高顶点绘制速度的手法，一是把常规的glBegin() - glEnd()类代码段放入一个<a target="_blank" href="http://bbs.pfan.cn/post-219518.html">显示列表</a>中（通常在初始化阶段完成），然后每遍渲染都调用这个显示列表；二是使用顶点数组，把顶点以及顶点属性数据作为数组，渲染的时候直接用一个或几个函数调动这些数组里的数据进行绘制，形式上是减少函数调用的次数（glVertex再见），提高绘制效率。但是这两种方法都有缺点。在说这个之前，应该对&ldquo;客户端状态&rdquo;和&ldquo;服务端状态&rdquo;两个名词有点了解。</p><p>OpenGL是个状态机，我们通常见到的glEnable - glDisable函数就是通知OpenGL开启/关闭某种状态的，譬如光照、深度检测等等。但是也有glEnableClientState - glDisableClientState这对。它们的区别是通知的具体对象在概念上不一样&mdash;&mdash;分别是服务端和客户端。事实上我也无法很清楚地告诉你区别之处，反正你把你电脑上的具体程序，包括它用到的内存等等看作客户端，把你电脑里面的&mdash;&mdash;显卡里的OpenGL&ldquo;模块&rdquo;，乃至整张拥有OpenGL流水线、硬件实现OpenGL功能的显卡，作为服务端。它们各自维护一些&ldquo;状态&rdquo;，glEnable 等是直接维护流水线处理相关的状态的，glEnableClientState 维护的则是进入流水线前的状态。流水线早期的T&amp;L阶段，程序的顶点数据就被获知而接受处理了。至于顶点是怎么来的&mdash;&mdash;是glVertex来的，还是glDrawArray来的，流水线没必要知道&mdash;&mdash;这就是客户端的任务，所以是否使用顶点数组（作为一种状态是否需要被启动）都是由客户端决定。显示列表的glCallList比较特殊，它绕过客户端，直接通知服务端把之前初始化时设定的代码段所映射的硬件设置&ldquo;启亮&rdquo;，这是相当于直接把显存的某一段占有而随时呼唤了，硬件对此命令没有丝毫犹豫地接受，对该呼唤的答应变成一种&ldquo;神经反射&rdquo;行为&mdash;&mdash;这是最理想最高级的&ldquo;绘制&rdquo;。</p><p>2. VBO，存在的理由</p><p>VBO出现的背景是人们发现VA还有不让人满足的地方，同样，显示列表也是。VA（顶点数组）是在客户端设置的，所以执行这类函数(glDrawArray,glDrawElement等等)后，客户端还得把得到的顶点数据向服务端传输一次（所谓的&ldquo;二次处理&rdquo;），这样一来就有了不必要的动作了，降低了效率&mdash;&mdash;如果我们写的函数能直接把顶点数据发给服务端就好了&mdash;&mdash;这正是VBO的特性之一。你可能会说，既然上面说到显示列表这么强大，用显示列表不就好了？显示列表的缺点正在于它的古板&mdash;&mdash;一旦设定，就不容许更改，所以它只适合对一些&ldquo;固定&rdquo;的东西的绘制进行包装。（我们无办法直接在硬件层改顶点数据，因为这是脱离了流水线的事物。）而VBO直接把顶点数据交到流水线的第一步，与显示列表的效率还是有差距，但它这样就得到了操作数据的弹性&mdash;&mdash;渲染阶段，我们的VBO绘制函数持续把顶点数据交给流水线，在某一刻我们可以把该帧到达了流水线的顶点数据捏回客户端修改（Vertex mapping），再提交回流水线（Vertex unmapping），（或者用glBufferData或glBufferSubData重新全部/部分提交更改了的顶点数据，）这是VBO的另一特性。</p><p>或者说，VBO结合了VA和显示列表的这个说法不太妥当，应该说它结合了两者的一些的特性，绘制效率在两者之间，且拥有良好的数据更改弹性。这种折衷造就了它一直为目前&ldquo;最高&rdquo;的地位。</p><p>3.VBO的使用</p><p>VBO的使用一方面跟FBO相似（应该调转一下主谓），另一方面与VA相似，绘制部分函数的样子根本就是同一个（毕竟叫作VA的升级版嘛）。以前VBO用不成功，现在用VBO最初也老出问题，其实不是没掌握好VBO而是没掌握好VA。的确，顶点数组是我学过而没怎么用过的OpenGL功能，一直偏向于使用容易理解的glVertex,glNormal系列，这种倾向希望从今天开始改变吧。</p><p>初始化部分：</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt">&nbsp;</li>    <li><span class="comment">//mVertexBufferObject为VBO对象ID </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;GLsizeiptr&nbsp;VertDataSize&nbsp;=&nbsp;XSCALE&nbsp;*&nbsp;ZSCALE&nbsp;*&nbsp;</span><span class="keyword">sizeof</span><span>(CVector3); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glGenBuffers(1,&nbsp;&amp;mVertexBufferObject); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(GL_ARRAY_BUFFER,&nbsp;mVertexBufferObject); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBufferData(GL_ARRAY_BUFFER,&nbsp;VertDataSize,&nbsp;Waterpos,&nbsp;GL_STREAM_DRAW); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;GLsizeiptr&nbsp;TexCoordDataSize&nbsp;=&nbsp;XSCALE&nbsp;*&nbsp;ZSCALE&nbsp;*&nbsp;</span><span class="keyword">sizeof</span><span>(TexCoord); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glGenBuffers(1,&nbsp;&amp;mTexCoordBufferObject); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(GL_ARRAY_BUFFER,&nbsp;mTexCoordBufferObject); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glBufferData(GL_ARRAY_BUFFER,&nbsp;TexCoordDataSize,&nbsp;Watertex,&nbsp;GL_STREAM_DRAW); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;GLsizeiptr&nbsp;NormalDataSize&nbsp;=&nbsp;XSCALE&nbsp;*&nbsp;ZSCALE&nbsp;*&nbsp;</span><span class="keyword">sizeof</span><span>(CVector3); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glGenBuffers(1,&nbsp;&amp;mNormalBufferObject); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(GL_ARRAY_BUFFER,&nbsp;mNormalBufferObject); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBufferData(GL_ARRAY_BUFFER,&nbsp;NormalDataSize,&nbsp;Waternorm,&nbsp;GL_STREAM_DRAW); </span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//mVertexBufferObject为VBO对象ID	GLsizeiptr VertDataSize = XSCALE * ZSCALE * sizeof(CVector3);	glGenBuffers(1, &amp;mVertexBufferObject);	glBindBuffer(GL_ARRAY_BUFFER, mVertexBufferObject);	glBufferData(GL_ARRAY_BUFFER, VertDataSize, Waterpos, GL_STREAM_DRAW);    GLsizeiptr TexCoordDataSize = XSCALE * ZSCALE * sizeof(TexCoord);	glGenBuffers(1, &amp;mTexCoordBufferObject);	glBindBuffer(GL_ARRAY_BUFFER, mTexCoordBufferObject);	glBufferData(GL_ARRAY_BUFFER, TexCoordDataSize, Watertex, GL_STREAM_DRAW);	    GLsizeiptr NormalDataSize = XSCALE * ZSCALE * sizeof(CVector3);	glGenBuffers(1, &amp;mNormalBufferObject);	glBindBuffer(GL_ARRAY_BUFFER, mNormalBufferObject);	glBufferData(GL_ARRAY_BUFFER, NormalDataSize, Waternorm, GL_STREAM_DRAW);</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>跟FBO[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/learn-fbo.html">学一学，FBO</a>]很像吧。其他顶点属性（包括顶点位置glVertex，顶点法线glNormal，顶点颜色glColor，顶点纹理坐标glTexCoord，顶点雾坐标等等，或者shader的attribute属性变量）也是类似的。其中，Waterpos等储存了具体的数据（注意数据存放次序呀，一般栽会栽在这里）。GL_STATIC_DRAW，GL_STREAM_DRAW，GL_DYNAMIC_DRAW用于给OpenGL系统提醒：预期数据是一直不变、数据每帧变一次或几帧变一次、数据每帧变两三次以上，方便硬件内部优化吧。</p><p>渲染部分：</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="comment">// </span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(GL_ARRAY_BUFFER,&nbsp;mVertexBufferObject); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glEnableClientState(GL_VERTEX_ARRAY); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glVertexPointer(3,&nbsp;GL_FLOAT,&nbsp;0,&nbsp;0); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(GL_ARRAY_BUFFER,&nbsp;mNormalBufferObject); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glEnableClientState(GL_NORMAL_ARRAY); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glNormalPointer(GL_FLOAT,&nbsp;0,&nbsp;0); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;........ </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glDrawArrays(GL_QUADS,&nbsp;0,&nbsp;VertexCount); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;......... </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisableClientState(GL_NORMAL_ARRAY); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisableClientState(GL_VERTEX_ARRAY); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(GL_ARRAY_BUFFER,&nbsp;0); </span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//	glBindBuffer(GL_ARRAY_BUFFER, mVertexBufferObject);	glEnableClientState(GL_VERTEX_ARRAY);	glVertexPointer(3, GL_FLOAT, 0, 0);	glBindBuffer(GL_ARRAY_BUFFER, mNormalBufferObject);	glEnableClientState(GL_NORMAL_ARRAY);	glNormalPointer(GL_FLOAT, 0, 0);          ........       glDrawArrays(GL_QUADS, 0, VertexCount);          .........  	glDisableClientState(GL_NORMAL_ARRAY);	glDisableClientState(GL_VERTEX_ARRAY);	glBindBuffer(GL_ARRAY_BUFFER, 0);</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>简直就是VA！对吧（虽然我没什么感觉）。记得前后要对VBO进行绑定和解绑哦，跟FBO一样的流程了。</p><p>4.数据更改的方式</p><p>其实前面也提到了，就是三种：1.重新对VBO对象进行绑定，用glBufferData把新数据交给VBO。它在当前帧执行，会把当前VBO内容清空，放入新数据后继续；2.glBufferSubData只更新VBO中部分数据，但是如果当前数据正在进行中，它会等待数据全部发送完毕，然后待把某部分改好了再全部发送；3.glMapBuffer - glUnMapBuffer，如上所述，数据会全传回来再传过去。这里是参考资料的一断代码，可参考：</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span>glBindBuffer(GL_ARRAY_BUFFER,&nbsp;BufferName[COLOR_OBJECT]); </span></span></li>    <li><span>glBufferData(GL_ARRAY_BUFFER,&nbsp;ColorSize,&nbsp;NULL,&nbsp;GL_STREAM_DRAW); </span></li>    <li class="alt"><span>GLvoid*&nbsp;ColorBuffer&nbsp;=&nbsp;glMapBuffer(GL_ARRAY_BUFFER,&nbsp;GL_WRITE_ONLY); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>glBindBuffer(GL_ARRAY_BUFFER,&nbsp;BufferName[POSITION_OBJECT]); </span></li>    <li><span>glBufferData(GL_ARRAY_BUFFER,&nbsp;PositionSize,&nbsp;NULL,&nbsp;GL_STREAM_DRAW); </span></li>    <li class="alt"><span>GLvoid*&nbsp;PositionBuffer&nbsp;=&nbsp;glMapBuffer(GL_ARRAY_BUFFER,&nbsp;GL_WRITE_ONLY); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>memcpy(ColorBuffer,&nbsp;ColorData,&nbsp;ColorSize); </span></li>    <li><span>memcpy(PositionBuffer,&nbsp;PositionData,&nbsp;PositionSize); </span></li>    <li class="alt">&nbsp;</li>    <li><span>glBindBuffer(GL_ARRAY_BUFFER,&nbsp;BufferName[COLOR_OBJECT]); </span></li>    <li class="alt"><span>glUnmapBuffer(GL_ARRAY_BUFFER); </span></li>    <li><span>glColorPointer(3,&nbsp;GL_UNSIGNED_BYTE,&nbsp;0,&nbsp;0); </span></li>    <li class="alt">&nbsp;</li>    <li><span>glBindBuffer(GL_ARRAY_BUFFER,&nbsp;BufferName[POSITION_OBJECT]); </span></li>    <li class="alt"><span>glUnmapBuffer(GL_ARRAY_BUFFER); </span></li>    <li><span>glVertexPointer(2,&nbsp;GL_FLOAT,&nbsp;0,&nbsp;0);</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>glBindBuffer(GL_ARRAY_BUFFER, BufferName[COLOR_OBJECT]);glBufferData(GL_ARRAY_BUFFER, ColorSize, NULL, GL_STREAM_DRAW);GLvoid* ColorBuffer = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);glBindBuffer(GL_ARRAY_BUFFER, BufferName[POSITION_OBJECT]);glBufferData(GL_ARRAY_BUFFER, PositionSize, NULL, GL_STREAM_DRAW);GLvoid* PositionBuffer = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);memcpy(ColorBuffer, ColorData, ColorSize);memcpy(PositionBuffer, PositionData, PositionSize);glBindBuffer(GL_ARRAY_BUFFER, BufferName[COLOR_OBJECT]);glUnmapBuffer(GL_ARRAY_BUFFER);glColorPointer(3, GL_UNSIGNED_BYTE, 0, 0);glBindBuffer(GL_ARRAY_BUFFER, BufferName[POSITION_OBJECT]);glUnmapBuffer(GL_ARRAY_BUFFER);glVertexPointer(2, GL_FLOAT, 0, 0);</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>对本文有问题可在我的网站<a href="http://www.zwqxin.com/">www.zwqxin.com</a>一起讨论讨论？本文参考资料：<a target="_blank" href="http://www.ozone3d.net/tutorials/opengl_vbo.php">OpenGL Vertex Buffer Objects</a>、<a target="_blank" href="http://www.songho.ca/opengl/gl_vbo.html">OpenGL Vertex Buffer Object(VBO)</a></p><p>对VBO的初步学习先到此吧。接下来我会根据自己实际应用的经验，谈谈怎么控制顶点数据中顶点顺序的问题，构造网格不能简单使用glDrawArrays，而应该通过建立顶点索引，用GL_ELEMENT_ARRAY_BUFFER下的glDrawElements绘制；同时谈谈对于纹理坐标这种一顶点可能存在多个的问题（多重纹理），怎样设置VBO的问题。并可能简单介绍Interleaved arrays 、Serialized arrays这类用法。</p><p>好吧。见下篇文章吧：[<a href="http://www.zwqxin.com/archives/opengl/indexed-vbo-and-multitex-vbo.html" target="_blank">索引顶点的VBO与多重纹理下的VBO</a>]</p>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/learn-vbo.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=70</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=70&amp;key=08ffd576</trackback:ping></item><item><title>水效果Ⅱ - 涟漪</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/water-simulation-2.html</link><pubDate>Sat, 15 Aug 2009 20:00:23 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/water-simulation-2.html</guid><description><![CDATA[<p>在广州动物园南门等朋友。进口旁是一滩池水，波光粼粼之中，倒映着红嘴白鹭们的身姿，嬉戏之间，水滴自觉地在池中酿起一圈一圈细细的涟漪，回味其中。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p style="text-align: center"><img style="width: 211px; height: 232px" height="242" alt="" width="211" src="http://strongerteams.files.wordpress.com/2007/02/ripple-crop.jpg" /><img style="width: 304px; height: 230px" height="399" alt="" width="494" src="http://images.quickblogcast.com/1/2/9/8/4/158330-148921/ripple.jpg" /></p><p>上篇：[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/water-simulation-1.html">水效果Ⅰ - 水池</a>]</p><p>Ripple，涟漪，是一种圆形波（Circular Wave），从波源处呈圆状向外扩散。它最大的特点是衰减。水滴入水的刹那，引起该位置的振动从而产生频率很大的波。波无节制地向它所能蔓延的任何方向（水平面任何方向）蔓延，但是随着时间的增加，波传递的时候频率会逐渐减小&mdash;&mdash;在波速一定下，这相当于波长的增大，所以你看涟漪在水面上新产生的一圈与前一圈的差额会比前一圈与前前圈的差额大；同时，振幅也是随着时间&mdash;&mdash;以及随着传播距离逐渐变小&mdash;&mdash;所以你最后看到涟漪就像蔓延开来消失了一样。</p><p>因此，完全可以用一个正弦波函数来决定当前水面位置的高度。而且其振幅和频率按上述规律变化。理应，这个波只作用在波源附近一定范围内就够了，在之外的所产生的水面效应根本可以忽略，但是我只有一个水面网格，拆分不是那么容易的，特别是涟漪在随机位置产生的情况下。所以每个涟漪其实都影响整个网格。或者说，每个涟漪都在原来平静水面的基础上加添了一个PASS，使其基本形态改变，之后另一个涟漪同样作为一个PASS叠加到之前那个PASS之上&mdash;&mdash;相当于网格每个顶点都加两个正弦函数值&mdash;&mdash;多个涟漪互相叠加可形成趣味的交变效果。</p><p>H(x,y) = Amplitude(t,dis) * cos(freguency(t) * ( t + dis/velocity)) ;<br />dis = length(Point(x,y) - CenterPoint)</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt">&nbsp;</li>    <li><span class="keyword">double</span><span>&nbsp;TheRipple::RippleFunction(</span><span class="keyword">double</span><span>&nbsp;dist_to_waveCenter,&nbsp;</span><span class="keyword">float</span><span>&nbsp;waveCenterAmplitude,&nbsp;</span><span class="keyword">float</span><span>&nbsp;velocity,&nbsp;</span><span class="keyword">float</span><span>&nbsp;time) </span></li>    <li class="alt"><span>{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">double</span><span>&nbsp;attenuate&nbsp;=&nbsp;1&nbsp;+&nbsp;0.07&nbsp;*&nbsp;time&nbsp;*&nbsp;time&nbsp;+&nbsp;10.0&nbsp;*&nbsp;dist_to_waveCenter&nbsp;*&nbsp;dist_to_waveCenter; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">double</span><span>&nbsp;factor&nbsp;=&nbsp;waveCenterAmplitude&nbsp;/&nbsp;attenuate; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">double</span><span>&nbsp;OriginalFreq&nbsp;=&nbsp;0.05&nbsp;; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">double</span><span>&nbsp;freq&nbsp;=&nbsp;OriginalFreq&nbsp;/&nbsp;(1.0&nbsp;+&nbsp;0.07&nbsp;*&nbsp;time); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">double</span><span>&nbsp;deltaT&nbsp;=&nbsp;dist_to_waveCenter&nbsp;/&nbsp;velocity; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">double</span><span>&nbsp;DestVertexHeight&nbsp;=&nbsp;factor&nbsp;*&nbsp;cos(freq&nbsp;*&nbsp;(time&nbsp;+&nbsp;deltaT)&nbsp;+&nbsp;PI); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">return</span><span>&nbsp;DestVertexHeight; </span></li>    <li class="alt"><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>double TheRipple::RippleFunction(double dist_to_waveCenter, float waveCenterAmplitude, float velocity, float time){	double attenuate = 1 + 0.07 * time * time + 10.0 * dist_to_waveCenter * dist_to_waveCenter;	double factor = waveCenterAmplitude / attenuate;	double OriginalFreq = 0.05 ;	double freq = OriginalFreq / (1.0 + 0.07 * time);	double deltaT = dist_to_waveCenter / velocity;    double DestVertexHeight = factor * cos(freq * (time + deltaT) + PI);	return DestVertexHeight;}</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>其中，具体的各参数的数值，包括各衰减因子都是调试效果时所得的较优效果值（针对具体应用）。attenuate 是振幅衰减因子，waveCenterAmplitude是最初的振幅；OriginalFreq是最初的频率，freq是经过衰减厚的频率；deltaT决定了波传递至时的时刻（由波长决定）；DestVertexHeight作为输出反映该涟漪在该点对水面网格的高度增量。</p><p>实现涟漪的增量的叠加并不麻烦。麻烦的是涟漪的销毁。假设TheRipple是我的涟漪对象，我用一个容器譬如线性列表装下每个新生成的涟漪。问题是这种涟漪计算开销太大，如果涟漪不断生成[new]，同样的计算量在每一帧都不断附加，FPS很快会走向灭亡边缘。这应是可避免的&mdash;&mdash;在实时渲染中，涟漪在振幅很小很小的时候应该被消灭[deelete]。如何断定这个时机呢？</p><p>在每个涟漪对象里，检查每个顶点的的高度增量。要注意并非所有顶点都会在同一时间达到振幅最小，因为振幅除了是时间的函数外也是该顶点距波源中心的距离的函数。所以要用一个计数器记数，每有一个顶点满足要求就让计数器加1，并判断当前计数与总顶点数量的大小关系以判断是否该销毁整个涟漪对象，并把它从涟漪列表中去掉。</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="comment">//在涟漪的更新函数里 </span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">for</span><span>(z&nbsp;=&nbsp;0;&nbsp;z&nbsp;&lt;&nbsp;mGridScaleZ;&nbsp;z++) </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">for</span><span>(x&nbsp;=&nbsp;0;&nbsp;x&nbsp;&lt;&nbsp;mGridScaleX;&nbsp;x++) </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;index&nbsp;=&nbsp;x&nbsp;+&nbsp;z&nbsp;*&nbsp;mGridScaleX; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;mCurrentRippleDelta[index].y&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;RippleFunction(mDistToCenter[index],&nbsp;Amplitude,&nbsp;velocity,&nbsp;mTickCount);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">if</span><span>(fabs(mCurrentRippleDelta[index].y)&nbsp;&lt;&nbsp;0.0001f) </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">if</span><span>(mVertexStop[index]&nbsp;==&nbsp;</span><span class="keyword">false</span><span>) </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mVertexStop[index]&nbsp;=&nbsp;</span><span class="keyword">true</span><span>; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mVertexStops&nbsp;++; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">if</span><span>(mVertexStops&nbsp;&gt;=&nbsp;0.99&nbsp;*&nbsp;mGridScaleX&nbsp;*&nbsp;mGridScaleZ) </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mRippleGenerated&nbsp;=&nbsp;</span><span class="keyword">false</span><span>; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} </span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//在涟漪的更新函数里	 for(z = 0; z &lt; mGridScaleZ; z++)	  for(x = 0; x &lt; mGridScaleX; x++)	  {          index = x + z * mGridScaleX;	mCurrentRippleDelta[index].y 		= RippleFunction(mDistToCenter[index], Amplitude, velocity, mTickCount);		  		         if(fabs(mCurrentRippleDelta[index].y) &lt; 0.0001f)		  {			  if(mVertexStop[index] == false)			  {			    mVertexStop[index] = true;                mVertexStops ++;			  }		  }	  }	  if(mVertexStops &gt;= 0.99 * mGridScaleX * mGridScaleZ)	  {		  mRippleGenerated = false;	  }</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>mCurrentRippleDelta[index].y 记录当前顶点（index）的高度增量，其绝对值就是振幅，容差值是0.0001f。mVertexStop[index]是该顶点的bool变量，当它由false变true时就宣告了该顶点的&ldquo;破产&rdquo;，计数器mVertexStops加一。最后的判断里顶点总数为何乘以0.99呢？因为如果是0.995，你等待该涟漪毁灭的时间要加倍，0.998再加倍，0.999再加倍&hellip;&hellip;所以千万别直接就取顶点总数口牙~~</p><p>效果：</p><a target="_self" href="http://www.zwqxin.com/archives/opengl/water-simulation-2.html"><img alt="http://ww.zwqxin.com 水效" src="http://lh5.ggpht.com/_lYWT-2PnV0s/SobDz4pTuzI/AAAAAAAAA1Q/wvTfm6vm0jo/s800/tyud815.jpg" /></a><br /><br /><a target="_self" href="http://www.zwqxin.com/archives/opengl/water-simulation-2.html"><img alt="http://ww.zwqxin.com 水效" src="http://lh4.ggpht.com/_lYWT-2PnV0s/SobD0J59MiI/AAAAAAAAA1U/dDNyBBZZ9XU/s800/yuyy815.jpg" /></a></p><p>&nbsp;</p><p style="text-align: left">近看的话，发觉波纹比较尖不是吗？事实上这既与网格的分辨率有关，也与波源函数有关。首先这是200*200的网格，如果是250*250乃至400*400会含有更多细节吧，不过就渲染效率来看这颇愚蠢；另外波源函数本质只是简单的单余弦，有比较尖的头也是正常的。如果改用 2*A*[1+cos(angle)/2]<sup>exp</sup>的话，就可以用exp指数项控制头部的平滑程度了。当然，就本应用来说，一般不会近看，所以这样做反而也没必要。</p><p style="text-align: left">恩，在CPU上实现的涟漪就....到此吧。</p><p>下篇继续，见 <a target="_self" href="http://www.zwqxin.com/archives/opengl/water-simulation-3.html">水效果Ⅲ -&nbsp;抖动波浪</a></p>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/water-simulation-2.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=68</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=68&amp;key=2b58bcfd</trackback:ping></item><item><title>在OpenGL上设置字体和显示文字(下)</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/opengl-font-setting-showing-2.html</link><pubDate>Thu, 06 Aug 2009 10:22:27 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/opengl-font-setting-showing-2.html</guid><description><![CDATA[<p>本篇紧随上篇，继续说一下鄙人所了解的在OpenGL进行文字显示的方法，也方便不知从哪个次元来到这里的学习者提供一点这方面的信息。上一篇见：[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/opengl-font-setting-showing.html">在OpenGL上设置字体和显示文字(上)</a>] &mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>2. 纹理字体</p><p>最近接触Irrlicht引擎，里面的GUI模块涉及的字体设置就是用了这种纹理字体的方法。事实上，上篇的方法最后多少了涉及到了位图字体，但是这里所说的，是直接从一张&ldquo;写着各个英文字符和其他常用字符&rdquo;的纹理上，按对此纹理上的图案&ldquo;结构&rdquo;的先验知识而获取字符对应的&ldquo;纹理部分&rdquo;，显示到屏幕上。</p><p>换句话来说，这就是真正的纹理贴图了，因此必须结合一张特定设计的&ldquo;字体纹理&rdquo;。要显示一个字符，需要你绘制一个一定大小的矩形（这个长度值相当于之前的字体高度&mdash;&mdash;它控制最后出来的文字的大小），然后找到你所需字符在该纹理上的纹理坐标，作为索引检索出纹理上的对应字符部分的小纹理，贴到该矩形上。</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt">&nbsp;</li>    <li><span class="comment">//////////从字体集纹理中取出的字符 </span></li>    <li class="alt"><span class="keyword">void</span><span>&nbsp;MyFont::BuildTextureFont(GLuint&nbsp;fonttex,&nbsp;</span><span class="keyword">int</span><span>&nbsp;fontHeight,&nbsp;</span><span class="keyword">int</span><span>&nbsp;screenWidth,&nbsp;</span><span class="keyword">int</span><span>&nbsp;screenHeight) </span></li>    <li><span>{ </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;TextureFontFont&nbsp;=&nbsp;fonttex; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;&nbsp;&nbsp;cx;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Holds&nbsp;Our&nbsp;X&nbsp;Character&nbsp;Coord </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;&nbsp;&nbsp;cy;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Holds&nbsp;Our&nbsp;Y&nbsp;Character&nbsp;Coord </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glEnable(GL_TEXTURE_2D); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;TextureFontBase&nbsp;=&nbsp;glGenLists(256);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Creating&nbsp;256&nbsp;Display&nbsp;Lists </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindTexture(GL_TEXTURE_2D,&nbsp;TextureFontFont);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Select&nbsp;Our&nbsp;Font&nbsp;Texture </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">for</span><span>(</span><span class="keyword">int</span><span>&nbsp;loop=0;&nbsp;loop&lt;256;&nbsp;loop++)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Loop&nbsp;Through&nbsp;All&nbsp;256&nbsp;Lists </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cx=</span><span class="keyword">float</span><span>(loop%16)/16.0f;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;X&nbsp;Position&nbsp;Of&nbsp;Current&nbsp;Character </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cy=</span><span class="keyword">float</span><span>(loop/16)/16.0f;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Y&nbsp;Position&nbsp;Of&nbsp;Current&nbsp;Character </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glNewList(TextureFontBase&nbsp;+&nbsp;loop,&nbsp;GL_COMPILE);&nbsp;&nbsp;</span><span class="comment">//&nbsp;Start&nbsp;Building&nbsp;A&nbsp;List </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glBegin(GL_QUADS);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Use&nbsp;A&nbsp;Quad&nbsp;For&nbsp;Each&nbsp;Character </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glTexCoord2f(cx,1-cy-0.0625f);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Texture&nbsp;Coord&nbsp;(Bottom&nbsp;Left) </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glVertex2i(0,0);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Vertex&nbsp;Coord&nbsp;(Bottom&nbsp;Left) </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glTexCoord2f(cx+0.0625f,1-cy-0.0625f);&nbsp;&nbsp;</span><span class="comment">//&nbsp;Texture&nbsp;Coord&nbsp;(Bottom&nbsp;Right) </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glVertex2i(fontHeight,&nbsp;0);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Vertex&nbsp;Coord&nbsp;(Bottom&nbsp;Right) </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glTexCoord2f(cx+0.0625f,1-cy);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Texture&nbsp;Coord&nbsp;(Top&nbsp;Right) </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glVertex2i(fontHeight,fontHeight);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Vertex&nbsp;Coord&nbsp;(Top&nbsp;Right) </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glTexCoord2f(cx,1-cy);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Texture&nbsp;Coord&nbsp;(Top&nbsp;Left) </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glVertex2i(0,&nbsp;fontHeight);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Vertex&nbsp;Coord&nbsp;(Top&nbsp;Left) </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glEnd();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Done&nbsp;Building&nbsp;Our&nbsp;Quad&nbsp;(Character) </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glTranslated(fontHeight,0,0);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Move&nbsp;To&nbsp;The&nbsp;Right&nbsp;Of&nbsp;The&nbsp;Character </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glEndList();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Done&nbsp;Building&nbsp;The&nbsp;Display&nbsp;List </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Loop&nbsp;Until&nbsp;All&nbsp;256&nbsp;Are&nbsp;Built </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisable(GL_TEXTURE_2D); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;ScreenWidth&nbsp;=&nbsp;screenWidth; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;ScreenHeight&nbsp;=&nbsp;screenHeight; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;TextureFont&nbsp;=&nbsp;</span><span class="keyword">true</span><span>; </span></li>    <li><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//////////从字体集纹理中取出的字符void MyFont::BuildTextureFont(GLuint fonttex, int fontHeight, int screenWidth, int screenHeight){	TextureFontFont = fonttex;	float	cx;											// Holds Our X Character Coord	float	cy;											// Holds Our Y Character Coord	glEnable(GL_TEXTURE_2D);	TextureFontBase = glGenLists(256);					// Creating 256 Display Lists	glBindTexture(GL_TEXTURE_2D, TextureFontFont);	    // Select Our Font Texture	for(int loop=0; loop&lt;256; loop++)					// Loop Through All 256 Lists	{		cx=float(loop%16)/16.0f;						// X Position Of Current Character		cy=float(loop/16)/16.0f;						// Y Position Of Current Character		glNewList(TextureFontBase + loop, GL_COMPILE);	// Start Building A List			glBegin(GL_QUADS);							// Use A Quad For Each Character				glTexCoord2f(cx,1-cy-0.0625f);			// Texture Coord (Bottom Left)				glVertex2i(0,0);						// Vertex Coord (Bottom Left)				glTexCoord2f(cx+0.0625f,1-cy-0.0625f);	// Texture Coord (Bottom Right)				glVertex2i(fontHeight, 0);				// Vertex Coord (Bottom Right)				glTexCoord2f(cx+0.0625f,1-cy);			// Texture Coord (Top Right)				glVertex2i(fontHeight,fontHeight);		// Vertex Coord (Top Right)				glTexCoord2f(cx,1-cy);					// Texture Coord (Top Left)				glVertex2i(0, fontHeight);				// Vertex Coord (Top Left)			glEnd();									// Done Building Our Quad (Character)			glTranslated(fontHeight,0,0);				// Move To The Right Of The Character		glEndList();									// Done Building The Display List	}													// Loop Until All 256 Are Built    glDisable(GL_TEXTURE_2D);	ScreenWidth = screenWidth;	ScreenHeight = screenHeight;	TextureFont = true;}</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>这在BUILD阶段就做的必要是没有的，但是一次过导入纹理中256个字符，生成256个小纹理，并在每帧都选择对应的纹理索引检索纹理，且每个字符如此&mdash;&mdash;这样的重复不变而低效的事情，还是由OpenGL的显示列表技术来做比较好&mdash;&mdash;一次过在初始化时做好，放入显示列表。在 PRINT阶段只要调用显示列表就好。关于显示列表，eastcowboy在他的OPENGL入门学习中详细提及过，有兴趣的朋友可看看他的文章：<a target="_blank" href="http://bbs.pfan.cn/post-219518.html"><strong>OpenGL入门学习&mdash;&mdash;第八课-</strong>使用显示列表</a>&nbsp;。比起最时兴的VBO，显示列表在重复劳动上还是有一定优势的额~</p><p>最后的结果是按glTranslated进行排列的256个具有纹理字符的矩形。为什么要得到渲染窗口的大小呢？因为这些矩形要保证在屏幕最前方，就应该让它突破矩阵变换啊。在上篇[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/opengl-font-setting-showing.html">在OpenGL上设置字体和显示文字(上)</a>] 也提过glWindowPos2i(x, y)，但这里对实际的矩形它不是很适用。因此我还是选择原来的路子，来给予文字以独立于图形的属性&mdash;&mdash;在屏幕最前而位置只由屏幕坐标XY决定。方法就是，或许很多人也熟悉的，glOrtho正交投影变换。因此，屏幕大小（这里指渲染窗口的大小）是需要的。</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt">&nbsp;</li>    <li><span class="keyword">void</span><span>&nbsp;MyFont::PrintTextureText(GLint&nbsp;x,&nbsp;GLint&nbsp;y,&nbsp;</span><span class="keyword">char</span><span>&nbsp;*</span><span class="keyword">string</span><span>,&nbsp;</span><span class="keyword">int</span><span>&nbsp;TextureSet) </span></li>    <li class="alt"><span>{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">if</span><span>&nbsp;(TextureSet&nbsp;&gt;&nbsp;1)TextureSet&nbsp;=&nbsp;1; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">if</span><span>&nbsp;(TextureSet&nbsp;&lt;&nbsp;0)TextureSet&nbsp;=&nbsp;0; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glPushAttrib(GL_CURRENT_BIT&nbsp;|&nbsp;GL_LIGHTING_BIT&nbsp;|&nbsp;GL_ENABLE_BIT|&nbsp;&nbsp;GL_LIST_BIT); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisable(GL_LIGHTING);&nbsp; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glEnable(GL_TEXTURE_2D); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindTexture(GL_TEXTURE_2D,&nbsp;TextureFontFont);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Select&nbsp;Our&nbsp;Font&nbsp;Texture </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisable(GL_DEPTH_TEST);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Disables&nbsp;Depth&nbsp;Testing </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glEnable(GL_BLEND); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glBlendFunc(GL_SRC_ALPHA,GL_ONE); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glColor4f(mColor[0],&nbsp;mColor[1],&nbsp;mColor[2],&nbsp;mColor[3]);&nbsp;&nbsp; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glMatrixMode(GL_PROJECTION);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Select&nbsp;The&nbsp;Projection&nbsp;Matrix </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glPushMatrix();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Store&nbsp;The&nbsp;Projection&nbsp;Matrix </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glLoadIdentity();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Reset&nbsp;The&nbsp;Projection&nbsp;Matrix </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glOrtho(0,ScreenWidth,0,ScreenHeight,-1,1);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Set&nbsp;Up&nbsp;An&nbsp;Ortho&nbsp;Screen </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glMatrixMode(GL_MODELVIEW);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Select&nbsp;The&nbsp;Modelview&nbsp;Matrix </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glPushMatrix();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Store&nbsp;The&nbsp;Modelview&nbsp;Matrix </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glLoadIdentity();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Reset&nbsp;The&nbsp;Modelview&nbsp;Matrix </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glTranslated(x,y,0);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Position&nbsp;The&nbsp;Text&nbsp;(0,0&nbsp;-&nbsp;Bottom&nbsp;Left) </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glListBase(TextureFontBase-32&nbsp;+&nbsp;(128&nbsp;*&nbsp;TextureSet));</span><span class="comment">//&nbsp;Choose&nbsp;The&nbsp;Font&nbsp;Set&nbsp;(0&nbsp;or&nbsp;1) </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glCallLists(strlen(</span><span class="keyword">string</span><span>),GL_UNSIGNED_BYTE,</span><span class="keyword">string</span><span>);</span><span class="comment">//&nbsp;Write&nbsp;The&nbsp;Text&nbsp;To&nbsp;The&nbsp;Screen </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glMatrixMode(GL_PROJECTION);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Select&nbsp;The&nbsp;Projection&nbsp;Matrix </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glPopMatrix();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Restore&nbsp;The&nbsp;Old&nbsp;Projection&nbsp;Matrix </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glMatrixMode(GL_MODELVIEW);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Select&nbsp;The&nbsp;Modelview&nbsp;Matrix </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glPopMatrix();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Restore&nbsp;The&nbsp;Old&nbsp;Projection&nbsp;Matrix </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisable(GL_BLEND); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glEnable(GL_DEPTH_TEST);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Enables&nbsp;Depth&nbsp;Testing </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisable(GL_TEXTURE_2D); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glPopAttrib(); </span></li>    <li><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>void MyFont::PrintTextureText(GLint x, GLint y, char *string, int TextureSet){	if (TextureSet &gt; 1)TextureSet = 1;	if (TextureSet &lt; 0)TextureSet = 0;	glPushAttrib(GL_CURRENT_BIT | GL_LIGHTING_BIT | GL_ENABLE_BIT|  GL_LIST_BIT);	glDisable(GL_LIGHTING);     glEnable(GL_TEXTURE_2D);	glBindTexture(GL_TEXTURE_2D, TextureFontFont);		// Select Our Font Texture	glDisable(GL_DEPTH_TEST);							// Disables Depth Testing	glEnable(GL_BLEND);	glBlendFunc(GL_SRC_ALPHA,GL_ONE);	glColor4f(mColor[0], mColor[1], mColor[2], mColor[3]);  	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix	glPushMatrix();										// Store The Projection Matrix		glLoadIdentity();									// Reset The Projection Matrix	glOrtho(0,ScreenWidth,0,ScreenHeight,-1,1);			// Set Up An Ortho Screen	glMatrixMode(GL_MODELVIEW);							// Select The Modelview Matrix	glPushMatrix();										// Store The Modelview Matrix		glLoadIdentity();									// Reset The Modelview Matrix	glTranslated(x,y,0);								// Position The Text (0,0 - Bottom Left)		glListBase(TextureFontBase-32 + (128 * TextureSet));// Choose The Font Set (0 or 1)	glCallLists(strlen(string),GL_UNSIGNED_BYTE,string);// Write The Text To The Screen		glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix	glPopMatrix();										// Restore The Old Projection Matrix	glMatrixMode(GL_MODELVIEW);							// Select The Modelview Matrix	glPopMatrix();										// Restore The Old Projection Matrix		glDisable(GL_BLEND);	glEnable(GL_DEPTH_TEST);							// Enables Depth Testing    glDisable(GL_TEXTURE_2D);	glPopAttrib();}</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>TextureSet选择字符集，因为字符纹理里面每个字符对应两种字体，用0/1选择。glPushAttrib的作用前面说过了。之后我们用glPushMatrix保存当前的投影/模型视图矩阵，在弄好一切好就马上切换回去。弄什么呢？新的坐标变换。文字(glCallLists绘制)是脱离我们OPENGL图形世界的，因此单独给予它一套变换&mdash;&mdash;在屏幕最前方且不受视觉透视影响。glOrtho(0,ScreenWidth,0,ScreenHeight,-1,1)这样的投影变换设置配合glTranslated这样的模型变换就能满足我的要求了。因为创建的正交投影是与渲染窗口大小一致的，所以glTranslated的X，Y的单位与像素pixel对应&mdash;&mdash;这不也就是GDI那种设置方式了么哈。</p><p>具体应用：</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="comment">//CMAINFRAME&nbsp; </span></span></li>    <li><span>MyFont&nbsp;mFont;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp; </span></li>    <li><span class="comment">//初始化：&nbsp; </span></li>    <li class="alt"><span>mFont.BuildTextureFont(FontTextureID,&nbsp;25,&nbsp;VB_WIDTH,&nbsp;VB_HEIGHT); </span></li>    <li><span class="comment">//25是字体字高，控制字体大小&nbsp;,FontTextureID字体纹理的纹理ID </span></li>    <li class="alt"><span>&nbsp;&nbsp; </span></li>    <li><span class="comment">//渲染阶段（RenderGLScene）&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp; </span></li>    <li><span>mFont.PrintTextureText(790,645,</span><span class="string">&quot;Font&nbsp;Test&quot;</span><span>,1); </span></li>    <li class="alt"><span>&nbsp;&nbsp; </span></li>    <li><span class="comment">//将在坐标X&nbsp;=&nbsp;790，&nbsp;Y&nbsp;=645位置开始绘制文字。对1024*768大小的渲染窗口中， </span></li>    <li class="alt"><span class="comment">//即在右上角偏下 </span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//CMAINFRAME MyFont mFont;   //初始化： mFont.BuildTextureFont(FontTextureID, 25, VB_WIDTH, VB_HEIGHT);//25是字体字高，控制字体大小 ,FontTextureID字体纹理的纹理ID  //渲染阶段（RenderGLScene）   mFont.PrintTextureText(790,645,&quot;Font Test&quot;,1);  //将在坐标X = 790， Y =645位置开始绘制文字。对1024*768大小的渲染窗口中，//即在右上角偏下</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>3.GDI字体</p><p>事实上第一种方法也可以说是GDI的方法，但是这里更明显，结合GDI字体创建和位图创建。而且它不涉及显示列表。它是在渲染时动态创建包容文字的设备相关位图（具体可参考我的一篇旧文[<a target="_blank" href="http://www.zwqxin.com/archives/image-processing/bmp-operate-copy-memory.html">认识HBITMAP与Bmp操作(整内存拷贝版)</a>] ），再把此位图定位而成的。因此，它是很慢的~（抽）。但是为了中文字体，一点点的性能损失算得了什么呢。</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="comment">///////////GDI,&nbsp;位图字体&nbsp;可设中文字体 </span></span></li>    <li><span class="keyword">void</span><span>&nbsp;MyFont::BuildGDIFont(LPCTSTR&nbsp;lpszFacename,&nbsp;</span><span class="keyword">int</span><span>&nbsp;fontWeights,&nbsp;</span><span class="keyword">int</span><span>&nbsp;fontHeight) </span></li>    <li class="alt"><span>{ </span></li>    <li><span>&nbsp;&nbsp;</span><span class="keyword">int</span><span>&nbsp;tfontHeight&nbsp;=&nbsp;-1&nbsp;*&nbsp;fontHeight; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hGDIFont&nbsp;=&nbsp;CreateFont(tfontHeight,&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;fontWeights,&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;GB2312_CHARSET, </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;FF_MODERN,&nbsp;lpszFacename); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;GDIFont&nbsp;=&nbsp;</span><span class="keyword">true</span><span>; </span></li>    <li><span>} </span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>///////////GDI, 位图字体 可设中文字体void MyFont::BuildGDIFont(LPCTSTR lpszFacename, int fontWeights, int fontHeight){  int tfontHeight = -1 * fontHeight;      hGDIFont = CreateFont(tfontHeight, 0, 0, 0, fontWeights, 0, 0, 0, GB2312_CHARSET,		                0, 0, 0, FF_MODERN, lpszFacename);	  GDIFont = true;}</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>BUILD部分就不多说了，就是CreateFont~~fontWeights表示字体的重量，在0~900内可选，直接设置FW_BOLD之类的也行，这里给出这个参数不过是多给它一些可控性而已。首参数是字体，譬如可以是&quot;黑体&quot;,&quot;宋体&quot;等等，也可以是英文字体。当然这里应该是取你电脑的字库里的字体，所以考虑程序的通用性，别搞些另类的字体或只有自己有的字体~GB2312_CHARSET你该知道啦哈。</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="keyword">void</span><span>&nbsp;MyFont::PrintfChtext(</span><span class="keyword">int</span><span>&nbsp;x,&nbsp;</span><span class="keyword">int</span><span>&nbsp;y,&nbsp;LPCTSTR&nbsp;lpszText) </span></span></li>    <li><span>{ </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CBitmap&nbsp;bitmap; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BITMAP&nbsp;bm; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SIZE&nbsp;size; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HDC&nbsp;MDC&nbsp;=&nbsp;::CreateCompatibleDC(NULL); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SelectObject(MDC,&nbsp;hGDIFont); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;::GetTextExtentPoint32(MDC,lpszText,strlen(lpszText),&amp;size); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bitmap.CreateBitmap(size.cx,&nbsp;size.cy,&nbsp;1,&nbsp;1,&nbsp;NULL); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HBITMAP&nbsp;oldBmp=(HBITMAP)SelectObject(MDC,bitmap); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SetBkColor&nbsp;&nbsp;(MDC,&nbsp;RGB(0,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0,&nbsp;&nbsp;&nbsp;0)); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SetTextColor(MDC,&nbsp;RGB(255,&nbsp;255,&nbsp;255)); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TextOut(MDC,&nbsp;0,&nbsp;0,&nbsp;lpszText,&nbsp;strlen(lpszText)); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bitmap.GetBitmap(&amp;bm); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;size.cx&nbsp;=&nbsp;(bm.bmWidth&nbsp;+&nbsp;31)&nbsp;&amp;&nbsp;(~31); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">int</span><span>&nbsp;bufsize&nbsp;=&nbsp;size.cy&nbsp;*&nbsp;size.cx; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">struct</span><span>&nbsp;{&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BITMAPINFOHEADER&nbsp;bih; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RGBQUAD&nbsp;col[2]; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}bic;&nbsp; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BITMAPINFO&nbsp;*binf&nbsp;=&nbsp;(BITMAPINFO&nbsp;*)&amp;bic;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;binf-&gt;bmiHeader.biSize&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;</span><span class="keyword">sizeof</span><span>(binf-&gt;bmiHeader); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;binf-&gt;bmiHeader.biWidth&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;bm.bmWidth; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;binf-&gt;bmiHeader.biHeight&nbsp;&nbsp;&nbsp;=&nbsp;bm.bmHeight; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;binf-&gt;bmiHeader.biPlanes&nbsp;&nbsp;&nbsp;=&nbsp;1;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;binf-&gt;bmiHeader.biBitCount&nbsp;=&nbsp;1; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;binf-&gt;bmiHeader.biCompression&nbsp;=&nbsp;BI_RGB; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;binf-&gt;bmiHeader.biSizeImage&nbsp;&nbsp;&nbsp;=&nbsp;bufsize;&nbsp; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UCHAR*&nbsp;Bits&nbsp;=&nbsp;</span><span class="keyword">new</span><span>&nbsp;UCHAR[bufsize];&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;::GetDIBits(MDC,bitmap,&nbsp;0,&nbsp;bm.bmHeight,&nbsp;Bits,&nbsp;binf,&nbsp;DIB_RGB_COLORS);&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glPixelStorei(GL_UNPACK_ALIGNMENT,&nbsp;1); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//glRasterPos2i(x,&nbsp;y);&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glWindowPos2i(x,&nbsp;y); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glBitmap(size.cx,&nbsp;size.cy,&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;Bits);&nbsp; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delete&nbsp;Bits;&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SelectObject(MDC,&nbsp;oldBmp);&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;::DeleteDC(MDC); </span></li>    <li><span>} </span></li>    <li class="alt">&nbsp;</li>    <li><span class="keyword">void</span><span>&nbsp;MyFont::PrintGDIText(GLint&nbsp;x,&nbsp;GLint&nbsp;y,&nbsp;CString&nbsp;str) </span></li>    <li class="alt"><span>{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glLoadIdentity(); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glPushAttrib(GL_CURRENT_BIT&nbsp;|&nbsp;GL_LIGHTING_BIT); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisable(GL_TEXTURE_2D);&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisable(GL_LIGHTING);&nbsp; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glColor4f(mColor[0],&nbsp;mColor[1],&nbsp;mColor[2],&nbsp;mColor[3]);&nbsp;&nbsp; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;PrintfChtext&nbsp;(x,&nbsp;y,&nbsp;str);&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glPopAttrib(); </span></li>    <li class="alt"><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>void MyFont::PrintfChtext(int x, int y, LPCTSTR lpszText){	  CBitmap bitmap;	  BITMAP bm;	  SIZE size;	  	  HDC MDC = ::CreateCompatibleDC(NULL);	  SelectObject(MDC, hGDIFont);	  	  ::GetTextExtentPoint32(MDC,lpszText,strlen(lpszText),&amp;size);	  bitmap.CreateBitmap(size.cx, size.cy, 1, 1, NULL);	  HBITMAP oldBmp=(HBITMAP)SelectObject(MDC,bitmap);	  SetBkColor  (MDC, RGB(0,     0,   0));	  SetTextColor(MDC, RGB(255, 255, 255));	  TextOut(MDC, 0, 0, lpszText, strlen(lpszText));	  bitmap.GetBitmap(&amp;bm);	  size.cx = (bm.bmWidth + 31) &amp; (~31);	  int bufsize = size.cy * size.cx;	  struct {  		      BITMAPINFOHEADER bih;			  RGBQUAD col[2]; 			 }bic; 	  BITMAPINFO *binf = (BITMAPINFO *)&amp;bic; 	  binf-&gt;bmiHeader.biSize     = sizeof(binf-&gt;bmiHeader);	  binf-&gt;bmiHeader.biWidth    = bm.bmWidth;	  binf-&gt;bmiHeader.biHeight   = bm.bmHeight;	  binf-&gt;bmiHeader.biPlanes   = 1;   	  binf-&gt;bmiHeader.biBitCount = 1;	  binf-&gt;bmiHeader.biCompression = BI_RGB;	  binf-&gt;bmiHeader.biSizeImage   = bufsize; 	  UCHAR* Bits = new UCHAR[bufsize];		  ::GetDIBits(MDC,bitmap, 0, bm.bmHeight, Bits, binf, DIB_RGB_COLORS);                                       	  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);	  //glRasterPos2i(x, y); 	  glWindowPos2i(x, y);	  glBitmap(size.cx, size.cy, 0, 0, 0, 0, Bits); 	  delete Bits;    	  SelectObject(MDC, oldBmp);  	  ::DeleteDC(MDC);}void MyFont::PrintGDIText(GLint x, GLint y, CString str){    glLoadIdentity();	glPushAttrib(GL_CURRENT_BIT | GL_LIGHTING_BIT);	glDisable(GL_TEXTURE_2D); 	glDisable(GL_LIGHTING); 	glColor4f(mColor[0], mColor[1], mColor[2], mColor[3]);  	PrintfChtext (x, y, str); 		glPopAttrib();}</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>glPushAttrib和glWindowPos2i的意义不多说了。看主体函数PrintfChtext。几个GDI函数：</p><ul>    <li>GetTextExtentPoint32用当前所选字体来计算字符串尺寸，按逻辑单位计算的高和宽都没有考虑裁剪取的情况。</li>    <li>CreateBitmap创建单位色位图。</li></ul><p>函数所做的是依据字符串大小建立一张单色位图，把该位图信息存入UCHAR数组Bits内。类似的操作在[<a target="_blank" href="http://www.zwqxin.com/archives/image-processing/bmp-operate-copy-memory.html">认识HBITMAP与Bmp操作(整内存拷贝版)</a>] 也谈过，所以细节部分譬如为什么要取宽度位8倍数等等就略过。重点是要把数组Bits交给谁。恩，glBitmap函数道出了一切。</p><p>OpenGL里除了几何对象（点、线、多边形）和纹理图像对象外，还有一种不常用的对象，位图Bitmap。一般来说这样的位图是灰度的，也就是位图矩块内每个像素只有1BIT的信息&mdash;&mdash;0 OR 1。这对文字来说正好，因为文字就是黑色（/透明）背景中的白色部分，黑白分明。颜色可以在绘制位图后用glColor设置嘛。glBitmap函数就是用来根据数据显示位图的。而该位图内可以说是被打印了GDI文字上去（用TextOut），于是最后在屏幕上的还是位图文字，且可以用glWindowPos2i调节起始点位置。就这样，完成的GDI文字从基于DC的GDI环境，显示到基于RC的OPenGL环境上。可喜可贺。</p><p>具体应用：</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt">&nbsp;</li>    <li><span class="comment">//CMAINFRAME&nbsp;&nbsp; </span></li>    <li class="alt"><span>MyFont&nbsp;mFont;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span class="comment">//初始化：&nbsp;&nbsp; </span></li>    <li><span>mFont.BuildGDIFont(</span><span class="string">&quot;宋体&quot;</span><span>,&nbsp;FW_NORMAL,&nbsp;25); </span></li>    <li class="alt"><span class="comment">//25是字体字高，控制字体大小&nbsp;；FW_NORMAL常态重的字体 </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span class="comment">//渲染阶段（RenderGLScene）&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>mFont.PrintGDIText(840,&nbsp;675,&nbsp;</span><span class="string">&quot;抖动波演示[D]&quot;</span><span>); </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span class="comment">//将在坐标X&nbsp;=&nbsp;840，&nbsp;Y&nbsp;=675位置开始绘制文字。对1024*768大小的渲染窗口中，&nbsp; </span></li>    <li><span class="comment">//即在右上角偏下</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//CMAINFRAME  MyFont mFont;     //初始化：  mFont.BuildGDIFont(&quot;宋体&quot;, FW_NORMAL, 25);//25是字体字高，控制字体大小 ；FW_NORMAL常态重的字体   //渲染阶段（RenderGLScene）     mFont.PrintGDIText(840, 675, &quot;抖动波演示[D]&quot;);   //将在坐标X = 840， Y =675位置开始绘制文字。对1024*768大小的渲染窗口中， //即在右上角偏下</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>于是，我所知道的就这么多了。阁下若有更好的建议，........求指教！</p><p>效果（分别上到下对应方法1，3，2）：</p><p><a target="_self" href="http://www.zwqxin.com/archives/opengl/opengl-font-setting-showing.html"><img alt="www.zwqxin.com 在Opengl上设置字体 显示文字" src="http://lh4.ggpht.com/_lYWT-2PnV0s/SoZTa7LS3GI/AAAAAAAAAyU/9Cak8KZEk4U/s800/erere.jpg" /></a></p><p>&nbsp;最后是MyFont类了，加上纹理字体的那张特制字体纹理。阁下若有更好的改进，........求指教！</p><p><a target="_blank" href="http://www.zwqxin.com/upload/2009/8/MyFont类byZwqXin.zip">MyFont类byZwqXin.zip</a></p>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/opengl-font-setting-showing-2.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=66</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=66&amp;key=09d344d2</trackback:ping></item><item><title>在OpenGL上设置字体和显示文字(上)</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/opengl-font-setting-showing.html</link><pubDate>Wed, 05 Aug 2009 01:32:13 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/opengl-font-setting-showing.html</guid><description><![CDATA[<p>任何一个DEMO、仿真项目、游戏，都少不了文字这种媒体。这不可不说是对图形视觉媒体的补充&mdash;&mdash;我们还有一些无法超越文字来向观众表达的心事，或是补充说明，或是感悟，或是感激。&mdash;&mdash; <a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>一般的文字不属于图形渲染部分，而属于用户界面部分，这在游戏引擎中看或许一目了然，但是在底层的图形渲染API&mdash;&mdash;OPENGL或D3D中，文字的显示&ldquo;并不是必须&rdquo;，但它是多么深深地被需要着口牙。所以，把字体设置、文字显示作为一种图形学技术而非单纯的完全我属或他属，我是这么想的。(同样，拾取也算是这样吧？[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/opengl-picking-what.html">乱弹OpenGL选择-拾取机制Ⅰ</a>])</p><p>怎么表达文字呢？在OpenGL中，我们没有什么现成的东西可用，但确实有办法让我们&ldquo;得到这种技术&rdquo;。让我最记忆深刻的是NEHE的两三篇教程（貌似都是十几课吧），讲述的就是今天的这个主题。可以到<a target="_blank" href="http://nehe.gamedev.net/">NEHE网站</a>或者在DANCINGWIND的中文翻译（见[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/tutorial-recommendation.html">搜集的优良OpenGL教程</a>] ）看看~。</p><p>而我所知道的这三种方法，前两种应该就是来自那里吧（记得~~），当时要努力完成课程DEMO，于是胡胡混混地就把相应的那两三课学了，而且把它的文字显示方法应用到自己的程序中（还经历了一段艰辛的探索史...连我当时的MyFont类也记录了这份小辛酸，现在看来，是因为当时的知识水平不够理解吧）。然后后来又一个课程DEMO，老师后来觉得我应该&ldquo;写中文&rdquo;，于是我又去探索中文字体显示方法了，并在一个开源DEMO中找到，分析代码段后就拿来主义了，至昨不曾好好考究&mdash;&mdash;这就是我所知的第三种方法。</p><p>三种方法都是三步曲：在初始化的时候&ldquo;创建字体&rdquo;[Build]，在渲染阶段&ldquo;应用字体&rdquo;（显示文字）[Print]，在程序结束或不再需要文字显示的时候&ldquo;销毁字体&rdquo;[kill]。其中前两步比较重要，着重讨论讨论哈~</p><p>1. 常规的屏幕字体打印（NormalFont）</p><p>应用得比较广，大名鼎鼎的AZURE以前的DEMO就是用这个的。NEHE教程中作为首次出现的字体显示方法，介绍应该比较全面，大家想仔细了解的话请务必从上面网址进入学习（当然还有因为我理解不透不敢乱讲的缘由）。</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span>&nbsp;</span><span class="comment">/////////一般的英语字体打印 </span></span></li>    <li><span class="keyword">void</span><span>&nbsp;MyFont::BuildGLFont(</span><span class="keyword">int</span><span>&nbsp;fontHeight) </span></li>    <li class="alt"><span>{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;HDC&nbsp;&nbsp;&nbsp;hDC&nbsp;=::GetDC(HWND_DESKTOP);&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//////就是这里搞晕了半晚 </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;</span><span class="keyword">int</span><span>&nbsp;tFontHeight&nbsp;=&nbsp;-1&nbsp;*&nbsp;fontHeight; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;NormalFontBase&nbsp;=&nbsp;glGenLists(96);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Storage&nbsp;For&nbsp;96&nbsp;Characters </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;HFONT&nbsp;font&nbsp;=&nbsp;CreateFont(&nbsp;-tFontHeight,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Height&nbsp;Of&nbsp;Font </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Width&nbsp;Of&nbsp;Font </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Angle&nbsp;Of&nbsp;Escapement </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Orientation&nbsp;Angle </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FW_BOLD,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Font&nbsp;Weight </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TRUE,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Italic </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FALSE,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Underline </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FALSE,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Strikeout </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ANSI_CHARSET,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Character&nbsp;Set&nbsp;Identifier </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OUT_TT_PRECIS,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Output&nbsp;Precision </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CLIP_DEFAULT_PRECIS,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Clipping&nbsp;Precision </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ANTIALIASED_QUALITY,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Output&nbsp;Quality </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FF_DONTCARE|DEFAULT_PITCH,&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Family&nbsp;And&nbsp;Pitch </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="string">&quot;Georgia&quot;</span><span>);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Font&nbsp;Name </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;HFONT&nbsp;oldfont&nbsp;=&nbsp;(HFONT)SelectObject(hDC,&nbsp;font);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Selects&nbsp;The&nbsp;Font&nbsp;We&nbsp;Want </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;wglUseFontBitmaps(hDC,&nbsp;32,&nbsp;96,&nbsp;NormalFontBase);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Builds&nbsp;96&nbsp;Characters&nbsp;Starting&nbsp;At&nbsp;Character&nbsp;32 </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;SelectObject(hDC,&nbsp;oldfont);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Selects&nbsp;The&nbsp;Font&nbsp;We&nbsp;Want&nbsp;to&nbsp;return&nbsp;to </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;DeleteObject(font);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;Delete&nbsp;The&nbsp;Font </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;SetBkMode(hDC,TRANSPARENT);&nbsp;&nbsp;&nbsp; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;NormalFont&nbsp;=&nbsp;</span><span class="keyword">true</span><span>;&nbsp; </span></li>    <li><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre> /////////一般的英语字体打印void MyFont::BuildGLFont(int fontHeight){   HDC   hDC =::GetDC(HWND_DESKTOP);    //////就是这里搞晕了半晚      int tFontHeight = -1 * fontHeight;   NormalFontBase = glGenLists(96);						    // Storage For 96 Characters   HFONT font = CreateFont( -tFontHeight,					// Height Of Font							  0,							// Width Of Font						 	  0,							// Angle Of Escapement							  0,							// Orientation Angle							  FW_BOLD,						// Font Weight						  	  TRUE,							// Italic							  FALSE,						// Underline							  FALSE,						// Strikeout							  ANSI_CHARSET,					// Character Set Identifier							  OUT_TT_PRECIS,				// Output Precision							  CLIP_DEFAULT_PRECIS,			// Clipping Precision							  ANTIALIASED_QUALITY,			// Output Quality							  FF_DONTCARE|DEFAULT_PITCH,	// Family And Pitch							  &quot;Georgia&quot;);					// Font Name	HFONT oldfont = (HFONT)SelectObject(hDC, font);         // Selects The Font We Want		wglUseFontBitmaps(hDC, 32, 96, NormalFontBase);			// Builds 96 Characters Starting At Character 32		SelectObject(hDC, oldfont);							// Selects The Font We Want to return to	DeleteObject(font);									// Delete The Font    	SetBkMode(hDC,TRANSPARENT);   	NormalFont = true; }</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>其中bUild的时候首先用到的是GDI的<a target="_blank" href="http://blog.csdn.net/ShowLong/archive/2006/03/20/630232.aspx">CreateFont函数</a>创建字体&mdash;&mdash;这应该是比较常用的方法，设置了关于字体的一切并选入字体后，有一步重要的操作：wglUseFontBitmaps。</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt">&nbsp;</li>    <li><span>wglUseFontBitmap </span></li>    <li class="alt"><span>为当前选中的GDI字体创建一组OpenGL显示列表位图字体&nbsp; </span></li>    <li><span>BOOL&nbsp;wglUseFontBitmap(HDC&nbsp;hDC,&nbsp;DWORD&nbsp;dwFirst,&nbsp;DWORD&nbsp;dwCount,&nbsp;DWORD&nbsp;dwListBase);&nbsp; </span></li>    <li class="alt">&nbsp;</li>    <li><span>参数 </span></li>    <li class="alt"><span>hDC </span></li>    <li><span>设备环境句柄 </span></li>    <li class="alt">&nbsp;</li>    <li><span>dwFirst </span></li>    <li class="alt"><span>用于创建显示列表字体的第一个字符的ASCII值 </span></li>    <li>&nbsp;</li>    <li class="alt"><span>dwCount </span></li>    <li><span>字符数 </span></li>    <li class="alt">&nbsp;</li>    <li><span>dwListBase </span></li>    <li class="alt"><span>第一个显示列表的名称 </span></li>    <li>&nbsp;</li>    <li class="alt"><span>返回值 </span></li>    <li><span>成功返回TRUE，否则返回FALSE&nbsp; </span></li>    <li class="alt">&nbsp;</li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>wglUseFontBitmap为当前选中的GDI字体创建一组OpenGL显示列表位图字体 BOOL wglUseFontBitmap(HDC hDC, DWORD dwFirst, DWORD dwCount, DWORD dwListBase); 参数hDC设备环境句柄dwFirst用于创建显示列表字体的第一个字符的ASCII值dwCount字符数dwListBase第一个显示列表的名称返回值成功返回TRUE，否则返回FALSE </pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p><font size="2">输入为DC，32，96以及创建的96个新显示列表的base列表。函数绘制从ASCII码为32-128的字符进入显示列表，依赖OPENGL显示列表的高速显示能力（直接从硬件拿存储区），可以方便进行字符的切换。</font></p><p><font size="2">在Print过程中，调用glCallLists就能调动起这些列表了，但是怎么决定具体要&ldquo;调动&rdquo;哪些字母呢（96个之中）？</font></p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="keyword">void</span><span>&nbsp;MyFont::PrintGLText(GLint&nbsp;x,&nbsp;GLint&nbsp;y,&nbsp;</span><span class="keyword">const</span><span>&nbsp;</span><span class="keyword">char</span><span>&nbsp;*</span><span class="keyword">string</span><span>,&nbsp;...)&nbsp; </span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">char</span><span>&nbsp;&nbsp;text[256]; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;va_list&nbsp;pArguments; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">if</span><span>&nbsp;(</span><span class="keyword">string</span><span>&nbsp;==&nbsp;NULL) </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">return</span><span>; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;va_start(pArguments,&nbsp;</span><span class="keyword">string</span><span>); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;vsprintf(text,&nbsp;</span><span class="keyword">string</span><span>,&nbsp;pArguments); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;va_end(pArguments); </span></li>    <li class="alt">&nbsp;</li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glPushAttrib(GL_LIST_BIT&nbsp;|&nbsp;GL_CURRENT_BIT&nbsp;|&nbsp;&nbsp;GL_ENABLE_BIT&nbsp;|&nbsp;GL_LIGHTING_BIT); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisable(GL_DEPTH_TEST); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisable(GL_LIGHTING); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisable(GL_TEXTURE_2D); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glColor4f(mColor[0],&nbsp;mColor[1],&nbsp;mColor[2],&nbsp;mColor[3]); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glWindowPos2i(x,&nbsp;y); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glListBase(NormalFontBase&nbsp;-&nbsp;32); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glCallLists(strlen(text),&nbsp;GL_UNSIGNED_BYTE,&nbsp;text); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glPopAttrib(); </span></li>    <li><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>void MyFont::PrintGLText(GLint x, GLint y, const char *string, ...)		char  text[256];	va_list	pArguments;	if (string == NULL)		return;	va_start(pArguments, string);	vsprintf(text, string, pArguments);	va_end(pArguments);	glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT |  GL_ENABLE_BIT | GL_LIGHTING_BIT);	glDisable(GL_DEPTH_TEST);	glDisable(GL_LIGHTING);	glDisable(GL_TEXTURE_2D);	glColor4f(mColor[0], mColor[1], mColor[2], mColor[3]);	glWindowPos2i(x, y);	glListBase(NormalFontBase - 32);	glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);	glPopAttrib();}</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>首先看函数形式&mdash;&mdash;printf形式，若想有个详细了解，可到<a target="_blank" href="http://www.daydreaming.com.cn/article/2007-5-31/1838-1.htm">这里看看</a>。简单来说，就是C时代的可变参数列。va_start - vsprintf - （va_arg）- va_end这套机制就是为了把可变参数列的内容，通过va_list&nbsp;（char*指针）一个一个从栈中取出来赋予他者&mdash;&mdash;我们的glCallLists所要接受的所有&ldquo;具体字符&rdquo;，通过base为基础的索引快速寻觅而取得对应ASCII字符的字体信息（实际是位图字体），并依照期望使其形成为&ldquo;具体字符串&rdquo;印入屏幕。</p><p>另外着重介绍的是我所添加的两个优化&mdash;&mdash;它们贯穿三种文字显示方法之中。</p><p>其一是glPushAttrib，它与glPopAttrib配合，保证了其之间的OPENGL状态设置的独立性，使其不影响该代码逻辑的前后的具体渲染状态。当然参数取<strong>GL_ALL_ATTRIB_BITS</strong>是保险点，但只要你弄清楚自己的需要，像上面这样给予特定的状态作为参数效率会更高。恩，颜色，光照，深度测试，混合&hellip;&hellip;选择与当前方法最匹配的状态而没有对状态机的后顾之忧，如同文字本身一样&mdash;&mdash;作为对象完全独立于图形渲染&ldquo;模块&rdquo;。</p><p>另一是glWindowPos2i(x,&nbsp;y)，按<a target="_blank" href="http://book.51cto.com/art/200809/88171.htm">《OpenGL编程指南》第8章</a>所说，它取代我们以前用的glRasterPos2i，不再让作为描绘对象的物体承受模型-视图-投影变换之苦[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/opengl-matrix-what.html">乱弹OpenGL中的矩阵变换(上)</a>] ，而是直接独立到OPENGL世界的出口&mdash;&mdash;屏幕坐标系，如GDI般用窗口坐标（根据屏幕像素数）来描述文字的起点位置。这同样是赋予文字的独立性，而且意义重大&mdash;&mdash;可知道，当时我用glRasterPos2i多么狼狈，好难才让文字不在场景&ldquo;里面&rdquo;乱窜。</p><p>在具体应用中,在初始化调用BuildGLFont.，在渲染阶段调用PrintGLText。譬如：</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="comment">//CMAINFRAME </span></span></li>    <li><span>MyFont&nbsp;mFont; </span></li>    <li class="alt">&nbsp;</li>    <li><span class="comment">//初始化： </span></li>    <li class="alt"><span>mFont.BuildGLFont(25);</span><span class="comment">//25是字体字高，控制字体大小 </span></li>    <li>&nbsp;</li>    <li class="alt"><span class="comment">//渲染阶段（RenderGLScene） </span></li>    <li>&nbsp;</li>    <li class="alt"><span>mFont.PrintGLText(530,&nbsp;710,&nbsp;</span><span class="string">&quot;http://www.Zwqxin.com&nbsp;-&nbsp;My&nbsp;3D&nbsp;Graphics&quot;</span><span>); </span></li>    <li>&nbsp;</li>    <li class="alt"><span class="comment">//将在坐标X&nbsp;=&nbsp;530，&nbsp;Y&nbsp;=710位置开始绘制文字。对1024*768大小的渲染窗口中，即在右上角</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//CMAINFRAMEMyFont mFont;//初始化：mFont.BuildGLFont(25);//25是字体字高，控制字体大小//渲染阶段（RenderGLScene）mFont.PrintGLText(530, 710, &quot;http://www.Zwqxin.com - My 3D Graphics&quot;);//将在坐标X = 530， Y =710位置开始绘制文字。对1024*768大小的渲染窗口中，即在右上角</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>注意，OpenGL窗口坐标系的原点在窗口的左下角，横坐标为X，竖坐标为Y，最大值在右上角。（同见[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/opengl-picking-what-2.html">乱弹OpenGL选择-拾取机制Ⅱ</a>] ）</p><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/opengl/opengl-font-setting-showing.html"><img alt="www.zwqxin.com 在Opengl上设置字体 显示文字" src="http://lh4.ggpht.com/_lYWT-2PnV0s/SoZTa7LS3GI/AAAAAAAAAyU/9Cak8KZEk4U/s800/erere.jpg" /></a><br />浏览一下效果，第一行就是了，因为我默认用的是Georgia字体(应该一般人电脑都有)，所以很漂亮</p><p>接下来会谈及其余两种方法，并比较之。真正的纹理文字是怎样弄的呢？怎样让中文字体乖乖显示？什么时候用哪种方法？我集成的MyFont类是怎样个怪样？听下回分解：</p><p><a target="_self" href="http://www.zwqxin.com/archives/opengl/opengl-font-setting-showing-2.html">在OpenGL上设置字体和显示文字(下)</a></p>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/opengl-font-setting-showing.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=65</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=65&amp;key=4c853fe3</trackback:ping></item><item><title>水效果Ⅰ - 水池</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/water-simulation-1.html</link><pubDate>Sun, 02 Aug 2009 16:42:06 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/water-simulation-1.html</guid><description><![CDATA[<p>这些天都在着手实现一些水面效果，于是还是记录一下历程吧，本Blog不是为此而存在的么。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>学期初看了橙书关于反射折射一节，心想我也能尝试一下做水的效果吗，于是就开始动手。但是专业学习上的东西实在太多了，刚起了个头，后面又被时间挤兑着了。看着自己不断地浪费时间，而这东西有点胎死腹中的味道&hellip;&hellip;委实感觉不能让悲剧重演，于是假期来临后马上继续。</p><p>DEMO的环境很简单，也是一般尝试水效的朋友必用的天空盒+水池。但是我这人就有点怪，喜欢瞎折腾，于是就不想只是弄个矩形水池来契合矩形水网格，来个特别形状的如何？&mdash;&mdash;譬如五角星形的。网格我可不想弄成怪模样的，毕竟连索引起来也困难，还是矩形网格好。那么，即使我画出五角星形水池，它怎么与矩形网格配合呢？</p><p>首先是构造水池。这纯粹是考平面几何的功夫。好吧我还是很菜的，搞了大半天才画出来。把水池作为一个水池类对象看待的话，其属性除了中心位置和绕中心的旋转外，应该还有：角的数目（譬如五角星形是5，六角星形是6，如此），内接圆外接圆半径，池宽比例（池的壁厚由这个比例决定）等等：</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="comment">// </span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;GLuint&nbsp;PoolTex; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;Color&nbsp;PoolColor; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;GLint&nbsp;AngleNum;&nbsp;&nbsp;</span><span class="comment">//池型等分之角数，例如五角型，六角型 </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;Radius_inscribe;&nbsp;&nbsp;</span><span class="comment">//池型的内接圆半径 </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;Radius_circumscribe;&nbsp;&nbsp;</span><span class="comment">//池型的外接圆半径 </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;Height;&nbsp;</span><span class="comment">//池高 </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;Width_Scale;&nbsp;&nbsp;</span><span class="comment">//池宽比例 </span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//    GLuint PoolTex;	Color PoolColor;	GLint AngleNum;  //池型等分之角数，例如五角型，六角型	float Radius_inscribe;  //池型的内接圆半径	float Radius_circumscribe;  //池型的外接圆半径	float Height; //池高	float Width_Scale;  //池宽比例</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/opengl/water-simulation-1.html"><img height="286" alt="http://ww.zwqxin.com 水效" width="471" src="http://lh6.ggpht.com/_lYWT-2PnV0s/SoaD1xBYCfI/AAAAAAAAAzo/-AWAWi1Mneo/s800/WaterSim815Snap2.jpg" /></a><br /><a target="_self" href="http://www.zwqxin.com/archives/opengl/water-simulation-1.html"><img height="286" alt="http://ww.zwqxin.com 水效" width="471" src="http://lh4.ggpht.com/_lYWT-2PnV0s/SoaD3KcFn3I/AAAAAAAAAzs/AIPVvbRQqIs/s800/WaterSim815Snap2-1.jpg" /></a></p><p>这样通过调节这些属性能改变池的形状，当初放弃建模的想法还是正确的。那么加入水网格之后又如何呢？</p><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/opengl/water-simulation-1.html"><img height="286" alt="http://ww.zwqxin.com 水效" width="471" src="http://lh6.ggpht.com/_lYWT-2PnV0s/SoaGqYsCbfI/AAAAAAAAA0w/pe-LeuhiA7A/s800/WaterSim815Snap4-1.jpg" /></a></p><p>&nbsp;可见要得到一个&ldquo;水池&rdquo;就需要把五角星外的部分割掉~事实上我们可以利用shader（想不到这么快就上场了），要传入的是形状的角顶点，譬如五角星的五个角的坐标，以及这五个角到水池中心（物理坐标原点）的向量Vi，还有中心到角的距离D[外接圆半径]，每个角的角度ANG的COS(ANG)。计算的时候让Y值为0即可。因为暂时程序就用五角星了，所以我也不在shader上扩展，直接把这5 * 2个量传入shader。在像素shader里经过插值的顶点坐标成为&ldquo;像素坐标&rdquo;，求得角点坐标与这个像素坐标之间的向量，与传入的Vi分别求内积得到夹角的COS，并与传入的COS(ANG)比较，若前者大，且该&ldquo;像素坐标&rdquo;到原点的距离比D小，则证明该像素点在五角星内部&hellip;&hellip;对于此测试不通过的像素discard，最后稍微调整边界等，就能得到如下结果了：</p><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/opengl/water-simulation-1.html"><img height="286" alt="http://ww.zwqxin.com 水效" width="471" src="http://lh5.ggpht.com/_lYWT-2PnV0s/SoaGqJCuzLI/AAAAAAAAA0s/5LWL92QTv14/s800/WaterSim815Snap4.jpg" /></a><br /><a target="_self" href="http://www.zwqxin.com/archives/opengl/water-simulation-1.html"><img height="286" alt="http://ww.zwqxin.com 水效" width="471" src="http://lh6.ggpht.com/_lYWT-2PnV0s/SoaGpm0pP9I/AAAAAAAAA0o/hLzdVbtwQyE/s800/WaterSim815Snap4-2.jpg" /></a></p><p>在线框图中可以看到对应的顶点已经被割裂了。这样池水就在水池里面啦~</p><p>最后解释一下网格的纹理。很明显这里有张水纹理直接贴上去，但也有其他的&mdash;&mdash;反射和折射。详细的实现思路可参见我的这篇日志：</p><p>[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-reflect-and-refract.html">Shader快速复习：Reflection And Refraction(反射与折射)</a>]</p><p>shader实现是一致的，关键只是我们在程序中构建一个CUBEMAP而已。关于CUBEMAP的创建就不多说了，网路上到处都有，经典的是NVIDIA这篇~ <a class="l" target="_blank" onmousedown="return clk(0,'','','res','1','')" href="http://developer.nvidia.com/object/cube_map_ogl_tutorial.html"><font size="3"><font color="#cc0033">Cube Map</font><font color="#2200cc"> OpenGL Tutorial</font></font></a></p><p>下篇继续，见 <a target="_self" href="http://www.zwqxin.com/archives/opengl/water-simulation-2.html">水效果Ⅱ -&nbsp;涟漪</a></p>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/water-simulation-1.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=67</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=67&amp;key=e41d354c</trackback:ping></item></channel></rss>
