我是一名资深游戏开发,小时候喜欢看十万个为什么
介绍
- 本文旨在搞清楚延迟渲染在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源码即可