﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><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>C++博客-xosen-文章分类-Direct3D</title><link>http://www.cppblog.com/xosen/category/10100.html</link><description /><language>zh-cn</language><lastBuildDate>Sun, 26 Apr 2009 12:38:25 GMT</lastBuildDate><pubDate>Sun, 26 Apr 2009 12:38:25 GMT</pubDate><ttl>60</ttl><item><title>关于多线程渲染</title><link>http://www.cppblog.com/xosen/articles/81048.html</link><dc:creator>xosen</dc:creator><author>xosen</author><pubDate>Sat, 25 Apr 2009 09:11:00 GMT</pubDate><guid>http://www.cppblog.com/xosen/articles/81048.html</guid><wfw:comment>http://www.cppblog.com/xosen/comments/81048.html</wfw:comment><comments>http://www.cppblog.com/xosen/articles/81048.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/xosen/comments/commentRss/81048.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/xosen/services/trackbacks/81048.html</trackback:ping><description><![CDATA[<h2><a id=homepage1_HomePageDays_DaysList_ctl02_DayItem_DayList_ctl00_TitleUrl href="http://www.cnblogs.com/ixnehc/archive/2008/09/04/1284708.html"><u><font color=#0000ff>关于多线程渲染</font></u></a><span style="FONT-SIZE: 12pt"><strong><br></strong></h2>
<div>&nbsp;</div>
<div><strong><br></strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>多核的cpu现在是大势所趋,渲染是一个很费时的活,所以应该考虑考虑能不能利用多核来提升这部分的性能.引擎一开始没有在多线程方面作任何的考虑,因为我从来就不喜欢多线程,这方面的思考能力不强,而且一开始写个单线程的engine已经够费事了,要加入多线程的设计对我来说实在是太难了.但是在积累了这么多时间的经验以后,我开始考虑加入多线程的支持,这玩意对架构的影响很大,所以加入设计还是宜早不宜迟.一开始先加入了多线程载入资源的模块,也顺便恢复一下早已荒废多年的多线程编程技能.接下来就该考虑将渲染部分放入一个单独的线程中去了.</strong></div>
<div><strong><br></strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>首先,为什么呢?为什么要把渲染部分放到一个单独的线程中去呢?有什么好处呢?我的理解是这样的:显卡可以看成是一个外设,渲染的过程就是cpu不停的给gpu发各种命令,根据d3d的文档上说,d3d内部有一个command buffer,cpu在调用各种d3d的api时,其实是往这个command buffer里添加命令,这个command buffer是有一定大小的,当这个buffer满了以后,会有一个flush过程:这个buffer里的命令会被一齐扔给gpu去执行,然后这个buffer会被清空,以接受新的命令.按照d3d的文档上说,这个flush过程是很慢的,它必须要等待这个command buffer里的命令全部被gpu执行完,才会返回,不过我自己测了测,似乎不完全是这样,会有等待,但等待的时间好像并不是全部的执行完这些命令的时间,具体为什么,我也搞不清楚,也许写显卡驱动的人会更了解一些吧,d3d对我们这些写应用程序的人来说就是黑盒子.不过阻塞是一定会有的.所以我认为可以把gpu连同D3D看成一个类似硬盘的io设备.cpu通过调用d3d的api来给这个设备发命令,大多数情况下,这些api立即就返回了(这些命令被cache在d3d内部的一个command buffer里),但是偶尔的,当一个api调用试图在一个已经满了的command buffer里再加入命令时,就会触发一次d3d内部的flush过程,而这个时候,这个api调用就必须等待这个io设备了,也就是cpu这个时候是空闲的,计算能力在这里被浪费了.很多关于d3d的教材都提到不要调用太多的draw call,应该把多个draw call合并在同一个batch里,我以前以为,可能是因为d3d api的调用本身有开销,我现在的理解是,draw call太多,会导致太多的d3d api调用,太多的命令,而这样会导致d3d内部的command buffer频繁溢出,从而导致d3d内部频繁的flush,加剧了cpu等待gpu的情况. 如果d3d的内部运作模式真是我上面描述的那样的话,把渲染部分放到一个单独的线程中去就显得有必要了,因为可以把cpu在等待gpu的时间充分利用起来,或者至少不会让这些等待耽误到主线程的运行.</strong></div>
<div><strong><br></strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>接下来,就要考虑怎么来做了.显然,这是一个典型的producer-consumer的结构,主线程(procucer)将要执行的渲染命令不停的加到一个队列里,再开一个渲染线程(consumer)不停的从这个个队列里读取命令,并执行.一开始我主要考虑要减少这些命令的个数,这样实现可以简单些,效率可以高些,所以打算把我自己实现的整个渲染系统放到一个单独的线程中去,把原来的接口全部转换为可以被队列化的一个个命令.干了一段时间后,发现实现难度还不小,接口数量还是太多了,所以又打算把接口提升到更高的层次中去,也就是把整个场景的渲染模块放到一个单独的线程中去,这个渲染模块主要包含了渲染对象的场景管理以及渲染分类.这样做的确减少了接口数量,所以虽然比较艰辛,我终于几乎还是把它给实现了,不过就在接近实现的时候,我又把整个问题重新考虑了一下,发现似乎又走了弯路.我的想法是这样:</strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>*.对于目前大多数的游戏,每一帧的运算主要包括两部分:渲染和逻辑.</strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>*.对于3D游戏,gpu完成渲染部分,cpu完成逻辑部分.</strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>*.渲染部分是非常耗时的,一般来说比逻辑部分的消耗的时间要多</strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>*.发展的趋势是cpu朝多核方向发展,逻辑部分可以被分到多个线程去做.</strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>*.XBOX360已经有6个核了.</strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>*.gpu也会发展,但是目前的即时渲染的水平离电影级的渲染水平仍然有很大的差距.</strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>*.所以,相当长的时间内,渲染的耗时仍然会是瓶颈</strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>*.而目前的D3D的架构只允许使用一个单独的线程去驱动gpu</strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>*.所以不应该让这个单独的线程做额外的工作了,也就是渲染线程应该越单纯越好,</strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>*.而最最单纯的就是在这个渲染线程里只做一件事情,就是给D3D发命令.</strong></div>
<div><strong><br></strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>所以最后我采取了这样的方法,我把所用到的D3D接口函数全部重新写了一下,在这些函数里,我把原来对d3d的调用全部转化成一个个的命令,加到一个队列里,而由一个单独的渲染线程从这个队列里读取命令,再把它们传递给D3D.这样做还有一个附加的好处就是,它对整个引擎的架构的影响很小,所有的功能被局限在一个集中的模块里,并且可以很方便的enable/disable,很方便的在多线程/单线程渲染这两者之间切换,便于对比调试.而且如果这个模块写得完美,甚至可以开放给公众使用.</strong></div>
<div><strong><br></strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><em><strong>Threading the OGRE3D Render System(by Jeff Andrews)</strong></em><strong>,这篇文章也提到过这种方法,它提出了三种线程化的方案,主要也是按照渲染线程所处的层级来分的,和我上面说的比较类似,并且讨论了不少实现细节.不过它似乎认为在最高层次上进行线程化可以最大限度的发挥多核的威力,我想他可能是站在cpu计算瓶颈的角度上来看的吧.作者也为在最低层次上线程化的方案(重写D3D接口)作了实现(有源代码下载,我参考了一下),并做了测试,但效果似乎并不那么显著.不过他说it's doable.</strong></div>
<div><strong><br></strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>实现这套接口对我来说并不是件容易的事,先是磨磨蹭蹭花了一周才写完,测了一下,结果很糟糕,在同事的双核电脑上比单线程版本要慢很多,以我贫乏的多核应用经验,一开始我以为是cpu的问题,后来在vista上测试的时候,发现了不错的结果,多线程版本比单线程版本几乎快了一倍(说实话,我第一次对vista有了好感),但是xp下就不行,进而怀疑操作系统的问题,下载了一个多核性能的bench mark,结果一切正常,问题看来还是在自己身上.最终还是找出了问题所在.目前的这个测试用例在双核的电脑上的有不小的性能提升,执行时间大概是单线程版本的60%左右,在单核的电脑上性能有微小的下降.还没有用更多的例子测过,所以不一定能说明问题,但至少说明是有前途的,是doable的.</strong></div>
<div><strong><br></strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>实现过程中有一个地方值得注意,就是vertex buffer的Lock()/Unlock()的处理.上面说的这篇文章里也重点提到了这个问题,并提出了两种解决方法,Partially Buffered&nbsp;Locks和Fully Buffered Locks.我的方法有所不同,我将vb的lock分为三种情况:</strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>1.static的vertex buffer,通常这种vb用来存储不会发生变化的vertex 数据,一般只在初始化的时候需要lock()/unlock(),对性能的影响不是很大,所以在lock时,会对command buffer进行一次flush,也就是说主线程等待渲染线程将当前command buffer里的所有命令全处理完后,才进行lock()/unlock()</strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>2.dynamic的vertex buffer,no overwrite的lock(),所谓no overwrite的lock,就是lock vb后,使用者可以保证不去覆写那些已经被用到的vertex数据(这些vertex的数据可能正在被gpu用来绘制),在这种情况下,可以比较简单的处理,只要暂时冻结渲染线程的处理,然后进行lock就行了,然后在unlock后,再恢复渲染线程的处理.</strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>3.dynamic的vertex buffer,discard的lock(),这种lock将会丢弃原来vb中的所有内容,这时候显然不能简单的对这个vb使用discard标志进行lock,因为使用这个vb进行绘制的命令可能还在command buffer里,等待渲染线程的处理.我一开始的解决方法是:先冻结住渲染线程的执行,然后添加一条release这个vb的命令到command buffer中去,然后再创建一个新的dynamic的vertex buffer,并对它进行lock,返回lock的数据指针,当unlock后,再恢复渲染线程的执行.后来做了些优化,因为在nVidia的卡上,创建一个vertex buffer似乎对性能有很大的影响，所以我改为使用一个vertex buffer的pool,vb不会被真正release掉,而是扔到这个pool里,当需要新的vb时,再从这个pool里分配.这样可以使性能开销降到最低.</strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>在实际应用中,情况2应该是最常使用的,它的性能开销也是最小的.只需要要锁一下渲染线程.</strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>对于texture/surface的lock()/unlock(),似乎没有很好的方法,必须flush一下command buffer.好在这种情况并不会太频繁的出现.而偶尔flush一下command buffer也不是那么的不可忍受.</strong></div>
<div><strong><br></strong></div>
<div><span class=Apple-tab-span style="WHITE-SPACE: pre"><strong></strong></span><strong>顺便提一下多线程编程的一个注意点,A线程访问变量a,同时B线程访问变量b,如果a和b这两个变量在地址空间上离的很近的话,是会降低性能的,不能做到真正的同步访问,所谓false sharing.要避免这种情况,这两个变量要分配在不同的内存段上面,内存段的长度可能和硬件有关系吧,一般比如说128个字节,不放心的话,再远一点.</strong></div>
<div></div>
</span>
<img src ="http://www.cppblog.com/xosen/aggbug/81048.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/xosen/" target="_blank">xosen</a> 2009-04-25 17:11 <a href="http://www.cppblog.com/xosen/articles/81048.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>渲染状态管理</title><link>http://www.cppblog.com/xosen/articles/78942.html</link><dc:creator>xosen</dc:creator><author>xosen</author><pubDate>Sat, 04 Apr 2009 05:53:00 GMT</pubDate><guid>http://www.cppblog.com/xosen/articles/78942.html</guid><wfw:comment>http://www.cppblog.com/xosen/comments/78942.html</wfw:comment><comments>http://www.cppblog.com/xosen/articles/78942.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/xosen/comments/commentRss/78942.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/xosen/services/trackbacks/78942.html</trackback:ping><description><![CDATA[文档简介：提高3D图形程序的性能是个很大的课题。图形程序的优化大致可以分成两大任务，一是要有好的场景管理程序，能快速剔除不可见多边形，并根据对象距相机远近选择合适的细节（LOD）；二是要有好的渲染程序，能快速渲染送入渲染管线的可见多边形。
<p>&nbsp;&nbsp;&nbsp; 我们知道，使用OpenGL或Direct3D渲染图形时，首先要设置渲染状态，渲染状态用于控制渲染器的渲染行为。应用程序可以通过改变渲染状态来控制OpenGL或Direct3D的渲染行为。比如设置Vertex/Fragment Program、绑定纹理、打开深度测试、设置雾效等。</p>
<p>&nbsp;&nbsp;&nbsp; 改变渲染状态对于<a title=显卡 style="COLOR: #000000" href="http://product.it168.com/list/b/0206_1.shtml" target=_blank><u>显卡</u></a>而言是比较耗时的操作，而如果能合理管理渲染状态，避免多余的状态切换，将明显提升图形程序性能。这篇文章将讨论渲染状态的管理。</p>
<p>&nbsp;&nbsp;&nbsp; 我们考虑一个典型的游戏场景，包含人、动物、植物、建筑、交通工具、武器等。稍微分析一下就会发现，实际上场景里很多对象的渲染状态是一样的，比如所有的人和动物的渲染状态一般都一样，所有的植物渲染状态也一样，同样建筑、交通工具、武器也是如此。我们可以把具有相同的渲染状态的对象归为一组，然后分组渲染，对每组对象只需要在渲染前设置一次渲染状态，并且还可以保存当前的渲染状态，设置渲染状态时只需改变和当前状态不一样的状态。这样可以大大减少多余的状态切换。下面的代码段演示了这种方法：</p>
<p>&nbsp;&nbsp;&nbsp; // 渲染状态组链表，由场景管理程序填充<br><br>&nbsp;&nbsp;&nbsp; RenderStateGroupList groupList;<br>&nbsp;&nbsp;&nbsp; // 当前渲染状态<br>&nbsp;&nbsp;&nbsp; RenderState curState;<br>&nbsp;&nbsp;&nbsp; &#8230;&#8230;<br><br>&nbsp;&nbsp;&nbsp; // 遍历链表中的每个组<br>&nbsp;&nbsp;&nbsp; RenderStateGroup *group = groupList.GetFirst();<br>&nbsp;&nbsp;&nbsp; while ( group != NULL )</p>
<p>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; // 设置该组的渲染状态<br>&nbsp;&nbsp;&nbsp; RenderState *state = group-&gt;GetRenderState();<br>&nbsp;&nbsp;&nbsp; state-&gt;ApplyRenderState( curState );<br>&nbsp;&nbsp;&nbsp; // 该渲染状态组的对象链表<br>&nbsp;&nbsp;&nbsp; RenderableObjectList *objList = group-&gt;GetRenderableObjectList();<br>&nbsp;&nbsp;&nbsp; // 遍历对象链表的每个对象<br>&nbsp;&nbsp;&nbsp; RenderableObject *obj = objList-&gt;GetFirst();<br>&nbsp;&nbsp;&nbsp; while ( obj != NULL )</p>
<p>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; // 渲染对象<br>&nbsp;&nbsp;&nbsp; obj-&gt;Render();<br>&nbsp;&nbsp;&nbsp; obj = objList-&gt;GetNext();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; group = groupList.GetNext();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; 其中RenderState类的ApplyRenderState方法形如：</p>
<p>&nbsp;&nbsp;&nbsp; void RenderState::ApplyRenderState( RenderState &amp;curState )</p>
<p>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; // 深度测试<br>&nbsp;&nbsp;&nbsp; if ( depthTest != curState.depthTest )</p>
<p>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; SetDepthTest( depthTest );<br>&nbsp;&nbsp;&nbsp; curState.depthTest = depthTest;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; // Alpha测试<br>&nbsp;&nbsp;&nbsp; if ( alphaTest != curState.alphaTest )</p>
<p>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; SetAlphaTest( alphaTest );<br>&nbsp;&nbsp;&nbsp; curState.alphaTest = alphaTest;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; // 其它渲染状态<br>&nbsp;&nbsp;&nbsp; &#8230;&#8230;<br>&nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp;&nbsp; 这些分组的渲染状态一般被称为Material或Shader。这里Material不同于OpenGL和Direct3D里面用于光照的材质，Shader也不同于OpenGL里面的Vertex/Fragment Program和Direct3D里面的Vertex/Pixel Shader。而是指封装了的显卡渲染图形需要的状态（也包括了OpenGL和Direct3D原来的Material和Shader）。</p>
<p>&nbsp;&nbsp;&nbsp; 从字面上看，Material（材质）更侧重于对象表面外观属性的描述，而Shader（这个词实在不好用中文表示）则有用程序控制对象表面外观的含义。由于显卡可编程管线的引入，渲染状态中包含了Vertex/Fragment Program，这些小程序可以控制物体的渲染，所以我觉得将封装的渲染状态称为Shader更合适。这篇文章也将称之为Shader。<br><br>&nbsp;&nbsp;&nbsp;上面的代码段只是简单的演示了渲染状态管理的基本思路，实际上渲染状态的管理需要考虑很多问题。</p>
<p>&nbsp;&nbsp;&nbsp; 消耗时间问题<br><br>&nbsp;&nbsp;&nbsp; 改变渲染状态时，不同的状态消耗的时间并不一样，甚至在不同条件下改变渲染状态消耗的时间也不一样。比如绑定纹理是一个很耗时的操作，而当纹理已经在<a title=显卡 style="COLOR: #000000" href="http://product.it168.com/list/b/0206_1.shtml" target=_blank><u>显卡</u></a>的纹理缓存中时，速度就会非常快。而且随着硬件和<a title=软件 style="COLOR: #000000" href="http://software.it168.com/" target=_blank><u>软件</u></a>的发展，一些很耗时的渲染状态的消耗时间可能会有减少。因此并没有一个准确的消耗时间的数据。</p>
<p>&nbsp;&nbsp;&nbsp; 虽然消耗时间无法量化，情况不同消耗的时间也不一样，但一般来说下面这些状态切换是比较消耗时间的：</p>
<p>&nbsp;&nbsp;&nbsp; Vertex/Fragment Program模式和固定管线模式的切换（FF，Fixed Function Pipeline）</p>
<p>&nbsp;&nbsp;&nbsp; Vertex/Fragment Program本身程序的切换<br><br>&nbsp;&nbsp;&nbsp; 改变Vertex/Fragment Program常量<br><br>&nbsp;&nbsp;&nbsp; 纹理切换<br><br>&nbsp;&nbsp;&nbsp; 顶点和索引缓存（Vertex &amp; Index Buffers）切换有时需要根据消耗时间的多少来做折衷，下面将会遇到这种情况。</p>
<p>&nbsp;&nbsp;&nbsp; 渲染状态分类<br><br>&nbsp;&nbsp;&nbsp; 实际场景中，往往会出现这样的情况，一类对象其它渲染状态都一样，只是纹理和顶点、索引数据不同。比如场景中的人，只是身材、长相、服装等不同，也就是说只有纹理、顶点、索引数据不同，而其它如Vertex/Fragment Program、深度测试等渲染状态都一样。相反，一般不会存在纹理和顶点、索引数据相同，而其他渲染状态不同的情况。我们可以把纹理、顶点、索引数据不归入到Shader中，这样场景中所有的人都可以用一个Shader来渲染，然后在这个Shader下对纹理进行分组排序，相同纹理的人放在一起渲染。</p>
<p>&nbsp;&nbsp;&nbsp; 多道渲染（Multipass Rendering）</p>
<p>&nbsp;&nbsp;&nbsp; 有些比较复杂的图形效果，在低档显卡上需要渲染多次，每次渲染一种效果，然后用GL_BLEND合成为最终效果。这种方法叫多道渲染Multipass Rendering，渲染一次就是一个pass。比如做逐像素凹凸光照，需要计算环境光、漫射光凹凸效果、高光凹凸效果，在NV20显卡上只需要1个pass，而在NV10显卡上则需要3个pass。Shader应该支持多道渲染，即一个Shader应该分别包含每个pass的渲染状态。</p>
<p>&nbsp;&nbsp;&nbsp; 不同的pass往往渲染状态和纹理都不同，而顶点、索引数据是一样的。这带来一个问题：是以对象为单位渲染，一次渲染一个对象的所有pass，然后渲染下一个对象；还是以pass为单位渲染，第一次渲染所有对象的第一个pass，第二次渲染所有对象的第二个pass。下面的程序段演示了这两种方式：</p>
<p>&nbsp;&nbsp;&nbsp; 以对象为单位渲染<br><br>&nbsp;&nbsp;&nbsp; // 渲染状态组链表，由场景管理程序填充<br>&nbsp;&nbsp;&nbsp; ShaderGroupList groupList;<br>&nbsp;&nbsp;&nbsp; &#8230;&#8230;<br>&nbsp;&nbsp;&nbsp; // 遍历链表中的每个组<br>&nbsp;&nbsp;&nbsp; ShaderGroup *group = groupList.GetFirst();<br>&nbsp;&nbsp;&nbsp; while ( group != NULL )</p>
<p>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; Shader *shader = group-&gt;GetShader();<br>&nbsp;&nbsp;&nbsp; RenderableObjectList *objList = group-&gt;GetRenderableObjectList();<br>&nbsp;&nbsp;&nbsp; // 遍历相同Shader的每个对象<br>&nbsp;&nbsp;&nbsp; RenderableObject *obj = objList-&gt;GetFirst();<br>&nbsp;&nbsp;&nbsp; while ( obj != NULL )</p>
<p>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; // 获取shader的pass数<br>&nbsp;&nbsp;&nbsp; int iNumPasses = shader-&gt;GetPassNum();<br>&nbsp;&nbsp;&nbsp; for ( int i = 0; i &lt; iNumPasses; i++ )</p>
<p>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; // 设置shader第i个pass的渲染状态<br>&nbsp;&nbsp;&nbsp; shader-&gt;ApplyPass( i );<br>&nbsp;&nbsp;&nbsp; // 渲染对象<br>&nbsp;&nbsp;&nbsp; obj-&gt;Render();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; obj = objList-&gt;GetNext();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; group = groupList-&gt;GetNext();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; 以pass为单位渲染<br>&nbsp;&nbsp;&nbsp; // 渲染状态组链表，由场景管理程序填充<br>&nbsp;&nbsp;&nbsp; ShaderGroupList groupList;<br>&nbsp;&nbsp;&nbsp; &#8230;&#8230;<br>&nbsp;&nbsp;&nbsp; for ( int i = 0; i &lt; MAX_PASSES_NUM; i++ )</p>
<p>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; // 遍历链表中的每个组<br>&nbsp;&nbsp;&nbsp; ShaderGroup *group = groupList.GetFirst();<br>&nbsp;&nbsp;&nbsp; while ( group != NULL )</p>
<p>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; Shader *shader = group-&gt;GetShader();<br>&nbsp;&nbsp;&nbsp; int iNumPasses = shader-&gt;GetPassNum();<br>&nbsp;&nbsp;&nbsp; // 如果shader的pass数小于循环次数，跳过此shader<br>&nbsp;&nbsp;&nbsp; if( i &gt;= iNumPasses )</p>
<p>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; group = groupList-&gt;GetNext();<br>&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; // 设置shader第i个pass的渲染状态<br>&nbsp;&nbsp;&nbsp; shader-&gt;ApplyPass( i );<br>&nbsp;&nbsp;&nbsp; RenderableObjectList *objList =<br>&nbsp;&nbsp;&nbsp; group-&gt;GetRenderableObjectList();<br>&nbsp;&nbsp;&nbsp; // 遍历相同Shader的每个对象<br>&nbsp;&nbsp;&nbsp; RenderableObject *obj = objList-&gt;GetFirst();<br>&nbsp;&nbsp;&nbsp; while ( obj != NULL )</p>
<p>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; obj-&gt;Render();<br>&nbsp;&nbsp;&nbsp; obj = objList-&gt;GetNext();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; group = groupList-&gt;GetNext();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp;&nbsp; 这两种方式各有什么优缺点呢？</p>
<p>&nbsp;&nbsp;&nbsp; 以对象为单位渲染，渲染一个对象的第一个pass后，马上紧接着渲染这个对象的第二个pass，而每个pass的顶点和索引数据是相同的，因此第一个pass将顶点和索引数据送入显卡后，显卡Cache中已经有了这个对象顶点和索引数据，后续pass不必重新将顶点和索引数据拷到显卡，因此速度会非常快。而问题是每个pass的渲染状态都不同，这使得实际上每次渲染都要设置新的渲染状态，会产生大量的多余渲染状态切换。</p>
<p>&nbsp;&nbsp;&nbsp; 以pass为单位渲染则正好相反，以Shader分组，相同Shader的对象一起渲染，可以只在这组开始时设置一次渲染状态，相比以对象为单位，大大减少了渲染状态切换。可是每次渲染的对象不同，因此每次都要将对象的顶点和索引数据拷贝到显卡，会消耗不少时间。</p>
<p>&nbsp;&nbsp;&nbsp; 可见想减少渲染状态切换就要频繁拷贝顶点索引数据，而想减少拷贝顶点索引数据又不得不增加渲染状态切换。鱼与熊掌不可兼得 :-(</p>
<p>&nbsp;&nbsp;&nbsp; 由于硬件条件和场景数据的情况比较复杂，具体哪种方法效率较高并没有定式，两种方法都有人使用，具体选用那种方法需要在实际环境测试后才能知道。</p>
<p>&nbsp;&nbsp;&nbsp; 多光源问题<br>&nbsp;&nbsp;&nbsp; 待续&#8230;&#8230;<br>&nbsp;&nbsp;&nbsp; 阴影问题<br>&nbsp;&nbsp;&nbsp; 待续&#8230;&#8230;<br>&nbsp;&nbsp;&nbsp; 渲染脚本<br>&nbsp;&nbsp;&nbsp; 现在很多图形程序都会自己定义一种脚本文件来描述Shader。</p>
<p>&nbsp;&nbsp;&nbsp; 比如较早的OGRE（Object-oriented Graphics Rendering Engine，面向对象图形渲染引擎）的Material脚本，Quake3的Shader脚本，以及刚问世不久的Direct3D的Effect File，nVIDIA的CgFX脚本（文件格式与Direct3D Effect File兼容），ATI RenderMonkey使用的xml格式的脚本。OGRE Material和Quake3 Shader这两种脚本比较有历史了，不支持可编程渲染管线。而后面三种比较新的脚本都支持可编程渲染管线。</p>
<p>&nbsp;&nbsp;&nbsp; 脚本&nbsp; 特性&nbsp; 范例<br><br>&nbsp;&nbsp;&nbsp; OGRE Material 封装各种渲染状态，不支持可编程渲染管线&nbsp; &gt;&gt;&gt;&gt;<br><br>&nbsp;&nbsp;&nbsp; Quake3 Shader 封装渲染状态，支持一些特效，不支持可编程渲染管线&nbsp; &gt;&gt;&gt;&gt;<br><br>&nbsp;&nbsp;&nbsp; Direct3D Effect File 封装渲染状态，支持multipass，支持可编程渲染管线&nbsp; &gt;&gt;&gt;&gt;<br><br>&nbsp;&nbsp;&nbsp; nVIDIA CgFX脚本 封装渲染状态，支持multipass，支持可编程渲染管线&nbsp; &gt;&gt;&gt;&gt;<br><br>&nbsp;&nbsp;&nbsp; ATI RenderMonkey脚本 封装渲染状态，支持multipass，支持可编程渲染管线&nbsp; &gt;&gt;&gt;&gt;<br><br>&nbsp;&nbsp;&nbsp; 使用脚本来控制渲染有很多好处：</p>
<p>&nbsp;&nbsp;&nbsp; 可以非常方便的修改一个物体的外观而不需重新编写或编译程序<br><br>&nbsp;&nbsp;&nbsp; 可以用外围工具以所见即所得的方式来创建、修改脚本文件（类似ATI RenderMonkey的工作方式），便于美工、关卡设计人员设定对象外观，建立外围工具与图形引擎的联系可以在渲染时将相同外观属性及渲染状态的对象（也就是Shader相同的对象）归为一组，然后分组渲染，对每组对象只需要在渲染前设置一次渲染状态，大大减少了多余的状态切换。</p>
<img src ="http://www.cppblog.com/xosen/aggbug/78942.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/xosen/" target="_blank">xosen</a> 2009-04-04 13:53 <a href="http://www.cppblog.com/xosen/articles/78942.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>