我是一名資深游戲開發,小時候喜歡看十萬個為什么
介紹
- 本文旨在搞清楚延遲渲染在unity下如何實現的,為自己寫延遲渲染打一個基礎,打開從知到行的大門
- 延遲渲染 = 輸出物體表面信息(rt1, rt2, rt3, …) + 著色(rt1, rt2, rt3, …)
- 研究完感覺核心特征像后處理,像屏幕空間效果
要研究的問題
怎么生成G-Buffer,生成了哪些數據
生成G-Buffer
DeferredLights類里創建G-Buffer紋理資源句柄,管線setup時創建實際的RT資源
GBufferPass類里填充G-Buffer
- Config方法里ConfigureTarget
- 調用CoreUtils綁定RenderTarget,最終調用CommandBuffer的SetRenderTarget把GBuffer對應紋理綁定輸出
- ExecutePass方法中繪制物體
context.DrawRenderers(renderingData.cullResults, ref data.drawingSettings, ref data.filteringSettings, s_ShaderTagUniversalMaterialType, false, tagValues, stateBlocks);
- shader中填充數據,見 UnityGBuffer.hlsl 中 SurfaceDataToGbuffer、 BRDFDataToGbuffer 方法
輸出了下面的數據
見文件:UnityGBuffer.hlsl
half4 GBuffer0 : SV_Target0; //diffuse,表面顏色
half4 GBuffer1 : SV_Target1; //metallic/specular,高光
half4 GBuffer2 : SV_Target2; //encode normal,法線
half4 GBuffer3 : SV_Target3; // Camera color attachment,GI,全局光#ifdef GBUFFER_OPTIONAL_SLOT_1
GBUFFER_OPTIONAL_SLOT_1_TYPE GBuffer4 : SV_Target4; //clip z,剪裁空間z值,即深度
#endif
怎么傳入多個光源計算光照
逐類型光源
DeferredLights里RenderStencilLights,逐個調用直射光、點光源、射燈進行渲染
using (new ProfilingScope(cmd, m_ProfilingSamplerDeferredStencilPass))
{NativeArray<VisibleLight> visibleLights = renderingData.lightData.visibleLights;if (HasStencilLightsOfType(LightType.Directional))RenderStencilDirectionalLights(cmd, ref renderingData, visibleLights, renderingData.lightData.mainLightIndex);if (HasStencilLightsOfType(LightType.Point))RenderStencilPointLights(cmd, ref renderingData, visibleLights);if (HasStencilLightsOfType(LightType.Spot))RenderStencilSpotLights(cmd, ref renderingData, visibleLights);
}
繪制命令
- 直射光,遍歷光源,逐光源DrawCall
for (int soffset = m_stencilVisLightOffsets[(int)LightType.Directional]; soffset < m_stencilVisLights.Length; ++soffset)省略cmd.SetGlobalVector(ShaderConstants._LightColor, lightColor); // VisibleLight.finalColor already returns color in active color spacecmd.SetGlobalVector(ShaderConstants._LightDirection, lightDir);cmd.SetGlobalInt(ShaderConstants._LightFlags, lightFlags);cmd.SetGlobalInt(ShaderConstants._LightLayerMask, (int)lightLayerMask);// 因為GBufferPass已經把光照數據都輸出到紋理,這里只需要繪制全屏的mesh,在shader中采樣之前輸出的GBuffer計算光照// Lighting pass.cmd.DrawMesh(m_FullscreenMesh, Matrix4x4.identity, m_StencilDeferredMaterial, 0, m_StencilDeferredPasses[(int)StencilDeferredPasses.DirectionalLit]);cmd.DrawMesh(m_FullscreenMesh, Matrix4x4.identity, m_StencilDeferredMaterial, 0, m_StencilDeferredPasses[(int)StencilDeferredPasses.DirectionalSimpleLit]);省略
- 點光源、射燈也是遍歷光源,逐光源DrawCall,但是mesh不是全屏mesh,是代表光源形狀的memsh,代碼不贅述,今天不水文
渲染
入口StencilDeferred.shader
half4 DeferredShading(Varyings input) : SV_Target//省略部分代碼,這些代碼是:取GBuffer的值,拼出計算光照需要的數據//計算光照InputData inputData = InputDataFromGbufferAndWorldPosition(gbuffer2, posWS.xyz);#if defined(_LIT)#if SHADER_API_MOBILE || SHADER_API_SWITCH// Specular highlights are still silenced by setting specular to 0.0 during gbuffer pass and GPU timing is still reduced.bool materialSpecularHighlightsOff = false;#elsebool materialSpecularHighlightsOff = (materialFlags & kMaterialFlagSpecularHighlightsOff);#endifBRDFData brdfData = BRDFDataFromGbuffer(gbuffer0, gbuffer1, gbuffer2);color = LightingPhysicallyBased(brdfData, unityLight, inputData.normalWS, inputData.viewDirectionWS, materialSpecularHighlightsOff);#elif defined(_SIMPLELIT)SurfaceData surfaceData = SurfaceDataFromGbuffer(gbuffer0, gbuffer1, gbuffer2, kLightingSimpleLit);half3 attenuatedLightColor = unityLight.color * (unityLight.distanceAttenuation * unityLight.shadowAttenuation);half3 diffuseColor = LightingLambert(attenuatedLightColor, unityLight.direction, inputData.normalWS);half smoothness = exp2(10 * surfaceData.smoothness + 1);half3 specularColor = LightingSpecular(attenuatedLightColor, unityLight.direction, inputData.normalWS, inputData.viewDirectionWS, half4(surfaceData.specular, 1), smoothness);// TODO: if !defined(_SPECGLOSSMAP) && !defined(_SPECULAR_COLOR), force specularColor to 0 in gbuffer codecolor = diffuseColor * surfaceData.albedo + specularColor;#endifreturn half4(color, alpha);
渲染物體
不透明物體
輸出G-Buffer,渲染著色
半透物體
延遲渲染不支持半透,所以走Forward渲染,用額外一個 ScriptableRenderPass 渲染半透,見UniversalRenderer 的 m_RenderTransparentForwardPass
貼花
使用方法
- URP管線資源里創建Renderer,設置使用Deferred
- 相機里Renderer選上面創建的Renderer
延伸知識
SSAO
- Screen Space Ambient Occlusion -> 屏幕空間環境光遮蔽
- SSAO 通過使用深度緩沖、法線緩沖和隨機采樣核生成遮蔽效果,模擬場景中物體周圍環境光被遮擋的情況,從而增強畫面的真實感和層次感,讓場景看起來更有深度
源碼分析
結論
管線流程
- 生成GBuffer
- shader中通過SV_TargetXXX指定輸出到某個綁定的緩沖區
- 要求圖形接口支持一次輸出到多個目標
- 通過GBuffer渲染
管線
GBufferPass
輸出GBuffer
DeferredPass
用GBuffer著色
DrawObjectsPass
繪制物體,不透、半透
RenderGraph
渲染節點圖,可以通過編輯器定制渲染管線
DeferredLights
延遲渲染具體的邏輯
shader源碼分析
Lit.shader
GBuffer Pass,輸出GBuffer數據的Pass
LitGBufferPass.hlsl
輸出GBuffer的Pass源碼
UnityGBuffer.hlsl
真干活的著色代碼
像素著色器輸出
struct FragmentOutput
{half4 GBuffer0 : SV_Target0; //diffuse,表面顏色half4 GBuffer1 : SV_Target1; //metallic/specular,高光half4 GBuffer2 : SV_Target2; //encode normal,法線half4 GBuffer3 : SV_Target3; // Camera color attachment,GI,全局光#ifdef GBUFFER_OPTIONAL_SLOT_1GBUFFER_OPTIONAL_SLOT_1_TYPE GBuffer4 : SV_Target4; //clip z,剪裁空間z值,即深度#endif#ifdef GBUFFER_OPTIONAL_SLOT_2half4 GBuffer5 : SV_Target5;#endif#ifdef GBUFFER_OPTIONAL_SLOT_3half4 GBuffer6 : SV_Target6;#endif
};輸出GBuffer
FragmentOutput SurfaceDataToGbuffer(SurfaceData surfaceData, InputData inputData, half3 globalIllumination, int lightingMode)
{half3 packedNormalWS = PackNormal(inputData.normalWS);uint materialFlags = 0;// SimpleLit does not use _SPECULARHIGHLIGHTS_OFF to disable specular highlights.#ifdef _RECEIVE_SHADOWS_OFFmaterialFlags |= kMaterialFlagReceiveShadowsOff;#endif#if defined(LIGHTMAP_ON) && defined(_MIXED_LIGHTING_SUBTRACTIVE)materialFlags |= kMaterialFlagSubtractiveMixedLighting;#endifFragmentOutput output;output.GBuffer0 = half4(surfaceData.albedo.rgb, PackMaterialFlags(materialFlags)); // albedo albedo albedo materialFlags (sRGB rendertarget)output.GBuffer1 = half4(surfaceData.specular.rgb, surfaceData.occlusion); // specular specular specular occlusionoutput.GBuffer2 = half4(packedNormalWS, surfaceData.smoothness); // encoded-normal encoded-normal encoded-normal smoothnessoutput.GBuffer3 = half4(globalIllumination, 1); // GI GI GI unused (lighting buffer)#if _RENDER_PASS_ENABLEDoutput.GBuffer4 = inputData.positionCS.z;#endif#if OUTPUT_SHADOWMASKoutput.GBUFFER_SHADOWMASK = inputData.shadowMask; // will have unity_ProbesOcclusion value if subtractive lighting is used (baked)#endif#ifdef _WRITE_RENDERING_LAYERSuint renderingLayers = GetMeshRenderingLayer();output.GBUFFER_LIGHT_LAYERS = float4(EncodeMeshRenderingLayer(renderingLayers), 0.0, 0.0, 0.0);#endifreturn output;
}
StencilDeferred.shader
使用GBuffer進行著色
StencilDeferred.hlsl
頂點、像素方法
著色
half4 DeferredShading(Varyings input) : SV_Target...省略代碼,不水字數,感興趣的朋友看URP源碼即可