Version: 2020.3
在运行时替换着色器
Error and loading shaders

计算着色器

计算着色器是在显卡上并位于正常渲染管线之外运行的程序。它们可用于大规模并行的 GPGPU 算法,或用于加速游戏渲染的某些部分。为了有效使用它们,通常需要深入了解 GPU 架构和并行算法;并了解 DirectComputeOpenGL ComputeCUDAOpenCL

Unity 中的计算着色器与 DirectX 11 DirectCompute 技术紧密配合。计算着色器适用的平台:

  • Windows 和 Windows 应用商店,使用 DirectX 11 或 DirectX 12 图形 API 和 Shader Model 5.0 GPU

  • macOS 和 iOS,使用 Metal 图形 API

  • Android、Linux 和 Windows 平台,Vulkan API

  • 现代 OpenGL 平台(Linux 或 Windows 上的 OpenGL 4.3;Android 上的 OpenGL ES 3.1)。请注意,Mac OS X 不支持 OpenGL 4.3

  • 现代游戏主机(Sony PS4 和 Microsoft Xbox One)

在运行时可使用 SystemInfo.supportsComputeShaders 来查询计算着色器支持情况。

计算着色器资源

类似于常规着色器,计算着色器是项目中的资源文件,文件扩展名为 .compute。它们是以 DirectX 11 样式 HLSL 语言编写的,具有最少数量的 #pragma 编译指令来指示哪些函数将编译为计算着色器内核。

下面是计算着色器文件的基本示例,它使用红色填充输出纹理:

// test.compute

# pragma kernel FillWithRed

RWTexture2D<float4> res;

[numthreads(1,1,1)]
void FillWithRed (uint3 dtid : SV_DispatchThreadID)
{
    res[dtid.xy] = float4(1,0,0,1);
}

此处的语言是标准 DX11 HLSL,具有附加的 #pragma kernel FillWithRed 指令。一个计算着色器资源文件必须包含至少一个可以调用的 compute kernel,该函数由 #pragma directive 指示。文件中可以有更多内核;只需添加多个 #pragma kernel 行。

使用多个 #pragma kernel 行时,请注意在 #pragma kernel 指令的同一行上不允许 // text 样式的注释,如果使用,会导致编译错误。

可选择性地在 #pragma kernel 行后面添加要在编译该内核时定义的多个预处理器宏,例如:

# pragma kernel KernelOne SOME_DEFINE DEFINE_WITH_VALUE=1337
# pragma kernel KernelTwo OTHER_DEFINE
// ...

调用计算着色器

在脚本中,应定义 ComputeShader 类型的变量并分配对资源的引用。如此便可使用 ComputeShader.Dispatch 函数来调用它们。请参阅关于 ComputeShader 类的 Unity 文档以了解更多详细信息。

与计算着色器密切相关的是 ComputeBuffer 类,该类将定义任意数据缓冲区(在 DX11 术语中称为“结构化缓冲区”)。如果已设置“随机访问”标志(在 DX11 中称为“无序访问视图”),也可从计算着色器中写入渲染纹理。请参阅 RenderTexture.enableRandomWrite 以了解与此相关的更多信息。

计算着色器中的纹理采样器

纹理和采样器不是 Unity 中的单独对象,因此要在计算着色器中使用它们,必须遵循以下 Unity 特定规则之一:

  • 使用与纹理名称相同的名称,以 sampler 开头(例如,Texture2D MyTexSamplerState samplerMyTex)。在此情况下,采样器将初始化为纹理的过滤/包裹/各向异性 (filter/wrap/aniso) 设置。

  • 使用预定义采样器。因此,该名称必须具有 LinearPoint(对于过滤模式)和 ClampRepeat(包裹模式)。例如,SamplerState MyLinearClampSampler 会创建一个具有线性过滤模式和钳制包裹模式的采样器。

有关更多信息,请参阅采样器状态文档。

跨平台支持

与常规着色器一样,Unity 可将计算着色器从 HLSL 转换为其他着色器语言。因此,对于最简单的跨平台版本,应以 HLSL 编写计算着色器。但是,在执行此操作时需要考虑一些因素。

跨平台最佳实践

DirectX 11 (DX11) 支持在其他平台(如 MetalOpenGL ES)上不支持的许多操作。因此,应始终确保着色器在提供更少支持的平台(而不是仅在 DX11 上)上具有良好定义的行为。以下是要考虑的一些事项:

  • 越界内存访问是错误的。DX11 在读取时可能始终返回零,并且在读取某些写入时没有问题,但提供较少支持的平台可能会在执行此操作时导致 GPU 崩溃。密切注意特定于 DX11 的破解问题,与线程组大小倍数不匹配的缓冲区大小,试图从缓冲区的开头或结尾读取相邻的数据元素,以及类似的不兼容性。

  • 初始化您的资源。新缓冲区和纹理的内容是未定义的。有些平台可能会提供全零,但在其他平台上,可能会有某种内容(包括非数字)。

  • 绑定计算着色器声明的所有资源。即使您确定着色器在当前状态下由于分支而没有使用资源,仍必须确保有资源与其绑定。

平台特定差异

  • Metal(适用于 iOS 和 tvOS 平台)不支持对纹理的原子操作。Metal 也不支持对缓冲区的 GetDimensions 查询。如果需要,请将缓冲区大小信息作为常量传递给着色器。

  • OpenGL ES 3.1(适用于 Android、iOS、tvOS 平台)仅保证一次支持 4 个计算缓冲区。实际的实现通常支持更多数量,但在一般情况下,如果为 OpenGL ES 进行开发,应考虑在结构中对相关数据分组,而不是将每个数据项放在自己的缓冲区中。

仅限 HLSL 或仅限 GLSL 的计算着色器

通常情况下会以 HLSL 编写计算着色器文件,并自动将这些文件编译或转换到所有需要的平台中。但是,可以阻止转换为其他语言(即仅保留 HLSL 平台)或者手动编写 GLSL 计算代码。

以下信息仅适用于仅限 HLSL 或仅限 GLSL 的计算着色器,而不适用于跨平台版本。这是因为此信息可能导致计算着色器源代码被排除在某些平台之外。

  • 对于非 HLSL 平台,不会处理 CGPROGRAMENDCG 关键字包围的计算着色器源代码。

  • GLSLPROGRAMENDGLSL 关键字包围的计算着色器源代码视为 GLSL 源代码,并逐字发出。这仅在目标平台为 OpenGL 或 GLSL 平台时才奏效。还应注意,虽然自动转换的着色器遵循缓冲区上的 HLSL 数据布局,但是手动编写的 GLSL 着色器将遵循 GLSL 布局规则。

变体和关键字

You can use keywords to produce multiple variants of compute shaders, the same as you can for graphics shaders.

For general information on variants, see Shader variants. For information on how to implement these features in compute shaders, see Declaring and using shader keywords in HLSL and the ComputeShader API documentation.

在运行时替换着色器
Error and loading shaders