前言

很多游戏中存在大量的点光源(PointLight),环境艺术家为了让游戏模拟现实的氛围,一个场景下放下上千个点光源(PointLight)毫不奇怪。

下面介绍下传统的渲染管线大量点光源的表现。

传统前向渲染(Traditional Forward Rendering)的点光源计算

总体意思就是每个物体进行一次renderPass,并把在影响到物体的点光源作为一个数组放在Shader进行计算

总结:传统的前向渲染因为同个像素可能覆盖大量的物体,造成Overdraw很高,浪费了大量的计算,很多计算是不必要的,因为不透明物体的Shading呈现在屏幕的只有最前面的像素。因此引出了延迟渲染。

传统的延迟渲染(Traditional Deffered Rendering)

传统的延迟渲染很简单,就是渲染整个场景的物体输出多张几何贴图,然后利用几何贴图在一次全屏幕绘制的Shading中计算。其中渲染点光源比较流行的办法是把点光源当做一个几何球体(LightSphereVolume),渲染到全屏上,有效计算的每个点光源半径内影响的像素。 并且设置RT为累加模式,N个光源就累加N次,最后得到所有点光源着色的最终效果。

内的像素,而不必要每个光源对全屏像素都计算一次.

总结: 相比于前向渲染,因为我们只渲染最前面的一层像素, overdraw 大量减少,浪费的计算也减少了, 但是N个点光源意味着计算N次光源球体RenderPass, 每个pass中我们都读取了一次各种gbuffer和写入一次shading结果,这导致GPU bandwidth浪费严重。如下所示:

因此图形工程师针对延迟渲染提出了更有效计算点光源的渲染管线:Tiled Deffered Shading

基于分片的延迟渲染(Tiled Based Deffered Shading)

上面传统延迟渲染的示意图说明了传统延迟渲染的GPU Bangwidth高的缺点,按照理想的改进模型如下:

就是最理想的状态是:对于着色每个像素应该只读取一次GBuffer和只写入一次Shading结果

针对这个理想的状态模型, 图形渲染工程师提出分块(tiled)的思想: 延迟渲染的基础上把整个屏幕划分为NxN块,一块(tile)的分辨率是16x16, 利用并行能力强大的computeShader计算哪些光源了哪些块(tile),并且让这些有效点光源对相应块的像素进行Pixel着色

下面简称 TiledBasedDefferedShading 为 TBDS

TBDS的渲染流程:

(1)渲染整个场景的GBuffer

(2)在computeShader里分好每个块(tile),一个块(tile)一般是16x16或者32x32, 计算每个tile的所有像素(一般相机空间比较好)最大和最小的PosZ值

Texture2D<float4> DepthTex:register(t0);
Texture2D<float4> WorldPosTex:register(t1);
Texture2D<float4> WorldNormalTex:register(t2);
Texture2D<float4> SpecularRoughMetalTex:register(t3);
Texture2D<float4> AlbedoTex:register(t4);
SamplerState clampLinearSample:register(s0);
StructuredBuffer<PointLight> PointLights : register(t5);
RWTexture2D<float4> OutputTexture : register(u0);
groupshared uint minDepthInt;
groupshared uint maxDepthInt;
groupshared uint visibleLightCount = 0;
groupshared uint visibleLightIndices[1024];[numthreads(GroundThreadSize, GroundThreadSize, 1)]
void CS(uint3 groupId :  SV_GroupID,uint3 groupThreadId : SV_GroupThreadID,uint groupIndex : SV_GroupIndex,uint3 dispatchThreadId : SV_DispatchThreadID)
//(2)计算每个Tiled的相机空间的MaxZ和MinZfloat depth = DepthTex[dispatchThreadId.xy].r;float viewZ = DepthBufferConvertToLinear(depth);uint depthInt = asuint(viewZ);minDepthInt = 0xFFFFFFFF;maxDepthInt = 0;GroupMemoryBarrierWithGroupSync();if (depth != 0.0){InterlockedMin(minDepthInt, depthInt);InterlockedMax(maxDepthInt, depthInt);}GroupMemoryBarrierWithGroupSync();float minViewZ = asfloat(minDepthInt);float maxViewZ = asfloat(maxDepthInt);

(3)计算每个块(tile)对应的frustum(相机空间的视截体)

 float3 frustumEqn0, frustumEqn1, frustumEqn2, frustumEqn3;uint tileResWidth = GroundThreadSize * GetNumTilesX();uint tileResHeight = GroundThreadSize * GetNumTilesY();uint pxm = GroundThreadSize * groupId.x;uint pym = GroundThreadSize * groupId.y;uint pxp = GroundThreadSize * (groupId.x + 1);uint pyp = GroundThreadSize * (groupId.y + 1);// four corners of the tile, clockwise from top-leftfloat3 frustum0 = ConvertProjToView(float4(pxm / (float)tileResWidth*2.f - 1.f, (tileResHeight - pym) / (float)tileResHeight*2.f - 1.f, 1.f, 1.f)).xyz;float3 frustum1 = ConvertProjToView(float4(pxp / (float)tileResWidth*2.f - 1.f, (tileResHeight - pym) / (float)tileResHeight*2.f - 1.f, 1.f, 1.f)).xyz;float3 frustum2 = ConvertProjToView(float4(pxp / (float)tileResWidth*2.f - 1.f, (tileResHeight - pyp) / (float)tileResHeight*2.f - 1.f, 1.f, 1.f)).xyz;float3 frustum3 = ConvertProjToView(float4(pxm / (float)tileResWidth*2.f - 1.f, (tileResHeight - pyp) / (float)tileResHeight*2.f - 1.f, 1.f, 1.f)).xyz;frustumEqn0 = CreatePlaneEquation(frustum0, frustum1);frustumEqn1 = CreatePlaneEquation(frustum1, frustum2);frustumEqn2 = CreatePlaneEquation(frustum2, frustum3);frustumEqn3 = CreatePlaneEquation(frustum3, frustum0);

(4) 对每一个块(tile),遍历所有点光源,用frustum和Depth双重剔除,并把影响点光源的的全局索引加入到块(tile)的可见光源列表

//(3)计算和每个Tiled相交的点光源数量,并记录它们的索引uint threadCount = GroundThreadSize * GroundThreadSize;uint passCount = (int(lightCount) + threadCount - 1) / threadCount;for (uint i = 0; i < passCount; ++i){uint lightIndex = i * threadCount + groupIndex;if (lightIndex >= lightCount)continue;PointLight light = PointLights[lightIndex];float3 viewLightPos = mul(float4(light.pos, 1.0), View).xyz;if(TestFrustumSides(viewLightPos, light.radius, frustumEqn0, frustumEqn1, frustumEqn2, frustumEqn3)){if (minViewZ - viewLightPos.z < light.radius && viewLightPos.z - maxViewZ < light.radius){uint offset;InterlockedAdd(visibleLightCount, 1, offset);visibleLightIndices[offset] = lightIndex;}}}GroupMemoryBarrierWithGroupSync();

(5)遍历块(tile)的 可见光源列表的光源,对块内的所有像素进行着色,这样GBuffer的各种RT做到了只读一次,并只写一次Shading结果, GPU bandwidth低

if (visibleLightCount > 0){//G-Buffer-Pos(浪费1 float)float2 uv = float2(float(dispatchThreadId.x) / ScreenWidth, float(dispatchThreadId.y) / ScreenHeight);float3 worldPos = WorldPosTex.SampleLevel(clampLinearSample, uv, 0).xyz;//G-Buffer-Normal(浪费1 float)float3 worldNormal = WorldNormalTex.SampleLevel(clampLinearSample, uv, 0).xyz;worldNormal = normalize(worldNormal);float3 albedo = AlbedoTex.SampleLevel(clampLinearSample, uv, 0).xyz;//G-Buffer-Specual-Rough-Metal(浪费1 float)float3 gBufferAttrbite = SpecularRoughMetalTex.SampleLevel(clampLinearSample, uv, 0).xyz;float specular = gBufferAttrbite.x;float roughness = gBufferAttrbite.y;float metal = gBufferAttrbite.z;for (uint index = 0; index < visibleLightCount; ++index){uint lightIndex = visibleLightIndices[index];PointLight light = PointLights[lightIndex];float3 pixelToLightDir = light.pos - worldPos;float distance = length(pixelToLightDir);float3 L = normalize(pixelToLightDir);float3 V = normalize(cameraPos - worldPos);float3 H = normalize(L + V);float4 attenuation = light.attenuation;float attenua = 1.0 / (attenuation.x + attenuation.y * distance + distance * distance * attenuation.z);float3 radiance = light.color * attenua;//f(cook_torrance) = D* F * G /(4 * (wo.n) * (wi.n))float D = DistributionGGX(worldNormal, H, roughness);float G = GeometrySmith(worldNormal, V, L, roughness);float3 fo = GetFresnelF0(albedo, metal);float cosTheta = max(dot(V, H), 0.0);float3 F = FresnelSchlick(cosTheta, fo);float3 ks = F;float3 kd = float3(1.0, 1.0, 1.0) - ks;kd *= 1.0 - metal;float3 dfg = D * G * F;float nDotl = max(dot(worldNormal, L), 0.0);float nDotv = max(dot(worldNormal, V), 0.0);float denominator = 4.0 * nDotv * nDotl;float3 specularFactor = dfg / max(denominator, 0.001);color.xyz += (kd * albedo / PI + specularFactor * specular) * radiance * nDotl * 2.2;}}OutputTexture[dispatchThreadId.xy] = color;

渲染结果对比

传统延迟渲染SphereLightVolume, 400个点光源

分块延迟渲染(TiledBasedDefferedShading),1024个点光源

项目源码链接

https://github.com/2047241149/SDEngine

资料参考

【1】https://newq.net/dl/pub/SA2014Practical.pdf

【2】DirectX 11 Rendering in Battlefield 3 - Frostbite

【3】OpenGL Step by Step - OpenGL Development

【4】AMD Tiled Lighting Direct3D 11 Demo | Geeks3D

【5】http://newq.net/dl/pub/SA2014ManyLightIntro.pdf

Directx11进阶教程之Tiled Based Deffered Shading相关推荐

  1. Directx11进阶教程之Cluster Based Deffered Shading

    前言 很多游戏中存在大量的点光源(PointLight),环境艺术家为了让游戏模拟现实的氛围,一个场景下放下上千个点光源(PointLight)毫不奇怪. 在上一章中  Directx11进阶教程之T ...

  2. Directx11进阶教程之CascadeShadowMap(层级阴影)(上)

    好久没写博客了,紧接着前面的博客,不过这次读取纹理的借口换为了DXUT框架里面的接口. 程序结构 如果没有学会ShadowMap,PCF软阴影原理的建议回去回顾下面几个教程: Directx11教程三 ...

  3. Directx11进阶教程之CascadeShadowMap(层级阴影)(下)---解决阴影丢失和阴影抖动问题

    承接上一篇博客,我们探讨下阴影丢失和阴影抖动的原因和提出相应的解决办法. CSM算法的阴影丢失或者显示错乱问题: 再次看看试验场景的阴影丢失或者阴影显示错乱的现象: 错误的显示:(上个教程不完善的CS ...

  4. Tiled Based Deferred Shading与Forward+

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

  5. 根据用户查进程_【磨叽教程】Android进阶教程之在Android系统下各进程之间的优先级关系...

    导读:本文大约2000字,预计阅读时间3分钟.本文纯属技术文,无推广. 正文     首先应用进程的生命周期并不由应用本身直接控制,而是由系统综合多种因素来确定的.Android系统有自己的一套标准, ...

  6. Vue 进阶教程之:详解 v-model

    分享 Vue 官网教程上关于 v-model 的讲解不是十分的详细,写这篇文章的目的就是详细的剖析一下, 并介绍 Vue 2.2 v-model 改进的地方,然后穿插的再说点 Vue 的小知识. 在 ...

  7. Spring进阶教程之在ApplicationContext初始化完成后重定义Bean

    之前遇到一个很有意思的问题:我需要批量重定义特定类型的由Spring容器托管的Bean.具体体现在,我有很多控制器类(Controller)和校验器类(Validator),我希望他们都是多例(Pro ...

  8. C++高级进阶教程之STL 教程

    STL 教程 在前面的章节中,我们已经学习了 C++ 模板的概念.C++ STL(标准模板库)是一套功能强大的 C++ 模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和 ...

  9. rom diy进阶教程之apk反编译基础(插桩解析)

    [Android] [SMALI]smali文件内容具体介绍 大家都应该知道APK文件其实就是一个MIME为ZIP的压缩包,我们修改ZIP后缀名方式可以看到内部的文件结构,例如修改后缀后用RAR打开鳄 ...

最新文章

  1. 内核同步机制——互斥量
  2. Android开发切换host应用
  3. JAVA面试常考系列十
  4. java操作oracle数据_Java jdbc操作oracle数据库的两种方式
  5. Silkroad 与 Tesseract 通信协议 QuestionModel
  6. JSON和JavaScript对象互转
  7. SilverLight:布局(3)StackPanel 对象
  8. windows 实验报告
  9. Windows平台安装dlib方法汇总
  10. mysql数据库密码字段查看_mysql基本操作(数据库,表,字段,记录)
  11. MED-V实战之镜像测试,MED-V系列之五
  12. Codejock 19.x 定制Crack Version
  13. Tushare的安装及使用介绍
  14. html天天生鲜项目,day54-天天生鲜项目订单管理
  15. 大数据实习生的年终总结,2022继续与CSDN同行
  16. 揭露淘宝不良商家,利用UI设计缺陷进行恶意修改销量以及评价!
  17. Hive 官网函数全列表(聚合函数/日期函数/字符串函数...)
  18. 使用433MHz RF模块制作一艘简易的Arduino遥控小船
  19. 游戏BI数据统计分析相关
  20. Wavesurfer.js 生成音频波形图

热门文章

  1. rk3288编译android,RK3288 源码编译Android 7.1.2 自动编译
  2. pl/sql完全破解
  3. 史上最简单的图片二维码识别
  4. Python 虚拟环境迁移
  5. 2021年全球手持式工业红外线测温仪行业调研及趋势分析报告
  6. Android 输入法表情上传服务器
  7. JSD1806——8——SpringMVC
  8. qcon_从QCon San Francisco 2008中学到的主要知识点和教训
  9. 动画中的关键帧动画的原理
  10. python实现视频ai换脸_Python如何实现AI换脸功能 Python实现AI换脸功能代码