<?xml version="1.0" encoding="UTF-8" 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</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>Tue, 07 Sep 2010 23:43:03 +0800</pubDate><item><title>显示本站所有图片</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/Way/show-all-picture.html</link><pubDate>Tue, 30 Mar 2010 14:45:44 +0800</pubDate><guid>http://www.zwqxin.com/archives/Way/show-all-picture.html</guid><description><![CDATA[<p>&nbsp;</p><p><strong><span style="color: #ff0000"><span style="font-size: xx-large">显示本站所有图片</span></span></strong></p><p>在你的电脑上显示本小站(以及其他众多&quot;同盟&quot;网站)的网站图片的方法:</p><blockquote><p><strong>1.下载本压缩包:</strong><a target="_blank" href="http://www.zwqxin.com/upload/2009/7/12480932800.rar"><strong>12480932800.rar</strong></a></p><p><strong>2.解压后双击打开picasa.bat文件<br />(如果防火墙有提示,选择允许修改.放心,这是绝对安全的操作)</strong></p><p><strong>3.重新打开浏览器,所有图片能正常显示</strong></p></blockquote><p>&nbsp;</p><p>本方法即 修改hosts文件 ，安全无副作用。<br />修改后绝不会影响你的其他上网体验，只是将工信部屏蔽的域名*.ggpht.com指向其真实IP，请放心操作。[<a target="_blank" href="http://xc84.com/google-picasa-album-was-a-harmonious-solution">小程故事多</a>]</p><p style="text-align: center"><img alt="" src="http://www.thewwwblog.com/images/google/picasa-logo.png" /></p><p>PICASA相册是GOOGLE旗下的产品，因为一些原因不小心被GFW了，让我们群众受苦了。</p><p>本人申明：本站所链图片均为自家上传，绝无任何可疑的图片。</p><p style="text-align: right">方法由[&nbsp;ChenMJ's bOx...+終卷+&nbsp;]<a target="_blank" href="http://jobox.blogbus.com/logs/42690443.html">Picasa相册图片不显示的解决办法</a>&nbsp;提供</p><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/Way/show-all-picture.html" target="_blank">继续阅读《显示本站所有图片》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/Way.html">心途</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=%E6%95%99%E7%A8%8B">教程</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/Way/show-all-picture.html#comment" target="_blank">添加评论</a>(2)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/tutorial-recommendation.html">搜集的优良OpenGL教程</a> (2009-1-18 14:12:37)  </li></ul>]]></description><category>心途</category><comments>http://www.zwqxin.com/archives/Way/show-all-picture.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=59</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=59&amp;key=852246bc</trackback:ping></item><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><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html" target="_blank">继续阅读《软阴影的实现尝试Ⅱ》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/opengl.html">OpenGL技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=OpenGL">OpenGL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=shader">shader</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=Shadow">Shadow</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E7%BA%B9%E7%90%86">纹理</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html#comment" target="_blank">添加评论</a>(2)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html">软阴影的实现尝试Ⅰ</a> (2010-2-11 10:51:26)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html">shader复习与深入：Diffraction(衍射)</a> (2009-9-25 15:3:9)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html">shader复习与深入：Anisotropic Lighting(各向异性光照)</a> (2009-9-22 20:30:25)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html">shader复习与深入：Normal Map(法线贴图)Ⅱ</a> (2009-9-16 14:43:41)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html">shader复习与深入：Normal Map(法线贴图)Ⅰ</a> (2009-9-15 23:29:17)  </li></ul>]]></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><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html" target="_blank">继续阅读《软阴影的实现尝试Ⅰ》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/opengl.html">OpenGL技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=OpenGL">OpenGL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=shader">shader</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=Shadow">Shadow</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html#comment" target="_blank">添加评论</a>(0)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html">软阴影的实现尝试Ⅱ</a> (2010-2-13 10:29:39)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html">shader复习与深入：Diffraction(衍射)</a> (2009-9-25 15:3:9)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html">shader复习与深入：Anisotropic Lighting(各向异性光照)</a> (2009-9-22 20:30:25)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html">shader复习与深入：Normal Map(法线贴图)Ⅱ</a> (2009-9-16 14:43:41)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html">shader复习与深入：Normal Map(法线贴图)Ⅰ</a> (2009-9-15 23:29:17)  </li></ul>]]></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>shader复习与深入：Diffraction(衍射)</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html</link><pubDate>Fri, 25 Sep 2009 15:03:09 +0800</pubDate><guid>http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html</guid><description><![CDATA[<p>既然都提到光的波动性了，实时渲染图形学里与之关系最大的衍射效果（Diffraction）就不得不马上拿上水面了。释放亮丽的Rainbow吧。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>与本文相关的光照Shader技术：<br />[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-per-pixel-lighting-shader.html">Shader快速复习：Per Pixel Lighting(逐像素光照)</a>] <br />[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html">shader复习与深入：Anisotropic Lighting(各向异性光照)</a>] <br />[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-reflect-and-refract.html">Shader快速复习：Reflection And Refraction(反射与折射)</a>] <br />[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-cube-mapping-shader.html">Shader快速复习：Cube Mapping(立方环境贴图)</a>]</p><p>也是在差不多一年前，虚拟物理实验室项目里，我接受的第一个任务就是实现衍射效果，然后后面还实现过色散效果。不过那是圆孔衍射，而且我只需要把参数可控的、颗粒感的泊松斑表现出来，不涉及光学规律的模拟；色散效果同样是只需要一条参数可控的、漂亮的七彩折射光线。但因为需要设置参数，我还是上网温习了不少光学知识 - -，所以也知道这些变换的效果在现实中都可算是衍射的产物。太阳光中每种色光具有不同的波长，导致了区别；光具有波动性，导致了衍射。</p><p><a target="_blank" href="http://www.dgp.toronto.edu/people/stam/reality/Research/pdf/diff.pdf">《Diffraction Shaders》</a>是Jos Stam提出这种在实时渲染中应用光波动性的SIGGRAPH文章。里面颇算艰深的数学推理之后，提出了Diffraction shader这种&ldquo;算法模型&rdquo;，然后，在Nvidia的Gems 1中，提出了该模型的简化版本。</p><p>如果一个表面有整齐的微小沟槽，光照情况会变成如何呢？在高三我们或许学过光的二性：粒子性与波动性。基本Phong光照模型中，光被认为是持续的直线，能发生反射折射等，是因为把光抽象成了不断发射的粒子流，物质无外力非静止的话自然就是直线传播了。但光也有其波动性的一面（或者说，直线传播只是整体概念，局部放大它就是如波般运动了）。它们分别对应宏观现象（反射折射etc）和微观现象（干涉衍射），所以当研究到微观现象的时候，如Phong光照模型等就不合适了，要用一种&ldquo;认识到&rdquo;光的波动性的模型。什么时候会遭到&ldquo;微观&rdquo;呢？譬如衍射，当光传播过程中遇到的障碍物的线性很小（比光的波长还小或相差不大的时候），就会绕行&hellip;&hellip;</p><p>我在上篇[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html">shader复习与深入：Anisotropic Lighting(各向异性光照)</a>]中提到了该情况下光路的变化，形成扩散的高光（用的是the most significant&nbsp;light-reflect来模拟，但实际是光的四散），并没有说四散的光之间的相互影响。实际上，反射光线（更准确地说是反射的球面光波）之间因交叠而会互相影响（干涉），波之间彼此加合或抵消形成亮纹暗纹（与相位、光程相关），同时因为各色光的光波长不一样（影响光程），造成更复杂的交杂情况。如果这里也要挑选一种the most significant的情况的话，应该是相位相同的光之波。因为相位相反的光波会抵消，相当于都消失了；有一定相位差的则会按其程度增加波之间抵消的程度&mdash;&mdash;会逐渐衰弱下去。所以可以假设惟有相位相同的波有可能进入我们的眼睛，然后考虑它们的光程：</p><p style="text-align: center"><img alt="" src="http://http.developer.nvidia.com/GPUGems/elementLinks/fig08-04.jpg" /><br />（from NVidia）</p><p style="text-align: left">如果还稍微记得高中所学的几何和光学知识（光程应该是其波长的整数倍才能最终有&ldquo;合体效果&rdquo;，导致光程变化的是反射时刻），就明白这个条件：（sin <em><font face="symbol">q</font></em> <sub>1</sub> - sin <em><font face="symbol">q</font></em> <sub>2</sub>）*d = N* lambda（N为任意正整数，d是波在沟槽表面的间距）就是我们想要的。我们要找出所有满足这个条件的波长lambda，相同波长的波的振幅总和就是最终进入我们眼睛的光的亮度。这就跑出了几个问题：</p><p>1. 七彩的表面</p><p>这种Diffraction最具体的表现样例就是CD背面：其表面呈现各种色泽且某位置的具体颜色随光线、视线变化而变化。不同的颜色对应不同的波长，我们看到该位置上的该颜色是因为该颜色对应的波长满足上述条件，没有消失且很可能亮度得到加强并进入我们的眼睛。因为sin <em><font face="symbol">q</font></em> <sub>1</sub> - sin <em><font face="symbol">q</font></em> <sub>2</sub>是随视线向量和光源向量变化的，所以当这些向量变化时满足条件的波长就会变化（即该位置反射回来的绿色光可能在稍倾斜CD前能加强并到达我们的眼睛，倾斜CD后其波长可能就不满足要求而消失或在到达我们眼睛前衰弱到不能被察觉的情况，这时我们看到该位置上是红色是紫色反正就没有绿色的成分了[对吗]）。反正我们能看到的都是其lambda通过（sin <em><font face="symbol">q</font></em> <sub>1</sub> - sin <em><font face="symbol">q</font></em> <sub>2</sub>）*d = N* lambda考验的光线。</p><p>2. lambda与颜色的转换</p><p>lambda对应颜色，前者是不可实际感知的量，后者才是而且是计算机图形学里最重要的感知量。可见光的波长lambda大概在400-700多之间，在[<a target="_blank" href="http://www.zwqxin.com/archives/image-processing/image-hsl-hsv.html">图像色彩空间与HSL/HSV</a>] 里提过真实颜色的范围，但没有什么确定的范围（这里不能直接用0-255）；况且RGB颜色有三通道，怎么进行这种1&rarr;3的转换呢？其实最好还是给一张检索纹理，用归一化的波长去检索这个纹理得出检索位置的RGB。确实有这么一个彩虹图：</p><p style="text-align: center"><img alt="" src="http://lh4.ggpht.com/_lYWT-2PnV0s/SsYCqweKlhI/AAAAAAAABGE/MZ2Sf6QglFI/s800/Snap910025.jpg" /><br />(From Nvidia)</p><p>那么里面蕴涵的公式是什么呢？在下面shader的lambdatoRGB函数给出了端倪：</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>vec3&nbsp;lambdatoRGB(</span><span class="keyword">float</span><span>&nbsp;lambda,&nbsp;</span><span class="keyword">float</span><span>&nbsp;contrlW) </span></li>    <li class="alt"><span>{ </span></li>    <li><span>&nbsp;</span><span class="comment">//光的波长大概在400um&nbsp;-&nbsp;700um </span></li>    <li class="alt"><span>&nbsp;</span><span class="comment">//把传入的lambda在此区间normalize </span></li>    <li><span>&nbsp; </span></li>    <li class="alt"><span>&nbsp;</span><span class="keyword">float</span><span>&nbsp;vLambda&nbsp;=&nbsp;lambda&nbsp;-&nbsp;400.0&nbsp;/&nbsp;300.0; </span></li>    <li><span>&nbsp; </span></li>    <li class="alt"><span>&nbsp;vec3&nbsp;bumpvec&nbsp;=&nbsp;vec3(vLambda)&nbsp;-&nbsp;vec3(0.75,&nbsp;0.5,&nbsp;0.25); </span></li>    <li><span>&nbsp; </span></li>    <li class="alt"><span>&nbsp;bumpvec&nbsp;=&nbsp;contrlW&nbsp;*&nbsp;bumpvec&nbsp;*&nbsp;bumpvec; </span></li>    <li><span>&nbsp; </span></li>    <li class="alt"><span>&nbsp;</span><span class="keyword">return</span><span>&nbsp;max(vec3(1.0)&nbsp;-&nbsp;bumpvec,&nbsp;vec3(0.0));&nbsp; </span></li>    <li><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//vec3 lambdatoRGB(float lambda, float contrlW){ //光的波长大概在400um - 700um //把传入的lambda在此区间normalize  float vLambda = lambda - 400.0 / 300.0;  vec3 bumpvec = vec3(vLambda) - vec3(0.75, 0.5, 0.25);  bumpvec = contrlW * bumpvec * bumpvec;  return max(vec3(1.0) - bumpvec, vec3(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>对应上面光谱分明的彩虹图，归一化到（0，1）的波长lambda与结果并非线性关系：result = 1 - C * lambda` * lambda`。其中C是调节系数，lambda` = vec3(lambda) - vec3(0.75,0.5,0.25)。但这表明可见光中红色光波长最大，紫色最小。这个函数是Opengl Shading Language（橙书）的Diffraction一章里取的，实际上直接用彩虹纹理检索可能更好。</p><p>3. sin <em><font face="symbol">q</font></em> <sub>1</sub> - sin <em><font face="symbol">q</font></em> <sub>2</sub>怎么算</p><p>这个我是一头雾水，它与视线向量、光源向量相关（或者说，它与半向量有关），但是直接算出来很麻烦。在GEMS 1和橙书中，TdotH == sin <em><font face="symbol">q</font></em> <sub>1</sub> - sin <em><font face="symbol">q</font></em> <sub>2</sub>，直观上我很难想明白，无奈对BRDF的理论不熟悉，<a target="_blank" href="http://www.dgp.toronto.edu/people/stam/reality/Research/pdf/diff.pdf">《Diffraction Shaders》</a>里也没找着这样做的根据（其实是看不懂啊哈），但是就结果来看的确TdotH&nbsp;可代替 sin <em><font face="symbol">q</font></em> <sub>1</sub> - sin <em><font face="symbol">q</font></em> <sub>2</sub>啊。但是又要顶点切线数据了，这可真够头疼恩。</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">//&nbsp;Diffraction&nbsp;shader&nbsp;&nbsp;www.<a href="http://www.zwqxin.com/">ZwqXin</a>.com </span></span></li>    <li>&nbsp;</li>    <li class="alt"><span class="comment">//&nbsp;vertex&nbsp;shader </span></li>    <li><span>uniform&nbsp;vec4&nbsp;lightpos; </span></li>    <li class="alt"><span>uniform&nbsp;vec4&nbsp;eyepos; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>attribute&nbsp;vec3&nbsp;rm_tangent; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>varying&nbsp;vec3&nbsp;tangent; </span></li>    <li><span>varying&nbsp;vec3&nbsp;norm; </span></li>    <li class="alt"><span>varying&nbsp;vec3&nbsp;halfVec; </span></li>    <li>&nbsp;</li>    <li class="alt"><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>) </span></li>    <li><span>{ </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;norm&nbsp;=&nbsp;normalize(gl_NormalMatrix&nbsp;*&nbsp;gl_Normal); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;tangent&nbsp;=&nbsp;normalize(gl_NormalMatrix&nbsp;*&nbsp;rm_tangent);&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;pos&nbsp;=&nbsp;gl_ModelViewMatrix&nbsp;*&nbsp;gl_Vertex; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;vlightpos&nbsp;=&nbsp;(gl_ModelViewMatrix&nbsp;*&nbsp;lightpos).xyz;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;veyepos&nbsp;=&nbsp;(gl_ModelViewMatrix&nbsp;*&nbsp;eyepos).xyz; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;vlightdir&nbsp;=&nbsp;normalize(vlightpos&nbsp;-&nbsp;pos.xyz); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;veyedir&nbsp;=&nbsp;normalize(veyepos&nbsp;-&nbsp;pos.xyz); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//h&nbsp;=&nbsp;normalize(l&nbsp;+&nbsp;e) </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;halfVec&nbsp;=&nbsp;(veyedir&nbsp;+&nbsp;vlightdir); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;gl_Position&nbsp;=&nbsp;gl_ModelViewProjectionMatrix&nbsp;*&nbsp;gl_Vertex; </span></li>    <li class="alt"><span>} </span></li>    <li>&nbsp;</li>    <li class="alt"><span class="comment">//fragment&nbsp;shader </span></li>    <li><span>uniform&nbsp;vec4&nbsp;baseColor; </span></li>    <li class="alt"><span>uniform&nbsp;</span><span class="keyword">float</span><span>&nbsp;GroovesSpacing; </span></li>    <li><span>uniform&nbsp;</span><span class="keyword">float</span><span>&nbsp;ControlBumpWidth; </span></li>    <li class="alt">&nbsp;</li>    <li><span>varying&nbsp;vec3&nbsp;tangent; </span></li>    <li class="alt"><span>varying&nbsp;vec3&nbsp;norm; </span></li>    <li><span>varying&nbsp;vec3&nbsp;halfVec; </span></li>    <li class="alt">&nbsp;</li>    <li><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>) </span></li>    <li class="alt"><span>{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;vnorm&nbsp;=&nbsp;normalize(norm); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;vtangent&nbsp;=&nbsp;normalize(tangent); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;vhalf&nbsp;=&nbsp;halfVec;&nbsp;&nbsp;&nbsp;</span><span class="comment">//传说不要normalize </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;lambda&nbsp;=&nbsp;0.0; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;spectrumColor&nbsp;=&nbsp;vec3(0.0); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;TdotH&nbsp;=&nbsp;abs(dot(vtangent,&nbsp;vhalf)); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">for</span><span>(</span><span class="keyword">int</span><span>&nbsp;i&nbsp;=&nbsp;1;&nbsp;i&nbsp;&lt;=&nbsp;7;&nbsp;++i) </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;{ </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lambda&nbsp;=&nbsp;GroovesSpacing&nbsp;*&nbsp;TdotH&nbsp;/&nbsp;</span><span class="keyword">float</span><span>(i); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;spectrumColor&nbsp;+=&nbsp;lambdatoRGB(lambda,&nbsp;ControlBumpWidth);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;} </span></li>    <li class="alt"><span>&nbsp; </span></li>    <li><span>&nbsp;&nbsp;gl_FragColor&nbsp;=&nbsp;&nbsp;baseColor&nbsp;*&nbsp;vec4(spectrumColor,1.0)&nbsp;;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>// Diffraction shader  www.<a href="http://www.zwqxin.com/">ZwqXin</a>.com// vertex shaderuniform vec4 lightpos;uniform vec4 eyepos;attribute vec3 rm_tangent;varying vec3 tangent;varying vec3 norm;varying vec3 halfVec;void main(void){   norm = normalize(gl_NormalMatrix * gl_Normal);   tangent = normalize(gl_NormalMatrix * rm_tangent);       vec4 pos = gl_ModelViewMatrix * gl_Vertex;   vec3 vlightpos = (gl_ModelViewMatrix * lightpos).xyz;     vec3 veyepos = (gl_ModelViewMatrix * eyepos).xyz;       vec3 vlightdir = normalize(vlightpos - pos.xyz);    vec3 veyedir = normalize(veyepos - pos.xyz);        //h = normalize(l + e)    halfVec = (veyedir + vlightdir);            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;}//fragment shaderuniform vec4 baseColor;uniform float GroovesSpacing;uniform float ControlBumpWidth;varying vec3 tangent;varying vec3 norm;varying vec3 halfVec;void main(void){   vec3 vnorm = normalize(norm);   vec3 vtangent = normalize(tangent);   vec3 vhalf = halfVec;   //传说不要normalize       float lambda = 0.0;    vec3 spectrumColor = vec3(0.0);        float TdotH = abs(dot(vtangent, vhalf));        for(int i = 1; i &lt;= 7; ++i)    {      lambda = GroovesSpacing * TdotH / float(i);          spectrumColor += lambdatoRGB(lambda, ControlBumpWidth);            }   gl_FragColor =  baseColor * vec4(spectrumColor,1.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>我把算法弄到fragment shader里了，这样看上去更和谐点，N取1-7其实是少了点，但多了也无谓。</p><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html"><img alt="Diffraction  http://www.zwqxin.com " src="http://lh5.ggpht.com/_lYWT-2PnV0s/SsYCiauxVZI/AAAAAAAABF0/ilsFFcgSRcU/s800/Snap0910021.jpg" /></a><br /><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html"><img alt="Diffraction  http://www.zwqxin.com " src="http://lh5.ggpht.com/_lYWT-2PnV0s/SsYCinygMII/AAAAAAAABF4/uOkj1mJjiYQ/s800/Snap0910022.jpg" /></a></p><p>对于物体上完全无光线能反射进我们眼睛的位置是黑色的，为了让结果漂亮点，自己又多贴了层CubeMAP[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-cube-mapping-shader.html">Shader快速复习：Cube Mapping(立方环境贴图)</a>] 。如果完全表现Diffraction的效果，高光部分也不要忽略，于是又加多一层Ansotropic lighting[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html">shader复习与深入：Anisotropic Lighting(各向异性光照)</a>] ：</p><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html"><img alt="Diffraction  http://www.zwqxin.com " src="http://lh4.ggpht.com/_lYWT-2PnV0s/SsYCjjinPAI/AAAAAAAABGA/BdDjMF23xQ0/s800/Snap0910023.jpg" /></a></p><p>效果还不错吧？</p><p style="text-align: right">参考：GPU GEMS1 &amp; OpenGL Shading Language</p><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html" target="_blank">继续阅读《shader复习与深入：Diffraction(衍射)》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/shaderglsl.html">Shader技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=GLSL">GLSL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=shader">shader</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E5%85%89%E7%85%A7">光照</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html#comment" target="_blank">添加评论</a>(0)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html">软阴影的实现尝试Ⅱ</a> (2010-2-13 10:29:39)  </li><li><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html">软阴影的实现尝试Ⅰ</a> (2010-2-11 10:51:26)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html">shader复习与深入：Anisotropic Lighting(各向异性光照)</a> (2009-9-22 20:30:25)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html">shader复习与深入：Normal Map(法线贴图)Ⅱ</a> (2009-9-16 14:43:41)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html">shader复习与深入：Normal Map(法线贴图)Ⅰ</a> (2009-9-15 23:29:17)  </li></ul>]]></description><category>Shader技术</category><comments>http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=82</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=82&amp;key=c14b9361</trackback:ping></item><item><title>shader复习与深入：Anisotropic Lighting(各向异性光照)</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html</link><pubDate>Tue, 22 Sep 2009 20:30:25 +0800</pubDate><guid>http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html</guid><description><![CDATA[<p>&nbsp;Anisotropic Lighting(各向异性光照)是一种模拟有大量细小齐整沟槽（grooves）的表面的光照情况的图形学技术。在这种表面，光产生的效果是普通的图形学光照模型所无法模拟的。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>普通的图形学光照模型：[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-per-pixel-lighting-shader.html">Shader快速复习：Per Pixel Lighting(逐像素光照)</a>]</p><p>大概是一年前了，虚拟物理实验室项目工作中，为了模拟透镜表面那种粘性的高光效果，尝试用GLSL shader实现Anisotropic Lighting，参考资料是这篇文章（<a target="_blank" href="http://www.bluevoid.com/opengl/sig00/advanced00/notes/node159.html">Anisotropic Lighting </a>）以及NVIDIA SDK的一个<a target="_blank" href="http://http.download.nvidia.com/developer/SDK/Individual_Samples/DEMOS/Direct3D9/src/HLSL_Aniso/docs/HLSL_Aniso.pdf">HLSL DEMO</a>。</p><p>其实最实在的样例还是CD背面（为什么？google!）。在现实生活中，镜面高光的产生自然跟接触面&ldquo;是个镜面&rdquo;有关，事实上，把任何一个&ldquo;光-物体&rdquo;接触处&ldquo;微观&rdquo;到一定程度，都可以是一个平面，接触点只要恰好在这个平面上就好了，就有镜面光了（即使那是多么微不足道）。关键在于，对于有微小沟槽的表面，一旦光&ldquo;陷入&rdquo;了沟槽，就会被这微小沟槽的&ldquo;无数法线&rdquo;搞晕：该沿那条法线（作为中线）反射出去呢（能不能反射得出去还是另一个问题）？光它自己又有粒子性又有波动性，故增加了它必须&ldquo;考虑&rdquo;的烦恼。其实我们外人来想就觉得这样很自然：光被叭喇叭喇地拆碎，然后沿着不同的法线四散出去。（至于另一问题：反弹来反弹去始终是要出去的，除非那接触物很爱吸光。）这样的结果是一束光进、好多束光出，形成扩散的高光（然后根据能量首恒可知，扩散的高光不会有一进一出时那么亮，但照亮物体的范围变大了）。当然，出来光的还有diffuse成分嘛。</p><p>当然了，以上只是我当时看完那篇文章后对模型的YY，实际发生了什么最好请示学光电的人。但大概情况就是这样，而该文章给出了更强的YY（其实是抽象假设喇）：从微小沟槽内的法线形成的法线平面上，选择一个方向作为the most significant Normal（最具代表性/最重要的法线？），然后按镜面反射公式反射出来的光线方向R作为the most significant light reflection 。首先怎么选那个法线？文章说是光源向量在法线平面的投影喔。这样麻烦了点，于是文章又直接抛出以下公式让我们计算出NdotL和VdotR，还记得吗，它们就是Phong模型里头diffuse和specular成分的原材料（VdotR == NdotH）[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-per-pixel-lighting-shader.html">Shader快速复习：Per Pixel Lighting(逐像素光照)</a>] 。具体怎么根据上面the most significant light reflection的理论推导出来的，有兴趣的同学找[Banks94]和[Stalling97]来看吧（我是不知道怎么找了~顺带一提这是某AMD文章<a target="_blank" href="http://developer.amd.com/media/gpu_assets/ShaderX_PerPixelAniso.pdf">Per-Pixel Strand Based Anisotropic Lighting</a>里提及的）。</p><p style="text-align: center"><img alt="" src="http://lh5.ggpht.com/_lYWT-2PnV0s/SsTD0C8YA9I/AAAAAAAABAg/e5_E7aSib9U/s800/Snap2009100113.jpg" /></p><p>上式中只需要计算LdotT和VdotT，L、V是光源向量和视向量，T则麻烦，它是沟槽的方向向量。有人把这两个公式&ldquo;变成&rdquo;纹理检索过程：LdotT和VdotT分别作为UV检索的S和T坐标，出来的是NdotL和VdotR。(这方法好，很好很强大 - -)注意我们只要一张RGB或RGBA格式的图就够了，毕竟我们只需要两个通道。对第一图只需要一个T方向检索量（LdotT）就够了，第二图则两个一起上，而且S、T可互换（式子[==纹理]很对称嘛）。</p><p style="text-align: center"><img alt="" src="http://lh6.ggpht.com/_lYWT-2PnV0s/SsTPNEExbdI/AAAAAAAABBA/rY306ByT5tI/s800/Snap2009100114.jpg" /><br />（from <a target="_blank" href="http://developer.amd.com/media/gpu_assets/ShaderX_PerPixelAniso.pdf">Per-Pixel Strand Based Anisotropic Lighting</a>）</p><p>在说一次：T很麻烦，它是沟槽的方向向量。对CD那种圆切线还好说，很多物件的切线很难得到的啊（这也是NormalMap的难处[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html">shader复习与深入：Normal Map(法线贴图)Ⅰ</a>] ）。前面提到NVidia有个SDK sample，弄的也是Anisotropic Lighting，不过就很简洁：不要用T了，用LdotN和HdotN来检索纹理吧。它明显没有阐述这里头的数学原理，也就给了一张新的Texture：</p><p style="text-align: center"><img alt="" src="http://lh5.ggpht.com/_lYWT-2PnV0s/SsTPSTFTQZI/AAAAAAAABBE/58nBnCEeUYg/s800/Snap2009100115.jpg" /><br />（from <a target="_blank" href="http://http.download.nvidia.com/developer/SDK/Individual_Samples/DEMOS/Direct3D9/src/HLSL_Aniso/docs/HLSL_Aniso.pdf">NV SDK Anisotropic Lighting</a>）</p><p>注意这与上图是颇有区别的，但我不好推测这是代表个啥公式，但它就能方便地用LdotN（T）和HdotN（S）来检索。其中N是顶点法向量（不是那个the most significant哦），H是半向量。然后我也不知道它出来的是不是就是NdotL和VdotR（这个N和R都是the most significant），文章讲得很模糊。但sample的效果很有Anisotropic Lighting的feel。我上面提及去年做的那个透镜表面粘光效果就是直接用这个弄成的。今天重新修整了这个shader：</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">//www.<a href="http://www.zwqxin.com/">ZwqXin</a>.com&nbsp;&nbsp;Anisotropic&nbsp;lighting </span></span></li>    <li>&nbsp;</li>    <li class="alt"><span class="comment">//vertex&nbsp;shader </span></li>    <li><span>uniform&nbsp;vec4&nbsp;lightpos; </span></li>    <li class="alt"><span>uniform&nbsp;vec4&nbsp;eyepos; </span></li>    <li><span>varying&nbsp;vec2&nbsp;texCoord0; </span></li>    <li class="alt">&nbsp;</li>    <li><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>) </span></li>    <li class="alt"><span>{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;norm&nbsp;=&nbsp;normalize(gl_NormalMatrix&nbsp;*&nbsp;gl_Normal); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;pos&nbsp;=&nbsp;gl_ModelViewMatrix&nbsp;*&nbsp;gl_Vertex; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;vlightpos&nbsp;=&nbsp;(gl_ModelViewMatrix&nbsp;*&nbsp;lightpos).xyz;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;veyepos&nbsp;=&nbsp;(gl_ModelViewMatrix&nbsp;*&nbsp;eyepos).xyz; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;vlightdir&nbsp;=&nbsp;normalize(vlightpos&nbsp;-&nbsp;pos.xyz); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;veyedir&nbsp;=&nbsp;normalize(veyepos&nbsp;-&nbsp;pos.xyz); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//h&nbsp;=&nbsp;normalize(l&nbsp;+&nbsp;e) </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;halfVec&nbsp;=&nbsp;normalize(veyedir&nbsp;+&nbsp;vlightdir);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;texCoord0.x&nbsp;=&nbsp;2.0&nbsp;*&nbsp;dot(halfVec,&nbsp;norm)&nbsp;-&nbsp;1.0;</span><span class="comment">//每个顶点的值随视线变化而变化&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;texCoord0.y&nbsp;=&nbsp;2.0&nbsp;*&nbsp;dot(vlightdir,&nbsp;norm)&nbsp;-&nbsp;1.0;</span><span class="comment">//每个顶点拥有固定值&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;gl_Position&nbsp;=&nbsp;gl_ModelViewProjectionMatrix&nbsp;*&nbsp;gl_Vertex; </span></li>    <li><span>} </span></li>    <li class="alt">&nbsp;</li>    <li><span class="comment">//fragment&nbsp;shader: </span></li>    <li class="alt"><span>uniform&nbsp;sampler2D&nbsp;tLookup; </span></li>    <li><span>uniform&nbsp;</span><span class="keyword">float</span><span>&nbsp;intensity; </span></li>    <li class="alt"><span>varying&nbsp;vec2&nbsp;texCoord0; </span></li>    <li>&nbsp;</li>    <li class="alt"><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>) </span></li>    <li><span>{ </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vec4&nbsp;col&nbsp;=&nbsp;&nbsp;intensity&nbsp;*&nbsp;texture2D(tLookup,&nbsp;texCoord0.xy); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;</span><span class="comment">//gl_FragColor&nbsp;=&nbsp;&nbsp;col;&nbsp;//1 </span></li>    <li><span>&nbsp;&nbsp;&nbsp;gl_FragColor&nbsp;=&nbsp;&nbsp;vec4(col.rgb&nbsp;*&nbsp;(col.a),&nbsp;1.0);&nbsp;</span><span class="comment">//2 </span></li>    <li class="alt"><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//www.<a href="http://www.zwqxin.com/">ZwqXin</a>.com  Anisotropic lighting//vertex shaderuniform vec4 lightpos;uniform vec4 eyepos;varying vec2 texCoord0;void main(void){   vec3 norm = normalize(gl_NormalMatrix * gl_Normal);   vec4 pos = gl_ModelViewMatrix * gl_Vertex;     vec3 vlightpos = (gl_ModelViewMatrix * lightpos).xyz;      vec3 veyepos = (gl_ModelViewMatrix * eyepos).xyz;       vec3 vlightdir = normalize(vlightpos - pos.xyz);    vec3 veyedir = normalize(veyepos - pos.xyz);        //h = normalize(l + e)    vec3 halfVec = normalize(veyedir + vlightdir);           texCoord0.x = 2.0 * dot(halfVec, norm) - 1.0;//每个顶点的值随视线变化而变化        texCoord0.y = 2.0 * dot(vlightdir, norm) - 1.0;//每个顶点拥有固定值        gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;}//fragment shader:uniform sampler2D tLookup;uniform float intensity;varying vec2 texCoord0;void main(void){     vec4 col =  intensity * texture2D(tLookup, texCoord0.xy);   //gl_FragColor =  col; //1   gl_FragColor =  vec4(col.rgb * (col.a), 1.0); //2}</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>fragment&nbsp;shader中，1是不应用镜面光分量的结果，2是应用镜面光分量（在alpha通道）的结果(见下面两图)。其实用vec3(col.r)或vec3(col.g)代替col.rgb也可，后者的r分量比gb小点所以结果呈现青色。其实这些无什么所谓，要弄颜色的话取某单通道再乘个颜色量就好。另外建议纹理的S_WRAP选择<font face="Courier New">GL_MIRRORED_REPEAT,看<a target="_blank" href="http://www.opengl.org/sdk/docs/man/xhtml/glTexParameter.xml">SPEC</a>就知道其作用了，保险点好。</font></p><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html"><img alt="Anisotropic lighting http:www.zwqxin.com" src="http://lh6.ggpht.com/_lYWT-2PnV0s/SsTZiNCBfJI/AAAAAAAABBo/H3yuxNUB46o/s800/Snap2009100111.jpg" /></a><br /><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html"><img alt="Anisotropic lighting http:www.zwqxin.com" src="http://lh6.ggpht.com/_lYWT-2PnV0s/SsTZibuk4gI/AAAAAAAABBs/vAure-vpk8o/s800/Snap2009100112.jpg" /></a></p><p>最后说一下另一个Anisotropic lighting的shader实现。在《Gems 1》里看到的，在《GLSL Shading Language》里Diffraction一节里也用到了。它没有用特制纹理，而是直接拿tangent切线计算，而且算法貌似是完全不一样的。其中还有TdotH，真不知道是怎么搞的，据介绍是WARD92里提出的，在Jos Stam的Diffraction开山论文《Diffraction Shaders》里也提到，貌似跟BRDF算法有关，这个&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">//Anisotropic&nbsp;Lighting&nbsp;2&nbsp;&nbsp;www.<a href="http://www.zwqxin.com/">ZwqXin</a>.com </span></span></li>    <li>&nbsp;</li>    <li class="alt"><span class="comment">//vertex&nbsp;shader </span></li>    <li><span>uniform&nbsp;vec4&nbsp;baseColor; </span></li>    <li class="alt"><span>uniform&nbsp;</span><span class="keyword">float</span><span>&nbsp;controlR; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>uniform&nbsp;vec4&nbsp;lightpos; </span></li>    <li><span>uniform&nbsp;vec4&nbsp;eyepos; </span></li>    <li class="alt">&nbsp;</li>    <li><span>attribute&nbsp;vec3&nbsp;rm_tangent; </span></li>    <li class="alt">&nbsp;</li>    <li><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>) </span></li>    <li class="alt"><span>{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;norm&nbsp;=&nbsp;normalize(gl_NormalMatrix&nbsp;*&nbsp;gl_Normal); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;tang&nbsp;=&nbsp;normalize(gl_NormalMatrix&nbsp;*&nbsp;rm_tangent);&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;pos&nbsp;=&nbsp;gl_ModelViewMatrix&nbsp;*&nbsp;gl_Vertex; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;vlightpos&nbsp;=&nbsp;(gl_ModelViewMatrix&nbsp;*&nbsp;lightpos).xyz;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;veyepos&nbsp;=&nbsp;(gl_ModelViewMatrix&nbsp;*&nbsp;eyepos).xyz; </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;vlightdir&nbsp;=&nbsp;normalize(vlightpos&nbsp;-&nbsp;pos.xyz); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;veyedir&nbsp;=&nbsp;normalize(veyepos&nbsp;-&nbsp;pos.xyz); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//h&nbsp;=&nbsp;normalize(l&nbsp;+&nbsp;e) </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;halfVec&nbsp;=&nbsp;normalize(veyedir&nbsp;+&nbsp;vlightdir); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span class="comment">//0.3是光的波长，干脆就只用后面的调节系数controlR&nbsp;调节好了 </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;u&nbsp;=&nbsp;dot(tang,&nbsp;halfVec)*0.3；&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;w&nbsp;=&nbsp;dot(norm,&nbsp;halfVec); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;e&nbsp;=&nbsp;controlR&nbsp;*&nbsp;u&nbsp;/&nbsp;w; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;c&nbsp;=&nbsp;exp(-e&nbsp;*&nbsp;e); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;vec4&nbsp;aniColor&nbsp;=&nbsp;baseColor&nbsp;*&nbsp;c; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;gl_Position&nbsp;=&nbsp;gl_ModelViewProjectionMatrix&nbsp;*&nbsp;gl_Vertex; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;gl_FrontColor&nbsp;=&nbsp;aniColor; </span></li>    <li><span>} </span></li>    <li class="alt">&nbsp;</li>    <li><span class="comment">//fragment&nbsp;shader </span></li>    <li class="alt"><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>) </span></li>    <li><span>{ </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;gl_FragColor&nbsp;=&nbsp;&nbsp;gl_Color; </span></li>    <li><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//Anisotropic Lighting 2  www.<a href="http://www.zwqxin.com/">ZwqXin</a>.com//vertex shaderuniform vec4 baseColor;uniform float controlR;uniform vec4 lightpos;uniform vec4 eyepos;attribute vec3 rm_tangent;void main(void){   vec3 norm = normalize(gl_NormalMatrix * gl_Normal);   vec3 tang = normalize(gl_NormalMatrix * rm_tangent);       vec4 pos = gl_ModelViewMatrix * gl_Vertex;   vec3 vlightpos = (gl_ModelViewMatrix * lightpos).xyz;     vec3 veyepos = (gl_ModelViewMatrix * eyepos).xyz;       vec3 vlightdir = normalize(vlightpos - pos.xyz);    vec3 veyedir = normalize(veyepos - pos.xyz);        //h = normalize(l + e)    vec3 halfVec = normalize(veyedir + vlightdir);    //0.3是光的波长，干脆就只用后面的调节系数controlR 调节好了    float u = dot(tang, halfVec)*0.3；     float w = dot(norm, halfVec);        float e = controlR * u / w;    float c = exp(-e * e);        vec4 aniColor = baseColor * c;         gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;   gl_FrontColor = aniColor;}//fragment shadervoid main(void){   gl_FragColor =  gl_Color;}</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>以上两种shader都是在顶点级处理的，搬到fragment shader处理也就那么简单，边界柔和点而已。给出两种shader的结果（但我并没有作比较的意思，以下两图光照条件和角度都8一样）：</p><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html"><img alt="Anisotropic lighting http:www.zwqxin.com" src="http://lh4.ggpht.com/_lYWT-2PnV0s/SsTZh28spaI/AAAAAAAABBk/q3yvyS7d8Zs/s800/Snap200910013.jpg" /></a><br />（用前一种方法）<br /><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html"><img alt="Anisotropic lighting http:www.zwqxin.com" src="http://lh4.ggpht.com/_lYWT-2PnV0s/SsTZhhblv1I/AAAAAAAABBg/mmrW3Z88WeI/s800/Snap200910011.jpg" /></a><br />（用后一种方法）</p><p>最后再佩服一下造出前一种方法中那张纹理的人。这也就是所谓的预处理的一种吧，把函数关系式用纹理表达；这也是GPGPU中纹理概念[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/glsl-vertex-texture-fetch.html">Vertex Texture Fetch 顶点纹理拾取</a>] 的又一延伸吧，还是有种神奇的感觉&hellip;&hellip;</p><p>该纹理下载：<a target="_blank" href="http://www.zwqxin.com/upload/2009/10/Aniso2.rar">Aniso2.rar</a></p><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html" target="_blank">继续阅读《shader复习与深入：Anisotropic Lighting(各向异性光照)》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/shaderglsl.html">Shader技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=shader">shader</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=GLSL">GLSL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E5%85%89%E7%85%A7">光照</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html#comment" target="_blank">添加评论</a>(0)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html">软阴影的实现尝试Ⅱ</a> (2010-2-13 10:29:39)  </li><li><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html">软阴影的实现尝试Ⅰ</a> (2010-2-11 10:51:26)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html">shader复习与深入：Diffraction(衍射)</a> (2009-9-25 15:3:9)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html">shader复习与深入：Normal Map(法线贴图)Ⅱ</a> (2009-9-16 14:43:41)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html">shader复习与深入：Normal Map(法线贴图)Ⅰ</a> (2009-9-15 23:29:17)  </li></ul>]]></description><category>Shader技术</category><comments>http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=80</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=80&amp;key=87cf4e4e</trackback:ping></item><item><title>shader复习与深入：Normal Map(法线贴图)Ⅱ</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html</link><pubDate>Wed, 16 Sep 2009 14:43:41 +0800</pubDate><guid>http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html</guid><description><![CDATA[<p>在前文中我尽可能地把我所理解Normal Map原理总结了一下，本续篇将从实践部分继续开始，各位看官尽情拍砖。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>上篇见：[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html">shader复习与深入：Normal Map(法线贴图)Ⅰ</a>]</p><p><strong>1. 怎样获得顶点的TBN</strong></p><p>其实我觉得这个是实践部分最麻烦的地方。OpenGL提供了诸如glNormal、normal-vbo之类的接口设置顶点的法线，然后在shader中以gl_Normal等方式取得顶点法线数据，但是没有提供切线和副法线的。当然两者只要其一就足够了（另一者可通过叉乘和左/右手定则获得）。因为要把TBN导入shader，干脆就设置attribute变量，记录每个顶点的切线。切线一般就是相邻顶点的差向量了（其实这有时候是非常繁重的工作）。</p><p>如果是通常的3DS模型的话，顶点法线是共顶点的面的面法线的加权，这样法线就不一定垂直于某个面，即与切线不垂直。但只要它们还是近似垂直的，<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html">上篇</a>提及的Gram-Schmidt 算法应该可以处理。或者在shader中，把法线与切线叉乘出副法线，再用法线与副法线叉乘得新的切线，也能确保两两垂直。这样之前的TBN矩阵的转置矩阵就能直接作为其逆矩阵，完成向量从模型坐标系往切线空间坐标系的变换了。</p><p>问题不只这样。对于一些模型，共享顶点的三角面片面法线差角太大，这时候计算出的该顶点法线和切线就可能带来麻烦。在橙书（OpenGL Shading Language）中，谈及了切线必须是一致的（consistently），面片相邻的顶点切线不应该差距太大。但若相邻面片夹角太大，得到的该顶点法线就可能与&ldquo;共享该顶点的面片&rdquo;上的其他顶点的法线差异很大，从而切线也会相差很大，直接导致光向量等在这两顶点的切线空间差异很大，插值的各个针对像素的光向量方向差异很大，与像素法线点乘的cos也会差异得很明显（而现实中一般的凹凸面漫反射光线不会有太大方向差异）。解决方法是把该出了问题的顶点拆成两个（原地拷贝，3DS模型就不用了- -），一个面片用一个，其法线只受所属的面片的面法线决定（这样最后会形成突出的边缘，但夹角大的面片之间实际上就应该会是有这样的效果吧）。</p><p>另一个问题，我们向shader传入顶点法线切线，希望副法线由两者叉乘得出。但既然叉乘就有个方向问题（结果可以有两个方向，AXB与BXA是不一样的，我以前弄shadow volume就曾被它这种特性作弄过）。AXB改成BXA实际上会导致凹凸感反向，原来凹的变凸了，原来凸的变凹了（要仔细比对，不然会有首因效应）。一般就用N X T吧，因为基本上都是这个顺序的，结果也符合原Normal Map。</p><p><strong>2. GLSL&nbsp;1.2&nbsp;Shader实现代码</strong></p><p>没什么好说的，就是前面算法翻译成GLSL。</p><p><strong>Vertex Shader：</strong></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">//&nbsp;vertex&nbsp;shader </span></span></li>    <li><span>uniform&nbsp;vec3&nbsp;lightpos;&nbsp;</span><span class="comment">//传入光源的模型坐标吧 </span></li>    <li class="alt"><span>uniform&nbsp;vec4&nbsp;eyepos; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>varying&nbsp;vec3&nbsp;lightdir; </span></li>    <li><span>varying&nbsp;vec3&nbsp;halfvec; </span></li>    <li class="alt"><span>varying&nbsp;vec3&nbsp;norm; </span></li>    <li><span>varying&nbsp;vec3&nbsp;eyedir; </span></li>    <li class="alt">&nbsp;</li>    <li><span>attribute&nbsp;vec3&nbsp;rm_Tangent; </span></li>    <li class="alt">&nbsp;</li>    <li><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>) </span></li>    <li class="alt"><span>{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;pos&nbsp;=&nbsp;gl_ModelViewMatrix&nbsp;*&nbsp;gl_Vertex; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;pos&nbsp;=&nbsp;pos&nbsp;/&nbsp;pos.w; </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span class="comment">//把光源和眼睛从模型空间转换到视图空间 </span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;vlightPos&nbsp;=&nbsp;(gl_ModelViewMatrix&nbsp;*&nbsp;vec4(lightpos,&nbsp;1.0)); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;veyePos&nbsp;&nbsp;&nbsp;=&nbsp;(gl_ModelViewMatrix&nbsp;*&nbsp;eyepos); </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;lightdir&nbsp;=&nbsp;normalize(vlightPos.xyz&nbsp;-&nbsp;pos.xyz); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;eyedir&nbsp;=&nbsp;normalize(veyePos.xyz&nbsp;-&nbsp;pos.xyz); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;</span><span class="comment">//模型空间下的TBN </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;norm&nbsp;=&nbsp;normalize(gl_NormalMatrix&nbsp;*&nbsp;gl_Normal); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;vtangent&nbsp;&nbsp;=&nbsp;normalize(gl_NormalMatrix&nbsp;*&nbsp;rm_Tangent); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;vbinormal&nbsp;=&nbsp;cross(norm,vtangent); </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;</span><span class="comment">//将光源向量和视线向量转换到TBN切线空间 </span></li>    <li><span>&nbsp;&nbsp;&nbsp;lightdir.x&nbsp;=&nbsp;dot(vtangent,&nbsp;&nbsp;lightdir); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;lightdir.y&nbsp;=&nbsp;dot(vbinormal,&nbsp;lightdir);&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;lightdir.z&nbsp;=&nbsp;dot(norm&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;,&nbsp;lightdir); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;lightdir&nbsp;=&nbsp;normalize(lightdir); </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;eyedir.x&nbsp;=&nbsp;dot(vtangent,&nbsp;&nbsp;eyedir); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;eyedir.y&nbsp;=&nbsp;dot(vbinormal,&nbsp;eyedir); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;eyedir.z&nbsp;=&nbsp;dot(norm&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;,&nbsp;eyedir); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;eyedir&nbsp;=&nbsp;normalize(eyedir); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;halfvec&nbsp;=&nbsp;normalize(lightdir&nbsp;+&nbsp;eyedir); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;gl_FrontColor&nbsp;=&nbsp;gl_Color; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;gl_TexCoord[0]&nbsp;=&nbsp;gl_MultiTexCoord0; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;gl_Position&nbsp;=&nbsp;ftransform(); </span></li>    <li class="alt"><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>// vertex shaderuniform vec3 lightpos; //传入光源的模型坐标吧uniform vec4 eyepos;varying vec3 lightdir;varying vec3 halfvec;varying vec3 norm;varying vec3 eyedir;attribute vec3 rm_Tangent;void main(void){   vec4 pos = gl_ModelViewMatrix * gl_Vertex;   pos = pos / pos.w;   //把光源和眼睛从模型空间转换到视图空间   vec4 vlightPos = (gl_ModelViewMatrix * vec4(lightpos, 1.0));   vec4 veyePos   = (gl_ModelViewMatrix * eyepos);      lightdir = normalize(vlightPos.xyz - pos.xyz);   vec3 eyedir = normalize(veyePos.xyz - pos.xyz);     //模型空间下的TBN   norm = normalize(gl_NormalMatrix * gl_Normal);   vec3 vtangent  = normalize(gl_NormalMatrix * rm_Tangent);   vec3 vbinormal = cross(norm,vtangent);      //将光源向量和视线向量转换到TBN切线空间   lightdir.x = dot(vtangent,  lightdir);   lightdir.y = dot(vbinormal, lightdir);    lightdir.z = dot(norm     , lightdir);   lightdir = normalize(lightdir);      eyedir.x = dot(vtangent,  eyedir);   eyedir.y = dot(vbinormal, eyedir);   eyedir.z = dot(norm     , eyedir);   eyedir = normalize(eyedir);      halfvec = normalize(lightdir + eyedir);   gl_FrontColor = gl_Color;      gl_TexCoord[0] = gl_MultiTexCoord0;      gl_Position = ftransform();}</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>传入的lightPos，eyePos，gl_Vertex，gl_Normal，rm_Tangent是其模型坐标系下的坐标、向量，乘以ModelView矩阵（法线切线乘以ModelView矩阵的转置逆矩阵）到了视图空间（vlightPos，veyePos，pos，norm, vtangent）；在视图空间它们已经有了&ldquo;世界&rdquo;的概念了，因此可以平等地相互影响（在各自封闭的模型空间是享受不了的），可以作各种点乘叉乘加减乘除计算。</p><p>注意，lightPos，eyePos虽说是在其各自模型坐标系下定义的，但不对它们弄什么平移旋转缩放操作的话，其模型矩阵就是一单位阵，此时其&ldquo;世界坐标 == 模型坐标&rdquo;。所以这时我可以当它是在世界空间定义的坐标（实际上一般我们都会在世界空间定义这两个点）。（注意，前提是不对它们做模型变换。）</p><p>从以上量得到光源向量、视线向量后（它们在视图空间），N、T叉乘得B（注意它们现在都在视图空间），通过TBN矩阵逆矩阵把两向量变换到当前顶点的切线空间，交给光栅去插值。&nbsp;</p><p>对以上有不理解的朋友，可能是没看上篇：[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html">shader复习与深入：Normal Map(法线贴图)Ⅰ</a>]</p><p><strong>fragment shader：</strong></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">//fragment&nbsp;shader </span></span></li>    <li><span>uniform&nbsp;</span><span class="keyword">float</span><span>&nbsp;shiness; </span></li>    <li class="alt"><span>uniform&nbsp;vec4&nbsp;ambient,&nbsp;diffuse,&nbsp;specular; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>uniform&nbsp;sampler2D&nbsp;bumptex; </span></li>    <li><span>uniform&nbsp;sampler2D&nbsp;basetex; </span></li>    <li class="alt">&nbsp;</li>    <li><span class="keyword">float</span><span>&nbsp;amb&nbsp;=&nbsp;0.2; </span></li>    <li class="alt"><span class="keyword">float</span><span>&nbsp;diff&nbsp;=&nbsp;0.2; </span></li>    <li><span class="keyword">float</span><span>&nbsp;spec&nbsp;=&nbsp;0.6; </span></li>    <li class="alt">&nbsp;</li>    <li><span>varying&nbsp;vec3&nbsp;lightdir; </span></li>    <li class="alt"><span>varying&nbsp;vec3&nbsp;halfvec; </span></li>    <li><span>varying&nbsp;vec3&nbsp;norm; </span></li>    <li class="alt"><span>varying&nbsp;vec3&nbsp;eyedir; </span></li>    <li>&nbsp;</li>    <li class="alt"><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>) </span></li>    <li><span>{ </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;vlightdir&nbsp;=&nbsp;normalize(lightdir); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;veyedir&nbsp;=&nbsp;normalize(eyedir); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;vnorm&nbsp;=&nbsp;&nbsp;&nbsp;normalize(norm); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;vhalfvec&nbsp;=&nbsp;&nbsp;normalize(halfvec);&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;baseCol&nbsp;=&nbsp;texture2D(basetex,&nbsp;gl_TexCoord[0].xy);&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;</span><span class="comment">//Normal&nbsp;Map里的像素normal定义于该像素的切线空间 </span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;tbnnorm&nbsp;=&nbsp;texture2D(bumptex,&nbsp;gl_TexCoord[0].xy).xyz; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;tbnnorm&nbsp;=&nbsp;normalize((tbnnorm&nbsp;&nbsp;-&nbsp;vec3(0.5))*&nbsp;2.0);&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;diffusefract&nbsp;=&nbsp;&nbsp;max(&nbsp;dot(lightdir,tbnnorm)&nbsp;,&nbsp;0.0);&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;specularfract&nbsp;=&nbsp;max(&nbsp;dot(vhalfvec,tbnnorm)&nbsp;,&nbsp;0.0); </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;</span><span class="keyword">if</span><span>(specularfract&nbsp;&gt;&nbsp;0.0){ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;specularfract&nbsp;=&nbsp;pow(specularfract,&nbsp;shiness); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;} </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;gl_FragColor&nbsp;=&nbsp;vec4(amb&nbsp;*&nbsp;ambient.xyz&nbsp;*&nbsp;baseCol.xyz </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+&nbsp;diff&nbsp;*&nbsp;diffuse.xyz&nbsp;*&nbsp;diffusefract&nbsp;*&nbsp;baseCol.xyz </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+&nbsp;spec&nbsp;*&nbsp;specular.xyz&nbsp;*&nbsp;specularfract&nbsp;,1.0); </span></li>    <li><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//fragment shaderuniform float shiness;uniform vec4 ambient, diffuse, specular;uniform sampler2D bumptex;uniform sampler2D basetex;float amb = 0.2;float diff = 0.2;float spec = 0.6;varying vec3 lightdir;varying vec3 halfvec;varying vec3 norm;varying vec3 eyedir;void main(void){   vec3 vlightdir = normalize(lightdir);   vec3 veyedir = normalize(eyedir);   vec3 vnorm =   normalize(norm);   vec3 vhalfvec =  normalize(halfvec);        vec4 baseCol = texture2D(basetex, gl_TexCoord[0].xy);       //Normal Map里的像素normal定义于该像素的切线空间   vec3 tbnnorm = texture2D(bumptex, gl_TexCoord[0].xy).xyz;      tbnnorm = normalize((tbnnorm  - vec3(0.5))* 2.0);       float diffusefract =  max( dot(lightdir,tbnnorm) , 0.0);    float specularfract = max( dot(vhalfvec,tbnnorm) , 0.0);      if(specularfract &gt; 0.0){   specularfract = pow(specularfract, shiness);   }      gl_FragColor = vec4(amb * ambient.xyz * baseCol.xyz                 + diff * diffuse.xyz * diffusefract * baseCol.xyz                 + spec * specular.xyz * specularfract ,1.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>注意把normal map里的normal由(0,1)映射回(-1,1)。baseCol得到的是基底纹理的像素颜色。其余部分就是per pixel lighting的东西了。[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-per-pixel-lighting-shader.html">Shader快速复习：Per Pixel Lighting(逐像素光照)</a>]</p><p style="text-align: center"><img style="width: 223px; height: 214px" height="211" alt="" width="218" src="http://lh4.ggpht.com/_lYWT-2PnV0s/Sr48Lht_qHI/AAAAAAAAA-s/6hxLbODS-so/s800/090925Snap3.jpg" /><img style="width: 203px; height: 213px" height="221" alt="" width="210" src="http://lh4.ggpht.com/_lYWT-2PnV0s/Sr48K-RSmeI/AAAAAAAAA-k/15yrW4FtgmE/s800/face_norm.jpg" /><br />(上为底纹理和法线纹理，下为它们与某破壁模型合作的效果，纹理from planetpixelemporium.com)<br />&nbsp;<a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html"><img alt="Normal Map   www.zwqxin.com" src="http://lh6.ggpht.com/_lYWT-2PnV0s/Sr49MK0j_bI/AAAAAAAAA_c/vysH1bjHCh0/s800/090924Snap4.jpg" /></a></p><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html"><img alt="Normal Map   www.zwqxin.com" src="http://lh4.ggpht.com/_lYWT-2PnV0s/Sr49Mp8_ZYI/AAAAAAAAA_g/aD1AfMgVlo4/s800/090924Snap1.jpg" /></a><br />(我想是游戏最常用的用途：砖墙。我想是最常用的NormalMap,from NEHE)<br /><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html"><img alt="Normal Map   www.zwqxin.com" src="http://lh5.ggpht.com/_lYWT-2PnV0s/Sr49M1Hb6CI/AAAAAAAAA_k/9if7bHmfhjo/s800/090924Snap3.jpg" /></a><br /><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html"><img alt="Normal Map   www.zwqxin.com" src="http://lh5.ggpht.com/_lYWT-2PnV0s/Sr49Lz0z6xI/AAAAAAAAA_Y/S5T70dAAL9c/s800/S090924nap2.jpg" /></a><br />(自己把墙壁BaseMap放入Photoshop的normalMapFilter里弄的NormalMap，呃.....)</p><p style="text-align: right">参考资料：N多网上资料+OpenGL Shading Language 2nd Edition</p><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html" target="_blank">继续阅读《shader复习与深入：Normal Map(法线贴图)Ⅱ》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/shaderglsl.html">Shader技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=OpenGL">OpenGL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=GLSL">GLSL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=shader">shader</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E4%BB%A3%E7%A0%81">代码</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html#comment" target="_blank">添加评论</a>(1)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html">软阴影的实现尝试Ⅱ</a> (2010-2-13 10:29:39)  </li><li><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html">软阴影的实现尝试Ⅰ</a> (2010-2-11 10:51:26)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html">shader复习与深入：Diffraction(衍射)</a> (2009-9-25 15:3:9)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html">shader复习与深入：Anisotropic Lighting(各向异性光照)</a> (2009-9-22 20:30:25)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html">shader复习与深入：Normal Map(法线贴图)Ⅰ</a> (2009-9-15 23:29:17)  </li></ul>]]></description><category>Shader技术</category><comments>http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=79</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=79&amp;key=c604676b</trackback:ping></item><item><title>shader复习与深入：Normal Map(法线贴图)Ⅰ</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html</link><pubDate>Tue, 15 Sep 2009 23:29:17 +0800</pubDate><guid>http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html</guid><description><![CDATA[<p>Normal Map法线贴图，想必每个学习计算机图形学的人都不陌生。今天在这里按我的理解总结一下，作为复习，也作为深入学习吧。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>自从看完那本《数学在计算机图形学上的应用》后，一直想好好地真正实践一次法线贴图/凹凸贴图呢（以前是根据橙书弄了一下罢了）。昨天偶尔看到篇涉及BumpMap的文，正好觉得是个机会，便在网上狂找相关资料&mdash;&mdash;果然，越看越觉得自己还有很多理论的地方需要弄明白呢。</p><p>说起Normal Map（法线贴图），就会想起Bump Map（凹凸贴图）。Bump Mapping是Blin大师在1978年提出的图形学算法，目的是以低代价给予计算机几何体以更丰富的表面信息（高模盖低模）。30年来，这项技术不断延展，尤其是计算机图形学成熟以后，相继出现了不少算法变体，90年代末的Normal Map解放了必须自行计算纹理像素法线的痛苦，新世纪以来相继又出现了Parallax Mapping, Relief Mapping等技术。抛开那些无聊的概念区分，它们的本体还是Bump Map，目的也是一致的。</p><p><strong>1. 传统的Bump Map</strong></p><p>如果你对纯净的Bump Map有兴趣，<a target="_blank" href="http://www.cg.tuwien.ac.at/courses/Realtime/slides/PracticalBumpMap.pdf">A Practical and Robust Bump-mapping Technique for Today's GPU</a>应该是值得一看的论文。说Today，其实是GDC 2000的事情了，但对于传统的Bump Map的理论是很丰富的，我是没精力看完它啦&hellip;&hellip;</p><p>那时候的Bump Map须要我们计算纹理图上每个像素的法线信息，简单的还可能做到，对复杂的纹理要搞清面光背光份量简直要命，于是就用Height Map，在一张高度图上记录每个像素对应的纹理位置的高度信息（这个比较容易办到，NEHE22也是这类）。看上去就是一张地形网格&mdash;&mdash;这样的话，计算每个像素点的法线就不那么难了。XY方向相邻像素的高度相减就是两条正交的切向量，叉乘外加左/右手定则就获得法线。或者更精确点，用八邻域弄个边缘检测算子（sobel、拉普拉斯之类 ）[<a target="_blank" href="http://www.zwqxin.com/archives/image-processing/image-process-spatial-domain-filter.html">图像处理里的空间域滤波</a>]，或者应用斜坡法([<a target="_blank" href="http://www.zwqxin.com/archives/opengl/water-simulation-3.html">水效果Ⅲ - 抖动波</a>] )来求切线、法线。</p><p style="text-align: center"><img alt="" src="http://www.gamedev.net/columns/hardcore/cgbumpmapping/coords.gif" /></p><p>&nbsp;<strong>2. 制作NormalMap</strong></p><p>但是这样还是挺麻烦的，既然都动用额外的贴图了，何不把这些与实现无关的预处理&mdash;&mdash;作为结果的法线信息&mdash;&mdash;都放进纹理里呢？这就是Normal Map的思想起源。但是，谁来做这样的一张法线图呢？敲定美工了。每个像素的RGB分别存储该像素对应法线的XYZ分量，只要把法线的分量由（-1，1）映射成（0，255）就可了。观察一张法线图，以蓝色为主，是因为朝向图面外的法线（0，0，1）都被编码成（0，0，127）了（读入OpenGL后即(0,0,0.5)），而图上越红的地方表明法线越向右，越绿的地方表明法线越向上，就可以理解了。总体来说，就是一张紫蓝色的图。怎么做这样的图呢？当然最好是有一个工具，输入原图和高度图后执行上述的算法得出新图了，事实上已经有很多这类工具了（譬如比较著名的photoshop的NV插件Normal Map Filter，甚至不用高度channel也可[效果- -]），以下几篇文章有详细介绍，有兴趣的可以看一看：</p><p><a target="_blank" href="http://wiki.gamedev.net/index.php/Tutorial_On_Normal_Mapping">Tutorial On Normal Mapping</a>&nbsp;（PHOTOSHOP [ENGLISH]）<br /><a target="_blank" href="http://phoenixzz.blogbus.com/logs/1597471.html">怎样用PhotoShop创建Bump Map图像</a>&nbsp;（PHOTOSHOP [CHINESE]）<br /><a target="_blank" href="http://lichong.blogbus.com/logs/40862743.html">Nvidia Normal Map 插件参数之详解</a>&nbsp;(PHOTOSHOP [翻译])<br /><a target="_blank" href="http://nifelheim.dyndns.org/~cocidius/normalmap/">GIMP normalmap plugin</a>&nbsp;&nbsp; (GIMP&nbsp;&nbsp; [ENG]）</p><p>关于NormalMap制作的原理，更详细的可参考此文：<a target="_blank" href="http://www.hxsd.com/tutorial/jianmozhuanlan/Maya/20090916/21912.html">Normalmap原理及去除接缝</a></p><p>&nbsp;<strong>3. 切线空间(Tangent Space)</strong></p><p>其实这个概念前文已经提及了。每个像素根据高度图生成的三轴坐标系，就是被称为切线空间坐标系的东西，每个像素人手一个。可见Normal Map里面每个像素的法线就是定义在这个切线空间的。注意，这些法线是属于像素的，而不是顶点，我们平时用的法线是顶点法线，是定义在模型坐标系的[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/opengl-matrix-what.html">乱弹OpenGL中的矩阵变换(上)</a>] ，定义于所属物件的唯一的局部坐标系原点之上。而这些像素法线定义于切线坐标系，其原点就在该像素上，切线副法线在法线的垂直平面上。</p><p style="text-align: center"><img alt="" src="http://www.gamedev.net/columns/hardcore/cgbumpmapping/lighting.gif" /><br /><img alt="" src="http://www.gamedev.net/columns/hardcore/cgbumpmapping/bumplighting.gif" /><br />（表面依然是平的，但通过搅动法线，使进入我们眼睛的光线强度不一，模拟出凹凸面漫反射的特点。图from GDNet）</p><p>应用这些像素法线的目的无非是计算出该像素的OutPut颜色：col = baseColor * (amb + diffuse) + specular。这些都应该在像素着色器（fragment shader）里进行，因为我们要做的是针对每个像素的处理[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-per-pixel-lighting-shader.html">Shader快速复习：Per Pixel Lighting(逐像素光照)</a>] 。其中需要用到像素法线的是diffuse和specular（以前是用通过顶点法线线性插值而来的normal），法线分别与光线向量、半向量作点乘得到对应因子。这个因子是个夹角cos而已，所以只要满足像素法线与两个向量单位化并在同一坐标系下（而无论是哪个坐标系），夹角就是一定的。这样看来，两个选择：<br />1. 把像素法线都从各自的切线空间转到视图空间来，再点乘；<br />2.把光线向量、半向量从视图空间转到像素各自的切空间来，再点乘。</p><p>很多文章一口咬定就是第2种好，原因是第1种要变换N个量；第2种只变换2个量。仔细分析，其实两种选择变换的次数是一样的，都是2*N。说第2种好，是因为：</p><p>第1种必须在fragment shader里进行，对象是从Normal Map读出的像素法线和经过线性插值而来的两个向量，它们不是同一坐标系的，按描述应该是各像素法线乘以各自一个的变换矩阵，转到视图空间来，但确实没有其他的可提供构筑这个矩阵的信息了，若有可能应该就是另外的varying变量传入了；</p><p>第2种可以选择在vertex shader里进行，但是能不能就在这里变换到切线空间呢？假设可以，那么得到的针对顶点的数值在光栅化-线性插值后能否满足呢？</p><p>要回答这个问题，还得考虑像素的切线空间和顶点的切线空间之间的关系。是的，顶点法线也可以变换到切线空间，但这有什么用呢？一步一步来吧。先考虑切线空间在OpenGL世界里的次元位置：</p><p style="text-align: center"><img alt="" src="http://www.paulsprojects.net/tutorials/simplebump/spaces.jpg" /><br />(from paulsprojects)</p><p style="text-align: left">为什么是紧挨模型坐标系呢？其实想想也能理解，在上面谈及切线坐标系的时候，并没有广阔的&ldquo;世界&rdquo;这个概念。只针对每个像素/顶点，无疑是比模型坐标系更狭隘的&ldquo;世界观&rdquo;，所以那个位置是适合的（箭头方向无所谓，坐标系之间是可以相互转换的）。其实对于某个具体的物体上的像素/顶点，你可以考虑那是把模型空间的原点平移到该像素/顶点上，各模型坐标系方向轴向量一起经过旋转，使Z轴与像素/顶点的法线重合，XY轴分别与像素/顶点的切线副法线重合&mdash;&mdash;这只是一个仿射变换而已，如同模型/世界/视图空间之间的变换一样。</p><p style="text-align: left">如果你记得图形学书上关于世界/视图空间的变换矩阵的构建的话，就更容易理解这样的形式了。从切线空间到模型空间的变换矩阵（TBN矩阵M<sub>TBN</sub>）为：</p><p style="text-align: center"><img alt="" src="http://jerome.jouvie.free.fr/images/OpenGl/Lessons/Lesson8-TBN.png" /></p><p style="text-align: left">&nbsp;其中T，B，N是定义在<strong>模型空间</strong>的该像素/顶点的&ldquo;切/副法/法向量&rdquo;。稍微检验一下，考虑某个三角面上的某个顶点，其法线充当切线空间的Z轴，在切线空间中表示为（0，0，1），在OpenGL里解释为一个列向量（0，0，1）<sup>T</sup>，用上面的矩阵M<sub>TBN</sub>左乘该向量，得到（N<sub>x</sub>，N<sub>y</sub>，N<sub>z</sub>）<sup>T</sup>，正是该向量在模型空间的表示。其他两轴同理。说明该矩阵把切线空间的坐标系统转换到模型空间了（一切变换都是在变换坐标系[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/opengl-matrix-what.html">乱弹OpenGL中的矩阵变换(上)</a>] ）。当然这是特例说明，但确实这个矩阵包含仿射矩阵里的旋转元素了（它只包含旋转，不设置平移，是因为我们只需要它来变换向量，向量是可以任意平移的，若要弄完整的4X4矩阵，第4列平移列就是该顶点模型坐标）。具体推导也不难，随便Google一下&quot;tangent space&quot;就出来一堆了，而且都是基本一样的推导过程，推一个：<a target="_blank" href="http://jerome.jouvie.free.fr/OpenGl/Lessons/Lesson8.php">Tangent Space</a>。</p><p style="text-align: left">其逆变换（矩阵M<sub>TBN</sub><sup>-1</sup>）就可以把向量从模型空间变换到对应顶点的切线空间了。如果你确保T，B，N两两垂直，这个正交矩阵的逆矩阵就是其转置矩阵，这很理想。但万一你不确保这点（涉及到具体应用，很多问题的，后面会说），就保证它们大致满足三叉状，用所谓的Gram-Schmidt 算法矫正：</p><p><strong>T&prime;</strong> = <b>T</b> &minus; (<b>N</b> &middot; <b>T</b>)<b>N</b><br /><b>B&prime;</b> = <b>B</b> &minus; (<b>N</b> &middot; <b>B</b>)<b>N</b> &minus; (<b>T&prime;</b> &middot; <b>B</b>)<b>T&prime;</b></p><p>反正最后得到的是这样的形式&mdash;&mdash;用它左乘光源向量和半向量，就得到对应于该顶点切线空间的光源向量和半向量了：</p><p style="text-align: center"><table cellspacing="0" cellpadding="0">    <tbody>        <tr>            <td class="matrix"><i>T&prime;<sub>x</sub></i><br />            <i>B&prime;<sub>x</sub></i><br />            <i>N<sub>x</sub></i></td>            <td class="matrix"><i>T&prime;<sub>y</sub></i><br />            <i>B&prime;<sub>y</sub></i><br />            <i>N<sub>y</sub></i></td>            <td class="matrix"><i>T&prime;<sub>z</sub></i><br />            <i>B&prime;<sub>z</sub></i><br />            <i>N<sub>z</sub></i></td>            <td class="rbracket">&nbsp;</td>        </tr>    </tbody></table></p><p>为什么是顶点？因为这是你唯一能取得其切线/副法线/法线的东西了。这也是之前说的选择1不行的原因，在那张Normal Map里面已经没有任何法线副法线的确实信息了（只知道它们在法线垂直平面上），即使能通过别的方法取得（起码要增加传入数据），那要在fragment shader里每像素人手又计算一个矩阵，这就又是一个&ldquo;计算量&rdquo;（不是次数）的问题。所以还是用选择2吧，也就是上面矩阵M<sub>TBN</sub><sup>-1</sup>的讨论。</p><p>选择2的第一个问题现在很清楚了：是可以的。只要取得顶点的切线/副法线/法线数据就能建立矩阵并变换光源向量和半向量，但结果是针对顶点的，我们需要的是针对像素的。光栅化线性插值这两个向量，就是对应像素的值，但这对吗？直觉上不对，但结果显示这样做没有不妥（或者说不会与真实所须差太多）。一般文章都没有直接透视这个问题，其实考虑一个矩形平面就露馅了，它四个顶点的TBN一致，变换得的光源向量也该一致，插值后得光源向量也该一致，但NormalMap中的像素有各自不同的切线空间系统，光源向量不该一致的呃（虽则同向光源、不同法线足够形成凹凸效果）。所以我对选择2的第二个问题保持疑问，有道深者请为鄙人指点迷津！</p><p>反正即使计算两向量夹角的计算可能会有偏差，也不会太离谱，问题到此结束。至于有的文章提及对diffuse的计算，光源向量插值后不须再归一化的问题（我尝试过，整体会变暗一点），就不深入了。注意我们在vertex shader里变换到切线空间的是模型空间下的光源向量和视线向量（半向量是它们的和），而一般这两个向量定义在视图空间，所以之前还要做一个视图空间-&gt;模型空间的变换（用ModelView矩阵的逆矩阵）。这是很多文章囫囵掉的一点。但如果你能取得视图空间下的顶点TBN，也不需。因为切线/副法线/法线若是被变换到<strong>视图空间</strong>，则上面的TBN矩阵M<sub>TBN</sub>就是把东西从该顶点的切线空间变换到视图空间（道理是一样的），M<sub>TBN</sub><sup>-1</sup>就能把视图空间下的这两个向量变换到该顶点的切线空间（参见<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html">下篇</a>的代码）。</p><p>&nbsp;最后的问题：怎么去取得模型空间下的顶点的切线，副法线，法线？连同shader实现代码一起，我会在下篇谈及，请留意了哦。<br /><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html">shader复习与深入：Normal Map(法线贴图)Ⅱ</a></p><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html" target="_blank">继续阅读《shader复习与深入：Normal Map(法线贴图)Ⅰ》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/shaderglsl.html">Shader技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=shader">shader</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=GLSL">GLSL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=OpenGL">OpenGL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E7%9F%A9%E9%98%B5">矩阵</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html#comment" target="_blank">添加评论</a>(1)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html">软阴影的实现尝试Ⅱ</a> (2010-2-13 10:29:39)  </li><li><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html">软阴影的实现尝试Ⅰ</a> (2010-2-11 10:51:26)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html">shader复习与深入：Diffraction(衍射)</a> (2009-9-25 15:3:9)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html">shader复习与深入：Anisotropic Lighting(各向异性光照)</a> (2009-9-22 20:30:25)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html">shader复习与深入：Normal Map(法线贴图)Ⅱ</a> (2009-9-16 14:43:41)  </li></ul>]]></description><category>Shader技术</category><comments>http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=78</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=78&amp;key=01be4b5b</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><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/opengl/swapbuffers-fps-vsync.html" target="_blank">继续阅读《SwapBuffers的等待，虚伪的FPS》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/opengl.html">OpenGL技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=OpenGL">OpenGL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E7%AC%94%E8%AE%B0">笔记</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/opengl/swapbuffers-fps-vsync.html#comment" target="_blank">添加评论</a>(0)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html">软阴影的实现尝试Ⅱ</a> (2010-2-13 10:29:39)  </li><li><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html">软阴影的实现尝试Ⅰ</a> (2010-2-11 10:51:26)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html">shader复习与深入：Normal Map(法线贴图)Ⅱ</a> (2009-9-16 14:43:41)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html">shader复习与深入：Normal Map(法线贴图)Ⅰ</a> (2009-9-15 23:29:17)  </li><li><a href="http://www.zwqxin.com/archives/opengl/water-simulation-3.html">水效果Ⅲ - 抖动波</a> (2009-8-30 15:39:30)  </li></ul>]]></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><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/opengl/water-simulation-3.html" target="_blank">继续阅读《水效果Ⅲ - 抖动波》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/opengl.html">OpenGL技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=OpenGL">OpenGL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=shader">shader</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E6%B0%B4%E6%95%88">水效</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/opengl/water-simulation-3.html#comment" target="_blank">添加评论</a>(2)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-2.html">软阴影的实现尝试Ⅱ</a> (2010-2-13 10:29:39)  </li><li><a href="http://www.zwqxin.com/archives/opengl/try-soft-shadow-1.html">软阴影的实现尝试Ⅰ</a> (2010-2-11 10:51:26)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html">shader复习与深入：Diffraction(衍射)</a> (2009-9-25 15:3:9)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-anisotropic-lighting.html">shader复习与深入：Anisotropic Lighting(各向异性光照)</a> (2009-9-22 20:30:25)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html">shader复习与深入：Normal Map(法线贴图)Ⅱ</a> (2009-9-16 14:43:41)  </li></ul>]]></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>不要犹豫了，用sstream吧</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/cpp/use-sstream.html</link><pubDate>Fri, 28 Aug 2009 21:54:32 +0800</pubDate><guid>http://www.zwqxin.com/archives/cpp/use-sstream.html</guid><description><![CDATA[<p>sstream是标准C++里面多么强大多么有用的东西啊，从纷繁的格式字符串处理中稍微解放出来吧~&nbsp; &mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>包含&lt;sstream&gt;，顺便加个使用名字空间std的声明，可以开始了。sstream里包括istringstream、ostringstream、stringstream、stringbuf和各宽字符版本的对象类型。更方便的格式化字符串处理，更安全的类型转换。</p><p style="text-align: center"><img alt="" src="http://www.pconline.com.cn/pcedu/empolder/gj/c/0504/pic/08cppios01.gif" /></p><p>这天还是要做那个机器视觉的编程实验，涉及不少数据的提取和保存。也就是&quot;硬盘文件-&gt;内存数据&quot;、&quot;内存数据-&gt;硬盘文件&quot;这类IO。对我来说，一般会使用的是C时代的FILE* - fprintf - sprintf - sscanf - fread/fwrite，或者C++的fstream - read/write之类的，保存到文件的操作比较简单，那么就来说从文件读入数据了：</p><p style="text-align: center"><table cellspacing="1" cellpadding="1" width="200" border="1">    <tbody>        <tr>            <td style="text-align: center">2</td>            <td style="text-align: center">45.3</td>            <td style="text-align: center">68.2</td>            <td style="text-align: center">36.8</td>        </tr>        <tr>            <td style="text-align: center">7</td>            <td style="text-align: center">77.4</td>            <td style="text-align: center">55.7</td>            <td style="text-align: center">70.1</td>        </tr>        <tr>            <td style="text-align: center">3</td>            <td style="text-align: center">22.0</td>            <td style="text-align: center">79.9</td>            <td style="text-align: center">53.5</td>        </tr>        <tr>            <td style="text-align: center">...</td>            <td style="text-align: center">...</td>            <td style="text-align: center">...</td>            <td style="text-align: center">...</td>        </tr>    </tbody></table></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="keyword">char</span><span>&nbsp;Buffer[50]&nbsp;=&nbsp;{0}; </span></span></li>    <li>&nbsp;</li>    <li class="alt"><span>ifstream&nbsp;infile;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//ifstream&nbsp;infile(filename); </span></li>    <li><span>infile.open(filename,&nbsp;ios::</span><span class="keyword">in</span><span>);&nbsp;&nbsp;</span><span class="comment">// </span></li>    <li class="alt">&nbsp;</li>    <li><span class="keyword">int</span><span>&nbsp;DATA1; </span></li>    <li class="alt"><span class="keyword">float</span><span>&nbsp;DATA2,&nbsp;DATA3,&nbsp;DATA4; </span></li>    <li><span class="keyword">char</span><span>&nbsp;c; </span></li>    <li class="alt"><span class="keyword">while</span><span>(infile.</span><span class="keyword">get</span><span>(c)) </span></li>    <li><span>{ </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">if</span><span>(c==0x00&nbsp;||&nbsp;c&nbsp;==&nbsp;</span><span class="string">'\n'</span><span>&nbsp;&nbsp;||&nbsp;c&nbsp;==</span><span class="string">'\t'</span><span>)</span><span class="keyword">continue</span><span>; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">else</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;infile.seekg(-1,&nbsp;ios::cur); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;infile.getline(Buffer,&nbsp;50); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;sscanf(Buffer,&nbsp;</span><span class="string">&quot;%d&nbsp;&nbsp;%f&nbsp;%f&nbsp;%f&nbsp;&quot;</span><span>, </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&amp;DATA1,&nbsp;&amp;DATA2,&nbsp;&amp;DATA3,&nbsp;&amp;DATA4); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DATA1list.push_back(DATA1);&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DATA2list.push_back(DATA2);&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DATA3list.push_back(DATA3);&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DATA4list.push_back(DATA4); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;} </span></li>    <li>&nbsp;</li>    <li class="alt"><span>} </span></li>    <li>&nbsp;</li>    <li class="alt"><span>infile.close();</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>	char Buffer[50] = {0};	ifstream infile;    //ifstream infile(filename);	infile.open(filename, ios::in);  //	int DATA1;	float DATA2, DATA3, DATA4;	char c;	while(infile.get(c))	{		if(c==0x00 || c == '\n'  || c =='\t')continue;		else		{		infile.seekg(-1, ios::cur);		infile.getline(Buffer, 50);		sscanf(Buffer, &quot;%d  %f %f %f &quot;,		&amp;DATA1, &amp;DATA2, &amp;DATA3, &amp;DATA4);        DATA1list.push_back(DATA1);         DATA2list.push_back(DATA2);         DATA3list.push_back(DATA3);         DATA4list.push_back(DATA4);		}	}	infile.close();</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>基本上就是字面的意思。但是，这个样子经常会出错，特别是格式化字符那里，又有多少人铭记double类型的格式化字符是%lf而不是%f呢&hellip;&hellip;用sstream，就可以这样做：</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>ifstream&nbsp;infile;&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>infile.open(filename,&nbsp;ios::</span><span class="keyword">in</span><span>);&nbsp; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">int</span><span>&nbsp;DATA1; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;DATA2,&nbsp;DATA3,&nbsp;DATA4; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;stringstream&nbsp;sem; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;sem&nbsp;&lt;&lt;&nbsp;infile.rdbuf(); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">while</span><span>(</span><span class="keyword">true</span><span>) </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sem&nbsp;&gt;&gt;&nbsp;DATA1; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">if</span><span>(sem.eof())</span><span class="keyword">break</span><span>; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sem&nbsp;&gt;&gt;&nbsp;DATA2; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sem&nbsp;&gt;&gt;&nbsp;DATA3; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sem&nbsp;&gt;&gt;&nbsp;DATA4; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DATA1list.push_back(DATA1); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DATA2list.push_back(DATA2); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DATA3list.push_back(DATA3); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DATA4list.push_back(DATA4); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} </span></li>    <li class="alt"><span>infile.close(); </span></li>    <li>&nbsp;</li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//ifstream infile;    infile.open(filename, ios::in); 	int DATA1;	float DATA2, DATA3, DATA4;	stringstream sem;	sem &lt;&lt; infile.rdbuf();	  while(true)	  {		sem &gt;&gt; DATA1;		if(sem.eof())break;		sem &gt;&gt; DATA2;		sem &gt;&gt; DATA3;		sem &gt;&gt; DATA4;		DATA1list.push_back(DATA1);		DATA2list.push_back(DATA2);		DATA3list.push_back(DATA3);		DATA4list.push_back(DATA4);	  }infile.close();</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: left">1. fstream对象的rdbuf()成员函数返回的是一个std::filebuf*指针，用以重定向流，结果就是文件的内容全部作为一个C++流，流向我们的stringstream对象里。</p><p style="text-align: left">2.我们从该stringstream对象里从头取出各个数据到中间变量DATAi中，这可不是一个一个字符取哦，因为它是以空格或者\n,\t之类字符为分隔的，所以很方便；</p><p style="text-align: left">3.同时，这里取出到DATA的也不是字符串，而确实就是连续一个int三个float哦，因为内部有安全的转换机制，所以不要再担心C那种格式化的问题了，无论是INT还是DOUBLE还是CHAR*都能做到。这是stringstream最强大之处。</p><p style="text-align: left">4.判断&ldquo;取&rdquo;的动作结束的eof()为什么要在最后做了一次&ldquo;无用功&rdquo;后才判断呢？因为只有这样能判断。stringstream这时候扮演着一个乐施者，他不会去数自己包包里还剩下多少块波板糖，只会不停伸手入包里取出糖，而只有最后一次伸手入包包发现已经没糖了&mdash;&mdash;所以才叫最后一次&mdash;&mdash;才会醒觉：哦，原来我一贫如洗了。</p><p style="text-align: left">基本上太强了。再举例，这次是保存数据到文件，虽是简单，但如果按我以前用fstream文件流的方法，效果比C的fwrite要糟糕：</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">char</span><span>&nbsp;Buffer[50]&nbsp;=&nbsp;{0}; </span></span></li>    <li>&nbsp;</li>    <li class="alt"><span>ofstream&nbsp;fileSave; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;fileSave.open(filename,ios::ate); </span></li>    <li class="alt">&nbsp;</li>    <li><span class="keyword">for</span><span>(</span><span class="keyword">int</span><span>&nbsp;i&nbsp;=&nbsp;0&nbsp;;&nbsp;i&nbsp;&lt;&nbsp;Num;&nbsp;i++) </span></li>    <li class="alt"><span>{ </span></li>    <li><span>&nbsp;sprintf(Buffer,&nbsp;</span><span class="string">&quot;%d&nbsp;&nbsp;%.2f&nbsp;%.2f&nbsp;%.2f&nbsp;&quot;</span><span>, </span></li>    <li class="alt"><span>DATA1[i],&nbsp;DATA2[i],&nbsp;DATA3[i],&nbsp;DATA4[i]); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;fileSave.write(Buffer,</span><span class="keyword">sizeof</span><span>(</span><span class="string">&quot;00&nbsp;&nbsp;00.00&nbsp;00.00&nbsp;00.00&quot;</span><span>)); </span></li>    <li><span>} </span></li>    <li class="alt"><span>&nbsp;&nbsp;fileSave.close;</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>	char Buffer[50] = {0};	ofstream fileSave;    fileSave.open(filename,ios::ate);	for(int i = 0 ; i &lt; Num; i++)	{	 sprintf(Buffer, &quot;%d  %.2f %.2f %.2f &quot;,	DATA1[i], DATA2[i], DATA3[i], DATA4[i]);	 fileSave.write(Buffer,sizeof(&quot;00  00.00 00.00 00.00&quot;));	}	  fileSave.close;</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: left"><span class="keyword">问题在sizeof</span><span>(</span><span class="string">&quot;00&nbsp;&nbsp;00.00&nbsp;00.00&nbsp;00.00&quot;</span><span>)这里，我没办法确定真实数据的大小。事实上原始数据某组是（3 21.52 63.31 22.22 1.36）或者（320 25.36 54.322 72.31 32.101）都会很麻烦&mdash;&mdash;一般是尽量让这个size大点，不要让它把后面的吃了，但过小的话后面就会出现其他多余的字符，或者每组数据不对齐造成文件不雅观(用strlen()就是另话了)。用stringstream的话，我只想说：很文雅：</span></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;ofstream&nbsp;fileSave;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;fileSave.open(filename,ios::ate);&nbsp; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;ostringstream&nbsp;strStream; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;strStream.setf(ios::showpoint); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;strStream&nbsp;&lt;&lt;&nbsp;std::setprecision(4); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;</span><span class="keyword">for</span><span>(</span><span class="keyword">int</span><span>&nbsp;i&nbsp;=&nbsp;0&nbsp;;&nbsp;i&nbsp;&lt;&nbsp;Num;&nbsp;i++)&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;{&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;strStream&lt;&lt;DATA1[i]&lt;&lt;</span><span class="string">&quot;&nbsp;&nbsp;&quot;</span><span>&nbsp;&lt;&lt;DATA2[i]&lt;&lt;&nbsp;</span><span class="string">&quot;&nbsp;&nbsp;&quot;</span><span>&lt;&lt;DATA3[i]&lt;&lt;&nbsp;</span><span class="string">&quot;&nbsp;&nbsp;&quot;</span><span>&lt;&lt;DATA4[i]&lt;&lt;endl; </span></li>    <li class="alt"><span>&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fileSave.write(&nbsp;strStream.str().c_str(),&nbsp;strStream.str().size()&nbsp;); </span></li>    <li class="alt"><span>&nbsp;&nbsp;} </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;fileSave.close;</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//   ofstream fileSave;    fileSave.open(filename,ios::ate); 	ostringstream strStream;	strStream.setf(ios::showpoint);	strStream &lt;&lt; std::setprecision(4);   for(int i = 0 ; i &lt; Num; i++)   {      strStream&lt;&lt;DATA1[i]&lt;&lt;&quot;  &quot; &lt;&lt;DATA2[i]&lt;&lt; &quot;  &quot;&lt;&lt;DATA3[i]&lt;&lt; &quot;  &quot;&lt;&lt;DATA4[i]&lt;&lt;endl;      fileSave.write( strStream.str().c_str(), strStream.str().size() );  }  fileSave.close;</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>setprecision(4)是有效数字位数，所以数据不会难看（当然小数位数也是可设定的），配合strStream&lt;&lt;DATA1[i]&lt;&lt;<span class="string">&quot;&nbsp;&nbsp;&quot;</span><span>&nbsp;&hellip;&hellip;这样的&ldquo;流&rdquo;式输出，感觉太好了。最后write的参数就是strStream内容转为std::string后，其大小以及再转化为char*所得的底层字符串地址了。太好了。</span></p><p><span>我不太研究STD里的效率问题，所以我不能给出效率方面的建议，但是，即使效率有小小损失，抵得过它的方便吗？以前基本用fprint,sscanf现在想多用stringstream了；以前是char*用得多、MFC也用CString，但现在觉得标准C++的string其实也很可爱的；以前是atoi,atof，然后sprintf做f-to-char,i-to-char(a)，现在知道stringstream才是这类转换的高手哈。</span></p><p><span>最后提下，清空一个stringstream对象(stringstream strs)里的内容时，单strs.clear()是不行的，要用strs.str(&quot;&quot;)这句才行哦。最后最后，写程序时写出的一段代码，给有缘人参考，多多指教：</span></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">//把整个文件内容存储到单独一个字符串里：&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;ofstream&nbsp;dfileSave;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;stringstream&nbsp;outstream;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;dfileSave.open(</span><span class="string">&quot;tResult.txt&quot;</span><span>,&nbsp;&nbsp;ios::</span><span class="keyword">in</span><span>&nbsp;|&nbsp;ios::app);&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;std::filebuf&nbsp;*pFileBuffer&nbsp;=&nbsp;&nbsp;dfileSave.rdbuf();&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;outstream&nbsp;&lt;&lt;&nbsp;pFileBuffer;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">int</span><span>&nbsp;filesize&nbsp;=&nbsp;pFileBuffer-&gt;pubseekoff(0,&nbsp;ios::end,&nbsp;ios::</span><span class="keyword">in</span><span>);&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;std::</span><span class="keyword">string</span><span>&nbsp;sre&nbsp;=&nbsp;outstream.str();&nbsp;</span><span class="comment">//1&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">const</span><span>&nbsp;</span><span class="keyword">char</span><span>&nbsp;*de&nbsp;=&nbsp;sre.c_str();&nbsp;&nbsp;</span><span class="comment">//2&nbsp; </span></li>    <li><span>&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;dfileSave.close();&nbsp; </span></li>    <li>&nbsp;</li>    <li class="alt"><span class="comment">//ZwqXin:&nbsp;拜拜~！</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//把整个文件内容存储到单独一个字符串里：     ofstream dfileSave;     stringstream outstream;       dfileSave.open(&quot;tResult.txt&quot;,  ios::in | ios::app);       std::filebuf *pFileBuffer =  dfileSave.rdbuf();       outstream &lt;&lt; pFileBuffer;       int filesize = pFileBuffer-&gt;pubseekoff(0, ios::end, ios::in);       std::string sre = outstream.str(); //1     const char *de = sre.c_str();  //2       dfileSave.close(); //ZwqXin: 拜拜~！</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>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/cpp/use-sstream.html" target="_blank">继续阅读《不要犹豫了，用sstream吧》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/cpp.html">C/C++</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=C">C</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E7%AC%94%E8%AE%B0">笔记</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=IO">IO</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/cpp/use-sstream.html#comment" target="_blank">添加评论</a>(1)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/swapbuffers-fps-vsync.html">SwapBuffers的等待，虚伪的FPS</a> (2009-9-4 19:47:14)  </li><li><a href="http://www.zwqxin.com/archives/opengl/indexed-vbo-and-multitex-vbo.html">索引顶点的VBO与多重纹理下的VBO</a> (2009-8-25 9:46:0)  </li><li><a href="http://www.zwqxin.com/archives/opengl/learn-vbo.html">学一学，VBO</a> (2009-8-23 22:33:18)  </li><li><a href="http://www.zwqxin.com/archives/opengl/opengl-font-setting-showing-2.html">在OpenGL上设置字体和显示文字(下)</a> (2009-8-6 10:22:27)  </li><li><a href="http://www.zwqxin.com/archives/opengl/opengl-font-setting-showing.html">在OpenGL上设置字体和显示文字(上)</a> (2009-8-5 1:32:13)  </li></ul>]]></description><category>C/C++</category><comments>http://www.zwqxin.com/archives/cpp/use-sstream.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=74</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=74&amp;key=f21b065b</trackback:ping></item></channel></rss>
