【URP】[实时阴影]计算流程解析

在Unity URP中,实时阴影的工作流程基于‌ShadowMap技术‌实现,主要流程如下:

【从UnityURP开始探索游戏渲染】专栏-直达

一、阴影生成流程

‌阴影贴图(ShadowMap)生成

在光源位置设置虚拟相机,渲染场景深度到纹理:

  • 主光源阴影由ShadowCaster Pass处理,输出深度图到_MainLightShadowmapTexture
  • 使用级联阴影(Cascaded Shadow Mapping)提升精度:将ShadowMap划分为2×2图集,对应不同精度等级‌
csharp 复制代码
csharp
// URP核心流程if (主光源开启阴影) {
    MainLightShadowCasterPass.Render();// 生成_MainLightShadowmapTexture
}

‌深度值比较

正常渲染时执行:

  • 将像素坐标转换到光源空间获取深度值
  • 采样ShadowMap比较深度:若当前深度 > ShadowMap值,则判定为阴影区域‌

二、关键Pass的作用

ShadowCaster Pass

  • 作用‌:专用于生成阴影贴图
  • 输出目标 ‌:_MainLightShadowmapTexture(主光源)或自定义ShadowMap
  • Shader要求‌:
cpp 复制代码
hlsl
Pass {
    Tags { "LightMode" = "ShadowCaster" }
    #pragma multi_compile_shadowcaster // 生成SHADOWS_DEPTH/SHADOW_CUBE宏‌
    V2F_SHADOW_CASTER; // 声明数据结构
    TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) // 顶点着色器处理深度偏移‌
}

来自于Lit的阴影投射pass

cpp 复制代码
// ShadowCaster 计算灯光的深度贴图,相当于以光的位置主动投射到物体上,形成的偏移阴影。用来投射阴影到其他位置的计算。
// 这里涉及到_ShadowBias在Shadow.hlsl中x分量表示DepthBias深度方向偏移,y分量表示NormalBias法线方向偏移。
Pass
{
    Name "ShadowCaster"
    Tags
    {
        "LightMode" = "ShadowCaster"
    }

    // -------------------------------------
    // Render State Commands
    ZWrite On
    ZTest LEqual
    ColorMask 0
    Cull[_Cull]

    HLSLPROGRAM
    #pragma target 2.0

    // -------------------------------------
    // Shader Stages
    #pragma vertex ShadowPassVertex
    #pragma fragment ShadowPassFragment

    // -------------------------------------
    // Material Keywords
    #pragma shader_feature_local _ALPHATEST_ON
    #pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A

    //--------------------------------------
    // GPU Instancing
    #pragma multi_compile_instancing
    #include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DOTS.hlsl"

    // -------------------------------------
    // Universal Pipeline keywords

    // -------------------------------------
    // Unity defined keywords
    #pragma multi_compile_fragment _ LOD_FADE_CROSSFADE

    // This is used during shadow map generation to differentiate between directional and punctual light shadows, as they use different formulas to apply Normal Bias
    #pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW

    // -------------------------------------
    // Includes
    #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
    #include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
    ENDHLSL
}

ShadowCasterPass.hlsl

阴影投射主要计算在顶点计算位置,片元不需要处理纹理颜色阴影返回0给黑色。

cpp 复制代码
#ifndef UNIVERSAL_SHADOW_CASTER_PASS_INCLUDED
#define UNIVERSAL_SHADOW_CASTER_PASS_INCLUDED

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
#if defined(LOD_FADE_CROSSFADE)
    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/LODCrossFade.hlsl"
#endif

// Shadow Casting Light geometric parameters. These variables are used when applying the shadow Normal Bias and are set by UnityEngine.Rendering.Universal.ShadowUtils.SetupShadowCasterConstantBuffer in com.unity.render-pipelines.universal/Runtime/ShadowUtils.cs
// For Directional lights, _LightDirection is used when applying shadow Normal Bias.
// For Spot lights and Point lights, _LightPosition is used to compute the actual light direction because it is different at each shadow caster geometry vertex.
float3 _LightDirection;
float3 _LightPosition;

struct Attributes
{
    float4 positionOS   : POSITION;
    float3 normalOS     : NORMAL;
    float2 texcoord     : TEXCOORD0;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct Varyings
{
    #if defined(_ALPHATEST_ON)
        float2 uv       : TEXCOORD0;
    #endif
    float4 positionCS   : SV_POSITION;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

float4 GetShadowPositionHClip(Attributes input)
{
    float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
    float3 normalWS = TransformObjectToWorldNormal(input.normalOS);

#if _CASTING_PUNCTUAL_LIGHT_SHADOW
    float3 lightDirectionWS = normalize(_LightPosition - positionWS);
#else
    float3 lightDirectionWS = _LightDirection;
#endif

    float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, lightDirectionWS));

#if UNITY_REVERSED_Z
    positionCS.z = min(positionCS.z, UNITY_NEAR_CLIP_VALUE);
#else
    positionCS.z = max(positionCS.z, UNITY_NEAR_CLIP_VALUE);
#endif

    return positionCS;
}

Varyings ShadowPassVertex(Attributes input)
{
    Varyings output;
    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_TRANSFER_INSTANCE_ID(input, output);

    #if defined(_ALPHATEST_ON)
        output.uv = TRANSFORM_TEX(input.texcoord, _BaseMap);
    #endif

    output.positionCS = GetShadowPositionHClip(input);
    return output;
}

half4 ShadowPassFragment(Varyings input) : SV_TARGET
{
    UNITY_SETUP_INSTANCE_ID(input);

    #if defined(_ALPHATEST_ON)
        Alpha(SampleAlbedoAlpha(input.uv, TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap)).a, _BaseColor, _Cutoff);
    #endif

    #if defined(LOD_FADE_CROSSFADE)
        LODFadeCrossFade(input.positionCS);
    #endif

    return 0;
}

#endif

DepthOnly Pass

  • 作用‌:生成场景深度图(非直接用于阴影)
  • 输出目标 ‌:_CameraDepthTexture
  • 阴影关联‌:为屏幕空间阴影计算提供场景深度数据,非阴影贴图直接来源‌

三、阴影生成位置

  • 核心Pass ‌:阴影完全由LightMode="ShadowCaster"的Pass生成‌
  • 执行阶段 ‌:在MainLightShadowCasterPass渲染管线阶段完成‌
  • 纹理存储 ‌:
    • 主光源阴影 → _MainLightShadowmapTexture
    • 点光源阴影 → CubeMap形式存储‌

四、深度偏移处理

通过TRANSFER_SHADOW_CASTER_NORMALOFFSET宏:

  • 自动计算法线偏移
  • 解决Shadow Acne(阴影痤疮)和Peter Panning(边缘剥离)问题‌

五、级联阴影优化

URP采用2×2级联图集:

  • 每级对应不同视锥区域
  • 动态分配精度:近距离高精度,远距离低精度‌
  • 通过GetShadowCasterBounds()计算光源影响范围‌

⚠️ 注意事项

  • 物体需包含ShadowCaster Pass才能投射阴影‌
  • 深度测试冲突可能导致阴影缺失,需检查材质配置

【从UnityURP开始探索游戏渲染】专栏-直达

(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)

相关推荐
程序员侠客行5 小时前
Mybatis连接池实现及池化模式
java·后端·架构·mybatis
bobuddy7 小时前
射频收发机架构简介
架构·射频工程
桌面运维家7 小时前
vDisk考试环境IO性能怎么优化?VOI架构实战指南
架构
一个骇客8 小时前
让你的数据成为“操作日志”和“模型饲料”:事件溯源、CQRS与DataFrame漫谈
架构
鹏北海-RemHusband9 小时前
从零到一:基于 micro-app 的企业级微前端模板完整实现指南
前端·微服务·架构
2的n次方_11 小时前
Runtime 内存管理深化:推理批处理下的内存复用与生命周期精细控制
c语言·网络·架构
前端市界12 小时前
用 React 手搓一个 3D 翻页书籍组件,呼吸海浪式翻页,交互体验带感!
前端·架构·github
文艺理科生12 小时前
Nginx 路径映射深度解析:从本地开发到生产交付的底层哲学
前端·后端·架构
C澒12 小时前
Vue 项目渐进式迁移 React:组件库接入与跨框架协同技术方案
前端·vue.js·react.js·架构·系统架构
消失的旧时光-194313 小时前
从 Kotlin 到 Dart:为什么 sealed 是处理「多种返回结果」的最佳方式?
android·开发语言·flutter·架构·kotlin·sealed