深入分析NVIDIA實(shí)時(shí)光線追蹤焦散技術(shù)

最近光線追蹤技術(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ò)漏之處在所難免式塌,還望各路英雄不吝賜教博敬。

一 原理

圖0 UnrealEngine4通過DXR支持NVIDIA RTX技術(shù)

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 TracerPath 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】

圖1 UnrealEngine4中的Post-Process Volume設(shè)置

實(shí)際上現(xiàn)在的游戲引擎正在趨同屁奏,Unity也基于類似的原理實(shí)現(xiàn)了DXR光線追蹤渲染岩榆,通過替換原有的光柵化管線中的后處理流程來實(shí)現(xiàn)相同的功能错负》仄埃【8】

圖2 Unity引擎的混合光線追蹤引擎架構(gòu)

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)格焦散做一下分析。


圖3 焦散的標(biāo)志性特效:三棱錐色散

網(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)各向異性光子濺射)官辽。

圖4 AAPS的工作流程

AAPS使用了四種緩存:

  • 任務(wù)緩存(Task Buffer)蛹磺,保存當(dāng)前幀需要跟蹤的光線

  • 光子緩存(Photon Buffer),保存所有的光子信息

  • 反饋緩存(Feedback Buffer)同仆,保存最近幾幀被光子覆蓋的區(qū)域用于優(yōu)化

  • 焦散緩存(Caustics Buffer)萤捆,保存被光子照亮的圖像,最終與光柵化圖像混合在一起

工作流程可以分成四個(gè)步驟:

  1. 根據(jù)任務(wù)緩存發(fā)射光子到場景中,一旦碰到不透明表面就把光子碰撞信息保存到光子緩存中俗或,并把覆蓋區(qū)域更新到反饋緩存市怎。

  2. 在焦散緩存上繪制光子濺射:從光子緩存中取出一個(gè)光子根據(jù)參數(shù)繪制一個(gè)橢圓形光斑。

  3. 使用延遲渲染技術(shù)辛慰,把焦散緩存與場景圖像混合在一起区匠。

  4. 把當(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色比較敏感癞埠。


圖5 色散算法的圖像

最終光子繪制則通過光柵化方法状原,繪制到焦散貼圖上,再利用過濾算法對該貼圖進(jìn)行模糊苗踪,避免出現(xiàn)比較硬的邊緣颠区,最終與場景圖像混合在一起。涉及到的shader如下:


圖6 焦散涉及到的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】

圖7 NVIDIA官方的焦散示例廓八,使用了很多光柵化渲染的手段

參考文獻(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奉芦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子剧蹂,更是在濱河造成了極大的恐慌声功,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件国夜,死亡現(xiàn)場離奇詭異,居然都是意外死亡短绸,警方通過查閱死者的電腦和手機(jī)车吹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來醋闭,“玉大人窄驹,你說我怎么就攤上這事≈ぢ撸” “怎么了乐埠?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長囚企。 經(jīng)常有香客問我丈咐,道長,這世上最難降的妖魔是什么龙宏? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任棵逊,我火速辦了婚禮,結(jié)果婚禮上银酗,老公的妹妹穿的比我還像新娘辆影。我一直安慰自己,他們只是感情好黍特,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布蛙讥。 她就那樣靜靜地躺著,像睡著了一般灭衷。 火紅的嫁衣襯著肌膚如雪次慢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天翔曲,我揣著相機(jī)與錄音经备,去河邊找鬼。 笑死部默,一個(gè)胖子當(dāng)著我的面吹牛侵蒙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播傅蹂,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼纷闺,長吁一口氣:“原來是場噩夢啊……” “哼算凿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起犁功,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤氓轰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后浸卦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體署鸡,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年限嫌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了靴庆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡怒医,死狀恐怖炉抒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情稚叹,我是刑警寧澤焰薄,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站扒袖,受9級特大地震影響塞茅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜季率,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一凡桥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蚀同,春花似錦缅刽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至刹孔,卻和暖如春啡省,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背髓霞。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工卦睹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人方库。 一個(gè)月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓结序,卻偏偏與公主長得像,于是被迫代替她去往敵國和親纵潦。 傳聞我的和親對象是個(gè)殘疾皇子徐鹤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容