Deferred Shading

到目前为止,本书中所使用的渲染方法都是forward rendering,本质上来说是指独立渲染场景中的每一个objects,对于每一个object都要使用lights,textures以及shadows计算最终的pixels。另外,我们还探讨了single-pass rendering(用于一个object的所有光照都在一个shader中进行计算)和multipass rendering(通过执行多次绘制操作组合成object的最终pixel值)的概念。在任何使用forward rendering的系统中,我们都会受限于影响object的光源数量。影响object的光源越多,系统的计算量越大,那么应用程序的运行速度就越慢。但是另一种渲染方法deferred shading,支持在场景中使用大量的光源而不会显著降低性能。

注意
Deferred shading也称为deferred rendering。只不过deferred rendering在图表学术语中已经被使用了。特别是在Microsoft的产品中,该术语表示buffering commands,用于在其他时间段进行“回放”(意思是把渲染指令缓冲起来,等到需要的时候再进行渲染,因此可以称为延迟渲染)。这种延迟渲染的方法与本节所到讨论的deferred shading完全不同。

在使用deferred shading的系统中,首先渲染场景中的所有objects,而不包含光照。而且这些objects并不是被渲染到back buffer中,而是使用multiple render targets(MRT)渲染到一些off-screen buffers中(离线的bufffers)。在这些render targets中存储了场景的geometry数据,包括object的position,normal,color(一般是从一个diffuse color texture中采样得到),specular power和specular intensity。前面已经讲过render targets其实就是2D textures,但是在这里这些geometry buffers(也称为G-buffer)并不是存储color值,而是存储影响objects的lighting passs数据用于计算场景中所有pixels的最终color值。

其中lighting pass类似于在第18章“Post Processing”中所讲的post-processing effect。对于场景中的每一种光源,都是从geometry buffers中采样数据,并计算被该光源影响的每一个pixel的直接和间接光照值。最后使用场景中所有光源产生的影响组合成最终的图像数据,并映射到一个full-screen quad中。

由于光照是在screen space中进行计算,渲染每一帧的计算成本主要由光源的数量以及受光源影响的pixels数量决定。Geometry数据是每一帧渲染一次,并且只对真正受到光源影响的geometry才会执行高成本的光照运算。除了具有高性能的优点之外,deferred shading还能简单一个渲染引擎的架构设计,因为使用这种方法场景中的所有objects可以共用一个shader。经常切换shaders会产生较大的性能开销,因此使用forward rendering方法的渲染引擎通常会批量处理使用相同materials的objects。如果不使用deferred shading系统,就需要一个更复杂的引擎架构。

也就是说,使用deferred shading只能共用一种materials。此外,deferred shading还有很多不足之处,这时仍然需要使用forward rendering。第一,geometry buffers需要占用大量的内存。第二,deferred shading无法处理transparency(透明),透明效果通常由一个排序步骤单独处理(from back to front,也就是距离camera的距离由远到近),并对透明的objects使用forward rendering pass。另外一个不足的地方是,把geometry和lighting阶段分开计算,就无法使用硬件的anti-aliasing功能,因此需要使用一个额外的post-processing technique处理边缘的光滑度。

最后在一个现代的渲染引擎中,通常都会同时包含forward和deferred shading两种渲染方法。

Global Illumination

Global illumination是指在场景中增加更真实的光照效果的一组技术。具体地说,global illumination用于模拟indirect lighting(间接光照),意思是指照射到一个表面上的光线是由其他objects反射出来的。考虑一下在本书所编写的shaders中使用的ambient lighting(环境光),我们使用了一个表示光照强度的常量值,并统一用于整个object的表面。显然,这种方式并不是真正的ambient lighting。Ambient light是由场景中objects之间各种光线的相互反射的结果;一个表面被遮挡的越多(相对于其他的反射表面)接收到的间接光照就越少,因此就会显得越暗。

有多种流行的技术用于模拟间接光照,从实现上可以在物理准确性和程序运行速度之间进行折衷。Ambient occlusion描述了一组渲染技术用于模拟照射到一个表面的光照数量。Ambient occlusion还可以通过提前计算,并像涂画一样添加到textures中。在实现开发中这是一种常用的方法,因为这种方法可以增加光照的真实感而不会影响程序运行时的性能。但是,这种textures无法用于动态的光照,如果光照改变了或者周围的环境被修改了,就会揭示出这种静态的光照textures。一个被广泛采用的实时光照技术是screen-space ambient occlusion(SSAO)。SSAO主要在screen space执行(作为一个post-processing步骤),因此独立于场景的复杂运算。具体地说,先把场景渲染到一个depth buffer中,然后采样一个pixel周围的深度值计算一个遮挡因子。此外,SSAO可以完全使用GPU实现,并且能很好的支持场景中动态的objects。关于SSAO的详细讲解超出了本书的范围,我们把这个主题留给读者自己研究。

在现在渲染技术中另一个常用于global illumination的主题是spherial harmonic lighting(SH lighting)(同样,关于SH lighting详细讲解也超出了本书的范围)。虽然SH lighting涉及大量的数学计算,但是可以概括为一组技术用于提前计算场景环境的作用(比如全局光照环境)得到spherial harmonic coefficients(系数)。我们所讨论的所有光照技术都可以看作rendering equation的简化,rendering equation是指一套系统用于生成完全基于物理运动的图像。另外,rendering equation是在各个方向半球表面的积分,无法支持实时渲染。SH lighting使用spherical函数替换rendering equation中的部分函数。这样就可以把积分计算简化为SH系数的dot product运算,而不用在运行时计算rendering equation。使用SH lighting可以实时地产生非常直接的画面,但是场景中objects的位置和形式必须保持不变,或者每一个object都有一组独立的SH系数。

Compute Shaders

在DirectX发布的同时,Microsoft引入了DirectCompute API,该库支持基于GPU的通用编程(而不仅仅是图形编程)。Compute shaders允许你在实际编程中,把任何计算都转移到GPU中,旨在减少GPU的计算工作并充分利于显卡的大规模并行运算的架构。

Computer shader(CS)阶段位于正常的渲染管线之外,但是可以读写GPU的数据。尽管computer shaders可以支持任意数量的通用程序,但是用于图形程序中能够生成一些特别有趣的效果。例如,我们可以把compute shader用于处理deferred shading lighing pass,执行deferred shading的边缘光滑运算,depth-of-field blurring(根据深度值进行局部区域模糊),或者通用的post-processing。从compute shader阶段输出的数据可以直接与渲染管线的输入进行绑定,而不用把这些输出数据再传递到CPU中。

Threads

Compute shaders可以使用一个thread group,在多个线程上并行的运行。一个thread group包含一组线程,这些线程可以访问同一块共享内存区域。Thread groups被创建为一个三维列表,并在执行compute shader时通过调用ID3D11DeviceContext::Dispatch(UINT threadGroupCountX, UINT threadGroupCountY, UINT threadGroupCountZ)函数指定列表的大小。另外,使用compute shader包含的numthreads(UINT x, UINT y, UINT z)属性指定一个group中所包含的线程数量。例如:

[numthreads(32, 32, 1)]
void compute_shader(uint3 threadID : SV_DispatchThreadID)
{// Shader body
}

通过计算thread groups数量与每一个group包含的线程数量的乘积,可以得到总线程数量。例如,以这些参数调用ID3D11DeviceContext::Dispatch(32, 24, 1),结合上面的compute shader就会总共产生(32×32, 24×32, 1×1) = 1024×768×1个线程。在GPU中根据处理器的数量在这些线程中分配对应的计算工作。

A Simpler Compute Shader

编写复杂的compute shaders超出了本节的范围,但是列表22.4中列出一个简单的compute shader用于向一个2D texture中写入颜色值。
列表22.4 A Compute Shader That Writes to a 2D Texture

RWTexture2D<float4> OutputTexture;cbuffer CBufferPerFrame
{float2 TextureSize;float BlueColor;
};[numthreads(32, 32, 1)]
void compute_shader(uint3 threadID : SV_DispatchThreadID)
{OutputTexture[threadID.xy] = float4((threadID.xy / TextureSize), BlueColor, 1);
}technique11 compute
{pass p0{SetVertexShader(NULL);SetGeometryShader(NULL);SetPixelShader(NULL);SetComputeShader(CompileShader(cs_5_0, compute_shader()));}
}

在该shader中首先声明了一个可读写(RW)的2D textures(OutputTexture),用于接受compute shader的写入。这种数据类型允许多个线程同时向texture中写入数据。通过访问一个使用数组样式规则的2D纹理坐标,可以获取一个指定的texel。

在该compute shader中包含有一个输入参数threadID,并使用SV_DisaptchThreadID semantic进行标记。该参数包含了compute shader运行时所在线程的唯一标识符。在列表22.4所示的shader使用这个标识符作为访问output texture的索引值。使用这种方法的情况下,每一次执行compute shader时都会写入一个特定的texel。如果没有指定足够多的线程数(在本例中)用于覆盖整个texture,那么部分texels就会被写入颜色的数据。

需要注意的是,与vertex和pixel shaders一样,在compute shader也可以声明并访问constant buffer数据。在列表22.4所示的例子中,变量TextureSize用于格式化线程ID(ID值的类型为无符号整形),变量BlueColor用于指定texel的blue通道值。

在该示例对应的CPU端程序中,使用一个unordered access view(UAV)与OutputTexture变量进行绑定。一个UVA支持多个线程随机访问(读写)一个resource。具体地说就是,可以使用多个线程同时读写resource而不用担心内存冲突问题。列表22.5中列出了创建UAV的初始化代码。另外,还使用同一个texture创建了一个shader resource view(SRV),并与一个pxiel shader进行绑定用于渲染compute shader的输出颜色值。
列表22.5 Initializing a UAV and an SRV for the Compute Shader Demo

D3D11_TEXTURE2D_DESC textureDesc;
ZeroMemory(&textureDesc, sizeof(textureDesc));
textureDesc.Width = mGame->ScreenWidth();
textureDesc.Height = mGame->ScreenHeight();
textureDesc.MipLevels = 1;
textureDesc.ArraySize = 1;
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
textureDesc.SampleDesc.Count = 1;
textureDesc.SampleDesc.Quality = 0;
textureDesc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;HRESULT hr;
ID3D11Texture2D* texture = nullptr;
if (FAILED(hr = mGame->Direct3DDevice()->CreateTexture2D(&textureDesc, nullptr, &texture)))
{throw GameException("IDXGIDevice::CreateTexture2D() failed.", hr);
}D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc;
ZeroMemory(&uavDesc, sizeof(uavDesc));
uavDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
uavDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D;
uavDesc.Texture2D.MipSlice = 0;if (FAILED(hr = mGame->Direct3DDevice()->CreateUnorderedAccessView(texture, &uavDesc, &mOutputTexture)))
{ReleaseObject(texture);throw GameException("IDXGIDevice::CreateUnorderedAccessView() failed.", hr);
}D3D11_SHADER_RESOURCE_VIEW_DESC resourceViewDesc;
ZeroMemory(&resourceViewDesc, sizeof(resourceViewDesc));
resourceViewDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
resourceViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
resourceViewDesc.Texture2D.MipLevels = 1;if (FAILED(hr = mGame->Direct3DDevice()->CreateShaderResourceView(texture, &resourceViewDesc, &mColorTexture)))
{ReleaseObject(texture);throw GameException("IDXGIDevice::CreateShaderResourceView() failed.", hr);
}ReleaseObject(texture);

注意其中是如何把mOutputTexture(UAV)和mColorTexture(SRV)引用同一个texture。

该示例的绘制操作主要是,更新compute shader对应的material数据,调度compute shader的线程,最后把compute shader输出的texture渲染到一个full-screen quad中。列表22.6中列出了ComputeShaderDemo::Draw()函数的代码。
列表22.6 The Draw() Method of the Compute Shader Demo

void ComputeShaderDemo::Draw(const GameTime& gameTime)
{ID3D11DeviceContext* direct3DDeviceContext = mGame->Direct3DDeviceContext();// Update the compute shader's materialmMaterial->TextureSize() << XMLoadFloat2(&mTextureSize);mMaterial->BlueColor() << mBlueColor;mMaterial->OutputTexture() << mOutputTexture;mComputePass->Apply(0, direct3DDeviceContext);// Dispatch the compute shaderdirect3DDeviceContext->Dispatch(mThreadGroupCount.x, mThreadGroupCount.y, 1);// Unbind the UAV from the compute shader, so we can bind the same underlying resource as an SRVstatic ID3D11UnorderedAccessView* emptyUAV = nullptr;direct3DDeviceContext->CSSetUnorderedAccessViews(0, 1, &emptyUAV, nullptr);// Draw the texture written by the compute shadermFullScreenQuad->Draw(gameTime);mGame->UnbindPixelShaderResources(0, 1);mRenderStateHelper.SaveAll();mSpriteBatch->Begin();std::wostringstream helpLabel;helpLabel << std::setprecision(2) << "Color Offset: " << mBlueColor;mSpriteFont->DrawString(mSpriteBatch, helpLabel.str().c_str(), mTextPosition);mSpriteBatch->End();mRenderStateHelper.RestoreAll();
}

其中,通过调用ID3D11DeviceContext::CSSetUnorderedAccessViews()函数解除了UAV与compute shder阶段的绑定。之所以要解除绑定是因为在full-screen quad的Draw()函数中使用了同一个resource。尤其是full-screen quad与ComputeShaderDemo::UpdateRenderingMaterial()回调函数进行关联,用于设置material的ColorTexture变量值。

在该示例中,使用以下的方法在每一帧更新shader变量BlueColor:

void ComputeShaderDemo::Update(const GameTime& gameTime)
{mBlueColor = (0.5f) * static_cast<float>(sin(gameTime.TotalGameTime())) + 0.5f;
}

图22.2中显示了compute shader示例程序的输出截图。

图22.2 Output of the compute shader demo.

本节只是简单的介绍了compute shaders。DirectCompute可以为应用程序带来惊人的计算能力。

Deferred Shading相关推荐

  1. OpenGL deferred shading延迟渲染的实例

    OpenGL deferred shading延迟渲染 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <glad/glad.h> # ...

  2. OpenGL 延迟着色法Deferred Shading

    OpenGL 延迟着色法Deferred Shading 延迟着色法Deferred Shading简介 G缓冲 延迟光照处理阶段 结合延迟渲染与正向渲染 更多的光源 计算一个光源的体积或半径 真正使 ...

  3. GPU Gems2 - 9 S.T.A.L.K.E.R.中的延迟着色(Deferred Shading in S.T.A.L.K.E.R.)

    关于延迟着色的详细技术及最新渲染管线技术可以参考我的这篇文章: https://blog.csdn.net/yinfourever/article/details/90263638 [章节概览] 本章 ...

  4. Deferred Shading,延迟渲染(提高渲染效率,减少多余光照计算)

    Deferred Shading,看过<Gems2> 的应该都了解了.最近很火的星际2就是使用了Deferred Shading. 原帖位置:    http://blog.csdn.ne ...

  5. Deferred Shading介绍

    在本文我将展示如何在XNA中使用deferred rendering.首先让我们理解什么是deferred shading,然后学习这个技术的几个步骤,从创建Geometry Buffer一直到管理材 ...

  6. Tiled Based Deferred Shading与Forward+

    Tiled Based流程 1.将屏幕分成小块,每个小块为一个视锥体 2,(depth bounds)在每一个视锥体中,根据ZBuffer得到每个Tile的MinZ和MaxZ,用MinZ到MaxZ这片 ...

  7. Learn OpenGL 笔记6.9 Deferred Shading(延迟着色)

    到目前为止,我们进行照明的方式称为forward rendering前向渲染或forward shading前向着色.我们渲染对象,根据场景中的所有光源对其进行照明.我们为场景中的每个对象分别为每个对 ...

  8. Deferred Shading VS Deferred Lighting

    鉴于传统的Forward Rendering对于多光源渲染时的低效问题,各种Deferred Rendering的方法被提出并且广泛使用.比如Deferred Shading以及其之后的Deferre ...

  9. Deferred Shading,延迟渲染(提高渲染效率,减少多余光照计算)【转】

    Deferred Shading,看过<Gems2> 的应该都了解了.最近很火的星际2就是使用了Deferred Shading. 原帖位置:    http://blog.csdn.ne ...

最新文章

  1. oracle-pl/sql之三
  2. C语言经典例24-分数累加和
  3. 遥感计算机分类有哪些,遥感数字图像的计算机分类.doc
  4. 买的情侣裤衩寄前男友家了,怎么办?
  5. php重写curl_setopt函数,PHP curl_share_setopt函数
  6. java读文件几种方式_java中读取文件的方式有哪几种
  7. Linux内核变迁杂谈——感知市场的力量
  8. 360更新补丁一直提示正在安装_Microsoft .NET Framework 4.7 安装失败,产生阻滞的问题...
  9. matlab中如何创建使用构架数组?
  10. sql 删除一条记录_京东笔试题:如何实现 MySQL 删除重复记录并且只保留一条?...
  11. 王道计算机考研——计算机组成原理笔记
  12. 扩散模型——下一个图像生成热点,快上车!!!
  13. vex编程语言是基于c语言,vex机器人编程软件 vex机器人大赛
  14. 湖北飞young使用任意路由器教程
  15. [渝粤教育] 西南科技大学 管理学原理 在线考试复习资料(2)
  16. ubuntu16.04+Titan Xp的驱动官网上找不到
  17. 面试计算机应用技术自我介绍,计算机应用专业面试的自我介绍
  18. 申论指导议论文八大高分标准
  19. 关于MobaXterm在4K屏幕下SSH显示软件分辨率不太正确的问题
  20. 电脑用计算机软件一直闪退,电脑软件打开后闪退或崩溃怎么办

热门文章

  1. MaxCompute(DataIDE)数据核查
  2. 2022 re:Invent 凌云驭势 重塑未来
  3. 独角兽企业面试题:这是盒子还是板子?
  4. 命运2服务器维护公告在哪看,《命运2》将于明日进行脱机维护
  5. 大数据Hadoop生态圈介绍
  6. 表格的操作包括:标记行、移动行、删除行、插入行
  7. java读取rdb_剖析Redis RDB文件
  8. 【linux】linux中fork()详解(实例讲解)|fork的运行机制
  9. 戴尔页面计算机怎么创建快捷方式,Dell笔记本电脑怎么Fn功能快捷键?
  10. 来也科技:RPA+AI的赋能者和布道者