97.性能优化-GPU-着色器优化-ComputeShader-多个入口函数
97.1 知识点
多个入口函数的含义
一个 Compute Shader 文件可以定义多个 Kernel(内核),即多个入口函数。每个内核可以有独立的线程组配置([numthreads])。在 CPU 端(C#)需要为每个入口函数单独调用 Dispatch()。
基本原理: 每声明一个入口函数(通过 #pragma kernel),都会被编译为一个独立的内核,相当于 GPU 上一个可单独调用的计算程序。
多个入口函数使用方式
主要步骤
在 Compute Shader 中定义多个内核入口函数
- 入口函数不能重名(同一文件内)。
- 多个入口函数可以共享该 Compute Shader 中声明的变量(如
RWTexture2D)。
在 C# 侧调用时,分别获取对应入口索引,通过多次
Dispatch调用不同内核,用索引区分。
注意: Dispatch 调用类似异步,不会阻塞 CPU,只是把执行指令提交给 GPU。GPU 会按 Dispatch 的调用顺序依次执行:单个 Dispatch 内部是并行的;多个 Dispatch 之间是串行的。
在 Compute Shader 中定义多个内核
// 每个 #kernel 指令声明一个可编译为 Compute Shader 入口点的函数
// 一个文件中可以声明多个内核,分别对应不同计算任务
#pragma kernel CSMain
#pragma kernel CSMain2
RWTexture2D<float4> Result;
// 第一个内核:8×8×1 线程组,适合二维图像
[numthreads(8,8,1)]
void CSMain(uint3 id : SV_DispatchThreadID,
uint3 id2 : SV_GroupID,
uint3 id3 : SV_GroupThreadID)
{
Result[id.xy] = float4(id.x & id.y, (id.x & 15) / 15.0, (id.y & 15) / 15.0, 0.0);
}
// 第二个内核:1×1×1,适合单次或全局逻辑
[numthreads(1,1,1)]
void CSMain2(uint3 id : SV_DispatchThreadID)
{
// 例如:初始化缓冲区、全局同步等
}
在 C# 中分别调用
int index = computeShader.FindKernel("CSMain");
int index2 = computeShader.FindKernel("CSMain2");
computeShader.Dispatch(index, 64, 64, 1);
computeShader.Dispatch(index2, 64, 64, 1);
多个入口函数的意义
分阶段执行:利用多入口函数串行执行的特点,实现类似流水线的分步处理。例如:第一个内核初始化/填充数据,第二个内核更新位置与速度,第三个内核将结果写入纹理。
逻辑模块化:不同内核负责不同功能,结构更清晰、易维护。
不同任务可使用不同线程组配置:每个内核可单独设置
[numthreads(X,Y,Z)],针对任务优化。减少 Compute Shader 文件数量:一个文件中包含多个内核,C# 只需加载一次即可执行多个入口。
共享数据:同一文件内的内核共享声明的缓冲区与纹理,便于阶段间传递数据。
总结: 多入口函数相当于 GPU 并行计算的多阶段流水线,把复杂计算拆成多个独立、可组合的模块,提高性能、可读性和可维护性。
97.2 知识点代码
Lesson97_性能优化_GPU_着色器优化_ComputeShader_多个入口函数.cs
using UnityEngine;
public class Lesson97_性能优化_GPU_着色器优化_ComputeShader_多个入口函数 : MonoBehaviour
{
public ComputeShader computeShader;
private void Start()
{
#region 知识点一 多个入口函数的含义
// 一个 Compute Shader 文件可以定义多个 Kernel(内核),即多个入口函数
// 每个 Kernel 入口函数可以有独立的线程数量配置
// 我们在 CPU 端(C# 侧)需要为每个入口函数单独调用 Dispatch()
// 基本原理:每声明一个入口函数,都会被编译为一个独立的内核(Kernel),
// 相当于 GPU 上的一个可单独调用的计算程序
#endregion
#region 知识点二 多个入口函数使用方式
// 1. 在 Compute Shader 中定义多个内核入口函数(注意:入口不能重名,可共享该文件中声明的变量)
// 2. 在 C# 侧调用时,获取对应入口索引,通过多次 Dispatch 用索引区分
// 利用 API 找到入口函数索引:FindKernel("入口函数名")
int index = computeShader.FindKernel("CSMain");
int index2 = computeShader.FindKernel("CSMain2");
// 启动计算:Dispatch(入口函数索引, x 线程组数, y 线程组数, z 线程组数)
computeShader.Dispatch(index, 64, 64, 1);
computeShader.Dispatch(index2, 64, 64, 1);
// 注意:Dispatch 调用类似异步,不阻塞 CPU,只是把指令提交给 GPU;
// GPU 按 Dispatch 顺序依次执行:单个 Dispatch 内部并行,多个 Dispatch 之间串行
#endregion
#region 知识点三 多个入口函数的意义
// 1. 分阶段执行:将计算逻辑分步、分阶段处理,利用多入口串行特点实现类似流水线(如初始化→模拟→写纹理)
// 2. 逻辑模块化:不同内核负责不同功能,结构清晰易维护
// 3. 不同任务可使用不同线程组配置:各内核可单独设置 [numthreads]
// 4. 减少 Compute Shader 文件数量:一个文件多内核,C# 只需加载一次
// 5. 共享数据:同一文件内的内核可共享声明的缓冲区与纹理
// 总结:多入口函数相当于 GPU 并行计算的多阶段流水线,可把复杂计算拆成多个独立、可组合的模块
#endregion
}
}
Lesson97_ComputeShader.compute
// 每个 #kernel 指令声明一个可编译为 Compute Shader 入口点的函数
// 一个文件中可以声明多个内核函数,分别对应不同的计算任务
#pragma kernel CSMain
#pragma kernel CSMain2
// 纹理缓冲区类型
RWTexture2D<float4> Result; // 可读写二维纹理(存储 float4,通常用于输出图像)
// 线程组配置:指定每个线程组包含的线程数量
// [numthreads(X, Y, Z)] 定义一个线程组包含 X*Y*Z 个线程
// 常见配置如 8x8x1=64 个线程,适合处理二维图像数据
[numthreads(8,8,1)]
void CSMain(uint3 id : SV_DispatchThreadID, // 全局线程 ID(在整个 Dispatch 调用中唯一)
uint3 id2 : SV_GroupID, // 线程组 ID(标识当前线程组的位置)
uint3 id3 : SV_GroupThreadID) // 线程组内线程 ID(在线程组内唯一)
{
// 核心计算逻辑:根据线程 ID 生成棋盘格图案
// R 通道:id.x 和 id.y 的按位与结果
// G 通道:id.x 的低 4 位映射到 [0,1] 范围
// B 通道:id.y 的低 4 位映射到 [0,1] 范围
// A 通道:固定为 0.0(完全透明)
Result[id.xy] = float4(id.x & id.y, (id.x & 15) / 15.0, (id.y & 15) / 15.0, 0.0);
}
// 第二个计算着色器内核函数
// 使用 1x1x1 线程组配置,适合执行单次原子操作或全局控制逻辑
[numthreads(1,1,1)]
void CSMain2(uint3 id : SV_DispatchThreadID) // 仅需全局线程 ID
{
// 此处可编写第二个内核的核心逻辑
// 例如:初始化缓冲区、执行全局同步操作等
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com