8.自定义Shader支持GPUInstancing

8.补充知识-如何让自定义Shader支持GPUInstancing


8.1 知识点

为何要让自定义Shader支持GPU Instancing

  • 我们的最终优化目标是利用 VAT 来减少 CPU 端的动画相关计算开销。
  • 再利用 GPU Instancing 来减少 DrawCall
  • 目前我们已经学习了生成 VAT 的关键知识点。
  • 而想要使用 VAT 中的数据,我们之后会自定义 Shader。
  • 因为我们需要让自定义 Shader 支持 GPU Instancing 功能。
  • 所以我们需要学习该知识点。

如何让自定义Shader支持GPU Instancing

主要目的

  • 让我们自定义的顶点片元着色器能够使用 GPU Instancing 功能。

主要思路

  • 在 Shader 代码中按照 GPU Instancing 的规则添加对应指令或代码。

主要步骤

  1. 添加编译指令 #pragma multi_compile_instancing

    • 用于命令 Unity 生成实例化变体。
  2. 在结构体中添加 UNITY_VERTEX_INPUT_INSTANCE_ID

    • 用于在顶点着色器输入/输出结构中定义实例 ID。
  3. 在顶点或片元着色器中添加

    • UNITY_SETUP_INSTANCE_ID(v)

    • 用于使着色器函数可以访问实例 ID。

    • 它必须在顶点着色器的最开头使用,并且对于片元着色器是可选的。

    • UNITY_TRANSFER_INSTANCE_ID(v, o)

    • 用于将实例 ID 从顶点着色器的输入结构体复制到输出结构体中。

    • 仅当需要访问片元着色器中的每个实例的数据时才有必要这样做。

  4. 可选内容(可配合之后要补充的 MPB 材质属性块使用,达到同一材质不同表现并且不增加 DrawCall 的目标)

    • 在 Shader 里声明一块每个实例都有一份的数据。

    • 在结构体声明前可以添加:

    • UNITY_INSTANCING_BUFFER_START(Props) //开始定义一个实例缓冲区,名字叫 Props(这是你自己起的组名)

    • UNITY_DEFINE_INSTANCED_PROP(float4, _Color) //声明一个 float4 类型的 _Color 颜色字段

    • UNITY_INSTANCING_BUFFER_END(Props) //结束定义

    • Unity 会帮我们定义一个结构体数组/常量缓冲区,里面装着每实例属性。

    • 使用缓冲区中定义的字段:

    • float4 c = UNITY_ACCESS_INSTANCED_PROP(Props, _Color); //通过传入组名和字段名获取

注意

  • 在编写 GPU Instancing 的 Shader 时。
  • 在顶点坐标转换时务必使用 UnityObjectToClipPos(v.vertex) 将顶点坐标转换到裁剪空间。
  • 不要使用 mul(UNITY_MATRIX_MVP,v.vertex) 矩阵乘法。

具体操作

添加编译指令 #pragma multi_compile_instancing
  • 这一步的目的,是让 Unity 为当前 Shader 生成实例化相关变体。
  • 没有这句编译指令,即使你后面加了实例 ID 宏和实例化属性,Shader 也不具备真正的实例化支持能力。
  • 它相当于开启 GPU Instancing 的总开关。
// 编译指令:启用GPU实例化支持,核心功能开关
#pragma multi_compile_instancing
在结构体中添加 UNITY_VERTEX_INPUT_INSTANCE_ID
  • 这一步的目的,是在顶点输入结构体和顶点输出结构体中携带实例 ID。
  • 这样顶点阶段才能知道“当前处理的是哪一个实例”。
  • 如果片元着色器之后也要访问实例化属性,那么还需要把实例 ID 从顶点阶段传递到片元阶段。
//  用于在顶点着色器输入/输出结构中定义实例 ID

// ==================== 顶点着色器输入结构体 ====================
// 接收网格数据:顶点位置、UV、实例ID
struct appdata
{
    float4 vertex : POSITION;   // 模型空间顶点坐标
    float2 uv : TEXCOORD0;       // 模型第一套纹理坐标
    UNITY_VERTEX_INPUT_INSTANCE_ID // 内置宏:存储GPU实例ID
};

// ==================== 顶点着色器输出结构体 ====================
// 传递数据给片元着色器:UV、雾效、裁剪空间坐标、实例ID
struct v2f
{
    float2 uv : TEXCOORD0;       // 传递给片元的纹理坐标
    UNITY_FOG_COORDS(1)         // 内置宏:存储雾效计算数据
    float4 vertex : SV_POSITION; // 裁剪空间顶点坐标(屏幕坐标)
    UNITY_VERTEX_INPUT_INSTANCE_ID // 传递实例ID到片元着色器
};
在顶点或片元着色器中添加实例 ID 初始化和传递逻辑
  • 这一步的目的,是让实例 ID 真正“活起来”。
  • UNITY_SETUP_INSTANCE_ID(v) 负责初始化当前实例 ID。
  • UNITY_TRANSFER_INSTANCE_ID(v, o) 负责把实例 ID 从顶点输入结构体传到输出结构体。
  • 如果片元着色器中需要读取实例化属性,那么片元着色器里也要调用 UNITY_SETUP_INSTANCE_ID
//  UNITY_SETUP_INSTANCE_ID(v)
//  用于使着色器函数可以访问实例 ID
//  它必须在顶点着色器的最开头使用,并且对于片元着色器是可选的

//  UNITY_TRANSFER_INSTANCE_ID(v, o)
//  用于将实例 ID 从顶点着色器的输入结构体复制到输出结构体中
//  仅当需要访问片元着色器中的每个实例的数据时才有必要这样做

// ==================== 顶点着色器 ====================
// 功能:处理顶点空间转换、实例化数据传递、纹理坐标计算
v2f vert(appdata appdata)
{
    // 定义输出结构体变量
    v2f v2f;

    // 初始化实例ID:必须在访问实例化属性前调用
    UNITY_SETUP_INSTANCE_ID(appdata);
    // 传递实例ID:将顶点输入的实例ID复制到输出结构体
    UNITY_TRANSFER_INSTANCE_ID(appdata, v2f);

    //...
}

// ==================== 片元着色器 ====================
// 功能:采样纹理、应用雾效、输出最终像素颜色
fixed4 frag(v2f v2f) : SV_Target
{
    // 初始化实例ID:片元着色器中使用实例属性的必备步骤
    UNITY_SETUP_INSTANCE_ID(v2f);

    //...
}
声明并访问每实例独立数据(可选内容)
  • 这一步不是强制的,但实际项目里很有用。
  • 它通常会和后面的 MPB(材质属性块)一起配合使用。
  • 作用是:让多个实例共用同一材质,但每个实例又能有自己的颜色、位置偏移、索引等数据。
  • 这样既能保持 GPU Instancing,又能实现“同材质不同表现”。
//  在 Shader 里声明一块 每个实例都有一份 的数据
//  在结构体声明前可以添加
//  UNITY_INSTANCING_BUFFER_START(Props) //开始定义一个实例缓冲区,名字叫 Props(这是你自己起的组名)
//      UNITY_DEFINE_INSTANCED_PROP(float4, _Color)  // 声明一个float4类型的_Color颜色字段
//   UNITY_INSTANCING_BUFFER_END(Props) //结束定义
//  Unity会帮我们定义了一个结构体数组/常量缓冲区,里面装着每实例属性

//  使用缓冲区中定义的字段
//  float4 c = UNITY_ACCESS_INSTANCED_PROP(Props, _Color); //通过传入组名和字段名获取

// ==================== GPU实例化属性缓冲区 ====================
// 定义实例化参数组,每个物体实例可独立修改以下参数
UNITY_INSTANCING_BUFFER_START(MrTao)
    // 实例化颜色:每个实例独立的颜色参数
    UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
    // 实例化位置:每个实例独立的位置偏移参数
    UNITY_DEFINE_INSTANCED_PROP(float4, _Pos)
    // 实例化偏移索引:用于区分不同实例的序号参数
    UNITY_DEFINE_INSTANCED_PROP(int, _OffsetIndex)
UNITY_INSTANCING_BUFFER_END(MrTao)

// ==================== 顶点着色器 ====================
// 功能:处理顶点空间转换、实例化数据传递、纹理坐标计算
v2f vert(appdata appdata)
{
    //...

    // 获取当前物体实例的自定义颜色属性
    float4 color = UNITY_ACCESS_INSTANCED_PROP(MrTao, _Color);

    // ...
}

8.2 知识点代码

CustomShaderToGUIInstancing.cs

using UnityEngine;

public class CustomShaderToGUIInstancing
{
    #region 知识点一 为何要让自定义Shader支持GPU Instancing

    //我们的最终优化目标是利用VAT来减少CPU端的动画相关计算开销
    //再利用GPU Instancing来减少DrawCall
    //目前我们学习了生成VAT的关键知识点
    //而想要使用VAT中的数据,我们之后会自定义Shader
    //因为我们需要让自定义Shader支持GPU Instancing功能
    //所以我们需要学习该知识点

    #endregion

    #region 知识点二 如何让自定义Shader支持GPU Instancing

    //主要目的:
    //让我们自定义的顶点片元着色器能够使用GPU Instancing功能

    //主要思路:
    //在Shader代码中按照GPU Instancing的规则添加对应指令或代码

    //主要步骤:
    //1.添加编译指令 #pragma multi_compile_instancing
    //  用于命令 Unity 生成实例化变体

    //2.在结构体中添加 UNITY_VERTEX_INPUT_INSTANCE_ID
    //  用于在顶点着色器输入/输出结构中定义实例 ID

    //3.在顶点或片元着色器中添加
    //  UNITY_SETUP_INSTANCE_ID(v)
    //  用于使着色器函数可以访问实例 ID
    //  它必须在顶点着色器的最开头使用,并且对于片元着色器是可选的

    //  UNITY_TRANSFER_INSTANCE_ID(v, o)
    //  用于将实例 ID 从顶点着色器的输入结构体复制到输出结构体中
    //  仅当需要访问片元着色器中的每个实例的数据时才有必要这样做

    //4.可选内容(可配合之后要补充的MPB材质属性块使用,达到同一材质不同表现并且不增加DrawCall的目标)
    //  在 Shader 里声明一块 每个实例都有一份 的数据
    //  在结构体声明前可以添加
    //  UNITY_INSTANCING_BUFFER_START(Props) //开始定义一个实例缓冲区,名字叫 Props(这是你自己起的组名)
    //      UNITY_DEFINE_INSTANCED_PROP(float4, _Color)  // 声明一个float4类型的_Color颜色字段
    //   UNITY_INSTANCING_BUFFER_END(Props) //结束定义
    //  Unity会帮我们定义了一个结构体数组/常量缓冲区,里面装着每实例属性

    //  使用缓冲区中定义的字段
    //  float4 c = UNITY_ACCESS_INSTANCED_PROP(Props, _Color); //通过传入组名和字段名获取

    //注意:
    //在编写GPU Instancing 的Shader时
    //在顶点坐标转换时务必使用 UnityObjectToClipPos(v.vertex) 将顶点坐标转换到裁剪空间
    //不要使用mul(UNITY_MATRIX_MVP,v.vertex) 矩阵乘法

    #endregion
}

CustomShaderToGUIInstancing.shader

// 支持GPU实例化的Unlit通用着色器
// 适用于不透明物体渲染,可通过实例化属性批量修改物体参数
Shader "Unlit/CustomShaderToGUIInstancing"
{
    // 材质面板可编辑的属性
    Properties
    {
        // 主纹理:用于物体表面的基础贴图,默认填充白色
        _MainTex ("Texture", 2D) = "white" {}
    }

    // 子着色器:核心渲染逻辑定义
    SubShader
    {
        // 渲染标签:标记为不透明物体,适配Unity渲染管线
        Tags
        {
            "RenderType"="Opaque"
        }
        // 细节级别:控制渲染距离,100为基础不透明物体标准值
        LOD 100

        // 渲染通道:执行顶点/片元渲染的核心通道
        Pass
        {
            CGPROGRAM
            // 编译指令:指定顶点着色器函数
            #pragma vertex vert
            // 编译指令:指定片元着色器函数
            #pragma fragment frag
            // 编译指令:启用雾效多版本编译,适配场景雾效
            #pragma multi_compile_fog
            // 编译指令:启用GPU实例化支持,核心功能开关
            #pragma multi_compile_instancing

            // 引入Unity内置CG函数库,包含空间转换、雾效等核心函数
            #include "UnityCG.cginc"

            // ==================== GPU实例化属性缓冲区 ====================
            // 定义实例化参数组,每个物体实例可独立修改以下参数
            UNITY_INSTANCING_BUFFER_START(MrTao)
                // 实例化颜色:每个实例独立的颜色参数
                UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
                // 实例化位置:每个实例独立的位置偏移参数
                UNITY_DEFINE_INSTANCED_PROP(float4, _Pos)
                // 实例化偏移索引:用于区分不同实例的序号参数
                UNITY_DEFINE_INSTANCED_PROP(int, _OffsetIndex)
            UNITY_INSTANCING_BUFFER_END(MrTao)

            // ==================== 顶点着色器输入结构体 ====================
            // 接收网格数据:顶点位置、UV、实例ID
            struct appdata
            {
                float4 vertex : POSITION;   // 模型空间顶点坐标
                float2 uv : TEXCOORD0;      // 模型第一套纹理坐标
                UNITY_VERTEX_INPUT_INSTANCE_ID // 内置宏:存储GPU实例ID(无分号)
            };

            // ==================== 顶点着色器输出结构体 ====================
            // 传递数据给片元着色器:UV、雾效、裁剪空间坐标、实例ID
            struct v2f
            {
                float2 uv : TEXCOORD0;      // 传递给片元的纹理坐标
                UNITY_FOG_COORDS(1)         // 内置宏:存储雾效计算数据(无分号!)
                float4 vertex : SV_POSITION;// 裁剪空间顶点坐标(屏幕坐标)
                UNITY_VERTEX_INPUT_INSTANCE_ID // 传递实例ID到片元着色器(无分号)
            };

            // 主纹理采样器:用于采样纹理颜色
            sampler2D _MainTex;
            // 主纹理缩放偏移:Unity自动生成,适配纹理Tiling/Offset属性
            float4 _MainTex_ST;

            
            // ==================== 顶点着色器 ====================
            // 功能:处理顶点空间转换、实例化数据传递、纹理坐标计算
            v2f vert(appdata appdata)
            {
                // 定义输出结构体变量
                v2f v2f;

                // 初始化实例ID:必须在访问实例化属性前调用
                UNITY_SETUP_INSTANCE_ID(appdata);
                // 传递实例ID:将顶点输入的实例ID复制到输出结构体
                UNITY_TRANSFER_INSTANCE_ID(appdata, v2f);

                // 获取当前物体实例的自定义颜色属性
                float4 color = UNITY_ACCESS_INSTANCED_PROP(MrTao, _Color);

                // 空间转换:将模型空间顶点转换为屏幕裁剪空间坐标
                v2f.vertex = UnityObjectToClipPos(appdata.vertex);
                // 纹理坐标计算:应用纹理的缩放和偏移
                v2f.uv = TRANSFORM_TEX(appdata.uv, _MainTex);
                // 雾效数据计算:传递顶点雾效参数
                UNITY_TRANSFER_FOG(v2f, v2f.vertex);

                // 将处理完成的数据传递给片元着色器
                return v2f;
            }

            // ==================== 片元着色器 ====================
            // 功能:采样纹理、应用雾效、输出最终像素颜色
            fixed4 frag(v2f v2f) : SV_Target
            {
                // 初始化实例ID:片元着色器中使用实例属性的必备步骤
                UNITY_SETUP_INSTANCE_ID(v2f);

                // 纹理采样:根据UV坐标获取主纹理的颜色
                fixed4 col = tex2D(_MainTex, v2f.uv);
                // 雾效应用:根据雾效数据混合最终颜色
                UNITY_APPLY_FOG(v2f.fogCoord, col);

                // 输出最终渲染颜色
                return col;
            }
            ENDCG
        }
    }
}


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

×

喜欢就点赞,疼爱就打赏