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)的缓存中,存在在这个缓存中的数据通常是:

可见一个像素所需的数据是非常多的,因此 导致了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)。它包含以下文件:

本文的代码会用在后面的章节中,如果你想略过此步,可以下载DeferredShadingTutorial01.zip,然后进入第二章。

本文会创建一个deferred renderer,它可以很容易地集成到已有游戏项目中。首先,在XNA中创建一个新项目,名为DeferredShadingTutorial,在项目中添加Camera.csQuadRenderer.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  阅读次数:10601

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号