【渲染】Unity-分析URP的延迟渲染-DeferredShading

我是一名资深游戏开发,小时候喜欢看十万个为什么

介绍

  • 本文旨在搞清楚延迟渲染在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 space
    cmd.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;
        #else
        bool materialSpecularHighlightsOff = (materialFlags & kMaterialFlagSpecularHighlightsOff);
        #endif
        BRDFData 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 code
        color = diffuseColor * surfaceData.albedo + specularColor;
    #endif

    return 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_1
    GBUFFER_OPTIONAL_SLOT_1_TYPE GBuffer4 : SV_Target4; //clip z,剪裁空间z值,即深度
    #endif
    #ifdef GBUFFER_OPTIONAL_SLOT_2
    half4 GBuffer5 : SV_Target5;
    #endif
    #ifdef GBUFFER_OPTIONAL_SLOT_3
    half4 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_OFF
    materialFlags |= kMaterialFlagReceiveShadowsOff;
    #endif

    #if defined(LIGHTMAP_ON) && defined(_MIXED_LIGHTING_SUBTRACTIVE)
    materialFlags |= kMaterialFlagSubtractiveMixedLighting;
    #endif

    FragmentOutput 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        occlusion
    output.GBuffer2 = half4(packedNormalWS, surfaceData.smoothness);                     // encoded-normal  encoded-normal  encoded-normal  smoothness
    output.GBuffer3 = half4(globalIllumination, 1);                                      // GI              GI              GI              unused          (lighting buffer)
    #if _RENDER_PASS_ENABLED
    output.GBuffer4 = inputData.positionCS.z;
    #endif
    #if OUTPUT_SHADOWMASK
    output.GBUFFER_SHADOWMASK = inputData.shadowMask; // will have unity_ProbesOcclusion value if subtractive lighting is used (baked)
    #endif
    #ifdef _WRITE_RENDERING_LAYERS
    uint renderingLayers = GetMeshRenderingLayer();
    output.GBUFFER_LIGHT_LAYERS = float4(EncodeMeshRenderingLayer(renderingLayers), 0.0, 0.0, 0.0);
    #endif

    return output;
}

StencilDeferred.shader

使用GBuffer进行着色

StencilDeferred.hlsl

顶点、像素方法

着色
复制代码
half4 DeferredShading(Varyings input) : SV_Target
    ...省略代码,不水字数,感兴趣的朋友看URP源码即可
相关推荐
Doc.S1 小时前
多无人机任务自定义(基于ZJU-FAST-Lab / EGO-Planner-v2)
游戏引擎·无人机·cocos2d
那个村的李富贵2 小时前
Unity打包Webgl后 本地运行测试
unity·webgl
nnsix3 小时前
Unity OpenXR开发HTC Vive Cosmos
unity·游戏引擎
nnsix4 小时前
Unity OpenXR,扳机键交互UI时,必须按下扳机才触发
unity·游戏引擎
nnsix4 小时前
Unity XR 编辑器VR设备模拟功能
unity·编辑器·xr
老朱佩琪!4 小时前
Unity访问者模式
unity·游戏引擎·访问者模式
不定时总结的那啥5 小时前
Unity实现点击Console消息自动选中预制体的方法
unity·游戏引擎
nnsix5 小时前
Unity OpenXR 关闭手柄的震动
unity·游戏引擎
CreasyChan5 小时前
Unity 中的反射使用详解
unity·c#·游戏引擎·游戏开发
Jessica巨人5 小时前
Shader显示为黑色
unity·shader