<?xml version="1.0" standalone="yes"?>
<?xml-stylesheet type="text/xsl" href="css/rss.xslt"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>ZwqXin - Shader技术</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>Wed, 08 Sep 2010 05:24:36 +0800</pubDate><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>]]></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>]]></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>]]></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>]]></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>Vertex Texture Fetch 顶点纹理拾取</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/shaderglsl/glsl-vertex-texture-fetch.html</link><pubDate>Wed, 19 Aug 2009 22:50:32 +0800</pubDate><guid>http://www.zwqxin.com/archives/shaderglsl/glsl-vertex-texture-fetch.html</guid><description><![CDATA[<p>&nbsp;Vertex Texture Fetch，简称VTF，是Shader Model的一个特性。其本质没有什么复杂的：就是在顶点Shader里检索纹理。&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>] <br />[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-cube-mapping-shader.html">Shader快速复习：Cube Mapping(立方环境贴图)</a>] <br />[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-reflect-and-refract.html">Shader快速复习：Reflection And Refraction(反射与折射)</a>]</p><p>是的，在vertex shader里也可以检索纹理。我本来觉得这没什么好奇怪的，因为我一直也觉得这很当然可以啊~当初橙书(OpenGL Shading Language Edtion2)也说过texture2D这类函数不是fragment shader专用的，倒还有texture2DLod这种在vertex shader里专用的（后面一句是马后炮~），只是我不知道怎么用，在哪里用，以及更重要的：为什么要用。</p><p>为什么要在vertex shader里检索纹理。</p><p>都知道，纹理里的一般是一幅图像，无论是外部导入的还是通过FBO等手段渲染到的。既然如此，有意义的当然是图像里的每一个像素啦，通过纹理坐标检索纹理中的像素打印到屏幕某个地方，并可控制细节程度&hellip;&hellip;反映在GPU编程中，一般就是把当前绑定的纹理的纹理单元（默认为0）传送给Fragment Shader作为sampler，在vertex shader里用gl_TexCoord[0] = glMutiTexCoord0这样的语句，获取固定流水线中为每个顶点设置好的纹理坐标（顶点纹理索引，即glMutiTexCoord0），赋给本质为varying的gl_TexCoord[0]，让它带着纹理坐标在光栅化过程中插值&mdash;&mdash;对应每个像素点拥有属于它的插值后像素纹理索引（gl_TexCoord[0]），以此作为参数用texture2D类函数检索纹理sampler。</p><p>直接在顶点阶段就检索纹理意义何在？获得的只是那些顶点的纹理坐标检索出的&ldquo;孤立&rdquo;像素值而已。</p><p>你认识吗？GPGPU。</p><p style="text-align: center"><img alt="" src="http://www.gpgpu.org/forums/templates/subSilver/images/logo_phpBB.gif" />[<strong>gpgpu</strong>.org]</p><p>GPGPU（General Purpose Graphic Process Unit，通用目的图形处理单元），是应用GPU的高速并行能力和浮点运算能力进行科学计算等SIMD类型[单指令多数据]的应用。在这里，GPU-shader不仅仅着眼于图形。而GPGPU的一个重要概念就是：<span style="color: #ff0000">纹理 =&nbsp; 数组<font color="#000000">。是的，为什么不可以呢？纹理确实就是数组啊。我们能传入shader的只有具体的数值，bool,int,float,vec,matrix，其中最大的matrix4也只有16个量。那么如果我们要把大量的数据传入shader，譬如一个巨大的float数组，怎么办呢？对啊，用纹理！这时候，纹理内部每个数值不再是像素的值，而是数组的数据项。我们只是通过纹理这种灵活的媒介，让数据&ldquo;进入&rdquo;GPU的视野，让shader可以对这些数据项变量进行访问和操作。</font></span></p><p><span style="color: #ff0000"><font color="#000000">顺带一提，现在科学计算领域已经进入GPGPU的进化时代 -CUDA时代了。好吧，不要扯远了。</font></span></p><p><span style="color: #ff0000"><font color="#000000">既然纹理 = 数组， VTF顶点纹理拾取的存在就不言自明了：<span style="color: #ff0000">其实不是在拾取含有图像像素信息的那个纹理，而是在拾取含有顶点数据信息的那个&ldquo;数组&rdquo;啊！</span>在这里，数组的索引就是顶点纹理坐标&hellip;&hellip;看例子：</font></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">//RenderMonkey: </span></span></li>    <li><span class="comment">//Vertex&nbsp;Program </span></li>    <li class="alt"><span>varying&nbsp;vec4&nbsp;vertColor; </span></li>    <li><span>uniform&nbsp;sampler2D&nbsp;baseMap; </span></li>    <li class="alt">&nbsp;</li>    <li><span class="keyword">void</span><span>&nbsp;main(&nbsp;</span><span class="keyword">void</span><span>&nbsp;) </span></li>    <li class="alt"><span>{ </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//vertColor&nbsp;=&nbsp;texture2D(baseMap,&nbsp;gl_MultiTexCoord0.xy);&nbsp;&nbsp;与下句等价&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;vertColor&nbsp;=&nbsp;texture2DLod(baseMap,&nbsp;gl_MultiTexCoord0.xy,&nbsp;0.0);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;vec4&nbsp;pos&nbsp;=&nbsp;gl_ModelViewMatrix&nbsp;*&nbsp;(gl_Vertex)&nbsp;; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;gl_Position&nbsp;=&nbsp;gl_ProjectionMatrix&nbsp;*&nbsp;pos&nbsp;;&nbsp; </span></li>    <li><span>} </span></li>    <li class="alt">&nbsp;</li>    <li><span class="comment">//Fragment&nbsp;Program </span></li>    <li class="alt"><span>varying&nbsp;vec4&nbsp;vertColor; </span></li>    <li>&nbsp;</li>    <li class="alt"><span class="keyword">void</span><span>&nbsp;main(&nbsp;</span><span class="keyword">void</span><span>&nbsp;) </span></li>    <li><span>{ </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;gl_FragColor&nbsp;=&nbsp;vertColor; </span></li>    <li><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//RenderMonkey://Vertex Programvarying vec4 vertColor;uniform sampler2D baseMap;void main( void ){    //vertColor = texture2D(baseMap, gl_MultiTexCoord0.xy);  与下句等价      vertColor = texture2DLod(baseMap, gl_MultiTexCoord0.xy, 0.0);            vec4 pos = gl_ModelViewMatrix * (gl_Vertex) ;    gl_Position = gl_ProjectionMatrix * pos ; }//Fragment Programvarying vec4 vertColor;void main( void ){    gl_FragColor = vertColor;}</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>这个例子是说明：诶？原来Vertex Shader里也可以做纹理拾取口牙！顺带一提，这里用texture2D，和用&ldquo;texture2DLod+尾参数[细节参数LOD] = 0.0&rdquo;的效果是一样的：<br />&nbsp;</p><p style="text-align: center"><a href="http://www.zwqxin.com/archives/shaderglsl/glsl-vertex-texture-fetch.html"><img alt="www.zwqxin.com  VTF 顶点纹理拾取" src="http://lh5.ggpht.com/_lYWT-2PnV0s/Snwlqxw1_cI/AAAAAAAAAxQ/QwOnNzir8gE/s800/806Snap2.jpg" /></a><br />（对比用。这是FTF，传统的fragment shader获取纹理）<br /><a href="http://www.zwqxin.com/archives/shaderglsl/glsl-vertex-texture-fetch.html"><img alt="www.zwqxin.com  VTF 顶点纹理拾取" src="http://lh4.ggpht.com/_lYWT-2PnV0s/SnwlqbiGVrI/AAAAAAAAAxM/a--DzX9tI70/s800/806Snap1.jpg" /></a><br />（这是VTF，顶点纹理拾取，也就是上面代码的产物，对比两图哈）</p><p>纹理只是普通的纹理。只是为了证明VTF能行- -。把顶点纹理的值做插值，预料最后的结果类似于顶点颜色插值，三角片元的颜色在三角的三顶点所获得的纹理颜色间进行线性插值，得出如此&ldquo;重过渡味+模糊&rdquo;的怪象（嘛~这纹理即使是那FTF出来的也是怪象）。然后测试texture2DLod这个函数，把最后的LOD参数增大调为0.4：</p><p style="text-align: center"><a href="http://www.zwqxin.com/archives/shaderglsl/glsl-vertex-texture-fetch.html"><img alt="www.zwqxin.com  VTF 顶点纹理拾取" src="http://lh4.ggpht.com/_lYWT-2PnV0s/SnwlrfDaCFI/AAAAAAAAAxY/KbjF5j0dOj8/s800/806Snap4.jpg" /></a></p><p>lod是细节参数，这跟以前的FTF（PTF）差不多。好吧，换张纹理后，再用VTF做点更有趣的：</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">//RenderMonkey:&nbsp; </span></span></li>    <li><span class="comment">//Vertex&nbsp;Program&nbsp; </span></li>    <li class="alt"><span>varying&nbsp;vec4&nbsp;vertColor;&nbsp; </span></li>    <li><span>uniform&nbsp;sampler2D&nbsp;baseMap;&nbsp; </span></li>    <li class="alt">&nbsp;</li>    <li><span class="keyword">void</span><span>&nbsp;main(&nbsp;</span><span class="keyword">void</span><span>&nbsp;)&nbsp; </span></li>    <li class="alt"><span>{&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vertColor&nbsp;=&nbsp;texture2DLod(baseMap,&nbsp;gl_MultiTexCoord0.xy,&nbsp;0.0); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;vec4&nbsp;offset&nbsp;=&nbsp;vec4(0.0); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">if</span><span>(gl_Vertex.z&nbsp;&gt;&nbsp;0.0) </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;offset&nbsp;=&nbsp;vertColor; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="keyword">else</span><span>&nbsp;</span><span class="keyword">if</span><span>(gl_Vertex.z&nbsp;&lt;&nbsp;0.0) </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;offset&nbsp;=&nbsp;vec4(1.0)-vertColor; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;vec4&nbsp;pos&nbsp;=&nbsp;gl_ModelViewMatrix&nbsp;*&nbsp;(gl_Vertex+&nbsp;offset)&nbsp;;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;gl_Position&nbsp;=&nbsp;gl_ProjectionMatrix&nbsp;*&nbsp;pos&nbsp;;&nbsp; </span></li>    <li class="alt"><span>}&nbsp; </span></li>    <li>&nbsp;</li>    <li class="alt"><span class="comment">//Fragment&nbsp;Program&nbsp; </span></li>    <li><span>varying&nbsp;vec4&nbsp;vertColor;&nbsp; </span></li>    <li class="alt">&nbsp;</li>    <li><span class="keyword">void</span><span>&nbsp;main(&nbsp;</span><span class="keyword">void</span><span>&nbsp;)&nbsp; </span></li>    <li class="alt"><span>{&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;gl_FragColor&nbsp;=&nbsp;vertColor;&nbsp; </span></li>    <li class="alt"><span>} </span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//RenderMonkey: //Vertex Program varying vec4 vertColor; uniform sampler2D baseMap; void main( void ) {      vertColor = texture2DLod(baseMap, gl_MultiTexCoord0.xy, 0.0);    vec4 offset = vec4(0.0);    if(gl_Vertex.z &gt; 0.0)          offset = vertColor;    else if(gl_Vertex.z &lt; 0.0)           offset = vec4(1.0)-vertColor;               vec4 pos = gl_ModelViewMatrix * (gl_Vertex+ offset) ;     gl_Position = gl_ProjectionMatrix * pos ; } //Fragment Program varying vec4 vertColor; void main( void ) {     gl_FragColor = vertColor; }</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>能猜到结果变成这样吗？哈，VTF出来的vertColor果然充满力量：</p><p style="text-align: center"><a href="http://www.zwqxin.com/archives/shaderglsl/glsl-vertex-texture-fetch.html"><img alt="www.zwqxin.com  VTF 顶点纹理拾取" src="http://lh6.ggpht.com/_lYWT-2PnV0s/SnwlrJOhE8I/AAAAAAAAAxU/sdGv1Na8Kdw/s800/806Snap3.jpg" /></a></p><p>另外一个例子则用VTF做点有意义的事情。还记得高度图纹理吗？(我在[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/texture-array-demo.html">Terrain Texture-Array Demo</a>] 里也用到过~)</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span class="comment">//RenderMonkey:&nbsp;&nbsp; </span></span></li>    <li><span class="comment">//Vertex&nbsp;Program&nbsp;&nbsp; </span></li>    <li class="alt"><span>varying&nbsp;vec2&nbsp;&nbsp;texCoord; </span></li>    <li><span>uniform&nbsp;sampler2D&nbsp;Texture0; </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;vcol&nbsp;=&nbsp;&nbsp;texture2D(&nbsp;Texture0,&nbsp;gl_MultiTexCoord0.xy); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;gray&nbsp;=&nbsp;0.2990*vcol.r&nbsp;+&nbsp;0.5870*vcol.g&nbsp;+&nbsp;0.1140*vcol.b; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;pos&nbsp;=&nbsp;&nbsp;gl_Vertex; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos.z&nbsp;=&nbsp;pos.z&nbsp;*&nbsp;(1.0&nbsp;-&nbsp;5.0*gray); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;gl_Position&nbsp;=&nbsp;gl_ModelViewProjectionMatrix&nbsp;*&nbsp;pos; </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;texCoord&nbsp;=&nbsp;gl_MultiTexCoord0.xy;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>} </span></li>    <li class="alt">&nbsp;</li>    <li><span class="comment">//Fragment&nbsp;Program&nbsp;&nbsp; </span></li>    <li class="alt"><span>uniform&nbsp;sampler2D&nbsp;Texture0; </span></li>    <li><span>varying&nbsp;vec2&nbsp;texCoord; </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;&nbsp;gl_FragColor&nbsp;=&nbsp;texture2D(&nbsp;Texture0,&nbsp;texCoord&nbsp;); </span></li>    <li class="alt"><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//RenderMonkey:  //Vertex Program  varying vec2  texCoord;uniform sampler2D Texture0;void main(void){   vec4 vcol =  texture2D( Texture0, gl_MultiTexCoord0.xy);       float gray = 0.2990*vcol.r + 0.5870*vcol.g + 0.1140*vcol.b;   vec4 pos =  gl_Vertex;      pos.z = pos.z * (1.0 - 5.0*gray);   gl_Position = gl_ModelViewProjectionMatrix * pos;       texCoord = gl_MultiTexCoord0.xy;   }//Fragment Program  uniform sampler2D Texture0;varying vec2 texCoord;void main(void){    gl_FragColor = texture2D( Texture0, texCoord );}</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>这里本来只有一个平整的网格，和一张类似高度图的纹理。运用VTF把顶点对应的纹理坐标的像素值拉出来转化为灰度（转化法同见[<a target="_blank" href="http://www.zwqxin.com/archives/image-processing/image-intensity-binarization.html">基于亮度的图像二值化处理</a>] ），并转化为该网格顶点的&ldquo;高度&rdquo;。最后的纹理只是平铺上去（那不是阴影哦）。看，灰度高的地方对应的高度高，灰度低的地方对应的高度低。这就是高度场啊，这就是VTF最典型的应用啊！</p><p style="text-align: center"><a href="http://www.zwqxin.com/archives/shaderglsl/glsl-vertex-texture-fetch.html"><img alt="www.zwqxin.com  VTF 顶点纹理拾取" src="http://lh3.ggpht.com/_lYWT-2PnV0s/SnwlrrMgwoI/AAAAAAAAAxc/k29ikdVH2uc/s800/806Snap5.jpg" /></a></p><p>在Vertex Shader里面，通过纹理坐标的检取，VTF获取的是真正的&ldquo;高度值&rdquo;数据&hellip;&hellip;只是这些数据被储存在一张纹理上罢了。</p>]]></description><category>Shader技术</category><comments>http://www.zwqxin.com/archives/shaderglsl/glsl-vertex-texture-fetch.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=69</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=69&amp;key=84a5b876</trackback:ping></item><item><title>Shader快速复习：Reflection And Refraction(反射与折射)</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/shaderglsl/review-reflect-and-refract.html</link><pubDate>Wed, 15 Apr 2009 20:58:29 +0800</pubDate><guid>http://www.zwqxin.com/archives/shaderglsl/review-reflect-and-refract.html</guid><description><![CDATA[<p>&nbsp;在Shader里面，反射和折射的实现其实很简单。关键是使用者要懂得把它们使用在哪里。一起来复习一下啦。&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>]<br /><br/>[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-cube-mapping-shader.html">Shader快速复习：Cube Mapping(立方环境贴图)</a>]</p><p style="text-align: center"><img alt="" src="http://lh4.ggpht.com/_lYWT-2PnV0s/SnmGYYEVvgI/AAAAAAAAAwQ/9ts0QKhe37E/s800/805Snap1.jpg" /></p><p>首先，反射与折射的物理就不多说了。光线矢量在两种介质之间发生方向的改变，而人眼睛和目标物分别在这两种介质当中，非直线的光线连接两者。然而人总会&ldquo;自觉&rdquo;地认为自己的视线总是直线，所以看到目标物在原始位置的上前方，此为折射现象。反射类似。其实这样理解不直观，更&ldquo;白&rdquo;点地说，人的视线在图中5线交汇处的点（称O点）停顿了，所看到的只是交界面处的O点的颜色，好比交界面是一块镜子，人所见的只是镜子的像，而这像由两部分组成，一是沿着反射光像来的&ldquo;投影&rdquo;，二是镜底沿着折射光线来的&ldquo;投影&rdquo;。是的，真真假假在这里已经不重要了，因为这是图形学中反射与折射的&ldquo;图形学原理&rdquo;啊。</p><p>譬如绘制一个湖面后，我们为了要让湖面真实表现反射与折射，需要真的在湖底把真实世界再绘制一次吗？恩，我在学模板缓冲的时候尝试过（小场景），况且不提效率上的浪费，如果湖面不是场景的&ldquo;底面&rdquo;呢？这样就很不好办了吧。折射方面，因为湖底物没有其他参照嘛，肯定要画的。但是有时候你只不过想表达一下湖底的境况&mdash;&mdash;譬如静止的沙床，你其实只需要一张纹理就够了，什么都不用画（如果你觉得可以更随便，干脆忽略折射）&hellip;&hellip;</p><p>要做到这样，就要考虑图形学上的折射和反射了。把湖面作为一块大镜子，在上面帖帖花吧~恩，就是帖纹理，多重纹理的混合。</p><p>1.在哪里找这种纹理呢？</p><p>答案是立方环境帖图[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-cube-mapping-shader.html">Shader快速复习：Cube Mapping(立方环境贴图)</a>] 。而且不仅是通常的反射向量，折射向量同样可以用来检索cubemap：</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">//在fragment&nbsp;shader里检索纹理 </span></li>    <li class="alt"><span class="comment">//basecolor&nbsp;是从湖水的水纹理检索出来的基色 </span></li>    <li><span class="comment">//ReflectColor&nbsp;和RefractColor&nbsp;通过混合因子Ratio混合 </span></li>    <li class="alt"><span class="comment">//结果则是反-折射混合色与基色的混合 </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;basecolor&nbsp;=&nbsp;tex2D(WaterTex,gl_TexCoord[1].xy); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;ReflectColor&nbsp;=&nbsp;vec3(textureCube(WaterCube,&nbsp;ReflectVec)); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;RefractColor&nbsp;=&nbsp;vec3(textureCube(WaterCube,&nbsp;RefractVec));&nbsp; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;outputCol&nbsp;=&nbsp;mix(RefractColor,&nbsp;ReflectColor,&nbsp;Ratio);&nbsp; </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;gl_FragColor&nbsp;=&nbsp;vec4(&nbsp;outputCol&nbsp;*&nbsp;basecolor&nbsp;,&nbsp;1.0&nbsp;);</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//在fragment shader里检索纹理<br/>//basecolor 是从湖水的水纹理检索出来的基色<br/>//ReflectColor 和RefractColor 通过混合因子Ratio混合<br/>//结果则是反-折射混合色与基色的混合<br/><br/>   vec4 basecolor = tex2D(WaterTex,gl_TexCoord[1].xy);<br/>   vec3 ReflectColor = vec3(textureCube(WaterCube, ReflectVec));<br/>   vec3 RefractColor = vec3(textureCube(WaterCube, RefractVec)); <br/><br/>   vec3 outputCol = mix(RefractColor, ReflectColor, Ratio); <br/><br/>   gl_FragColor = vec4( outputCol * basecolor , 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>其中，WaterTex是水纹理的纹理句柄号；WaterCube是环境纹理的句柄号（静态场景中最常见的，是直接把天空盒的纹理组成CUBEMAP环境纹理）。</p><p>2. 反射向量，折射向量怎么来的啊？</p><p>恩，有了上面那张原理图，有了眼睛（相机）坐标，有了湖面中各个顶点的坐标，湖面的法向量，还有两种介质的折射率，当然可以自己动手计算了。但是其实这种小计算，GLSL还是给我们准备好的了：</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">//在vertex&nbsp;&nbsp;shader里：&nbsp; </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;Vpos&nbsp;=&nbsp;gl_ModelViewMatrix&nbsp;*&nbsp;gl_Vertex; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;pos&nbsp;=&nbsp;Vpos.xyz&nbsp;/&nbsp;Vpos.w;&nbsp;&nbsp;</span><span class="comment">//在视图空间下的w矫正的湖面（各顶点）坐标 </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;norm&nbsp;=&nbsp;normalize(gl_NormalMatrix&nbsp;*&nbsp;gl_Normal); </span></li>    <li><span>&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;InVec&nbsp;=&nbsp;&nbsp;normalize(pos&nbsp;-&nbsp;eyepos.xyz);&nbsp;&nbsp;</span><span class="comment">//湖面（各顶点）到眼睛的入射向量 </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;RefractVec&nbsp;=&nbsp;refract(InVec,&nbsp;norm,&nbsp;Eta);</span><span class="comment">//Eta，折射率，0.66 </span></li>    <li><span>&nbsp;&nbsp;&nbsp;ReflectVec&nbsp;=&nbsp;reflect(InVec,&nbsp;norm); </span></li>    <li class="alt">&nbsp;</li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;RefractVec&nbsp;=&nbsp;vec3(matViewInverse&nbsp;*&nbsp;vec4(RefractVec,&nbsp;1.0)&nbsp;);</span><span class="comment">// </span></li>    <li><span>&nbsp;&nbsp;&nbsp;ReflectVec&nbsp;=&nbsp;vec3(matViewInverse&nbsp;*&nbsp;vec4(ReflectVec,&nbsp;1.0)&nbsp;);</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>//在vertex  shader里： <br/><br/>   vec4 Vpos = gl_ModelViewMatrix * gl_Vertex;<br/>   vec3 pos = Vpos.xyz / Vpos.w;  //在视图空间下的w矫正的湖面（各顶点）坐标<br/><br/>   vec3 norm = normalize(gl_NormalMatrix * gl_Normal);<br/>  <br/>   vec3 InVec =  normalize(pos - eyepos.xyz);  //湖面（各顶点）到眼睛的入射向量<br/><br/>   RefractVec = refract(InVec, norm, Eta);//Eta，折射率，0.66<br/>   ReflectVec = reflect(InVec, norm);<br/><br/><br/>   RefractVec = vec3(matViewInverse * vec4(RefractVec, 1.0) );//<br/>   ReflectVec = vec3(matViewInverse * vec4(ReflectVec, 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>refract函数和reflect函数，很容易了解。最后是乘以视图转换矩阵的逆矩阵，把向量转回世界空间了。在橙书里提到可利用gl_TextureMatrix纹理矩阵[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/more-about-matrix-opengl.html">再弹OpenGL矩阵&mdash;&mdash;到另一个空间去</a>] ，但其实不是说要做什么纹理坐标变换，只是用来保存旋转而已。</p><p>最后把它俩作为varying经过插值输出到fragment shader，也不需要重归一化了，因为只用来检索CubeMap（见上篇）。</p><p>3.怎样确定反射与折射结果之间的混合因子？</p><p>其实就是上面提到的Ratio。有这么一种现象：我们在湖边看湖的时候更多地看到的是反射的结果，在湖的上空望下去则更多看到的是折射的结果&mdash;&mdash;换言之，这个混合因子应该跟我们看湖的角度有关（入射角，上图的&Theta;)。这叫Fresnel现象，用其发现的关系式表达，则是：......呃，听说挺复杂的，于是Christophe Schlick 为我们计算机图形学编程提供了一条近似的简单式：</p><p style="text-align: center">Ratio = f + (1 - f) (1 - <span class="docEmphasis">InVec</span> &bull; <span class="docEmphasis">norm</span>)<sup>fresnelPower</sup></p><p>式子的几个参数含义就是上面所说的。另外fresnelPower一般取5就可以了。而 f 则明显得与具体的介质有关，即跟折射率有关：</p><p style="text-align: center"><img alt="" src="http://lh3.ggpht.com/_lYWT-2PnV0s/SnmGTL6FXZI/AAAAAAAAAwM/0iAyYgg01Xc/s800/805Snap2.jpg" /></p><p>n1/n2就是空气与水间的折射率了。</p><p>&nbsp;同时对环境帖图拥有反射和折射的模型(renderMonkey内)：</p><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-reflect-and-refract.html"><img alt="折射 反射 SHADER GLSL  www.zwqxin.com" src="http://lh5.ggpht.com/_lYWT-2PnV0s/SnmjPYEORQI/AAAAAAAAAws/t6QOxgaHMlc/s800/d8205.jpg" /></a><br /><br/><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-reflect-and-refract.html"><img alt="折射 反射 SHADER GLSL  www.zwqxin.com" src="http://lh3.ggpht.com/_lYWT-2PnV0s/SnmjPyjWMRI/AAAAAAAAAww/YYTXVUOTIPk/s800/ertt805.jpg" /></a></p><p style="text-align: right">&nbsp;<br /><br/>(参考资料：OpenGL Shading Language Secong Edition)</p>]]></description><category>Shader技术</category><comments>http://www.zwqxin.com/archives/shaderglsl/review-reflect-and-refract.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=64</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=64&amp;key=d5bb9224</trackback:ping></item><item><title>Shader快速复习：Cube Mapping(立方环境贴图)</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/shaderglsl/review-cube-mapping-shader.html</link><pubDate>Sun, 08 Mar 2009 21:36:55 +0800</pubDate><guid>http://www.zwqxin.com/archives/shaderglsl/review-cube-mapping-shader.html</guid><description><![CDATA[<p>Shader快速复习，巩固知识，加强感觉。今天的内容是Cube Mapping(立方环境贴图)&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com。</p><p style="text-align: left">广告：<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: left">与Cube map认识挺久了，至少在认识GLSL之前就认识了。在固定管道上应用cubemap也比较简单，不过既然与今天的主题无关，就不多说了（给个连接：<a target="_blank" href="http://developer.nvidia.com/object/cube_map_ogl_tutorial.html">CUBE MAP OPENGL TUTORIAL</a>）。</p><p style="text-align: left">Cube map技术说到底就是用一个虚拟的立方体(cube)包围住物体，眼睛到物体某处的向量eyevec经过反射（以该处的法线为对称轴），反射向量reflectvec射到立方体上，就在该立方体上获得一个纹素了（见下图）。明显，我们需要一个类似天空盒般的6张纹理贴在这个虚拟的立方体上。按CUBE MAPPING原意，就是一种enviroment map，因此把周围场景渲染到这6张纹理里是&ldquo;正统&rdquo;的。也就是每次渲染时，都作一次离线渲染，分别在每个矩形中心放置相机&ldquo;拍下&rdquo;场景，用FBO渲染到纹理，然后把这张纹理作为一个cube map对象的六纹理之一。这样即使是动态之物也能被映射到物体表面了（虽然缺点是不能映射物体自身的任何部分）。</p><p style="text-align: center"><img height="452" alt="" width="418" src="http://www.paulsprojects.net/tutorials/simplebump/cubemap.jpg" /></p><p style="text-align: left">以上这些都需要在opengl实现里完成，而我接下来只想应用shader，就只用一个天空盒的六张图好了，也暂不设置opengl实现了（反正rendermonkey里内置了些cubemap）。</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>uniform&nbsp;vec4&nbsp;eyepos; </span></span></li>    <li><span>varying&nbsp;vec3&nbsp;reflectvec; </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;normalize(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>&nbsp;&nbsp;&nbsp;vec3&nbsp;eyevec&nbsp;=&nbsp;normalize(eyepos.xyz&nbsp;-&nbsp;pos.xyz); </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; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;reflectvec&nbsp;=&nbsp;reflect(-eyevec,&nbsp;norm); </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>uniform vec4 eyepos;<br/>varying vec3 reflectvec;<br/><br/>void main(void)<br/>{<br/>   vec4 pos = normalize(gl_ModelViewMatrix * gl_Vertex);<br/>   pos = pos / pos.w;<br/>   <br/>   vec3 eyevec = normalize(eyepos.xyz - pos.xyz);<br/>   vec3 norm = normalize(gl_NormalMatrix * gl_Normal);<br/>   <br/>   reflectvec = reflect(-eyevec, norm);<br/>   <br/>   gl_Position = ftransform();<br/>}</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>一切都很单纯，在vertex shader里，依靠传入的眼睛位置计算单位化视线向量eyevec，并依靠正确处理好的顶点法线作为对称轴求出反射向量，注意reflect函数接受一个入射向量，产出出射向量，而-eyevec才是入射向量（入射点 - 光线发出点[眼睛]）。</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>uniform&nbsp;samplerCube&nbsp;cubemap; </span></span></li>    <li><span>varying&nbsp;vec3&nbsp;reflectvec; </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;texcolor&nbsp;=&nbsp;textureCube(cubemap,&nbsp;reflectvec); </span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp;&nbsp;&nbsp;gl_FragColor&nbsp;=&nbsp;texcolor; </span></li>    <li class="alt"><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>uniform samplerCube cubemap;<br/>varying vec3 reflectvec;<br/><br/>void main(void)<br/>{<br/>   vec4 texcolor = textureCube(cubemap, reflectvec);<br/><br/>   gl_FragColor = texcolor;<br/>}</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更加简洁，不过就是检取一个纹理对象而已。不过用的检索器是samplerCube而不是单纯的二维纹理检索器sampler2D。颜色提取函数也是cube map版本的textureCube，索引就正是反射向量！显然这里不需要单位化的向量也可。物体象素颜色就取用纹素颜色就可以了。当然可以做些其他处理，譬如加了天蓝色散射光最后进行混合。结果分别如下：</p><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-cube-mapping-shader.html"><img alt="zwqxin.com  cubemap shader" src="http://lh5.ggpht.com/_lYWT-2PnV0s/SbvL8L0trDI/AAAAAAAAAUc/a3JHuKe6qP0/s800/4cubem.jpg" /></a><br /><br/><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-cube-mapping-shader.html"><img alt="zwqxin.com  cubemap shader" src="http://lh3.ggpht.com/_lYWT-2PnV0s/SbvL8vJpvLI/AAAAAAAAAUk/7wm_mCY9rYo/s800/5cubem.jpg" /></a><br /><br/>(换模型，换cubemap后：)<br /><br/><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-cube-mapping-shader.html"><img alt="zwqxin.com  cubemap shader" src="http://lh4.ggpht.com/_lYWT-2PnV0s/SbvL9MiEorI/AAAAAAAAAUs/xK25C4saQSc/s800/6cubem.jpg" /></a></p><p style="text-align: right"><strong>(参考资料：OpenGL Shading Language Secong Edition)</strong></p>]]></description><category>Shader技术</category><comments>http://www.zwqxin.com/archives/shaderglsl/review-cube-mapping-shader.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=38</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=38&amp;key=a1b09902</trackback:ping></item><item><title>Shader快速复习：Per Pixel Lighting(逐像素光照)</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/shaderglsl/review-per-pixel-lighting-shader.html</link><pubDate>Fri, 06 Mar 2009 15:32:32 +0800</pubDate><guid>http://www.zwqxin.com/archives/shaderglsl/review-per-pixel-lighting-shader.html</guid><description><![CDATA[<p style="text-align: left">Shader快速复习，巩固知识加强感觉。今天的内容是Per Pixel Lighting(逐像素光照)&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>抛开光线跟踪和辐射度算法，现在的实时渲染主要用的是GOURAUD模型和PHONG模型，粗俗地说，就是一个是顶点级别的一个是像素级别的，对应per vertex lighting和per pixel lighting。细节点说，就是一个是对顶点插值，一个是对法线插值。具体的区别网络上到处可见辨析。我真正&ldquo;接触&rdquo;per pixel lighting，是在初学GLSL的时候。</p><p>在shader中，我们需要自己计算光照。这最初觉得应该算是麻烦活了，要知道，固定管道中只是简单的API啊，可是那毕竟只是per vertex lighting，在shader里写光照模型最好的就是可以方便地实现per pixel lighting。例如我的<a target="_blank" href="http://www.zwqxin.com/archives/opengl/shadow-map-demo-1.html">shadow map demo</a>就用了，不过只计算散射光而已。</p><p>如上所言，per pixel lighting最重要的是法线插值这步，接下来得十分留意场景传入的法线在shader里的&ldquo;流向&rdquo;；同时不得不&ldquo;提防&rdquo;的还有半向量halfvector的&ldquo;流向&rdquo;。</p><p>经典的opengl光照模型是如下这样的：<br /><a href="http://www.zwqxin.com/archives/shaderglsl/review-per-pixel-lighting-shader.html"><img alt="zwqxin.com per pixel lighting" src="http://lh5.ggpht.com/_lYWT-2PnV0s/SbuutzeeLmI/AAAAAAAAAT8/Cjy85z0h1oc/s800/3plight.jpg" /></a></p><p>但是这里先别理会自发光emission，不理会全局环境光，也不理会光的衰减等等。关注最重心的PHONG模型，有：</p><p style="text-align: center"><strong>pixel color = ambient_color + diffuse_color + specular_color</strong></p><p style="text-align: left">像素最终颜色由3项直接组成，分别是环境光分量（底色），谩反射分量（由光源和物体的相对位置确定，一般作为主分量），镜面光分量（你可以看作物体外覆盖的一层&ldquo;膜&rdquo;，它除受光源和物体的相对位置影响外，还受视线影响）。更多相关内容可看任意一本图形学的书。综上，因此，传入shader的必要物是：物体位置，光源位置，眼睛位置；三种光的颜色；法线。另外还需要一个与镜面光分量相关的shiness参数，决定高光范围。</p><p>顶点shader要做的仅仅是把&ldquo;力所能及&rdquo;的东西做好。计算正确的<strong>光源向量，法线向量和半向量</strong>。半向量是&ldquo;光向量与视线向量的&lsquo;半&rsquo;&rdquo;，等会再多解释。</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>uniform&nbsp;vec3&nbsp;lightpos; </span></span></li>    <li><span>uniform&nbsp;vec4&nbsp;eyepos; </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">&nbsp;</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;vec4&nbsp;pos&nbsp;=&nbsp;gl_ModelViewMatrix&nbsp;*&nbsp;gl_Vertex; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;pos&nbsp;=&nbsp;pos&nbsp;/&nbsp;pos.w; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;lightdir&nbsp;=&nbsp;normalize(lightpos&nbsp;-&nbsp;pos.xyz); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;eyedir&nbsp;=&nbsp;normalize(eyepos.xyz&nbsp;-&nbsp;pos.xyz); </span></li>    <li><span>&nbsp;&nbsp;&nbsp;halfvec&nbsp;=&nbsp;normalize(lightdir&nbsp;+&nbsp;eyedir); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;norm&nbsp;=&nbsp;normalize(gl_NormalMatrix&nbsp;*&nbsp;gl_Normal); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;gl_FrontColor&nbsp;=&nbsp;gl_Color; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;gl_Position&nbsp;=&nbsp;ftransform(); </span></li>    <li><span>}</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>uniform vec3 lightpos;uniform vec4 eyepos;varying vec3 lightdir;varying vec3 halfvec;varying vec3 norm;void main(void){   vec4 pos = gl_ModelViewMatrix * gl_Vertex;   pos = pos / pos.w;      lightdir = normalize(lightpos - pos.xyz);   vec3 eyedir = normalize(eyepos.xyz - pos.xyz);   halfvec = normalize(lightdir + eyedir);      norm = normalize(gl_NormalMatrix * gl_Normal);      gl_FrontColor = gl_Color;   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 style="text-align: left">在像素shader里，首先是把从顶点shader插值而来的向量重新单位化。原因是插值过程中这些向量会把&ldquo;大小&rdquo;也插值了，而我们必须保证这些向量的单位化，从而保证不破坏光照模型。<strong>光源向量</strong>也可以不重新插值（如果它是方向光而不是点光源的话&mdash;&mdash;取决于应用）。</p><div class="HighLighter" contenteditable="false"><div class="dp-highlighter" contenteditable="false"><div class="bar">&nbsp;</div><ol class="dp-c">    <li class="alt"><span><span>lightdir&nbsp;=&nbsp;normalize(lightdir); </span></span></li>    <li><span>norm&nbsp;=&nbsp;&nbsp;&nbsp;normalize(norm); </span></li>    <li class="alt"><span>halfvec&nbsp;=&nbsp;&nbsp;normalize(halfvec);</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>lightdir = normalize(lightdir);norm =   normalize(norm);halfvec =  normalize(halfvec);</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>三种光的公式：</p><ul>    <li><strong>ambient_color = ambient_light_color * ambient_material_color ;</strong></li>    <li><strong>diffuse_color =</strong> <strong>diffuse_light_color * diffuse_material_color * cos(&Theta;);</strong></li>    <li><strong>specular_color = specular_light_color * specular_material_color * cos(&alpha;)<sup>shiness</sup>.</strong></li></ul><p>其中，如果能保证各向量的正确单位化的话，有：</p><p style="text-align: center"><strong>cos(&Theta;) =&nbsp;&nbsp;dot(light_vec , normal_vec) ；<br />cos(&alpha;) = &nbsp;dot(reflect_vec , eye_vec) ；</strong></p><p style="text-align: center"><strong><img alt="" src="http://www.lighthouse3d.com/opengl/glsl/images/ndotl.gif" /></strong><br /><strong><img alt="" src="http://www.lighthouse3d.com/opengl/glsl/images/reflectionV.gif" /></strong></p><p>reflect_vec的求解比较麻烦（主要是reflect这个函数有点耗GPU了），因此按blin模型，可以这样：</p><p style="text-align: center"><strong>dot(reflect_vec , eye_vec) = dot(halfvec , normal_vec)</strong></p><p style="text-align: center"><strong><img alt="" src="http://www.lighthouse3d.com/opengl/glsl/images/halfV.gif" /></strong></p><p>这里的半向量&nbsp;<strong>halfvec = eyevec - Lightvec<sub>(input)</sub>,</strong>注意图中的都是入射光向量<strong>Lightvec<sub>(input) </sub>（= gl_vertex - lightpos)</strong>&nbsp;,但我们shader里是用<strong>Lightvec<sub> </sub>（=&nbsp;lightpos - gl_vertex)</strong>&nbsp;,因此halfvec的计算应该是&nbsp;<strong>halfvec = eyevec&nbsp;+ Lightvec</strong>，即vertex shader里那样。</p><p>简化一下，我shader里就预先把材质颜色和光源颜色合在一起传入，即ambient,&nbsp;diffuse,&nbsp;specular。另外设置三种光所占最终颜色的百分比（这个重要，因为即使是最终颜色，也不过是个0.0~1.0的颜色值，三种光的结果各自就是个0.0~1.0的颜色值，它们要求直接加合的话肯定得超过1.0，因此需要给予权值再加~）。</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>uniform&nbsp;vec4&nbsp;ambient,&nbsp;diffuse,&nbsp;specular; </span></span></li>    <li>&nbsp;</li>    <li class="alt"><span class="keyword">float</span><span>&nbsp;amb&nbsp;=&nbsp;0.3; </span></li>    <li><span class="keyword">float</span><span>&nbsp;diff&nbsp;=&nbsp;0.4; </span></li>    <li class="alt"><span class="keyword">float</span><span>&nbsp;spec&nbsp;=&nbsp;0.3;</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>uniform vec4 ambient, diffuse, specular;float amb = 0.3;float diff = 0.4;float spec = 0.3;</pre></div><div contenteditable="false"><link href="/admin/FCKeditor/editor/plugins/highlighter/dp.SyntaxHighlighter/Styles/SyntaxHighlighter.css" type="text/css" rel="stylesheet" /></div></div><p>最后就是这样了：</p><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">float</span><span>&nbsp;diffusefract&nbsp;=&nbsp;max(&nbsp;dot(lightdir,norm)&nbsp;,&nbsp;0.0); </span></span></li>    <li><span>&nbsp;</span><span class="keyword">float</span><span>&nbsp;specularfract&nbsp;=&nbsp;max(&nbsp;dot(halfvec,norm)&nbsp;,&nbsp;0.0); </span></li>    <li class="alt"><span>&nbsp;specularfract&nbsp;=&nbsp;pow(specularfract,&nbsp;shiness); </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;gl_FragColor&nbsp;=&nbsp;vec4(amb*ambient.xyz&nbsp;+&nbsp;diff&nbsp;*&nbsp;diffuse.xyz&nbsp;*&nbsp;diffusefract&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+&nbsp;spec&nbsp;*&nbsp;specular.xyz&nbsp;*&nbsp;specularfract&nbsp;,1.0);</span></li></ol></div><div class="c#" contenteditable="false" style="display: none"><pre>  float diffusefract = max( dot(lightdir,norm) , 0.0);   float specularfract = max( dot(halfvec,norm) , 0.0);   specularfract = pow(specularfract, shiness);   gl_FragColor = vec4(amb*ambient.xyz + diff * diffuse.xyz * diffusefract                                    + 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>恩.....考虑到cos(&Theta;) 、cos(&alpha;)小于0的时候说明该物体部分不接受光照，直接取0.0（不产生光照颜色）就行。啊，对了，既然该部分不接受光照，那该部分何必还继续半向量呀取指数呀的计算？浪费。于是，最后的像素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>uniform&nbsp;</span><span class="keyword">float</span><span>&nbsp;shiness; </span></span></li>    <li><span>uniform&nbsp;vec4&nbsp;ambient,&nbsp;diffuse,&nbsp;specular; </span></li>    <li class="alt">&nbsp;</li>    <li><span class="keyword">float</span><span>&nbsp;amb&nbsp;=&nbsp;0.3; </span></li>    <li class="alt"><span class="keyword">float</span><span>&nbsp;diff&nbsp;=&nbsp;0.4; </span></li>    <li><span class="keyword">float</span><span>&nbsp;spec&nbsp;=&nbsp;0.3; </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">&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;lightdir&nbsp;=&nbsp;normalize(lightdir); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;norm&nbsp;=&nbsp;&nbsp;&nbsp;normalize(norm); </span></li>    <li><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;</span><span class="keyword">float</span><span>&nbsp;diffusefract&nbsp;=&nbsp;max(&nbsp;dot(lightdir,norm)&nbsp;,&nbsp;0.0); </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; </span></li>    <li class="alt">&nbsp;&nbsp;&nbsp;<span class="keyword">float</span><span>&nbsp;specularfract</span>&nbsp;= 0.0；</li>    <li><span>&nbsp;&nbsp;&nbsp;</span><span class="keyword">if</span><span>(<span>&nbsp;</span>diffusefract&nbsp;&gt;&nbsp;0.0){ </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;halfvec&nbsp;=&nbsp;&nbsp;normalize(halfvec);&nbsp; </span></li>    <li class="alt">&nbsp;&nbsp;&nbsp;<span>specularfract&nbsp;=&nbsp;max(&nbsp;dot(halfvec,norm)&nbsp;,&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*ambient.xyz&nbsp; </span></li>    <li><span>&nbsp;&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; </span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+&nbsp;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>uniform float shiness;uniform vec4 ambient, diffuse, specular;float amb = 0.3;float diff = 0.4;float spec = 0.3;varying vec3 lightdir;varying vec3 halfvec;varying vec3 norm;void main(void){   lightdir = normalize(lightdir);   norm =   normalize(norm);      float diffusefract = max( dot(lightdir,norm) , 0.0);   float specularfract = max( dot(halfvec,norm) , 0.0);      if(specularfract &gt; 0.0){   halfvec =  normalize(halfvec);    specularfract = pow(specularfract, shiness);   }      gl_FragColor = vec4(amb*ambient.xyz                   + diff * diffuse.xyz * diffusefract                   + 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>效果查看（RenderMonkey）：</p><p style="text-align: center"><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-per-pixel-lighting-shader.html"><img alt="zwqxin.com per pixel lighting" src="http://lh3.ggpht.com/_lYWT-2PnV0s/SbuusYy2_cI/AAAAAAAAATs/A9AdcaeDu7Y/s800/4plight.jpg" /></a><br />(改变模型，颜色，增加镜面光权值，减小shiness后：)<br /><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/review-per-pixel-lighting-shader.html"><img alt="zwqxin.com per pixel lighting" src="http://lh4.ggpht.com/_lYWT-2PnV0s/SbuutExMMoI/AAAAAAAAAT0/qShBHnwOw0g/s800/5plight.jpg" /></a></p><p style="text-align: left">如果打算直接从opengl应用中取值，那注意下面这个内置的gl_LightSourceParameters结构，把相应的变量替换就可以了，例如上面的ambient,&nbsp;diffuse,&nbsp;specular等等，可以不用，而以<strong>gl_LightSource[0].ambient*gl_FrontMaterial].ambient</strong>表示; 另外halfvector甚至不用自己算。但这样做之前可记得在opengl实现里要指定glLightXXX这类函数喔。</p><p style="margin-left: 80px">////////获取应用中设定的光源特性，gl_LightSource[i]对应第i号光源<br /><strong>struct gl_LightSourceParameters {<br />vec4 ambient; <br />vec4 diffuse; <br />vec4 specular; <br />vec4 position; <br />vec4 halfVector; <br />vec3 spotDirection; <br />float spotExponent; <br />float spotCutoff; // (range: [0.0,90.0], 180.0)<br />float spotCosCutoff; // (range: [1.0,0.0],-1.0)<br />float constantAttenuation; <br />float linearAttenuation; <br />float quadraticAttenuation; <br />};<br /><br />uniform gl_LightSourceParameters gl_LightSource[gl_MaxLights];<br />&nbsp;</strong></p><p style="margin-left: 80px">////////获取应用中设定的全局环境光<br /><strong>&nbsp;struct gl_LightModelParameters {<br />vec4 ambient; <br />};<br />&nbsp;<br />uniform gl_LightModelParameters gl_LightModel;</strong></p><p style="margin-left: 80px">////////获取应用中设定的材质<br /><strong>struct gl_MaterialParameters {<br />vec4 emission;&nbsp;&nbsp; <br />vec4 ambient;&nbsp;&nbsp;&nbsp; <br />vec4 diffuse;&nbsp;&nbsp;&nbsp; <br />vec4 specular;&nbsp;&nbsp; <br />float shininess; <br />};<br /><br />uniform gl_MaterialParameters gl_FrontMaterial;<br />uniform gl_MaterialParameters gl_BackMaterial;<br /></strong></p><p style="text-align: right"><strong>参考资料：<a target="_blank" href="http://www.lighthouse3d.com">LightHouse3D</a></strong></p><p style="text-align: left">(碎碎念：明明是快速复习的说，明明是快速复习的说.....)</p>]]></description><category>Shader技术</category><comments>http://www.zwqxin.com/archives/shaderglsl/review-per-pixel-lighting-shader.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=37</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=37&amp;key=1367ea45</trackback:ping></item></channel></rss>
