Shader "Cielonos/MeshEffects/Outline" { Properties { [Header(Outline Core Parameters)] [HDR] _OutlineColor ("Outline Color (HDR)", Color) = (1, 0, 0, 1) [Space(10)] _OutlineWidth ("Base Outline Width", Range(0.0, 0.1)) = 0.01 _MinScreenThickness ("Lower Bound (Distance-Based Width)", Range(0.0, 10.0)) = 1.0 _OutlineScale ("Outline Transition Scale (Animation)", Range(0.0, 1.0)) = 1.0 [Toggle] _UseVertexColorMask ("Use Vertex Color Alpha as Mask", Float) = 0.0 [Header(Flow Texture)] _OutlineTexture ("Flow Texture", 2D) = "white" {} _FlowSpeedU ("Flow Speed U", Float) = 0.0 _FlowSpeedV ("Flow Speed V", Float) = 0.0 [Header(Render State)] [Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend ("Src Blend", Float) = 1 // One [Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("Dst Blend", Float) = 0 // Zero [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Culling", Float) = 1 // Front [Enum(Off,0,On,1)] _ZWrite ("Z Write", Float) = 1 [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest("Z Test", Float) = 4 // LEqual } SubShader { Tags { // 如果作为基于屏幕后期组合之前的几何体Pass,可以选用更合适的 RenderType // 此处设定为不透明,并且在常规几何体之后绘制 "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="AlphaTest+50" } Pass { Name "CharacterStateOutline" Tags { "LightMode" = "SRPDefaultUnlit" } Cull [_Cull] ZWrite [_ZWrite] ZTest [_ZTest] Blend [_SrcBlend] [_DstBlend] HLSLPROGRAM // 因为明确是 PC 端,直接上 Target 4.5 获得更好的指令支持 #pragma target 4.5 #pragma vertex vert #pragma fragment frag // 暴露 keyword 给面板复选框 #pragma shader_feature_local _USEVERTEXCOLORMASK_ON // 包含 URP 核心库 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; float4 color : COLOR; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID UNITY_VERTEX_OUTPUT_STEREO }; // Cbuffer 声明以支持 SRP Batcher 优化,这在 Render Graph 下极其重要 CBUFFER_START(UnityPerMaterial) half4 _OutlineColor; float _OutlineWidth; float _MinScreenThickness; float _OutlineScale; float _FlowSpeedU; float _FlowSpeedV; float4 _OutlineTexture_ST; CBUFFER_END TEXTURE2D(_OutlineTexture); SAMPLER(sampler_OutlineTexture); Varyings vert(Attributes input) { Varyings output = (Varyings)0; UNITY_SETUP_INSTANCE_ID(input); UNITY_TRANSFER_INSTANCE_ID(input, output); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); // --- 描边核心算法:距离感知的自适应法线外扩 --- // 1. 获取世界空间位置与法线 float3 positionWS = TransformObjectToWorld(input.positionOS.xyz); float3 normalWS = TransformObjectToWorldNormal(input.normalOS); // 2. 计算摄像机到顶点的真实距离 // 我们在最高质量要求下不使用带透视除法的 ClipSpace.w,而是精确判断到相机的绝对距离 float distToCam = distance(GetCameraPositionWS(), positionWS); // 3. 动态控制描边宽度:基础宽度 vs 结合距离修正的最小宽度 // 解释:这实际上是通过构建一个根据距离放大的“屏占比安全下界”。 // _MinScreenThickness * 0.001 意味着参数面板上给个 1.0 时,能得到极好控制感 float minWidth = _MinScreenThickness * distToCam * 0.001; float finalWidth = max(_OutlineWidth, minWidth); // 应用全局缩放系数,用于实现淡出/消失的动画 finalWidth *= _OutlineScale; // 4. 获取遮罩影响(如果启用了顶点颜色通道遮罩) #ifdef _USEVERTEXCOLORMASK_ON // 利用顶点色的 Alpha 通道进行描边权重遮罩(如脸部不想要描边,可刷为0) finalWidth *= input.color.a; #endif // 5. 沿法线方向挤出顶点 positionWS += normalize(normalWS) * finalWidth; // 6. 转换回裁剪空间 output.positionCS = TransformWorldToHClip(positionWS); // 传递并处理流动 UV 的 Tiling 和 Offset output.uv = TRANSFORM_TEX(input.uv, _OutlineTexture); return output; } half4 frag(Varyings input) : SV_Target { UNITY_SETUP_INSTANCE_ID(input); UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); // UV 滚动逻辑 float2 flowOffset = float2(_FlowSpeedU, _FlowSpeedV) * _Time.y; float2 finalUV = input.uv + flowOffset; // 采样流动纹理,由于是作为 Mask 或者渐变,使用最高精度的采样器 half4 flowTexColor = SAMPLE_TEXTURE2D(_OutlineTexture, sampler_OutlineTexture, finalUV); // 将流光纹理和高动态范围(HDR)的发光颜色完美结合 // 半精度 half4 已足够满足输出和 Bloom 后处理需求 half4 finalColor = _OutlineColor * flowTexColor; return finalColor; } ENDHLSL } } // Editor Fallback,发生编译错误时会高亮为品红 FallBack "Hidden/Universal Render Pipeline/FallbackError" }