越 的个人资料三无之徒照片日志列表 工具 帮助

日志


5月11日

Epic Games: Tim Sweeney Interview

Ø         GDC08 上展示的UE3 的新特性是否存在平台相关性

n         一句话,针对所有平台,不过不同平台的硬件限制可能会导致效果不同。

Ø         UE3的版本问题

n         这个问题主要是针对 UE2 XBox 版本的,Tim的回答是 UE3 在设计当初就考虑到平台差异性,所以整个引擎在核心代码上都是完全相同的,针对不同平台的特性,实现略有不同,但基本核心完全相同。

Ø         关于演示中的高密度人群系统技术

n         Tim回答说这个系统可以支持不同类型的角色数据,但是不同的类型越多,优化就越困难(Me:借助硬件的Instance技术都有这个毛病),他建议说,最好不要用这技术做交互性的功能,这个技术更适合视觉上的特效(Me:看了Gow2的演示,我非常相信这一点,也符合我之前的猜测。)

Ø         关于《刺客信条》中的人群 AI技术

n         一句话,如果需要,UE3也可以做得到。

Ø         SSAO

n         由于是基于屏幕空间计算,SSAO和场景复杂度无关。之前的 UE3 Shadow 技术,是基于对象空间的,所以当有更多角色或者更多动态光,代价就会越大。(Me:至少产生阴影是基于Screen的吧?)

Ø         实时全局光照还有多远?

n         他提到了 hl2 ,简单说 hl2 业时全局光照计算,但是需要做大量的预处理工作,这样即使 dx7 级别的硬件也可以跑的起来。前提是静态场景和光照。同时他还提到了 Geometrics 团队,他们同样采用了预处理但是可以支持动态光照。(Me:那个演示我看过,确实不错。)Tim认为在2012年左右会进行下一次的主机换代(Me:这么久?)他认为下一代主机可以实现相当真实的光照计算,因为即使是现在也可以实现不错的光照效果,虽然是每场景只有几个光照而已。

Ø         关于UE4

n         UE3将贯穿整个次世代主机时代,他希望能在20112012甚至更晚时还能看到用 UE3 作的游戏。Epic将全力的改进、优化和扩展 UE3。尽管不断的加入新的特性,但是引擎的整体架构不会变化,游戏开发人员可以很方便的移植到不同的UE3版本中(Me:真的这么方便吗?)目前在Epic 中有个UE4的研究团队,但是只有一个人,那就是TimMe:大权独揽啊。),目前进展非常慢,明年也不会有太多人加入这个团队,除非快要进入下个次世代。

Ø         UE3如何决定加入新特性

n         基本上UE3的特性都是被内部的游戏开发项目所驱动的,比如《战争机器2》。目前物理破坏系统和柔体系统是当前优先级比较高的特性。对开发来说,困难的不仅是加入新特性,而是如何集成正确的特性并使之能正常运转起来。(Me:深有同感!)唯一的方法就是自己去使用这些新功能,并运用到游戏中去(MeGow2,而且要让美术日复一日的去使用,在对程序员不断的抱怨新功能有多么的糟糕的过程中,这些功能就会不断地在完善。整个过程都是基于内部的开发需求驱动的。(Me:说得太好了,说到底引擎也是个软件,也需要靠需求驱动,有什么样的需求,就有什么样的引擎功能。)

Ø         IBM Power架构和IntelAMDx86架构的比较

n         (这里说了许多,都是经济上的,总的来说就是性价比,虽然Intel Cpu 性能很好,但是不经济实惠,而Power芯片就好很多,未来的发展主要还是经济实惠型的。看来Tim很有经济头脑,并不是一个只研究技术的程序员。)

Ø         多核技术

n         首先,多核架构的开发是困难的。但是硬件的发展趋势是阻挡不住的,因为多核是唯一能更经济的提升性能的方法(Me:又一次提到经济!)。多核下的引擎架构比单线程更有效率,但是也付出了更多的设计、实现、维护、调试….所有的成本都随着多核而变大,这是相当的昂贵,但是最后必须忍受。但是其它硬件架构比这更糟,比如 Cell,他们发现需要付出5倍的努力才能达到相同的效果。Tim希望能出现针对多核编程的语言,这样程序员在写程序时和写单线程一样,由编译器来完成多核的优化工作,可惜这种语言目前还在实验室中,还不能商用,他相信接下来的5年多核程序语言将会越来越成熟,那时程序员写多核程序就和写单线程程序一样简单。(Me:我也是这样想的,一直到现在我都觉得多核的程序优化应该是编译器的事情,不需要程序员进行过多的参与就可以实现针对多核程序设计。)

Ø         其它:

n         Tim20年前高中时就实现过自己的编程语言。

n         Tim认为GPUCPU 都可能被对方取代。

n         GPUCPUPhysic计算,Tim认为当前阶段GPU尚需努力,毕竟 CPU 已经发展这么多年,而且有完善的缓存机制,强大的分支预测能力等,这都是 GPU 的弱项。

 

原文奉上,有兴趣的自己看吧:

http://interviews.teamxbox.com/xbox/2169/Epic-Games-Tim-Sweeney-Interview/p1/

8月1日

D3D 渲染状态 API 调用开销表

在DXSDK里找到的,这个是一般情况下的调用开销,计算单位是 CPU Cycles,可以依据这个构建一张静态渲染状态开销表,根据这个标准来构建 shader tree 。

API Call

 

Average number of Cycles

SetVertexDeclaration 6500 - 11250
SetFVF 6400 - 11200
SetVertexShader 3000 - 12100
SetPixelShader 6300 - 7000
SPECULARENABLE 1900 - 11200
SetRenderTarget 6000 - 6250
SetPixelShaderConstant (1 Constant) 1500 - 9000
NORMALIZENORMALS 2200 - 8100
LightEnable 1300 - 9000
SetStreamSource 3700 - 5800
LIGHTING 1700 - 7500
DIFFUSEMATERIALSOURCE 900 - 8300
AMBIENTMATERIALSOURCE 900 - 8200
COLORVERTEX 800 - 7800
SetLight 2200 - 5100
SetTransform 3200 - 3750
SetIndices 900 - 5600
AMBIENT 1150 - 4800
SetTexture 2500 - 3100
SPECULARMATERIALSOURCE 900 - 4600
EMISSIVEMATERIALSOURCE 900 - 4500
SetMaterial 1000 - 3700
ZENABLE 700 - 3900
WRAP0 1600 - 2700
MINFILTER 1700 - 2500
MAGFILTER 1700 - 2400
SetVertexShaderConstant (1 Constant) 1000 - 2700
COLOROP 1500 - 2100
COLORARG2 1300 - 2000
COLORARG1 1300 - 1980
CULLMODE 500 - 2570
CLIPPING 500 - 2550
DrawIndexedPrimitive 1200 - 1400
ADDRESSV 1090 - 1500
ADDRESSU 1070 - 1500
DrawPrimitive 1050 - 1150
SRGBTEXTURE 150 - 1500
STENCILMASK 570 - 700
STENCILZFAIL 500 - 800
STENCILREF 550 - 700
ALPHABLENDENABLE 550 - 700
STENCILFUNC 560 - 680
STENCILWRITEMASK 520 - 700
STENCILFAIL 500 - 750
ZFUNC 510 - 700
ZWRITEENABLE 520 - 680
STENCILENABLE 540 - 650
STENCILPASS 560 - 630
SRCBLEND 500 - 685
TWOSIDEDSTENCILMODE 450 - 590
ALPHATESTENABLE 470 - 525
ALPHAREF 460 - 530
ALPHAFUNC 450 - 540
DESTBLEND 475 - 510
COLORWRITEENABLE 465 - 515
CCW_STENCILFAIL 340 - 560
CCW_STENCILPASS 340 - 545
CCW_STENCILZFAIL 330 - 495
SCISSORTESTENABLE 375 - 440
CCW_STENCILFUNC 250 - 480
SetScissorRect 150 - 340

7月28日

状态管理(State Management)

翻译:丛越

本文只为学习之用,版权所属原作者,如有转载,请注明出处。

原文地址http://www.delphi3d.net/articles/viewarticle.php?article=stateman.htm

 

状态变化是昂贵的,并且应该避免。在这篇文件里,我将大体描绘出一个可以帮助最小状态变化的渲染架构。我将使用 OpenGL 术语,当然,原理上可以应用于任何图形 API

 

我先从定义几个贯穿文章中的术语开始。然后我将会谈论我们如何描述状态变化——毕竟假如我们不能描述他们,我们就不能管理他们。接下来,我将讨论我们怎样能建立一个基于渲染状态的批量多边形渲染器,这样可以避免冗余的状态设置。最后,我将提供几个额外的优化技巧。

术语

你已经知道状态变化(State Change)的定义了,或者至少我希望这样。我们将任何影响Fragment 渲染的 OpenGL函数调用定义为一个状态变化。这其中包括纹理、材质、光照或者混合变化。纹理时目前最昂贵的状态变化,所以普通的优化是用纹理对多边形排序。这意味着你不必调用多余的 glBindTexture(),这样可以极大地提高性能。

 

对你的应用程序来说只是避免纹理变化也许还不够,如果你大量的使用 OpenGL 的光照,你会希望更避免多余的材质变化。我们将会使用更通用的方法来寻找一个能避免所有多余的状态改变的系统。

 

我们假设场景由几个对象组成,或者网格(meshes)。网格只是多边形的几何。这些多边形在逻辑上不一定聚在一起。他们的共同之处仅仅是他们有着把他们关联在一起的相同渲染状态设置的集合。我们称这样的状态设置集合为 shader —— 也许这种定义让每个人想起了 Quake 3 Arena 的术语。一个shader 可以包含下列信息:

 

两个纹理,以及如何将他们混合在一起的信息。我们假设我们希望使用笨重的多纹理(multitexturing)。
材质参数。这包括 ambient, diffuse, specular emissive 颜色,还有shininess .
混合函数。

 

当然,我们可以在 shader 里加入任何我们想要的东西。实际上这依赖于我们要改变哪一个状态设置。例如如果我们希望支持环境贴图,我们必须在 shader 中加入必要的纹理生成 (texgen )参数。

 

实际上为避免状态改变, 我们需要根据meshshader进行排序。为了找到一个好的shader排序方法,我们会将shader插入到shader tree中。树的顶部描述最昂贵的状态变化,反之叶子则描述最便宜的状态变化,让我们通过更多的细节来观察它是如何运作的。

• Shaders

我们将插入一个状态变化到树中,所以一个 Shader 在遍历这个数的时候会被重构。我们可以说我们有如下Shaders,每个Shader由两个纹理,一个材质和一个混合模式组成。:

Shader 1
• BrickTexture
• DetailTexture1
• RedMaterial
• OpaqueBlending

Shader 2
• BrickTexture
• DetailTexture1
• GrayMaterial
• OpaqueBlending

Shader 3
• BrickTexture
• DetailTexture2
• RedMaterial
• OpaqueBlending

Shader 4
• FXTexture
• No texture
• ChromeMaterial
• AdditiveBlending

这就形成了这样的 shader tree:

 http://www.delphi3d.net/articles/shadertree.gif

注意每个树中的节点可以拥有任意数量的孩子。思路是将最昂贵的状态变化放置在层次的顶级,所以这种变化会很少发生。在这个实例中,我们认为纹理变化是最应该避免的。

可是这意味着一些状态将不得不重复设置。在这个例子中,我们通知系统切换 Alpha Blending 状态3 次。我们接受这个事实,因为移动混合模式到树的更高级别会增加一定数量(更昂贵的)的材质变化。我们稍后会看到这实际上不是什么问题,因为我们仍然能避免多余的状态变化。

首先,让我们来看另一个shader 。最后的shader只有一个纹理贴图,因此它没有使用多纹理贴图。为了让我们的系统运转,这个信息(例如:没有第二个纹理)也必须被记录在shader tree中。一般来讲,如果一个 shader 缺少任何所支持的状态值,我们将给它设置一个缺省值。

如果我们有适当的文件格式和我们头脑中能清晰地描述状态变化,建立shader tree是一个简单的过程。我建议创建一个抽象基类来描述状态变化(任何状态变化),并且派生类来描述纹理、材质或者混合函数变化(或者其他的应用的需求)。

渲染

既然我们有了一个shader tree,我们就可以用最少数量的状态变化来渲染场景。我们通过一个分支一个分支的遍历shader tree来做到这一点。在每个节点,我们设置存储在节点上的状态。树中的每个叶子关联到一个shader,因此当我们到达叶子时,就可以用这个shader渲染所有的mesh

在介绍中,我规定每个mesh都关联一个shader,但是这里我们需要颠倒这个关系。我们应该建立一个所有shader的列表,每个shader存储一个使用到它的mesh的列表。作为一个与shader tree一起构造的预处理步骤, 这点极易实现。

这个渲染方案意味着所有的mesh一定是通过一个通用的接口来进行渲染。最简单的方法是为可渲染(renderable)的对象创建一个抽象基类。我们可以派生子类使用 glBegin()/glEnd()绘制多边形,通过渲染一个顶点数组,或者通过调用一个显示列表。

进一步的改进

我们仍然有几个可以做的事情来改进状态管理。对于初学者,还记得我们是如何被迫多次插入某个状态变化到shader tree中吗?在我们的例子中,3个连续的shader共享同样的混合设置。这导致多余的状态改变,但是这很容易避免。我们能做的是维护一个当前状态的列表。这个列表包含了指向当前在 effect 中状态设置的指针。在shader tree中的每个节点,我们检查当前的状态,看我们是否重复设置了这个状态改变。如果是,忽略,否则将用这个变化来更新当前的状态。

我们也实现一个通用的渲染mesh的接口。从我们的角度来说,这是很优雅并且灵活的,因为这不会将我们束缚在单一描述mesh的内存上。但是在 OpenGL的角度来说,这个方法有点。为了更大发挥3D显卡的功效,我们应该通过最优化的驱动路径渲染所有的mesh。对于大多数驱动,应该使用 glDrawElements合并顶点数组。

如果我们能用顶点索引数组描述所有的mesh,状态管理能带来额外的一些优化。例如,在 GeForce 显卡上,状态管理在AGP内存上分配一个顶点缓冲区并且把mesh放在其中。这意味着我们可以大批量的渲染多边形,这是很好的事情。这也能改善了CPU3D显卡的并行性能。如果频繁的在mesh中复用顶点,状态管理也能在mesh开始渲染之前锁住顶点数组。

结论

开始从状态变化是有害的事实,我们已经差不多开发出了一个能尽量避免变化的机制。这应该在任何一个非一般要求的应用中导致速度提升。我们讨论的系统是很普遍的,并且能用于管理任何和所有你的应用依赖的状态设置。此外,这个系统也把实际的几何体传输过程变得简单了,因为所有的顶点在被传输到3D显卡上时必须通过一个单一的接口传输。

然而仍然有几个我们没有涵盖到的问题:

假定有一些透明对象并且需要对他们进行深度排序?
假定需要多遍渲染?
当我们依赖 OpenGL 扩展时,但用户的3D显卡并不支持会发生什么?

所有这些都能被解决,就看我们如何更聪明的组织shader treemesh,所以我希望给读者留下这个练习。如果你想讨论这个话题,不要犹豫给我写信吧!

5月17日

OGRE 120 Release 场景管理部分变化说明。

增加 MoveableObjectFactory 接口及相应 对象工厂的实现,包括 EntityFactoryLightFactory BillboardSetFactory ManualObjectFactory BillboardChainFactory RibbonTrailFactory。原来由 SceneManager 各个 MoveableObject 的创建实现细节交给各自的工厂对象,把细节屏蔽起来,使 SceneManager减少了依赖,职责更加清晰,接口独立。

SceneManager 中增加 mMovableObjectCollectionMap 成员,将实现 MoveableObject 的对象按照对象类型保存在不同的列表中,提高了检索对象的效率。

3月9日

今天解决了两个不是问题的问题

一早上就兴冲冲的把自己在挤公车时想出来的关于 3dmax 导出材质的想法跟老 Z 说了,并看了一上午的 NDL 源代码,想找出它是如何实现多级材质与 Face 对应的,结果倒是没找到,把 NDL 的渲染过程和渲染状态设置部分看了个差不多,中午的时候和老 Z 又讨论了一下,下午反复试了几次,结果发现原本我以为引擎的导出器不支持多级材质居然能正确绘制出来!我突然意识到 从 INode 获取的 Mtl 并不是3dmax 的根材质,而是这个 node 所使用的材质,比如材质 a 是 root 并且是 multi 的,b 是 a 的子材质 并且也是 multi 的,c 是 b 的子材质且是 std,那么当 mesh 使用的是 b 的时候,通过 INode 拿到的就是 b,当使用 c 的时候得到的就是 c,所以虽然通过 Face 的 getMatID 是相对的材质 ID,但也能得到正确的材质信息,而我原以为 INode 得到的是 root 材质,通过相对的 mtlID 是无法获得正确的材质信息,是自己想的复杂了,导出材质的本来就是正确的,不明白为什么 3dmax 的 sdk 里不说明这点。第二个问题是在导出 3dmax 中自带的 国际象棋 的马的时候,总是法线出现问题,出现不正确的光照效果,以为自己的导出方式有问题,仔细看看,觉得也没有问题,百思不得其解。在跟老 Z 讨论的过程中突然间想到,如果模型经过缩放,顶点法线是要重新归一化的,而 d3d 的固定管线的 T&L 默认不会重新计算顶点法线,想到这里,设置 NormalizeNormals 渲染状态为 true ,运行,OK!本来这两个问题都不是问题,却搞了我一天。

《完美世界》图形系统分析

国内3D大作,听说很不错,前几天下了个客户端,可玩性这里不谈,今天主要谈谈3D画面。第一次进入感觉很卡,我的6600gt效果全开在主城里跑起来都比较吃力,试着调节游戏里的图形选项,发现影响最大的是水面特效和视野范围,估计水面的shader代码不是很优化,水面的效果我也看了一下,似乎和ATI的Rendermonkey里的水面特效差不多。影子居然是每个人都有,其实只要玩家自己有影子或者一定范围内的物体有影子就够了。全屏blur效果不是很好,尤其在画面运动的时候有些闪烁,估计为了性能的考虑只实现了2pass的模糊。动画采用了lod技术,根据距离来更新物体的动画,越近越连续。不过远看还以为是美术偷懒制作了几祯动画而已。太阳的光晕效果非常不错,还有遮挡判断,而且当被遮挡之后是淡出淡入,最开始以为使用了hdr,后来想不过是借用了alpha混合技术再加上全屏柔化,不过整个的效果不错。场景中的静态物体使用了lod,不过选项中的树木的细节调节似乎没有用处,而且地表纹理细节不够,可能也是为了性能考虑。水面岸边的波浪效果可能是在生成地形时,采样同水面相交的顶点,生成岸边轮廓,然后每两点放置一个与水面在同一平面的并以这两个轮廓点为边的四边形,然后在这个面片上进行纹理动画,可以多放置几个面片,播放纹理动画时产生不同时序,这样看起来更真实。场景管理似乎采用的SpeedTree,因为我在其目录下发现一个名为SpeedTreeRt.dll,不清楚为什么不使用一些室外场景管理算法,比如:四叉树、八叉树。总体来说作为国内自主研发的3D网游是很非常不错的,但感觉还有优化的空间,希望《完美世界》越来越完善。
2月27日

OGRE内部的渲染流程(三)

好久没更新这个系列了,今天在同同事的讨论中说到了渲染对象排序的问题,正好自己的引擎也快做到这部分了,晚上回家看了一下 Ogre 的代码,整体如下:
先贴上一张 RenderQueue 的类和数据类型关系图:
 (怎么贴不上图???参看照片中的“Ogre渲染队列类图”吧。)
数据类型说明:
1. RenderableList:
   原型:typedef std::vector<Renderable*> RenderableList;
   描述:可渲染对象列表。
2. TransparentRenderablePassList:
   原型: typedef std::vector<RenderablePass> TransparentRenderablePassList;
   描述:透明的渲染对象列表。
3. RenderableSolidPassMap:
   原型: typedef std::map<Pass*, RenderableList*, SolidQueueItemLess> SolidRenderablePassMap;
   描述:以Pass分组的非透明可渲染对象列表,每个Pass对应一个到多个可渲染对象,也就是说将相同渲染状态的对象作为一组进行渲染,减少渲染管线的切换操作,提高渲染性能。
4. RenderPriorityGroupMap:
   原型: typedef std::map<ushort, RenderPriorityGroup*, std::less<ushort> > PriorityMap;
   描述:以优先级分组的可渲染对象队列的列表,以优先级作为列表中每一项的关键字。
5. RenderQueueGroupMap:
   原型: typedef std::map< RenderQueueGroupID, RenderQueueGroup* > RenderQueueGroupMap;
   描述:以渲染分组ID分组的渲染队列的列表,以渲染分组ID作为列表中每一项的关键字。所谓渲染分组标示(RenderQueueGroupID)就是标示粗粒度分组,比如2D背景一定会先渲染,天空会次之,2DUI可能会最后渲染,Ogre中已经定义好了一系列的粗粒度分组号,具体参考枚举类型RenderQueueGroupID的定义说明,大部分渲染对象使用的是RENDER_QUEUE_MAIN,这也是缺省的渲染粒度。
对象说明:
1. Pass:
   描述:从顶点到像素的一次生成过程,包含渲染状态的设置参数,比如光照参数、纹理阶段设置、Alpha 混合操作、深度缓冲、雾、裁减方式、填充模式等等,也包含了可编程管线PS\VS的相关设置。
2. Renderable:
   描述:请参考我的《关于 OGRE 中的 Renderable 》的文章。
3. RenderQueue:
   描述:定义了渲染队列,此队列中包含了按照渲染分组ID排序的可渲染对象列表,也就是上面的 RenderQueueGroupMap 类型。这里会进行最粗粒度的排序,按照2D背景、天空、场景对象、2DUI等进行排序。SceneManager 中包含此对象。
4. RenderQueueGroup:
   描述:渲染队列组,按照渲染对象的渲染优先级别排列的渲染对象列表,此类中拥有一个上面的 RenderPriorityGroupMap 类型成员。
5. RenderPriorityGroup:
   描述:渲染优先级组,包含的所有的渲染对象都具有相同的优先级。分出透明对象和非透明对象及其它渲染方式对象,使渲染状态切换最小。其中的渲染对象烈表是以 Pass 分组的,对于半透明物体,每个对象对应一个 Pass。
渲染流程:
每桢更新渲染队列,首先清除 SceneManager 中的 RenderQueue,然后在具体的场景管理器(BSP\OCTREE)中剔除不可见物体,将可见物体按照最粗粒度的划分加入到 RenderQueue 中,RenderQueue 将对象传递到 RenderQueueGroup 中按照并按照指定的优先级加入到 RenderPriorityGroup 中的 SolidRenderablePassMap(非透明) 或者 TransparentRenderablePassList (透明)中,在 SolidRenderablePassMap 中还要按照渲染状态排序,也就是 Pass 排序,将相同 Pass 的对象保存在一个列表中。这样,更新渲染队列的工作就完成了,在渲染时按照已经排好的顺序进行绘制就可以了。

探求架构的奥秘 拆解“管线”看现代显卡(转贴)

小熊在线——geese  北京 2006年02月08日18:17


第1页:游戏是如何制作的?显卡又如何发展?读懂梦幻般的3D世界

引言...

公元1998年3月 nVIDIA RIVA TNT推出 采用0.35微米工艺 共计7百万晶体管

公元2002年7月 ATI 9700PRO推出 采用0.15微米工艺 共计1亿7百万晶体管

公元2006年1月 ATI X1900XTX推出 采用0.09微米工艺 共计3亿八千万晶体管

......

电脑芯片技术,以摩尔定律高速发展着,并有超越之势。晶体管的高速高度集成带来了更高的频率,更先进新技术。这些都给我们带来了全新的体验。广大的硬件玩家们也喜欢学习并读懂这些东西,大都是兴趣决定了这些,同时也可以让自己做到买的清楚,用的明白。

WINFAST TNT II

RADEON 9700PRO

RADEON X1900XTX

一个3D游戏期开发:

用电脑的朋友绝大多数喜欢游戏,笔者的一位朋友在游戏公司工作,另笔者了解了下游戏初期是如何开发的:好象电影一样,一个游戏公司会有游戏的策划部,都是一些创作剧本的人,用文字来表达游戏世界的剧情和角色等等。剧本好了就发给原画设计部,原画设计人员就开始根据剧本里对角色,怪物和世界里的城堡树木等物体的文字描述来进行原画创作。当然他们也会和剧本的编写者进行沟通,辩论,据说大多都是在吵架,呵呵!人物都画好了,就该把这些原画给3D建模部门了。开始构造画纸上的3D世界,3D建模,贴纹理,后期混合等等...用专业图形学来描述呢?就是构造顶点、几何变换(Transform)、光照(Lighting) 、设定(Setup) 、光栅化(Rasterize)...

FF10 硫库角色原画

FF10 硫库的3D角色

读懂显卡评测

游戏是令人兴奋的,在由始至终,显卡在这里扮演了主角,它造就梦幻般的3D世界。但是说到显卡,不得不涉及复杂的图形学,深奥的显卡架构,这些东西往往另读者一头雾水,别说普通读者,就是玩家和小编我们又有几个能读懂呢?这篇文章借解析ATI最期的高端显卡R580和以往显卡的架构对比,给读者灌输全新的显卡架构理念,既然是“讲”就要讲得明白,令读者读懂。解析R580为次,弄清现代显卡架构才是重点。以便读者以后能够更好的读懂显卡评测,甚至是官方的PDF。这里为了让读者看懂,本文描述了很多图形学的基础。

 

第2页:是各个时期的概念变化?还一时平衡的产物?完全拆解显卡中的“渲染管线”

X1900的到来,只字不提渲染管线

北京时间2006年1月24日晚22点,X1900XTX在全球正式发布,其评测文章也如雨后春笋般大量涌出。

ATI在X1900XTX的介绍中,几乎没有任何地方提及之前NV40和R520所宣传的“渲染管线”或是“渲染流水线”,而是强调“像素渲染单元”增加了三倍。达到了48个,那么渲染单元是什么呢?ALU? 渲染管线呢,还是16条?要弄清这些,首先我们需要放弃渲染管线的说法,与其说放弃不如说是拆解,原因是这个“管线”已经开始限制了我们的一些理解。

形成体系架构观:

解惑,首先我们需要在大脑中形成一个完整的显卡的内部结构框架,尤其是处理顶点之后进入像素那部分的结构框架。

R580 体系架构图

ATI给放出的R580显卡架构图,主要分为Vertex Shader Engine和Pixel Shader Engine两部分外,还有从X800开始宣传的Setup Engine。而nVIDIA放出的架构图除了上边的VS和PS之外,还引入了ROP部分,这个概念是从NV40开始宣传的。

最早的所谓"渲染管线":

记得GeForce4 TI4200吗?那时候多是这样宣传:TI4200具有2个顶点单元;4个渲染管线,每个渲染管线搭配两个贴图单元。RADEON9700呢? 多是这样宣传:9700具有4个顶点单元;8个渲染管线,每个渲染管线仅有个1贴图单元。

公式暂时这么写:  GF TI4200=2VS+4PS*2TMU

                RADEON9700=4VS+8PS*1TMU

                RADEON9000=1VS+4PS*1TMU

抛开顶点部分(Vertex Shader Engine),那个时候的PS部分是像素单元Pixel Shader Unit和纹理单元TMU是分开宣传的。而进入R9800时期一直到NV40、R520之间,就没有人更多的议论TMU部分了,为什么呢?看完了这段答案自然揭晓。我们记得这个时期对于6800Ultra的宣传是具有6个顶点单元;16条渲染管线;16个光栅化引擎ROP。

公式暂时这么认为: 6800U=6VS+16PS+16ROP

1:1:1的比例形成:

此时的“渲染管线”这个名词正式被厂商炒作。而上面公式中的16PS也就是前面的16PSU*1TMU,即16PSU;16TMU

下面进入重点,什么叫一条“像素渲染管线”?简单点说,传统的一条渲染管线是由包括Pixel Shader Unit(像素着色单元)+ TMU(纹理单元) + ROP(光栅化引擎)三部分组成的。

公式如此表达写:PS=PSU+TMU+ROP

从功能上看,PSU完成像素处理,TMU负责纹理渲染,而ROP则负责像素的最终输出...因此,一条完整的像素管线意味着在一个时钟周期完成至少进行1个PS运算,并输出一次纹理。

现在我们需要做的就是拆开像素渲染管线来看,那么NV40和R520的PS:TMU:ROP是标准的1:1:1的关系。或许就是这1:1:1的关系形成了传统的像素渲染管线的概念。

 

注释:

1,现在我们把PS定为Pixel Shader Engine的简写即像素渲染管线;PSU则是Pixel Shader Unit或者叫Pixel Shader Processor即管线中的像素处理器单元

2,至于为什么在TI4200,R8500这两款产品会使用PS:TMU=1:2的关系,更多是出于DirectX8.0的Pixel Shader1.0/1.1 API的帧路径并不足够的灵活,对于当时的游戏和显卡而言,更多的贴图单元,弥补了在渲染中帧路径上的缺陷。而DirectX9.0的Pixel Shader2.0很大程度的解决了这个问题。所以更多的贴图单元也就没有那么必要了。由于具体涉及了复杂的图形学和DirectX知识,所以只讨论到这里。

3,上面的PS也就是Pixel Shader Processor和TMU仍可再分,在体系架构图中也有明确的标识。按照用途我们把他们看成一个单位,即像素处理单元和纹理单元。

第3页:渲染管线的定义在改变——7800并不是传统的24条流水线!

G70,GeForce7800的24条像素渲染管线并不是传统意义的24条渲染管线。我们先来看看G70的体系架构图:

G70的体系架构 图片来源GZeasy

R580的Pixel Shader Engine部分体系架构

Quad:

从NV40开始就引入了Quad的概念,意思四方的院子,可以理解为四个组成一组。ATI称为Quad Pixel Shader Core每个包含4个完全相同的Pixel Shader Processor。而R520和G70也是大同小异。就目前来看,在今后的一段时期内这种Quad像素处理器的结构显然还会继续流行下去。

传统的管线架构比例被打破

我们在G70的架构图中不难看出,其架构为6组Pixel Shader Quad,共包含了6*4=24个像素处理器。也就是24PSU;24TMU;16ROP,这样1:1:1的比例又被打破了,变成了3:3:2。

(为什么是“又”被打破?忘了刚才的TI4200啦?!)

如果按照传统的像素管线来看,这不能称为24条管线。但是如果放弃ROP部分,仅考虑PS和TMU的话,就成为了nVIDIA所述的24条管线。实际上很多事情和说法都不是呈文的规定,谁说谁有理。比如现在nVIDIA就把24PS和24TMU合称为自己的24条渲染管线。

按照这个方式写两个公式,其中画线的部分也就是它们所宣传的部分:

                      G70=8VS+24PSU+24TMU+16ROP

                     R580=8VS+48PSU+16TMU+16ROP

                 推测 G71=8VS+32PSU+32TMU+16ROP

为何不提高ROP的数量?

恐怕有读者会问为何不线性的把ROP部分也提高到24ROP呢?这样7800就成为了毫无争议的24管。nVIDIA当然想这么做!ROP即为像素输出部分,其数量要看显存控制器的规模取向。目前4×64的话就是16ROP,显存位宽是256bit,而24ROP,就是6×64,384bit位宽。32ROP就要达到512bit的显存位宽。这对目前来说还是比较困难的。所以目前,nVIDIA和ATI都相持在了16ROP。

GF7800是真正的24条渲染管线吗?至少,这个渲染管线的定义从再次变得模糊。

现在我们应该明白为什么nVIDIA宣传自己的G70会用“多少条像素渲染管线”,而ATI的则不再以“管”来做宣传,而是48个像素处理单元了巴?!

注释:

什么是ROP(光栅化引擎)?也可以称为像素结果输出处理器 负责像素的最终输出,是像素子系统,执行像素读/写操作、z-buffer检查、色彩混合、抗锯齿操作等。

第4页:现在的显卡迫切的发展方向在何处?ATI和nVIDIA已不谋而合

说道显卡的发展方向,我们先来粗略的回顾一下3D的历史,3D加速是从何而来,它又是如何发展的?

PS SS 早期的多边形3D时代:

1994年12月上市的SONY PlayStation 以每秒38万个三角形生成能力,号称当时最强的3D游戏平台。

3D物体是由一个一个的点构成的,人们称之为顶点Vertex。大家都知道三个点确定一个平面,无数个平面就可以组成一个复杂的曲面物体了。所以最早的显卡3D能力强大与否,是由三角形的生成能力表示的。记得1995~1997年游戏杂志里常说的PLOYGON吗?是的!早期显卡3D能力的强弱,多是看三角形的生成能力。

  

1994年何SEGA 土星游戏主机同步发售的3D格斗游戏 《VR战士》

说道3D加速的三角形,需要简单引入设定Setup和光栅化Rasterize两个概念,为后文铺垫。简单的说Setup决定三角形三个点的位置,然后通过光栅化Rasterize来描绘出这个三角形以及填充三角形内部各个像素的颜色,使它变成一个有色的三角形。Setup和Rasterize也是光栅化图形学的两个重要步骤。

《DOOM3》现代的纹理,材质时代:

多边形就一个固定的颜色,光秃秃很不好看,《VR战士》就是个不错的例子。为了让这些3D人物更好看,人们觉得给这些多边形上贴上图片,来体现更多的细节,这是个好主意,这样纹理的概念出现了,纹理也就是贴在多边形上的图片。

之后,这纹理的学问就越来越复杂,这么多图片,信息的寄存问题;怎么对应的贴在相应的多边形上?如何和屏幕上的颜色混合起来?从不同的角度看,图片变形而导致的不真实问题怎么处理?等等 等等,问题虽然一大筐,但我们的3D世界确变得越来越漂亮。是的,纹理真是个麻烦的好东西!

把3D游戏引入大材质时代的代表作《DOOM3》

上图可以很容易的看出,纹理和材质所带来的视觉效果是翻天复地的。渐渐的,当图形卡的顶点处理能力到达一定程度的时候,业界一致认为以像素和纹理的重要性要超越顶点了。从这个时候,nVIDIA和ATI也早已开始对自己GPU在Pixel Shader Engine方面进行重点加强。一直以来,他们的宣传也都是以PSE为重心的。

 

“超”线性增长的像素处理

上面得知,光栅化图形学是以运算为基础的,而处理的单位就是像素。光栅化的过程如果看作是n个相关像素运算,那么每个像素会在现今的游戏中会不止一次的被计算吗?答案是肯定的,举个常用的例子:在游戏中像素被光栅化并输出,但如果打开AA,那么会有像素要被重新采样,计算,设定。那么这个像素在一个周期里就被几次运算了,但只做了一次贴图。在如今各式各样的Shader运算上,ATI认为像素处理的重要性已经高过了TMU部分,它们可以不在1:1的线性增长了,于是R580诞生了......

实际上nVIDIA,的G70 24PSU+24TMU和即将发布的G71 32PSU+32TMU来看,nVIDIA同样把重点放在了PSU和TMU部分,显然,nVIDIA认为线性的增加他们两者的比例也很重要。放开这不同所带来的争议,增强PSU的数量,也就是增强像素处理器的运算能力已经是nVIDIA和ATI所达成的共识。

不同的仅是以1:1的比例线性的增强的PSU和TMU,还是在保持TMU不变的情况下,成倍的增加像素处理器的数量。但不可否认,PSU的作用已经超过了TMU。

看一些ATI PDF中发布的研究结果:

根据ATI的研究,在2001年刚刚出现具备像素着色器的显示卡时,当时游戏的像素着色器程序中算术指令和纹理指令数量的比例在1:1左右,之后,算术指令的数量呈显著增加之势。在2005年,游戏中每个像素的平均着色器程序指令数目是30条,算术指令和纹理指令数量平均比率达到了5:1,也就是说现在的像素着色器程序中,平均每5条算术指令才会出现一条贴图指令,而这样的算术指令数量急剧增长趋势仍将继续保持下去。

 

注释:

材质:经过光照,混合屏幕上原有的颜色,等一系列处理的纹理

早期的纹理尺寸仅有128×128像素或更小,而在《DOOM3》为例的游戏中,纹理逐渐发展到了1024×1024甚至更大


第5页:深入探究,再度挖掘X1900相对于X1800的架构改进

如果觉得R580 PS=48PSU+16TMU+16ROP概念还是有点模糊,那么带着上文观点,再来看看R580和R520的架构图,这里仅论述PSU和TMU部分,ROP暂不讨论。

R520和R580的架构:

R520

现在规类,R520 PSU部分包含Ultra-Threading Dispatch Processor(极多线程分发处理器)和4个Quad Pixel Shader Core;TMU部分相关包括有Texture address Units和Texture Units。其中4个Quad Pixel Shader Core共计16个Pixel Shader Unit。再来看R580:

R580

ATI的R580架构图细化了很多,我们看见的是成大量增长的Quad Pixel Shader Core,达到了12个,共计48个Pixel Shader Unit。随之还线性增加了General Purpose Register Arrays,可以看作是Pixel Shader Unit计算之后的寄存器队列。而TMU部分部分显然没有什么变化。

R580 48个PSU即96个ALU

经过ATI给予R520和R580 PDF的对比,他们的Pixel Shader Unit没有什么架构上的变化,每个PSU包含ALU1和ALU2两个数学运算器和一个分支执行单元。两个ALU分别用于加法,乘法和承加等运算,分别处理不同的向量和标量。其中全功能的成为Main ALU,而只能运行加法的称为Mini ALU。既然一个PSU包含两个ALU,那么R850的PS部分,一共有96个ALU!而并非笔者早先的48ALU的错误观点。

现在您应该已经经脉打通,了解这第7代,不,第8代?图形卡是怎么一回事了巴?!

注释:

Ultra-Threading Dispatch Processor(极多线程分发处理器):在功能上这些处理器组和内部ALU都由ATI X1000系列刚刚引入的Ultra-Threading Dispatch Processor来进行任务分配,他会智能寻找时机重新分配任务给不同的ALUs。以保证每个ALUs都能满负荷的进行工作,并提高效率。

第6页:简述本文的四个观点,不同的发展方向由未来的3D游戏来检验

最后用几句话来论述本文的观点:

1,渲染流水线或者渲染管线定义从来都是模糊的,不同时期的它们都不曾相同,或许只是厂商的宣传手段,为了让我们更清楚的理解显卡架构,现在是应该放弃这个“管”的时候了。

2,ATI推出的X1900系列显卡,3倍增强了目前显卡中VS/PSU/TMU/ROP四大架构中最急需增强的像素处理器PSU部分,打破了传统比例,是对未来游戏的展望。改进也是革命性的。

3,现阶段,Ultra-Threading Dispatch Processor;48个PSU带来的超前像素运算能力;16FP HDR+AA ;AVIVO VIDEO ;Hierarchical Z的改进和Fetch-4等相对的技术优势,给予了X1900XTX高效而强大的王者地位。

4,G71的32PSU 32TMU和R580的48PSU 16TMU诠释了线性和非线性的PS架构发展方向,到底谁主沉浮,还需要用未来的游戏来检验!

 

2月25日

关于 quake3 bsp 中的 brush 的概念

在看 quake3 的代码时一直对这个概念感到迷惑,不知道代表什么意思,也曾经问过几个人,可惜没有回答,昨晚在翻 bbs.chinagamedev.net 的老贴时发现有人问过了这个问题,并且得到了回答,实际上可以把它看成墙的可视面的集合,我的引擎中把它成为 cluster,意思差不多。困扰了我一个多月的概念问题总算有了结果,在寻找答案的过程中发现这方面的资料实在是少之又少,国外的开发论坛会好很多。

细节

我的 Dream 引擎中室内的场景管理采用 BSP 算法,所以要完整地实现 BSP ,包括 BSP 的构造、存储、绘制以及在这之上的 Portal管理,春节之后上班的我第一天完成了 BSP 的核心算法,原计划这周之内用引擎写一个 BSP 的演示程序,但是到今天还没有完全写完,主要有许多细节问题没有考虑到,比如 BSP 的数据结构的调整,包括和多边形以及多变形顶点的对应关系,从自定义的 LYM 模型文件中读取BSP MESH 数据并进行 BSP 的优化调整,BSP文件的存储格式,还要考虑到读取时的效率,当然还要考虑到渲染时的效率,这些都会影响 BSP 的文件存储结构。原以为代码写得差不多了,但今天看来还是有许多细节需要完善,这样我感到构建一个引擎的是很不容易的,构建一个好引擎就更难了,但是我不会好高骛远,要脚踏实地一步一步的实现、优化,每个细节都要做好,因为有句话说得很对:细节决定成败。
2月6日

过度的开发技巧

在 quake3 和 halflife2 的代码中发现如下代码:
 
 bspbrush_t *bb;
 int   c;
 c = (int)&(((bspbrush_t *)0)->sides[numsides]);
 bb = (bspbrush_t*)malloc(c);

 
这段代码的作用是分配内存,刚开始我看不出这样做有什么好处,后来仔细看了一下函数声明:
 
bspbrush_t *AllocBrush (int numsides)
 
使用这种方式就可以分配可变大小 bspbrush_t 的内存,sides 成员是另外一个结构体的数组,这样可以不用分配无用的内存,按需分配内存,这种分配内存方式还是头一次遇见。
1月11日

浅析 Quake3 渲染流程

为了研究 portal 渲染技术,下载了 quake3 的源代码,上午看了一下 Renderer 的渲染流程,在这里简单描述一下。

说明:   1.主要流程都集中在 Renderer 工程中。

         2.按照调用顺序说明,这里只是给出关键的函数调用。

         3.只是描述了一般渲染流程,对于其他渲染方式没有描述。

         4.只对表面渲染流程进行了说明。

         5.不包括纹理设置、纹理坐标、雾、光照等设置的说明。

一、  函数调用流程

调用顺序

函数名称

函数说明

所在源文件

1

RE_EndFrame

执行当前祯的渲染

Tr_cmds.c

2

R_IssueRenderCommands

触发渲染命令

Tr_cmds.c

3

RB_ExecuteRenderCommands

执行渲染命令

Tr_backEnd.c

4

RB_DrawSurfs

渲染表面

Tr_backEnd.c

5

RB_RenderDrawSurfList

对待渲染表面进行排序,按照分类进行渲染

Tr_backEnd.c

6

RB_BeginSurface

初始化当前渲染命令参数

Tr_shade.c

7

RB_SurfaceFace

将当前表面的顶点数据、顶点索引、法线、纹理坐标加入当前渲染命令中

Tr_surface.c

8

RB_EndSurface

根据渲染命令渲染表面

Tr_shade.c

9

RB_StageIteratorGeneric

根据渲染命令参数设置ogl渲染参数,顶点、纹理、颜色等

Tr_shade.c

10

RB_IterateStagesGeneric

根据渲染命令pass进行渲染

Tr_shade.c

11

R_DrawElements

渲染三角形带

Tr_shade.c

 

二、  流程说明:

和大多数3D引擎一样,quake3对待渲染的图元首先进行分类,按照着色器(shader) 索引、渲染实体数量、雾索引、动态光索引排序,需要说明的是,这里将上述几项合并到一个32位整数,这会大大加快图元排序。排序使用二分算法,在msqsort上作了修改。

为了减少绘制次数,quake3在每次渲染前将待渲染图元的顶点数据送入到缓冲区中,并设置相应的顶点索引,由于每个渲染表面的顶点索引是局部索引,所以很容易与其它表面合并为一个索引缓冲区,这样就可以批量绘制同一渲染状态的图元。

以上只是非常简单的说明,具体的代码中要更复杂,但大体流程如此,如有错误,恳请指正。

12月28日

Direct3D 10 预览版简介(一):API 特性

Direct3D 10图形管线呈现了基础架构的改变,基于硬件和软件的重构给下一代游戏和3D多媒体应用提供了强有力的保障,它构建于 windows Vista Display Driver ModelVDDM),这将带来性能的提升和增强的功能以及GPU全部的虚拟内存。

熟悉Direct3D 9的开发人员将会发现在DirectX10中一系列功能增强和性能的提高,这些包括:

1.        处理全部基本图元(带有相邻图元的)的能力,在新的几何渲染阶段(geometry shader stage)放大和压缩数据的能力。

2.        使用流输出阶段(stream out stage)将图形管线产生(pipeline-generated)的顶点数据输出到内存中的能力。

3.        新的对象和范例提供了在 Direct3D 运行时和驱动中进行校验和处理时的最少的 CPU 消耗。

Ø         管线状态组织在5个不变的的状态对象之中,这使管线的配置更快速。

Ø         着色常量(shader constant variables)组织在常量缓冲区中,在提供常量数据之上保证了最小化的带宽占用。

Ø         拥有使用一个几何渲染(geometry shader)完成每图元(pre-primitive)的材质交换和设置的能力。

4.        新的资源类型(包括可索引的着色纹理数组(shader-indexable arrays of textures))和资源格式。

5.        增强的在内存中和一般资源访问的资源的广义性-资源视图(resource views),它能够使在内存中的资源解释为不同的资源类型和呈现方式。

6.        传统的硬件能力的检查在丰富的功能性保证中被移除,为了能保证这一点和其他设计的改进,Direct3D 10 API Direct3D 10 级和之后的的硬件作为目标。

7.        分层的运行时(Layered Runtime)设计,Direct3D 10 API 使用层次来构成的,在核心中开始基本的功能,在外部层次中构建可选功能和开发人员辅助功能(例如debug等)。

8.        完整的HLSL的集成-所有的 Direct3D 10 shaders 都是用HLSL来撰写,并且由 shader common core 来执行。

9.        在渲染目标、纹理、采样器(render targets \ textures \samples)数量上有一定的增加。shader的长度不再有限制。

10.     对下列技术提供完整的支持:

Ø         整形和位操作

Ø         深度缓冲和模版缓冲的回读和在shader中的多采样器资源。

Ø         多重采样的 Alpha-to-Coverage

12月19日

GPU为什么跑得快?(转贴)

计算机3D游戏最基本的一个要求是:能以每秒数十帧的速率,根据当前的三维景物实时生成三维动画。你所看到的图像会随着你视点的变化而即时改变使人产生“身临其境”的感觉。的帧率可以得到更流畅的画面。一般来说30fps就是可以接受的,但是将帧速增加60fps则可以明显提升交互感。这与计算机影视制作不同,电影特效可以通过很多台工作站,花几十个小时绘制出长度仅为几秒的高质量画面,并不强调实时性。

渲染一个复杂的三维场景,需要在一秒内处理几千万个三角形顶点和光栅化几十亿的像素。早期的3D游戏,显卡只是为屏幕上显示像素提供一个缓存所有的图形处理都是由CPU单独完成。图形渲染适合并行处理,擅长于执行串行工作的CPU实际上难以胜任这项任务。所以,那时在PC实时生成的三维图像都很粗糙。不过在某种意义上,当时的图形绘制倒是完全可编程的,只是由CPU来担纲此项重任,速度上实在是达不到要求。

直到1995年,PC机领域第一款GPU (Graphical Processing Unit) 3dfx Voodoo出来以后,游戏的速度、画质才取得了一个飞跃。3dfx Voodoo有两个主要的特征:深度缓冲区(z-buffer)和纹理映射(texture mapping)。z-buffer执行“隐藏面消除”这一工作,这样可以避免渲染不可视的无效像素。利用纹理映射功能则可以十分逼真地表达物体表面细节。1999年,第二代GPU (Nvidia GeForce256,GeForce 2ATI Radeon 7500)包括了图形的几何变换与光照计算功能(T&L)而在此之前T&L都是由CPU完成的,这对CPU来说是很复杂的计算。第二代GPU解决了系统的一个瓶颈,减轻了CPU的负荷,速度明显提高了。但是由于是固定的渲染流水线,缺乏灵活性,束缚了开发人员的创造性。2001年,NIVIDA公司的GeForce 3首先引入了可编程的顶点着色器(Vertex Shader)单元。紧接着在2002年,可编程的像素着色器(Pixel Shader)单元也加入了GPU (见图1)。在绘制时,GPU首先接收CPU以三角形顶点形式的发送的几何数据。然后由可编程的顶点着色器单元进行处理,完成几何变换与顶点属性计算等功能。接着,這些三维空间的三角形由一个固定功能的光栅生成器转换为二维屏幕上的像素。每个像素的最终颜色值都通过运行在像素着色器上的小程序运算而得。目前三维游戏借助于GPU,已经能够实时生成十分细腻、逼真的画面。

1.  GPU体系结构示意图

 


GPU的功能更新很迅速,平均每一年多便有新一代的GPU诞生,运算速度也越来越快。2004,2004年推出的GPU Nvidia GeForce 6800 Ultra可达到峰值40 Gigaflops( 1 GigaFLOPS1秒钟进行10亿次的浮点运算)2005刚发布Nvidia GeForce 7800 GTX更是将峰值提高至令人惊讶的169 Gigaflops。而Intel 3GHz Pentium 4采用SSE指令集也只能达到6 Gigaflops(见图2)。GPU的运算速度如此之快,主要得益于GPU是对图形实时渲染量身定制的,具有两点主要特征:超长流水线与并行计算。

2.  GPUCPU浮点运算速度对比图

流水线技术与工厂里的装配线在原理上类似。如果装配一台汽车需要10个时间单元,将它分成10个流水线阶段,每个阶段分配一个时间单元,那么一条装配线每一个时间单元就可以生产一辆汽车。显然流水线模式的生产在理想状况下要比串行方式快了十倍。从这个例子中,可以看出为了提高流水线的速度,可以将任务划分成更小的单元,这样流水线的级数就增加了。CPU的设计中就使用了流水线原理,奔腾IV就有20级的流水线。但是流水线级数越多,一条指令从开始进入流水线到最后被执行完毕这之间的延迟间隔会相当大。换句话说,当流水线级数过多时,控制台发出一条指令,会经过很长时间才会真正生效,这不适用于快速反应要求很高的场合。打个比方,当用消防水龙头救火时,正常情况下打开阀门开关几秒后水便喷射出来,立刻可用来扑灭火灾。但是如果延迟了十几分钟,才有水流出,即使这时水流速度(吞吐量)还是很快,不过这时火灾就会造成更大的损失了。CPU的设计目标是不仅要有很高的吞吐量,还要求很小的延迟,这是CPU并不采用过多流水线级数的原因之一。另外流水线只有在满载时,才能发挥出最佳效率来。由于CPU执行的代码中有很多分支语句,因此长流水线需要用有效的技术来预测分支,尽量保持流水线在满负荷状态。但是一旦预测分支失败,就会清除流水线中滞留的大量无用指令,同时将新指令流重新注入流水线。但是如果流水线阶段过多的话,充满整个流水线就需要很长的时间,这样使流水线保持满载的机会不多,速度反而下降了。所以权衡利弊,CPU不会使用深度流水线。

但是GPU却采用了几百级的流水线,比如GeForce 3的流水线有800个阶段。是什么原因,GPU的应用为何可以忍受这么大的延迟呢?假设以每秒50帧的速率显示画面,那么只要求每帧在20ms以内生成就行。而GeForce 3的时钟频率是200MHz(每个时钟周期是5ns,5ns * 800 = 4μs < 20ms。所以对GPU来说这一些延迟根本不成问题,而且GPU中执行的Shader程序中,分支语句用的很少(在早期的GPU中,甚至不提供动态的分支语句)。因此,GPU的流水线深度变大后,利大于弊,大大提升了整体性能。GPU的执行速度很快,但是当运行从内存中获取纹理数据这样的指令时(由于内存访问是瓶颈,此操作比较缓慢),整个流水线便出现长时间停顿。在CPU内部,使用多级Cache来提高访问内存的速度。GPU中也使用Cache,不过Cache命中率不高,只用Cache解决不了这个问题。所以,为了保持流水线保持忙碌,GPU的设计者使用了多线程机制(multi-threading),见图3。当像素着色器针对某个像素的线程A遇到存取纹理的指令时,GPU会马上切换到另外一个线程B,对另一个像素进行处理。等到纹理从内存中取回时,可再切换到线程A。但是使用这种方法有一个前提,线程A与线程B没有数据依赖性,也就是说两线程之间无需通讯。如果线程B需要线程A提供某些数据,那么即使切换到线程B,线程B仍是无法运行,流水线还是处于空闲状态。不过幸运的是,图形渲染本质上是一个并行任务。无论是CPU送给GPU的顶点数据,还是GPU光栅生成器产生的像素数据都是互不相关的,可以并行地独立处理。而且顶点数据(xyzw),像素数据(RGBA)一般都用四元数表示,适合于并行计算。在GPU中专门设置了SIMD指令来处理向量,一次可同时处理四路数据。SIMD指令使用起来非常简洁,可以看一个顶点矩阵变换的例子(见图4)。此外,纹理片要么只能读取,要么只能写入,不允许可读可写,从而解决了存贮器访问的读写冲突。GPU这种对内存使用的约束也进一步保证了并行处理的顺利完成。

3. GPU中的多线程机制

 

// c0 - c3 = 几何变换矩阵(world/view/proj matrix

 

dp4 oPos.x, r0, c0      //指令dp4完成两个四元数的点积运算

//oP0s.x=(r0.x*c0.x)+(r0.y*c0.y)+

//       (r0.z*c0.z)+(r0.w*c0.w)

//四条dp4指令就实现了对一个顶点的矩阵变换

dp4 oPos.y, r0, c1   

dp4 oPos.z, r0, c2   

dp4 oPos.w, r0, c3   

 

 

 

 

 

 

 

 

 

4.  GPU中的SIMD指令

 


为了进一步提高并行度,可以增加流水线的条数。在GeForce 6800 Ultra中,有多达16像素着色器流水线, 6组顶点着色器流水线。多条流水线可以在单一控制部件的集中控制下运行,也可以独立运行。在单指令多数据流(SIMD)的结构中,单一控制部件向每条流水线分派指令,同样的指令被所有处理部件同时执行。另外一种控制结构是多指令多数据流(MIMD),每条流水线都能够独立于其他流水线执行不同的程序。GeForce 6800 Ultra顶点着色器流水线使用MIMD方式控制,像素着色器流水线使用SIMD结构。MIMD能比较有效率地执行分支程序,而SIMD体系结构运行条件语句时会造成很低的资源利用率。不过SIMD需要硬件少,这是一个优势。

CPU中大部分晶体管主要用于构建控制电路(分支预测等)和Cache,只有少部分的晶体管来完成实际的运算工作。而GPU的控制相对简单,而且对Cache的需求小,所以大部分晶体管可以组成各类专用电路、多条流水线,使得GPU的计算速度有了突破性的飞跃,拥有了惊人的处理浮点运算的能力现在CPU的技术进步正在慢于摩尔定律,而GPU(视频卡上的图形处理器)的运行速度已超过摩尔定律,每6个月其性能加倍。

虽然GPU最初专门是为图形渲染设计的,但是GPU还可以有效地执行多种通用计算,从线性代数和信号处理到数值仿真等等。有的专家甚至认为GPU将进入计算的主流。过去,学生们在课堂学习计算机体系结构时,听老师讲解各类并行计算机时,仿佛觉得这些东西都是科学家才使用的尖端产品。可又何曾想到,现在并行计算机(GPU) 就在我们的普通PC电脑内,触手可及。

 

主要参考文献:

1.  GPU Gems 2 : Programming Techniques for High-Performance Graphics
and General-Purpose Computation (Gpu Gems): Books by Matt Pharr,Randima Fernando.

2.  The Cg Tutorial: The Definitive Guide to Programmable Real-Time Graphics.

3. Lindholm E, Kilgard MJ, Moreton H. A user-programmable vertex engine. In: Proc. of the SIGGRAPH 2001. Los Angeles, 2001. 149~158.

 

作者:沈璐

EMAILszlongman@hotmail.com

GameRes游戏开发资源网 http://www.gameres.com

11月8日

Ogre内部的渲染流程(二)

具体流程如下:
首先Ogre引擎在启动时会根据配置创建一个合适的场景管理器(SceneManager),SceneManager会自动创建一个根节点对象(SceneNode),有了这个root node 我们就可以在这个 root node下创建场景节点并将3D渲染实体附加到节点上。应用程序可以在任意节点下创建子节点,创建之后将3D渲染实体附加到此节点上。在SceneNode内部,保存着一个MoveableObject列表,由于Entity是从MoveableObject继承过来,所以可以把Entity附着在SceneNode上,同时,Entity内部包含一个SubEntity的列表,而SubEntity是从Renderable继承过来,这样在场景更新时,首先调用SceneNode的 _addToRenderQueue 方法,这个方法内部迭代MoveableObject列表的每一项,调用MoveableObject的_updateRenderQueue抽象方法,每个从MoveableObject继承的类都要实现此方法,比如Entity,在Entity的这个方法内部,迭代SubEntity列表的每一项(SubEnbtity从Renderable继承),将每个SubEntity送入渲染队列中去,这样就完成了渲染队列的更新工作。以上说明只是很粗略的描述,具体还有许多细节没有说明,比如进行可视判断,是否透明物体,是否为骨骼节点等等,但主要的流程是这样的。下一部份我将会说明加入渲染队列之后的内部流程。
11月4日

OGRE内部的渲染流程(一)

先说明OGRE内部重要的几个对象:

1.       RenderSystem,此抽象类定义出 3D 渲染系统的基本功能,同时实现了一些通用的方法。各个不同的渲染平台实现此类,OGRE 内部交互由此抽象类负责,从而实现了渲染平台无关性。应用一般不会与此对象直接进行交互,在OGRE内部的其他重要对象,如SceneManager与它进行交互,整个过程是透明的。

2.       SceneManager,组织场景中的对象并将对象送入渲染系统中渲染。这个类定义了场景管理器的基本功能,应用客户端根据自己需要重新实现场景的组织功能。这个对象掌控了所有的可渲染对象。

3.       RenderTarget , 定义:接受渲染操作结果的画布。此抽象类定义了基本的渲染目标属性和操作的功能。渲染目标可以是窗口、屏幕、或者离屏表面,如:渲染到一张纹理图等等。每个具体的渲染引擎需要实现自己的RenderTarget,如:D3D9RenderWindowD3D9RenderTexture 等。这个抽象类包含了一个到多个的 ViewPort 对象,在渲染时会跌代更新每个ViewPort

4.       ViewPort定义:一块渲染目标区域。
说明:视口是相机和渲染表面的交集,并把这个结果放在整个渲染表面或者表面的一部分。每个视口都有一个相机作为源,一个目标作为目标。一个相机只有一个视口,但一个渲染目标可以有多个视口。视口有 z 序属性,如果渲染目标有多个视口,要对视口进行 z 排序.

5.       Camera,这个就不需要解释了吧,需要注意的是它与ViewPort SceneManager之间的关系。

6.       SceneNode这个类从Node继承过来,除了自身是一个树形结构之外,它还可以附加多个可移动对象(MoveableObject),这样他可以与世界中的所有可移动对象(比如实体对象)关联起来。

7.       Entity定义了一个离散的、基于 mesh 的可移动对象的实例。

OGRE 通常将可渲染对象分为2组,一是在世界中移动的离散的极其相关小对象。一种是大规模的杂乱的通常组成静态场景的对象。

Mesh SubMesh 处理那些在离散的可移动对象中使用的图元的定义。Entity 在世界中是真正的基于这种图元的对象的实例,因此对于一个汽车来说它就是一个独立的网格集合,但是在世界中可以有多个基于相同网格集合的实体,这些实体可以改变网格的外在表现,例如通过改变材质属性(这样你就可以在相同的图元数据上用不同的纹理来拥有不用的小汽车),为了这个目标,因为 Mesh 被分成多个 SubMesh,所以实体类是一个分组的类(与Mesh类很相似)并且相关的独立的个体改变的细节信息被保存在 SubEntity 类中。这是11的关系,Entity SubEntity及其关联的 Mesh SubMesh

实体和子实体都不会被直接的创建出来,用 SceneManager createEntity (传递一个模型的名字)方法来创建他们。

实体是被关联他们的场景节点对象包含在场景中的,使用 attachEntity方法关联,参照 SceneNode 类的说明或取完整的信息。

8.      RenderQueue,定义了渲染队列,此队列中包含了按照材质排序的可渲染对象,这样会使渲染状态的切换最少,它包含了一组RenderQueueGroup对象

9.       RenderQueueGroup,渲染队列组,按照渲染的优先级别排列的渲染对象列表。

10.   RenderPriorityGroup,渲染优先级分组,包含的所有的渲染对象都具有相同的优先级。分出透明对象和非透明对象及其它渲染方式对象,使渲染状态切换最小。

 
10月25日

关于 OGRE 中的 Renderable

要理解OGRE引擎,就要理解其中占很重要位置的 Renderable接口,今天先将一部分我分析的内容贴出来。

以下是 Ogre 的代码中的详细说明:

RenderableOGRE中所有可渲染对象的抽象接口

这个接口抽象出了在渲染管线中的被分组的离散的可渲染对象基本的方法。

此接口的实现类必须是基于单一的材质、单一的世界矩阵(或者是一组通过权重混合的世界矩阵),以及单一的渲染操作。

通过这个说明,应该能明确的是,Renderable 封装了3D世界中被渲染对象的基本属性和数据,这包括:渲染操作,材质属性,光照信息、变换矩阵(四元组)、LOD信息、渲染方式等信息。这些信息在渲染循环中被取出,并应用在图形渲染管线中。其中需要注意的是RenderOperation(渲染操作)对象,它封装了图形硬件的顶点数据和渲染的图元类型,在最终的渲染操作里使用这些数据来进行图元渲染,值得注意的是 RenderOpration 中的保存的顶点信息的类VertexData中是抽象的,它包含了一组硬件顶点缓存数据对象-HardwareVertexBuffer,这个类是从 HardwareBuffer 中继承过来的,这个类包括了 hardware buffer中的抽象操作方法,这里使用了模板方法和抽象工厂等设计模式,每个具体的渲染系统可以实现自己的硬件缓存数据,当然,这当中也一定有HardwareBufferManager,而且具体的渲染系统也要实现这个类,来创建具体的渲染系统相关的数据缓存。

10月23日

Ogre部分类层次结构图

这个图早就画好了,一直都在作修改,改日把详细说明写上去。