Unity3D:计算着色器
推荐:将NSDT场景编辑器加入你的3D工具链
3D工具集:NSDT简石数字孪生
计算着色器
计算着色器是在正常渲染管道之外的 GPU 上运行的着色器程序。
它们可用于大规模并行的GPGPU算法,或加速部分游戏渲染。为了有效地使用它们,通常需要对 GPU 架构和并行算法有深入的了解;以及DirectCompute,OpenGL Compute,CUDA或OpenCL的知识。
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
- Modern consoles
在运行时可使用 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 FillWithRedcompute 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 MyTex
;SamplerState samplerMyTex
)。在此情况下,采样器将初始化为纹理的过滤/包裹/各向异性 (filter/wrap/aniso) 设置。 - 使用预定义采样器。因此,该名称必须具有
Linear
或Point
(对于过滤模式)和Clamp
或Repeat
(包裹模式)。例如,SamplerState MyLinearClampSampler
会创建一个具有线性过滤模式和钳制包裹模式的采样器。
有关更多信息,请参阅采样器状态文档。
跨平台支持
与常规着色器一样,Unity 能够将计算着色器从 HLSL 转换为其他着色器语言。因此,对于最简单的跨平台生成,应在 HLSL 中编写计算着色器。但是,执行此操作时需要考虑一些因素。
跨平台最佳实践
DirectX 11 (DX11) 支持在其他平台(如 Metal 或 OpenGL ES)上不支持的许多操作。因此,应始终确保着色器在提供更少支持的平台(而不是仅在 DX11 上)上具有良好定义的行为。以下是要考虑的一些事项:
- 越界内存访问是错误的。DX11 在读取时可能始终返回零,并且在读取某些写入时没有问题,但提供较少支持的平台可能会在执行此操作时导致 GPU 崩溃。密切注意特定于 DX11 的破解问题,与线程组大小倍数不匹配的缓冲区大小,试图从缓冲区的开头或结尾读取相邻的数据元素,以及类似的不兼容性。
- 初始化您的资源。新缓冲区和纹理的内容是未定义的。有些平台可能会提供全零,但在其他平台上,可能会有某种内容(包括非数字)。
- 绑定计算着色器声明的所有资源。即使您确定着色器在当前状态下由于分支而没有使用资源,仍必须确保有资源与其绑定。
平台特定差异
- Metal(适用于 iOS 和 tvOS 平台)不支持对纹理的原子操作。Metal 也不支持对缓冲区的
GetDimensions
查询。如果需要,请将缓冲区大小信息作为常量传递给着色器。 - OpenGL ES 3.1(适用于 Android、iOS、tvOS 平台)仅保证一次支持 4 个计算缓冲区。实际的实现通常支持更多数量,但在一般情况下,如果为 OpenGL ES 进行开发,应考虑在结构中对相关数据分组,而不是将每个数据项放在自己的缓冲区中。
- OpenGL (ES) 和 Vulkan 需要一个图像格式限定符,因为这不是只写的。
Unity 从尖括号中的类型 T 派生此限定符。格式限定符需要与绑定到 RWTexture 的 RenderTexture 的 GraphicsFormat/RenderTextureFormat 匹配。下表将 Unity 渲染纹理图形格式和渲染纹理格式映射到其相应的 HLSL 类型和图像格式限定符:RWTextures<T>
GraphicsFormat | RenderTextureFormat | HLSL type | GLSL image format qualifier |
---|---|---|---|
R32G32B32A32_SFloat | ARGBFloat | float4 | rgba32f |
R16G16B16A16_SFloat | ARGBHalf | min16float4/half4 | rgba16f |
R32G32_SFloat | RGFloat | float2 | rg32f |
R16G16_SFloat | RGHalf | min16float2/half2 | rg16f |
B10G11R11_UFloatPack32 | RGB111110Float | min10float3 | r11f_g11g_b10f |
R32_SFloat | RFloat | 浮点精度 | r32f |
R16_SFloat | RHalf | min16float/half | r16f |
R16G16B16A16_UNorm | ARGB64 | unorm min16float4/half4 | rgba16 |
A2B10G10R10_UNormPack32 | ARGB2101010 | unorm min10float4 | rgb10_a2 |
R8G8B8A8_UNorm | ARGB32 | unorm float4 | rgba8 |
R16G16_UNorm | RG32 | unorm min16float2/half2 | rg16 |
R8G8_UNorm | RG16 | unorm float2 | rg8 |
R16_UNorm | R16 | unorm min16float/half | r16 |
R8_UNorm | R8 | unorm float | r8 |
R16G16B16A16_SNorm | unsupported | snorm min16float4/half4 | rgba16_snorm |
R8G8B8A8_SNorm | unsupported | snorm float4 | rgba8_snorm |
R16G16_SNorm | unsupported | snorm min16float2/half2 | rg16_snorm |
R8G8_SNorm | unsupported | snorm float2 | rg8_snorm |
R16_SNorm | unsupported | snorm min16float/half | r16_snorm |
R8_SNorm | unsupported | snorm float | r8_snorm |
R32G32B32A32_SInt | ARGBInt | int4 | rgba32i |
R16G16B16A16_SInt | unsupported | min16int4 | rgba16i |
R8G8B8A8_SInt | unsupported | min12int4 | rgba8i |
R32G32_SInt | RGInt | int2 | rg32i |
R16G16_SInt | unsupported | min16int2 | rg16i |
R8G8_SInt | unsupported | min12int2 | rg8i |
R32_SInt | RInt | int | r32i |
R16_SInt | unsupported | min16int | r16i |
R8_SInt | unsupported | min12int | r8i |
R32G32B32A32_UInt | unsupported | uint4 | rgba32i |
R16G16B16A16_UInt | RGBAUShort | min16uint4 | rgba16ui |
R8G8B8A8_UInt | unsupported | unsupported | rgba8ui |
R32G32_UInt | unsupported | uint2 | rg32ui |
R16G16_UInt | unsupported | min16uint2 | rg16ui |
R8G8_UInt | unsupported | unsupported | rg8ui |
R32_UInt | unsupported | uint | r32ui |
R16_UInt | unsupported | min16uint | r16ui |
R8_UInt | unsupported | unsupported | r8ui |
A2B10G10R10_UIntPack32 | unsupported | unsupported | rgb10_a2ui |
仅限 HLSL 或仅限 GLSL 的计算着色器
通常情况下会以 HLSL 编写计算着色器文件,并自动将这些文件编译或转换到所有需要的平台中。但是,可以阻止转换为其他语言(即仅保留 HLSL 平台)或者手动编写 GLSL 计算代码。
以下信息仅适用于仅限 HLSL 或仅限 GLSL 的计算着色器,而不适用于跨平台版本。这是因为此信息可能导致计算着色器源代码被排除在某些平台之外。
- 对于非 HLSL 平台,不会处理
CGPROGRAM
和ENDCG
关键字包围的计算着色器源代码。 GLSLPROGRAM
和ENDGLSL
关键字包围的计算着色器源代码视为 GLSL 源代码,并逐字发出。这仅在目标平台为 OpenGL 或 GLSL 平台时才奏效。还应注意,虽然自动转换的着色器遵循缓冲区上的 HLSL 数据布局,但是手动编写的 GLSL 着色器将遵循 GLSL 布局规则。
变体和关键字
可以使用关键字生成计算着色器的多个变体,与图形着色器相同。
有关变体的一般信息,请参阅着色器变体。有关如何在计算着色器中实现这些功能的信息,请参阅在 HLSL 中声明和使用着色器关键字和计算着色器 API 文档。
由3D建模学习工作室整理翻译,转载请注明出处!