GameTech/URP 渲染实战
源码 / Ch26/EdgeDetect.cs
Ch26/EdgeDetect.cs
// 案例 26.9:基于深度 + 法线的 Sobel 描边(屏幕空间描边)。
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

namespace Cookbook.Ch26
{
    [Serializable, VolumeComponentMenuForRenderPipeline("Cookbook/Edge Detect", typeof(UniversalRenderPipeline))]
    public sealed class EdgeDetectVolume : VolumeComponent, IPostProcessComponent
    {
        public ClampedFloatParameter intensity = new ClampedFloatParameter(0f, 0f, 1f);
        [Tooltip("采样间距(像素),越大线越粗")]
        public ClampedFloatParameter thickness = new ClampedFloatParameter(1f, 0.5f, 4f);
        [Tooltip("深度梯度 / 中心深度 超过它算边。越小越敏感")]
        public ClampedFloatParameter depthThreshold = new ClampedFloatParameter(0.15f, 0.001f, 1f);
        [Tooltip("法线梯度超过它算边。设到最大即“只用深度”")]
        public ClampedFloatParameter normalThreshold = new ClampedFloatParameter(1.5f, 0.05f, 4f);
        public ColorParameter edgeColor = new ColorParameter(new Color(0.05f, 0.05f, 0.08f), false, false, true);
        [Tooltip("0 = 线叠在原图上;1 = 只显示线(背景换成 backgroundColor)")]
        public ClampedFloatParameter edgesOnly = new ClampedFloatParameter(0f, 0f, 1f);
        public ColorParameter backgroundColor = new ColorParameter(new Color(0.95f, 0.93f, 0.88f), false, false, true);

        public bool IsActive() => intensity.value > 0f;
        public bool IsTileCompatible() => false;
    }

    public class EdgeDetectFeature : PostFeatureBase
    {
        protected override string ShaderName => "Cookbook/Ch26/EdgeDetect";
        protected override PostPassBase CreatePass(Material m) => new Pass(m);

        class Pass : SimplePostPass<EdgeDetectVolume>
        {
            static readonly int s_Params = Shader.PropertyToID("_EdgeParams");     // x 强度, y 粗细, z 深度阈值, w 法线阈值
            static readonly int s_EdgeColor = Shader.PropertyToID("_EdgeColor");
            static readonly int s_BgColor = Shader.PropertyToID("_EdgeBgColor");   // a = edgesOnly

            public Pass(Material m) : base(m, "Ch26 Edge Detect") { }

            // 告诉 URP:本 Pass 需要 _CameraDepthTexture 和 _CameraNormalsTexture。
            // Normal 会让 URP 额外跑一遍 DepthNormals 预渲染(前向路径下),这是这个效果的主要开销。
            public override ScriptableRenderPassInput RequiredInputs =>
                ScriptableRenderPassInput.Depth | ScriptableRenderPassInput.Normal;

            protected override void Apply(EdgeDetectVolume v, ref RenderingData rd)
            {
                material.SetVector(s_Params, new Vector4(v.intensity.value, v.thickness.value, v.depthThreshold.value, v.normalThreshold.value));
                material.SetColor(s_EdgeColor, v.edgeColor.value);
                var bg = v.backgroundColor.value; bg.a = v.edgesOnly.value;
                material.SetColor(s_BgColor, bg);
            }
        }
    }
}