T.TAO
返回博客
/5 min read/Technical Art

Unity Shader #3 升级到 URP

#Unity#Shader#TechnicalArt

这篇笔记主要写一下怎么把 Built-in RP 的 shader 文件升级成 URP 的写法。网上大多数 Shader 教程仍然是 Built-in 的,所以这套翻译规则实际上比想象中用得多。

结构

SubShader

Tags {} 中添加新的渲染管线 Tag,URP 才会认这个 SubShader。

Plain TextSubShader {
    Tags {
        "RenderPipeline" = "UniversalPipeline"
    }
}

LightMode 的取值也换了一套。Built-in 的 ForwardBase / ForwardAdd 在 URP 中统一成一个 UniversalForward,因为 URP 的前向渲染在同一个 Pass 内处理所有光源,不再为每盏额外光源跑一遍几何。常用的还有:

用途Built-inURP
主光照 PassForwardBaseUniversalForward
额外光源ForwardAdd(并入 UniversalForward
投射阴影ShadowCasterShadowCaster
深度预通道DepthOnly / DepthNormals
无光照附加 Pass任意SRPDefaultUnlit

没有匹配到已知 LightMode 的 Pass 会被 URP 直接丢弃,不会报错也不会渲染。描边这类附加 Pass 找不到原因不出现,九成是卡在这里。

CG Macro

CGPROGRAM / ENDCG 改为 HLSLPROGRAM / ENDHLSLCGINCLUDE 改为 HLSLINCLUDE

#include 文件的改动

常用的几个 #include 现在做如下改动。

Plain Text#include "UnityCG.cginc"
=> #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

#include "Lighting.cginc"
=> #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

#include "AutoLight.cginc"
=> #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"

Core.hlsl 会把 Common.hlslSpaceTransforms.hlsl 这些一起带进来,所以大多数时候只 include 它就够了。

内置辅助函数的替换

顶点坐标变换

Built-inURP
UnityObjectToClipPos(v)TransformObjectToHClip(v)
UnityObjectToWorldDir(v)TransformObjectToWorldDir(v)
UnityObjectToWorldNormal(v)TransformObjectToWorldNormal(v)
mul(unity_ObjectToWorld, v)TransformObjectToWorld(v.xyz)
UnityObjectToViewPos(v)TransformWorldToView(TransformObjectToWorld(v))
UnityWorldSpaceViewDir(p)GetWorldSpaceViewDir(p)

URP 还提供了一个 VertexPositionInputs 结构,一次性把 object / world / view / clip 四个空间的位置都算出来,比手动逐个转换省事也省指令:

HLSLVertexPositionInputs positions = GetVertexPositionInputs(IN.positionOS.xyz);
VertexNormalInputs   normals   = GetVertexNormalInputs(IN.normalOS, IN.tangentOS);

OUT.positionCS = positions.positionCS;
OUT.positionWS = positions.positionWS;
OUT.normalWS   = normals.normalWS;

纹理采样

Built-in 的 sampler2D + tex2D 在 URP 里换成显式的纹理/采样器对,这样同一个 sampler 可以被多张贴图复用,省下宝贵的 sampler 槽位。

HLSL// Built-in
sampler2D _MainTex;
half4 c = tex2D(_MainTex, uv);

// URP
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
half4 c = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);

光照

_LightColor0_WorldSpaceLightPos0 都没有了,改为向 URP 索要光源对象:

HLSLLight mainLight = GetMainLight(shadowCoord);
half3 lightDir   = mainLight.direction;
half3 lightColor = mainLight.color;
half  atten      = mainLight.distanceAttenuation * mainLight.shadowAttenuation;

// 额外光源需要自己遍历
uint count = GetAdditionalLightsCount();
for (uint i = 0; i < count; i++) {
    Light light = GetAdditionalLight(i, positionWS);
    // ... 累加
}

SRP Batcher 与 CBUFFER

这一条不是语法要求,但会直接决定性能:所有 per-material 的属性必须放进 UnityPerMaterial 常量缓冲区,否则这个 Shader 会被 SRP Batcher 排除,退回到逐材质 SetPass。

HLSLCBUFFER_START(UnityPerMaterial)
    float4 _MainTex_ST;
    half4  _BaseColor;
    half   _Smoothness;
CBUFFER_END

要求也很死:CBUFFER 里变量的声明顺序和类型必须在所有 Pass 中完全一致,而且不能包含贴图本身(只能放 _MainTex_ST 这类伴随属性)。改完之后在 Inspector 里把 Shader 展开,顶部会显示 "SRP Batcher: compatible",那才算过关。

接收阴影

URP 的阴影需要显式开启关键字,否则采样出来永远是 1(全亮)。

HLSL#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _SHADOWS_SOFT
#pragma multi_compile _ _ADDITIONAL_LIGHTS

然后在片元里取阴影坐标:

HLSLfloat4 shadowCoord = TransformWorldToShadowCoord(positionWS);
Light  mainLight   = GetMainLight(shadowCoord);
half   shadow      = mainLight.shadowAttenuation;

一份迁移检查清单

  1. RenderPipeline Tag 加了吗?
  2. LightMode 换成 URP 认识的值了吗?
  3. CGPROGRAMHLSLPROGRAM
  4. include 换成 URP 的 ShaderLibrary 了吗?
  5. 坐标变换和纹理采样的宏都换过来了吗?
  6. 材质属性进 UnityPerMaterial 了吗?Inspector 里确认 SRP Batcher 兼容。
  7. 需要阴影的话,multi_compile 关键字加了吗?
  8. 需要被后处理/深度用到的话,DepthOnly Pass 补了吗?

前四条不做,Shader 会直接变紫或不渲染;后四条不做,Shader 能跑,但性能或表现会悄悄退化——这类问题反而更难查。

本系列文章

Unity Shader
  1. 01Unity Shader #0 数学基础
  2. 02Unity Shader #1 渲染管线
  3. 03Unity Shader #2 代码基础
  4. 04Unity Shader #2.1 基础指令
  5. 05Unity Shader #3 升级到 URP
  6. 06Unity Shader #4 在 UV 上画图形
  7. 07Unity Shader #5 边缘光
  8. 08Unity Shader #6 扫描与全息
  9. 09Unity Shader #7 日式卡通渲染
  10. 10Unity Shader #8 溶解
  11. 11Unity Shader #9 镜头上的雨滴
  12. 12Unity Shader #13 屏幕后处理
  13. 13URP #1 Universal Lit 与 URP ShaderLab