解剖一个 Unity shader:Properties/SubShader/Pass

Unity 知识 · Shader 区域 · 语言与结构 · 第 1 篇 更新于 2026-10-15

Unity 的 shader 文件不是纯粹的着色器代码,而是一份「材质说明书」:声明参数、给出多套渲染方案、每套方案里才是真正跑在 GPU 上的程序。读懂文件结构,是读懂任何现成 shader 的第一步。这篇用 Built-in 管线拆一个完整最小文件,URP 的差异点到即止。

一个完整的最小 shader

以下文件可直接保存为 .shader 使用(Built-in 管线):

Shader "MyGame/UnlitBasic"
{
    Properties
    {
        _MainTex ("Base Color (RGB)", 2D) = "white" {}
        _Color   ("Tint", Color) = (1,1,1,1)
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" "Queue" = "Geometry" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct AppData {
                float4 vertex : POSITION;
                float2 uv     : TEXCOORD0;
            };
            struct V2F {
                float4 pos : SV_POSITION;
                float2 uv  : TEXCOORD0;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;

            V2F vert(AppData v) {
                V2F o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv  = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag(V2F i) : SV_Target {
                return tex2D(_MainTex, i.uv) * _Color;
            }
            ENDCG
        }
    }

    FallBack "Diffuse"
}

每段干什么用

块职责关键点
Shader "名字"注册名,材质选择器里显示与文件名无关
Properties材质面板上的参数只声明「可被材质覆盖」的量,见 /notes/unity/structure-4/
SubShader一套完整可渲染方案Unity 从上往下选第一个硬件兼容的
SubShader Tags给管线看的分类Queue 决定绘制队列,RenderType 供替换/后处理识别
Pass一次绘制(一组 Draw Call)多 Pass = 同一物体画多遍(描边、回照光都靠它)
CGPROGRAM...ENDCG真正的 GPU 程序#pragma vertex/fragment 指定入口函数
FallBack所有 SubShader 都不兼容时引用的别的 shader老设备/低端机的保底

多 SubShader 的典型用法:第一套用高端特性(如曲面细分),第二套纯保底;FallBack "Diffuse" 还能让 Unity 用 Diffuse 的编译结果做回退与部分固定功能(如投影)。

CGPROGRAM 与 HLSLPROGRAM 差在哪

两者都是「在这里写 GPU 程序」,差别在语言库:

CGPROGRAM(Built-in 老写法)HLSLPROGRAM(URP/SRP 写法)
头文件UnityCG.cginc / Lighting.cgincCommon.hlsl / Lighting.hlsl
采样tex2D(_MainTex, uv)SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv)
类型有 fixed/half/float只有 float/half(fixed 已废弃)
现状Built-in 仍可用,URP 不支持URP/HDRP 唯一选择

fixed 的历史是「固定精度省移动端算力」,现代编译器的自动精度选择已接管这件事,Unity 官方已废弃 fixed——新代码按 float/half 写,精度坑见 /notes/unity/performance-3/。迁移对照表见 /notes/unity/engineering-1/。

Pass 的 Tags 与 LightMode

Pass 级 Tags { "LightMode" = "..." } 是给管线看的:Built-in 里值如 ForwardBase/ShadowCaster;URP 里换成 UniversalForward/ShadowCaster/DepthOnly 等,且 URP 靠这个标签挑 Pass——标签写错,阴影或深度贴图直接缺这个物体。这是老 shader 迁 URP 的第一大坑,展开见 /notes/unity/engineering-1/。

为什么一个文件要写多个 Pass

一个 Pass 画一遍。需要「同一物体叠加多次」时就得多个 Pass:描边的倒模法是两个 Pass(先背面描边再正面本体,见 /notes/unity/effects-3/);回照光、平面镜像同理。代价也要按遍数算——多一个 Pass 就多一组 Draw Call 与片元执行,见 /notes/unity/performance-1/。

自检三问

  • 材质面板上没有某个参数:是没写进 Properties,还是没声明对应变量?两者后果一样吗?
  • 硬件不支持第一个 SubShader 时,Unity 怎么选?
  • URP 工程里把 CGPROGRAM 原样保留会发生什么?