9.表面着色器基本原理和结构

9.表面着色器-基本原理和结构


9.1 知识点

回顾表面着色器基本原理

我们在之前已经对表面着色器有一定的认识,下面回顾相关知识。

我们一般会使用以下三种形式来编写 Unity Shader:

  • 表面着色器(可控性较低)
  • 顶点/片元着色器(重点学习)
  • 固定函数着色器(基本已弃用,仅供了解)

表面着色器(Surface Shader)是 Unity 自创的一种着色器代码类型,其本质是对顶点/片元着色器的一层封装。
它需要的代码量较少,很多工作都已由系统帮助完成,但缺点是渲染消耗较大、可控性较低。
优点在于它处理了大量光照细节,我们无需自己编写复杂的光照计算即可实现光照、阴影、反射、折射等效果。

在创建 Shader 时,我们可以选择创建 Standard Surface Shader。
观察该 Shader 文件内部结构时会发现,着色器相关代码位于 SubShader 语句块中 CGPROGRAM 和 ENDCG 之间。

表面着色器的主要特点:

  • 直接在 SubShader 语句块中书写着色器逻辑
  • 无需关注多个 Pass 的渲染细节,Unity 内部自动处理
  • 可使用 CG 或 HLSL 编写 Shader 逻辑
  • 代码量少,但可控性较低,性能消耗较高
  • 适用于需要处理复杂光照效果的着色器(主机、PC 平台较为适用,移动平台需谨慎考虑性能)

总的来说,表面着色器是在顶点/片元着色器基础上的一次封装,简化了光照、阴影、反射、折射等效果的计算流程,Unity 内部会自动完成这些计算。

默认表面着色器

// 自定义着色器,名为 "Custom/Lesson09_Test"
Shader "Custom/Lesson09_Test"
{
    // 定义材质属性,这些属性可以在 Unity 的材质面板中进行调整
    Properties
    {
        // 颜色属性,显示名称为 "Color",默认值为白色 (RGBA: 1,1,1,1)
        _Color ("Color", Color) = (1,1,1,1)
        // 主纹理属性,显示名称为 "Albedo (RGB)",默认纹理为白色
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        // 光泽度属性,显示名称为 "Smoothness",取值范围 0 到 1,默认值为 0.5
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        // 金属度属性,显示名称为 "Metallic",取值范围 0 到 1,默认值为 0.0
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    // 子着色器,包含实际渲染逻辑
    SubShader
    {
        // 标签,指定该着色器用于渲染不透明物体
        Tags { "RenderType"="Opaque" }
        // 细节层次 (LOD) 值,用于控制不同情况使用的着色器版本
        LOD 200

        // 开始 CG 代码块
        CGPROGRAM
        // 使用基于物理的标准光照模型,并开启所有光照类型的阴影计算
        #pragma surface surf Standard fullforwardshadows

        // 使用 Shader Model 3.0 目标以获得更好的光照效果
        #pragma target 3.0

        // 定义主纹理采样器
        sampler2D _MainTex;

        // 定义输入结构体,用于传递顶点数据到表面着色器
        struct Input
        {
            // 主纹理的 UV 坐标
            float2 uv_MainTex;
        };

        // 光泽度变量,类型为 half(16 位浮点数)
        half _Glossiness;
        // 金属度变量,类型为 half(16 位浮点数)
        half _Metallic;
        // 颜色变量,类型为 fixed4(32 位定点数)
        fixed4 _Color;

        // 为该着色器添加实例化支持。使用该 Shader 的材质需要勾选 'Enable Instancing'。
        // 更多信息请参阅:https://docs.unity3d.com/Manual/GPUInstancing.html
        // #pragma instancing_options assumeuniformscaling
        // 开始实例化缓冲区,用于存储每个实例的属性
        UNITY_INSTANCING_BUFFER_START(Props)
            // 可在此添加更多实例属性
        UNITY_INSTANCING_BUFFER_END(Props)

        // 表面着色器函数,用于计算表面的光照属性
        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // 反照率(基础颜色)由纹理采样结果乘以颜色属性得到
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            // 将颜色的 RGB 分量赋值给表面的反照率属性
            o.Albedo = c.rgb;
            // 金属度直接取自材质属性
            o.Metallic = _Metallic;
            // 光滑度直接取自材质属性
            o.Smoothness = _Glossiness;
            // 将颜色的 Alpha 分量赋值给表面 Alpha 属性
            o.Alpha = c.a;
        }
        // 结束 CG 代码块
        ENDCG
    }
    // 如果当前子着色器不被支持,回退到 "Diffuse" 着色器
    FallBack "Diffuse"
}

了解表面着色器的结构

通过对比表面着色器与顶点/片元着色器,我们可以发现两者的主要区别在于表面着色器中没有显式的 Pass。
表面着色器的关键部分包括:

  • 编译指令
  • 结构体
  • 自定义函数

编写完表面着色器后,Unity 会自动将其编译为顶点/片元着色器,并自动加入光照处理、反射、折射、阴影、雾效等相关计算代码。

查看示例表面着色器结构

Shader "Custom/Lesson09_Shader"
{
    // 材质属性面板参数
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1) // 基础颜色参数
        _MainTex ("Albedo (RGB)", 2D) = "white" {} // 主纹理(漫反射颜色)
        _BumpMap("BumpMap", 2D) = "white"{} // 法线贴图纹理
        _BumpScale("BumpScale", Range(0,1)) = 1 // 法线强度调节参数(0-1 范围)
    }

    SubShader
    {
        // 渲染标签设置
        Tags
        {
            "RenderType"="Opaque" // 标记为不透明物体
        }

        CGPROGRAM
        // 表面着色器编译指令:
        // surf - 表面处理函数名称
        // Lambert - 使用兰伯特光照模型
        #pragma surface surf Lambert
        #pragma target 3.0          // 使用 Shader Model 3.0 特性

        // 声明属性对应的变量
        sampler2D _MainTex; // 主纹理采样器
        sampler2D _BumpMap; // 法线贴图采样器
        float _BumpScale; // 法线强度系数
        fixed4 _Color; // 颜色参数

        // 输入结构体(自动填充顶点数据)
        struct Input
        {
            // 自动匹配的 UV 坐标(uv_ + 纹理变量名)
            float2 uv_MainTex; // 主纹理 UV 坐标
            float2 uv_BumpMap; // 法线贴图 UV 坐标
        };

        // 表面着色器处理函数
        void surf(Input IN, inout SurfaceOutput o)
        {
            // 主纹理采样,获取漫反射颜色和透明度
            fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
            o.Albedo = tex.rgb * _Color; // 组合纹理颜色与参数颜色
            o.Alpha = tex.a * _Color.a; // 计算最终透明度

            /* 法线贴图处理流程 */
            // 1. 从法线贴图获取切线空间法线(UnpackNormal 用于解码压缩格式)
            float3 tangentNormal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));

            // 2. 调整法线强度:通过缩放 XY 分量实现
            tangentNormal.xy *= _BumpScale;

            // 3. 重新计算 Z 分量,保持法线向量归一化
            // 根据单位向量性质:x² + y² + z² = 1
            tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

            // 4. 将处理后的法线赋值给输出结构
            o.Normal = tangentNormal;
        }
        ENDCG
    }
    // 后备着色器(当当前着色器不支持时使用)
    FallBack "Diffuse"
}

查看效果

从上述示例中可以看到,表面着色器无需编写任何显式的光照逻辑即可接受光照、生成阴影和投射阴影。
这是因为表面着色器对顶点/片元着色器进行了封装,统一处理了光照、阴影、反射、折射等计算逻辑,避免了重复编写。
Unity 会自动将其编译为顶点/片元着色器,并可通过“显示生成的代码”查看 Unity 自动生成的顶点/片元着色器代码。



9.2 知识点代码

Lesson09_Test.shader

Shader "Custom/Lesson09_Test"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

Lesson09_Shader.shader

Shader "Custom/Lesson09_Shader"
{
    // 材质属性面板参数
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1) // 基础颜色参数
        _MainTex ("Albedo (RGB)", 2D) = "white" {} // 主纹理(漫反射颜色)
        _BumpMap("BumpMap", 2D) = "white"{} // 法线贴图纹理
        _BumpScale("BumpScale", Range(0,1)) = 1 // 法线强度调节参数(0-1范围)
    }

    SubShader
    {
        // 渲染标签设置
        Tags
        {
            "RenderType"="Opaque" // 标记为不透明物体
        }

        CGPROGRAM
        // 表面着色器编译指令:
        // surf - 表面处理函数名称
        // Lambert - 使用兰伯特光照模型
        #pragma surface surf Lambert
        #pragma target 3.0          // 使用 Shader Model 3.0 特性

        // 声明属性对应的变量
        sampler2D _MainTex; // 主纹理采样器
        sampler2D _BumpMap; // 法线贴图采样器
        float _BumpScale; // 法线强度系数
        fixed4 _Color; // 颜色参数

        // 输入结构体(自动填充的顶点数据)
        struct Input
        {
            // 自动匹配的 UV 坐标(uv_ + 纹理变量名)
            float2 uv_MainTex; // 主纹理 UV 坐标
            float2 uv_BumpMap; // 法线贴图 UV 坐标
        };

        // 表面着色器处理函数
        void surf(Input IN, inout SurfaceOutput o)
        {
            // 主纹理采样(获取漫反射颜色和透明度)
            fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
            o.Albedo = tex.rgb * _Color; // 组合纹理颜色和参数颜色
            o.Alpha = tex.a * _Color.a; // 计算最终透明度

            /* 法线贴图处理流程 */
            // 1. 从法线贴图获取切线空间法线(UnpackNormal 负责解码压缩格式)
            float3 tangentNormal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));

            // 2. 调整法线强度:通过缩放 XY 分量实现
            tangentNormal.xy *= _BumpScale;

            // 3. 重新计算 Z 分量,保持法线向量归一化
            // 公式推导:根据单位向量性质 x² + y² + z² = 1
            tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

            // 4. 将处理后的法线传递给输出结构
            o.Normal = tangentNormal;
        }
        ENDCG
    }
    // 后备着色器(当当前着色器不支持时使用)
    FallBack "Diffuse"
}

Lesson09_表面着色器_基本原理和结构.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson09_表面着色器_基本原理和结构 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 回顾表面着色器基本原理

        //我们在之前已经对表面着色器有一定的认识
        //我们先来回顾下相关知识

        //我们一般会使用以下3种形式来编写Unity Shader
        //1.表面着色器(可控性较低)
        //2.顶点/片元着色器(重点学习)
        //3.固定函数着色器(基本已弃用,了解即可)

        //表面着色器(Surface Shader)是Unity自己创造的一种着色器代码类型
        //它的本质是对顶点/片元着色器的一层封装
        //它需要的代码量很少,很多工作都帮助我们去完成了
        //但是缺点是渲染的消耗较大,可控性较低
        //它的优点在于,它帮助我们处理了很多光照细节,我们可以直接使用而无需自己计算实现光照细节

        //我们可以在创建Shader时,选择创建Standard Surface Shader
        //通过观察该Shader文件的内部结构,你会发现
        //着色器相关代码被放在SubShader语句块中(并非Pass)的 CGPROGRAM 和 ENDCG 之间

        //表面着色器的特点就是
        //1.直接在SubShader语句块中书写着色器逻辑
        //2.我们不需要关心也不需要使用多个Pass,每个Pass如何渲染,Unity会在内部帮助我们去处理
        //3.可以使用CG或HLSL两种Shader语言去编写Shader逻辑
        //4.代码量较少,可控性较低,性能消耗较高
        //5.适用于处理需要和各种光源打交道的着色器(主机、PC平台时更适用,移动平台需要考虑性能消耗)

        //总的来说:
        //表面着色器实际上就是在顶点/片元着色器的基础上进行了一次封装
        //在处理光照、阴影、反射、折射等等效果时,简化了繁琐的计算流程
        //我们不需要自己去处理这些内容,Unity内部会自动帮助我们进行计算

        #endregion

        #region 知识点二 了解表面着色器的结构

        //我们通过事例对比表面着色器和顶点/片元着色器的区别
        //最大区别就是
        //表面着色器中没有显示的Pass了
        //而其中最为关键的几个部分为:
        //1.编译指令
        //2.结构体
        //3.自定义函数

        //当我们编写完表面着色器后,Unity会自动将其编译为顶点/片元着色器
        //会将光照处理、反射、折射、阴影、雾效等等相关计算代码自动加入

        #endregion
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏