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的规则添加对应指令或代码。
主要步骤
添加编译指令
#pragma multi_compile_instancing- 用于命令 Unity 生成实例化变体。
在结构体中添加
UNITY_VERTEX_INPUT_INSTANCE_ID- 用于在顶点着色器输入/输出结构中定义实例 ID。
在顶点或片元着色器中添加
UNITY_SETUP_INSTANCE_ID(v)用于使着色器函数可以访问实例 ID。
它必须在顶点着色器的最开头使用,并且对于片元着色器是可选的。
UNITY_TRANSFER_INSTANCE_ID(v, o)用于将实例 ID 从顶点着色器的输入结构体复制到输出结构体中。
仅当需要访问片元着色器中的每个实例的数据时才有必要这样做。
可选内容(可配合之后要补充的
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