Version: 6000.3
语言: 中文
将信息传递到 HLSL 中的着色器编译器
Unity 中的 GLSL

为不同的图形 API 编写着色器

在某些情况下,不同图形 API 之间的图形渲染行为方式存在差异。大多数情况下,Unity 编辑器会隐藏差异,但在某些情况下,编辑器无法为您执行此作。下面列出了这些情况,以及发生这些情况时需要采取的措施。

渲染纹理坐标

垂直纹理坐标约定在两种类型的平台之间有所不同:类似 Direct3D 和类似 OpenGL。

  • 类似 Direct3D:坐标顶部为 0,向下增加。这适用于 Direct3D、Metal 和主机。
  • 类似 OpenGL:坐标底部为 0,向上增加。这适用于 OpenGL 和 OpenGL ES。

这种差异往往不会对您的项目产生任何影响,除非在渲染成渲染纹理(Render Texture一种特殊类型的纹理,在运行时创建和更新。要使用它们,请先创建一个新的渲染纹理,并指定要渲染到其中的摄像机之一。然后,你可以在材质中使用渲染纹理,就像使用常规纹理一样。更多信息
请参阅术语表
.在类似 Direct3D 的平台上渲染到纹理时,Unity 会在内部颠倒渲染。这使得平台之间的约定匹配,以类似 OpenGL 的平台约定为标准。

图像效果和 UV 空间中的渲染是着色器GPU 上运行的程序。更多信息
请参阅术语表
您需要采取措施以确保不同的坐标约定不会在项目中产生问题。

图像效果

使用图像效果和抗锯齿时,图像效果的生成源纹理不会翻转以匹配类似 OpenGL 的平台约定。在这种情况下,Unity 会渲染到屏幕上以获得抗锯齿,然后将渲染解析为 Render Texture,以便使用 Image Effect 进行进一步处理。

如果你的图像效果是一次处理一个渲染纹理的简单效果,则 Graphics.Blit 会处理不一致的坐标。但是,如果在图像效果中同时处理多个呈现纹理,则呈现纹理可能会在类似 Direct3D 的平台中以及使用抗锯齿时以不同的垂直方向出现。要标准化坐标,您需要手动“翻转”屏幕纹理顶点着色器 渲染模型时在 3D 模型的每个顶点上运行的程序。更多信息
请参阅术语表
使其符合类似 OpenGL 的坐标标准。

以下代码示例演示了如何执行此作:

// Flip sampling of the Texture:
// The main Texture
// texel size will have negative Y).

#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
        uv.y = 1-uv.y;
#endif

类似的情况也发生在 GrabPass 上。生成的渲染纹理实际上可能不会在类似 Direct3D(非类似 OpenGL)的平台上颠倒过来。如果你的着色器代码对GrabPass纹理进行采样,请使用ComputeGrabScreenPos函数。

在UV空间中渲染

在纹理坐标 (UV) 空间中呈现特殊效果或工具时,可能需要调整着色器,以便在类似 Direct3D 和类似 OpenGL 的系统之间呈现一致。你可能还需要在渲染到屏幕和渲染到纹理之间调整渲染。通过上下颠倒类似 Direct3D 的投影来调整这些内容,使其坐标与类似 OpenGL 的投影坐标匹配。

内置变量 ProjectionParams.x包含一个+1–1价值。-1表示投影已倒置以匹配类似 OpenGL 的投影坐标,而+1表示它尚未翻转。您可以在着色器中检查此值,然后执行不同的作。下面的示例检查投影是否已翻转,如果是,则翻转,然后返回要匹配的UV坐标。

float4 vert(float2 uv : TEXCOORD0) : SV_POSITION
{
    float4 pos;
    pos.xy = uv;
    // This example is rendering with upside-down flipped projection,
    // so flip the vertical UV coordinate too
    if (_ProjectionParams.x < 0)
        pos.y = 1 - pos.y;
    pos.z = 0;
    pos.w = 1;
    return pos;
}

裁剪空间坐标

与纹理坐标类似,剪辑空间坐标 (也称为投影后空间坐标) 在类似 Direct3D 和类似 OpenGL 的平台之间有所不同:

  • 类似 Direct3D:剪辑空间深度从近平面的 +1.0 到远平面的 0.0。这适用于 Direct3D、Metal 和控制台。

  • 类似 OpenGL:剪辑空间深度从近平面的 –1.0 到远平面的 +1.0。这适用于 OpenGL 和 OpenGL ES。

在着色器代码中,您可以使用UNITY_NEAR_CLIP_VALUE 内置宏,根据平台获取近平面值。

在脚本代码中,使用 GL.GetGPUProjectionMatrix 从 Unity 的坐标系(遵循类似 OpenGL 的约定)转换为类似 Direct3D 的坐标(如果这是平台所期望的)。

着色器计算的精度

为避免精度问题,请确保在目标平台上测试着色器。移动设备和PC中的GPU在处理浮点类型的方式上有所不同。PC GPU将所有浮点类型(浮点、半点和固定)视为相同 - 它们使用完整的32位精度执行所有计算,而许多移动设备GPU不这样做。

有关详细信息,请参阅在着色器中使用 16 位精度

着色器数据类型支持

在 Unity 中编写着色器时,请务必注意 Unity 定义half作为floatmin16float. 如果 着色器精度模型(Shader Precision Model) 设置为 平台默认(Platform Default),halffloat在 macOS 上和min16float在iOS/tvOS上。如果将着色器精度模型设置为统一,则halfmin16float在 macOS/tvOS/iOS 上。

从 Unity 6 开始,的大小和对齐方式min16float在 Metal 上,任何 CPU 可见缓冲区上都是 4 个字节,例如顶点着色器输入、常量缓冲区和结构化缓冲区。因此,的大小和对齐方式half无论平台还是项目设置广泛的设置集合,允许您配置物理、音频、网络、图形、输入和项目的许多其他区域的行为方式。更多信息
请参阅术语表
. 在以前版本的 Unity 上,作为min16float是 2 个字节,缓冲区的布局包含half因平台和所选的着色器精度模型设置而异。由于此问题,iOS 和 tvOS 用户在将数据上传到 iOS/tvOS 上的 GPU 缓冲区时必须添加 C# 代码作为解决方法,这不再适用于 Unity 6。要在 Unity 6 中使用 FXC 编译时启用旧行为,您可以包含#pragma metal_fxc_allow_float16_in_cpu_visible_buffers在着色器中。

着色器中的常量声明

用途constHLSL(参见 msdn.microsoft.com)和 OpenGL 的 GLSL(参见维基百科)着色器语言Microsoft不同。

  • Microsoft 的 HLSLconst与 C# 和 C++ 中的含义大致相同,因为声明的变量在其作用域内是只读的,但可以以任何方式初始化。

  • OpenGL 的 GLSLconst意味着该变量实际上是一个编译时常量,因此必须使用编译时约束(文字值或其他计算)对其进行初始化consts).

最好遵循 OpenGL 的 GLSL 语义,仅将变量声明为const当它真正不变时。避免初始化constvariable 与其他一些可变您可以更改可变包的内容。这是不可变的反义词。只有本地包嵌入式包可变的。
请参阅术语表
值(例如,作为函数中的局部变量)。这也适用于 Microsoft 的 HLSL,因此使用const这样可以避免某些平台上的混淆错误。

将缓冲区与 GPU 缓冲区一起使用

如果使用缓冲区在着色器中声明变量,然后使用 GPU 计算缓冲区图形缓冲区中的数据设置值,则数据布局可能不匹配,具体取决于图形 API。这意味着您可能会覆盖数据或将变量设置为错误的值。

例如,如果您使用cbuffer或 Unity 的常量缓冲区宏,具体取决于常量缓冲区的数据布局和图形 API,一个float3可能会成为float4float可能会成为float2.

您可以执行以下作,以确保所有图形 API 都使用相同的数据布局编译缓冲区:

  • float4float4x4而不是float3float3x3因为float4变量在所有图形 API 上的大小相同,而float3变量在某些图形 API 上可能会变为不同的大小。
  • 按大小递减顺序声明变量,例如float4然后float2然后float,因此所有图形 API 都以相同的方式构建数据。

例如:

cbuffer myConstantBuffer {
    float4x4 matWorld;
    float4 vObjectPosition; // Uses a float4 instead of a float3
    float arrayIndex;
}

着色器使用的语义

要使着色器在所有平台上工作,某些着色器值应使用以下语义:

  • 顶点着色器输出(剪辑空间)位置SV_POSITION.有时着色器使用 POSITION 语义来使着色器在所有平台上工作。请注意,这不适用于索尼 PS4 或曲面细分。

  • 片段着色器输出颜色SV_Target.有时着色器会使用COLORCOLOR0让着色器在所有平台上工作。请注意,这不适用于索尼 PS4。

将网格体渲染为点时,输出PSIZE来自顶点着色器的语义(例如,将其设置为 1)。某些平台(如 OpenGL ES 或 Metal)在未从着色器写入点时将点大小视为“未定义”。

有关更多详细信息,请参阅有关着色器语义的文档。

Direct3D 着色器编译器语法

Direct3D 平台使用 Microsoft 的 HLSL 着色器编译器。HLSL 编译器比其他编译器更严格地处理各种细微的着色器错误。例如,它不接受未正确初始化的函数输出值。

使用此功能时可能遇到的最常见情况是:

  • 一个表面着色器为内置渲染管线编写着色器的简化方法。更多信息
    请参阅术语表
    顶点修饰符,该修饰符具有out参数。在表面着色器中,按如下方式初始化输出:
  void vert (inout appdata_full v, out Input o)
      {
        **UNITY_INITIALIZE_OUTPUT(Input,o);**
        // ...
      }
  • 部分初始化的值。例如,函数返回float4但代码仅将.xyz它的价值观。设置所有值或更改为float3如果您只需要三个值。

  • tex2D在顶点着色器中。这是无效的,因为顶点着色器中不存在 UV 导数。您需要改为对显式 mip 级别进行采样;例如,使用tex2Dlod (tex, float4(uv,0,0)).您还需要添加#pragma target 3.0tex2Dlod是着色器模型 3.0 的一项功能。

着色器中的 DirectX 11 (DX11) HLSL 语法

Surface 着色器编译管道的某些部分不理解特定于 DirectX 11 的 HLSL(Microsoft 的着色器语言)语法。

如果使用 HLSL 功能,例如StructuredBuffers,RWTextures和其他非 DirectX 9 语法,将它们包装在仅限 DirectX X11 的预处理器宏中,如以下示例所示。

#ifdef SHADER_API_D3D11
// DirectX11-specific code, for example
StructuredBuffer<float4> myColors;
RWTexture2D<float4> myRandomWriteTexture;
#endif

着色器中的深度(Z)方向

深度(Z)方向在不同的着色器平台上有所不同。

DirectX 11、DirectX 12、金属:方向相反

  • 深度 (Z) 缓冲区在近平面处为 1.0,在远平面处减小到 0.0。

  • 裁剪空间范围为 [near,0](表示近平面处的近平面距离,远平面处减小到 0.0)。

其他平台:传统方向

  • 深度 (Z) 缓冲区值在近平面上为 0.0,在远平面上为 1.0。

  • 剪辑空间取决于具体平台:
    • 在类似 Direct3D 的平台上,范围为 [0,far] (表示近平面处为 0.0,增加到远平面处的远平面距离) 。
    • 在类似 OpenGL 的平台上,范围为 [-near,far](表示减去近平面处的近平面距离,增加到远平面处的远平面距离)。

请注意,反向深度 (Z) 与浮点相结合深度缓冲区保存图像中每个像素的 z 值深度的内存存储,其中 z 值是投影平面中每个呈现像素的深度。更多信息
请参阅术语表
,显着提高了相对于传统方向的深度缓冲区精度。这样做的优点是 Z 坐标的冲突更少,阴影更好,尤其是在使用小近平面和大远平面时。

因此,当您从深度 (Z) 反转的平台上使用着色器时:

  • UNITY_REVERSED_Z已定义。
  • _CameraDepth纹理纹理范围为 1(近)到 0(远)。
  • 剪辑空间范围在“近”(近)到 0(远)之间。

但是,以下宏和函数会自动计算深度 (Z) 方向的任何差异:

  • Linear01Depth(float z)
  • LinearEyeDepth(float z)
  • UNITY_CALC_FOG_FACTOR(坐标)

获取深度缓冲区

如果要手动获取深度 (Z) 缓冲区值,则可能需要检查缓冲区方向。下面是一个示例:

float z = tex2D(_CameraDepthTexture, uv);
#if defined(UNITY_REVERSED_Z)
    z = 1.0f - z;
#endif

使用剪辑空间

如果手动使用剪辑空间 (Z) 深度,您可能还希望使用以下宏抽象平台差异:

float clipSpaceRange01 = UNITY_Z_0_FAR_FROM_CLIPSPACE(rawClipSpace);

注意:此宏不会更改 OpenGL 或 OpenGL ES 平台上的剪辑空间,因此它在这些平台上返回“-near”1(近)到远(远)范围内。

投影矩阵

GL.GetGPUProjectionMatrix() 如果您位于深度 (Z) 反转的平台上,则返回 z 还原矩阵。 但是,如果您手动从投影矩阵进行合成(例如,用于自定义阴影或深度渲染),则需要通过脚本自行恢复深度 (Z) 方向。

下面是一个例子:

var shadowProjection = Matrix4x4.Ortho(...); //shadow camera projection matrix
var shadowViewMat = ...     //shadow camera view matrix
var shadowSpaceMatrix = ... //from clip to shadowMap texture space

//'m_shadowCamera.projectionMatrix' is implicitly reversed
//when the engine calculates device projection matrix from the camera projection
m_shadowCamera.projectionMatrix = shadowProjection;

//'shadowProjection' is manually flipped before being concatenated to 'm_shadowMatrix'
//because it is seen as any other matrix to a Shader.
if(SystemInfo.usesReversedZBuffer)
{
    shadowProjection[2, 0] = -shadowProjection[2, 0];
    shadowProjection[2, 1] = -shadowProjection[2, 1];
    shadowProjection[2, 2] = -shadowProjection[2, 2];
    shadowProjection[2, 3] = -shadowProjection[2, 3];
}
    m_shadowMatrix = shadowSpaceMatrix * shadowProjection * shadowViewMat;

深度 (Z) 偏差

Unity 会自动处理深度 (Z) 偏差,以确保它与 Unity 的深度 (Z) 方向相匹配。但是,如果您使用的是本机代码渲染插件,则需要消除 C 或 C++ 代码中的(反向)深度 (Z) 偏差。

用于检查深度 (Z) 方向的工具

将信息传递到 HLSL 中的着色器编译器
Unity 中的 GLSL