这篇是给 Unity 程序用的 URP Shader 自查表:少讲玄学,多放结构、宏、模板、常用效果和排错。
写 Shader 时可以先看索引,再复制对应模板改变量名。
快速索引
按任务查
必背最短表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| ShaderLab # Unity Shader 外层结构:Properties / SubShader / Pass HLSLPROGRAM # URP 使用 HLSL 代码块 Core.hlsl # URP 基础函数、矩阵、坐标变换 Lighting.hlsl # URP 光照、Light、阴影、Lambert、PBR 相关函数 RenderPipeline # SubShader Tag,URP 写 UniversalPipeline LightMode # Pass Tag,决定 Pass 在 URP 哪个阶段执行 UnityPerMaterial # 材质属性 CBUFFER,SRP Batcher 关键 Attributes # 顶点输入结构 Varyings # 顶点到片元插值结构 POSITION / NORMAL / TEXCOORD0 # Mesh 顶点输入语义 SV_POSITION # 裁剪空间输出语义 SV_Target # 片元颜色输出语义 TransformObjectToHClip # 物体空间坐标转裁剪空间 GetVertexPositionInputs # 一次拿 WS / VS / CS / NDC 坐标 GetVertexNormalInputs # 一次拿世界空间 normal / tangent / bitangent TEXTURE2D / SAMPLER # URP 纹理和采样器声明 SAMPLE_TEXTURE2D # URP 纹理采样 TRANSFORM_TEX # 应用 Tiling / Offset GetMainLight # 获取主光源 GetAdditionalLight # 获取附加光源 LIGHT_LOOP_BEGIN # URP 附加光循环 ComputeScreenPos # 屏幕坐标相关 SampleSceneDepth # 采样相机深度图 ComputeWorldSpacePosition # 从深度和屏幕 UV 重建世界坐标
|
0. 版本与使用约定
0.1 版本说明
1 2 3 4
| 主要目标:URP 12+ / 14+ / 16+ / Unity 6 URP 常用项目:Unity 2021 LTS、2022 LTS、2023、Unity 6 写法倾向:ShaderLab + HLSL,不依赖 Shader Graph 光照目标:先能写 Unlit、自定义光照、常见特效,再补 PBR
|
注意:
1 2 3 4
| URP 包版本不同,部分内部函数签名可能变化 基础 include、Pass Tag、SRP Batcher 写法相对稳定 复杂 PBR 建议参考当前项目 PackageCache 里的 Lit.shader 需要长期维护的材质,优先少碰 URP 内部未公开函数
|
0.2 文件位置
1 2 3 4
| Assets/Shaders/xxx.shader # ShaderLab 文件 Assets/Shaders/HLSL/xxx.hlsl # 可复用 HLSL include Assets/Materials/xxx.mat # 材质 Assets/Textures/xxx.png # 贴图
|
0.3 命名约定
1 2 3 4 5 6 7 8 9 10 11 12
| positionOS # Object Space,物体空间 positionWS # World Space,世界空间 positionVS # View Space,观察空间 positionCS # Clip Space,裁剪空间 positionHCS # Homogeneous Clip Space,齐次裁剪空间 positionNDC # Normalized Device Coordinates,归一化设备坐标 normalOS # 物体空间法线 normalWS # 世界空间法线 tangentOS # 物体空间切线 tangentWS # 世界空间切线 viewDirWS # 世界空间视线方向 uv # UV
|
0.4 float / half / fixed 怎么选
1 2 3 4
| float value; // 高精度,坐标、深度、世界位置、矩阵计算优先用 half value; // 中精度,颜色、法线、光照、移动端常用 int value; // 整数计数、索引 bool value; // 条件开关,注意分支成本
|
URP 里不要再用 Built-in 时代的 fixed:
1 2 3 4
| half4 frag(Varyings IN) : SV_Target { return half4(1, 1, 1, 1); // URP 常用 half4 输出颜色 }
|
0.5 写 Shader 的推荐顺序
1 2 3 4 5 6 7
| 先写最小 Unlit,让材质不粉 加 Properties 和 UnityPerMaterial 加贴图采样和 UV 加渲染状态:Opaque / Transparent / AlphaClip 加光照:主光源 -> 阴影 -> 附加光 加效果:溶解 / Rim / 扰动 / 描边 最后处理性能、关键字、变体和平台差异
|
1. URP Shader 心智模型
1.1 GPU 管线最短版
1 2 3 4 5 6
| Mesh 顶点数据 -> Vertex Shader # 处理每个顶点,输出裁剪空间坐标和插值数据 -> Rasterization # 光栅化,把三角形变成像素片元 -> Fragment Shader # 处理每个像素,输出颜色 -> Depth / Blend / Stencil # 深度、混合、模板测试 -> Frame Buffer # 最终画面
|
1.2 Unity 程序员关注什么
1 2 3 4 5
| 顶点阶段:位置变换、顶点动画、传 UV / 法线 / 世界坐标 片元阶段:采贴图、算颜色、算光照、透明裁剪、溶解 Pass 状态:是否写深度、是否透明混合、是否剔除背面 材质属性:Inspector 参数、C# 传参、SRP Batcher 是否兼容 变体关键字:功能开关、光照阴影开关、构建体积
|
1.3 URP 和 Built-in 最大区别
1 2 3 4 5 6 7
| CGPROGRAM -> HLSLPROGRAM UnityCG.cginc -> Core.hlsl / Lighting.hlsl UnityObjectToClipPos -> TransformObjectToHClip appdata / v2f -> Attributes / Varyings sampler2D / tex2D -> TEXTURE2D / SAMPLE_TEXTURE2D 固定管线光照宏 -> URP ShaderLibrary 函数 材质属性散放 -> UnityPerMaterial CBUFFER
|
2. 最小 URP Shader 骨架
2.1 最小可显示 Unlit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| Shader "Xiaoyun/URP/MinimumUnlit" { Properties { [MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1) }
SubShader { Tags { "RenderPipeline" = "UniversalPipeline" // 声明这是 URP SubShader "RenderType" = "Opaque" // 不透明对象 "Queue" = "Geometry" // 不透明队列 }
Pass { Name "ForwardUnlit" Tags { "LightMode" = "UniversalForward" }
HLSLPROGRAM #pragma vertex vert #pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes { float4 positionOS : POSITION; // 模型顶点,物体空间 };
struct Varyings { float4 positionCS : SV_POSITION; // 裁剪空间位置,必须输出 };
CBUFFER_START(UnityPerMaterial) half4 _BaseColor; // 材质颜色,放进 UnityPerMaterial CBUFFER_END
Varyings vert(Attributes IN) { Varyings OUT; OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz); return OUT; }
half4 frag(Varyings IN) : SV_Target { return _BaseColor; } ENDHLSL } } }
|
2.2 最小模板必有项
1 2 3 4 5 6 7 8 9 10 11 12
| Shader "路径/名字" # Inspector 里选择 Shader 的路径 Properties # 材质面板参数 SubShader # 一套渲染实现 Tags { "RenderPipeline"="UniversalPipeline" } # URP 识别 Pass # 一次渲染 Pass HLSLPROGRAM / ENDHLSL # HLSL 代码块 #pragma vertex vert # 顶点函数入口 #pragma fragment frag # 片元函数入口 Core.hlsl # 基础 URP 函数 TransformObjectToHClip # 顶点转裁剪空间 SV_POSITION # 顶点输出位置语义 SV_Target # 片元颜色输出语义
|
2.3 常见漏项现象
1 2 3 4 5
| 没写 RenderPipeline = UniversalPipeline # 可能被 URP 跳过或表现异常 没 include Core.hlsl # TransformObjectToHClip 找不到 没输出 SV_POSITION # 顶点阶段输出不合法 材质属性没进 UnityPerMaterial # SRP Batcher 不兼容 Pass Tag 不对 # 阴影、深度、SSAO、Forward 阶段不执行
|
3. ShaderLab 结构速查
3.1 完整结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| Shader "Folder/ShaderName" { Properties { _BaseColor("Base Color", Color) = (1,1,1,1) _BaseMap("Base Map", 2D) = "white" {} }
SubShader { Tags { "RenderPipeline"="UniversalPipeline" "RenderType"="Opaque" "Queue"="Geometry" }
Pass { Name "PassName" Tags { "LightMode"="UniversalForward" }
Cull Back ZWrite On ZTest LEqual
HLSLPROGRAM #pragma vertex vert #pragma fragment frag ENDHLSL } }
FallBack Off }
|
3.2 Properties 类型
1 2 3 4 5 6 7
| _Color("Color", Color) = (1,1,1,1) // 颜色 _Vector("Vector", Vector) = (0,0,0,0) // 四维向量 _Float("Float", Float) = 1 // 浮点 _Range("Range", Range(0,1)) = 0.5 // 滑条 _Int("Int", Int) = 1 // 整数 _BaseMap("Base Map", 2D) = "white" {} // 2D 贴图 _Cube("Cube Map", Cube) = "" {} // Cubemap
|
3.3 常用 Inspector 属性装饰
1 2 3 4 5 6 7
| [MainTexture] _BaseMap("Base Map", 2D) = "white" {} // 主贴图 [MainColor] _BaseColor("Base Color", Color) = (1,1,1,1) // 主颜色 [NoScaleOffset] _MaskMap("Mask", 2D) = "white" {} // 不显示 Tiling / Offset [Normal] _NormalMap("Normal Map", 2D) = "bump" {} // 法线贴图槽 [HDR] _EmissionColor("Emission", Color) = (0,0,0,0) // HDR 颜色 [Toggle] _UseRim("Use Rim", Float) = 0 // 0/1 开关 [Enum(UnityEngine.Rendering.CullMode)] _Cull("Cull", Float) = 2 // 枚举
|
3.4 属性名建议
1 2 3 4 5 6 7 8 9 10 11
| _BaseMap # 主贴图,URP 常用 _BaseColor # 主颜色,URP 常用 _NormalMap # 法线贴图 _MaskMap # 遮罩图 _EmissionMap # 自发光贴图 _EmissionColor # 自发光颜色 _Cutoff # Alpha Clip 阈值 _Metallic # 金属度 _Smoothness # 光滑度 _RimColor # 边缘光颜色 _RimPower # 边缘光强度
|
3.5 Shader 名字路径
1 2 3 4
| Shader "Xiaoyun/URP/UnlitTexture" // 自己的 Shader Shader "Xiaoyun/URP/FX/Dissolve" // 特效类 Shader "Xiaoyun/URP/Character/Toon" // 角色类 Shader "Xiaoyun/URP/UI/CustomImage" // UI 类
|
1 2 3 4 5 6
| Tags { "RenderPipeline" = "UniversalPipeline" // URP 必备 "RenderType" = "Opaque" // 渲染类型,用于替换 Shader、深度等 "Queue" = "Geometry" // 渲染队列 }
|
4.2 Queue 常用值
| Queue |
数值 |
用途 |
Background |
1000 |
天空、背景 |
Geometry |
2000 |
默认不透明 |
AlphaTest |
2450 |
Alpha Clip,例如树叶、草 |
Transparent |
3000 |
半透明 |
Overlay |
4000 |
覆盖层 |
1 2 3 4 5
| Tags { "Queue"="Geometry" } // 不透明 Tags { "Queue"="AlphaTest" } // 裁剪透明 Tags { "Queue"="Transparent" } // 半透明 Tags { "Queue"="Geometry+10" } // 比普通不透明稍晚 Tags { "Queue"="Transparent-10" } // 比普通透明稍早
|
4.3 RenderType 常用值
1 2 3
| Tags { "RenderType"="Opaque" } // 不透明 Tags { "RenderType"="TransparentCutout" } // Alpha Clip Tags { "RenderType"="Transparent" } // 半透明
|
4.4 Pass LightMode 常用值
| LightMode |
用途 |
UniversalForward |
URP Forward 主渲染 Pass,常用 |
UniversalForwardOnly |
强制走 Forward,Deferred 时也按 Forward 渲染 |
UniversalGBuffer |
Deferred GBuffer Pass |
ShadowCaster |
投射阴影 |
DepthOnly |
写入深度 |
DepthNormalsOnly |
写入深度和法线,SSAO 等会用到 |
Universal2D |
URP 2D Renderer |
SRPDefaultUnlit |
未指定 LightMode 时的默认类型之一 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Pass { Name "ForwardLit" Tags { "LightMode"="UniversalForward" } }
Pass { Name "ShadowCaster" Tags { "LightMode"="ShadowCaster" } }
Pass { Name "DepthOnly" Tags { "LightMode"="DepthOnly" } }
|
4.5 常见搭配
1 2 3 4 5 6 7
| 普通不透明:Queue Geometry + RenderType Opaque + ZWrite On + Blend Off 树叶草地:Queue AlphaTest + RenderType TransparentCutout + ZWrite On + clip 半透明特效:Queue Transparent + RenderType Transparent + ZWrite Off + Blend SrcAlpha OneMinusSrcAlpha 加法特效:Queue Transparent + ZWrite Off + Blend One One 描边 Pass:通常 Cull Front,把背面外扩 阴影 Pass:LightMode ShadowCaster 深度 Pass:LightMode DepthOnly + ColorMask 0
|
5. Pass 渲染状态
5.1 Cull 剔除
1 2 3
| Cull Back // 默认,剔除背面,常用不透明物体 Cull Front // 剔除正面,常用于描边外扩 Pass Cull Off // 双面渲染,常用于叶子、布料、纸片特效
|
5.2 ZWrite 深度写入
1 2
| ZWrite On // 写深度,常用不透明和 Alpha Clip ZWrite Off // 不写深度,常用半透明
|
5.3 ZTest 深度测试
1 2 3 4
| ZTest LEqual // 默认,小于等于已有深度就通过 ZTest Less // 更严格,只小于才通过 ZTest Greater // 被遮挡部分才显示,常用于 XRay ZTest Always // 永远通过,常用于屏幕覆盖效果
|
5.4 Blend 混合
1 2 3 4 5
| Blend Off // 不混合,不透明 Blend SrcAlpha OneMinusSrcAlpha // 普通透明 Blend One One // 加法发光 Blend One OneMinusSrcAlpha // 预乘 Alpha Blend DstColor Zero // 乘法
|
5.5 ColorMask
1 2 3
| ColorMask RGB // 写 RGB,不写 A ColorMask A // 只写 Alpha ColorMask 0 // 不写颜色,常用于 DepthOnly
|
5.6 Offset
1 2
| Offset 1, 1 // 深度偏移,减少 Z-fighting Offset -1, -1 // 往镜头方向偏一点,谨慎使用
|
5.7 常用状态模板
不透明:
1 2 3 4 5
| Tags { "Queue"="Geometry" "RenderType"="Opaque" } Cull Back ZWrite On ZTest LEqual Blend Off
|
Alpha Clip:
1 2 3 4 5
| Tags { "Queue"="AlphaTest" "RenderType"="TransparentCutout" } Cull Back ZWrite On ZTest LEqual Blend Off
|
普通透明:
1 2 3 4 5
| Tags { "Queue"="Transparent" "RenderType"="Transparent" } Cull Back ZWrite Off ZTest LEqual Blend SrcAlpha OneMinusSrcAlpha
|
加法特效:
1 2 3 4
| Tags { "Queue"="Transparent" "RenderType"="Transparent" } Cull Off ZWrite Off Blend One One
|
XRay 被遮挡显示:
1 2 3 4
| Tags { "Queue"="Transparent" "RenderType"="Transparent" } ZWrite Off ZTest Greater Blend SrcAlpha OneMinusSrcAlpha
|
6. HLSL 类型、语义与命名
6.1 常用类型
1 2 3 4 5 6 7 8 9 10 11
| float // 32 位浮点,坐标和矩阵优先 float2 // 二维向量,UV float3 // 三维向量,位置、法线、方向 float4 // 四维向量,颜色、齐次坐标 half // 半精度,颜色、光照、移动端常用 half2 half3 half4 int uint bool
|
6.2 常用语义
1 2 3 4 5 6 7 8 9 10
| POSITION // 顶点位置输入 NORMAL // 顶点法线输入 TANGENT // 顶点切线输入 COLOR // 顶点色输入 TEXCOORD0 // UV0 或自定义插值 TEXCOORD1 // UV1 或自定义插值 TEXCOORD2 // 自定义插值 SV_POSITION // 顶点 Shader 输出到光栅化的裁剪空间位置 SV_Target // Fragment Shader 输出颜色 SV_Depth // Fragment Shader 输出深度,少用
|
6.3 Attributes 常用写法
1 2 3 4 5 6 7 8 9
| struct Attributes { float4 positionOS : POSITION; // 模型顶点 float3 normalOS : NORMAL; // 模型法线 float4 tangentOS : TANGENT; // 模型切线,w 表示副切线方向 float2 uv : TEXCOORD0; // 第一套 UV float2 uv2 : TEXCOORD1; // 第二套 UV,常给 lightmap half4 color : COLOR; // 顶点色 };
|
6.4 Varyings 常用写法
1 2 3 4 5 6 7 8
| struct Varyings { float4 positionCS : SV_POSITION; // 必须给光栅化 float2 uv : TEXCOORD0; // 传给片元阶段 float3 positionWS : TEXCOORD1; // 世界坐标 half3 normalWS : TEXCOORD2; // 世界法线 half4 color : COLOR; // 顶点色 };
|
6.5 插值数量注意
1 2 3 4
| TEXCOORD 插值越多,带宽越高 移动端少传 float4,多用 half2 / half3 能片元里算的未必都要顶点传,但片元计算次数更高 世界坐标、法线、UV 是最常传的三类
|
6.6 分支和函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| saturate(x) // clamp 到 0~1 lerp(a, b, t) // 插值 step(edge, x) // x >= edge ? 1 : 0 smoothstep(a, b, x) // 平滑过渡 dot(a, b) // 点乘 cross(a, b) // 叉乘 normalize(v) // 单位化 length(v) // 长度 distance(a, b) // 距离 pow(x, p) // 幂,移动端注意成本 abs(x) // 绝对值 frac(x) // 小数部分 floor(x) // 向下取整 ceil(x) // 向上取整
|
7. URP 常用 include
7.1 Core.hlsl
1
| #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
常用内容:
1 2 3 4 5 6 7 8 9 10 11 12 13
| TransformObjectToHClip TransformObjectToWorld TransformWorldToHClip GetVertexPositionInputs GetVertexNormalInputs GetWorldSpaceViewDir GetWorldSpaceNormalizeViewDir _Time _SinTime _CosTime _ProjectionParams _ScreenParams _ScaledScreenParams
|
7.2 Lighting.hlsl
1
| #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
|
常用内容:
1 2 3 4 5 6 7 8 9
| Light GetMainLight GetAdditionalLight GetAdditionalLightsCount LightingLambert LightingSpecular UniversalFragmentPBR UniversalFragmentBlinnPhong TransformWorldToShadowCoord
|
7.3 RealtimeLights.hlsl
1
| #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/RealtimeLights.hlsl"
|
常用内容:
1 2 3 4
| Forward / Forward+ 附加光循环 GetAdditionalLight LIGHT_LOOP_BEGIN LIGHT_LOOP_END
|
7.4 DeclareDepthTexture.hlsl
1
| #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
|
常用内容:
1 2
| SampleSceneDepth # 采样相机深度图 _CameraDepthTexture # 相机深度纹理
|
使用前检查:
1 2 3
| URP Asset 勾选 Depth Texture Camera 允许生成 Depth Texture 透明物体通常不会写入常规深度
|
7.5 DeclareOpaqueTexture.hlsl
1
| #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareOpaqueTexture.hlsl"
|
常用内容:
1 2
| SampleSceneColor # 采样不透明物体颜色 _CameraOpaqueTexture # 相机不透明纹理
|
使用前检查:
1 2 3
| URP Asset 勾选 Opaque Texture 透明队列里采样更常见 用来做热扭曲、玻璃、屏幕扰动
|
7.6 不要乱 include
1 2 3 4 5
| Unlit:Core.hlsl 足够 简单光照:Lighting.hlsl 深度图:Core.hlsl + DeclareDepthTexture.hlsl 屏幕颜色:Core.hlsl + DeclareOpaqueTexture.hlsl 附加光 Forward+:RealtimeLights.hlsl 或 Lighting.hlsl 中相关支持
|
8. 坐标空间转换
8.1 一句话记法
1 2 3 4 5
| OS:模型本地 WS:世界 VS:相机观察 CS/HCS:裁剪空间,给 SV_POSITION NDC:透视除法后的屏幕相关坐标
|
8.2 常用转换函数
1 2 3
| float4 positionCS = TransformObjectToHClip(positionOS.xyz); // OS -> Clip float3 positionWS = TransformObjectToWorld(positionOS.xyz); // OS -> WS float4 clipPos = TransformWorldToHClip(positionWS); // WS -> Clip
|
8.3 一次拿多种位置
1 2 3 4
| VertexPositionInputs posInput = GetVertexPositionInputs(IN.positionOS.xyz);
OUT.positionCS = posInput.positionCS; // 裁剪空间 OUT.positionWS = posInput.positionWS; // 世界空间
|
8.4 一次拿法线、切线、副切线
1 2 3 4 5
| VertexNormalInputs normalInput = GetVertexNormalInputs(IN.normalOS, IN.tangentOS);
OUT.normalWS = normalInput.normalWS; // 世界法线 OUT.tangentWS = normalInput.tangentWS; // 世界切线 OUT.bitangentWS = normalInput.bitangentWS; // 世界副切线
|
8.5 获取视线方向
1
| half3 viewDirWS = GetWorldSpaceNormalizeViewDir(positionWS); // 从点指向相机
|
8.6 屏幕 UV
1
| float2 screenUV = IN.positionCS.xy / _ScaledScreenParams.xy; // 当前像素屏幕 UV
|
_ScaledScreenParams 会考虑动态分辨率,比直接用 _ScreenParams 更适合 URP。
8.7 顶点阶段标准写法
1 2 3 4 5 6 7 8 9 10 11 12 13
| Varyings vert(Attributes IN) { Varyings OUT;
VertexPositionInputs posInput = GetVertexPositionInputs(IN.positionOS.xyz); VertexNormalInputs normalInput = GetVertexNormalInputs(IN.normalOS, IN.tangentOS);
OUT.positionCS = posInput.positionCS; OUT.positionWS = posInput.positionWS; OUT.normalWS = normalInput.normalWS;
return OUT; }
|
9. 纹理采样
9.1 Properties 里声明贴图
1 2
| [MainTexture] _BaseMap("Base Map", 2D) = "white" {} [MainColor] _BaseColor("Base Color", Color) = (1,1,1,1)
|
9.2 HLSL 里声明贴图和采样器
1 2
| TEXTURE2D(_BaseMap); // 贴图对象 SAMPLER(sampler_BaseMap); // 采样器
|
9.3 UnityPerMaterial 里声明 Tiling Offset
1 2 3 4
| CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; // xy = tiling,zw = offset half4 _BaseColor; CBUFFER_END
|
9.4 顶点阶段应用 Tiling Offset
1
| OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap); // 等价于 IN.uv * _BaseMap_ST.xy + _BaseMap_ST.zw
|
9.5 片元阶段采样
1 2 3
| half4 baseTex = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv); half4 color = baseTex * _BaseColor; return color;
|
9.6 完整贴图模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| Shader "Xiaoyun/URP/UnlitTexture" { Properties { [MainTexture] _BaseMap("Base Map", 2D) = "white" {} [MainColor] _BaseColor("Base Color", Color) = (1,1,1,1) }
SubShader { Tags { "RenderPipeline"="UniversalPipeline" "RenderType"="Opaque" "Queue"="Geometry" }
Pass { Name "ForwardUnlit" Tags { "LightMode"="UniversalForward" }
HLSLPROGRAM #pragma vertex vert #pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; half4 _BaseColor; CBUFFER_END
struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; };
struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; };
Varyings vert(Attributes IN) { Varyings OUT; OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz); OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap); return OUT; }
half4 frag(Varyings IN) : SV_Target { half4 baseTex = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv); return baseTex * _BaseColor; } ENDHLSL } } }
|
9.7 采样指定 mip
1
| half4 col = SAMPLE_TEXTURE2D_LOD(_BaseMap, sampler_BaseMap, uv, 0); // mip 0
|
9.8 法线贴图采样
1 2 3 4 5
| TEXTURE2D(_NormalMap); SAMPLER(sampler_NormalMap);
half4 normalSample = SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, uv); half3 normalTS = UnpackNormal(normalSample); // 切线空间法线
|
9.9 Mask 图通道约定
1 2 3 4
| R:金属度 / 溶解 / 自定义遮罩 G:粗糙度 / AO / 自定义遮罩 B:细节遮罩 A:平滑度 / 透明度
|
示例:
1 2 3 4
| half4 mask = SAMPLE_TEXTURE2D(_MaskMap, sampler_MaskMap, IN.uv); half dissolveMask = mask.r; half occlusion = mask.g; half smoothness = mask.a;
|
10. 材质属性与 SRP Batcher
10.1 标准写法
1 2 3 4 5 6 7 8 9 10
| TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; half4 _BaseColor; half _Cutoff; half _Metallic; half _Smoothness; CBUFFER_END
|
10.2 什么放 CBUFFER
1 2 3 4
| 放:float / half / int / vector / color / texture_ST / 参数开关 不放:TEXTURE2D 声明 不放:SAMPLER 声明 不放:Unity 内置矩阵
|
10.3 多 Pass 必须一致
1 2 3
| Forward Pass 里 UnityPerMaterial 有 _BaseColor / _Cutoff ShadowCaster Pass 里也要同样声明 _BaseColor / _Cutoff 不同 Pass 的 UnityPerMaterial 不一致,容易 SRP Batcher 不兼容
|
10.4 不推荐写法
1 2 3
| half4 _BaseColor; // 不推荐:散放材质属性 float _Cutoff; // 不推荐:SRP Batcher 可能不兼容 sampler2D _BaseMap; // 不推荐:老 Built-in 写法
|
10.5 Inspector 检查
1 2 3 4
| 选中材质 Inspector 查看 SRP Batcher 显示 Compatible 才比较理想 如果 Not compatible,先查 UnityPerMaterial
|
11. Unlit 常用模板
11.1 纯色
1 2 3 4
| half4 frag(Varyings IN) : SV_Target { return _BaseColor; }
|
11.2 贴图乘颜色
1 2 3 4 5
| half4 frag(Varyings IN) : SV_Target { half4 tex = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv); return tex * _BaseColor; }
|
11.3 顶点色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| struct Attributes { float4 positionOS : POSITION; half4 color : COLOR; };
struct Varyings { float4 positionCS : SV_POSITION; half4 color : COLOR; };
Varyings vert(Attributes IN) { Varyings OUT; OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz); OUT.color = IN.color; return OUT; }
half4 frag(Varyings IN) : SV_Target { return IN.color; }
|
11.4 UV 可视化
1 2 3 4
| half4 frag(Varyings IN) : SV_Target { return half4(IN.uv, 0, 1); // R=U,G=V }
|
11.5 世界坐标可视化
1 2 3 4 5
| half4 frag(Varyings IN) : SV_Target { half3 color = frac(abs(IN.positionWS) * 0.1); return half4(color, 1); }
|
11.6 法线可视化
1 2 3 4 5
| half4 frag(Varyings IN) : SV_Target { half3 n = normalize(IN.normalWS); return half4(n * 0.5 + 0.5, 1); }
|
12. 透明、Alpha Clip 与双面
12.1 Alpha Clip
ShaderLab:
1 2 3 4
| Tags { "RenderPipeline"="UniversalPipeline" "RenderType"="TransparentCutout" "Queue"="AlphaTest" } Cull Back ZWrite On ZTest LEqual
|
HLSL:
1 2 3 4 5 6 7 8 9 10 11 12
| CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; half4 _BaseColor; half _Cutoff; CBUFFER_END
half4 frag(Varyings IN) : SV_Target { half4 col = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv) * _BaseColor; clip(col.a - _Cutoff); // 小于 0 的像素丢弃 return col; }
|
Properties:
1
| _Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5
|
12.2 普通半透明
ShaderLab:
1 2 3
| Tags { "RenderPipeline"="UniversalPipeline" "RenderType"="Transparent" "Queue"="Transparent" } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha
|
HLSL:
1 2 3 4 5
| half4 frag(Varyings IN) : SV_Target { half4 col = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv) * _BaseColor; return col; // alpha 参与 Blend }
|
12.3 加法透明
1 2 3 4
| Tags { "Queue"="Transparent" "RenderType"="Transparent" } Cull Off ZWrite Off Blend One One
|
适合:
12.4 预乘 Alpha
1
| Blend One OneMinusSrcAlpha
|
片元输出:
1 2
| col.rgb *= col.a; // 预乘 return col;
|
12.5 双面透明
1 2 3
| Cull Off ZWrite Off Blend SrcAlpha OneMinusSrcAlpha
|
适合:
12.6 透明排序常见坑
1 2 3 4 5
| 透明通常不写深度,所以前后顺序容易错 大面积透明面片容易互相穿插 粒子和透明模型排序会受 Sorting Priority / Queue 影响 能用 Alpha Clip 就不要用半透明 能拆网格就不要靠一个大透明面解决所有层次
|
13. 主光源 Lambert 光照
13.1 include
1
| #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
|
13.2 顶点传世界法线
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; };
struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float3 positionWS : TEXCOORD1; half3 normalWS : TEXCOORD2; };
Varyings vert(Attributes IN) { Varyings OUT; VertexPositionInputs posInput = GetVertexPositionInputs(IN.positionOS.xyz); VertexNormalInputs normalInput = GetVertexNormalInputs(IN.normalOS);
OUT.positionCS = posInput.positionCS; OUT.positionWS = posInput.positionWS; OUT.normalWS = normalInput.normalWS; OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap); return OUT; }
|
13.3 主光源漫反射
1 2 3 4 5 6 7 8 9 10 11 12
| half4 frag(Varyings IN) : SV_Target { half4 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv) * _BaseColor;
half3 normalWS = normalize(IN.normalWS); Light mainLight = GetMainLight();
half NdotL = saturate(dot(normalWS, mainLight.direction)); half3 diffuse = albedo.rgb * mainLight.color * NdotL;
return half4(diffuse, albedo.a); }
|
13.4 带环境底色
1 2 3
| half3 ambient = albedo.rgb * 0.2; half3 finalColor = ambient + diffuse; return half4(finalColor, albedo.a);
|
13.5 Half Lambert
1 2
| half halfLambert = dot(normalWS, mainLight.direction) * 0.5 + 0.5; half3 diffuse = albedo.rgb * mainLight.color * halfLambert;
|
13.6 Blinn-Phong 高光
1 2 3 4 5 6 7
| half3 viewDirWS = GetWorldSpaceNormalizeViewDir(IN.positionWS); half3 halfDir = normalize(mainLight.direction + viewDirWS);
half NdotH = saturate(dot(normalWS, halfDir)); half spec = pow(NdotH, _SpecPower) * _SpecIntensity;
half3 finalColor = diffuse + spec * mainLight.color;
|
Properties:
1 2
| _SpecPower("Spec Power", Range(1, 128)) = 32 _SpecIntensity("Spec Intensity", Range(0, 4)) = 1
|
CBUFFER:
1 2
| half _SpecPower; half _SpecIntensity;
|
14. 附加光源与 Forward Plus
14.1 关键字
include:
1 2 3
| #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/RealtimeLights.hlsl"
|
如果这段 Shader 同时还要用 GetMainLight、LightingLambert、阴影等,也可以直接 include:
1
| #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
|
关键字:
1 2
| #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS #pragma multi_compile _ _CLUSTER_LIGHT_LOOP // Unity 6 / 新 URP Forward+
|
14.2 基础附加光循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| half3 ApplySingleLight(half3 normalWS, half3 albedo, Light light) { half NdotL = saturate(dot(normalWS, light.direction)); half attenuation = light.distanceAttenuation * light.shadowAttenuation; return albedo * light.color * NdotL * attenuation; }
half3 ApplyAdditionalLights(float4 positionCS, float3 positionWS, half3 normalWS, half3 albedo) { half3 result = 0;
// Unity 6 / Forward+ 路径下,LIGHT_LOOP_BEGIN 需要当前作用域存在 inputData InputData inputData = (InputData)0; inputData.positionWS = positionWS; inputData.normalWS = normalWS; inputData.viewDirectionWS = GetWorldSpaceNormalizeViewDir(positionWS); inputData.normalizedScreenSpaceUV = GetNormalizedScreenSpaceUV(positionCS);
#if defined(_ADDITIONAL_LIGHTS)
// Forward+ 里的非主方向光循环 #if USE_CLUSTER_LIGHT_LOOP UNITY_LOOP for (uint lightIndex = 0; lightIndex < min(URP_FP_DIRECTIONAL_LIGHTS_COUNT, MAX_VISIBLE_LIGHTS); lightIndex++) { Light light = GetAdditionalLight(lightIndex, inputData.positionWS, half4(1, 1, 1, 1)); result += ApplySingleLight(inputData.normalWS, albedo, light); } #endif
// Forward 路径里的逐对象附加光循环 uint pixelLightCount = GetAdditionalLightsCount();
LIGHT_LOOP_BEGIN(pixelLightCount) Light light = GetAdditionalLight(lightIndex, inputData.positionWS, half4(1, 1, 1, 1)); result += ApplySingleLight(inputData.normalWS, albedo, light); LIGHT_LOOP_END
#endif
return result; }
|
14.3 片元里合并主光和附加光
1 2 3 4 5 6 7 8 9 10 11 12 13
| half4 frag(Varyings IN) : SV_Target { half4 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv) * _BaseColor; half3 normalWS = normalize(IN.normalWS);
Light mainLight = GetMainLight(); half mainNdotL = saturate(dot(normalWS, mainLight.direction)); half3 color = albedo.rgb * mainLight.color * mainNdotL;
color += ApplyAdditionalLights(IN.positionCS, IN.positionWS, normalWS, albedo.rgb);
return half4(color, albedo.a); }
|
14.4 Forward Plus 注意
1 2 3 4 5 6 7
| Forward+ 对附加光的处理和普通 Forward 不完全一样 Unity 6 / 新 URP 使用 _CLUSTER_LIGHT_LOOP 变体 旧 URP 项目如果包内示例使用 _FORWARD_PLUS,就按当前项目包内宏为准 LIGHT_LOOP_BEGIN 所在作用域需要构造 InputData 至少补 positionWS / normalWS / viewDirectionWS / normalizedScreenSpaceUV Forward+ 下 GetAdditionalLightsCount 可能返回 0,所以要处理 USE_CLUSTER_LIGHT_LOOP 分支 复杂项目里建议直接参考当前 URP 包里的 LitForwardPass.hlsl
|
14.5 附加光排查
1 2 3 4 5 6
| URP Asset 是否开启 Additional Lights 灯光 Render Mode / Layer 是否允许 Shader 是否编译 _ADDITIONAL_LIGHTS Forward+ 是否编译 _CLUSTER_LIGHT_LOOP 或当前 URP 包对应宏 GetAdditionalLightsCount 是否为 0 移动端附加光数量是否被 URP Asset 限制
|
15. 接收阴影
15.1 关键字
1 2 3
| #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN #pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS #pragma multi_compile _ _SHADOWS_SOFT
|
15.2 顶点传世界坐标
1 2 3 4 5 6
| struct Varyings { float4 positionCS : SV_POSITION; float3 positionWS : TEXCOORD1; half3 normalWS : TEXCOORD2; };
|
15.3 片元计算主光阴影
1 2 3 4 5 6 7 8 9 10 11 12 13
| half4 frag(Varyings IN) : SV_Target { half3 normalWS = normalize(IN.normalWS);
float4 shadowCoord = TransformWorldToShadowCoord(IN.positionWS); Light mainLight = GetMainLight(shadowCoord);
half NdotL = saturate(dot(normalWS, mainLight.direction)); half shadow = mainLight.shadowAttenuation;
half3 color = _BaseColor.rgb * mainLight.color * NdotL * shadow; return half4(color, _BaseColor.a); }
|
15.4 阴影值含义
1 2 3
| shadowAttenuation = 1 # 完全受光 shadowAttenuation = 0 # 完全阴影 0~1 # 软阴影或过滤后的过渡
|
15.5 接收阴影不显示排查
1 2 3 4 5 6 7
| URP Asset 开了 Main Light Shadows Light 开了 Cast Shadows 接收物体材质 Shader 编译了主光阴影关键字 投影物体有 ShadowCaster Pass 物体 Renderer 勾选 Cast Shadows 相机距离在 Shadow Distance 内 Layer 没被剔除
|
16. ShadowCaster Pass
16.1 什么时候需要
1 2 3
| 自定义 Shader 想投射阴影 Alpha Clip 物体想按透明轮廓投影 顶点动画物体想投影也跟着变形
|
16.2 简化 ShadowCaster Pass
这是方便理解和改 Alpha Clip 的简化版。正式项目如果遇到阴影偏移、级联阴影边界或平台差异,优先参考当前 URP 包里的 ShadowCasterPass.hlsl。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| Pass { Name "ShadowCaster" Tags { "LightMode"="ShadowCaster" }
ZWrite On ZTest LEqual ColorMask 0 Cull Back
HLSLPROGRAM #pragma vertex ShadowPassVertex #pragma fragment ShadowPassFragment #pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; };
struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; };
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; half4 _BaseColor; half _Cutoff; CBUFFER_END
Varyings ShadowPassVertex(Attributes IN) { Varyings OUT; OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz); OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap); return OUT; }
half4 ShadowPassFragment(Varyings IN) : SV_Target { half alpha = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv).a * _BaseColor.a; clip(alpha - _Cutoff); // Alpha Clip 阴影轮廓 return 0; } ENDHLSL }
|
16.3 顶点动画必须同步到 ShadowCaster
Forward Pass:
1 2 3 4 5 6
| float3 ApplyWind(float3 positionOS, float3 normalOS) { float wave = sin(_Time.y * _WindSpeed + positionOS.x * _WindFrequency); positionOS += normalOS * wave * _WindStrength; return positionOS; }
|
ShadowCaster Pass 也要调用:
1 2
| float3 animatedPosOS = ApplyWind(IN.positionOS.xyz, IN.normalOS); OUT.positionCS = TransformObjectToHClip(animatedPosOS);
|
不然会出现:
1 2 3
| 模型在动,影子不动 影子轮廓和模型对不上 草和树叶阴影穿帮
|
17. 法线、切线与 Normal Map
17.1 需要的顶点输入
1 2 3 4 5 6 7
| struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv : TEXCOORD0; };
|
17.2 顶点输出 TBN
1 2 3 4 5 6 7 8
| struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; half3 normalWS : TEXCOORD1; half3 tangentWS : TEXCOORD2; half3 bitangentWS : TEXCOORD3; };
|
17.3 顶点阶段
1 2 3 4
| VertexNormalInputs normalInput = GetVertexNormalInputs(IN.normalOS, IN.tangentOS); OUT.normalWS = normalInput.normalWS; OUT.tangentWS = normalInput.tangentWS; OUT.bitangentWS = normalInput.bitangentWS;
|
17.4 片元阶段切线空间转世界空间
1 2 3 4 5 6 7 8 9
| half3 normalTS = UnpackNormal(SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, IN.uv));
half3x3 tangentToWorld = half3x3( normalize(IN.tangentWS), normalize(IN.bitangentWS), normalize(IN.normalWS) );
half3 normalWS = normalize(mul(normalTS, tangentToWorld));
|
17.5 法线贴图强度
1 2 3
| half3 normalTS = UnpackNormal(SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, IN.uv)); normalTS.xy *= _NormalScale; normalTS.z = sqrt(1.0 - saturate(dot(normalTS.xy, normalTS.xy)));
|
Properties:
1 2
| [Normal] _NormalMap("Normal Map", 2D) = "bump" {} _NormalScale("Normal Scale", Range(0, 2)) = 1
|
17.6 法线贴图排查
1 2 3 4 5 6
| 贴图 Texture Type 设置为 Normal Map 模型有 tangent 数据 Shader 输入 TANGENT TBN 顺序正确 法线最终 normalize 移动端 normal map 过多会增加采样成本
|
18. Fog 雾效
18.1 顶点输出 fogCoord
1 2 3 4 5
| struct Varyings { float4 positionCS : SV_POSITION; float fogCoord : TEXCOORD1; };
|
18.2 顶点阶段计算
1
| OUT.fogCoord = ComputeFogFactor(OUT.positionCS.z);
|
18.3 片元阶段混合雾
1 2 3
| half3 color = _BaseColor.rgb; color = MixFog(color, IN.fogCoord); return half4(color, _BaseColor.a);
|
18.4 常见忘记项
1 2 3 4
| 项目 Lighting / Environment 开了 Fog Shader 里计算 fogCoord Shader 里调用 MixFog 透明材质雾效根据效果决定是否加
|
19. 深度纹理与屏幕空间
19.1 采样深度前检查
1 2 3 4
| URP Asset -> General -> Depth Texture 开启 Camera 没禁用 Depth Texture 目标物体是否写入深度 透明物体通常不会出现在常规深度里
|
19.2 include
1
| #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
|
19.3 当前像素屏幕 UV
1
| float2 screenUV = IN.positionCS.xy / _ScaledScreenParams.xy;
|
19.4 采样 Scene Depth
1
| real rawDepth = SampleSceneDepth(screenUV);
|
19.5 平台兼容深度
1 2 3 4 5
| #if UNITY_REVERSED_Z real depth = SampleSceneDepth(screenUV); #else real depth = lerp(UNITY_NEAR_CLIP_VALUE, 1, SampleSceneDepth(screenUV)); #endif
|
19.6 重建世界坐标
1
| float3 worldPos = ComputeWorldSpacePosition(screenUV, depth, UNITY_MATRIX_I_VP);
|
19.7 深度差边缘泡沫
1 2 3 4 5 6 7 8
| float2 screenUV = IN.positionCS.xy / _ScaledScreenParams.xy; real sceneDepth = SampleSceneDepth(screenUV);
float sceneEyeDepth = LinearEyeDepth(sceneDepth, _ZBufferParams); float thisEyeDepth = IN.positionCS.w;
half depthDiff = saturate((sceneEyeDepth - thisEyeDepth) / _FoamDistance); half foam = 1 - depthDiff;
|
用途:
1 2 3 4
| 水边泡沫 角色脚底接触光 能量罩与场景交界 软粒子
|
19.8 Opaque Texture 采样
检查:
1 2
| URP Asset -> General -> Opaque Texture 开启 通常用于 Transparent Queue
|
include:
1
| #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareOpaqueTexture.hlsl"
|
采样:
1 2
| float2 screenUV = IN.positionCS.xy / _ScaledScreenParams.xy; half3 sceneColor = SampleSceneColor(screenUV);
|
19.9 屏幕扰动
1 2 3 4
| half2 noise = SAMPLE_TEXTURE2D(_NoiseMap, sampler_NoiseMap, IN.uv).rg * 2 - 1; float2 distortUV = screenUV + noise * _DistortStrength; half3 sceneColor = SampleSceneColor(distortUV); return half4(sceneColor, _Alpha);
|
20. 常见效果片段
20.1 溶解 Dissolve
Properties:
1 2 3 4
| _NoiseMap("Noise", 2D) = "white" {} _Dissolve("Dissolve", Range(0, 1)) = 0 _EdgeWidth("Edge Width", Range(0, 0.2)) = 0.05 [HDR] _EdgeColor("Edge Color", Color) = (1, 0.5, 0, 1)
|
HLSL:
1 2 3 4 5 6
| half noise = SAMPLE_TEXTURE2D(_NoiseMap, sampler_NoiseMap, IN.uv).r; clip(noise - _Dissolve);
half edge = smoothstep(_Dissolve, _Dissolve + _EdgeWidth, noise); half3 color = lerp(_EdgeColor.rgb, baseColor.rgb, edge); return half4(color, baseColor.a);
|
20.2 边缘光 Rim
1 2 3 4 5 6
| half3 viewDirWS = GetWorldSpaceNormalizeViewDir(IN.positionWS); half rim = 1 - saturate(dot(normalize(IN.normalWS), viewDirWS)); rim = pow(rim, _RimPower) * _RimIntensity;
half3 finalColor = baseColor.rgb + rim * _RimColor.rgb; return half4(finalColor, baseColor.a);
|
Properties:
1 2 3
| [HDR] _RimColor("Rim Color", Color) = (0, 0.6, 1, 1) _RimPower("Rim Power", Range(0.5, 8)) = 3 _RimIntensity("Rim Intensity", Range(0, 5)) = 1
|
20.3 Fresnel
1 2
| half fresnel = pow(1 - saturate(dot(normalWS, viewDirWS)), _FresnelPower); half3 finalColor = lerp(baseColor.rgb, _FresnelColor.rgb, fresnel * _FresnelIntensity);
|
20.4 UV 滚动
1 2 3
| float2 uv = IN.uv; uv += _ScrollDirection.xy * _ScrollSpeed * _Time.y; half4 col = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv);
|
Properties:
1 2
| _ScrollDirection("Scroll Direction", Vector) = (1, 0, 0, 0) _ScrollSpeed("Scroll Speed", Float) = 1
|
20.5 扫光
1 2 3
| half band = abs(frac(IN.uv.x + _Time.y * _ScanSpeed) - 0.5) * 2; half scan = 1 - smoothstep(0, _ScanWidth, band); half3 finalColor = baseColor.rgb + scan * _ScanColor.rgb * _ScanIntensity;
|
20.6 Toon 阶梯光
1 2 3 4 5 6
| half NdotL = saturate(dot(normalWS, mainLight.direction)); half toon = smoothstep(_ShadowStep, _ShadowStep + _ShadowFeather, NdotL);
half3 shadowColor = baseColor.rgb * _ShadowColor.rgb; half3 litColor = baseColor.rgb * mainLight.color; half3 finalColor = lerp(shadowColor, litColor, toon);
|
Properties:
1 2 3
| _ShadowStep("Shadow Step", Range(0, 1)) = 0.5 _ShadowFeather("Shadow Feather", Range(0.001, 0.5)) = 0.05 _ShadowColor("Shadow Color", Color) = (0.5, 0.5, 0.5, 1)
|
20.7 外描边 Pass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| Pass { Name "Outline" Tags { "LightMode"="SRPDefaultUnlit" }
Cull Front ZWrite On
HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
CBUFFER_START(UnityPerMaterial) half4 _OutlineColor; half _OutlineWidth; CBUFFER_END
struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; };
struct Varyings { float4 positionCS : SV_POSITION; };
Varyings vert(Attributes IN) { Varyings OUT; float3 posOS = IN.positionOS.xyz + normalize(IN.normalOS) * _OutlineWidth; OUT.positionCS = TransformObjectToHClip(posOS); return OUT; }
half4 frag(Varyings IN) : SV_Target { return _OutlineColor; } ENDHLSL }
|
描边注意:
1 2 3 4
| 法线外扩描边对硬边模型可能裂 屏幕空间等宽描边更复杂 透明物体描边排序更麻烦 角色描边通常需要专门处理法线
|
20.8 XRay 透视轮廓
Pass 状态:
1 2 3
| ZTest Greater ZWrite Off Blend SrcAlpha OneMinusSrcAlpha
|
片元:
1
| return half4(_XRayColor.rgb, _XRayAlpha);
|
20.9 顶点波动
1 2 3 4
| float3 posOS = IN.positionOS.xyz; float wave = sin(_Time.y * _WaveSpeed + posOS.x * _WaveFrequency); posOS.y += wave * _WaveStrength; OUT.positionCS = TransformObjectToHClip(posOS);
|
20.10 草/旗帜简易风
1 2 3 4 5
| float heightMask = saturate(IN.positionOS.y); // 越高摆动越大,按模型调整 float wave = sin(_Time.y * _WindSpeed + IN.positionOS.x * _WindFrequency); float3 posOS = IN.positionOS.xyz; posOS.xz += wave * _WindStrength * heightMask; OUT.positionCS = TransformObjectToHClip(posOS);
|
20.11 闪烁
1 2
| half blink = sin(_Time.y * _BlinkSpeed) * 0.5 + 0.5; half3 finalColor = lerp(baseColor.rgb, _BlinkColor.rgb, blink * _BlinkIntensity);
|
20.12 网格线
1 2 3
| float2 grid = abs(frac(IN.uv * _GridScale) - 0.5); float line = 1 - smoothstep(_LineWidth, _LineWidth + _LineFeather, min(grid.x, grid.y)); half3 finalColor = lerp(baseColor.rgb, _LineColor.rgb, line);
|
21. C# 传参和 MaterialPropertyBlock
21.1 直接改材质
1 2 3 4 5 6 7 8
| [SerializeField] private Material material;
private static readonly int BaseColorId = Shader.PropertyToID("_BaseColor");
private void SetColor(Color color) { material.SetColor(BaseColorId, color); }
|
注意:
1 2 3
| 改 sharedMaterial 会影响所有共用材质的对象 renderer.material 会实例化材质,可能增加内存 大量对象单独参数优先 MaterialPropertyBlock
|
21.2 MaterialPropertyBlock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| using UnityEngine;
public sealed class SetShaderColor : MonoBehaviour { [SerializeField] private Renderer targetRenderer; [SerializeField] private Color color = Color.cyan;
private static readonly int BaseColorId = Shader.PropertyToID("_BaseColor"); private MaterialPropertyBlock block;
private void Awake() { block = new MaterialPropertyBlock(); }
private void Apply() { targetRenderer.GetPropertyBlock(block); block.SetColor(BaseColorId, color); targetRenderer.SetPropertyBlock(block); } }
|
21.3 常用 Set 方法
1 2 3 4
| block.SetFloat(Shader.PropertyToID("_Dissolve"), value); block.SetColor(Shader.PropertyToID("_RimColor"), color); block.SetVector(Shader.PropertyToID("_ScrollDirection"), direction); block.SetTexture(Shader.PropertyToID("_BaseMap"), texture);
|
21.4 开关关键字
1 2
| material.EnableKeyword("_USE_RIM"); material.DisableKeyword("_USE_RIM");
|
URP 新项目也可以使用 Local Keyword API:
1 2
| var keyword = new UnityEngine.Rendering.LocalKeyword(material.shader, "_USE_RIM"); material.SetKeyword(keyword, true);
|
21.5 不同对象不同溶解值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public sealed class DissolveSetter : MonoBehaviour { [SerializeField] private Renderer target; [Range(0, 1)] public float dissolve;
private static readonly int DissolveId = Shader.PropertyToID("_Dissolve"); private MaterialPropertyBlock block;
private void Awake() { block = new MaterialPropertyBlock(); }
private void LateUpdate() { target.GetPropertyBlock(block); block.SetFloat(DissolveId, dissolve); target.SetPropertyBlock(block); } }
|
21.6 全局 Shader 参数
1 2 3
| Shader.SetGlobalFloat("_GlobalWindStrength", windStrength); Shader.SetGlobalVector("_GlobalWindDirection", windDirection); Shader.SetGlobalTexture("_GlobalNoiseTex", noiseTexture);
|
HLSL:
1 2 3 4
| float _GlobalWindStrength; float4 _GlobalWindDirection; TEXTURE2D(_GlobalNoiseTex); SAMPLER(sampler_GlobalNoiseTex);
|
注意:
1 2 3
| 全局参数影响所有使用同名变量的 Shader 适合风、天气、时间、全局雾、全局交互点 不要拿全局参数传单个角色的临时状态
|
22. Shader Keywords 与变体
22.1 shader_feature 和 multi_compile
1 2
| #pragma shader_feature_local _USE_RIM // 材质不用时可剔除变体 #pragma multi_compile _ _MAIN_LIGHT_SHADOWS // 管线功能需要,通常不能随便剔除
|
22.2 常用选择
| 需求 |
推荐 |
| 材质功能开关 |
shader_feature_local |
| 管线光照阴影开关 |
multi_compile |
| 全局质量等级 |
multi_compile 或项目自定义剔除 |
| 只在当前 Shader 内使用 |
带 _local |
22.3 代码中使用
1 2 3
| #if defined(_USE_RIM) color += ApplyRim(normalWS, viewDirWS); #endif
|
22.4 Properties 里做 Toggle
1
| [Toggle(_USE_RIM)] _UseRim("Use Rim", Float) = 0
|
HLSL:
1
| #pragma shader_feature_local _USE_RIM
|
22.5 常见 URP 光照关键字
1 2 3 4 5
| #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS #pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS #pragma multi_compile _ _SHADOWS_SOFT #pragma multi_compile _ _CLUSTER_LIGHT_LOOP
|
22.6 变体爆炸
1 2 3 4 5
| 2 个开关 = 4 个变体 5 个开关 = 32 个变体 10 个开关 = 1024 个变体 每个 Pass 都会受影响 平台越多,编译越久,包体越大
|
22.7 减少变体
1 2 3 4 5 6
| 能用数值分支就别加关键字 同一类效果合并成一个 enum 少在通用 Shader 里塞太多功能 移动端单独做轻量 Shader URP Asset 关闭不用的光照阴影功能 构建前清理没用材质
|
23. 性能优化速查
23.1 片元比顶点贵的场景
1 2 3 4 5 6
| 全屏特效 大面积透明 多层粒子 高分辨率移动端 Overdraw 高的 UI / 特效 复杂光照和多贴图采样
|
23.2 常见成本排序
1 2 3 4 5 6 7 8
| 纹理采样多 透明 Overdraw 动态分支 pow / sin / cos 等复杂函数 多光源循环 高精度 float 过多 大量变体 复杂 ShadowCaster
|
23.3 移动端建议
1 2 3 4 5 6 7
| 颜色和光照优先 half 少用透明叠很多层 少用实时多光源 法线贴图、Mask 图合并通道 少用高频 Grab/Opaque Texture 少用屏幕空间多次采样 少用复杂噪声实时算,优先噪声贴图
|
23.4 贴图采样优化
1 2 3 4 5
| BaseMap + MaskMap + NormalMap 已经是 3 次采样 Mask 多通道打包,减少贴图数量 同采样器复用 不需要 Tiling Offset 的贴图用 [NoScaleOffset] 特效图尽量压缩、降分辨率
|
23.5 透明优化
1 2 3 4 5
| 能 Alpha Clip 就别半透明 粒子贴图留白越少越好 大面片拆小,减少无效透明区域 关闭不必要 ZWrite 需要软粒子才采深度
|
23.6 分支优化
1 2 3 4 5 6 7 8
| // 不一定更快,取决于平台和分支一致性 if (_UseRim > 0.5) { color += rim; }
// 简单开关可用 lerp,避免动态分支 color += rim * _UseRim;
|
23.7 Debug 工具
1 2 3 4 5 6
| Frame Debugger # 看 Pass 顺序、SetPass、Batch RenderDoc # 抓帧看纹理、Shader 输入输出 Profiler Rendering # 看 CPU/GPU 渲染成本 Scene View Overdraw # 看透明重叠 Material Inspector # 看 SRP Batcher compatible Shader Variant Log # 看变体数量
|
24. 常见报错排查
24.1 材质变粉
1 2 3 4 5 6
| Shader 编译失败 URP 项目用了 Built-in Shader 没写 RenderPipeline UniversalPipeline include 路径错误 HLSL 函数或变量名不存在 当前 URP 版本不支持某函数签名
|
优先看:
1 2 3 4 5
| Console 第一条 Shader error 报错文件和行号 当前使用的 URP 包版本 Shader 是否真的保存 材质是否引用了正确 Shader
|
24.2 找不到 Core.hlsl
1 2 3 4
| 项目没安装 URP include 路径拼错 包名写错 当前项目不是 URP 管线
|
正确路径:
1
| #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
1 2 3 4
| 没 include Core.hlsl include 写在函数后面 拼写错误 用了 CGPROGRAM 而不是 HLSLPROGRAM
|
24.4 sampler2D / tex2D 老写法问题
推荐替换:
1 2 3
| TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); half4 col = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv);
|
24.5 SRP Batcher Not Compatible
检查:
1 2 3 4
| 所有材质属性是否在 UnityPerMaterial 多个 Pass 的 UnityPerMaterial 是否一致 是否散放了 _BaseColor / _Cutoff 等属性 是否用了不兼容的 CBUFFER 布局
|
24.6 贴图 Tiling Offset 不生效
检查:
1 2 3 4
| Properties 里贴图没有 [NoScaleOffset] CBUFFER 有 _BaseMap_ST 顶点阶段用了 TRANSFORM_TEX(IN.uv, _BaseMap) 变量名严格匹配:_BaseMap -> _BaseMap_ST
|
24.7 透明显示顺序错
1 2 3 4 5 6
| 半透明不写深度,排序按对象中心和队列 拆分网格 调整 Render Queue 减少透明层叠 改 Alpha Clip 必要时写 Depth Prepass
|
24.8 阴影没有
1 2 3 4 5 6 7 8
| 接收阴影:Forward Pass 里算 shadowAttenuation 投射阴影:Shader 有 ShadowCaster Pass URP Asset 开 Shadows Light 开 Cast Shadows Renderer Cast Shadows 打开 物体 Layer 被 Light 和 Camera 渲染 Shadow Distance 足够 Alpha Clip 阴影 Pass 也做 clip
|
24.9 法线贴图怪
1 2 3 4 5 6
| 贴图导入类型不是 Normal Map 模型没有 Tangent TBN 矩阵方向错 normalTS 没 UnpackNormal normalWS 没 normalize 法线强度过大
|
24.10 Scene Depth 采样全白或全黑
1 2 3 4 5
| URP Asset 没开 Depth Texture 当前 Camera 没生成深度 透明物体不在深度里 深度没处理 UNITY_REVERSED_Z 用了 _ScreenParams 但动态分辨率下 UV 不对
|
25. Built-in 到 URP 迁移
25.1 关键替换表
| Built-in |
URP |
CGPROGRAM |
HLSLPROGRAM |
ENDCG |
ENDHLSL |
UnityCG.cginc |
Core.hlsl |
Lighting.cginc |
Lighting.hlsl |
appdata |
Attributes |
v2f |
Varyings |
UnityObjectToClipPos(v.vertex) |
TransformObjectToHClip(positionOS.xyz) |
sampler2D |
TEXTURE2D + SAMPLER |
tex2D |
SAMPLE_TEXTURE2D |
fixed4 |
half4 |
| 散放材质属性 |
CBUFFER_START(UnityPerMaterial) |
25.2 迁移步骤
1 2 3 4 5 6 7 8 9
| 改 HLSLPROGRAM / ENDHLSL include Core.hlsl SubShader Tags 加 RenderPipeline UniversalPipeline appdata/v2f 改 Attributes/Varyings 坐标变换改 TransformObjectToHClip 贴图采样改 TEXTURE2D / SAMPLE_TEXTURE2D 材质属性放 UnityPerMaterial 需要光照就 include Lighting.hlsl 需要阴影就补关键字和 ShadowCaster Pass
|
25.3 Built-in 老代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| CGPROGRAM #include "UnityCG.cginc"
sampler2D _MainTex; fixed4 _Color;
v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord; return o; }
fixed4 frag(v2f i) : SV_Target { return tex2D(_MainTex, i.uv) * _Color; } ENDCG
|
25.4 URP 新代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| HLSLPROGRAM #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; half4 _BaseColor; CBUFFER_END
Varyings vert(Attributes IN) { Varyings OUT; OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz); OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap); return OUT; }
half4 frag(Varyings IN) : SV_Target { return SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv) * _BaseColor; } ENDHLSL
|
26. 写完 Shader 自查清单
26.1 基础检查
1 2 3 4 5 6 7
| 材质不粉 Console 没 Shader error RenderPipeline Tag 正确 LightMode Tag 正确 Properties 和 CBUFFER 变量名一致 贴图 _ST 和 TRANSFORM_TEX 正确 SRP Batcher Compatible
|
26.2 渲染检查
1 2 3 4 5 6 7 8
| Opaque / Transparent / AlphaTest 队列正确 ZWrite 是否符合预期 Blend 是否符合预期 Cull 是否符合预期 阴影投射是否正常 阴影接收是否正常 Depth Texture / Opaque Texture 依赖是否打开 Forward / Forward+ 都测试过
|
26.3 平台检查
1 2 3 4 5
| Game View 和 Scene View 都看过 目标平台真机跑过 移动端半精度表现正常 OpenGL / Vulkan / D3D 深度方向处理正确 动态分辨率下屏幕 UV 正确
|
26.4 性能检查
1 2 3 4 5 6
| 贴图采样次数可接受 透明 Overdraw 可接受 关键字数量可控 Frame Debugger Pass 数正常 Profiler 没异常 GPU 峰值 变体编译时间可接受
|
26.5 团队维护检查
1 2 3 4 5 6
| 变量命名符合项目规范 Shader 名字路径清楚 材质参数 Tooltip / 名字能看懂 特效参数范围合理 公共 HLSL include 没乱放业务逻辑 复杂效果有注释
|
27. 常用完整模板:Alpha Clip Lit
下面是一个偏实用的 Alpha Clip + 主光 + 阴影模板,适合草、树叶、纸片、简单角色局部材质继续改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| Shader "Xiaoyun/URP/AlphaClipMainLight" { Properties { [MainTexture] _BaseMap("Base Map", 2D) = "white" {} [MainColor] _BaseColor("Base Color", Color) = (1,1,1,1) _Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5 }
SubShader { Tags { "RenderPipeline"="UniversalPipeline" "RenderType"="TransparentCutout" "Queue"="AlphaTest" }
Pass { Name "ForwardLit" Tags { "LightMode"="UniversalForward" }
Cull Back ZWrite On ZTest LEqual
HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN #pragma multi_compile _ _SHADOWS_SOFT
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; half4 _BaseColor; half _Cutoff; CBUFFER_END
struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; };
struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float3 positionWS : TEXCOORD1; half3 normalWS : TEXCOORD2; };
Varyings vert(Attributes IN) { Varyings OUT; VertexPositionInputs posInput = GetVertexPositionInputs(IN.positionOS.xyz); VertexNormalInputs normalInput = GetVertexNormalInputs(IN.normalOS);
OUT.positionCS = posInput.positionCS; OUT.positionWS = posInput.positionWS; OUT.normalWS = normalInput.normalWS; OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap); return OUT; }
half4 frag(Varyings IN) : SV_Target { half4 baseCol = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv) * _BaseColor; clip(baseCol.a - _Cutoff);
half3 normalWS = normalize(IN.normalWS); float4 shadowCoord = TransformWorldToShadowCoord(IN.positionWS); Light mainLight = GetMainLight(shadowCoord);
half NdotL = saturate(dot(normalWS, mainLight.direction)); half3 ambient = baseCol.rgb * 0.2; half3 diffuse = baseCol.rgb * mainLight.color * NdotL * mainLight.shadowAttenuation;
return half4(ambient + diffuse, baseCol.a); } ENDHLSL } } }
|
28. 官方参考入口
建议实际项目里同时打开:
1 2 3 4
| Packages/com.unity.render-pipelines.universal/Shaders/Lit.shader Packages/com.unity.render-pipelines.universal/Shaders/LitForwardPass.hlsl Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl
|
学习顺序:
1 2 3 4 5 6 7
| 先写 Unlit 再写 Texture + Color 再写 Alpha Clip 再写 Main Light 再写 ShadowCaster 再写 Rim / Dissolve / Outline 最后看 Lit.shader 学 PBR 和复杂 Pass
|