管理Shader

现在基本图像引擎已经被启动并且运行了,你暂时可以把注意力放在shader开发。正如上一章提及的,在XNA中一切事物在shader的帮助下被渲染,即便当你使用 SpriteBatch 类或者 SimpleEffect 类模仿固定功能行为,不直接和shader交互的时候。本章遍历从零编写shader的整个过程,然后你学习所有关于vertex buffer、vertex shader 处理过程,以及pixel shader最终如何在屏幕上产生像素。如果你已经是一个shader专家,你很可能想跳过本章或者仅仅快速浏览。但是因为下面几章都依赖于shader的基本知识,所以我想确保所有人在同一水准上。

因为在XNA中shader是如此重要,如果你还没有牢固的shader知识,你的确应该尝试遵循本章的所有步骤。本章的设计方式是为了让你亲自遵循每一个独立步,在本章结尾你将编写你的第一个shader,你应该知道一切是如何从开始到完成的。然后你将为下几章做准备,并且你能容易地编写自己的shader。

在开始下几章炫酷的normal mapping effect或者post screen shader之前,你首先必须学会基础。shader不仅仅用于高质量的特效,也取代非常简单的渲染过程,这些处理曾经被用在固定功能管道的渲染。在学习了一点儿shader和图形卡发展的历史之后,你将经历创建一个简单shader的过程,只要渲染一个带有每像素镜面反射光的简单3D模型,然后把shader导入到你的游戏引擎。你将用来创建shader、甚至在你开始把更多代码放进引擎前测试shader的主要工具,是来自于Nvidia的FX Composer,它是一个快速创建自己的shader的好工具。

Shader概览

固定功能意味着GPU开发者已经安排了一种固定方式来转换顶点(显示卡能够T&L,意思是能transform 和 light vertices)并且以预编程的方式直接在GPU硬件上输出像素。改变这种行为是不可能;你只能enable和disable某种特性。对于早期游戏这样相当好,因为GPU硬件做了大量的工作,保证CPU自由安排其他任务,并且GPU比CPU渲染3D数据快很多,因为GPU正是为了渲染多边形而被深度优化过的。如果你听说过“Direct3D 7.0 capable graphic cards”,这正是它所能做的;早期的显示卡有一个相似的行为,不过在Direct3D 5.0以及之前直接在硬件级的带有执行buffer之类的GPU上编程真的很困难。在1999年左右Nvidia GeForce系列图形卡问世的时候,Direct3D 7.0 简化了这个渲染过程。现在一次性渲染带有多重纹理的多边形成为了可能,使用纹理压缩让显存适应容纳更多的纹理,直接在显存中使用vertex buffer来提高执行效能。

历史

即使是在Direct3D 7.0中取得这些巨大进步之后,游戏们看起来并没有太大的不同,它们只是运行得更快,也许有更多的多边形,并且利用纹理压缩和多重纹理的特效来展现更多的纹理,以及混合它们。真正的革命随着DirectX 8和DirectX 9而到来。DirectX 8最终删除了DirectDraw组件,并且被引入了vertex shader、pixel shaders和许多特效——bump mapping凸凹映射,自定义纹理映射等等……但是它只支持Shader Model 1.0,由于众多的API改变了,在一开始并不对用户很友好,并且编写汇编shaders 不是一件轻松的事。

DirectX 9添加了对HLSL的支持(High Level Shader Language高级着色语言,你将在本章中学习到),伴随着即将到来的图形卡的新特性,比如HDR渲染(high dynamic range)和多渲染目标,它允许像deferred shading延迟明暗处理的technique技法。 DirectX 9非常流行,许多开发者都从OpenGL转移到DirectX,这正是因为OpenGL在一开始不能恰当的支持shader,以及OpenGL 2.0 标准直到被认可走了太长的路。其对于shader的扩展模式也比使用Direct3D的shader 类更为逊色,并且直到花了很长时间OpenGL中才有更好的结构可用。 Direct3D 10也再一次大大领先于OpenGL,支持Shader Model 4.0和geometry shader至今已经有一年多了,OpenGL有大量的追赶工作要做。Direct3D 10目前只能工作在Windows Vista,不过你仍然可以在XP中使用OpenGL的扩展来做某些Shader Model 4.0的开发。但大多数的Windows游戏开发者仅仅为了Direct3D 10,在几年里也将转到Vista。和有些人的信念相反,DirectX 9中不支持Shader Model 4.0,即所谓的“DirectX 9.0L”只是一种在Vista 中支持DirectX 9.0游戏和应用程序的方式。在我看来,对于游戏开发者被如此之多不同的DirectX版本环绕,支持DirectX 9并且退回到可能只支持DirectX 7或8的旧硬件,然后为了Vista中的最新特效,不得不执行Direct3D 10并最终也使运行Windows XP的人有一个DirectX 9.0的选择,并不好。那听起来不像是一个愉快的旅行。对于你幸运的是XNA没有这些问题;这儿只有一个版本可用,Shader Models 1~3 (DirectX8和9),你不用担心任何其它的事。它将会在Windows XP、Vista、甚至在Xbox 360 上运行良好。

有关DirectX历史的有关信息,Windows版本和可用的硬件技术的关系,如图6-1。它显示了DirectX的版本经常以某些方式绑定到Microsoft 最新的操作系统,你能预期在将来也会实现的。DirectX 1 and 2没有真正的被使用。DirectX 3仍然支持保留模式(high level),但是这种方式没有被游戏开发者太多使用,并且在接下来的版本中被轻视。DirectX 5和Windows 98一起出现,并且被更广泛的运用( DirectX 4版本号被跳过)。DirectX几乎每年改进一个版本,直到DirectX 7。它被更广泛的使用,并且图形硬件每年都变得越来越快。和Windows XP一起,DirectX 8被发布,并且第一次引入shader。半年以后,DirectX 9已经可以使用,并且有许多版本。在头几年(2002,2003,2004),Microsoft 用一个新字母命名每个新版本(DirectX 9.0a、DirectX 9.0b等),并且每个版本都在最新的图形硬件上允许一个新shader model的应用;在9.0c之后,他们停止了对版本的命名,不过许多DirectX 9.0c的版本(现在已经有11个了)被发行,经常是每隔几个月。Direct3D10 在2005年末就已经在beta测试中,最终和Windows Vista一起在2007年初发布。支持Shader Model 4.0的图形硬件在2006年末(Nvidia GeForce 8800)已经可以获得了。

 0
图 6-1

Shaders Everywhere in XNA

在前面几章你已经看到,要在XNA中渲染一切,shader非常重要;没有shader在屏幕上输出像素,你甚至不能把一条线放在屏幕上。当然你能对2D界面图形使用一些精灵类,也许你还使用SimpleEffect 类显示一些3D数据,不过我不喜欢SimpleEffect 类,本书中我也根本不会使用它。我认为它太复杂以至于不能使用,并且当你能够编写自己的shader的时候,它也确实没什么益处,编写自定义shader要简单得多,快得多,也灵活得多。使用这些简单特效意味着你必须使用预定义的特性,如果你再次需要某个事物你必须再次开始,总之编写你自己的shader吧。对于本书即将到来的游戏,你将主要使用normal mapping shader来显示3D数据,然后一些炫酷的post screen shader特效来生成最终的屏幕数据。这些shader都不可能仅仅使用SimpleEffect 类。

在你之前使用OpenGL 或者 DirectX ,并且仅仅使用固定功能管道的时候,你可能想知道为什么你把如此之多的特效放进shader,为什么你不能仅仅在屏幕上渲染某些3D数据,并且处理它呢。好的,比起使用,编写是有点儿难,虽然你在DirectX 中可能仅仅渲染.x模型,甚至不知道太多关于纹理,或者GPU内部如何工作,但是如果你从来没学过这些细节,你就不能真正做到重要的修改。学会所有关于shader也许比较难,不过至少你会更加走近图形硬件,你将能够更加深入理解多边形如何被渲染在屏幕上,为什么某些bug会发生,以及如何快速修正它。
本书接下来的两章之后,不再讨论shader,并且你很可能不再会花太多时间在你的游戏的shader上,以后你有重要的一些事要开始。然后你将仅仅使用这些shader,你甚至不需要再次考虑它们。

我想提及的最后一件事是有关shader使用增加的工作量。不但你作为一个程序员必须学习所有关于shader,你很可能要编写大多数shader,除非在你的团队中有一个专门写shader的家伙,而且你的图形艺术家(真希望不再是你)也必须知道关于shader。举个例子,为了实现像次时代游戏(例如,Doom3)那样宏伟的外观特效,如果你的3D数据组成不仅仅是基本几何体,还提供normal map(法线映射)来实现那些特效,使用一些normal mapping shader就很有意义。
不再牵扯下一章的内容了,第七章会深入细节讨论normal mapping shader,你通常需要一个3D对象的高多边形版本(多达几百万个多边形)和一个要渲染的低多边形版本(仅仅几百或者上千个多边形)。如你在图6-2所见,没有应用normal mapping的低多边形对象看上去相当糟糕,不过一旦激活之(看右边),球体看上去突然好得多了。

1

图 6-2

作为一个创建高细节模型的替代品,这些高细节模型通常大量的工作,你也能使用绘图程序来“paint”你自己的normal map ,通过首先画一个高度图,然后把它转换为一个法线映射图。通常这样的映射看上去很糟糕,用这种途径你不可能实现顶级游戏的图象质量,不过一次又一次地你能看到这些以这种方式实现normal map 的游戏节约很多钱。无论如何,你的3D图形艺术师应该意识到所有的新技术,并且在你开始编写你的引擎或者游戏代码之前,应该考虑到content(资源内容)被建立的方式。

范例游戏

如果你想看看shader的表现只需玩任何最近的游戏——它们几乎全都支持shader,即使你不能看到normal mapping 法线映射和post screen effects屏幕显示效果的大量使用,shader也许仍然被用来高效率的渲染3D模型或执行shadow mapping 阴影映射技法。

3D射击游戏像Doom 3、Half-Life 2、Far Cry和更多最近的游戏像Fear、Ghost Recon和Gears of War,展示了真正的顶级的图像,并且大量使用了shader特效。射击游戏从大多数的新图形技术中受益,因为你看见你的周围世界在如此多的细节水平。首先你看见你自己或者至少你的武器,然后你或许在室内,并且看到墙和近距离的周围物体,但是也有一些大的房间甚至是你能看到很远距离的外面世界。这意味着level关卡通常很大,你仍然可以仔细去看你所想看到的,因为你可以移动要你想去的任何地方。显然图形卡,尤其是早期的那些,不能够在显存中存储许多细节,仅仅是映射一张覆盖整个3D level关卡的大纹理,或者场所到处就使用同样的墙的纹理,不是一个真正的好选择。取而代之,游戏开发者们不得不思考许多炫酷的技术来改善视觉质量,通过使用细节映射、法线映射来使玩家认为几何体精细很多,混合进post screen effect(屏幕显示效果)来使整个游戏看起来更真实。

其他游戏比如策略游戏或角色扮演游戏,也能从shader中受益,不过之前的策略或者角色扮演游戏甚至花了更长时间实现shader,这些游戏更专注于游戏进行,如何一次性渲染许多单位、缩小和放大的问题。

总之,你很可能也想在你的游戏中用上炫酷的shader effect,并且如果你去看Rocket Commander的屏幕截图(如图6-3),你会看到一些法线映射,以及post screen shader在工作。没有shader激活(左面的屏幕)的游戏看起来真的很糟糕,并且这些小行星看起来也实在不真实,因为光源是不正确的。请注意这个没有shader的版本,只是提供了一种支持旧图形硬件的选择;游戏被设计为使用shader的。固定功能管道版本很可能创造一个较好的屏幕截图,但是如果光源没有改变,如果你没有任何的post screen effects(屏幕显示效果),在真实的时间里,游戏会看起来甚至更不真实。

2
图 6-3

至于在游戏中使用shader和固定功能管道之间更好的例子,看一看3D游戏中的water effects。水面已经在过去几年里进步很多了。

如果你想看看游戏中更多的范例或者想获得更多的讲解,我建议你看看我的Rocket Commander视频指南,你能www.RocketCommander.com找到的。或许你能在www.XnaRacingGame.com,看我的XNA Racing Game视频指南。那里也有更多的有用的shader资源站点的链接,更多的屏幕截图等等。