10.表面着色器编译指令

10.表面着色器-编译指令


10.1 知识点

知识回顾:表面着色器中的编译指令

表面着色器中最为关键的部分包括编译指令、结构体和自定义函数
编译指令是表面着色器中用来与 Unity 沟通的重要方式,通过它我们可以告诉 Unity 需要执行哪些操作、忽略哪些部分。
由于表面着色器只需要实现少量代码,大部分功能(如光照、阴影、反射、折射等)均由 Unity 自动生成。

表面着色器中编译指令的基本构成

编译指令的基本格式为:

#pragma surface 表面函数名 光照模型 可选额外参数

它由以下四个部分构成:

  • 固定部分:#pragma surface
  • 表面函数名
  • 光照模型
  • 可选额外参数

例如,一个表面着色器的编译指令示例如下:

  • surf —— 表面处理函数名称
  • Lambert —— 使用兰伯特光照模型
#pragma surface surf Lambert

第一部分:固定写法

在编译指令中,#pragma surface 是固定写法,用于指明该编译指令适用于表面着色器。
在它后面必须填写表面函数和光照模型,并可附加一些可选参数以控制表面着色器的行为。

第二部分:表面函数名

表面函数名可以随意命名,但后续代码中必须存在同名的函数。
表面函数的参数有三种固定格式:

  • void 表面函数名(Input IN, inout SurfaceOutput o)
  • void 表面函数名(Input IN, inout SurfaceOutputStandard o)
  • void 表面函数名(Input IN, inout SurfaceOutputStandardSpecular o)

其中,Input 为输入结构体,可获取各种表面属性;
SurfaceOutputSurfaceOutputStandardSurfaceOutputStandardSpecular 是 Unity 内置的输出结构体,分别用于不同的工作流,可以配合不同的光照模型使用。
在函数中利用 Input 结构体中的数据进行计算,然后将结果赋值给输出结构体 o 的各个成员,后续这些数据会自动传递给光照函数进行计算(如果不自定义,Unity 会自动生成计算代码)。

第三部分:光照模型

光照模型用于计算物体表面的光照效果。
Unity 内置了基于物理的光照模型函数,例如 StandardStandardSpecular,以及非基于物理的简单光照模型如 LambertBlinnPhong
我们可以直接填写这些预定义的光照模型,也可以自定义光照模型计算,只需按照以下格式书写函数即可:

  • 不依赖视角的光照模型(如漫反射):
    half4 Lighting自定义名字 (SurfaceOutput s, half3 lightDir, half atten)
  • 依赖视角的光照模型(如高光反射):
    half4 Lighting自定义名字 (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)

只需在编译指令中的光照模型位置填写自定义的函数名称,Unity 将自动调用该函数中的逻辑来处理光照计算。

下面是一段示例 Shader 代码:

Shader "Custom/Lesson10_Shader"
{
    // 材质属性面板参数
    Properties
    {
        //...
    }

    SubShader
    {
        //...

        CGPROGRAM
        //...

        // 自定义光照模型编译指令
        #pragma surface surf LightingMyLight
        
        //...

        // 自定义光照模型
        half4 LightingMyLight(SurfaceOutput s, half3 lightDir, half atten)
        {
            // 自定义光照计算逻辑
        }
        ENDCG
    }
    // 后备着色器(当当前着色器不支持时使用)
    FallBack "Diffuse"
}

可选额外参数

可选额外参数包含了许多非常有用的指令,用于控制顶点修改、最终颜色修改、阴影、透明性以及代码生成等。

透明效果相关编译指令
阴影和细分相关编译指令
控制代码生成的编译指令
其他编译指令

以下列举几个常用的可选参数:

  • 自定义顶点修改函数
    使用格式:vertex:顶点函数名
    对应函数格式:
    void 顶点函数名(inout appdata_full v)

  • 最终颜色修改函数
    使用格式:finalcolor:自定义最终颜色修改函数名
    对应函数格式:
    void 自定义最终颜色修改函数名(Input IN, SurfaceOutput o, inout fixed4 color)

  • 阴影相关

    • addshadow:用于对进行了顶点动画、透明度测试的物体进行阴影投射,避免 FallBack 无法准确处理
    • fullforwardshadows:在前向渲染路径中支持所有光源类型和阴影
    • noshadow:禁用阴影
  • 透明相关

    • alphatest:变量名:使用指定名字的变量剔除不满足条件的片元
  • 光照相关

    • noambient:不使用任何环境光和光照探针
    • novertexlights:不使用逐顶点光照
    • noforwardadd:去掉所有前向渲染中的额外 pass,只支持一个逐像素平行光,其余光源按逐顶点计算
    • nofog:关闭雾效处理
    • nolightmap:关闭光照烘焙处理
  • 控制代码生成
    默认情况下,表面着色器自动生成的代码包含前向渲染和延迟渲染路径的 Pass,可能使 Shader 文件较大。
    如果确定只在某些渲染路径中使用,可以使用以下指令排除不需要的路径,来明确告诉Unity,不需要为某些渲染路径生成代码:

    • exclude_path:deferred(排除延迟渲染路径)
    • exclude_path:forward(排除前向渲染路径)
    • exclude_path:prepass(排除预通道渲染路径)

总结

表面着色器中的编译指令用于定义着色器的基本行为、配置光照模型并优化渲染流程。

  • #pragma surface:固定写法,表明这是用于表面着色器的编译指令。
  • 表面函数名:指定一个用于计算物体表面颜色、法线、反射等特性的函数,Unity 会自动生成对应的顶点着色器和片段着色器代码,并结合指定的光照模型进行渲染。
  • 光照模型:指定表面着色器使用的光照计算模型,既可以选择 Unity 提供的预定义模型,也可以自定义。
  • 可选额外参数:允许传递额外的编译选项,用于自定义顶点计算、最终颜色计算、阴影、透明测试、混合以及控制代码生成等。

通过这些指令,我们能够精准地控制表面着色器的行为,优化渲染流程,并实现更复杂的光照和视觉效果。


10.2 知识点代码

Lesson10_Shader.shader

Shader "Custom/Lesson10_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 surface surf LightingMyLight
        
        #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;
        }

        // 自定义光照模型
        half4 LightingMyLight(SurfaceOutput s, half3 lightDir, half atten)
        {
        }
        ENDCG
    }
    // 后备着色器(当当前着色器不支持时使用)
    FallBack "Diffuse"
}

Lesson10_表面着色器_编译指令.cs

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

public class Lesson10_表面着色器_编译指令 : MonoBehaviour
{
    void Start()
    {
        #region 知识回顾 表面着色器中的 编译指令

        //表面着色器中
        //最为关键的几个部分为:
        //1.编译指令
        //2.结构体
        //3.自定义函数

        //编译指令是表面着色器中用来和Unity沟通的重要方式
        //通过编译指令,我们可以告诉Unity需要让他做什么和不做什么
        //因为通过上节课我们知道表面着色器只需要实现少量代码
        //大部分代码(比如光照、阴影、反射、折射等)都交给Unity自动生成

        #endregion

        #region 知识点一 表面着色器中 编译指令 的基本构成

        //#pragma surface 表面函数名 光照模型 可选额外参数
        //它是又4个部分

        //1.固定部分 #pragma surface 
        //2.表面函数名
        //3.光照模型
        //4.可选额外参数

        //构成的

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

        #endregion

        #region 知识点二 第一部分 固定写法

        //#pragma surface 表面函数名 光照模型 可选额外参数
        //中
        //#pragma surface 是固定写法
        //是用于指明该编译指令是用于表面着色器的
        //在他后面我们必须填写表面函数和光照模型
        //还可以填写一些可选参数来控制表面着色器的行为

        #endregion

        #region 知识点三 第二部分 表面函数名

        //#pragma surface 表面函数名 光照模型 可选额外参数
        //中
        //表面函数名可以随意取名,但是需要在后面的代码中有对应名字的函数
        //函数的参数有三种固定格式
        //1. void 表面函数名(Input IN, inout SurfaceOutput o)
        //2. void 表面函数名(Input IN, inout SurfaceOutputStandard o)
        //3. void 表面函数名(Input IN, inout SurfaceOutputStandardSpecular o)
        //其中 Input 为输入结构体,其中可以得到各种表面属性
        //SurfaceOutput、SurfaceOutputStandard 和 SurfaceOutputStandardSpecular
        //是Unity内置的写好的用于输出的结构体,他们分别用于不同的工作流,可以配合不同的光照模型使用
        //我们之后就可以利用Input结构体中的数据进行计算,计算得到的结构赋值给输出结构体o中的成员
        //之后会自动传递给光照函数进行下一步计算(如果我们不自定义,Unity会自动生成计算代码)

        #endregion

        #region 知识点四 第三部分 光照模型

        //#pragma surface 表面函数名 光照模型 可选额外参数
        //中
        //光照模型是用来计算物体表面的光照效果的
        //Unity内置了基于物理的光照模型函数 Standard 和 StandardSpecular
        //还有简单的非基于物理的光照模型函数 Lambert 和 BlinnPhong
        //我们可以直接填写他们来应用对应的光照计算
        //同样我们也可以自定义光照模型计算相关
        //只需要在代码中按照格式书写以下函数

        //不依赖视角的光照模型,比如漫反射
        //half4 Lighting自定义名字 (SurfaceOutput s, half3 lightDir, half atten)
        //依赖视角的光照模型,比如高光反射
        //half4 Lighting自定义名字 (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
        //然后只需要在光照模型处填写 自定义名字 即可
        //就会自动调用函数中的逻辑来处理光照相关逻辑了

        #endregion

        #region 知识点五 第四部分 可选额外参数

        //#pragma surface 表面函数名 光照模型 可选额外参数
        //中
        //可选额外参数包含了很多非常有用的指令类型
        //我们着重提及几个可选参数:
        //1.自定义顶点修改函数 vertex:顶点函数名
        //  void 顶点函数名(inout appdata_full v)
        //2.最终颜色修改函数 finalcolor:自定义最终颜色修改函数名
        //  void 自定义最终颜色修改函数名(Input IN, SurfaceOutput o, inout fixed4 color)
        //3.阴影相关
        //  addshadow 为一些进行了顶点动画、透明度测试的物体显示的进行阴影投射,避免通过FallBack无法准确处理
        //  fullforwardshadows 在前向渲染路径中支持所有光源类型和阴影
        //  noshadow 禁用阴影
        //4.透明相关
        //  alphatest:变量名 利用它可以使用指定名字的变量来剔除不满足条件的片元
        //5.光照
        //  noambient 不使用任何环境光和光照探针
        //  novertexlights 不使用任何逐顶点光照
        //  noforwardadd 去掉所有前向渲染中的额外pass,只会支持一个逐像素平行光,其他光源按照逐顶点huoSH来计算
        //  nofog 关闭雾效处理
        //  nolightmap 关闭光照烘焙处理
        //6.控制代码生成
        //  默认情况下,表面着色器自动生成的代码包含前向渲染路径、延迟渲染路径使用的Pass
        //  这会让Shader文件较大,如果我们明确表面着色器只会在某些渲染路径中使用
        //  可以使用
        //  exclude_path:deferred(排除延迟渲染路径)
        //  exclude_path:forward(排除前向渲染路径)
        //  exclude_path:prepass(排除预通道渲染路径)
        //  来明确告诉Unity,不需要为某些渲染路径生成代码

        #endregion

        #region 总结

        //表面着色器中的编译指令
        //是用于定义着色器的基本行为、配置光照模型,优化渲染流程的
        //#pragma surface 表面函数名 光照模型 可选额外参数

        //#pragma surface:
        //固定写法,表明是表面着色器的编译指令

        //表面函数名:
        //指定一个表面着色器函数的名称,这个函数负责计算物体表面的颜色、法线、反射等特性。
        //Unity会自动生成对应的顶点着色器和片段着色器代码,然后结合指定的光照模型来处理表面渲染。

        //光照模型:
        //指定表面着色器使用的光照计算模型,Unity提供了几种预定义的光照模型,也可以自定义

        //可选额外参数:
        //允许你传递一些额外的编译选项
        //这些选项可以自定义顶点计算、自定义最终颜色计算、控制是否计算阴影、控制透明测试和混合等等

        #endregion
    }
}


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

×

喜欢就点赞,疼爱就打赏