Deferred Shading介绍

Deferred Shading介绍在本文我将展示如何在 XNA 中使用 deferredrend 首先让我们理解什么是 deferredshad 然后学习这个技术的几个步骤 从创建 GeometryBuff 一直到管理材质 最后 我们介绍如何创建一个内容管道处理器使用这个技术 在每个步骤中 我会详细解释原理 在后面的章节中 有时我也会回到前面并重写某些代码 你最好理解不同的坐标系 例如世界空间 视空间和屏幕空间 这可

在本文我将展示如何在XNA中使用deferred rendering。首先让我们理解什么是deferred shading,然后学习这个技术的几个步骤,从创建Geometry Buffer一直到管理材质。最后,我们介绍如何创建一个内容管道处理器使用这个技术。在每个步骤中,我会详细解释原理,在后面的章节中,有时我也会回到前面并重写某些代码。你最好理解不同的坐标系,例如世界空间、视空间和屏幕空间,这可以参考creators.xna.com上的Shader Series。

实时光照

现今的游戏中一个物体往往要被许多光源照亮,今天这仍是一个代价昂贵的操作,也没有一个完美的解决方案。让我们首先看一下解决这个问题的几种常用方法。

Single-Pass光照方法中,每个对象对需要被绘制,所有光照运算都在一个shader中进行。可参加creators.xna.com上的示例。但一个shader有指令数量的限制,所以这个技术只适用于光源数量较少的情况(例如,Creator’s Club上的例子Shader Series 4: Materials and Multiple Light Sources(或本站的译文,注意:这个例子没有升级到XNA4.0)在SM 2.0中支持2个光源,SM 3.0支持8个。在某些游戏中,只需要少量光源,例如室外白天场景,这就是个较好的选择。这个技术的缺点是光源数量较少,而且shader计算会浪费在不可见的物体上。

另一个方法是Multi-Pass光照。对每个光源,物体光照的计算只在当前光源shader中进行。这会导致非常高的batch数量(调用Draw的次数),最坏的情况会达到光源数量乘以物体数量。绘制不可见对象的缺点仍然存在,某些操作会重复多次,例如顶点的转换。Creator’s Club上的例子为Shader Series 5: Multipass Lighting。

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.tganull_specular.tga是两张以后要用到的纹理。
  • Models文件夹包含本教程用到的模型文件。

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

本文会创建一个deferred renderer,它可以很容易地集成到已有游戏项目中。首先,在XNA中创建一个新项目,名为DeferredShadingTutorial,在项目中添加Camera.csQuadRenderer.cs

然后,创建一个新GameComponent(右击项目,选择添加->新建项,然后选择GameComponent),命名为DeferredRenderer并设置从DrawableGameComponent继承。

然后添加两个变量,一个用于Camera,另一个用于QuadRenderer,并在Initialize方法中进行初始化。现在的DeferredRenderer.cs代码如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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的类,并添加方法进行初始化和绘制。代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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中进行初始化。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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的构造函数中添加以下代码:

?
1
2
3
4
5
6
public
Game1()
{
    
[...]
    
DeferredRenderer renderer = 
new
DeferredRenderer(
this
);
    
Components.Add(renderer);
}

现在我们完成了准备工作,可以进行后继步骤了。

显卡要求

为了实现本文中的技术,你的显卡需要支持Multiple Render Targets和floating point textures。对于ATI显卡来说,需要Radeon 9500以上,对于NVIDIA,需要6000系列以上。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/219793.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午9:53
下一篇 2026年3月17日 下午9:53


相关推荐

  • 【stm32f407】SPI实验 驱动W25Q128「建议收藏」

    一.SPI介绍SPI是英语SerialPeripheralinterface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚…

    2022年4月7日
    44
  • 正数、负数和补码_正数原码反码补码

    正数、负数和补码_正数原码反码补码计算机中,正数、负数是怎么区分的呢,如何存放正数和负数?这里,就要用到补码这个概念了,先给出结论吧:正数和负数在计算机其实都是使用补码来存放的,并且在计算机中是没有减法运算的,减法实际上就是补码直接相加。正数和负数的补码补码是计算机存放数据之前对数据做了一种转换操作得到的,与补码相关的几个名词还有原码、反码:1、原码:字节的最高位为符号位,其余表示数值大小,最简单;2、反码:正数的反码和原码一样,负数的反码除最高位符号位外,其他位都取反;3、补码:在反码的基础上加1,这样可以方便计算机进行计算,可

    2025年7月16日
    6
  • 谷歌Nano Banana模型文字渲染实战提示词教程:生成含清晰文本的图像

    谷歌Nano Banana模型文字渲染实战提示词教程:生成含清晰文本的图像

    2026年3月15日
    2
  • 方法区元空间实现之jdk7和8字符串常量池、运行时常量池、静态变量到底在哪?

    方法区元空间实现之jdk7和8字符串常量池、运行时常量池、静态变量到底在哪?方法区(落地实现jdk7永久代,jdk8元空间),元空间并不在虚拟机中,而是使用本地内存1、此区域是线程共享的。储存已加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;2、常量池:编译器生成的各种字面量和符号引用;3、关于字符串常量池和运行时常量池的位置说明:jdk1.6存在永久代,字符串常量池、运行时常量池都是在永久代中;jdk1.7存在永久代,字符串常量池被移动到了堆当中,运行时常量池还是在永久代中;jdk1.8不存在永久代,实现形式是元空间,字符串常量池仍然在堆当中,运行.

    2022年5月23日
    44
  • windows恶意软件删除工具 MRT.EXE

    windows恶意软件删除工具 MRT.EXEMRT是微软自Windows7开始就自带的一款绿色的恶意软件删除工具。具体路径为C:\WINDOWS\system32\MRT.exe默认已经在系统环境变量中。所以我们直接win+R输入mrt即可运行操作也极其简单,一路下一步看情况选择相应的扫描模式,一般建议用快速扫描,10来分钟可以完成。扫描过程直观明了,有进度条显示。完成后即可直接退出。…

    2022年6月24日
    57
  • 安装OpenStack Identity Service

    安装OpenStack Identity Service

    2021年8月28日
    46

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号