<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?xml-stylesheet type="text/xsl" href="css/rss.xslt"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>ZwqXin</title><link>http://www.zwqxin.com/</link><description>    一起学习OPENGL吧</description><generator>RainbowSoft Studio Z-Blog 1.8 Walle Build 100427</generator><language>zh-CN</language><copyright>Copyright 2008-2012 ZwqXin. All Rights Reserved. Theme edited from ipati. </copyright><pubDate>Wed, 16 May 2012 23:49:55 +0800</pubDate><item><title>乱弹纪录IV:Transform Feedback</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html</link><pubDate>Sat, 12 May 2012 15:55:31 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html</guid><description><![CDATA[<p>Transform Feedback是SM4.0显卡新流水线下带来的又一项新特性，它使得当前在GPU端流水线上的顶点数据能够有机会回传到我们本地端的应用程序内存中。这个特性带来的渲染流程上的变化，也使得基于2-Pass的场景决策渲染技术进入我们的视野。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html">乱弹纪录I:Geometry Shader</a>]</p><p>[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html">乱弹纪录II:Alpha To Coverage</a>]</p><p>[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/talk-about-geometry-instancing.html">乱弹纪录III:Geometry Instancing</a>]</p><p><span style="font-size: smaller;">本文来源于 <strong><#ZC_BLOG_TITLE#></strong> (<em><a href=""http://www.zwqxin.com/"">http://www.zwqxin.com/</a></em>), 转载请注明<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;原文地址：<em><a href=""<#article/url#>""><#article/url#></a></em></span></p><p>首先，我真不知道该怎么翻译表达Transform Feedback，变换-反馈？Feedback很好理解，因为它点明了这项技术的特性&mdash;&mdash;让OpenGL反馈给我们关于流水线中的数据的信息（在Direct3D 中，这技术被称为Stream-output，所以我们也会把Feedback这种行为称为&ldquo;stream out&rdquo;。）。那么很自然地要问，是什么数据？在OpenGL的辞典中，Transform更常用于矩阵变换这个意义（在传统渲染管线中，顶点进入流水线的第一步就是T&amp;L&mdash;&mdash;顶点矩阵变换+顶点光照计算），因此我们很容易联想到，这里的数据应该就是指顶点数据了。Transform Feedback，就是将Geometry Shader输出的顶点数据（如果没有使用Geometry Shader，则是Vertex Shader输出的数据）回传到我们客户端的一个缓存&mdash;&mdash;Transform Feedback Buffer中。</p><p>&nbsp;话说，如果要体验Transform Feedback的强大，最好还是拥有一张SM5.0的显卡，也就是说，在OpenGL 4.x下使用。毕竟，Transform Feedback这技术也在不断强化中，但要说最大的原因，就是使用风格上的不同&mdash;&mdash;在OpenGL 4.x上的Transform Feedback（GL_transform_feedback2）跟很多其他GL的概念一样，是作为一种State Object（transfrom feedback object）的形式存在的，可以在初始化时绑定一个或多个VBO（[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/learn-vbo.html">学一学，VBO</a>] ）作为自己的Transform Feedback Buffer（这一点和VAO就很像了[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/vao-and-vbo-stuff.html">AB是一家?VAO与VBO</a>] ），这个状态对象可以直接用于后续的渲染步骤（第二个Pass的绘制），也可以通过它对对应的transform feedback过程进行暂停和重启等等；而SM4.0下的OpenGL 3.x，只有通过GL_NV_transform_feedback来支持Transform Feedback，先不说这本质是N卡的拓展，其使用方式也很tricky：绑定某个VBO来作为输出这个步骤是渲染时才设定的，之后使用这个VBO的数据若需要知道其大小还得用个query object去进行异步查询（容易产生time block）&hellip;&hellip;但是呢，如果你是像我一样穷到没钱为显卡更新换代的话，那就只好将就着用了（本文基于OpenGL 3.x）。顺便得提及一下，GL 3.x下要用glew来检查这个功能是否能用，最好使用GL_NV_transform_feedback这个宏而不是GL_transform_feedback。</p><p>举一个常见的应用场合：粒子系统。以前（还可以随心所欲使用glVertex3f的时代），这东西很好理解：初始化、更新、渲染、销毁，for循环，CPU计算。但是一旦步入GL3.0这只有VBO的时代，就傻眼了：这是怎么更新的？</p><div class="codeText"><div class="codeHead">OpenGL代码 （glVertex3f的时代）</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span class="keyword">struct</span><span>&nbsp;Particle&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;pos;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;rot;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;<span class="comment">//...</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>};&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>Particle&nbsp;particle[COUNT];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="comment">//初始化</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>init（）&nbsp;&nbsp;</span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;<span class="keyword">for</span><span>（</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;COUNT;&nbsp;++i）&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;particle[i].pos&nbsp;=&nbsp;vec3(...);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;particle[i].rot&nbsp;=&nbsp;...;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//...</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>}&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="comment">//渲染</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>render()&nbsp;&nbsp;</span></li>    <li class="alt"><span>{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span><span>（</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;COUNT;&nbsp;++i）&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//....</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glBegin(...);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glVertex3fv(particle[i].pos);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glEnd();&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//...</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="comment">//更新</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>update（）&nbsp;&nbsp;</span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span><span>（</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;COUNT;&nbsp;++i）&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;particle[i].pos&nbsp;+=&nbsp;vec3(...);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;particle[i].rot&nbsp;+=&nbsp;...;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//...</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>}&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="comment">//销毁</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>destroy（）{}&nbsp;&nbsp;</span></li></ol></div><p>当然，只是一对一替换成VBOs的话也很简单的，初始化跟渲染替换一下：</p><div class="codeText"><div class="codeHead">OpenGL代码 （COUNT VBOs）</div><ol class="dp-cpp" start="1">    <li class="alt"><span>GLuint&nbsp;particlePosVBO[COUNT];&nbsp;&nbsp;</span></li>    <li class="alt"><span>GLuint&nbsp;particleRotVBO[COUNT];&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="comment">//初始化</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>init()&nbsp;&nbsp;</span></li>    <li class="alt"><span>{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glGenBuffers(COUNT,&nbsp;particlePosVBO);&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glGenBuffers(COUNT,&nbsp;particleRotVBO);&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt">&nbsp;</li>    <li><span>&nbsp; PosData = vec3(0);<br />    </span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span><span>（</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;COUNT;&nbsp;++i）&nbsp;&nbsp;&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(particlePosVBO[i]...);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glBufferData(<span class="comment">.....Quad </span></span><span>PosData</span><span><span>);&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//...</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li class="alt"><span>}&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="comment">//渲染&nbsp;&nbsp;</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>render()&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>{&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span><span>（</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;COUNT;&nbsp;++i）&nbsp;&nbsp;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl</span><span>Translate(</span><span> particle[i].pos</span><span>);</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//....&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(particlePosVBO[i]...);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glDawArray(...);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//...&nbsp;&nbsp;</span><span>&nbsp;&nbsp;</span></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;</span></li></ol></div><p>COUNT有多大，就生成多少个VBO，这样是很不环保的。但是当你要把所有数据都放进一个VBO里的时候，初始化不需要for循环了，渲染也不需要多个DrawCall了，你才会意识到这时候的更新变得多么麻烦：</p><div class="codeText"><div class="codeHead">OpenGL代码 (One VBO)</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span>GLuint&nbsp;particlePosVBO;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></li>    <li><span>GLuint&nbsp;particleRotVBO;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span><span class="comment">//初始化&nbsp;&nbsp;</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>init()&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>{&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glGenBuffers(1,&nbsp;&amp;particlePosVBO);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glGenBuffers(1,&nbsp;&amp;particleRotVBO);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;particlePosData[]&nbsp;=&nbsp;{vec3(...),&nbsp;...};<span class="comment">//All&nbsp;Data</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(particlePosVBO...);&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glBufferData(.....PosData);&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//...&nbsp;&nbsp;</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span><span class="comment">//渲染&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>render()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//....&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(particlePosVBO...);&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glDawArray(...);&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="comment">//更新</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>update()&nbsp;&nbsp;</span></li>    <li class="alt"><span>{&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span><span>(</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;COUNT;&nbsp;++i)&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;particlePosData[i]&nbsp;+=&nbsp;vec3();...&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//...</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(particlePosVBO...);&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glBufferSubData(.....particlePosData);&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li></ol></div><p>更新的时候，相当于将一批新的顶点数据传给VBO了，这又将是巨大的性能问题。这时你想到了Shader，何不把整个更新过程移交Shader来完成？可是别忘了，VBO中的数据是固定的，每次传入Shader的顶点变量（vertex attribute）都是粒子在初始状态的数值，所以Shader里应该是一个累积型更新的过程：</p><div class="codeText"><div class="codeHead">glsl代码 (accumulated update)</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span class="keyword">void</span><span>&nbsp;main()&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec3&nbsp;newPos&nbsp;=&nbsp;mat(attribute_Rot)&nbsp;*&nbsp;gl_Vertex;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;newPos&nbsp;=&nbsp;gl_Vertex&nbsp;+&nbsp;attribute_velocity&nbsp;*&nbsp;uniform_Time;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;newPos&nbsp;=&nbsp;otherUpdate(newPos);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;<span class="comment">//...</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;gl_Position&nbsp;=&nbsp;newPos;&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li></ol></div><p>累积型更新只有在你从一开始就确定好粒子的所有运动轨迹的时候才有效。譬如你某个瞬间临时更改速度的方向、或是其他特性，都会导致粒子的位置突变。即使你设立了很多边界条件，确保粒子轨迹的正常，你也很难去调整粒子的个数&mdash;&mdash;粒子生命期的结束、新粒子的诞生等等；即使你在VBO中设定足够多的粒子数量，通过隐藏和重现某些粒子来模拟粒子的消失和诞生，你也很难去索引各个粒子的行为&hellip;&hellip;说了这么久就是说明了粒子系统在新世代的GPU渲染中有必要有新的渲染手法&mdash;&mdash;抱歉，终于把话题重新拐回来了：Transform Feedback。</p><p>&nbsp;避免累积型更新的方法，就是避免规划性。这时候需要回归之前的那种思考模式&mdash;&mdash;某个粒子在当前帧仅仅知道前一帧结束时自己的状态，并以此为状态更新的依据。但我们还是不想使用上述<span>glBufferSubData这种疯狂的更新方式，我们还是希望更新由Shader来完成&mdash;&mdash;Shader的顶点输入数据，可能是上一帧结束时的顶点数据吗？来看看Transform Feedback是怎么完成这个需求的：<br /></span></p><p style="text-align: left;"><a href="http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html" target="_self"><img width="518" height="163" src="https://lh3.googleusercontent.com/-OWGEPxvSbH8/T6--Yy6k18I/AAAAAAAAEH8/zs3kbey3Xm8/s518/tfbgraph20120512.jpg" alt="http://www.zwqxin.com" /></a></p><p>这类似于给予某个物体一个初始动力(Impulse)之后，让它一直在理想光滑圆形轨道上循环运动，同时可以在某个站台装货在某个站台卸货。在这里，我们将粒子的初始状态作为一次性的输入（之后就没它的事情了），粒子流在经过顶点相关的处理（更新）后，一方面继续流水线直到输出到屏幕，另一方面通过Transfom Feedback返回，作为下一轮循环的输入。因为每次作为输入的数据都是当前&ldquo;最新&rdquo;的，所以它可以很简单地针对每个粒子当前状态作出下一个状态的判断和更新（在[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html">乱弹纪录I:Geometry Shader</a>]中我提及过，使用GL_POINT去表达粒子，这样使得粒子的更新可以直接在Vertex Shader（或Geometry Shader下的顶点处理）这个层面进行）。粒子系统的更新问题，于是就可以通过这样的途径去解决。</p><p>要注意的一点是，我们上面的流水线中，粒子数据是使用一个Buffer作为输入、另一个Buffer作为（Trasform Feedback的）输出的，因为一个Buffer不可能同时作为同一批次流水线数据的输入和输出（考虑流水线顶点数据的并行处理）。于是我们的Input Buffer和Output Buffer是两个不同的Buffer，要完成上面那个循环，就得不断交换这两个Buffer作为&ldquo;输入&ldquo;和&rdquo;输出&ldquo;的角色&mdash;&mdash;<a name="ghr"></a>当前帧，VBO1作为Input Buffer数据在Shader内更新后通过Transform Feedback输出到Output Buffer（VBO2），下一帧则由VBO2作为Input Buffer，VBO1作为Output Buffer&hellip;&hellip;</p><p>另外，既然我们采用的是GL_POINT去表达粒子，以&rdquo;点&ldquo;为单位更新，那么我们在Output Buffer中输出也应该是GL_POINT，这样它才能在下一帧作为等价的输入。这里便产生了矛盾&mdash;&mdash;对Transform Feedback而言我们的Geometry Shader应该输出points；但对于我们的流水线而言要使接下来的Rasterization（栅格化）有意义、让最终在屏幕上能看见一个个Quad的粒子而不是一个个的点，Geometry Shader的输出就应该是triangle_strip。这是无法调和的，因为Transform Feedback必须会在确定性地产生图元后才好执行，而Geometry Shader阶段是目前流水线中最后影响图元输出的阶段（[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html">乱弹纪录I:Geometry Shader</a>]）！</p><p>折衷的方式就是采用2-Pass的渲染流程&mdash;&mdash;第一个Pass（Update-Pass）直接<a href="#ghr" target="_blank">按上所述</a>进行输入-输出的循环，但不进入后续的流水线操作；第二个Pass（Render-Pass）把刚给到Output Buffer（transform feedback buffer）里的数据再绘制出来，这一次不需要更新了（因为已经是本帧在Pass1进行更新后的结果了），但需要在Geometry Shader里把points膨胀成quads，输出进行裁剪和栅格化、像素处理、输出到屏幕。</p><p style="text-align: left;"><a href="http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html" target="_self"><img width="515" height="277" src="https://lh4.googleusercontent.com/-ik9xqVLMqOk/T6--Y5vJypI/AAAAAAAAEIE/9561yl2AK3Y/s515/tfbgraph2012051202.jpg" alt="http://www.zwqxin.com" /></a></p><p>半空的云簇乍一看的话，作为一个粒子系统貌似离题千尺。但是其实这些东西&mdash;&mdash;包括草簇啊之类各种&mdash;&mdash;都可以是粒子系统：成批细小对象的初始化、更新、渲染、销毁。当然了，那些细小的粒子系统是不会考虑深度排序的、也不会特意作为billboard，但这里的云簇就会具有这两种特性。（关于Geometry Shader产生billboard，我在[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html">乱弹纪录I:Geometry Shader</a>]这篇文章提及过了；关于解决大量物件的深度排序的一种方式&mdash;&mdash;A2C，我在[<a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html" target="_blank">乱弹纪录II:Alpha To Coverage</a>]这篇文章也描述过。）这里把它看做粒子系统，使用Transform Feedback执行2-Pass的更新和渲染，使各个云簇具有自己的速度并自主地飘动。</p><div class="codeText"><div class="codeHead">C++代码 （OpenGL Tranform Feedback）</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span class="comment">//Pass1&nbsp;&nbsp;Update</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;m_CloudFeedShader.Enable();&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">//...</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glEnable(GL_RASTERIZER_DISCARD);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">static</span><span>&nbsp;</span><span class="datatypes">bool</span><span>&nbsp;bShouldDoOriginalInput&nbsp;=&nbsp;</span><span class="keyword">false</span><span>;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span><span>&nbsp;(!bShouldDoOriginalInput)&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bShouldDoOriginalInput&nbsp;=&nbsp;<span class="keyword">true</span><span>;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glBindVertexArray(m_nCloudVAO);<span class="comment">//Input</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//Use&nbsp;Input&nbsp;VBO&nbsp;(在本VAO内)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></li>    <li><span><span class="comment">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; glBindVertexArray(m_nTFCloudVAO[m_nTFCloudVAOIndex]);//Input</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//m_nTFCloudVAOIndex现在指向下一个VBO（作为Output）</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_nTFCloudVAOIndex&nbsp;=&nbsp;(m_nTFCloudVAOIndex&nbsp;+&nbsp;1)&nbsp;%&nbsp;2;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="comment">&nbsp;&nbsp;&nbsp; //GL3.0&nbsp;Bind&nbsp;Output&nbsp;VBO&nbsp;as&nbsp;Transform&nbsp;Feedback&nbsp;Buffer</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="comment">&nbsp;&nbsp;&nbsp; //&nbsp;GL_INTERLEAVED_ATTRIBS&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span><span class="comment">&nbsp;&nbsp;&nbsp; //&nbsp;glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER,&nbsp;0,&nbsp;m_nTFCloudVBO);</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="comment">&nbsp;&nbsp;&nbsp; //GL_SEPARATE_ATTRIBS</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER,&nbsp;0,&nbsp;m_nTFCloudVBO0[m_nTFCloudVAOIndex]);<span class="comment">//Output</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER,&nbsp;1,&nbsp;m_nTFCloudVBO1[m_nTFCloudVAOIndex]);//Output</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER,&nbsp;2,&nbsp;m_nTFCloudVBO2[m_nTFCloudVAOIndex]);<span class="comment">//Output</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER,&nbsp;3,&nbsp;m_nTFCloudVBO3[m_nTFCloudVAOIndex]);<span class="comment">//Output</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glBeginTransformFeedback(GL_POINTS);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glDrawArrays(GL_POINTS,&nbsp;0,&nbsp;m_CloudClusterVec.size());&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glEndTransformFeedback();&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glDisable(GL_RASTERIZER_DISCARD);&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li>    <li>&nbsp;</li>    <li class="alt"><span>&nbsp; //Pass2 Render<br />    </span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;m_CloudShader.Enable();&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">//...</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;glBindTexture(GL_TEXTURE_2D,&nbsp;m_nCloudTex);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//Bind&nbsp;Output&nbsp;VBO(in&nbsp;the&nbsp;VAO)&nbsp;as&nbsp;data&nbsp;for&nbsp;Pass2</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp; glBindVertexArray(m_nTFCloudVAO[m_nTFCloudVAOIndex]);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;glDrawArrays(GL_POINTS,&nbsp;0,&nbsp;m_CloudClusterVec.size());&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;m_CloudShader.Disable();&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li></ol></div><p>这里是GL3.x的Transform Feedback（其实相比GL4.x虽然功能效率都差些，但也有可读性好点的优势嘛。GL4.x那一套我看过，如果是用于粒子系统这种输入输出交换的模式，那是很容易就头晕了）。使用<span>GL_RASTERIZER_DISCARD这个状态可以开启或关闭当前的DrawCall是否在栅格化前就结束（启用的话，数据在Geometry Shader之后就不再进入栅格化阶段了）；整个Tranform Feedback过程：在绑定了一个作为tranform feedback buffer的VBO后，在</span><span>glBeginTransformFeedback/</span><span>glEndTransformFeedback之间的DrawCall将会把结果按GL_PONTS的组织形式输出到该Buffer中（这里</span><span>glBeginTransformFeedback的这个图元组织参数要与Geometry Shader一致，或者没有Geometry Shader的话就得跟glDrawArrays的那个一致咯</span><span>）。云簇更新的操作在第一个Pass完成了，第二个Pass就是以该结果数据的VBO为输入的一般的Billboard渲染了。</span></p><div class="codeText"><div class="codeHead">glsl代码&nbsp;&nbsp; （Pass1 Geometry Shader for Updating）</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span class="preprocessor">#version&nbsp;330</span><span>&nbsp;&nbsp;</span></span></li>    <li><span><span class="preprocessor">#extension&nbsp;GL_EXT_gpu_shader4&nbsp;:&nbsp;enable</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>layout(points)&nbsp;in;&nbsp;&nbsp;</span></li>    <li class="alt"><span>layout(points,&nbsp;max_vertices&nbsp;=&nbsp;5)&nbsp;out;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>uniform&nbsp;<span class="datatypes">float</span><span>&nbsp;lastElapsedTime;&nbsp;&nbsp;</span></span></li>    <li><span>uniform&nbsp;<span class="datatypes">float</span><span>&nbsp;windStrength;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>uniform&nbsp;<span class="datatypes">float</span><span>&nbsp;windSpeed;&nbsp;&nbsp;</span></span></li>    <li><span>uniform&nbsp;vec3&nbsp;&nbsp;windDirection;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>in&nbsp;vec3&nbsp;varying_vg_randvalue[];&nbsp;&nbsp;</span></li>    <li class="alt"><span>in&nbsp;vec3&nbsp;varying_vg_dimension[];&nbsp;&nbsp;</span></li>    <li><span>in&nbsp;<span class="datatypes">int</span><span>&nbsp;&nbsp;varying_vg_cloudcount[];&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>out&nbsp;vec3&nbsp;varing_gf_position;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>out&nbsp;<span class="datatypes">int</span><span>&nbsp;&nbsp;varing_gf_cloudcount;&nbsp;&nbsp;</span></span></li>    <li><span>out&nbsp;vec3&nbsp;varing_gf_dimension;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>)&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;<span class="keyword">for</span><span>(</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;gl_in.length();&nbsp;++i)&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;vLastPos&nbsp;=&nbsp;gl_in[i].gl_Position.xyz;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">int</span><span>&nbsp;&nbsp;nCloudCount&nbsp;=&nbsp;varying_vg_cloudcount[i];&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;vNewPos&nbsp;=&nbsp;vLastPos&nbsp;+&nbsp;windDirection&nbsp;*&nbsp;windSpeed&nbsp;*&nbsp;lastElapsedTime&nbsp;/&nbsp;nCloudCount;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;varing_gf_position&nbsp;=&nbsp;vNewPos;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;varing_gf_cloudcount&nbsp;=&nbsp;nCloudCount;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;varing_gf_dimension&nbsp;=&nbsp;varying_vg_dimension[i];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EmitVertex();&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EndPrimitive();&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;EndPrimitive();&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li></ol></div><p>Transfom Feedback还有一个重要的方面，就是输出数据的形式。上面提及的图元组织形式只是一方面（而且它必须跟Geometry Shader输出图元或DrawCall函数的图元参数一致），还有一方面就是这些数据究竟是&rdquo;哪些数据&ldquo;。事实上，Transform Feedback输出的并不是gl_Position（甚至如果你不需要后续流水线操作的话，你在Vertex Shader或Geometry Shader里也不需要给这个输出值赋值了），而是我们预先告诉它的输出顶点属性（out varying）。在上面的Geometry Shader中，我们的输出其实是<span>varing_gf_position、</span><span><span> varing_gf_cloudcount、</span></span><span>varing_gf_dimension这三个varing变量。它们在哪里指定呢&mdash;&mdash;在建立Shader的时候，正确地说是该Shader program编译(compile)完各Shader但尚未链接(link)的时候：<br /></span></p><div class="codeText"><div class="codeHead">C++代码 （OpenGL&nbsp; CloudCluster-Update-Shader Setup）</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span>m_CloudFeedShader.SetShaders(</span><span class="string">&quot;CloudFeed.vert&quot;</span><span>,&nbsp;</span><span class="string">&quot;CloudFeed.geom&quot;</span><span>,&nbsp;NULL);&nbsp;&nbsp;</span></span></li>    <li class="alt">&nbsp;</li>    <li><span>m_CloudFeedShader.Load(<span class="keyword">false</span><span>);&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="keyword">const</span><span>&nbsp;GLchar&nbsp;*varyingOutCloudFeed[]&nbsp;=&nbsp;{</span><span class="string">&quot;varing_gf_position&quot;</span><span>,&nbsp;</span><span class="string">&quot;varing_gf_cloudcount&quot;</span><span>&nbsp;,</span><span class="string">&quot;varing_gf_dimension&quot;</span><span>};</span><span> <br />    </span></span></li>    <li>&nbsp;</li>    <li class="alt"><span>glTransformFeedbackVaryings(m_CloudFeedShader.GetProgramHandler(),&nbsp;3,&nbsp;varyingOutCloudFeed,&nbsp;GL_SEPARATE_ATTRIBS);&nbsp;&nbsp;</span></li>    <li class="alt">&nbsp;</li>    <li><span>m_CloudFeedShader.Link();&nbsp;&nbsp;</span></li></ol></div><p><span>glTransformFeedbackVaryings这个函数指定了这个Shader中将可用于Transform FeedBack的输出变量。末参数有两种值：</span><span>GL_SEPARATE_ATTRIBS或</span><span><span class="comment">GL_INTERLEAVED_ATTRIBS。前者指定这些Varying变量将输出到不同的VBO中（这时候Transform Feedback绑定相应数目的VBO，</span></span><span>glBindBufferBase第二个参数指定要绑定的VBO的slot。十二分注意的一点，就是数据的一致性，用于输入和输出的VBOs，还有用于初始输入的VBOs[如果另外设置的话]，顺序、大小都得一致。最好的方法就是都遵循粒子结构体[struct CloudCluster]的顺序</span><span><span class="comment">。像上述代码，我们的输入是包括一个固定随机值</span></span><span>randvalue的，但它没必要更新</span><span><span class="comment">，所以transform Feedback在绑定VBO的时候[</span></span><span>glBindBufferBase</span><span><span class="comment">]没必要绑定第二个VBO，但slot的值依然需要遵循输入的VBO的顺序&mdash;&mdash;</span></span><span>CloudCluster的四个成员按顺序对应4个输入和输出VBO，哪怕其中一个没实际作用</span><span><span class="comment">。这个我也觉得很tricky，也花费了我不少精力去搞清楚，但它就是这样了）；后者则是指定</span></span><span><span class="comment">这些Varying变量到一个Interleaved VBO</span></span>([<a href="http://www.zwqxin.com/archives/opengl/indexed-vbo-and-multitex-vbo.html" target="_blank">索引顶点的VBO与多重纹理下的VBO</a>])上。前者有个slot的数量限制，后者则没有，具体使用哪种方式主要还是看原VBO的设定形式和对实际数据集的方便性和习惯了。</p><p style="text-align: left;"><a href="http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html" target="_self"><img width="450" height="312" src="https://lh3.googleusercontent.com/-U-_8gpNWUNw/T6--YoxdbsI/AAAAAAAAEII/79vlmrdN5zk/s450/tfbcloud20120512.jpg" alt="http://www.zwqxin.com" /></a></p><p>最后要提及的就是，基于Transform Feedback的应用衍生了一些基于2-Pass的场景决策渲染技术，它们与上述粒子系统的渲染的主要区别在于它们在第一个Pass会根据某些规则选择剔除一些对于第二个Pass的最终渲染来说不需要的points，这样对于第二个Pass的渲染的提交数据量就会有效减少（要查询新的数据量[GL_PRIMITIVES_GENERATED]需要执行异步query，在GL3.x下这种GPU的反馈方式会造成客户端的阻塞式延迟，但GL4.x的Transform Feedback Object提供了数据量状态的记录用，使第二个Pass可以不查询数据量而直接用glDrawTransformFeedback执行第二个Pass）。这些技术中，基于场景管理的有广度方向的视锥体裁剪、深度的方向的z遮挡测试、模型个体方向的细节程度选择策略，等等。接下来的文章我也会谈及一些，谢谢关注。</p><p>本文到此结束。Transform Feedback这项GPU技术带来的渲染流程变革，除了以上提及的粒子系统、场景决策外，应该还有很多，也将会有越来越多。随着这项技术的演变和强化，可编程渲染管线下的图形程序编程方式也将会越来越多样化吧。</p><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html" target="_blank">继续阅读《乱弹纪录IV:Transform Feedback》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/opengl.html">OpenGL技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=OpenGL">OpenGL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=shader">shader</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=GLSL">GLSL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=VBO">VBO</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E7%AC%94%E8%AE%B0">笔记</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html#comment" target="_blank">添加评论</a>(0)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-geometry-instancing.html">乱弹纪录III:Geometry Instancing</a> (2012-5-6 10:57:23)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-depth-of-field.html">shader复习与深入:Depth of Field(景深)</a> (2012-4-29 15:5:43)  </li><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html">乱弹纪录II:Alpha To Coverage</a> (2012-4-14 15:30:17)  </li><li><a href="http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html">OpenGL常用命令备忘录(Part B)</a> (2012-4-4 14:46:59)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html">乱弹纪录I:Geometry Shader</a> (2012-4-2 15:43:51)  </li></ul>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=105</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=105&amp;key=36b769f1</trackback:ping></item><item><title>乱弹纪录III:Geometry Instancing</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/talk-about-geometry-instancing.html</link><pubDate>Sun, 06 May 2012 10:57:23 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/talk-about-geometry-instancing.html</guid><description><![CDATA[<p>Geometry Instancing（几何体实例化），是一种用于大批量重复物件渲染的GPU技术，以降低客户端和显卡端数据传输量，所谓的&ldquo;一次提交，多次渲染&rdquo;。在OpenGL 3.x下的Instancing技术已经是作为核心，本文也大致地记录一下自己最近使用时的一些思维片段罢。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>[<a href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html" target="_blank">乱弹纪录I:Geometry Shader</a>]</p><p>[<a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html" target="_blank">乱弹纪录II:Alpha To Coverage</a>]</p><p>&nbsp;<span style="font-size: smaller;">本文来源于 <strong><#ZC_BLOG_TITLE#></strong> (<em><a href=""http://www.zwqxin.com/"">http://www.zwqxin.com/</a></em>), 转载请注明<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;原文地址：<em><a href=""<#article/url#>""><#article/url#></a></em></span></p><p>不由得想起当年的<b>CityDreamSnow</b>，在那个Demo中，&ldquo;涉世未深&rdquo;的我是这样绘制封闭街道两旁的建筑群的：四种手工建筑，按一定顺序和错落关系列于两侧，整个场景中，每种建筑都大概有4、5个吧&mdash;&mdash;而且几乎都是一样的（可以回想的不同之处大概除了位置、旋转和缩放度外，还有配色和一些动画的随机控时之类）。对于每种建筑，大概是这样绘制的：</p><div class="codeText"><div class="codeHead">C++代码</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span class="keyword">for</span><span>(</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;NUM;&nbsp;++i)&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;topColor&nbsp;=&nbsp;nTopCol[rand()&nbsp;%&nbsp;COLCOUNT];&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;....&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;glPushMatrix();&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;glTranslate(...);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;glRotate(...,&nbsp;0,&nbsp;1,&nbsp;0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;glScale(..);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;DrawArchitecture(topColor,&nbsp;stripColor,&nbsp;startTick,...);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;glPopMatrix();&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li></ol></div><p>这里是通过一个循环调用了NUM个DrawCall（DrawCall在<span>DrawArchitecture里，当然，那时候都是用glBegin/glEnd的</span>，但是这里看做一个glDrawXX好了），在调用前可以设置这次渲染的各种状态（不仅GL状态，还包括上述的各种矩阵变换、配色等等的状态）。</p><p>把一次DrawCall作为一个Batch，这样做相当于我们在本地客户端（我们的程序所在）向显卡（OpenGL的&ldquo;服务端&rdquo;）连续传输同一份顶点数据共NUM次，这NUM个Batch不同之处仅在于一些顶点属性(attribute)之类的。对于更大的建筑群，或者说广阔的草簇群、NPC群，这样的NUM可能就是成千上万之巨了。显卡不会对这种重复数据多次传输做优化，所以内存和GPU的数据传输负载随着DrawCall的调用次数增多而增大，当程序的效率更多地损失在数据传输上之时，就造成了渲染瓶颈，FPS惨不忍睹。</p><p>Geometry Instancing技术就是为了这样的场合而产生的。这时候，我们可以只调用一次DrawCall，把该份顶点数据（VBO所维护的）传输到显卡，并告诉显卡需要绘制多少次（或者说，执行多少次Vertex Shader）。这就是Insatncing所谓的&ldquo;一次提交，多次渲染&rdquo;。对于OpenGL来说，这样的操作只需要简单地调用Draw函数的Intanced版本就可以了：</p><div class="codeText"><div class="codeHead">C++代码</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span class="keyword">void</span><span>&nbsp;glDrawArraysInstanced(GLenum&nbsp;mode,&nbsp;GLint&nbsp;first,&nbsp;GLsizei&nbsp;count&nbsp;&nbsp;GLsizei&nbsp;primcount);&nbsp;&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="keyword">void</span><span>&nbsp;glDrawElementsInstanced(GLenum&nbsp;mode,&nbsp;GLsizei&nbsp;count,&nbsp;GLenum&nbsp;type,&nbsp;</span><span class="keyword">const</span><span>&nbsp;</span><span class="keyword">void</span><span>&nbsp;*indicies,&nbsp;&nbsp;GLsizei&nbsp;primcount);&nbsp;&nbsp;</span></span></li></ol></div><p>对于VBO（[<a href="http://www.zwqxin.com/archives/opengl/learn-vbo.html" target="_blank">学一学，VBO</a>]  [<a href="http://www.zwqxin.com/archives/opengl/indexed-vbo-and-multitex-vbo.html" target="_blank">索引顶点的VBO与多重纹理下的VBO</a>] ）有了解的话，对上述DrawCall函数的原生版本也不会陌生：<span><span>glDrawArrays和</span></span><span><span>glDrawElements。这里的Instanced版本也就在最后加了个</span></span><span><span>primcount的参数指明需要绘制的次数而已。当然还有其他的变式函数（OpenGL的DrawCall函数的某些变式的名字那可是很让人惊叹的东西），就不一一列举。</span></span></p><p><span><span>调用该函数后，对于传入流水线的每个顶点，其Vertex Shader会执行</span></span><span><span>primcount次（当然包括后面的对应的流水线阶段了，都是执行</span></span><span><span>primcount次</span></span><span><span>），每一次就作为一次实例化，亦即一个Instance。在</span></span><span><span>Vertex Shader或者Geometry Shader（</span></span>[<a href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html" target="_blank">乱弹纪录I:Geometry Shader</a>] <span><span>）</span></span>里，可以使用gl_InstanceID这个attribute变量来获悉当前的Shader是该DrawCall的第几次执行（当前处理的是第几个Instance）。慢着！这样说的话，Instanced版本的Draw函数下，所有顶点的所有Instance都用同一个Vertex Shader，同一套流水线操作，那岂不最终的结果就是一模一样的？！这<span><span>primcount个物件岂不完全重叠在一起？</span></span></p><p><span><span>恩。当然咯。</span></span></p><p>那么我们以前是怎样做的呢？多个DrawCall下，我们可以在DrawCall之前设置好该DrawCall的所有属性。考虑一个简单的情况：让各个物件的位置各不相同，那就在调用DrawCall前传入不同的模型矩阵作为Vertex Shader的uniform。那在Geometry Instancing下，我们只有一个DrawCall，怎样做到上述的效果呢？</p><p>我们还有另一种方法向Vertex Shader输入数据：Attribute变量。我们可以把模型矩阵作为顶点的attribute变量，那么每个顶点就有它的一份模型矩阵了。等等，你说这有啥用？是的，这本身没啥改变：因为我们需要的是该顶点的每个Instance有不同的模型矩阵，反而是同一个Instance的所有顶点的模型矩阵都应该是相同的。这里要说的是，我们可以对每个Instance做同样的事情&mdash;&mdash;我们可以把模型矩阵作为顶点的attribute变量，让每个实例(Instance)有它的一份模型矩阵。</p><div class="codeText"><div class="codeHead">C++代码&nbsp; （OpenGL Instanced VAO Attribute Setup）</div><ol class="dp-cpp" start="1">    <li class="alt">glGenVertexArrays(1, &amp;m_nFloorVAO);</li>    <li class="alt">&nbsp;</li>    <li class="alt">glBindVertexArray(m_nFloorVAO);</li>    <li class="alt">&nbsp;</li>    <li class="alt">//......</li>    <li class="alt">&nbsp;</li>    <li class="alt"><span><span>glGenBuffers(1,&nbsp;&amp;nFloorLVBO);&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glBindBuffer(GL_ARRAY_BUFFER,&nbsp;nFloorLVBO);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glBufferData(GL_ARRAY_BUFFER,&nbsp;floorLocations.size()&nbsp;*&nbsp;<span class="keyword">sizeof</span><span>(ZWVector3),&nbsp;floorLocations.data(),&nbsp;GL_STATIC_DRAW);&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glEnableVertexAttribArray(FLOOR_ATTRIB);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glVertexAttribPointer(FLOOR_ATTRIB,&nbsp;3,&nbsp;GL_FLOAT,&nbsp;GL_FALSE,&nbsp;0,&nbsp;NULL);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glVertexAttribDivisor(FLOOR_ATTRIB,&nbsp;1);&nbsp;&nbsp;</span></li></ol></div><p>这里都是司空见惯的代码了（见[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/vao-and-vbo-stuff.html">AB是一家?VAO与VBO</a>] ），我们直接使用一个位置向量作为attribute（当然你也可以使用矩阵，但就要多使用几个attribute location来划分了。事实上我只需要&ldquo;不同的位置&rdquo;，那直接使用位置向量，在Shader里再结合进一个单位模型矩阵岂不更好）。但不同之处在于<span>FLOOR_ATTRIB这个shader attribute location的设置方法，有两点：第一点是数据本身。</span></p><div class="codeText"><div class="codeHead">C++代码</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span>std::vector&lt;ZWVector3&gt;&nbsp;floorLocations;&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="keyword">for</span><span>&nbsp;(</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;m_nInstanceCount;&nbsp;++i)&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;floorLocations.push_back(..floorLocation[i]);&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li></ol></div><p>上述交代了数据大致是怎么定义的。注意到了吗，总数是<span><span>m_nInstanceCount，也就是说它不是按顶点个数来组织的，而是以Instance个数来组织的&mdash;&mdash;它不是顶点的Attribute而是Instance的Attribute。如果单纯从数据量来改变，这是没有效果的（默认还是把这数据当做顶点的数据，一般如果数据个数小于顶点数，那渲染结果就是后半的顶点要悲剧了 - -），真正让它成为Instance专属数据的是</span></span><span>glVertexAttribDivisor这个函数&mdash;&mdash;这是第二点。</span></p><p><span>glVertexAttribDivisor第一个参数也还是attribute location，第二个参数指明当前的数据（</span><span>floorLocations</span><span>）是每多少个Instance变更一次。这里1的意思是每一个Instance（实例）变更一次，所以渲染时第一个Instance的vertex shader中的</span><span>FLOOR_ATTRIB对应的attribute都将全是</span><span>floorLocations[0]这个数据，第二个Insatnce则是对应</span><span>floorLocations[1]这个数据&hellip;&hellip;第</span><span><span>m_nInstanceCount个Instance</span></span><span>则是对应</span><span>floorLocations[</span><span><span>m_nInstanceCount - </span></span><span>1]这个数据：</span></p><div class="codeText"><div class="codeHead">C++代码 （OpenGL Instanced VAO Attribute Render）</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span>glBindVertexArray(m_nFloorVAO);&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glDrawElementsInstanced(GL_TRIANGLES,&nbsp;6,&nbsp;GL_UNSIGNED_SHORT,&nbsp;NULL, </span><span><span>m_nInstanceCount</span></span><span>);&nbsp;&nbsp;</span></li></ol></div><p style="text-align: center;"><a target="_self" href="http://www.zwqxin.com/archives/opengl/talk-about-geometry-instancing.html"><img width="405" height="291" alt="乱弹纪录III:Geometry Instancing - www.zwqxin.com" src="https://lh6.googleusercontent.com/-enb6T4qK0Zs/T6ZxlNELJmI/AAAAAAAAEHA/YqPbi-kQvVw/s405/%252850ETT3%257EUBJ8%2528%255DULR%2529CBN%257D2F.jpg" /></a></p><p>这就是我们需要的。接下来就是在Vertex Shader里根据该<span>attribute去构建模型矩阵，把顶点移到</span><span>floorLocations[i]指定的位置了。无论是变换矩阵、配色还是其他任何特定于各个Instance的特性，都可以通过这种方法去实现。回到开头的那段代码，应用Geometry Instancing的话：<br /></span></p><div class="codeText"><div class="codeHead">C++代码</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span class="comment">//Setup&nbsp;VAO</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glGenVAO(...,&nbsp;m_nVAO);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glBindVAO(...,&nbsp;m_nVAO);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glGenVBO(...);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glBindVBO(...);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glBufferData(...InstanceData...);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glEnableVertexAttrib(...);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glVertexAttribPointer(..);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glVertexAttribDivisor(..,&nbsp;1);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="comment">//.....and&nbsp;so&nbsp;on&nbsp;for&nbsp;every&nbsp;instance&nbsp;property&nbsp;</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="comment">//and&nbsp;Vertex&nbsp;Data&nbsp;VBO</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glBindVAO(...,&nbsp;NULL);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="comment">////</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="comment">//Render</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glBindVAO(...,&nbsp;m_nVAO);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>DrawArchitecture(...,&nbsp;NUM);&nbsp;&nbsp;&nbsp;&nbsp;</span></li></ol></div><p>这里只有一个DrawCall，而且所有实例Attribute都用VAO存储好。渲染的时候就简单很多了，&ldquo;一次提交，多次渲染&rdquo;。</p><p><span>再提一下，glVertexAttribDivisor的第二个参数，如果是2的话，那就是每两个Instance变更一次instance attribute&hellip;&hellip;如此类推。那如果是0呢？那就是跟以前一样，数据&ldquo;退化&rdquo;变成顶点Attribute了，呵呵。</span></p><p><span>还有没有其他方法呢？</span></p><p><span>再回头看一看Uniform这种类型的输入参数。Uniform一般是针对每个DrawCall的，目前是无法&ldquo;降格&rdquo;到针对每个Insatnce（与此相对，attribute一般是针对每个顶点的，可以&ldquo;升格&rdquo;到针对每个Instance，如上所述）。但是我们也可以把所有的Instance数据打包成一个Array，作为uniform传入vertex shader&mdash;&mdash;上面不是提及</span>gl_InstanceID这个东西的作用了么？用它来检索这个Array不就OK了么！当然了这个方法需要在DrawCall前传入一个或许很&ldquo;重&rdquo;的unifom变量（使用UBO或许可以减小GLSL对uniform变量占宽的限制），Vettex Shader里也得多个检索。至于什么方法更好，就看应用场合+见仁见智了。像如果每个实例需要不同的纹理，那最好的方案是传入一个texture Array（[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/learn-texture-array.html">学一学, Texture Array纹理数组</a>] ），然后使用gl_InstanceID来检索（注意它是个int值，传入fragment shader里的时候要指定flat来避免栅格化插值）。像一个天空盒SkyBox，六个面都是矩形，模型矩阵和纹理不一样，就可以这样做。</p><div class="codeText"><div class="codeHead">glsl代码 (fragment shader, texture for each instance)</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span class="preprocessor">#version&nbsp;330</span><span>&nbsp;&nbsp;</span></span></li>    <li><span><span class="preprocessor">#extension&nbsp;GL_EXT_gpu_shader4&nbsp;:&nbsp;enable</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>uniform&nbsp;sampler2DArray&nbsp;&nbsp;&nbsp;basetexArray;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>in&nbsp;vec2&nbsp;varying_texcoord;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>flat&nbsp;in&nbsp;<span class="datatypes">int</span><span>&nbsp;varying_InstanceID;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>layout(location&nbsp;=&nbsp;0)&nbsp;out&nbsp;vec4&nbsp;fragColor;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>)&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;texCol&nbsp;=&nbsp;texture2DArray(basetexArray,&nbsp;vec3(varying_texcoord,&nbsp;varying_InstanceID));&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;fragColor&nbsp;=&nbsp;texCol;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>}&nbsp;&nbsp;</span></li></ol></div><p>再谈到Geometry Shader的缺点，其中一个就是对于CPU端的视锥体剔除（在渲染前设立条件，视锥体外的物体都不渲染）。因为只有一个DrawCall，你将无法根据预先判断把不在视锥体内的Insatnce剔除渲染阵列&mdash;&mdash;所有流水线操作都将执行，这样对于大场景的大批量渲染的场景管理策略失效，会造成效率的负向影响，甚至Geometry Instancing这应用也得不偿失了。</p><p>在往后的文章，我将会谈及另一种针对Instanced Objects的剔除方法，也就是在[<a href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html" target="_blank">乱弹纪录I:Geometry Shader</a>]中提及的利用Geometry Shader进行几何元剔除的方式，通过额外的一个简单Pass判定可见性，剔除并FeedBack到第二个Pass渲染视锥体可见的物件。这种方式可以一定程度减小Instancing的上述负向影响。</p><p>本文到此结束。随着GPU图形技术的发展，以及大批量物件渲染的需要，过去使用范围很受限的Geometry Instancing如今也越来越重要了，OpenGL对这类技术的支持也越来越丰富，也将越来越更丰富。</p><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/opengl/talk-about-geometry-instancing.html" target="_blank">继续阅读《乱弹纪录III:Geometry Instancing》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/opengl.html">OpenGL技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=OpenGL">OpenGL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=shader">shader</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=GLSL">GLSL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E7%AC%94%E8%AE%B0">笔记</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=VBO">VBO</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/opengl/talk-about-geometry-instancing.html#comment" target="_blank">添加评论</a>(0)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html">乱弹纪录IV:Transform Feedback</a> (2012-5-12 15:55:31)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-depth-of-field.html">shader复习与深入:Depth of Field(景深)</a> (2012-4-29 15:5:43)  </li><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html">乱弹纪录II:Alpha To Coverage</a> (2012-4-14 15:30:17)  </li><li><a href="http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html">OpenGL常用命令备忘录(Part B)</a> (2012-4-4 14:46:59)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html">乱弹纪录I:Geometry Shader</a> (2012-4-2 15:43:51)  </li></ul>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/talk-about-geometry-instancing.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=104</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=104&amp;key=f19e3725</trackback:ping></item><item><title>shader复习与深入:Depth of Field(景深)</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/shaderglsl/review-depth-of-field.html</link><pubDate>Sun, 29 Apr 2012 15:05:43 +0800</pubDate><guid>http://www.zwqxin.com/archives/shaderglsl/review-depth-of-field.html</guid><description><![CDATA[<p>Depth of Field(DOF)与HDR类似，也是一种图形图像后处理手法。它渊源于物理光学中透镜成像过程中的焦散造成的前景和远景模糊化的现象，与HDR一样，都是为了增强Computer Graphics的真实感，在实时渲染中是很常见的一种技术。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>[<a href="http://www.zwqxin.com/archives/shaderglsl/review-high-dynamic-range.html" target="_blank">shader复习与深入:HDR(高动态范围)</a>]</p><p><span style="font-size: smaller;">本文来源于 <strong><#ZC_BLOG_TITLE#></strong> (<em><a href=""http://www.zwqxin.com/"">http://www.zwqxin.com/</a></em>), 转载请注明<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;原文地址：<em><a href=""<#article/url#>""><#article/url#></a></em></span></p><p>拍摄时我们说的&ldquo;景深&rdquo;，一般是指能够清晰成像的距离范围。譬如相机前的4~12米这段距离内的景物最终会清晰地反映在照片上，那这段距离就被认为是&ldquo;景深&rdquo;，而这段距离之外的地方（我这里分别称为前景和远景）在照片上会呈现虚化的现象。Depth of Field(DOF)就是为了描述这种现象而出现的&mdash;&mdash;我们的图形世界本来就是由一个虚拟的摄像机(Camera)来&ldquo;捕捉&rdquo;的啊。</p><p style="text-align: center;"><img width="518" height="174" src="https://lh5.googleusercontent.com/-Ucf-RjuIiJw/T6UrXmxJyCI/AAAAAAAAEFY/Dc5c6uMe5po/s518/Depth_of_field_diagram.png" alt="shader复习与深入:Depth of Field(景深)" /><br />(FROM wiki)</p><p>还是使用HDR那篇文章（[<a href="http://www.zwqxin.com/archives/shaderglsl/review-high-dynamic-range.html" target="_blank">shader复习与深入:HDR(高动态范围)</a>] ）中使用过的场景罢，不是说近处和远处模糊些而已吗？那就在渲染出原帧图像后，再根据它另外生成一张模糊图像（嘛，DownSample、Gaussion Blur什么的，HDR也需要这么张，正好拿来用了），把这两张图像根据场景距离（像素距离）做个混合就OK了：</p><div class="codeText"><div class="codeHead">glsl代码&nbsp; （原帧图像, fragment shader）</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span class="datatypes">float</span><span>&nbsp;fDistScale&nbsp;=&nbsp;length(varying_worldpos)&nbsp;/&nbsp;10.0;&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>fDistScale&nbsp;=&nbsp;clamp(fDistScale&nbsp;*&nbsp;fDistScale,&nbsp;0.0,&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>FragDataScene.a&nbsp;=&nbsp;fDistScale;&nbsp;<span class="comment">//输出，用a通道存储距离因子</span><span>&nbsp;&nbsp;</span></span></li></ol></div><div class="codeText"><div class="codeHead">glsl代码 （终Pass, fragment shader）</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>)&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;texCol&nbsp;=&nbsp;texture2D(basetex,&nbsp;varying_texcoord);&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;texOriginal&nbsp;=&nbsp;texCol;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;<span class="comment">//...</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;texBlur&nbsp;=&nbsp;texture2D(bluredtex,&nbsp;varying_texcoord);&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;FragDataScene&nbsp;=&nbsp;mix(texCol,&nbsp;texBlur,&nbsp;cos(6.28&nbsp;*&nbsp;texOriginal.a)&nbsp;*&nbsp;0.5&nbsp;+&nbsp;0.5);&nbsp;&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li></ol></div><p>&nbsp;</p><p style="text-align: center;"><a href="http://www.zwqxin.com/archives/shaderglsl/review-depth-of-field.html" target="_blank"><img width="497" height="291" src="https://lh6.googleusercontent.com/-JOEK-ZhfMxc/T6UrX0Phg8I/AAAAAAAAEFc/shNxqh9cju4/s912/5%255DZD%257B%2525%2524C5U5HH%25284%257B%25250P6%257DSF4.jpg" alt="shader复习与深入:Depth of Field(景深)" /></a></p><p>这里用了个比较无趣的方法把0 &rarr; 1的距离因子映射成0&rarr; 1&rarr; 0的余弦参量，作为混合参数了。更适合的应该是根据场景距离让混合因子呈现更&ldquo;U&rdquo;型而非现在的&ldquo;类V&rdquo;型，以让&ldquo;景深&rdquo;部分有更加足够的清晰度。嘛，还可以吐槽一下这个&ldquo;距离因子&quot;&mdash;&mdash;最好还是取&ldquo;顶点到像平面的垂直距离&rdquo;而非顶点到摄像机中心的直线距离，这里贪图方便取后者来插值出像素距离了，而且规范化的过程也很不规范(其实是很随便地根据场景大概用了个10.0的&ldquo;magic number&rdquo;)。嘛，意思一下咯。</p><p>好了。结束本文？还是继续唠叨吧&hellip;&hellip;首先，为什么会出现Depth of Field？</p><p>在物理学的小孔成像模型中，场景中所有点产生的光束都无一例外地经过这个无限小的&ldquo;孔&rdquo;打到成像面上，所以视野中场景每个点都会与成像面上的某个点形成一一对应的关系，成像面图像是活生生的场景图像的倒映。小孔成像是利用了光线直线传播（传统意义下）这个原理，简单，而且理论上成像总是足够清晰的，也作为现代摄像机原理的理论基石。但是，理论与现实是有距离的，首先你就不能找到一个&ldquo;无限小&rdquo;的孔，能够接纳所有光线而不减弱它们的强度（不然就得非常长时间曝光以增加进入的光线）。</p><p>顺便一提的就是我们的OpenGL（当然也包括D3D了）的摄像机原理就是小孔成像，还记得那平顶锥么？摄像机原点就是那小孔了，而它的近平面位于小孔之前，是正映的成像面。正因为我们绘制的东西没所谓光强弱的概念，这种&ldquo;即时成像&rdquo;性使它能直接套用这个理论。所以我们无论绘制什么东西，深度无论大小，它都会成&ldquo;清晰的像&rdquo;。这是很好，但是就没有&ldquo;缺陷美&rdquo;了&mdash;&mdash;我们真实使用的正常相机、乃至我们的眼球，所依据的是&ldquo;透镜成像&rdquo;原理，我们会有一个焦点，以及焦点范围(景深)，在该范围外的物体（我们不关心的东西）会虚化&hellip;&hellip;</p><p style="text-align: center;"><img width="500" height="260" src="https://lh5.googleusercontent.com/-VX0loNa_HXg/T6Ur8fgyRzI/AAAAAAAAEFg/GUHI7Jn0S6g/s500/28fig01.jpg" alt="shader复习与深入:Depth of Field(景深)" /><br />（From NVidia）</p><p>在摄像机的透镜成像中，场景中某点产生的光线束是通过凸透镜后产生折射，再打到成像平面上的。场景中每一个点所发出的实际光线都是球式外散的，所以如果是使用有一定径度（或者说，光圈[Aperture]）的透镜来就收的话，同一时间所接收的该点的光线量，相比小孔成像来说，就是很大量的了，所以收集足够光线所需要的曝光时间很短（当然也要看光圈大小了）。那这种方式造成的副作用就是我们关注的景深了：凸透镜都有焦点（位于主轴上，平行于主轴的光线都会聚在这个点上，关于物距像距和焦距的各种大小关系下的成像特点，看来得回去看高中物理了），距凸透镜中心的距离为焦距f，场景某点距凸透镜中心的距离为物距u，成像面距凸透镜中心的距离为像距v，那么就有以下这条经典公式了：</p><p style="text-align: center;"><strong>1/f = 1/u + 1/v</strong></p><p>我们控制摄像机的参数，得到某一固定值f和某一固定值v，那么视野场景中就只有十分有限的点，其距离u能够满足上面的公式。这有什么区别呢？能满足上式的点，它发出的那些光束经过折射后就一定能汇聚到成像面上的一个固定的点了&mdash;&mdash;这个成像面上的点与场景中那个点是一对一的，其光强是该点所有光束的总和，也就是说，很亮很清晰。那么场景中更多的是不满足上式的点啊！不妨设某个这样的点的物距是u<sub>1</sub>，代入上式，将有这样的结果：</p><p style="text-align: center;"><strong>1/f = 1/u<sub>1</sub> + 1/v<sub>1</sub></strong></p><p>可见，这个v<sub>1</sub>不同于成像面的像距v ，所以这样的点发出的光束将不在成像面而在像距为v<sub>1</sub>的地方汇集。无论那个聚点在成像面前还是后，都会造成该点的光线在传播到成像面的时候是&ldquo;散&rdquo;的&mdash;&mdash;形成一个小圆，就是所谓的弥散圆(Circle of Confusion, CoC)了，这样在成像面上该场景点的像不仅位置散开而且能量（亮度）也散开，造成模糊。啊，那既然除了物距恰好是u的点外的大部分场景点都会造成散焦，为什么景深是一个距离范围而不是距离值呢？&mdash;&mdash;因为与u相差不太大的点，它们造成的弥散圆也是比较小的，小到甚至眼睛察觉不到它的存在，所以也被认为是清晰的。这样在u的前后就有一个范围，保证成像的清晰度&mdash;&mdash;所谓的Depth of Field。</p><p style="text-align: center;"><a href="http://www.zwqxin.com/archives/shaderglsl/review-depth-of-field.html" target="_blank"><img width="533" height="399" src="https://lh4.googleusercontent.com/-iPRzbdjWa30/T6UrYcBB3II/AAAAAAAAEFs/7aF38FPV8rU/s640/cf%255B1%255D.jpg" alt="shader复习与深入:Depth of Field(景深)" /></a></p><p>上述就是Dof的基本理论，其中有两个值得注意的地方：一个是前景、背景和景深部分的场景其实是不存在一个明显的界限的，所以实现时的模糊因子与距离的关系曲线不应该有明显的角拐点。另一个就是Circle of Confusion（CoC），这对于一个更精确的Depth of Field模型的建立来说是至关重要的。</p><p>Depth of Field，用于在计算机图形学中正是着力于去<strong>还原</strong>这种透镜成像中的<strong>&ldquo;不完美&rdquo;</strong>的手段。</p><p><em>基于CoC的Depth of Field模型</em></p><p>基于相似三角形和上述公式得出的CoC尺寸大小（<a target="_blank" href="http://http.developer.nvidia.com/GPUGems/gpugems_ch23.html">GPU Gems 1</a>）：</p><p style="text-align: center;">D<sub>CoC</sub> = <strong><em>abs</em></strong>(D<sub>aperture</sub> * (f * (u<sub>x</sub> - u)) /           (u<sub>x</sub> * (u - f)))</p><p>可以认为CoC的大小代表了该点在最终渲染输出中的模糊程度，在GPU Gems 3中有篇较为经典的文章（<a target="_blank" href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch28.html">Practical Post-Process Depth of Field</a>），就利用了Coc去计算前景中像素的模糊程度。为什么集中对前景执行呢？因为在前景中的像素点的光束都会在像平面后侧聚集，具有一个Upper bound，其Coc的径值的增长速度也快于背景中的像素，在距离摄像机越近，这种模糊的表现感就更容易影响最终出来的效果。在深度突变的场合（譬如前景中有一个物体，景深范围中有另一个物体，当前者对后者产生半遮挡的时候），前景的模糊不应该影响到其余的场景像素，但也不应产生过突的边缘，此文采用的是用前景像素模糊前后的CoC推算新的CoC（嘛，具体我也觉得模糊了）。</p><p>&nbsp;</p><p>总的来说，Depth of Field，这种花费也不菲的后处理技术，更适合于某些特定场景，譬如视觉焦点需要集中在某些目标物（武器、标记等等）的时候使用。不然，这可真的是为了&ldquo;瑕疵&rdquo;而疯狂了吧。</p><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/shaderglsl/review-depth-of-field.html" target="_blank">继续阅读《shader复习与深入:Depth of Field(景深)》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/shaderglsl.html">Shader技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=shader">shader</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E7%AC%94%E8%AE%B0">笔记</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E5%9B%BE%E5%83%8F">图像</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=GLSL">GLSL</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/shaderglsl/review-depth-of-field.html#comment" target="_blank">添加评论</a>(0)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html">乱弹纪录IV:Transform Feedback</a> (2012-5-12 15:55:31)  </li><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-geometry-instancing.html">乱弹纪录III:Geometry Instancing</a> (2012-5-6 10:57:23)  </li><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html">乱弹纪录II:Alpha To Coverage</a> (2012-4-14 15:30:17)  </li><li><a href="http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html">OpenGL常用命令备忘录(Part B)</a> (2012-4-4 14:46:59)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html">乱弹纪录I:Geometry Shader</a> (2012-4-2 15:43:51)  </li></ul>]]></description><category>Shader技术</category><comments>http://www.zwqxin.com/archives/shaderglsl/review-depth-of-field.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=103</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=103&amp;key=d2012b0c</trackback:ping></item><item><title>100th Article,160thousand Hits！</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/Way/myblog-100th-160000hits.html</link><pubDate>Thu, 19 Apr 2012 20:58:33 +0800</pubDate><guid>http://www.zwqxin.com/archives/Way/myblog-100th-160000hits.html</guid><description><![CDATA[<p>恰好第16万Hit (<span style="color: rgb(68, 68, 68); font-family: Tahoma, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); display: inline !important; float: none; " class="Apple-style-span">浏览总数:160000</span>)，也恰好是时候发本博客的第100篇文章了。先谢谢一直以来各位对本博客的支持。</p><p><strong>ZwqXin.com的历程（建站至今）</strong></p><p>2009年01月15日 &nbsp; &nbsp; 购买Z-Blog空间，开始筹建本网站(Inspired by 大牛<a target="_blank" href="http://www.Azure.com.cn">Azure</a>的Website)。</p><p>&mdash;&mdash;往后的十天以十分蹩脚的Html知识对z-blog和ipati模板进行修改至大致目前这个模样。</p><p>2009年01月15日&nbsp;&nbsp;&nbsp;&nbsp; 同一天，发表第一篇博客[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/learn-opengl-first.html">学OpenGL的第一步,不应是配置环境</a>]。</p><p>&mdash;&mdash;当时主要利用空间商的三级域名看自己的网站效果，随后购买一元国产域名<a href="http://www.zwqxin.com/">ZwqXin</a>.cn（今已弃用）。</p><p>2009年02月05日&nbsp;&nbsp;&nbsp; 正式购买国外域名<a href="http://www.zwqxin.com/">ZwqXin</a>.com，确定为本博客正式域名。</p><p>&mdash;&mdash;了解国外购买域名方法是费了不少时间，加上当时文章发表得也较频繁，嘛，寒假嘛。</p><p>2009年02月08日&nbsp;&nbsp;&nbsp; 网站相继被Google和Baidu收录。</p><p>&mdash;&mdash;Google的速度是很快的，而且感觉把我的个人com域名网站放得挺前的哈。</p><p>2009年02月23日&nbsp;&nbsp;&nbsp; 网站<a href="http://www.zwqxin.com/">ZwqXin</a>.com通过备案。</p><p>&mdash;&mdash;粤ICP备09007175号。zblog备案客服速度8错。</p><p>2009年09月21日&nbsp;&nbsp; 网站第一次正式被黑，FTP资料被被全数删除。</p><p>&mdash;&mdash;嚣张到他这人渣&hellip;&hellip;还叫我给钱拿回数据。喵的，还好我有不定期备份数据的习惯，最后以损失三天左右的流量、2000多hits、几条文章评论信息的结果恢复正常，改密码好了。</p><p>2009年10月21日&nbsp;&nbsp; 网站被某个来自国外的IP狂踩，PV数一天内狂增三万。</p><p>&mdash;&mdash;其实那IP的活跃时间只持续了几小时，于是我再次恢复网站，就为了擦除掉那3万个不属于我的hits，基本没啥损失。这种情况后来没再遇见过了。</p><p>2012年02月17日&nbsp;&nbsp; 工信部发短信来说已经取消本网站的备案。</p><p>&mdash;&mdash;结果次日开始网站被Ban，域名无法进入。大概是传说中的中小网站大清洗行动终于波及到我了罢，需要按新一套的麻烦的备案流程重新备案了。</p><p>2012年03月29日&nbsp;&nbsp; 工信部发短信来说<a href="http://www.zwqxin.com/">ZwqXin</a>.com的备案已经通过。</p><p>&mdash;&mdash;粤ICP备12017796号，重生。虽然说网站&rdquo;被消失&ldquo;了将近一个半月，但还是要感谢zblog的备案客服的，帮忙克服了一些难关。这段时间，好几个同好在QQ等等地方上问候博客情况，令我颇受感动。因为我原本觉得本网站是没有什么常客的，能够有大家的关注，真幸福口牙~</p><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/Way/myblog-100th-160000hits.html" target="_blank">继续阅读《100th Article,160thousand Hits！》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/Way.html">心途</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=%E8%B7%AF">路</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E5%86%99%E5%8D%9A">写博</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/Way/myblog-100th-160000hits.html#comment" target="_blank">添加评论</a>(0)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/Way/image-processing-design-zwqxin.html">Image Processing 图像处理程序设计</a> (2009-7-13 1:23:57)  </li><li><a href="http://www.zwqxin.com/archives/Way/first-opengl-night-reida.html">一年前,首次献给OpenGL之夜.雷达追踪</a> (2009-4-5 22:18:52)  </li><li><a href="http://www.zwqxin.com/archives/Way/one-year-after-use-opengl.html">遇上OpenGL，一年</a> (2009-3-20 19:58:54)  </li><li><a href="http://www.zwqxin.com/archives/Way/my-first-opengl.html">忆我的第一次OpenGL</a> (2009-1-26 14:15:2)  </li><li><a href="http://www.zwqxin.com/archives/opengl/learn-opengl-first.html">学OpenGL的第一步,不应是配置环境</a> (2009-1-15 15:48:35)  </li></ul>]]></description><category>心途</category><comments>http://www.zwqxin.com/archives/Way/myblog-100th-160000hits.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=102</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=102&amp;key=bd074bf7</trackback:ping></item><item><title>乱弹纪录II:Alpha To Coverage</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html</link><pubDate>Sat, 14 Apr 2012 15:30:17 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html</guid><description><![CDATA[<p>Alpha To Coverage(A2C)是一种经由流水线完成的&ldquo;Alpha Test&rdquo;。在使用了多重采样(Multi-sample)的场合下，经由检测当前需要绘制的fragment的alpha值来决定该fragment在对应像素上的sample覆盖率。应该说，这也算是很有历史感的显卡应用技术了，而本文将重在谈及此技术之前将在流水线level老生重弹一下<em>MultiSample</em>。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>[<a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html">乱弹纪录I:Geometry Shader</a>]</p><p><span style="font-size: smaller;">本文来源于 <strong><#ZC_BLOG_TITLE#></strong> (<em><a href=""http://www.zwqxin.com/"">http://www.zwqxin.com/</a></em>), 转载请注明<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;原文地址：<em><a href=""<#article/url#>""><#article/url#></a></em></span></p><p>好多时候我们都会碰到这样一个问题：渲染Billboard集（譬如草簇、云簇、头发之类）的时候，因为billboard纹理中需要绘制出来只有其中一部分（见下图），图片中的&ldquo;背景&rdquo;部分就要在渲染时抠掉。传统的做法是ALpha-Test（曾经在那OpenGL传统渲染管道下，Alpha-Test跟Depth-Test、Stencil-Test、Scissor-Test一样是状态机管理系统中的&ldquo;耀眼明星&rdquo;，而在可编程渲染管道下，只要在fragment shader里discard掉不需要的fragment就可以了&mdash;&mdash;Alpha-Test处理从Fragment Shader后的阶段往前提送，降格成普通的逐像素处理法），但是正如大家写代码所验证的一样，这种&ldquo;非0即1&rdquo;的强硬手段，让billboard的边缘突显了出来，形成了强烈的非真实感。所以说，这种基于某个alpha阈值的剔除法，只对那些本身具有强边缘的物件有好的视觉效果（譬如二维标签），对于希望&ldquo;把三维的东西用二维的billboard去表达&rdquo;的物件（譬如上述的草、云之类例子）来说，应该要有一种弱化边缘，使边缘逐渐虚化的渲染机制。</p><div class="codeText"><div class="codeHead">glsl代码 (Alpha-Test Fragment Shader)</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span class="keyword">void</span><span>&nbsp;main()&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;<span class="comment">//...</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;texCol&nbsp;=&nbsp;texture2D(basetexSampler,&nbsp;varying_texcoord);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;<span class="keyword">if</span><span>(texCol.a&nbsp;&lt;=&nbsp;0.2)&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;discard;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;<span class="comment">//...</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>}&nbsp;&nbsp;</span></li></ol></div><p>&nbsp;</p><p style="text-align: center;"><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html" target="_self"><img width="448" height="446" src="https://lh3.googleusercontent.com/-m4GXppkxteg/T42HBw1-5kI/AAAAAAAAEBc/D9sDK0UbzVs/s448/ALPHATEST_20120416.jpg" alt="http://www.zwqxin.com" /></a><br /><span style="font-size: smaller;">(Alpha-Test 硬边缘)</span></p><p>&nbsp;Alpha-Blend。根据物件纹理本身的渐变透明度（或颜色本身），使该物件与背景进行一定的混合(GL_SRC_ALPHA之类的因子决定混合参数)，回答了这种机制。在Fragment-Shader之后的流水线阶段，在绘制上当前的渲染目标缓冲区（包括屏幕）时，每个fragment都被进行一次这样的混合处理，这样如果纹理本身带有渐变的透明度，就会在最终的渲染结果上显现出来。注意的是这种机制不是对每个物件并行处理的，对当前DrawCall产生作用时必然会考虑的是当前&ldquo;画板上已经画了些什么&rdquo;&mdash;&mdash;所谓的&ldquo;背景&rdquo;。这样最后的结果就与深度信息无关了，是一种很纯粹的&ldquo;画家算法&rdquo;&mdash;&mdash;混合的结果跟绘制的顺序有关（考虑画草簇，画第二株草体时，即使它位于第一株草体后面，也会与当前画面上的第一株草体混合，所以一画即错）。所以说，对于Alpha-Blend的物件，必须让绘制满足（对于视点）从后往前的顺序执行。</p><div class="codeText"><div class="codeHead">C++代码&nbsp; (OpenGL Alpha-Blend)</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span class="comment">//blended&nbsp;color&nbsp;=&nbsp;source&nbsp;color&nbsp;*&nbsp;source&nbsp;alpha&nbsp;+&nbsp;background&nbsp;color&nbsp;*&nbsp;(1.0&nbsp;-&nbsp;source&nbsp;alpha)</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glEnable(GL_BLEND);&nbsp;&nbsp;</span></li>    <li><span>glBlendFunc(GL_SRC_ALPHA,&nbsp;GL_ONE_MINUS_SRC_ALPHA);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="comment">//...Render</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>glDisable(GL_BLEND);&nbsp; <br />    </span></li></ol></div><p style="text-align: center;"><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html" target="_self"><img width="325" height="546" src="https://lh5.googleusercontent.com/-GUAkLKM3OCU/T42HBugR6uI/AAAAAAAAEBc/EjAIjUYm6S8/s546/ALPHABLEND_SRC_ONE_20120416.jpg" alt="http://www.zwqxin.com" /></a><br /><span style="font-size: smaller;">(Alpha-Blend)</span></p><p style="text-align: center;"><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html" target="_self"><img width="500" height="159" src="https://lh3.googleusercontent.com/-2ba_2TmWgTQ/T42HFJyNV-I/AAAAAAAAEBc/KyzTKwQQ_qQ/s500/two_alphablend_20120416.jpg" alt="http://www.zwqxin.com" /></a><br /><span style="font-size: smaller;">(Alpha-Blend 上图是视觉转到Billboard渲染顺序非从后到前时造成的错乱；下图是同样场景下视觉转到Billboards按视线的后到前排序时)</span></p><p>可以料想到，这样的话就得对草簇的位置在视图空间下排序。如果要旋转漫游，那就必须实时地进行再排序。对于大量的物体，譬如成万的草或云的billboard，这样的排序非常损害FPS。正因此，OIT（order independent transparent）这个概念也就衍生了。为了实现OIT，很多方法逐渐被提出，其中一个就是Alpha To Coverage(A2C)。相比于当时代显卡硬件引入的Per-Pixel Linked Lists技术，Alpha To Coverage自NVidia 8Series时代已经出来了（对于OpenGL来说，这是很早就有的一个功能），也算适合于当前主流显卡。</p><p><strong><em>MultiSample</em></strong></p><p>Alpha To Coverage的实现的一个前提就是场景的渲染缓冲区使用了multi-sample（多重采样，见[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/antialiasing-multi-sample-1.html">全屏反锯齿 - 多重采样Ⅰ</a>]  [<a target="_blank" href="http://www.zwqxin.com/archives/opengl/antialiasing-multi-sample-2.html">全屏反锯齿 - 多重采样Ⅱ</a>] ）。这里的缓冲区包括屏幕，也包括FBO（见[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/multisample-fbo-antialiasing.html">多重采样(MultiSample)下的FBO反锯齿</a>] ）。所以在引入这种机制之前，重温一下流水线中Multi-Sample是如何实现的吧。</p><p><a target="_blank" href="http://en.wikipedia.org/wiki/Multisample_anti-aliasing">多重采样</a>，顾名思义，就是对一个像素产生多个采样值并把它们合成最终输出缓冲区的单一值。这样就包括了两个过程：1.在栅格化(Rasterization)出像素(fragment)前，提高采样率；2.在把所有像素输出到渲染缓冲区前执行Resolve以生成单一像素值。</p><p>举4XMSAA为例。对于第一个过程，你可以认为栅格化时同一个Fragment对图元(Primative)位置点采样了4次，每一次的位置(sample point location)都稍微不同（我一般臆想成在该fragment对应的像素点中心的微偏上下左右四个采样点，类似&ldquo;regular grid&rdquo;的采样pattern，当然实际的采样模式我不想断定哦，毕竟OpenGL把它交给显卡制造商了）。注意，Fragment Shader不是执行4次哦，因为它是针对fragment而不是fragment-sample的，它所有的输入varying变量都是针对其像素中心点（或者应该说fragment-center更好）而言的，所以计算的output color结果始终是针对该栅格化出的像素中心点而言的。</p><p>那么，MultiSample表现在什么地方呢？</p><p>depth和stencil等&mdash;&mdash;它们都是针对fragment-sample的，但是又能对MSAA本质的抗锯齿属性有啥贡献呢？事实上，例如，虽然每个sample都会存储它自身的深度值（<em>一般</em>在fragment shader后写入），但resolve的时候该fragment的输出深度值一般只会取最靠近中心的那个sample的depth值。那何苦每个sample都去取深度值呢？那是因为之后需要每个sample都执行一下depth-test，以确定整个fragment是否要流向（通往缓冲区输出的）流水线下一阶段&mdash;&mdash;只有当全部fragment-sample的Depth-Test都Fail掉的时候，才决定抛弃掉这个fragment（蒙版值stencil也是这样的，每个sample都得进行Stencil-Test，如果有alpha-blend那也同样了）。</p><p>似乎上述跟MSAA的抗锯齿(anti-alias)没太大关系哦。那么，MultiSample还表现在什么地方呢？</p><p>coverage&mdash;&mdash;抱歉本文章的主题在这里才出现（引入），但我觉得上述背景介绍好了，才方便介绍这个概念。coverage（覆盖率）是MultiSample下每个fragment都带有的新属性（当然了，新增的存储变量还包括新增的depth和stencil），它是一个mask（如果还取4XMSAA为例，这个mask的表达形式就是：xxxx，其中x等于0或1），嘛，就是一个二进制的bit mask嘛。显然它的每一位(每一个x)代表的就是一个sample，其值为1代表该sample被（栅格化过程中的图元Primative）覆盖，其值为0代表没被覆盖&mdash;&mdash;事实上，对于没有覆盖的sample上述depth和stencil等都没必要进行测试了。在multisample的第二个过程（resolve）中，只有该coverage mask中指定被覆盖的sample才参与最终的color-output。</p><p>Coverage的mask值是栅格化时就决定了的，它直接影响&quot;像素后处理&quot;阶段（上述的各种test）。那么，我们就有可能在这两个处理阶段之间的可编程阶段&mdash;&mdash;Fragment Shader中去改变它。注意，在当代非最前端的显卡中（至少SM4.0），Fragment Shader没有直接改写coverage值的能力，但是它的确有能力去改变它，或者说，影响它&mdash;&mdash;<strong>Alpha To Coverage</strong>。</p><p>终于憋到这里了啊。Alpha To Coverage，顾名思义，就是这样一个转换：Fragment's Alpha -&gt; Fragment's Coverage mask for multisamples。当然了，即使Fragment Shader不去改变Alpha，这个转换还是会进行的，但是在我的例子中，我们需要一种更具有弹性的方法代替Alpha Test去控制billboard上各个像素的透明度，让它达到类似启用alpha-blend时的（依据物件图像边缘透明度而<strong>透出</strong>背景场景）效果，而这种方式就是Alpha To Coverage，所以需要指定每个fragment的透明度&mdash;&mdash;简单地sample一张纹理足矣，这样billboard每个像素就具有其依据图片（假设此图片是认真制作的^^）的切实的透明度，非物件部分的alpha为0；.物件主体部分alpha为1；主体的边缘部分则是0~1的渐变alpha值了。</p><p>关键部分是Fragment Shader执行之后&mdash;&mdash;Alpha To Coverage就在此时进行转换。一个fragment的Alpha值在0~1间，它对应着一个dither mask。还是以4XMSAA为例，这个dither mask也是xxxx的形式，Alpha为0对应了0000，alpha为1对应了1111，至于中间的值的对应关系，OpenGL是交由显卡制造商决定的&mdash;&mdash;其实一般就是类似[0~0.249 -&gt; 0000, 0.25~0.499 -&gt; 0001, 0.5~0.749 -&gt; 0011, 0.75~0.99-&gt; 0111]这样（在D3D11中，就可以自定义这个dither mask）。恩，dither mask就是用来决定该fragment的samples中，用于最终组成output color的那些samples。具体就是把它与coverage mask作一次逻辑与操作获得新的coverage mask。在本例中，根据上面叙述就理解了，其实除了billboard矩形边缘外，栅格化后其余像素的coverage mask都是1111。billboard中非物件部分（透明度为0）的默认dither mask是0000，逻辑与之后新的coverage mask就是0000了，也就是最终会把该fragment所有sample都丢弃；billboard中物件主体部分dither mask是1111，逻辑与后新的coverage mask还是1111，也就是说最后还是取全部sample去计算输出；billboard中主体边缘部分也直接就是coverage mask = dither mask了，所以最终会根据该新的coverage对应的渐变alpha去选择摒弃掉一些sample了。注意，所有sample的颜色贡献都是那根据fragment-center计算的fragment shader输出值，都是一样的，所以计算output color忽略的具体是哪些sample这毫无所谓。</p><p>那billboard矩形边缘呢？如果是边缘部分本身alpha为0，呵呵那新的Coverage mask不也是0000的结末嘛。如果是上面草体billboard图片的下边缘部位呢？栅格化出来的coverage mask不一定是0000，假设是1101吧，再假设alpha为0.8，dither mask取0111吧，逻辑与后的结果是0101，取两个sample去参与最终颜色的输出计算，比前面两个mask都要少。所以说这时它既兼顾了alpha也兼顾了边缘属性（见下述）咯？</p><p>再提一句，coverage的作用还包括在Depth-Test之类的&ldquo;后像素处理&rdquo;中，因为只有coverage mask为1的sample才会参与这些处理。</p><p>也该是时候谈到一直说的&ldquo;计算output color&rdquo;是怎么一回事了。MultiSample的Resolve阶段，如果是屏幕输出的话这个阶段会发生在设备的屏幕输出直前；如果是FBO输出，则是发生在把这个Multisample-FBO映射到非multisample的FBO（或屏幕）的时候（见[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/multisample-fbo-antialiasing.html">多重采样(MultiSample)下的FBO反锯齿</a>] ）。Resolve，说白了就是把MultiSample的存储区域的数据，根据一定法则映射到可以用于显示的Buffer上了（这里的输出缓冲区包括了Color、Depth或还有Stencil这几个）。Depth和Stencil前面已经提及了法则了，Color方面其实也简单，一般的显卡的默认处理就是把sample的color取平均了。注意，因为depth-test等Test以及Coverage mask的影响下，有些sample是不参与计算的（被摒弃），例如4XMSAA下上面的0101，就只有两个sample，又已知各sample都对应的只是同一个颜色值，所以output color = 2 * fragment color / 4 = 0.5 * fragment color。也就是是说该fragemnt最终显示到屏幕（或Non-MS-FBO）上是fragment shader计算出的color值的一半&mdash;&mdash;这不仅是颜色亮度减半还包括真&middot;透明度值的减半。</p><p>单纯针对Coverage mask的影响而言，现在可以再来看看怎样理解&ldquo;兼顾了alpha也兼顾了边缘属性&rdquo;。这个coverage mask包括了栅格化出的coverage mask（姑且叫last coverage mask）和dither mask两部分。前者反映着该Fragment的边缘属性，如果没有启用Alpha to Coverage（也就是说不用考虑dither mask），这就造成output color = 0.75 * fragment color，该Fragment因其25%的部分位于&ldquo;边缘外&rdquo;（假设采样完全正确）而导致25%的虚化&mdash;&mdash;这就是MSAA的真意，边缘虚化实现抗锯齿。那么现在考虑上Alpha To Coverage，该像素本身0.8的alpha值导致其进一步的虚化。对于上述&ldquo;除了billboard矩形边缘外其余像素&rdquo;的讨论更可知道：Alpha To Coverage依托于MSAA，但它也能作用于场景中的非边缘部分！</p><p>于是，Alpha To Coverage的实现理论就暂告一段落了。说到使用，代码上看真很简单&mdash;&mdash;只要enable了GL_SAMPLE_ALPHA_TO_COVERAGE就OK了。当然了，在此之前必须确认当前的渲染缓冲区启用了MultiSample（  [<a target="_blank" href="http://www.zwqxin.com/archives/opengl/antialiasing-multi-sample-2.html">全屏反锯齿 - 多重采样Ⅱ</a>] / [<a target="_blank" href="http://www.zwqxin.com/archives/opengl/multisample-fbo-antialiasing.html">多重采样(MultiSample)下的FBO反锯齿</a>] ）。另外，似乎一般情况下MultiSample的环境下会自动启用GL_MULTISAMPLE（如果没有就手动启用吧：glEnable(GL_MULTISAMPLE)），它确保MultiSample在流水线中执行，如果被Disable了的话MultiSample Buffer中的各Fragment的Coverage总会是&ldquo;全覆盖&rdquo;(1111...)、各sample也只会写入相同的值&mdash;&mdash;相当于给了那么大的多重采样存储区却不执行多重采样。</p><div class="codeText"><div class="codeHead">C++代码 (OpenGL ALpha-To-Coverage)</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span>glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="comment">//Render</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);&nbsp;&nbsp;</span></li></ol></div><p style="text-align: center;"><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html" target="_self"><img width="451" height="468" src="https://lh5.googleusercontent.com/-OWV2XLKe1L4/T42HCsURZEI/AAAAAAAAEBc/NR2nYVDITOw/s468/a2c_4XMSAA_20120416.jpg" alt="http://www.zwqxin.com" /></a><br /><span style="font-size: smaller;">(Alpha-To-Coverage 4xMSAA)</span></p><p>虽然相比Alpha-Test好很多了，但显然比不上Alpha-Blend的效果。从图中可以看到很明显的颗粒感，首先很容易想到是精度不足所致的。因为我们现在（4xMSAA）是把Alpha值为0~0.249范围内（仅为举例）的Fragment统一当做是0 Coverage（完全不覆盖），把0.25~0.499内的统一当作只覆盖一个sample&hellip;&hellip;这样就相当于把整个渐变的Alpha值范围硬性地划分成4个区域了（更准确来说，A2C本质是针对每个sample做Alpha-Test），从而精度很低。那么解决办法就是提高多重采样的sample数&mdash;&mdash;譬如使用16xMSAA，渲染结果如下图，明显好很多了吧？（但是谁会单纯为了这个而去承受16xMSAA带来的巨大FPS损耗哦~）</p><p style="text-align: center;"><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html" target="_self"><img width="427" height="333" src="https://lh6.googleusercontent.com/-9g_KDVGfRsg/T42HBUuHOdI/AAAAAAAAEBc/8sTU9k8AqOs/s427/A2C_16XMSAA__20120416.jpg" alt="http://www.zwqxin.com" /></a><br /><span style="font-size: smaller;">(Alpha-To-Coverage 16xMSAA)</span></p><p>其实仔细看上图还是觉得有线粒感，这是因为还有一个更重要因素&mdash;&mdash;Dither mask太统一而导致的。譬如50%Alpha时只有0011的mask而没有0101、1001之类的mask，这样每个50%Alpha的Fragment舍弃的sample可能都是相同的相对位置上的sample。虽说采样的方式是交由显卡制造商决定的，且某些显卡下Fragment在不同位置的采样pattern或许都会变化，但邻近的Fragment肯定更倾向于相同的pattern，譬如第一个sample总是fragment-center的偏上位置，第二个sample总是在偏右位置&hellip;&hellip;这样的结果是，0011的coverage mask总只保留这两个位置的sample而摒弃掉偏左和偏下位置的sample&mdash;&mdash;线粒感于是形成了。</p><p>在D3D11中有办法在Shader中为每一个Fragment指定sample mask，这样各种随机各种分布的机制就可以引入了。但OpenGL4.x中虽然也有sampleMask输出（也就是说输出Coverage mask），但对于输入（栅格化出来的coverage mask）暂时无法获得（根据spec，可能晚点会加入）？所以说暂无法自定义这个dither pattern。其实OpenGL也有其他方法去更改这个Coverage mask（当然还是要注意首先确定开启了multisample）：</p><div class="codeText"><div class="codeHead">C++代码 (OpenGL SampleMask)</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span>glEnable(GL_SAMPLE_MASK);&nbsp;&nbsp;</span></span></li>    <li><span><span class="comment">//glSampleCoverage(0.9f,&nbsp;GL_TRUE);</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>glSampleMaski(0,&nbsp;0x0E);&nbsp;<span class="comment">//&nbsp;4xMSAA:x00-0x0F;&nbsp;8xMSAA:0x00-0xFF;&nbsp;16xMSAA:0x0000-0xFFFF</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="comment">//...Render</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glDisable(GL_SAMPLE_MASK);&nbsp;&nbsp;</span></li></ol></div><p>这里开启了<span><span>GL_SAMPLE_MASK，并通过</span></span><span>glSampleMaski去定义这个mask（其实就是Coverage，4xMSAA下就是0000~1111，16xMSAA下就是0000000000000000~1111111111111111了，按上述16进制方式传入参数即可）。其实应该</span><span><span class="comment">glSampleCoverage也可以的（第一个参数范围0.0~1.0，像A2C一样映射到Coverage），但我没成功让它起作用的说~~</span></span></p><p>&nbsp;</p><p style="text-align: center;"><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html" target="_self"><img width="448" height="391" src="https://lh4.googleusercontent.com/-s1hPYcrDzEU/T42HDutQOrI/AAAAAAAAEBc/gQmPe_e-Uks/s448/glSampleMaski_4x_20120416.jpg" alt="http://www.zwqxin.com" /></a><br /><span style="font-size: smaller;">(SampleMask: 0x0E&nbsp; 4xMSAA)</span></p><p style="text-align: center;"><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html" target="_self"><img width="458" height="331" src="https://lh3.googleusercontent.com/-92zy_cc2K3o/T42HC_Nih3I/AAAAAAAAEBc/TAS0bBG3zm4/s458/glSampleMaski_16x_20120416.jpg" alt="http://www.zwqxin.com" /></a><br /><span style="font-size: smaller;">(SampleMask: 0xAFFF&nbsp; 16xMSAA)</span></p><p><span><span class="comment">这种方式的结果如上图。嘛，就是对于渲染的东西作为一个全局影响的Coverage了，结果简直就是类似于全局调整Alpha值嘛，坑爹啊？！</span></span></p><p><span><span class="comment">最后的比较：</span></span></p><p style="text-align: center;"><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html" target="_self"><img width="433" height="512" src="https://lh5.googleusercontent.com/-ylN9KYLxZaU/T42HFbT-QuI/AAAAAAAAEBc/smazce03QBA/s512/oit_default_20120416.jpg" alt="http://www.zwqxin.com" /></a></p><p>恩本文到此结束。随着图形学技术的发展，OIT的重要性越来越突显出来了，从最初的Alpha-Tset，到Alpha To Coverage，到其他领域的depth-peeling、ABuffer、Per-Pixel-Linked-List &hellip;&hellip;图形学的世界是越来越宽广了。</p><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html" target="_blank">继续阅读《乱弹纪录II:Alpha To Coverage》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/opengl.html">OpenGL技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=OpenGL">OpenGL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E7%AC%94%E8%AE%B0">笔记</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E5%A4%9A%E9%87%8D%E9%87%87%E6%A0%B7">多重采样</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html#comment" target="_blank">添加评论</a>(0)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html">乱弹纪录IV:Transform Feedback</a> (2012-5-12 15:55:31)  </li><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-geometry-instancing.html">乱弹纪录III:Geometry Instancing</a> (2012-5-6 10:57:23)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-depth-of-field.html">shader复习与深入:Depth of Field(景深)</a> (2012-4-29 15:5:43)  </li><li><a href="http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html">OpenGL常用命令备忘录(Part B)</a> (2012-4-4 14:46:59)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html">乱弹纪录I:Geometry Shader</a> (2012-4-2 15:43:51)  </li></ul>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=101</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=101&amp;key=48e1f854</trackback:ping></item><item><title>OpenGL常用命令备忘录(Part B)</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html</link><pubDate>Wed, 04 Apr 2012 14:46:59 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html</guid><description><![CDATA[<p><span style="color: rgb(68, 68, 68); font-family: Tahoma, sans-serif; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 19px; orphans: 2; text-align: left; text-indent: 26px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); display: inline !important; float: none; " class="Apple-style-span">给一些容易忘记的opengl命令做做备忘录吧~想这么说的时候，突然想起貌似好久好久以前也在博客上说过类似的话&hellip;&hellip;于是便记得有这么个小坑，坑得不成样子了。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</span></p><p><span style="color: rgb(68, 68, 68); font-family: Tahoma,sans-serif; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 19px; orphans: 2; text-align: left; text-indent: 26px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; background-color: rgb(255, 255, 255); display: inline ! important; float: none;" class="Apple-style-span">Part.A见此文：</span>[<a href="http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-1.html" target="_blank">OpenGL常用命令备忘录(Part A)</a>]</p><p>可以稍微给个规则，那就是此系列相关的的API都会是自己觉得有一定&ldquo;历史沉淀&rdquo;的，但又可能会时常有机会用到。<strong>备忘</strong>为主。</p><p><span style="font-size: smaller;">本文来源于 <strong><#ZC_BLOG_TITLE#></strong> (<em><a href=""http://www.zwqxin.com/"">http://www.zwqxin.com/</a></em>), 转载请注明<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;原文地址：<em><a href=""<#article/url#>""><#article/url#></a></em></span></p><p><span style="font-size: small;"><strong>3.glPixelStore</strong></span></p><p><span style="color: rgb(68, 68, 68); font-family: Tahoma,sans-serif; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 19px; orphans: 2; text-align: left; text-indent: 26px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; background-color: rgb(255, 255, 255); display: inline ! important; float: none;" class="Apple-style-span">像</span>glPixelStorei(GL_PACK_ALIGNMENT, 1)这样的调用，通常会用于像素传输(PACK/UNPACK)的场合。尤其是导入纹理(glTexImage2D)的时候：</p><div class="codeText"><div class="codeHead">C++代码</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span>glPixelStorei(GL_UNPACK_ALIGNMENT,&nbsp;1);&nbsp;&nbsp;</span></span></li>    <li class="alt">&nbsp;</li>    <li class="alt">glTexImage2D(,,,, &amp;pixelData);</li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glPixelStorei(GL_UNPACK_ALIGNMENT,&nbsp;4);&nbsp; <br />    </span></li></ol></div><p>很明显地，它是在改变某个状态量，然后再Restore回来。&mdash;&mdash;为什么是状态？你难道8知道OpenGL就是以状态机不？&mdash;&mdash;什么状态？其实名字已经很直白了，glPixelStore这组函数要改变的是像素的存储格式。</p><p>涉及到像素在CPU和GPU上的传输，那就有个<strong>存储格式</strong>的概念。在本地内存中端像素集合是什么格式？传输到GPU时又是什么格式？格式会是一样么？在glTexImage2D这个函数中，包含两个关于颜色格式的参数，一个是纹理（GPU端，也可以说server端）的，一个是像素数据（程序内存上，也就是client端）的，两者是不一定一样的，哪怕一样也无法代表GPU会像内存那样去存储。或者想象一下，从一张硬盘上的图片提取到内存的像素数据，上传给GPU成为一张纹理，这个&ldquo;纹理&rdquo;还会是原来的那种RGBARGBA的一个序列完事么？显然不是的。作为一张纹理，有其纹理ID、WRAP模式、插值模式，指定maipmap时还会有一串各个Level下的map，等等。就纹理的数据来说，本质纹理是边长要满足2的n次方（power of two）的数据集合，这样首先大小上就有可能不一样，另外排列方式也未必就是RGBA的形式。在OpenGL的&ldquo;解释&rdquo;中，纹理就是一个&ldquo;可以被采样的复杂的数据集合&rdquo;，无论外面世界千变万化，GPU只认纹理作为自己&ldquo;图像数据结构&rdquo;，这体现着&ldquo;规范化&rdquo;这条世界纽带的伟大之处。</p><p>姑且把GPU里面的像素存储格式看做一个未知数，把该存储空间内那批像素看做<em>一堆X</em>。不要深究<em>一堆X</em>究竟是什么样子的，嘛，反正就想象成一堆软绵绵的，或者模糊不清的，打满马赛克的，(哔&mdash;&mdash;)的一样的东西就可以了。与此相比，内存中的像素数据实在太规则规范了！可能源文件各种图片格式，什么bmp、jpg、png甚至dds，但只要你按该格式的算法结构来提取（类似[<a target="_blank" href="http://www.zwqxin.com/archives/image-processing/bmp-operate-print-pixel.html">Bmp文件的结构与基本操作(逐像素印屏版)</a>] ），总可以提取出一列整齐的RGBARGBA（或者RGBRGB什么的，反正很整齐就行了管他呢）的数据堆出来，是可以在程序中实测的东西。</p><p>涉及到像素在CPU和GPU上的传输，那就有个<strong>传输方向</strong>的概念。那就是大家耳濡目染的PACK和UNPACK。嘛，装载和卸载也可以，打包和解压也可以，随你怎么译了。结合上述存储格式的概念：</p><ul>    <li>PACK &mdash;&mdash; 把像素从<em>一堆X</em>的状态转变到规则的状态（把一堆泥土装载进一个花盆，把散散的货物装上货柜，或者把一堆各样的文件打包成一个rar压缩包，等等）；</li>    <li>UNPACK &mdash;&mdash; 把像素从规则的状态转变到<em>一堆X</em>的状态（把花盆里的泥倒出来，把货柜中的货物卸载到盐田港，或者解压压缩包，等等）。</li></ul><p>我认为这两个概念还是很容易混淆的，所以形象化一点总好点嘛。从本地内存向GPU的传输（UNPACK），包括各种glTexImage、glDrawPixel；从GPU到本地内存的传输（PACK），包括glGetTexImage、glReadPixel等。也正因如此，PBO也有PACK和UNPACK模式的区别。</p><p>好像说了好多不相关的事情。嘛，适当也当做延伸。回头来真正看一下glPixelStore吧。它的第一个参数，譬如ALIGNMENT、ROW_LENGTH、IMAGE_HEIGHT等等，都有PACK和UNPACK的两种版本，所以对应的也是上述关于PACH和UNPACK的两类函数。所以对于glTexImage2D，才使用<span><span>GL_UNPACK_ALIGNMENT的版本。</span></span><span><span>但要说明的是，无论是哪种传输方式，它都是针对本地内存端（client端）上的像素数据的。</span></span><span><span>在上述例子中，它起着补充</span></span>glTexImage2D中关于传输起点&mdash;&mdash;本地像素集合的<span><span>格式，的作用。</span></span></p><p>一般来说，这些本地的数据集合，只要知道其起始位置、大小(width*height)和颜色格式（譬如GL_RGBA等等）、值格式(GL_UNSIGNED_CHAR、GL_FLOAT等等)，就能准确地传输。而这些都是需要向glTexImage2D函数（或者上述的其他传输型函数）提供的。但是，这里头也一些细节，其实是需要glPixelStore这个函数来进行设置的。</p><p><em>3.1 <span><span>GL_UNPACK_ALIGNMENT / </span></span><span><span>GL_PACK_ALIGNMENT</span></span></em></p><p>通常，提取一张图像的时候，我们怎么知道一行的数据量呢？这个一行的数据量应该是：width * sizeof(Pixel) ，应对最一般RGBA、各通道各占一个字节的像素结构，width * sizeof(Pixel) = width * 4 * sizeof(byte)，是4的整数倍。但是也有时候，我们的像素数据中一行的数据量不一定是4的整数倍（譬如一张RGB的图像、宽度150、各通道各占一个字节的像素结构，一行的数据量就是450个字节）。</p><p>另一方面，跟编译器一样，GPU传输时也喜欢4字节对齐，也即是说喜欢对像素数据按4字节存取。所以它更偏向于喜欢每一行的数据量是4的整数倍（按上述，这恰好是比较常见的）。所以为了更高的存取效率，OpenGL默认让像素数据按4字节4字节的方式传输向GPU&mdash;&mdash;但是问题在于，对于行非4字节对齐的像素数据，第一行的最后一次存取的4字节将部分包括第一行的数据部分包括第二行的数据，当然致命的不是在这里，而是在最后一行：存取将很可能会越界。为了防止这样的情况，一是硬性把像素数据延展成4字节对齐的（就像BMP文件的存储方式一样，[<a target="_blank" href="http://www.zwqxin.com/archives/image-processing/bmp-operate-print-pixel.html">Bmp文件的结构与基本操作(逐像素印屏版)</a>] ）；二是选择绝对会造成4字节对齐的颜色格式或值格式（GL_RGBA啦，或者GL_INT、GL_FLOAT之类）；三是以牺牲一些存取效率为代价，去更改OpenGL的字节对齐方式&mdash;&mdash;这就是glPixelStore结合<span><span>GL_UNPACK_ALIGNMENT / </span></span><span><span>GL_PACK_ALIGNMENT。</span></span></p><div class="codeText"><div class="codeHead">C++代码</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span>glPixelStorei(GL_UNPACK_ALIGNMENT,&nbsp;1);&nbsp;&nbsp;&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>glTexImage2D(,,,,&nbsp;&amp;pixelData);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>glPixelStorei(GL_UNPACK_ALIGNMENT,&nbsp;4);&nbsp;&nbsp;&nbsp;&nbsp;</span></li></ol></div><p>再次看回这段代码，这时候就明白了：让字节对齐从默认的4字节对齐改成1字节对齐（选择1的话，无论图片本身是怎样都是绝对不会出问题的，嘛，以效率的牺牲为代价），UNPACK像素数据，再把字节对齐方式设置回默认的4字节对齐。至于哪种方式更适合，就看你依据硬件环境限制、麻烦程度等，去选择了。</p><p><em>3.2 GL_UNPACK_ROW_LENGTH/&nbsp;GL_PACK_ROW_LENGTH 和 </em><br /><em>GL_UNPACK_SKIP_ROWS / GL_PACK_SKIP_ROWS 、 GL_UNPACK_SKIP_PIXELS/GL_PACK_SKIP_PIXELS</em></p><p>有的时候，我们把一些小图片拼凑进一张大图片内，这样使用大图片生成的纹理，一来可以使多个原本使用不同的图片作为纹理的同质物件如今能够在同一个Batch内，节省了一些状态切换的开销，二来也容易综合地降低了显存中纹理的总大小。但是，也有些时候，我们需要从原本一张大的图片中，截取图片当中的某一部分作为纹理。要能够做到这样，可以通过预先对图片进行裁剪或者在获得像素数据后，把其中需要的那一部分另外存储到一个Buffer内再交给<span>glTexImage2D之类的函数。而上述这些参数下</span><span>glPixelStore的使用将帮助我们更好地完成这个目的：</span></p><div class="codeText"><div class="codeHead">C++代码</div><ol class="dp-cpp" start="1">    <li><span>&nbsp; //原图中需要单独提取出来制成纹理的区域</span></li>    <li>RECT subRect = {{100, 80}, {500, 400}}; //origin.x, origin.y, size.width, size.height</li>    <li>&nbsp;</li>    <li>//假设原图的宽度为BaseWidth, 高度为BaseHeight</li>    <li>&nbsp;</li>    <li class="alt"><span><span>glPixelStorei(GL_UNPACK_ROW_LENGTH,&nbsp; </span></span>BaseWidth<span><span>);&nbsp;&nbsp;&nbsp; </span></span>//指定像素数据中原图的宽度</li>    <li class="alt"><span>glPixelStorei(GL_UNPACK_SKIP_ROWS,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>subRect. origin.y.<span>); //指定纹理起点偏离原点的高度值<br />    </span></li>    <li><span>glPixelStorei(GL_UNPACK_SKIP_PIXELS,&nbsp;&nbsp;&nbsp;&nbsp; </span>subRect. origin.x<span>);&nbsp; //</span><span>指定纹理起点偏离原点的宽度值</span></li>    <li>&nbsp;</li>    <li class="alt"><span>glTexImage2D(..., </span>subRect.size.width, ubRect.size.height,..<span> &amp;pixelData);&nbsp; </span> //使用区域的宽高</li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>glPixelStorei(GL_UNPACK_ROW_LENGTH,&nbsp;0);&nbsp;&nbsp;</span></li>    <li class="alt"><span>glPixelStorei(</span><span>GL_UNPACK_SKIP_ROWS</span><span>,&nbsp;0);&nbsp;&nbsp;</span></li>    <li><span>glPixelStorei(</span><span>GL_UNPACK_SKIP_PIXELS</span><span>,&nbsp;0);&nbsp;&nbsp;</span></li></ol></div><p>这段代码本身，即使没有注释也很清楚了。注意的是<span><span>GL_UNPACK_ROW_LENGTH的必要性，因为为了确认区域起点的Offset，就需要把</span></span><span>线性数据pixelData上标记起点的</span><span><span>&ldquo;游标&rdquo;从0移动到</span></span><strong><span><span>OffsetToData = </span></span>subRect. origin.y * BaseWidth<span><span> + </span></span>subRect. origin.x</strong>的位置。有了区域纹理原点的在原图数据的位置，以及区域的尺寸，<span>glTexImage2D就可以确定区域纹理生成所需要的信息了。通过</span><span><span>glPixelStore的使用，避免了新建Buffer和自己处理图像数据的开销和麻烦了。</span></span></p><p><span><span>说到这里，到底为什么要这样做来提取区域纹理呢？尤其是原图若其他部分都是程序所需要的，那是不是就可以直接通过纹理坐标去切割更好呢？我想到的是一种情况（也可以说我是因为这种情况才注意到</span></span><span><span>glPixelStore的这种用法</span></span><span><span>）：如果这块区域纹理需要作重复铺设(wrap mode选择GL_REPEAT)呢？这时候纹理坐标的方法就没用了，因为REPEAT所依据的也是纹理坐标（使用纹理坐标的小数部分进行采样）。这时候就需要上述做法了。（事实上3DSMAX等软件纹理导入的类似区域纹理平铺的功能就能如此实现。）</span></span></p><p><strong><span><span>4.glScissor</span></span></strong></p><p><span><span>我想这个函数也应该很常见才对。裁剪测试啊，当年跟Alpha测试、Depth测试、Stencil测试可以并列哦，而今更是不掉时髦值啊。因为我实在很难想象在Shader里能容易地实现它的功能：裁剪。当然这只是矩形裁剪，但是对于discard掉渲染中不需要的像素真是颇简单粗暴。我使用它最多的是一些二维图片缩略图栏&mdash;&mdash;</span></span>有时候我们只需要把这些缩略图的显示限制在一个区域里，但又要支持滑动。</p><div class="codeText"><div class="codeHead">C++代码 （OpenGL）</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glEnable(GL_SCISSOR_TEST);&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glScissor(GLint(m_rtThumbRegion.x),&nbsp;GLint(m_rtThumbRegion.y),&nbsp;GLint(m_rtThumbRegion.width),&nbsp;GLint(m_rtThumbRegion.height));&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="comment">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //.....</span><span>&nbsp; Render</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; glDisable(GL_SCISSOR_TEST);&nbsp;&nbsp;</span></li></ol></div><p>其中，除了启用<span><span>GL_SCISSOR_TEST</span></span>外，<span>只要给glScissor指出需要保留显示的区域就可以了。在此区域外的像素依然会被渲染（不会怎么省流水线操作，所以也别指望它附带什么提高效率之类的功能），在下图中，其实左右两侧还是继续渲染其他的图片（或者说，其实这个缩略图栏横跨整个屏幕），但是就在fragment shader之后，它们会被检测到不在该区域内而被discard掉罢。<br /></span></p><p style="text-align: left;"><a target="_blank" href="http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html"><img width="453" height="59" alt="OpenGL常用命令备忘录(Part B)" src="https://lh6.googleusercontent.com/-u25rMf17M3c/T6U5DPKc68I/AAAAAAAAEGI/qZCCFTxEPAY/s590/5%2529%255B%255D5DC7P6Ge%25400F%257ED%257B39%257BP%255B8.jpg" /></a></p><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html" target="_blank">继续阅读《OpenGL常用命令备忘录(Part B)》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/opengl.html">OpenGL技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=OpenGL">OpenGL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E5%88%9D%E5%AD%A6%E8%80%85">初学者</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E7%AC%94%E8%AE%B0">笔记</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html#comment" target="_blank">添加评论</a>(0)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html">乱弹纪录IV:Transform Feedback</a> (2012-5-12 15:55:31)  </li><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-geometry-instancing.html">乱弹纪录III:Geometry Instancing</a> (2012-5-6 10:57:23)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-depth-of-field.html">shader复习与深入:Depth of Field(景深)</a> (2012-4-29 15:5:43)  </li><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html">乱弹纪录II:Alpha To Coverage</a> (2012-4-14 15:30:17)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html">乱弹纪录I:Geometry Shader</a> (2012-4-2 15:43:51)  </li></ul>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=100</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=100&amp;key=8d0339bf</trackback:ping></item><item><title>乱弹纪录I:Geometry Shader</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html</link><pubDate>Mon, 02 Apr 2012 15:43:51 +0800</pubDate><guid>http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html</guid><description><![CDATA[<p>Geometry Shader（几何元着色器）是继Vertex Shader和Fragment Shader之后，由Shader Model 4（第四代显卡着色架构）正式引入的第三个着色器。在OpenGL3.x中也成为核心，使图形程序开发者在可编程渲染管道（programable render pipline）下能够更大的发挥自由度。由本文开始的一系列乱弹中，Geometry Shader作为基础并重要的一环，现在权且是&ldquo;首当其冲&rdquo;吧。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p><span style="font-size: smaller;">本文来源于 <strong><#ZC_BLOG_TITLE#></strong> (<em><a href=""http://www.zwqxin.com/"">http://www.zwqxin.com/</a></em>), 转载请注明<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;原文地址：<em><a href=""<#article/url#>""><#article/url#></a></em></span></p><p>Shader Model 4（SM4）在Nvidia 8Series显卡时代已经出现，标志着可编程渲染管道的真正崛起（Shader不再被当作传统管道的&ldquo;高级扩展可选项&rdquo;而是其本身取代了传统管线中对应的处理阶段）。API阵营中，Direct3D10率先应运而生，OpenGL虽然在那个过渡期被一堆倾向守旧CAD类行业软件头头纠缠，也终于以其折衷兼容的方式，进入Shader核心的OpenGL 3.x时代。SM4.0带来的其中一个礼物，就是Geometry Shader，在当时可是被寄予众望（&ldquo;Geometry Shader&rdquo;的概念虽然提出得更早一些）。在SM4.0渲染管道中，Geometry Shader位于Vertex Shader与Fragment Shader之间：</p><p style="text-align: center;"><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html"><img alt="http://www.zwqxin.com" src="https://lh4.googleusercontent.com/-c7yJ47cEOIE/T3smEgIa6dI/AAAAAAAAD9U/x5xNcCtMyHs/s512/vggggg2012040201.jpg" /></a></p><p>OpenGL中，引起渲染pipline发生的是各种Draw函数，通过它们传入的，除了一些顶点信息，还有就是诸如GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_POINTS之类决定顶点的组织形式的信息（当然OpenGl还会需要结合当前的glPolygonMode决定真正的组织形式）。首先是与之关联的VBO对象中的顶点数据根据各种顶点相关的信息传入管道（见[<a href="http://www.zwqxin.com/archives/opengl/learn-vbo.html" target="_blank">学一学，VBO</a>] 、[<a href="http://www.zwqxin.com/archives/opengl/vao-and-vbo-stuff.html" target="_blank">AB是一家?VAO与VBO</a>] 等博客文章），在Vertex Shader处理的就是这些顶点；Fragment Shader处理的是Rasterization栅格化后的像素；而进入栅格化的是图元（Primative），这些图元怎么由Vertex Shader处理后的顶点演化出来？这中间有个Assembly的过程，根据的就是上面提及的GL_TRIANGLES这些信息，把顶点组合成图元（Primative）：</p><div class="codeText"><div class="codeHead">数据结构：</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span class="comment">//顶点（典型的Vertex&nbsp;Shader输出结构）：</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>Vertex&nbsp;&nbsp;</span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;vec4&nbsp;&nbsp;gl_Position;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">float</span><span>&nbsp;gl_PointSize;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">float</span><span>&nbsp;gl_ClipDistance[];&nbsp;&nbsp;</span></span></li>    <li><span>};&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="comment">//所组成的图元：</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>PrimitivePoint&nbsp; (根据GL_PONTS组织)</span></li>    <li class="alt"><span>{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;Vertex[1];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;...&nbsp;&nbsp;</span></li>    <li><span>};&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>PrimitiveLine&nbsp; </span><span>(根据</span>GL_LINES或GL_LINE_STRIP或GL_LINE_LOOP<span>组织)</span></li>    <li class="alt"><span>{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;Vertex[2];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;...&nbsp;&nbsp;</span></li>    <li><span>};&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>PrimitiveTriangle&nbsp; </span><span>(根据</span>GL_TRIANGLES或GL_TRIANGLE_STRIP或GL_TRIANGLE_FAN<span>组织)</span></li>    <li class="alt"><span>{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;Vertex[3];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;...&nbsp;&nbsp;</span></li>    <li><span>};&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>... Adjacency line/ Adjacency triangle等等 <br />    </span></li></ol></div><p>另外还有一些特殊情况组成的图元，一种就是在视锥体裁剪阶段（栅格化之前），边界处新生成的顶点造成的图元分割，这个不作讨论范畴；点精灵（PointSprite）根据一个顶点和其大小(pointSize)生成一个矩形（其实是两个tiangle），等等。</p><p>从简地说，上述的<span>PrimitivePoint 、</span><span>PrimitiveTriangle这些就是图元（Primative，也有称作片元的），从数据的结构上说，就是一个或数个顶点的集合而已。在Geometry Shader里，我们处理的单元就是这些</span><span>Primative。虽然根本上都是顶点的处理，但进入vertex shader里的是一次一个的顶点，而进入Geometry Shader的是一次一批的顶点，Geometry Shader掌握着这些顶点所组成的图元的信息。</span><span>Geometry Shader的处理阶段处于流水线的栅格化之前，也在视锥体裁剪和裁剪空间坐标归一化之前。虽说裁剪过程会剔除部分图元也会分割某些图元， 但就目前来说，不会有其他流水线的可编程阶段会在Geometry Shader之后提供出影响图元的性质（</span><span>形式和数量</span><span>）&mdash;&mdash;这是Geometry Shader鉴于其位置的特殊性而拥有的一个重要特点。</span></p><p><span>好吧。这些啰嗦的背景介绍就到这里，对刚接触Geometry Shader的同学希望有点帮助。在提一点，就是即使是Opengl 3.x的core profile（强制必须使用可编程渲染管道），Geometry Shader也不是必须的，而是可选项。这也是很多很多时候其实我们只需要Vertex Shader和Fragment Shader的原因。（当然了，符合某些条件的话，</span><span>Vertex Shader</span><span>或</span><span>Fragment Shader</span><span>也不是必须的，譬如transorm feedback下不需要栅格化和输出像素时，就不需要</span><span>Fragment Shader</span><span>；通过invocation可以只给个空心的</span><span>Vertex Shader意思一下</span><span>而在</span><span>Geometry Shader直接生成图元等等</span><span>。）正如上文所述，图元是在</span><span>Geometry Shader之前已经生成的了，</span><span>Geometry Shader的功用首先在于它能对进入的图元的</span><span>组织形式和数量有直接影响。</span></p><p>&nbsp;先看一对简单的Vertex Shader和Fragment Shader，Vertex Shader把进来的顶点Vertex转换到裁剪空间（[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/opengl-matrix-what.html">乱弹OpenGL中的矩阵变换(上)</a>]  [<a target="_blank" href="http://www.zwqxin.com/archives/opengl/opengl-matrix-what-2.html">乱弹OpenGL中的矩阵变换(下)</a>] ），直接传给流水线下一阶段，经内部Assembly生成图元Primative后，再一直到Rasterization栅格化出像素Fragment，Fragment Shader只是单纯采样一个纹理并把像素输出：</p><div class="codeText"><div class="codeHead">glsl代码:</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span class="comment">//Base.vert</span><span>&nbsp;&nbsp;</span></span></li>    <li><span><span class="preprocessor">#version&nbsp;330</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>uniform&nbsp;mat4&nbsp;matModel;&nbsp;&nbsp;</span></li>    <li class="alt"><span>uniform&nbsp;mat4&nbsp;matView;&nbsp;&nbsp;</span></li>    <li><span>uniform&nbsp;mat4&nbsp;matProj;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>layout(location&nbsp;=&nbsp;0)&nbsp;in&nbsp;vec3&nbsp;attrib_position;&nbsp;&nbsp;</span></li>    <li class="alt"><span>layout(location&nbsp;=&nbsp;1)&nbsp;in&nbsp;vec2&nbsp;attrib_texcoord;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>out&nbsp;vec2&nbsp;varying_vf_texcoord;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>)&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;varying_vf_texcoord&nbsp;=&nbsp;attrib_texcoord;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;gl_Position&nbsp;=&nbsp;matProj&nbsp;*&nbsp;matView&nbsp;*&nbsp;matModel&nbsp;*&nbsp;vec4(attrib_position,&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="comment">//Base.frag</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span><span class="preprocessor">#version&nbsp;330</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>uniform&nbsp;sampler2D&nbsp;&nbsp;basetex;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>in&nbsp;vec2&nbsp;varying_vf_texcoord;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>layout(location&nbsp;=&nbsp;0)&nbsp;out&nbsp;vec4&nbsp;fragColor;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>)&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;fragColor&nbsp;=&nbsp;texture(basetex,&nbsp;varying_vf_texcoord);&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>}&nbsp; <br />    </span></li></ol></div><p>正如上述，一旦OpenGL应用指定ShaderProgram使用Geometry Shader，则上面的叙述变成：在Assembly生成图元Primative后，再一直到<strong>栅格化前进行Geometry Shader的处理</strong>，再到Rasterization栅格化。我们也用一个简单的Geometry Shader，单纯把进入的图元原原本本地（组织形式和数量也一致）输出：</p><div class="codeText"><div class="codeHead">glsl代码</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span class="comment">//Base.vert</span><span>&nbsp;&nbsp;</span></span></li>    <li><span><span class="preprocessor">#version&nbsp;330</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>uniform&nbsp;mat4&nbsp;matModel;&nbsp;&nbsp;</span></li>    <li class="alt"><span>uniform&nbsp;mat4&nbsp;matView;&nbsp;&nbsp;</span></li>    <li><span>uniform&nbsp;mat4&nbsp;matProj;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>layout(location&nbsp;=&nbsp;0)&nbsp;in&nbsp;vec3&nbsp;attrib_position;&nbsp;&nbsp;</span></li>    <li class="alt"><span>layout(location&nbsp;=&nbsp;1)&nbsp;in&nbsp;vec2&nbsp;attrib_texcoord;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>out&nbsp;vec2&nbsp;<strong>varying_vg_texcoord</strong>;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>)&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;<strong>varying_vg_texcoord</strong>&nbsp;=&nbsp;attrib_texcoord;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;gl_Position&nbsp;=&nbsp;matProj&nbsp;*&nbsp;matView&nbsp;*&nbsp;matModel&nbsp;*&nbsp;vec4(attrib_position,&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="comment">//Base.geom</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span><span class="preprocessor">#version&nbsp;330</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span style="color: rgb(255, 0, 0);"><strong>layout(triangles)&nbsp;in;&nbsp;&nbsp;</strong></span></li>    <li><span style="color: rgb(255, 0, 0);"><strong>layout(triangle_strip,&nbsp;max_vertices&nbsp;=&nbsp;3)&nbsp;out;&nbsp;&nbsp;</strong></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>in&nbsp;&nbsp;vec2&nbsp;varying_vg_texcoord[];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>out&nbsp;vec2&nbsp;varying_gf_texcoord;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>)&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;<span class="keyword">for</span><span>(</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;gl_in.length();&nbsp;++i)&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gl_Position&nbsp;=&nbsp;gl_in[i].gl_Position;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;varying_gf_texcoord&nbsp;=&nbsp;varying_vg_texcoord[i];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EmitVertex();&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;EndPrimitive();&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="comment">//Base.frag</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span><span class="preprocessor">#version&nbsp;330</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>uniform&nbsp;sampler2D&nbsp;&nbsp;basetex;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>in&nbsp;vec2&nbsp;<strong>varying_gf_texcoord</strong>;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>layout(location&nbsp;=&nbsp;0)&nbsp;out&nbsp;vec4&nbsp;fragColor;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>)&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;fragColor&nbsp;=&nbsp;texture(basetex,&nbsp;<strong>varying_gf_texcoord</strong>);&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>}&nbsp; <br />    </span></li></ol></div><p>对于Vertex Shader和Fragment Shader来说，也就加粗黑体字部分有变化&mdash;&mdash;Varying变量的名字。为啥呢？因为Vertex Shader的varying变量作为输出，是要先进入Geometry Shader并通过它作为输出去给到Fragment Shader的。所以在Geometry Shader内同时有一个输入的varying和输出的varying，无法同名。</p><p>在这个简单的Geometry Shader中，红色粗体字部分的layout声明了进入的图元的组织形式(triangle，注意这个需要跟Opengl应用上对应的draw函数一致，PolygonMode无用)、数量为1(作为Geometry Shader的处理单元，每次处理的输入当然是一个Primative啦)；输出的图元的组织形式(同样是triangle)、数量为1（因为最大的输出顶点数是3，也就是最多只会输出一个triangle了）。作为输出的关键字，只有points、line_strip、triangle_strip三种，下面再述。</p><p>作为输入的图元，也就是上述的<span>PrimitivePoint 、</span><span>PrimitiveTriangle这些，所包括的Vertex结构用<em>gl_in[]</em>来表示：</span></p><div class="codeText"><div class="codeHead">glsl代码</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span>in&nbsp;gl_PerVertex&nbsp;{&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;vec4&nbsp;&nbsp;gl_Position;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">float</span><span>&nbsp;gl_PointSize;&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">float</span><span>&nbsp;gl_ClipDistance[];&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>}&nbsp;gl_in[];&nbsp;&nbsp;&nbsp;&nbsp; <br />    </span></li>    <li class="alt"><span>// 注意，除了这个顶点数组外，作为输入的图元结构还包括</span><span><span>gl_PrimitiveID等等</span></span></li></ol></div><p>没错，这个数组的个数（<span><span>gl_in.length()</span></span>）就是由输入的图元的组织形式决定的（譬如points的话个数就是1，triangles的话个数就是3）。作为Varying输入的<span>varying_vg_texcoord[]也是同样的道理。接下来的事情就很一目了然了：</span><em><span>EmitVertex()</span></em><span>这个函数相当于输出一个点到一张画布上，</span><em><span>EndPrimitive()</span></em><span>这个函数的调用将画布上</span><span>当前</span><span>的点组织成一个图元</span><span>（在这里是triangle_strip）</span><span>向外输出，并清空画布上当前的点。</span></p><p><span><em>看上去是</em>调用</span><em><span>EndPrimitive()</span></em><span>多少次就将输出多少个图元。这样调用</span><em><span>EndPrimitive()</span></em><span>的瞬间就有这样几种情况：<br /></span></p><ol>    <li>如果当前画布上的点不足够组成一个图元（譬如当前画布上只有两个点，而组成<span>triangle_strip至少要有3个点</span>）？&mdash;&mdash;这时候将不输出任何图元，但画布还是要清空的；</li>    <li>刚好能组成一个三角形图元（画布上刚好有3个点）&mdash;&mdash;这样没问题</li>    <li>如果当前画布上的点多于3个呢？&mdash;&mdash;注意，在输出的layout中max_vertices=3，也就是说，整个Geometry Shader执行的期间会统计你当前一共通过<em><span>EndPrimitive()</span></em>输出了多少个点，如果超过了max_vertices这个值，接下来的<em><span>EmitVertex()</span></em><span>就不会把点输出到画布上了，如果当前画布上点不足3个，这样</span><em><span>后续EndPrimitive()</span></em>的调用就跟第一种情况一样了；但现在既然多于3个，那就只会取前3个点去输出一个三角形。注意统计的不是你向画布上的输出的点，而是<em>E</em><em><span>ndPrimitive()</span></em>成功调用所发出去的点。</li></ol><p>&nbsp;那么假如layout中设的是max_vertices=4呢？再次考虑上面三种情况：</p><ol>    <li>情况一样；</li>    <li>情况也是一样的，因为3&lt;max_vertices,这时候输出的也是一个三角形图元；</li>    <li>如果当前画布上的点多于3个呢？&mdash;&mdash;如果是刚好4个，那么按照<span>triangle_strip的特性，将输出两个三角形图元；多于4个的话，那也是一样的，</span><span>输出两个三角形图元。</span></li></ol><p>所以当你编写Geometry Shader时，一定要时刻惦记自己设定的输出layout。如果无法事先确定该shader最终会输出多少个图元时，max_vertices就要设成所能预想的最大顶点数值。这个我是吃过不少亏的，因为shader写好经常忘记去对应layout的值。</p><p>&nbsp;另外，现在你已经知道输出只有points、line_strip、triangle_strip三种的意义了，因为其他形式的图元都可以用这三种输出，只要对应当前画布上点的数量就是了。说了那么久，画布是啥，点又是怎么定义的？</p><p>画布当然是GPU寄存器里的一个缓存区域了啊，一个提供临时线性存储Buffer（是吗？各位硬件牛大大 - -）。然后一个作为输出的点有如下构造：</p><div class="codeText"><div class="codeHead">C++代码</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span>out&nbsp;gl_PerVertex&nbsp;{&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;vec4&nbsp;&nbsp;gl_Position;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">float</span><span>&nbsp;gl_PointSize;&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">float</span><span>&nbsp;gl_ClipDistance[];&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>};&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>out&nbsp;<span class="datatypes">int</span><span>&nbsp;gl_PrimitiveID;&nbsp;&nbsp;</span></span></li>    <li><span>out&nbsp;<span class="datatypes">int</span><span>&nbsp;gl_Layer;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>....&nbsp;&nbsp;</span></li>    <li><span>out </span><span>XXX;</span><span>....<span class="comment">//其他varying输出变量，譬如上面的</span></span><span>varying_gf_texcoord</span><span><span>&nbsp;&nbsp;</span></span></li></ol></div><p>我们要往&ldquo;画布&rdquo;上画一个点，其实就是在单个<em><span>EmitVertex()</span></em>之前填充上面的构造就是了（如果你之前不清楚，那么告诉你凡以gl_开头的都是内置的变量，作为自己定义的变量名不要带有这个前缀）。在上面的Base.geom中，填充就是<span>代表顶点坐标的gl_Position（这个是必须的你懂的）和自定义的输出varying：</span><span>varying_gf_texcoord。其他值都会有其默认值，其中</span><span><span>gl_PrimitiveID是当前图元的ID，这东西在输入的内置变量中也有一个，在某些地方还是很有用的，譬如基于颜色的GPU执行拾取时，就可以把它直接赋予原来输入变量中的值，输出给Fragment Shader以用它去确认当前像素属于哪个图元；</span></span><span><span>gl_Layer主要用于Layered-Rendering，针对此下文会再述。注意，这里每个变量被填充后会一直保持该值到再被赋值或者&ldquo;画布&rdquo;的清除。</span></span><span><span>gl_PrimitiveID、</span></span><span><span>gl_Layer是针对图元的，所以一个图元对于它们最终采样怎样的值，取决于图元上其中一个顶点（</span></span>provoking vertex<span><span>）。</span></span></p><p><span><span>对Geometry Shader的应用，一个很经典很常用的，就是Billboard。以前渲染Billboard（譬如一张alpha纹理标识的树），为了不产生视觉怪像</span></span>，往往要（至少水平方向上）计算视线向量跟该Billboard-Quad平面向量的夹角，并实时地使用该夹角去反旋转billboard至与视线向量垂直。这都是在CPU上执行的。现在有了Geometry Shader，这种计算很方便了：</p><div class="codeText"><div class="codeHead">glsl代码 （billboard.geom）</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span class="preprocessor">#version&nbsp;330</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>layout(points)&nbsp;in;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//输入的只是一个点</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>layout(triangle_strip,&nbsp;max_vertices&nbsp;=&nbsp;4)&nbsp;out;&nbsp;<span class="comment">//输出的是一个Quad（两个triangle）</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>uniform&nbsp;<span class="datatypes">float</span><span>&nbsp;grassScale;&nbsp;&nbsp;</span><span class="comment">//Billboard的大小，也可以分别输入宽度高度</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>uniform&nbsp;vec3&nbsp;&nbsp;eyePosition;&nbsp;<span class="comment">//世界坐标系下的视点坐标</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>uniform&nbsp;mat4&nbsp;matModel;&nbsp;&nbsp;</span></li>    <li><span>uniform&nbsp;mat4&nbsp;matView;&nbsp;&nbsp;</span></li>    <li class="alt"><span>uniform&nbsp;mat4&nbsp;matProj;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>out&nbsp;vec2&nbsp;varying_texcoord;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>)&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;<span class="datatypes">float</span><span>&nbsp;fScale&nbsp;=&nbsp;grassScale&nbsp;/&nbsp;2.0;&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;mat4&nbsp;matPV =&nbsp;&nbsp;matProj&nbsp;*&nbsp;matView;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;position[4];&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;<span class="comment">//因为输入的是一个点（gl_in.length()&nbsp;=&nbsp;1），这里的for循环其实只执行一遍</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;<span class="keyword">for</span><span>(</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;gl_in.length();&nbsp;++i)&nbsp;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;inPos&nbsp;&nbsp;=&nbsp;gl_in[i].gl_Position.xyz;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//该输入点在世界坐标系下的坐标</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vec4&nbsp;posInWorld&nbsp;=&nbsp;matModel&nbsp;*&nbsp;gl_in[i].gl_Position;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;vDistFromCam&nbsp;=&nbsp;eyePosition&nbsp;-&nbsp;posInWorld.xyz;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//获得视线向量</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;cameraVec&nbsp;=&nbsp;normalize(vDistFromCam);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//Billboard平面的水平向量&nbsp;</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;vRight&nbsp;=&nbsp;cross(vec3(0.0,&nbsp;1.0,&nbsp;0.0),&nbsp;cameraVec);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//Billboard平面的数值向量&nbsp;</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vec3&nbsp;vUp&nbsp;=&nbsp;cross(cameraVec,&nbsp;vViewRight);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">//计算billboard四个角点的坐标</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; position[0]&nbsp;=&nbsp;matPV *&nbsp;vec4(inPos&nbsp;-&nbsp;fScale&nbsp;*&nbsp;vRight,&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; position[1]&nbsp;=&nbsp;matPV *&nbsp;vec4(inPos&nbsp;+&nbsp;fScale&nbsp;*&nbsp;vRight,&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; position[2]&nbsp;=&nbsp;matPV *&nbsp;vec4(inPos&nbsp;-&nbsp;fScale&nbsp;*&nbsp;vRight&nbsp;+&nbsp;fScale&nbsp;*&nbsp;2&nbsp;*&nbsp;vUp,&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; position[3]&nbsp;=&nbsp;matPV *&nbsp;vec4(inPos&nbsp;+&nbsp;fScale&nbsp;*&nbsp;vRight&nbsp;+&nbsp;fScale&nbsp;*&nbsp;2&nbsp;*&nbsp;vUp,&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">for</span><span>(</span><span class="datatypes">int</span><span>&nbsp;j&nbsp;=&nbsp;0;&nbsp;j&nbsp;&lt;&nbsp;4;&nbsp;++j)&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl_Position&nbsp;=&nbsp;position[j];&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; varying_texcoord&nbsp;=&nbsp;vec2(j&nbsp;%&nbsp;2,&nbsp;j&nbsp;/&nbsp;2);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EmitVertex();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EndPrimitive();&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li></ol></div><p>这就是通过一个点去生成一个广告牌矩形，用ponts去populate出billboards&mdash;&mdash;在OpenGL中只需要glDrawArray(GL_POINTS,...)，指定每个billboard底边中点的位置作为顶点传入即可（当然也可以是矩形中心，上面计算角点坐标时自己改一下就是了，注意无论哪种都好，这个点本身是不需要也一起输出的）。注意的是populated的角点坐标要与传入的视点坐标在同一个坐标系下，这样计算出来的Billboard向量次啊会正确。这里考虑到进入的点本来就统一定义在世界坐标系下所以就直接在世界坐标系下计算了。为了控制计算的坐标系，矩阵运算直接从Vertex Shader移到这里计算了（Vertex Shader只要直接输出所输入的顶点就可以了）。</p><p style="text-align: center;"><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html"><img style="width: 463px; height: 284px;" alt="http://www.zwqxin.com" src="https://lh5.googleusercontent.com/-Qd94A77OsN8/T3smEjj__UI/AAAAAAAAD9c/0MM7gf8x4T8/s812/asserry2012040202.jpg" /></a></p><p>利用Geometry Shader去populate矩形，在很多时候都会用到。再提一个熟悉的，就是粒子系统的粒子渲染。首先，我8知道过了那么久到了现在OpenGL也早已迈入4.0时代，PointSprite点精灵的方式去渲染小粒子的方式，比起直接用Quad来渲染还会优到何地步（上面也提过，它是到Assembly时生成两个triangle图元的），但我觉得还是让我们有更好的控制感比较好。粒子通过跟上述shader类似的方式，即可以由points生成矩形（鉴于粒子的特性，也不必再计算什么视觉向量来精确求出角点了，直接随便根据点的坐标向任意方向拓展一个矩形即可）。生成的时机在流水线上后延了，流水带宽也会降低点。当然了，如果纯性能上考虑，点精灵那种依赖PointSize的方式，跟需要另外起一个Geometry Shader带来的性能损耗，也8知道孰高孰低了。是的，Geometry Shader的插入会一定程度影响性能，容后再说。</p><p>再来说一下基于Geometry Shader的一个很独特的应用点：Layered-Rendering。</p><p>在以前玩Cascaded Shadow Map[<a href="http://www.zwqxin.com/archives/opengl/link-fbo-and-texture-array.html" target="_blank">联结FBO与Texture Array</a>] 时，想把几个层级的深度图渲染到一个纹理数组[<a href="http://www.zwqxin.com/archives/opengl/learn-texture-array.html" target="_blank">学一学, Texture Array纹理数组</a>]上，需要使用<font color="#000000">glFramebufferTextureLayer给每一层都关联一次FBO和TextureArray的layer，然后渲染一次场景。也就是说，要想分几层，就要预先给渲染几个Pass了（虽然说颜色输出禁用和FBO纹理格式的控制可以减低很多消耗，那也很耗了）。当时也想有没方法一个Pass解决呢？当时也许还没真正现形，但Geometry Shader提供了这么一个可能，也就是Layered-Rendering。简洁的说，它一个重要功能就是可以在Geometry Shader阶段</font><font color="#000000">把图元固定导向一个固定的渲染目标的某一特定层。再简洁点，就是可以在Geometry Shader中选择把图元输出到哪一个layer&mdash;&mdash;只要当前是渲染到FBO的一个具有层属性的渲染目标。符合的渲染目标，典型的就是TextureArray，还有CubeMap（</font>[<a href="http://www.zwqxin.com/archives/shaderglsl/review-cube-mapping-shader.html" target="_blank">Shader快速复习：Cube Mapping(立方环境贴图)</a>] <font color="#000000">，它本身就是6层的纹理结构）。<br /></font></p><p>&nbsp;以把场景渲染到CubeMap为例。譬如场景中间有个球，需要把除了球本身外的整个场景实时地渲染到这个球体上，使它像一个镜面球一样反射出360度的整个场景。传统做法是把摄像机Camera放置在球体中心，分别使之朝向上下左右前后6个正交的方向，分别渲染一次场景进FBO里（渲染对象是二维纹理），得到的6张纹理再组合成一张CubeMap，以法线采样的方式贴到球面上（至于为什么采用CubeMap方式进行球面贴图，见此博客文章：[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/sphere-mapping-cubemap-stuff.html">球体贴图小谈</a>] ）。<font color="#000000">Layered-Rendering的方式，则直接以一张CubeMap为FBO的渲染目标：</font></p><div class="codeText"><div class="codeHead">C++代码 （OpenGL Setup 生成FBO，渲染目标为GL_TEXTURE_CUBE_MAP）</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span>glBindFramebuffer(GL_FRAMEBUFFER,&nbsp;m_nHandle);&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glBindTexture(GL_TEXTURE_CUBE_MAP,&nbsp;renderTarget.nHandle);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glTexParameteri(nTargetType,&nbsp;GL_TEXTURE_WRAP_S,&nbsp;GL_CLAMP_TO_EDGE);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glTexParameteri(nTargetType,&nbsp;GL_TEXTURE_WRAP_T,&nbsp;GL_CLAMP_TO_EDGE);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glTexParameteri(nTargetType,&nbsp;GL_TEXTURE_WRAP_R,&nbsp;GL_CLAMP_TO_EDGE);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span><span>(</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;CUBE_FACE_COUNT;&nbsp;++i)&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X&nbsp;+&nbsp;i,&nbsp;0,&nbsp;nInternalFormat,&nbsp;m_nWidth,&nbsp;m_nHeight,&nbsp;0,&nbsp;nPixelDataFormat,&nbsp;nPixelDataType,&nbsp;NULL);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glFramebufferTexture(GL_FRAMEBUFFER,&nbsp;nAttachBuffer,&nbsp;renderTarget.nHandle,&nbsp;0);&nbsp;&nbsp;</span></li></ol></div><p>这里有一点是特特别别需要注意的，就是这里指定的<strong><span>m_nWidth和m_nHeight必须相等</span></strong><span>。也就是说，CubeMap每个面的纹理必须严格是正方形（其实很直观的：不是正方形能正常拼出盒子么&hellip;&hellip;不过我刚开始就是被这细节弄郁闷了不少时间）。</span><span>glFramebufferTexture是一个普适各种渲染目标的函数（没有后缀的版本），要支持</span><font color="#000000">Layered-Rendering也就靠它了。我们在正式渲染场景前，先把场景渲染进这个FBO（</font>[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/learn-fbo.html">学一学，FBO</a>] <font color="#000000">），控制所有场景渲染的S</font>h<font color="#000000">ader跟正常渲染时想比：</font></p><div class="codeText"><div class="codeHead">glsl代码&nbsp; layeredRenderCubeMap.geom</div><ol class="dp-cpp" start="1">    <li class="alt"><span><span class="keyword">if</span><span>(nRenderToCube&nbsp;&gt;&nbsp;0)&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//&nbsp;紧记此Shader输出layout的max_vertices需要是原来的6倍</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;<strong>&nbsp;<span class="keyword">for</span><span>(</span><span class="datatypes">int</span><span>&nbsp;k&nbsp;=&nbsp;0;&nbsp;k&nbsp;&lt;&nbsp;6;&nbsp;++k)&nbsp;&nbsp;</span></strong></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;matPVM&nbsp;=&nbsp;&nbsp;matProj&nbsp;*&nbsp;<strong>matCubeViewArray[k]</strong>&nbsp;*&nbsp;matModel;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><strong><span>gl_Layer&nbsp;=&nbsp;k;&nbsp;&nbsp;</span></strong></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;position[0]&nbsp;=&nbsp;matPVM&nbsp;*&nbsp;vec4(inPos&nbsp;+&nbsp;vec3(-fScale,&nbsp;0.0,&nbsp;0.0),&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;position[1]&nbsp;=&nbsp;matPVM&nbsp;*&nbsp;vec4(inPos&nbsp;+&nbsp;vec3(&nbsp;fScale,&nbsp;0.0,&nbsp;0.0),&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;position[2]&nbsp;=&nbsp;matPVM&nbsp;*&nbsp;vec4(inPos&nbsp;+&nbsp;vec3(-fScale,&nbsp;fScale&nbsp;*&nbsp;2.0,&nbsp;0.0)&nbsp;+&nbsp;vDeform,&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;position[3]&nbsp;=&nbsp;matPVM&nbsp;*&nbsp;vec4(inPos&nbsp;+&nbsp;vec3(&nbsp;fScale,&nbsp;fScale&nbsp;*&nbsp;2.0,&nbsp;0.0)&nbsp;+&nbsp;vDeform,&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span><span>(</span><span class="datatypes">int</span><span>&nbsp;j&nbsp;=&nbsp;0;&nbsp;j&nbsp;&lt;&nbsp;4;&nbsp;++j)&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gl_Position&nbsp;=&nbsp;position[j];&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;varying_texcoord&nbsp;=&nbsp;vec2(j&nbsp;%&nbsp;2,&nbsp;j&nbsp;/&nbsp;2);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EmitVertex();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EndPrimitive();&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="keyword">else</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;matPVM&nbsp;=&nbsp;&nbsp;matProj&nbsp;*&nbsp;matView&nbsp;*&nbsp;matModel;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;position[0]&nbsp;=&nbsp;matPVM&nbsp;*&nbsp;vec4(inPos&nbsp;+&nbsp;vec3(-fScale,&nbsp;0.0,&nbsp;0.0),&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;position[1]&nbsp;=&nbsp;matPVM&nbsp;*&nbsp;vec4(inPos&nbsp;+&nbsp;vec3(&nbsp;fScale,&nbsp;0.0,&nbsp;0.0),&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;position[2]&nbsp;=&nbsp;matPVM&nbsp;*&nbsp;vec4(inPos&nbsp;+&nbsp;vec3(-fScale,&nbsp;fScale&nbsp;*&nbsp;2.0,&nbsp;0.0)&nbsp;+&nbsp;vDeform,&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;position[3]&nbsp;=&nbsp;matPVM&nbsp;*&nbsp;vec4(inPos&nbsp;+&nbsp;vec3(&nbsp;fScale,&nbsp;fScale&nbsp;*&nbsp;2.0,&nbsp;0.0)&nbsp;+&nbsp;vDeform,&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span><span>(</span><span class="datatypes">int</span><span>&nbsp;j&nbsp;=&nbsp;0;&nbsp;j&nbsp;&lt;&nbsp;4;&nbsp;++j)&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gl_Position&nbsp;=&nbsp;position[j];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;varying_texcoord&nbsp;=&nbsp;vec2(j&nbsp;%&nbsp;2,&nbsp;j&nbsp;/&nbsp;2);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EmitVertex();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;EndPrimitive();&nbsp;&nbsp;</span></li>    <li class="alt" value="59">&nbsp;</li>    <li><span>}&nbsp;&nbsp;</span></li></ol></div><p>正如你想，两者的不同之处，是<font color="#000000">Layered-Rendering需要把图元输出6遍，每一遍选择不同的ViewMatrix（也就是Camera位于球心并朝向6个正交方向时所生成的视图变换矩阵，由OpenGL</font><font color="#000000">应用传入），还有就是使当前需要输出的图元所有顶点的gl_Layer设成当前渲染目标的层（0~5）。在CubeMap中，第0层就是</font><span>GL_TEXTURE_CUBE_MAP_POSITIVE_X所代表的纹理层，第1层是GL_TEXTURE_CUBE_MAP_NEGATIVE_X，如此类推。包括视图变换矩阵，都需要按此顺序。</span></p><p><span>在渲染到FBO后，再按正常方式渲染场景和那个球体。渲染球体的时候使用CubeMap贴图，用FBO输出的那张CubeMap就OK了。动态CubeMap在譬如汽车倒后镜、水面Cube反射（</span>[<a href="http://www.zwqxin.com/archives/opengl/water-simulation-2.html" target="_blank">水效果Ⅱ - 涟漪</a>] <span>）之类的场合还是很常用的。</span></p><p style="text-align: center;"><a target="_self" href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html"><img height="512" width="387" alt="http://www.zwqxin.com" src="https://lh5.googleusercontent.com/-9NurLSDuYfo/T47kXUpJc6I/AAAAAAAAECY/c3w1D45-b2c/s512/geomtalk_20120404_2.jpg" /></a></p><p>结果看起来很好。但是，它却使得帧率一下子下降了许多。根据大牛的详细测试（<a target="_blank" href="http://www.klayge.org/2011/07/26/%E4%B8%8D%E4%BA%89%E6%B0%94%E7%9A%84geometry-shader/">《不争气的GS》</a>），这甚至比6-pass的做法还要低效。为什么呢？是因为Geometry Shader！？这样，最后的议题终于要被提上论程了：使用Geometry Shader的性能代价。</p><p>Geometry Shader从概念的提出到进入SM4.0，是被赋予厚望的：希望它能产生更丰富的图元更丰富的视觉效果。或许应该这么说，是希望它实现出像如今SM5.0中Tessellation（细分曲面）技术的效果。但是实际上它的发展速度跟不上人们的期望。加上SM5.0的提出，如今感觉GPU架构的发展已经不再在Geometry Shader上下工夫了。也就是说如今它几乎被定型，也就是主要作为粒子系统、billboard、culling等的实现场所。说到底，为什么输出layout中需要有<span><span class="comment">max_vertices这个指定值呢？因为如果不显式指定的话，就没法针对输出的Buffer作出优化。这隐含这么一个意思：图元被扩展得越厉害，性能下降得也越厉害。</span></span></p><p><span><span class="comment">为什么</span></span>Geometry Shader变成了性能杀手呢？一个很显然的理由，也大概是我们了解它后的第一感觉：它是不是损害了流水线的并行性了？我们知道，无论是Vertex Shader处理顶点，还是Fragment Shader处理像素，都是由GPU的并行执行单元保障其高度并行性的，各顶点间的处理没有干扰和交集，像素几乎也是。那么图元呢？在这里，图元只是顶点的集合，甚至可以说Geometry Shader本身也是处理顶点的，但图元处理的并行性变相使得这些图元内的顶点丧失并行性（尤其是非points的输入组织形式）&mdash;&mdash;它们在Geometry Shader内都是同时可见的。另一方面，为了保障Geometry Shader执行的并行性，必然需要启用不少的存储单元（还有就是之前提及的&ldquo;画布&rdquo;），这对性能肯定也会造成一定压力（或者说，图元这种有序生成的东西本身就不太适用并行处理）。当然了，应该还有不少非臆想的硬件实现方面的障碍，共同导致了Geometry Shader如今这种尴尬地位。所以或者可以这样总结：能够不用Geometry Shader的场合就不要用，要用的时候就让输出顶点数尽量少、执行的内容尽量简单。</p><p>我们看它的主要应用：对于粒子和billboard，由于作为输入的是point，这样并行性能更好的保证，输出的通常也就4到8个顶点左右，对显卡的性能优化比较适合，所以在这方面Geometry Shader是比较适合的；对于instance-culling（以后文章会提及），由于它是&ldquo;减少&rdquo;而不是&ldquo;增加&rdquo;，所以性能负担也不会太重；对于<font color="#000000">Layered-Rendering，尽管看上去很棒，但就目前显卡水平来说，这个的效率很有缺陷；</font>而对于其他比较有名的应用场合，诸如Fur-Rendering，就不太熟悉了，还有以前做Shadow Volume（[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/shadow-volume-6.html">Shadow Volume 阴影锥技术之探Ⅵ</a>] ）时或许都提及过的，用Geometry Shader代替degenerated guad（退化矩形）创建并拉伸出Volume这些，由于也没试过，就不大好说了。</p><p>最后结束本文并自我提醒一句：请合理使用Geometry Shader。</p><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html" target="_blank">继续阅读《乱弹纪录I:Geometry Shader》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/shaderglsl.html">Shader技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=shader">shader</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E7%AC%94%E8%AE%B0">笔记</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=GLSL">GLSL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=OpenGL">OpenGL</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html#comment" target="_blank">添加评论</a>(0)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html">乱弹纪录IV:Transform Feedback</a> (2012-5-12 15:55:31)  </li><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-geometry-instancing.html">乱弹纪录III:Geometry Instancing</a> (2012-5-6 10:57:23)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-depth-of-field.html">shader复习与深入:Depth of Field(景深)</a> (2012-4-29 15:5:43)  </li><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html">乱弹纪录II:Alpha To Coverage</a> (2012-4-14 15:30:17)  </li><li><a href="http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html">OpenGL常用命令备忘录(Part B)</a> (2012-4-4 14:46:59)  </li></ul>]]></description><category>Shader技术</category><comments>http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=99</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=99&amp;key=ca1998bc</trackback:ping></item><item><title>shader复习与深入:HDR(高动态范围)</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/shaderglsl/review-high-dynamic-range.html</link><pubDate>Thu, 26 Jan 2012 14:50:43 +0800</pubDate><guid>http://www.zwqxin.com/archives/shaderglsl/review-high-dynamic-range.html</guid><description><![CDATA[<p>HDR(high dynamic range)应该算是很常见的图形后处理手法了。通俗点，就是让场景中光亮的部分更加光亮，暗的地方更加暗，在计算机的常态亮度范围（0-255）上模拟高光效果。如果这个高光还带点眩晕效果，那就颇绚颇&ldquo;HDR&rdquo;了。这些本想在09年末来写的，奈何不觉又穿越了那么久了。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p><span style="font-size: smaller;">本文来源于 <strong><#ZC_BLOG_TITLE#></strong> (<em><a href=""http://www.zwqxin.com/"">http://www.zwqxin.com/</a></em>), 转载请注明<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;原文地址：<em><a href=""<#article/url#>""><#article/url#></a></em></span></p><p>某个拍照的时候，通过快门光圈造成的过度曝光，可能会让照片更加有魅力；隧道出口处的光芒总是那么涣散人心。如果要在实时场景中实现这些效果，就要考虑两件事：怎么突出画面的明暗对比度，以及怎么伪造出那种光线给视觉造成的似隐若幻的模糊效果。我们常说的画面HDR其实（至少）是这两种效果的混合，前者是HDR的本意，映射高光的范围（最大亮度值大于255）到（0-255），同时做到不让人觉得太线性太均衡&mdash;&mdash;所谓的tone-mapping；后者是HDR的&ldquo;辅助技能&rdquo;，其实也就是远在HDR之前就为人熟悉的Bloom&mdash;&mdash;通过模糊画面造成柔化的效果。</p><p style="text-align: center;"><a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-high-dynamic-range.html"><img alt="shader复习与深入:HDR(高动态光照)" src="https://lh3.googleusercontent.com/-qKeioWixlaw/TyEdoTG2FuI/AAAAAAAAD54/fd6_-w87Rcg/s360/Snaphdr20120126dr.jpg" /></a></p><p>在当今图形学上，这是一种post-processing(后处理)。我们没必要去与光照打交道，而只不过是对每一个渲染帧做一些加工工作。跟shadow-map[<a href="../../../archives/opengl/shadow-map-1.html" target="_blank">Shadow Map阴影贴图技术之探Ⅰ</a>] 类似，我们给原帧叠加上一层&ldquo;HDR高光眩晕层&rdquo;，而问题在于这个&ldquo;高光眩晕层&rdquo;怎么得出来。按上所述，这要通过两个主要步骤，按其先后顺序：bloom和tone-mapping。当然也可以先进行高光映射再模糊映射后的画面，但很多时候我们想模糊的只是那产生高光的部分，又为了让结果更加自然，所以先处理（提取）画面亮度信息，模糊，把tone-shading放在处理链的最后。</p><p>另外说一下，通常视觉的变化会导致场景中某一部分高光的突出。场景HDR导致该点过度曝光，譬如上图对比中，原图正上方位置的不明物体被高光&ldquo;遮盖&rdquo;了，这是因为视觉从上往下有一定偏角。而如果从别的角度看，譬如直视，高光就不会在该点太过集中，这样细节就出来了。这跟我们日常的情况相似：</p><p style="text-align: center;"><a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-high-dynamic-range.html"><img alt="shader复习与深入:HDR(高动态光照)" src="https://lh3.googleusercontent.com/-aKgSdIs-1j8/TyFcaQMk9mI/AAAAAAAAD6I/Gpk1rADSmsw/s400/Snap2hdr20110126s.jpg" /></a></p><p>1.亮度信息</p><p>我们这里说的亮度可以直接用灰度来表达[<a href="../../../archives/image-processing/image-intensity-binarization.html" target="_blank">基于亮度的图像二值化处理</a>] 。这种转换是比较简单的，也适合shader来做：</p><p style="text-align: center;">IntenSity = 0.2990 * R + 0.5870 * G&nbsp; + 0.1140 * B</p><p>我们待处理的原始帧图像的亮度必然是（0-255），在shader中表现为（0-1）。我们要映射出伪高光（亮的部分更加光亮，暗的地方更加暗），首先要知道的就是当前帧所有可见像素的一个亮度均衡值&mdash;&mdash;简单点，就取平均亮度吧。怎么取平均亮度？当然是把总有亮度值加起来再除以总数啦。呵呵，先不说要预先另外准备一个pass，这计算量可不是盖的，尤其是大屏幕&hellip;&hellip;于是我们可以投机一下，把原帧图像不断下采样（down-sample），以降低&ldquo;分辨率&rdquo;，甚至到最后采样到一个像素，直接取其亮度为平均亮度？这跟纹理的mapmap很类似嘛。理论上是这样的。但是呢，首先，汲取前人的失败经验，我们不要依赖mipmap了，也不要直接把帧图像纹理映射到小矩形上，好好地进行正规下采样处理&mdash;&mdash;box-filtering（⑨领域滤波[<a href="../../../archives/image-processing/image-process-spatial-domain-filter.html" target="_blank">图像处理里的空间域滤波</a>] ）吧。鉴于在shader中box-filtering的代价（新建pass啊多次纹理采样啊亲）也颇大的，所以建议不要做得太绝了（什么下采样到一个像素的）。</p><p>最开始的pass，把原帧渲染进一个纹理FBO（注意，作为输入的渲染帧的颜色值没必要规范化，也就是说允许计算的亮度值大于1，为了不让OpenGL实现自动把RGBA值Clamp到0-1区间，所以最好使用浮点纹理）；第二个pass，给一个四分之一于原帧大小的屏幕矩形，shader里下采样一次，结果存入另一个FBO；第三个，再一次四分之一你懂的&hellip;&hellip;这样你觉得&ldquo;足够&rdquo;了之后，就把当前FBO中的数据（4N分之一于原帧图像的下采样样本）取出来，变成亮度后加合平均一下啦（你问怎么取得数据，这个可随便啦。我一般是预先给该FBO中的纹理数据分配一个PBO去跟踪一下）。得到平均亮度后，tone-mapping前，模糊吧。</p><p>2.Bloom</p><p>提到模糊效果，很多千奇百怪的图像处理filter都可以拿出来了。但最实用简单的，我想，就是那高斯模糊（GaussionBlur）了吧。虽然一次纵向处理+一次横向处理两个pass也是花费不菲的&hellip;&hellip;还有一点就是上面提及的，仅取高光部分来模糊（就是另用一个pass预先筛选亮度高于某个值的像素，再对之模糊），这个是可选了，视效果而定。再一点，就是如果实在觉得奢侈的话，可以利用上面那些4N分之一的采样样本作为输入源（还是那句，视效果而择好了），毕竟我们最后需要的也就渲染结果到一张纹理，这个bloom纹理。</p><p>3.Tone-mapping</p><p>最核心的部分。作为最后一个pass，现在我们准备一张原帧图像纹理，一张bloom纹理，一个平均亮度值AverageIntensity。tone-mapping本质上就是亮度映射：</p><p style="text-align: center;">Scaler = KeyIntenSity / AverageIntensity</p><p style="text-align: left;">这个Scaler就是一个比率，一头是实际亮度的均值AverageIntensity，另一头是我们的控制参数KeyIntenSity&mdash;&mdash;它对应于我们可显示亮度范围的均值。这就好似角度转弧度时的&ldquo; PI / 180&rdquo;，通过选取映射双方的系统某个特定值的比率，作为双方量域转化的比率。在大范围暗的场景中，我们可以把这个值设大一点，这样图像中占大部分的低亮度像素就可以映射在一个比较大的范围内，反之亦然。当然，这个值不一定需要规范到（0-1）区间的，但对于HDR而言，大部分常态场景，这个值以（0 - 0.5）之间为宜。这个值的算法基础详见《Photographic tone reproduction for digital images》这篇多年前的论文，该文称之为Key，一般场景以0.18为论（在伽马校正理论中，0.18经过校正后大概是0.5，也就是我们感官上的中等灰度级）。</p><p style="text-align: left;">以IntenSity是根据原帧图像纹理得的当前像素亮度值，则映射后的亮度值为：</p><p style="text-align: center;">ScaleredIntenSity  = Scaler * IntenSity</p><p style="text-align: left;">这样，程序中就可以更自由控制&ldquo;曝光&rdquo;的程度了，而不是完全交由画面去决定。接下来是怎么把这个值规范化到（0-1）：</p><p style="text-align: center;">IntenSityFin = ScaleredIntenSity / （1 + ScaleredIntenSity）</p><p style="text-align: left;">类似这样的规范化式子被称为tone-maping operator，作为最终决定范围映射结果的一步，不同的operator对于最终图像的细节反映程度有一定的影响。像上面这个operator（见于《Photographic tone reproduction for digital images》），对高光部分的影响（由未知大亮度趋向1.0）比暗部分（越接近0越不受限）的影响大，这样能尽量避免暗部细节的丢失的前提下，尽量把高光部分细节的层次感表达出来。针对不同情况选用不同的tone-mapping operator是比较英明的，但是这个就是很大很深的领域了。譬如最近比较有名的Filmic tone mapping（见此文章：<b>Filmic Tonemapping</b> Operators -<a href="http://filmicgames.com/archives/75" target="_blank">http://filmicgames.com/archives/75</a>），对高亮和低亮的两端都给予一定的调和，这样留给暗部的&ldquo;表达空间&rdquo;增多些些，在一些有强烈光暗对比的场景上保留细节得更好一些。</p><p style="text-align: left;">最后采用类似specular-map的方式，把这个tone-shading结果值，与bloom纹理的采样值相加。输出屏幕。fragment-shader（用的上面公式所示operator）：</p><div class="codeText"><div class="codeHead">GLSL代码 (fragment-shader)</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span class="preprocessor">#version&nbsp;130</span><span>&nbsp;&nbsp;</span></span></li>    <li><span><span class="preprocessor">#extension&nbsp;GL_EXT_gpu_shader4&nbsp;:&nbsp;enable</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>uniform&nbsp;sampler2D&nbsp;&nbsp;&nbsp;basetex;&nbsp;&nbsp;</span></li>    <li class="alt"><span>uniform&nbsp;sampler2D&nbsp;&nbsp; bloomtex;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>uniform&nbsp;<span class="datatypes">float</span><span>&nbsp;fAvgLum;&nbsp;&nbsp;</span></span></li>    <li><span>uniform&nbsp;<span class="datatypes">float</span><span>&nbsp;fDimmer;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>varying&nbsp;vec2&nbsp;varying_texcoord;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>out&nbsp;vec4&nbsp;FragDataScene;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>)&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;texCol&nbsp;=&nbsp;texture2D(basetex,&nbsp;varying_texcoord);&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;<span class="datatypes">float</span><span>&nbsp;vLum&nbsp;=&nbsp;0.27&nbsp;*&nbsp;texCol.r&nbsp;+&nbsp;0.67&nbsp;*&nbsp;texCol.g&nbsp;+&nbsp;0.06&nbsp;*&nbsp;texCol.b;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;<span class="datatypes">float</span><span>&nbsp;vLumScaled&nbsp;=&nbsp;fDimmer&nbsp;*&nbsp;vLum&nbsp;/&nbsp;fAvgLum;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;texCol&nbsp;=&nbsp;vLumScaled&nbsp;*&nbsp;texCol;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;texCol&nbsp;=&nbsp;texCol&nbsp;/&nbsp;(vec4(1.0)&nbsp;+&nbsp;texCol);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;texBloom =&nbsp;texture2D(</span><span>bloomtex</span><span>,&nbsp;varying_texcoord);&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;FragDataScene&nbsp;= </span><span>texBloom </span><span>+&nbsp;texCol;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>}&nbsp;&nbsp;</span></li></ol></div><p style="text-align: left;">在这篇文章中有更详细的讲述：<a href="http://dev.gameres.com/Program/Visual/3D/HDRTutorial/HDRTutorial.htm" target="_blank"><span style="font-size: larger;"><span style="font-weight: 700;">HDR渲染器的实现</span></span></a>。里面还提及了通过计算亮度的自然对数后再取平均的，这个我就不太深入了。最后是一图流：</p><p style="text-align: center;"><a target="_blank" href="http://www.zwqxin.com/archives/shaderglsl/review-high-dynamic-range.html"><img height="390" width="400" alt="shader复习与深入:HDR(高动态光照)" src="https://lh5.googleusercontent.com/-YbIVKyoDdCw/TyEdoU68CBI/AAAAAAAAD5s/4D8ue-r3aCw/s400/Snaphdr20120126.jpg" /></a></p><p style="text-align: left;">具体来说，HDR的大体算法不算复杂，也应该比较容易理解。不过要动用那么多PASS那么多FBO那么多Shader什么的，果然还是一个很麻烦的效果。</p><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/shaderglsl/review-high-dynamic-range.html" target="_blank">继续阅读《shader复习与深入:HDR(高动态范围)》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/shaderglsl.html">Shader技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=shader">shader</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E7%AC%94%E8%AE%B0">笔记</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=GLSL">GLSL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E5%9B%BE%E5%83%8F">图像</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/shaderglsl/review-high-dynamic-range.html#comment" target="_blank">添加评论</a>(1)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html">乱弹纪录IV:Transform Feedback</a> (2012-5-12 15:55:31)  </li><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-geometry-instancing.html">乱弹纪录III:Geometry Instancing</a> (2012-5-6 10:57:23)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-depth-of-field.html">shader复习与深入:Depth of Field(景深)</a> (2012-4-29 15:5:43)  </li><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html">乱弹纪录II:Alpha To Coverage</a> (2012-4-14 15:30:17)  </li><li><a href="http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html">OpenGL常用命令备忘录(Part B)</a> (2012-4-4 14:46:59)  </li></ul>]]></description><category>Shader技术</category><comments>http://www.zwqxin.com/archives/shaderglsl/review-high-dynamic-range.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=98</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=98&amp;key=bd489e4c</trackback:ping></item><item><title>MD5模型的格式、导入与顶点蒙皮式骨骼动画II</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-2.html</link><pubDate>Fri, 07 Oct 2011 17:11:07 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-2.html</guid><description><![CDATA[<p>在上一篇文章中简单介绍了MD5模型的格式和载入，本文将从渲染的层面上继续笔记一下&ldquo;顶点蒙皮&rdquo;（vertex-skinning）的实现，以及骨骼节点Joint的变换矩阵向vertex-shader(GLSL)传输的其中几种方法。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>上篇文章见：[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-1.html">MD5模型的格式、导入与顶点蒙皮式骨骼动画I</a>]</p><p>其他模型格式的文章见：<br />[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/idexed-vbo-for-model-3ds-rendering.html">用Indexed-VBO渲染3DS模型</a>]<br />[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/obj-model-format-import-and-render-1.html">OBJ模型文件的结构、导入与渲染Ⅰ</a>]<br />[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/md2-model-format-import-animation.html">MD2格式模型的格式、导入与帧动画</a>]<br />[<a target="_blank" href="http://www.zwqxin.com/archives/opengl/model-md3-format-import-animation.html">MD3模型的格式、导入与骨骼概念动画</a>]</p><p><span style="font-size: smaller;">本文来源于 <strong><#ZC_BLOG_TITLE#></strong> (<em><a href=""http://www.zwqxin.com/"">http://www.zwqxin.com/</a></em>), 转载请注明<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;原文地址：<em><a href=""<#article/url#>""><#article/url#></a></em></span></p><p>首先要说一下的是，如果只是把蒙皮工作完全放在CPU端进行计算的话，那么只看上篇文章已经足够了&mdash;&mdash;每一帧执行各个顶点的计算公式，其中的Joint矩阵由各个关健帧下该Joint的位移和旋转信息插值而来。只不过这样做的话，要承受帧率悲剧的痛苦罢了。现代的骨骼蒙皮主要都是在GPU端做的，这就是vertex-skinning On GPU。在<a target="_blank" href="http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-1.html">上篇</a>中提及一个顶点的计算公式如下：</p><div class="codeText"><ol start="1" class="dp-xml">    <li class="alt"><span><span class="attribute">VertexPos</span><span> =&nbsp;(M<sub>J-0</sub>&nbsp;*&nbsp;weight[<em>index0</em>].pos&nbsp;*&nbsp;weight[</span></span><em><span><span>index</span></span><span><span>0</span></span></em><span><span>].bias)&nbsp;+&nbsp;...&nbsp;+&nbsp;(M<sub>J-N</sub>&nbsp;*&nbsp;weight[<em>indexN</em>].pos&nbsp;*&nbsp;weight</span></span><em><span><span>[</span></span><span><span>index</span></span><span><span>N</span></span></em><span><span>].bias)</span></span><span><span> <br />    </span></span></li></ol></div><p>我们要做的只不过是把这个公式交给shader进行并行计算罢了。公式的右边都是原材料，我们一一细数一下：</p><ul>    <li><span><span>weight[<em>index0</em>]</span></span><span><span>....</span></span><span><span>weight[<em>indexN</em>]</span></span><span><span>，指定该顶点关联的是哪些weight，以及weiht的总数，这个是直接从md5mesh</span></span><span><span>文件的vert字段读入的wight信息；</span></span></li>    <li><span><span>pos、bias，同样，是从</span></span><span><span>md5mesh</span></span><span><span>文件的weight字段读入的信息；</span></span></li>    <li><span><span>M<sub>J</sub><sub>-x</sub>，是各帧经过插值计算得来</span></span>Joint矩阵，其中下标x（对应哪个joint）也是由<span><span>weight字段读入的信息；</span></span></li></ul><p>可见，这些都是已知的，直接都丢给shader做就OK了？哪有那么简单。传给vertex-shader的是顶点本身，如果预先都传入0值，那也还要吧上述信息传给shader&mdash;&mdash;怎么传呢？作为一个顶点的属性的话，它们的量太多了&mdash;&mdash;按一个顶点最多受4个weight影响来计算，那是4个bias+4个pos+4个矩阵=4个float+12个float+48个float(矩阵的上4X3)=64个float，作为顶点属性传入的话这很难让人接受。</p><p>我们要从矩阵空间的角度去考虑。空间变换（[<a href="../../../archives/opengl/opengl-matrix-what.html" target="_blank">乱弹OpenGL中的矩阵变换(上)</a>] /[<a href="../../../archives/opengl/opengl-matrix-what-2.html" target="_blank">乱弹OpenGL中的矩阵变换(下)</a>] ）在这里起着一个比纯数学公式变换更重要的作用，因为很难通过数学证明的方式把上式变换成以下将提及的另一个公式。</p><p>我们最终想要的东西是什么？没错，该模型每一帧所有顶点在&rdquo;模型空间&ldquo;下的坐标位置！（至于把模型空间的点转换到世界空间乃至裁剪空间这些并不是模型导入和自身渲染阶段要处理的事情，虽然同样要在vertex shader里完成。）这个模型坐标系下的坐标如果不在CPU进行所有帧的计算，那还有一个选择，就是从别的坐标系转换过来！我们手头上有哪个坐标系下的模型坐标呢？还记得<a target="_blank" href="http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-1.html">上篇</a>中提及的BindPose姿态吗？在那个姿态下的顶点坐标都是可以在无须动画信息的情况下计算出来的&mdash;&mdash;它也是模型坐标系下的坐标，但对应bindpose的姿态，不妨给予它一个别名&mdash;&mdash;bindpose坐标系下的坐标。好了，每一帧，我们手上有一个bindpose坐标系下的顶点位置，以及该帧各骨骼节点（Joint）的变换矩阵<span><span>M<sub>J</sub><sub>-x</sub></span></span>，我们怎样把它们转换成该帧下的顶点位置（<span><span class="attribute">VertexPos</span></span>）呢？</p><p>还有法宝！bindpose坐标系下的Joint的坐标位置都是经过变换得来的。是的，上篇刚开始谈到md5mesh文件格式的时候引入的M<sub>J-x(bindpose)</sub>！它把对应第x个Joint的weight的位置(weight.pos)转换到bindpose坐标系，那么对于其他东西呢？Joint它自身呢？是的，经过变换后的Joint在其bindpose坐标系下与M<sub>J-x(bindpose)</sub>是等价的（位移和旋转），所以反过来想，变换前的Joint的坐标为（0,0,0）&mdash;&mdash;M<sub>J-x(bindpose)</sub>把第x个joint从它的本地空间（姑且称为joint本地空间）变换到bindpose空间。所以，我们可以直接从每个Joint的角度去观看所有weight，以及与这个Joint有关的顶点。仔细想想，上面的公式中只有Joint的变换矩阵是可变参数，也就是说，只要从joint的角度去看它对应的顶点的话&mdash;&mdash;所有顶点都是静止的，固定的！</p><p>这一点认识摆在我们人体骨骼与皮肤关系上也许更容易直观感受。如此简单却如此重要&mdash;&mdash;任何一帧，对于一个骨骼节点Joint来说，关联的所有顶点的位置都是恒定的&mdash;&mdash;这个位置怎么获得？既然这个位置坐标左乘矩阵M<sub>J-x(bindpose)</sub>进行坐标表换后会变成bindpose下的坐标，那反过来：把bindpose坐标系下的一个顶点VertexPos<sub>bindpose</sub>左乘该变换的逆矩阵M<sub>J-x(bindpose)</sub><sup><sub>-1</sub></sup>就可以获得了。获得这个位置(VertexPos<sub>J-x</sub>)后，某个动画帧下，左乘该Joint的变换矩阵M<sub>J-x</sub>，就是该帧下该顶点的&rdquo;模型空间&ldquo;下的坐标位置了（上面不提及了bindpose空间也就是一个模型空间嘛）！</p><p>等等！在我们的程序里，一个顶点是通过weight对应至少一个至多四个的Joint的！ 那么这个顶点按上面的法子变换出某帧下的模型空间的坐标岂不是有1~4个？不错，所以对应的weight的比率bias再次对这些坐标进行加权平均，最后得到的就是同时受1~4个Joint影响的顶点的真正模型空间坐标位置：<span><span> </span></span><span><span> </span></span></p><div class="codeText"><div class="codeHead">第二个公式：</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span>VertexPos<sup>,</sup> = [</span></span><span><span>(</span></span><span><span>weight[index0].bias * </span></span><span><span>M<sub>J-0</sub>&nbsp;* </span></span>M<sub>J-0(bindpose)</sub><sup><sub>-1</sub></sup><span><span> ) +&nbsp;...&nbsp;+ </span></span><span><span>(</span></span><span><span>weight[indexN].bias * </span></span><span><span>M<sub>J-N</sub> * </span></span>M<sub>J-N(bindpose)</sub><sup><sub>-1</sub></sup><span><span> </span></span><span><span>)</span></span><span><span>]&nbsp; </span></span>* VertexPos<sub>bindpose &nbsp;</sub><span><span>&nbsp;&nbsp;&nbsp; <br />    </span></span></li></ol></div><p>怎么样？有没有兴趣来证明一个第二个式子等价于第一个式子（<span><span>VertexPos<sup>,</sup>&nbsp; = </span></span><span><span>VertexPos</span></span>）？</p><p>在第二个式子里，所有bindpose变量都是可以预先计算好的，bias也是固定的，对应哪些joint、多少个joint，这都是固定的。而且 VertexPos<sub>bindpos</sub><sub>e</sub>（一个vec3）、bias（1~4个float，可用一个vec4表示）、jointIndex（1~4个int，也可以用一个vec4表示）、jointCount（1个int），这些都可以作为顶点属性attribute传入GLSL shader（现在知道<a target="_blank" href="http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-1.html">上篇</a>中末尾数据结构那三个特殊的VBO是干什么的了吧）；不妨设M<sub><span><span>J-x</span></span></sub><sup>,</sup>= <span><span>M<sub>J-x</sub> * </span></span>M<sub>J-x(bindpose)</sub><sup><sub>-1</sub></sup>，骨骼节点有多少个它就有多少个，跟顶点数无关，而且需要每帧更新（直接算出M<sub><span><span>J-x</span></span></sub><sup>,</sup>）&mdash;&mdash;这样的变量（M<sub><span><span>J-x</span></span></sub><sup>,</sup>）必然要以uniform的形式传入GLSL shader。<span><span> </span></span></p><p>至今，骨骼的顶点蒙皮（vertex-skinning）的大貌已经揭示完成了。</p><p>下面看看怎么把Joint的变换矩阵（说的是M<sub><span><span>J-x</span></span></sub><sup>,</sup>）向vertex-shader传输。简单的，大致有四种方法：</p><ol>    <li>Uniform Array</li>    <li>Uniform Buffer Object</li>    <li>2D Texture</li>    <li>Texture Buffer Object</li></ol><p>其中最直接的当然是Uniform Array啦，定义一个uniform mat4 matJoint[MAX_JOINT]，然后把各M<sub><span><span>J-x</span></span></sub><sup>,</sup>直接连成一个数组给传入GLSL就OK了。但问题是GLSL中uniform的个数有限制，如果骨骼节点太多就会超出这个限制了，而且你也不好定MAX_JOINT这个const值。Uniform Buffer Object（UBO）能够解决这个限制，但鉴于不熟，我就不多说。不知道有没有人看过我之前的一篇文章【[<a href="../../../archives/shaderglsl/glsl-vertex-texture-fetch.html" target="_blank">Vertex Texture Fetch 顶点纹理拾取</a>] 】，里面提到一个很重要的观点：<strong>纹理=数组</strong>。没错，我们可以直接把数据放进一张纹理里，然后让shader用sampler去检索出所需要的数据啊！只不过要建立纹理，且纹理的检索有点麻烦（纹素的原点在其中心）也可能会出一丁点精度问题（我觉得可以忽略这些小问题啦）。我这里主要介绍一种新的方式：Texture Buffer Object（TBO）。</p><p>TBO是又一种Buffer Object，跟VBO（[<a href="../../../archives/opengl/learn-vbo.html" target="_blank">学一学，VBO</a>] ）、FBO（[<a href="../../../archives/opengl/learn-fbo.html" target="_blank">学一学，FBO</a>] ）、PBO一样，是一种对Buufer Object的使用方式（另外一提的是Uniform Buffer Object[UBO]也是）。但是它事实上十分简单&mdash;&mdash;它的目的是让一个Buffer Objext内的数据(buffer data)能够被shader作为一个纹理般读取。注意这个纹理只可能是一维的，而且不可以有mipmap、filter，不然是不可能映射到buffer object的buffer里的。</p><div class="codeText"><div class="codeHead">C++代码 初始化</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span>glGenBuffers(1,&nbsp;&amp;pModel-&gt;JointMatInfo.nBufferObject);&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glBindBuffer(GL_TEXTURE_BUFFER,&nbsp;pModel-&gt;JointMatInfo.nBufferObject);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glBufferData(GL_TEXTURE_BUFFER,&nbsp;MATRIX4X3ELEMS&nbsp;*&nbsp;nJointCount&nbsp;*&nbsp;<span class="keyword">sizeof</span><span>(GLfloat),&nbsp;NULL,&nbsp;GL_STREAM_DRAW);&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glGenTextures(1,&nbsp;&amp;pModel-&gt;JointMatInfo.nTexHandleJointMat);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glBindTexture(GL_TEXTURE_BUFFER,&nbsp;pModel-&gt;JointMatInfo.nTexHandleJointMat);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>glTexBuffer(GL_TEXTURE_BUFFER,&nbsp;GL_RGBA32F,&nbsp;pModel-&gt;JointMatInfo.nBufferObject);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>pModel-&gt;JointMatInfo.nTexObjJointMat&nbsp;=&nbsp;GL_TEXTURE1;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>m_pJointMatrixBuffer =&nbsp;<span class="keyword">new</span><span>&nbsp;GLfloat[MATRIX4X3ELEMS&nbsp;*&nbsp;nJointCount];&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>memset(m_pJointMatrixBuffer,&nbsp;0,&nbsp;MATRIX4X3ELEMS&nbsp;*&nbsp;nJointCount&nbsp;*&nbsp;<span class="keyword">sizeof</span><span>(GLfloat));&nbsp;&nbsp;</span></span></li></ol></div><p>初始化TBO很简单，也就是建立一个Buffer Object（数据可以为空可以不为空，反正后面每帧我都会重新填写数据，初始化时数据参量为NULL即可），建立一个纹理，然后用一个<span>glTexBuffer的函数关联两者即可（注意所有的target需要一致为</span><span>GL_TEXTURE_BUFFER</span><span>）。</span><span>m_pJointMatrixBuffer是为了后面填充数据准备的，因为</span>M<sub><span><span>J-x</span></span></sub><sup>,</sup><span>只有上面4列3行有实际意义（尾行一定是0,0,0,1的），所以可以趁机传输少一点数据，在shader里再还原成mat4。</span></p><div class="codeText"><div class="codeHead">C++代码 渲染部分</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pJointMatrixBuffer[MATRIX4X3ELEMS&nbsp;*&nbsp;i&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]&nbsp;=&nbsp;mtInterpolated.mt[0];&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pJointMatrixBuffer[MATRIX4X3ELEMS&nbsp;*&nbsp;i&nbsp;+&nbsp;&nbsp;1]&nbsp;=&nbsp;mtInterpolated.mt[4];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pJointMatrixBuffer[MATRIX4X3ELEMS&nbsp;*&nbsp;i&nbsp;+&nbsp;&nbsp;2]&nbsp;=&nbsp;mtInterpolated.mt[8];&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pJointMatrixBuffer[MATRIX4X3ELEMS&nbsp;*&nbsp;i&nbsp;+&nbsp;&nbsp;3]&nbsp;=&nbsp;mtInterpolated.mt[12];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pJointMatrixBuffer[MATRIX4X3ELEMS&nbsp;*&nbsp;i&nbsp;+&nbsp;&nbsp;4]&nbsp;=&nbsp;mtInterpolated.mt[1];&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pJointMatrixBuffer[MATRIX4X3ELEMS&nbsp;*&nbsp;i&nbsp;+&nbsp;&nbsp;5]&nbsp;=&nbsp;mtInterpolated.mt[5];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pJointMatrixBuffer[MATRIX4X3ELEMS&nbsp;*&nbsp;i&nbsp;+&nbsp;&nbsp;6]&nbsp;=&nbsp;mtInterpolated.mt[9];&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pJointMatrixBuffer[MATRIX4X3ELEMS&nbsp;*&nbsp;i&nbsp;+&nbsp;&nbsp;7]&nbsp;=&nbsp;mtInterpolated.mt[13];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pJointMatrixBuffer[MATRIX4X3ELEMS&nbsp;*&nbsp;i&nbsp;+&nbsp;&nbsp;8]&nbsp;=&nbsp;mtInterpolated.mt[2];&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pJointMatrixBuffer[MATRIX4X3ELEMS&nbsp;*&nbsp;i&nbsp;+&nbsp;&nbsp;9]&nbsp;=&nbsp;mtInterpolated.mt[6];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pJointMatrixBuffer[MATRIX4X3ELEMS&nbsp;*&nbsp;i&nbsp;+&nbsp;10]&nbsp;=&nbsp;mtInterpolated.mt[10];&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pJointMatrixBuffer[MATRIX4X3ELEMS&nbsp;*&nbsp;i&nbsp;+&nbsp;11]&nbsp;= mtInterpolated.mt[14];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(GL_TEXTURE_BUFFER,&nbsp;m_ModelMD5.JointMatInfo.nBufferObject);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glBufferData(GL_TEXTURE_BUFFER,&nbsp;MATRIX4X3ELEMS&nbsp;*&nbsp;nJointCount&nbsp;*&nbsp;<span class="keyword">sizeof</span><span>(GLfloat),&nbsp;NULL,&nbsp;GL_STREAM_DRAW);&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glBufferSubData(GL_TEXTURE_BUFFER,&nbsp;0,&nbsp;MATRIX4X3ELEMS&nbsp;*&nbsp;nJointCount&nbsp;*&nbsp;<span class="keyword">sizeof</span><span>(GLfloat),&nbsp;m_pJointMatrixBuffer);&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glBindBuffer(GL_TEXTURE_BUFFER,&nbsp;NULL);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; glActiveTexture(m_ModelMD5.JointMatInfo.nTexObjJointMat);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;glBindTexture(GL_TEXTURE_BUFFER,&nbsp;m_ModelMD5.JointMatInfo.nTexHandleJointMat);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...&nbsp;&nbsp;</span></li></ol></div><p>渲染部分首先就是更新当前的TBO数据了，其中<span>&nbsp;mtInterpolated.mt就是</span>M<sub><span><span>J-x</span></span></sub><sup>,</sup><span>了。更新TBO只是纯粹更新那个Buffer Object而已，跟纹理无关（至于针对buffer object数据传输的优化方式，诸如stream update啦使用带参的glMapBuffer</span>Range啦，我就先不进行喽<span>）。然后在VBO渲染前把纹理启用并传输给shader就可以了（注意依然是</span><span>GL_TEXTURE_BUFFER</span><span>）。vertex-shader进行顶点蒙皮的代码如下：</span></p><div class="codeText"><div class="codeHead">ModelVertexSkinningTB.vert&nbsp;&nbsp;&nbsp;&nbsp; - Texture Buffer Object方式的vertex-skinning：</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span class="preprocessor">#version&nbsp;140</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>in&nbsp;vec3&nbsp;attrib_position;&nbsp;&nbsp;</span></li>    <li><span>in&nbsp;vec3&nbsp;attrib_normal;&nbsp;&nbsp;</span></li>    <li class="alt"><span>in&nbsp;vec2&nbsp;attrib_texcoord;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>in&nbsp;vec4&nbsp;&nbsp;attrib_weightbias;&nbsp;&nbsp;</span></li>    <li><span>in&nbsp;<span class="datatypes">float</span><span>&nbsp;attrib_weightcount;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>in&nbsp;vec4&nbsp;&nbsp;attrib_weightjoint;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>uniform&nbsp;samplerBuffer&nbsp;jointtex;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>...&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>)&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;<span class="datatypes">int</span><span>&nbsp;nWeightCount&nbsp;=&nbsp;</span><span class="datatypes">int</span><span>(attrib_weightcount);&nbsp;&nbsp;&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;attribPos&nbsp;=&nbsp;vec4(attrib_position,&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;mat4&nbsp;mtRes&nbsp;=&nbsp;mat4(1.0);<span class="comment">//mat4(0.0)</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;<span class="keyword">if</span><span>(nWeightCount&nbsp;&gt;&nbsp;0)&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mtRes&nbsp;=&nbsp;mat4(0.0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span><span>(</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;nWeightCount;&nbsp;++i)&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mtRes[0]&nbsp;+= texelFetch(jointtex,&nbsp;<span class="datatypes">int</span><span>(3&nbsp;*&nbsp;attrib_weightjoint[i])&nbsp;&nbsp;&nbsp;&nbsp;)&nbsp;*&nbsp;attrib_weightbias[i];&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mtRes[1]&nbsp;+=&nbsp;texelFetch(jointtex,&nbsp;<span class="datatypes">int</span><span>(3&nbsp;*&nbsp;attrib_weightjoint[i])&nbsp;+&nbsp;1)&nbsp;*&nbsp;attrib_weightbias[i];&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mtRes[2]&nbsp;+=&nbsp;texelFetch(jointtex,&nbsp;<span class="datatypes">int</span><span>(3&nbsp;*&nbsp;attrib_weightjoint[i])&nbsp;+&nbsp;2)&nbsp;*&nbsp;attrib_weightbias[i];&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mtRes[3]&nbsp;=&nbsp;vec4(0.0,&nbsp;0.0,&nbsp;0.0,&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;mat4&nbsp;mtInvRes&nbsp;=&nbsp;transpose(mtRes);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;resPos&nbsp;=&nbsp;mtInvRes&nbsp;*&nbsp;attribPos;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;resPos&nbsp;=&nbsp;resPos&nbsp;/&nbsp;resPos.w;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;....&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li></ol></div><p>注意sampler名为<span>samplerBuffer，一维纹理数据只能通过</span><span>texelFetch去取。因为在纹理里是(RGBA)(RGBA)...这样的结构，所以对于一个骨骼节点Joint的矩阵，3个fetch就取够12个矩阵元素了。最后再给出</span>Uniform Array和2D Texture方式的GLSL顶点shader的代码片段吧：</p><div class="codeText"><div class="codeHead">ModelVertexSkinningUA.vert&nbsp;&nbsp;&nbsp;&nbsp; - Uniform Array方式的vertex-skinning：</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span class="preprocessor">#define&nbsp;MAX_JOINT&nbsp;71</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>uniform&nbsp;mat4&nbsp;jointMatrix[MAX_JOINT];&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>)&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;<span class="datatypes">int</span><span>&nbsp;nWeightCount&nbsp;=&nbsp;</span><span class="datatypes">int</span><span>(attrib_weightcount);&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;attribPos&nbsp;=&nbsp;vec4(attrib_position,&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;mat4&nbsp;mtRes&nbsp;=&nbsp;mat4(1.0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;<span class="keyword">if</span><span>(nWeightCount&nbsp;&gt;&nbsp;0)&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mtRes&nbsp;=&nbsp;mat4(0.0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span><span>(</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;nWeightCount;&nbsp;++i)&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mtRes&nbsp;+=&nbsp;jointMatrix[<span class="datatypes">int</span><span>(attrib_weightjoint[i])]&nbsp;*&nbsp;attrib_weightbias[i];&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;resPos&nbsp;=&nbsp;mtRes&nbsp;*&nbsp;attribPos;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;resPos&nbsp;=&nbsp;resPos&nbsp;/&nbsp;resPos.w;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;....&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li></ol></div><div class="codeText"><div class="codeHead">ModelVertexSkinningTD.vert&nbsp;&nbsp;&nbsp;&nbsp; - 2D Texture方式的vertex-skinning：</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span>uniform&nbsp;sampler2D&nbsp;jointtex;&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="keyword">void</span><span>&nbsp;main(</span><span class="keyword">void</span><span>)&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;<span class="datatypes">int</span><span>&nbsp;nWeightCount&nbsp;=&nbsp;</span><span class="datatypes">int</span><span>(attrib_weightcount);&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;attribPos&nbsp;=&nbsp;vec4(attrib_position,&nbsp;1.0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;ivec2&nbsp;jointTexSize&nbsp;=&nbsp;textureSize(jointtex,&nbsp;0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;<span class="datatypes">float</span><span>&nbsp;fTexcoordStepU&nbsp;=&nbsp;1.0&nbsp;/&nbsp;3.0;&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;<span class="datatypes">float</span><span>&nbsp;fTexcoordStepV&nbsp;=&nbsp;1.0&nbsp;/&nbsp;jointTexSize.y;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;vMtX&nbsp;=&nbsp;vec4(0.0);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;vMtY&nbsp;=&nbsp;vec4(0.0);&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;vMtZ&nbsp;=&nbsp;vec4(0.0);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;<span class="keyword">for</span><span>(</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;nWeightCount;&nbsp;++i)&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vMtX&nbsp;+=&nbsp;texture2D(jointtex,&nbsp;vec2(fTexcoordStepU&nbsp;*&nbsp;0.5,&nbsp;(attrib_weightjoint[i]&nbsp;+&nbsp;0.5)&nbsp;*&nbsp;fTexcoordStepV))&nbsp;*&nbsp;attrib_weightbias[i];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vMtY&nbsp;+=&nbsp;texture2D(jointtex,&nbsp;vec2(fTexcoordStepU&nbsp;*&nbsp;1.5,&nbsp;(attrib_weightjoint[i]&nbsp;+&nbsp;0.5)&nbsp;*&nbsp;fTexcoordStepV))&nbsp;*&nbsp;attrib_weightbias[i];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vMtZ&nbsp;+=&nbsp;texture2D(jointtex,&nbsp;vec2(fTexcoordStepU&nbsp;*&nbsp;2.5,&nbsp;(attrib_weightjoint[i]&nbsp;+&nbsp;0.5)&nbsp;*&nbsp;fTexcoordStepV))&nbsp;*&nbsp;attrib_weightbias[i];&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;vec4&nbsp;resPos;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;resPos.x&nbsp;=&nbsp;dot(vMtX,&nbsp;attribPos);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;resPos.y&nbsp;=&nbsp;dot(vMtY,&nbsp;attribPos);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;resPos.z&nbsp;=&nbsp;dot(vMtZ,&nbsp;attribPos);&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;resPos.w&nbsp;=&nbsp;1.0;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;....&nbsp;&nbsp;</span></li>    <li><span>}&nbsp;&nbsp;</span></li></ol></div><p><a href="http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-2.html" target="_self"><img src="https://lh6.googleusercontent.com/-JiIpYkjx9mY/TyEdoFI7deI/AAAAAAAAD5o/32YqJ02uniI/s512/Snapmd520120126drs.jpg" alt="MD5模型的格式、导入与顶点蒙皮式骨骼动画" /></a></p><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-2.html" target="_blank">继续阅读《MD5模型的格式、导入与顶点蒙皮式骨骼动画II》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/opengl.html">OpenGL技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=GLSL">GLSL</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=shader">shader</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=MD5">MD5</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E6%A8%A1%E5%9E%8B">模型</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-2.html#comment" target="_blank">添加评论</a>(0)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html">乱弹纪录IV:Transform Feedback</a> (2012-5-12 15:55:31)  </li><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-geometry-instancing.html">乱弹纪录III:Geometry Instancing</a> (2012-5-6 10:57:23)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-depth-of-field.html">shader复习与深入:Depth of Field(景深)</a> (2012-4-29 15:5:43)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/talk-about-geometry-shader.html">乱弹纪录I:Geometry Shader</a> (2012-4-2 15:43:51)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-high-dynamic-range.html">shader复习与深入:HDR(高动态范围)</a> (2012-1-26 14:50:43)  </li></ul>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-2.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=97</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=97&amp;key=e0fa7954</trackback:ping></item><item><title>MD5模型的格式、导入与顶点蒙皮式骨骼动画I</title><author>a@b.com (zwqxin)</author><link>http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-1.html</link><pubDate>Thu, 06 Oct 2011 21:14:49 +0800</pubDate><guid>http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-1.html</guid><description><![CDATA[<p>MD5模型是ID公司第一款真正意义上的骨骼格式模型，在04年随着Doom3一起面世，经过几个版本的变更，现在在骨骼模型格式中依然有其重要地位。本文记录一下ZWModelMD5中的一些细节，先是稍微笔记一下骨骼模型的基本概念和MD5文件的格式与导入。&mdash;&mdash;<a href="http://www.zwqxin.com/">ZwqXin</a>.com</p><p>[<a href="http://www.zwqxin.com/archives/opengl/md2-model-format-import-animation.html" target="_blank">MD2格式模型的格式、导入与帧动画</a>]</p><p>[<a href="http://www.zwqxin.com/archives/opengl/model-md3-format-import-animation.html" target="_blank">MD3模型的格式、导入与骨骼概念动画</a>]</p><p><span style="font-size: smaller;">本文来源于 <strong><#ZC_BLOG_TITLE#></strong> (<em><a href=""http://www.zwqxin.com/"">http://www.zwqxin.com/</a></em>), 转载请注明<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;原文地址：<em><a href=""<#article/url#>""><#article/url#></a></em></span></p><p>经过MD2的帧动画和MD3的骨骼概念动画，当然还有MD4/MDL的尝试，在那个骨骼模型开始风行的时代，MD5作为骨骼动画出现了。在今天，3D模型通常分为静态模型、帧动画模型、骨骼动画模型，它们分别应用于不同的场合，静态模型就不用说了，帧动画模型主要用于人物动作简单、固定、与场景不怎么需要交互的场合，而骨骼动画模型就是与此相对了。</p><p>骨骼的这个概念与我们人体的骨骼还是类似的。我们可以把自己看做一堆骨骼，然后外面蒙上一层肌肉啊皮啊什么的，然后这些肌肉啊皮肤啊的就跟随骨骼的运动而运动。当然了，重要的是我们体内还有那么多器官，那些MD5人体和怪物模型就没有了(笑)。骨骼与骨骼之间是用骨骼节点连接的，我们称骨骼为Bone，称骨骼节点为Joint，一根bone的一端或两端连着两个Joint，而一个Joint可能连着数条Bone。骨骼模型的描述也分为以Bone为主和以Joint为主，MD5是后者。你可以认为Joint就是控制点，通过控制Joint的位置和旋转，可以控制整个骨骼，而整个骨骼也就影响模型的外皮(顶点网格)，于是动画模式建立了。Joint的集合可以用一个树的数据结构描述&mdash;&mdash;跟MD3一样，有一个总的父节点，总的父节点下连着一个或多个子节点，这些子节点本身也作为父节点下连一个或多个子节点&hellip;&hellip;父节点的移动直接先作用到子节点上（抬动肩关节时手臂节点也跟着作同样的运动，之后手肘节点跟着手臂节点作同样移动&hellip;&hellip;类推到指尖节点），再叠加上子节点本身的移动（手臂节点本身可以再那基础上作移动，其影响共同作用到手肘节点&hellip;&hellip;用身体摆摆姿势，这其实是很形象的），于是这个前向的驱动模式建立了。每个Joint的运动信息可以抽象成一个变换矩阵M（[<a href="../../../archives/opengl/opengl-matrix-what.html" target="_blank">乱弹OpenGL中的矩阵变换(上)</a>] ），这样这个驱动模型可以看做是每个时刻给予每个节点一个变换矩阵，变换节点的位置和旋向以驱动骨架。</p><p>既然骨架模型建立了，接下来就是骨架与模型顶点数据的关系。骨骼模型本身渲染出来的不是骨架，而是组成网格（皮肤）的一堆顶点。这堆顶点是怎样定义的呢？在MD2中，每帧都包含一堆顶点位置数据，结果就是程序需要存储大规模的顶点位置数据。MD5则不直接储存顶点位置数据，而是让程序每帧&rdquo;计算&ldquo;出来。在MD5的文件中的网格数据包括纹理坐标（因为最后的顶点数目是固定的，做一纹理坐标数据的数目与之一致）、索引(把顶点组成三角面片，因为最后的顶点是有序的，前一帧的顶点跟后一帧的一一对应，所以只要按这个次序定义索引即可)、节点权重(weight，这就是关联骨骼节点跟顶点的东西，下述)；一个顶点数据有一个纹理坐标、一个或多个weight组成，然后索引数据组织顶点。与以往不同的是，这里面没有法线数据，MD5采用的是与3DS（[<a href="../../../archives/opengl/idexed-vbo-for-model-3ds-rendering.html" target="_blank">用Indexed-VBO渲染3DS模型</a>] ）和OBJ（[<a href="../../../archives/opengl/obj-model-format-import-and-render-1.html" target="_blank">OBJ模型文件的结构、导入与渲染Ⅰ</a>] ）一样的策略，让程序自己去计算。</p><p>一个weight包含了它对应的Joint的索引（这样一来就建立了 vertex-&gt;weight-&gt;joint的连接），一个位置值(pos)和一个作用比率(bias)。一个顶点的计算公式如下：</p><div class="codeText"><ol start="1" class="dp-xml">    <li class="alt"><span><span class="attribute">VertexPos</span><span>&nbsp;=&nbsp;(M<sub>J-0</sub>&nbsp;*&nbsp;weight[<em>index0</em>].pos&nbsp;*&nbsp;weight[</span></span><em><span><span>index</span></span><span><span>0</span></span></em><span><span>].bias)&nbsp;+&nbsp;...&nbsp;+&nbsp;(M<sub>J-N</sub>&nbsp;*&nbsp;weight[<em>indexN</em>].pos&nbsp;*&nbsp;weight</span></span><em><span><span>[</span></span><span><span>index</span></span><span><span>N</span></span></em><span><span>].bias)&nbsp;&nbsp;</span></span></li></ol></div><p>其中，<span><span>M<sub>J-x</sub></span></span>表示第x个weight对应的节点Joint的变换矩阵。作用比率bias的总和需要是1（100%），这样一个顶点位置可以看作是各个经过矩阵变换后的weight位置的加权平均。而这个Joint矩阵在动画过程中变化的话，结果就是对应计算出来的顶点位置也跟着变化了。这就是骨架驱动皮肤的过程，也称为&rdquo;蒙皮&ldquo;。这步计算可以在CPU上执行，也可以在GPU上执行&mdash;&mdash;通过vertex shadr执行蒙皮，就称为&rdquo;顶点蒙皮（vertex skinning）&ldquo;，我将在下篇文章讲述。</p><p>一个MD5模型包含两个文件，其中.md5mesh后缀的文件包含了该模型的几何体数据(mesh)，而.md5anim后缀的文件则包含了该模型的动画信息。这一点与MD3模型是一样的，只不过很多方面看上去更为规范，没有在[<a href="../../../archives/opengl/model-md3-format-import-animation.html" target="_blank">MD3模型的格式、导入与骨骼概念动画</a>]文末提及的那些令人不爽的&ldquo;小提示&quot;。另一点很本质上不同的是，md5的两个文件都是文本文件，这当然提供了更大的方便性，但同时也容易出现文件被乱改的问题（当然了，本来idSoft就只是想自用而已）。</p><p>一个MD5可以只有md5mesh文件，这样模型只不过不含动画信息而已。而这时候出来的模型的姿态被称为Bind-pose。以前看视频看人用maya建模（就是看那部《堕落的艺术》的幕后花粹时），在修改模型，未定义动作之前，人物会呈现一个站立并两手平举的姿态。这就是一个模型的bind-pose姿态吧。这个概念在顶点蒙皮过程中尤显重要，不过你只需要记住这就是没有动画信息（没有md5anim）时候给予模型的一个&rdquo;预设姿势&ldquo;好了。下面看看文件结构</p><div class="codeText"><div class="codeHead">md5mesh：</div><ol start="1" class="dp-xml">    <li class="alt"><span><span>joints&nbsp;{&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&quot;origin&quot;&nbsp;&nbsp;&nbsp;&nbsp;-1&nbsp;(&nbsp;-0.000000&nbsp;0.016430&nbsp;-0.006044&nbsp;)&nbsp;(&nbsp;0.707107&nbsp;0.000000&nbsp;0.707107&nbsp;)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp; &quot;body&quot;&nbsp;&nbsp;&nbsp; 0&nbsp;( -0.0000002384 0 56.5783920288 )&nbsp;(&nbsp;0.507041&nbsp;-0.578614&nbsp;0.354181&nbsp;)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;origin&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;....&nbsp;&nbsp;</span></li>    <li class="alt"><span>}&nbsp;&nbsp;</span></li></ol></div><p>那些版本号啊XX总数的就不管了，从md5mesh文件开头看起，首先是Joint的定义：名称、父节点序号（-1说明本身是总父节点，这个序号其实就是行号了，譬如上面&rdquo;origin&ldquo;节点的序号就是0，无父节点；<span> &quot;body&quot;节点序号是1，父节点序号是0，也就是</span>说父节点是&rdquo;origin&ldquo;）、bind-pose姿态下节点的位置（位移）和旋转（旋转用四元数【[<a href="../../../archives/arithmetic/gimballock-and-quaternion.html" target="_blank">GimbalLock万向节锁与四元数旋转</a>] 】表达，括号里是xyz，需程序自行计算w值）&mdash;&mdash;后面两者可以组成一个变换矩阵M<sub>self-bindpose</sub>，即bindpose姿态下各个节点自身的变换矩阵，如果给这个矩阵依次向上左乘该节点的树分支上各级父节点的变换矩阵，得到就是bindpose下该节点的真正变换矩阵M<sub>J-x(bindpose)</sub>了。</p><div class="codeText"><div class="codeHead">md5mesh：</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span>mesh&nbsp;{&nbsp;&nbsp;</span><span class="comment">//一个网格对象</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;shader&nbsp;<span class="string">&quot;body1.tga&quot;</span><span>&nbsp;&nbsp;</span><span class="comment">//该网格对象的纹理</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;numverts&nbsp;590&nbsp;&nbsp;&nbsp;<span class="comment">//顶点数据：vert&nbsp;序号&nbsp;（纹理坐标）&nbsp;对应weight的起始序号&nbsp;weight总数</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;vert&nbsp;0&nbsp;(&nbsp;0.394531&nbsp;0.513672&nbsp;)&nbsp;0&nbsp;2&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.....&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;numtris&nbsp;888&nbsp;&nbsp;<span class="comment">//索引数据:&nbsp;tri&nbsp;序号&nbsp;三角面片对应的顶点数据的序号</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tri&nbsp;0&nbsp;0&nbsp;2&nbsp;1&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.....&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;numweights&nbsp;967&nbsp;<span class="comment">//权重数据：weight&nbsp;序号&nbsp;对应的Joint的序号&nbsp;比率bias值&nbsp;（位置值）</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;weight&nbsp;0&nbsp;5&nbsp;1.000000&nbsp;(&nbsp;6.175774&nbsp;8.105262&nbsp;-0.023020&nbsp;)&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.....&nbsp;&nbsp;</span></li>    <li class="alt"><span>}&nbsp;&nbsp;</span></li></ol></div><p>md5mesh文件后面部分就是一个个网格对象(mesh)的数据了。看上面注释，跟前面的讲述是一致的。注意这里vert末尾两个数据是对应下面那堆weight的，而且总是相邻的一个或多个weight，所以只需要第一个的序号和连续的weight的个数就可以确定了。顶点仅会被附近的weight影响。</p><p>接下来看md5anim：</p><div class="codeText"><ol start="1" class="dp-xml">    <li class="alt"><span><span>hierarchy&nbsp;{&nbsp;&nbsp; //Joint 名字 父节点序号 flag 影响的帧数据起始索引<br />    </span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&quot;origin&quot;&nbsp;&nbsp;&nbsp;&nbsp;-1&nbsp;63&nbsp;0&nbsp;//&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&quot;Body&quot;&nbsp;&nbsp;0&nbsp;63&nbsp;6&nbsp;&nbsp;//&nbsp;origin&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;....&nbsp;&nbsp;</span></li>    <li class="alt"><span>}&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>bounds&nbsp;{&nbsp;&nbsp;//每帧的包围盒&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;(&nbsp;...&nbsp;)&nbsp;(&nbsp;...&nbsp;)&nbsp;&nbsp;</span></li>    <li class="alt"><span>}&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>baseframe{&nbsp;&nbsp;//&nbsp; 基础帧数据</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;(&nbsp;...&nbsp;)&nbsp;(&nbsp;...&nbsp;)&nbsp;&nbsp;</span></li>    <li class="alt"><span>}&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>frame&nbsp;0&nbsp;{&nbsp;&nbsp; //帧0数据<br />    </span></li>    <li><span>...&nbsp;&nbsp;</span></li>    <li class="alt"><span>}&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span>frame&nbsp;1&nbsp;{&nbsp;&nbsp;</span></li>    <li><span>...&nbsp;&nbsp;</span></li>    <li class="alt"><span>}&nbsp;&nbsp;</span></li>    <li><span>...&nbsp;&nbsp;</span></li></ol></div><p>老实说我觉得md5anim文件特别别扭，虽然理解起来不难。首先文件的开头也是joint的信息，不过这里主要针对帧数据，尾部的数据是一个索引值(nStartIndex)，指向后面每一帧（frame x）的数据堆里， flag是一个bit位。嘛，这样看吧。MD5虽然不是帧动画，但它依然有&rdquo;关健帧&ldquo;的概念（也可以说这是动画本身的概念），模型的某个动画由有限个关健帧穿插并近邻插值而成，但MD5不同于MD2之处在于它只需要每个关健帧骨骼节点Joint的数据。为了替换上面bindpose的顶点计算公式，我们需要的只是每个joint在动画期间的变换矩阵M<sub>J</sub>，但我们为了能在关健帧之间合理插值，通常并不直接保存矩阵而是分别保存位移信息(transform-vector3)和旋转信息(Rotation-quternion)。这个文件主要包含的就是这每个关健帧下每个Joint的这两个数据，当然还包括关健帧数目。至于这文件里的每帧的模型包围盒信息，并不是必要的。</p><p>在上面的baseframe里有与Joint数目相等的行数，把每行看作一个joint的位移信息+旋转信息（6个数字，这跟md5mesh文件开头joint的bindpose信息是一样的格式），但这里的baseframe数据无实际意义，仅表示一个&rdquo;基础数值&ldquo;，对于第x个关健帧，就拿下面frame x里的某些数据替换这些&rdquo;基础信息&ldquo;，具体每个joint要拿哪些数据去替换，正就是开头的索引值(nStartIndex)和flag决定的了。nStartIndex决定了替换开始对应数据堆的位置，nflag决定替换6个数字中的哪几个（flag分别与1、2、4、8、16、32作逻辑与，第一个出现为真的时候就拿nStartIndex处的数据替换掉，第二个出现真的时候就拿nStartIndex+1处的数据替换掉...如果逻辑与结果为假则不替换直接用回basefame里对应的数据）。这样下来就能取得我们要的&quot;每个关健帧下每个Joint的位移信息+旋转信息&quot;。</p><p>导入代码没什么特别的，也就按步骤进行&rdquo;文件-&gt;一定数据结构下的内存数据&ldquo;的转换。但确实颇冗长，尤其我还是以C语言方式进行读文件的&hellip;&hellip;最后计算法线、生成VBO等都跟以前的模型导入流程差不多，有些细节地方我将在下篇文章提及。最后给出我用于导入的数据结构：</p><div class="codeText"><div class="codeHead">C++代码</div><ol start="1" class="dp-cpp">    <li class="alt"><span><span class="comment">//包围盒信息</span><span>&nbsp;&nbsp;</span></span></li>    <li><span><span class="keyword">typedef</span><span>&nbsp;</span><span class="keyword">struct</span><span>&nbsp;tag3DBound&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;Vector3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vMin;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;Vector3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vMax;&nbsp;&nbsp;</span></li>    <li><span>}t3DBound;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="comment">//&nbsp;模型的帧动画信息</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span><span class="keyword">typedef</span><span>&nbsp;</span><span class="keyword">struct</span><span>&nbsp;tag3DFrameInfo&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;<span class="datatypes">int</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nFrameCount;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;总帧数</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;<span class="datatypes">int</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nAnimComponent;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;每帧动画数据量</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;<span class="datatypes">int</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nCurFrame;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;当前帧</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;<span class="datatypes">int</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nNextFrame;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;下一帧</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;<span class="datatypes">int</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nStartFrame;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;开始帧</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;<span class="datatypes">int</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nEndFrame;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;结束帧</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">float</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fSecPerKeyFrame;&nbsp;&nbsp;</span><span class="comment">//&nbsp;关健帧间隔</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">float</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fCurBlendValue;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;当前融合变量</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">DWORD</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DStartPlot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;开始时点</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;t3DBound&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*tBoundingBox;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//&nbsp;包围盒</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>}t3DFrameInfo;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;</span></li>    <li class="alt"><span><span class="comment">//&nbsp;Joint属性</span><span>&nbsp;&nbsp;</span></span></li>    <li><span><span class="keyword">typedef</span><span>&nbsp;</span><span class="keyword">struct</span><span>&nbsp;tag3DJointInfo&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;Vector3&nbsp;&nbsp;&nbsp;&nbsp;vTransform;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Quaternion&nbsp;qRotatation;&nbsp;&nbsp;</span></li>    <li><span>}t3DJointInfo;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="comment">//&nbsp;Joint模型关节点信息</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span><span class="keyword">typedef</span><span>&nbsp;</span><span class="keyword">struct</span><span>&nbsp;tag3DJoint&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">char</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;szJointName[MNAME];&nbsp;</span><span class="comment">//&nbsp;Joint名称</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">int</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nParentJointIndex;&nbsp;&nbsp;</span><span class="comment">//&nbsp;父关节点索引</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;Matrix16&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BindPoseMatrix;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//&nbsp;Joint&nbsp;基本变换(位移和旋转)矩阵</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;Matrix16&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BindPoseMatrixInv;&nbsp;&nbsp;<span class="comment">//&nbsp;Joint&nbsp;基本变换(位移和旋转)矩阵的逆矩阵</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;std::vector&lt;t3DJointInfo&gt;&nbsp;FramePoseInfoVec;&nbsp;&nbsp;&nbsp;<span class="comment">//&nbsp;Joint&nbsp;帧位移和旋转</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">BYTE</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nAffectFlags;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;产生影响的顶点数据对象的标记</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;<span class="datatypes">int</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nAffectStartIndex;&nbsp;&nbsp;</span><span class="comment">//&nbsp;产生影响的顶点数据对象在帧数据的起始位置</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>}t3DJoint;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="comment">//顶点权位信息</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span><span class="keyword">typedef</span><span>&nbsp;</span><span class="keyword">struct</span><span>&nbsp;tag3DWeight&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;<span class="datatypes">int</span><span>&nbsp;nAttachJoint;&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">float</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fBias;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;Vector3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vPos;&nbsp;&nbsp;</span></li>    <li><span>}t3DWeight;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="keyword">typedef</span><span>&nbsp;</span><span class="keyword">struct</span><span>&nbsp;tag3DVectorInfo&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>{&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;<span class="datatypes">int</span><span>&nbsp;nWeightStartIndex;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;<span class="datatypes">int</span><span>&nbsp;nWeightCount;&nbsp;&nbsp;</span></span></li>    <li><span>}VectorWeightInfo;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="comment">//&nbsp;网格对象信息</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span><span class="keyword">typedef</span><span>&nbsp;</span><span class="keyword">struct</span><span>&nbsp;tag3DObject&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;GLuint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nDiffuseMap;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;Vector3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*pPosVerts;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;Vector3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*pNormals;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;TexCoord&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*pTexcoords;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;t3DWeight&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*pPosWeights;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;VectorWeightInfo&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*pVecWeightInfo;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;<span class="datatypes">short</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*pIndexes;&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;<span class="datatypes">int</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nNumIndexes;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;<span class="datatypes">int</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nNumVerts;&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;<span class="datatypes">int</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nNumWeights;&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;GLuint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nPosVBO;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;GLuint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nNormVBO;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;GLuint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nTexcoordVBO;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;GLuint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nWeightVBO;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;GLuint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nWightCountVBO;&nbsp;&nbsp;</span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;GLuint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nJointIndexVBO;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;GLuint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nIndexVBO;&nbsp;&nbsp;</span></li>    <li><span>}t3DObject;&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;</span></li>    <li><span><span class="comment">//&nbsp;模型信息结构体</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span><span class="keyword">typedef</span><span>&nbsp;</span><span class="keyword">struct</span><span>&nbsp;tag3DModel&nbsp;&nbsp;&nbsp;</span></span></li>    <li><span>{&nbsp;&nbsp;</span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">bool</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bVisable;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;是否渲染</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">bool</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bIsTextured;&nbsp;&nbsp;</span><span class="comment">//&nbsp;是否使用纹理</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="datatypes">bool</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bHasAnim;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="comment">//&nbsp;是否含动画信息</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;GLuint&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TexObjMap;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//&nbsp;纹理对象</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;std::vector&lt;t3DObject&gt;&nbsp;&nbsp;t3DObjVec;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//&nbsp;网格对象列表</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;std::vector&lt;t3DJoint&gt;&nbsp;&nbsp;&nbsp;t3DJointVec;&nbsp;&nbsp;<span class="comment">//&nbsp;骨骼点列表</span><span>&nbsp;&nbsp;</span></span></li>    <li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;t3DFrameInfo&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tFrameInfo;&nbsp;&nbsp;&nbsp;<span class="comment">//&nbsp;帧信息</span><span>&nbsp;&nbsp;</span></span></li>    <li><span>}t3DModel;&nbsp;&nbsp;</span></li></ol></div><p>注意，对于Bindpose的Joint信息我是直接作为矩阵存储的（它的逆矩阵在顶点蒙皮的时候有用，所以也预先存储了），而动画过程中的Joint信息我是作为位移+旋转信息存储的（为了在关键帧中插值）。现在它们都在同一结构体内，迟些时候我应该会分开它们的（分开mesh部分和anim部分）。VBO部分有三个比较特殊的：<span>nWeightVBO（weight的比率bias，用vec4传输，也就是说如果影响一个顶点的weight多于4个，我会把它们压成4个）；</span><span>nWightCountVBO（实际的weight数目）；</span><span>nJointIndexVBO（该weight对应的Joint的序号），这些对于顶点蒙皮是有用的，所以需要作为顶点属性传入vertex-sahder。</span></p><p><span>下篇见：</span><a href="http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-2.html" target="_blank">MD5模型的格式、导入与顶点蒙皮式骨骼动画II</a></p><p>Copyright © 2008</p><p><a href="http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-1.html" target="_blank">继续阅读《MD5模型的格式、导入与顶点蒙皮式骨骼动画I》的全文内容...</a></p><p>分类: <a href="http://www.zwqxin.com/archives/opengl.html">OpenGL技术</a> | Tags: <a href="http://www.zwqxin.com/catalog.asp?tags=%E6%A8%A1%E5%9E%8B">模型</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=MD5">MD5</a>&nbsp;&nbsp;<a href="http://www.zwqxin.com/catalog.asp?tags=%E7%AC%94%E8%AE%B0">笔记</a>&nbsp;&nbsp; | <a href="http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-1.html#comment" target="_blank">添加评论</a>(1)</p><h3>相关文章:</h3><ul><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-transform-feedback.html">乱弹纪录IV:Transform Feedback</a> (2012-5-12 15:55:31)  </li><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-geometry-instancing.html">乱弹纪录III:Geometry Instancing</a> (2012-5-6 10:57:23)  </li><li><a href="http://www.zwqxin.com/archives/shaderglsl/review-depth-of-field.html">shader复习与深入:Depth of Field(景深)</a> (2012-4-29 15:5:43)  </li><li><a href="http://www.zwqxin.com/archives/opengl/talk-about-alpha-to-coverage.html">乱弹纪录II:Alpha To Coverage</a> (2012-4-14 15:30:17)  </li><li><a href="http://www.zwqxin.com/archives/opengl/opengl-api-memorandum-2.html">OpenGL常用命令备忘录(Part B)</a> (2012-4-4 14:46:59)  </li></ul>]]></description><category>OpenGL技术</category><comments>http://www.zwqxin.com/archives/opengl/model-md5-format-import-animation-1.html#comment</comments><wfw:comment>http://www.zwqxin.com/</wfw:comment><wfw:commentRss>http://www.zwqxin.com/feed.asp?cmt=96</wfw:commentRss><trackback:ping>http://www.zwqxin.com/cmd.asp?act=tb&amp;id=96&amp;key=44ff917f</trackback:ping></item></channel></rss>

