最近光線追蹤技術(shù)終于隨著兩大主機(jī)平臺的支持進(jìn)入了全面普及時(shí)代,AMD也緊跟NVIDIA的腳步推出了支持光追功能的新一代GPU中燥,光線追蹤已經(jīng)成為了當(dāng)前的一個(gè)技術(shù)熱點(diǎn)。
本文將深入分析NVIDIA基于UnrealEngine4實(shí)現(xiàn)的實(shí)時(shí)光線追蹤折射/焦散(Real-Time Ray-Tracing Refraction/Caustics)技術(shù)塘偎。由于NV的代碼幾乎沒有注釋褪那,以及個(gè)人水平有限,錯(cuò)漏之處在所難免式塌,還望各路英雄不吝賜教博敬。
一 原理
UnrealEngine4使用微軟DXR和NVIDIA RTX硬件實(shí)現(xiàn)了光線跟蹤渲染,DXR是微軟Direct3D12圖形API的一個(gè)組成部分峰尝,RTX則是NVIDIA支持光線追蹤的圖形設(shè)備的名稱偏窝。
NVIDIA維護(hù)了UnrealEngine4的一個(gè)私有分支(https://github.com/NvRTX/UnrealEngine 請注意該分支為私有,需要UnrealEingine4以及NVIDIA GameWorks的訪問權(quán)限)武学,主要目的是為引擎的光追效果集成專有技術(shù)DLSS祭往。本篇只關(guān)注該分支的一個(gè)特殊版本:對焦散的支持。
UnrealEngine4已經(jīng)實(shí)現(xiàn)的光線追蹤特性包含以下內(nèi)容:【1】
光線追蹤特性(Ray tracing feature) | 光柵化等效(Raster equivalent) |
---|---|
光線追蹤反射(Ray tracing reflections) | (SSR or cube maps) |
光線追蹤陰影(Ray tracing shadows) | (Shadow maps or DFS) |
光線追蹤環(huán)境遮蔽(Ray tracing ambient occlusion) | (SSAO or DFAO) |
光線追蹤半透明(Ray tracing translucency) | (Raster translucency) |
光線追蹤天光(Ray tracing skylight) | (Raster skylight) |
光線追蹤全局照明(Ray tracing global illumination) | (SSGI or LPV) |
實(shí)際上UnrealEngine4包含兩個(gè)光線追蹤器火窒,Ray Tracer和Path Tracer硼补,其中Ray Tracer是一個(gè)與現(xiàn)有光柵化管線混合的追蹤器,而Path Tracer則與電影級離線渲染器類似熏矿,目前并不能完全的實(shí)時(shí)運(yùn)行已骇。如無特別說明,本文內(nèi)容全部針對Ray Tracer追蹤器票编。
在UnrealEngine4中開啟實(shí)時(shí)光線追蹤褪储,需要給場景添加一個(gè)后處理體積(Post Process Volume),實(shí)際上UnrealEngine把所有光追特效看做是后處理的一部分慧域,通常是將光線追蹤特性渲染到一個(gè)離屏表面(Off-Screen Surface)上鲤竹,再與光柵化渲染的場景混合到一起。這樣的做法有兩個(gè)優(yōu)點(diǎn)昔榴,一是光線追蹤的精度與畫面精度分離辛藻,可以通過降低光追精度來加速;二是可以沿用光柵化管線的既有功能互订,使得光追實(shí)現(xiàn)困難或者計(jì)算消耗大的特性可以在光柵化管線實(shí)現(xiàn)吱肌。【2】
實(shí)際上現(xiàn)在的游戲引擎正在趨同屁奏,Unity也基于類似的原理實(shí)現(xiàn)了DXR光線追蹤渲染岩榆,通過替換原有的光柵化管線中的后處理流程來實(shí)現(xiàn)相同的功能错负》仄埃【8】
NVIDIA的UE4分支主要是集成專有的深度學(xué)習(xí)超采樣(DLSS)技術(shù)和對光線追蹤做貼近硬件的性能優(yōu)化勇边,其中有一個(gè)比較有趣的特性,就是實(shí)時(shí)光線追蹤焦散效果(Ray Traced Caustic Effect)折联。實(shí)際上NVIDIA實(shí)現(xiàn)了兩種焦散效果粒褒,一個(gè)是比較通用的網(wǎng)格焦散(Mesh Caustic),另一個(gè)是特殊優(yōu)化過的水面焦散(Water Caustic)诚镰,由于水面焦散是普通焦散的一個(gè)極端特例奕坟,本文僅就網(wǎng)格焦散做一下分析。
網(wǎng)格焦散使用的方法叫做光子濺射(Photon Splatting)清笨,是光子貼圖(Photon Mapping)的一個(gè)變種實(shí)現(xiàn)月杉。【3】
原始的光子貼圖使用的方法有兩個(gè)階段:跟蹤階段(Tracing) —— 從光源位置跟蹤光子穿過場景的路徑抠艾; 密度估計(jì)階段(Density Estimation) —— 在表面上給定的點(diǎn)周圍計(jì)算光子的密度苛萎,用于光照信息重建〖旌牛【6】【7】在NVIDIA的方法中腌歉,第二個(gè)階段被替換為光子濺射:在光子碰撞到不透明表面后使用加色混合的方法直接把顏色繪制出來。這樣做有幾個(gè)優(yōu)勢齐苛,可以使用GPU的光柵化能力加速計(jì)算并且存儲光子的結(jié)構(gòu)非常簡單翘盖,還有最重要的是,可以用很低的計(jì)算消耗進(jìn)行光子微分(Photon Differentials凹蜂,一種提高圖像質(zhì)量的數(shù)學(xué)方法)馍驯。光子微分算法可以針對每條光線跟蹤它們的微小擾動(dòng),然后用于估計(jì)光子的大小和形狀玛痊,這樣就可以在非鏡面反射的表面產(chǎn)生橢圓形的光斑泥彤。通過使用各向異性的光斑設(shè)置,算法就可以在低采樣率下重建焦散模型卿啡。
二 架構(gòu)
為了整套機(jī)制能夠?qū)崟r(shí)渲染吟吝,NVIDIA簡化了光子追蹤和微分計(jì)算,并使用了幀間反饋插值的方法來加速(Temporal Feedback颈娜,類似臨時(shí)反走樣Temporal AA)剑逃,NVIDIA稱之為AAPS(Adaptive Anisotropic Photon Scattering,自適應(yīng)各向異性光子濺射)官辽。
AAPS使用了四種緩存:
任務(wù)緩存(Task Buffer)蛹磺,保存當(dāng)前幀需要跟蹤的光線
光子緩存(Photon Buffer),保存所有的光子信息
反饋緩存(Feedback Buffer)同仆,保存最近幾幀被光子覆蓋的區(qū)域用于優(yōu)化
焦散緩存(Caustics Buffer)萤捆,保存被光子照亮的圖像,最終與光柵化圖像混合在一起
工作流程可以分成四個(gè)步驟:
根據(jù)任務(wù)緩存發(fā)射光子到場景中,一旦碰到不透明表面就把光子碰撞信息保存到光子緩存中俗或,并把覆蓋區(qū)域更新到反饋緩存市怎。
在焦散緩存上繪制光子濺射:從光子緩存中取出一個(gè)光子根據(jù)參數(shù)繪制一個(gè)橢圓形光斑。
使用延遲渲染技術(shù)辛慰,把焦散緩存與場景圖像混合在一起区匠。
把當(dāng)前幀的反饋緩存和上一幀的反饋緩存去重合并,產(chǎn)生下一幀所需的光線帅腌,存入任務(wù)緩存驰弄。
三 實(shí)現(xiàn)
焦散的實(shí)現(xiàn)代碼散落在UnrealEngine4的RayTraceRenderer和相關(guān)Shader中,但并未改變整體的光追架構(gòu)速客。
最核心的實(shí)現(xiàn)代碼在如下目錄中:
EngineSourceRuntimeRendererPrivateRayTracingRayTracingMeshCaustics.cpp
NVIDIA在FDeferredShadingSceneRenderer類中增加了一個(gè)函數(shù):
// NVCHANGE_BEGIN_YY : RT Caustics
void RenderCausticsMapInner(FRHICommandListImmediate& RHICmdList, const FViewInfo& View, FIntPoint CausticsMapSize, FIntPoint CausticsMapViewSize);
實(shí)現(xiàn)的核心代碼如下:
...
// 添加重置數(shù)據(jù)Pass
AddResetDataPass(View, GraphBuilder, Buffers, DispatchIndirectParametersBufferUAV);
// 檢查調(diào)試選項(xiàng)
int DebugType = 0;
bool bIsDebugView = false;
if (View.RayTracingRenderMode == ERayTracingRenderMode::RayTracingCausticsDebug && GetRayTracingDebugMode(View) == RAY_TRACING_DEBUG_VIZ_MESHCAUSTICS_DEBUG_DATA)
{
DebugType = GetLightDebugData(View);
bIsDebugView = true;
}
// 添加光子跟蹤Pass
AddPhotonTracingPass(View, LightParameters, GraphBuilder, Buffers, SceneDepthTexture, SceneMetallicTexture, SceneAlbedoTexture, PhotonBuffer, DispatchIndirectParametersBufferUAV, RectLightTextureArray, DebugType, EnableSoftCaustics);
// 添加更新光線密度Pass
AddUpdateRayDensityPass(View, GraphBuilder, Buffers, DispatchIndirectParametersBuffer);
// 添加光線生成Pass
AddGenerateRayQuadTreePass(View, GraphBuilder, Buffers);
// 檢查調(diào)試模式是否關(guān)閉了焦散
if (GET_CAUSTICS_CMD_VAR(DebugDisablePhotonSplatting) == 0)
{
// 添加光子散射Pass
AddPhotonScatteringPass(View, GraphBuilder, PhotonBuffer,
SceneDepthTexture, SceneNormalTexture, SceneMetallicTexture, SceneAlbedoTexture, DispatchIndirectParametersBuffer, RTCausticsIntensityBuffer);
// 添加臨時(shí)過濾Pass
AddTemporalFilterPass(View, GraphBuilder, Buffers, SceneDepthTexture, SceneNormalTexture, RTCausticsIntensityBuffer);
}
// 添加焦散混合Pass
AddCompositeCausticsPass(*this, GraphBuilder, RHICmdList, SceneColorTexture, View, Buffers, SceneDepthTexture, SceneNormalTexture, SceneAlbedoTexture, RTCausticsIntensityBuffer, bIsDebugView);
// 構(gòu)建繪圖
Buffers.ReleaseBuffers();
GraphBuilder.Execute();
涉及修改的Shader非常之多戚篙,此處不一一列舉,請參考項(xiàng)目的Git提交歷史溺职。實(shí)現(xiàn)折射的核心Shader放在了EngineShadersPrivateRayTracingRayTracingMaterialHitShaders.usf文件中:
...
// 判斷是反射還是折射
if (isReflect)
{
// 更新反射光微分參數(shù)
UpdateReflectRayDifferential(v.Normal, hitData.dPdx, dNdx, hitData.dDdx);
UpdateReflectRayDifferential(v.Normal, hitData.dPdy, dNdy, hitData.dDdy);
// 得到反射光線方向和顏色
R = reflect(rayDirW, v.Normal);
hitData.color = F * hitData.color;
}
else
{
// 取得折射向量
GetRefractVector(rayDirW, v.Normal, R, eta);
//更新折射光線微分參數(shù)
UpdateRefractRayDifferential(rayDirW, R, v.Normal, eta, hitData.dPdx, dNdx, hitData.dDdx);
UpdateRefractRayDifferential(rayDirW, R, v.Normal, eta, hitData.dPdy, dNdy, hitData.dDdy);
// 判斷是否開啟了色散
#if REFRACTION_CAUSTICS_ENABLE_DISPERSION
if(dispersionAmount != 0)
hitData.SetDispersion(1);
#endif
// 計(jì)算折射顏色
float3 filterClr = Emissive + BaseColor;
float filterAvgClr = max(filterClr.r, max(filterClr.g, filterClr.b));
filterClr = filterClr / filterAvgClr;
float transparency = 1 - Opacity;
filterClr = lerp(filterClr, 1, transparency * transparency);
hitData.color = filterClr * hitData.color;
}
計(jì)算色散的代碼放在EngineShadersPrivateRayTracingRayTracingMeshCaustics.usf文件中:
...
// 判斷是否開啟色散
if (EnableDispersion && hitData.IsDispersion())
{
// 根據(jù)光線索引計(jì)算色散偏移
uint3 launchIndex = DispatchRaysIndex();
uint3 launchDimension = DispatchRaysDimensions();
uint dispersionSample = DispersionSamples;
uint taskIdx = (launchIndex.y * launchDimension.x + launchIndex.x);
float dispersionOffset = float(taskIdx % dispersionSample) / (dispersionSample - 1);
// 將偏移縮放到[-1,1]之間
dispersionOffset = dispersionOffset * 2 - 1;
// 計(jì)算色調(diào)偏移
float greenWeight = float(dispersionSample + 1) / (2 * dispersionSample - 2);
float3 colorWeight = saturate(float3(-dispersionOffset, (1 - abs(dispersionOffset)) * greenWeight, dispersionOffset));
// 得到最終顏色和微分參數(shù)
float lengthFactor = sqrt(float(dispersionSample));
color *= colorWeight;// / dot(colorWeight, 0.333);
dPdx *= lengthFactor;
dPdy *= lengthFactor;
}
接下來拆分一下色散函數(shù)已球,其實(shí)是個(gè)非常簡單的線性組合公式:
隨著x增大,紅色分量逐漸減少辅愿,藍(lán)色分量則逐漸增大智亮,綠色分量則先增大再減小,最終顏色在七色色輪上移動(dòng)点待,形成色散阔蛉。DispersionSamples參數(shù)用來控制綠色分量的占比,因?yàn)槿搜蹖G色比較敏感癞埠。
最終光子繪制則通過光柵化方法状原,繪制到焦散貼圖上,再利用過濾算法對該貼圖進(jìn)行模糊苗踪,避免出現(xiàn)比較硬的邊緣颠区,最終與場景圖像混合在一起。涉及到的shader如下:
四 總結(jié)
NVIDIA通過硬件加速的光線追蹤實(shí)現(xiàn)了相當(dāng)不錯(cuò)的焦散效果通铲,但是在實(shí)際設(shè)備上運(yùn)行時(shí)消耗還是非常大毕莱,尤其是場景復(fù)雜度增加以后。NVIDIA官方的示例使用了反射探針颅夺,反射攝像機(jī)等傳統(tǒng)的效果與之混合朋截,如果僅僅使用光線追蹤很難在足夠的幀數(shù)下獲得同樣質(zhì)量的圖像。
可以預(yù)見在相當(dāng)長的一段時(shí)間里吧黄,類似RTX版本UnrealEngine4這樣混合光線追蹤的做法將成為主流技術(shù)部服,我們距離完整的光線追蹤還有一段不小的距離∞挚【4】
參考文獻(xiàn)
【1】 Introducing Ray Tracing in Unreal Engine 4 , NVIDIA
【2】 Real-time ray tracing in Unreal Engine, EPIC Games
【3】 An Introduction to Ray Traced Caustic Effects In Unreal Engine 4, NVIDIA
【4】 Are we done with Ray Tracing? SIGGRAPH2019 Alexander Keller (NVIDIA)
【5】 From Rasterization to Ray Tracing, SIGGRAPH2019 Morgan McGuire (NVIDIA)
【6】 Photon Mapping on Programmable Graphics Hardware, (2003) Stanford University/University of California
【7】 《Real-Time Rendering》 www.realtimerendering.com
【8】 Leveraging Real-Time Ray Tracing To Build A Hybrid Game Engine, SIGGRAPH2019 Unity