1.Deferred Shading介绍
在本文我将展示如何在XNA中使用deferred rendering。首先让我们理解什么是deferred shading,然后学习这个技术的几个步骤,从创建Geometry Buffer一直到管理材质。最后,我们介绍如何创建一个内容管道处理器使用这个技术。在每个步骤中,我会详细解释原理,在后面的章节中,有时我也会回到前面并重写某些代码。你最好理解不同的坐标系,例如世界空间、视空间和屏幕空间,这可以参考creators.xna.com上的Shader Series(http://create.msdn.com/en-US/education/catalog/article/shader_coord)。
实时光照
现今的游戏中一个物体往往要被许多光源照亮,今天这仍是一个代价昂贵的操作,也没有一个完美的解决方案。让我们首先看一下解决这个问题的几种常用方法。
在Single-Pass光照方法中,每个对象对需要被绘制,所有光照运算都在一个shader中进行。可参加creators.xna.com上的示例。但一个shader有指令数量的限制,所以这个技术只适用于光源数量较少的情况(例如,Creator’s Club上的例子Shader Series 4: Materials and Multiple Light Sources(http://create.msdn.com/en-US/education/catalog/sample/shader_series_4)(或本站的译文,注意:这个例子没有升级到XNA4.0)在SM 2.0中支持2个光源,SM 3.0支持8个。在某些游戏中,只需要少量光源,例如室外白天场景,这就是个较好的选择。这个技术的缺点是光源数量较少,而且shader计算会浪费在不可见的物体上。
另一个方法是Multi-Pass光照。对每个光源,物体光照的计算只在当前光源shader中进行。这会导致非常高的batch数量(调用Draw的次数),最坏的情况会达到光源数量乘以物体数量。绘制不可见对象的缺点仍然存在,某些操作会重复多次,例如顶点的转换。Creator’s Club上的例子为Shader Series 5: Multipass Lighting(http://create.msdn.com/en-US/education/catalog/sample/shader_series_5)。
Deferred Shading使用另一种不同的方法。首先,所有物体在不进行光照运算的情况下被绘制,然后对每个像素生成一组数据,这些数据包括位置、法线、高光颜色等。之后,将每个光源以一个2D后期处理的方式施加到最终图像上,这个过程使用的数据是在上一个pass中写入的。因为所有对象使用相同的shader ,导致引擎管理变得非常简单。我们无需基于对象使用的材质进行排序,调用绘制的数量减少到物体数量+光源数量。此外,光源计算只针对可见像素(这些像素生成最终的图像)。
Deferred Shading
让我们现在看一下deferred shading的细节。如前所述,我们首先需要绘制所有物体获取在后面的光照处理中需要的信息,这些信息存储在一个叫做Geometry Buffer (G-Buffer)的缓存中,存在在这个缓存中的数据通常是:
- Position – 这个数据对于区域光源(local lights,即不影响所有物体的光源)是必须的。全局光源(global light,例如环境光和单向光)均等地影响所有物体,而区域光源(点光源和聚光灯)只影响距离足够近的物体。所以我们需要每个像素的位置信息。
- Normal –除了环境光,法线对于任何一种光照计算都是必须的。它被用来确定一个表面是否被照亮,方法是计算光线方向和法线方向的点积。当生成法线时,我们还可以使用法线映射添加物体表面的细节。
- Color – 也被称为漫反射颜色(diffuse color)或反射率(albedo)。通常是来自于纹理的颜色。
- 其他数据 – 基于我们使用的光照模型,我们可能还会使用其他数据,例如:镜面高光强度(specular power),镜面高光颜色(specular intensity)等其他系数。
可见一个像素所需的数据是非常多的,因此 导致了deferred rendering的第一个缺点,称作memory usage,这是因为某些数据(法线,位置) 需要以一个很高的精度存储(floating point textures);这也是这些年来deferred rendering只是作为一个可行性选择的主要原因。要加速这个处理,我们还需使用Multiple Render Targets。
完成上述步骤后,我们就获取了施加光照所需的所有数据。对场景中的所有光源,我们将对图像进行2D后期处理并生成shading信息。在这个步骤中,我们还可以计算阴影,Shadow Map技术可以很好地整合到deferred shading中。
在施加光照时,我们首先确定场景中的哪些区域会被光照亮,对这个区域中的每个像素,我们将从G-buffer获取对应的信息,然后基于光照公式计算当前像素的光照情况。每个光源的光照被混合,最后和颜色数据组合在一起获取最终图像。根据工作原理,我们可知只有可见的像素才会被处理。我们还能发现计算光照所需的时间与光照的影响范围紧密相关,这意味着许多小光源可能比少量大光源运行得更快。
分析工作流程你会发现deferred shading的两个缺点。因为相同的光照shader施加在所有像素上,而且我们只能将这么多数据存储在G-Buffer中,导致物体上的材质数量会有所限制。在实际生活中,一个shader作用在所有物体上,而在游戏中,我们通常使用指定的shader作用在指定的物体上,我们会在后面的章节中处理这个问题。第二个缺点是deferred shading无法处理透明物体,这是因为deferred shading只会处理最近的表面,解决方法也会在最后一章进行讨论。
最后,当绘制最终的图像时,我们还可以在这张图像上施加其他效果,例如体积雾(Volumetric Fog),发光(Glow),HDR,Bloom,Edge Smoothing,Screen-Space Ambient Occlusion等。
开始代码
在开始编码前,请下载Resources.zip(16MB)。它包含以下文件:
- Camera.cs 是一个处理相机的GameComponent,它来自于官网的Skinned Model示例。使用手柄的扳机键或键盘Z和X键进行缩放控制,使用右摇杆或WASD移动相机。
- QuadRendered.cs 是一个来自于Ziggyware的GameComponent,它帮助我们在屏幕上绘制一个用于后期处理的长方形。我不使用SpriteBatch而使用这个类替代是因为SpriteBatch无法处理某些shader变量,例如纹理和采样器。
- null_normal.tga和null_specular.tga是两张以后要用到的纹理。
- Models文件夹包含本教程用到的模型文件。
本文的代码会用在后面的章节中,如果你想略过此步,可以下载DeferredShadingTutorial01.zip,然后进入第二章。
本文会创建一个deferred renderer,它可以很容易地集成到已有游戏项目中。首先,在XNA中创建一个新项目,名为DeferredShadingTutorial,在项目中添加Camera.cs和QuadRenderer.cs。
然后,创建一个新GameComponent(右击项目,选择添加->新建项,然后选择GameComponent),命名为DeferredRenderer并设置从DrawableGameComponent继承。
然后添加两个变量,一个用于Camera,另一个用于QuadRenderer,并在Initialize方法中进行初始化。现在的DeferredRenderer.cs代码如下所示:
using System; using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Media; namespace DeferredShadingTutorial { public class DeferredRenderer : Microsoft.Xna.Framework.DrawableGameComponent { private Camera camera; private QuadRenderComponent quadRenderer; public DeferredRenderer(Game game) : base(game) { } public override void Initialize() { camera = new Camera(Game); Game.Components.Add(camera); quadRenderer = new QuadRenderComponent(Game); Game.Components.Add(quadRenderer); base.Initialize(); } protected override void LoadContent() { base.LoadContent(); } public override void Update(GameTime gameTime) { base.Update(gameTime); } public override void Draw(GameTime gameTime) { base.Draw(gameTime); } } }
为了管理场景我们还想创建一个叫做Scene的类,并添加方法进行初始化和绘制。代码如下:
using System; using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework; namespace DeferredShadingTutorial { class Scene { private Game game; public Scene(Game game) { this.game = game; } public void InitializeScene() { } public void DrawScene(Camera camera, GameTime gameTime) { } } }
现在,将Scene类的对象插入DeferredRenderer.cs中,并在LoadContent中进行初始化。
public class DeferredRenderer : Microsoft.Xna.Framework.DrawableGameComponent { [...] private Scene scene; public DeferredRenderer(Game game) : base(game) { scene = new Scene(game); } protected override void LoadContent() { scene.InitializeScene(); [...] } }
现在这个scene类并不复杂,但足够用于这个教程了。
最后在Game1.cs的构造函数中添加以下代码:
public Game1() { [...] DeferredRenderer renderer = new DeferredRenderer(this); Components.Add(renderer); }
现在我们完成了准备工作,可以进行后继步骤了。
显卡要求
为了实现本文中的技术,你的显卡需要支持Multiple Render Targets和floating point textures。对于ATI显卡来说,需要Radeon 9500以上,对于NVIDIA,需要6000系列以上。
文件下载(已下载 1920 次)发布时间:2011/4/2 下午12:12:13 阅读次数:11028