深入分析虚幻4引擎光线追踪(一):框架分析

概述

本系列文章旨在拆解虚幻4引擎中光线跟踪功能的具体实现。虚幻引擎是非常流行的游戏引擎,各种拆解文章汗牛充栋,在此不会对虚幻引擎的整体架构多做分析。由于本民科水平有限,难免错漏,还请各位看官海涵。

由于内容繁多,打算分成几篇来详细分析:

  1. 框架分析,即是本篇,主要是从框架抽象的角度来看整体设计
  2. 场景空间反射
  3. 全局照明(计划中)
  4. 阴影和全局遮蔽(计划中)
  5. 路径跟踪(计划中)

虚幻4延迟渲染框架

此处只分析虚幻4引擎的延迟渲染框架。虚幻4引擎通过TaskGraph实现了渲染逻辑的异步拆分(具体分析文章很多,此处不再赘述),其延迟渲染器的主体部分放在 DeferredShadingRenderer.cpp 文件中,具体逻辑在 FDeferredShadingSceneRenderer::Render() 函数中。虽然TaskGraph是完全异步的,但是整体的分派逻辑整合到了这个巨大的函数中,这并不利于定制修改和维护。

一般延迟渲染分为4个步骤:Z-Pass,前向阶段,延迟阶段,透明渲染和后处理【3】,虚幻引擎也并不例外。光线追踪部分则通过嵌入的方式合并到渲染中,最终在延迟渲染阶段与光栅化的图像合并在一起。

除PathTracing外,虚幻4引擎将光追拆分成了几个部分,分别计算,这样可以将计算量控制在一定范围,以达到实时处理的目的。

虚幻4延迟渲染框架

DXR的渲染框架

传统的光线跟踪渲染器可能各部分拆分的不是很明显,典型的如Blender集成的Cycles渲染器。【5】

DXR和RTX则很小心的把BVH射线求交部分拆分出来,使用硬件进行加速,开创了实时光线追踪的新时代。

DXR光线追踪可以大致分成以下模块:

  • BVH加速结构(Top Level和Bottom Level)

  • 硬件加速的光线求交运算(硬件实现)

  • 可编程的逻辑处理(Pipeline)

  • 运算中临时存储结构(Shading Binding Table)

DXR的整体架构

其中可编程部分DXR提供几种新的Shader,详情请查询DXR的官方文档,此处拆解一下其运算流程:

  • 首先由光线产生Shader调用TraceRay()发射光线

  • 进入硬件实现部分,对两级的BVH加速结构进行遍历

  • 调用相交和碰撞的自定义Shader

  • 判断是否有碰撞,调用最近碰撞或是未命中Shader

DXR Shader的执行流程

光栅化与光线追踪的混合

以上介绍了DXR的基本框架,在引擎里面临的现实问题是纯粹的光线跟踪对于本世代硬件来说还是太慢,无法真正的实时运行。因此虚幻4引擎采用了混合光栅化管线与光线追踪管线的方法,在最终的延迟/后处理阶段将两者合并在一起。

本文第一部分描绘了整个管线的框架,下面来具体看一下光线追踪反射与全局光照的调用逻辑,NVIDIA修改版的半透明焦散效果请参考本民科的 [此篇文章]:(https://www.jianshu.com/p/a238f22133d6)。

这一部分涉及的调用逻辑代码在IndirectLightRendering.cpp文件中。首先是在开始渲染前先要构建加速结构,在引擎中我们需要两个步骤:

  • 首先要收集场景中参与光线追踪的所有物体 GatherRayTracingWorldInstances()

  • 其次需要将物体的信息和状态同步加速结构中DispatchRayTracingWorldUpdates()

然后是具体的调用:

void FDeferredShadingSceneRenderer::RenderDiffuseIndirectAndAmbientOcclusion(FRHICommandListImmediate& RHICmdListImmediate)
{
    ...
    if (bApplyRTGI)
    {
    
        bool bIsValid = RenderRayTracingGlobalIllumination(GraphBuilder, SceneTextures, View, /* out */ &RayTracingConfig, /* out */ &DenoiserInputs);
        if (!bIsValid)
        {
            DenoiseMode = 0;
        }
    }
    ...
}

void FDeferredShadingSceneRenderer::RenderDeferredReflectionsAndSkyLighting(FRHICommandListImmediate& RHICmdList, TRefCountPtr<IPooledRenderTarget>& DynamicBentNormalAO, TRefCountPtr<IPooledRenderTarget>& VelocityRT, FHairStrandsDatas* HairDatas)
{
    ...
            if (bRayTracedReflections)
            {
#if RHI_RAYTRACING
                RayTracingConfig.RayCountPerPixel = GetRayTracingReflectionsSamplesPerPixel(View);
#else
                RayTracingConfig.RayCountPerPixel = 0;
#endif
                RenderRayTracingReflections(
                    GraphBuilder,
                    SceneTextures,
                    View,
                    RayTracingConfig.RayCountPerPixel, RayTracingConfig.ResolutionFraction,
                    &DenoiserInputs);
            }
    ...
}
    

渲染的结果输出到指定的贴图上,最终通过加色混合模式叠加到帧缓存上。这个过程有时候会跟其他过程混合在一起,比如光线跟踪反射的结果是通过环境反射的Shader采样输出的,这样的好处是与屏幕空间反射可以无缝衔接到一起,减少不必要的消耗。

            GraphBuilder.AddPass(
                RDG_EVENT_NAME("ReflectionEnvironmentAndSky %dx%d", View.ViewRect.Width(), View.ViewRect.Height()),
                PassParameters,
                ERDGPassFlags::Raster,
                [PassParameters, &View, PixelShader, bCheckerboardSubsurfaceRendering](FRHICommandList& InRHICmdList)
            {
                ...
                if (GetReflectionEnvironmentCVar() == 2 || GAOOverwriteSceneColor)
                {
                    // override scene color for debugging
                    GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
                }
                else
                {
                    if (bCheckerboardSubsurfaceRendering)
                    {
                        GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGB, BO_Add, BF_One, BF_One>::GetRHI();
                    }
                    else
                    {
                        GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One>::GetRHI();
                    }
                }

                SetGraphicsPipelineState(InRHICmdList, GraphicsPSOInit);
                SetShaderParameters(InRHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters);
                FPixelShaderUtils::DrawFullscreenTriangle(InRHICmdList);
            });

可以看到,实际上虚幻4的光线追踪是分离成不同的特性,最终叠加到光栅化管线的结果上的,这样做的好处是可以最大化利用原有的流程,修改不用伤筋动骨。

总结

由于要兼容各种配置的项目,代码中有大量的宏开关和条件判断,这部分功能也尚未完善(4.25版本),有大量的TODO并且缺乏注释,给分析带来了一些困难。最近使用最新的3060Ti显卡搭建Demo,发现性能还是十分喜人的,希望虚幻引擎能够尽快完善光追功能。

引用:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容