Version: 2023.2
Declaring and using shader keywords in HLSL
使用 Cg/HLSL 访问着色器属性

着色器语义

编写 HLSL 着色器程序时, 输入和输出变量需要通过语义来表明 其“意图”。这是 HLSL 着色器语言中的 标准概念;请参阅 MSDN 上的语义 (Semantics) 文档以了解更多详细信息。

顶点着色器输入语义

The main vertex shader function (indicated by the #pragma vertex directive) needs to have semantics on all the input parameters. These correspond to individual Mesh data elements, like vertex position, normal mesh, and texture coordinates. See vertex program inputs for more details.

以下是一个简单的顶点着色器的示例,它采用顶点位置 和纹理坐标作为输入。像素着色器 将纹理坐标可视化为颜色。

Shader "Unlit/Show UVs"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            struct v2f {
                float2 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
            };

            v2f vert (
                float4 vertex : POSITION, // 顶点位置输入
                float2 uv : TEXCOORD0 // 第一个纹理坐标输入
                )
            {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex);
                o.uv = uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return fixed4(i.uv, 0, 0);
            }
            ENDCG
        }
    }
}

Instead of spelling out all individual inputs one by one, it’s also possible to declare a structure of them, and indicate semantics on each individual member variable of the struct.

片元着色器输出语义

通常,片元(像素)着色器会输出颜色,并具有 SV_Target 语义。上面示例中的片元着色器 完全就是这样的:

fixed4 frag (v2f i) : SV_Target

函数 frag 的返回类型为 fixed4(低精度 RGBA 颜色)。因为它只返回一个值,所以语义 由函数自身指示: SV_Target

It’s also possible to return a structure with the outputs. The fragment shader above could be rewritten this way too, and it would do exactly the same:

struct fragOutput {
    fixed4 color : SV_Target;
};
fragOutput frag (v2f i)
{
    fragOutput o;
    o.color = fixed4(i.uv, 0, 0);
    return o;
}

从片元着色器返回结构对于不止返回单个颜色的 着色器非常有用。片元着色器 输出支持的其他语义如下。

SV_TargetN:多个渲染目标

SV_Target1SV_Target2 等等:这些是着色器写入的附加颜色。这在一次渲染到多个渲染目标(称为“多渲染目标”渲染技术,简称 MRT)时使用。SV_Target0 等同于 SV_Target

SV_Depth:像素着色器深度输出

Usually the fragment shader doesn’t override the Z buffer value, and a default value is used from the regular triangle rasterization. However, for some effects it’s useful to output custom Z buffer depth values per pixel.

Note that on many GPUs this turns off some depth buffer optimizations, so don’t override Z buffer value without a good reason. The cost incurred by SV_Depth varies depending on the GPU architecture, but overall it’s fairly similar to the cost of alpha testing (using the built-in clip() function in HLSL). Render shaders that modify depth after all regular opaque shaders (for example, by using the AlphaTest rendering queue.

深度输出值必须为单个 float

顶点着色器输出和片元着色器输入

顶点着色器需要输出顶点的最终裁剪空间位置,以便 GPU 知道屏幕上的栅格化位置以及深度。此输出需要具有 SV_POSITION 语义,并为 float4 类型。

顶点着色器生成的所有其他输出(“插值器”或“变化”)都是您的特定着色器需要的。从顶点着色器输出的值将在渲染三角形的面上进行插值,并且每个像素的值将作为输入传递给片元着色器。

Many modern GPUs don’t really care what semantics these variables have; however some old systems (most notably, shader model 2 GPUs) did have special rules about the semantics:

  • TEXCOORD0TEXCOORD1 等语义用于指示任意高精度数据,如纹理坐标和位置。
  • 顶点输出和片元输入的 COLOR0COLOR1 语义用于低精度 0 到 1 范围的数据(如简单的颜色值)。

为了获得最佳的跨平台支持,应将顶点输出和 片元输入标记为 TEXCOORDn 语义。

插值器数量限制

对于总共可以使用多少个插值器变量将信息 从顶点传递到片元着色器,存在一些限制。该限制 取决于平台和 GPU,一般准则如下:

  • Up to 8 interpolators: Direct3D 11 9.x level (Windows Phone) . Since the interpolator count is limited, but each interpolator can be a 4-component vector, some shaders pack things together to stay within limits. For example, you can pass two texture coordinates in one float4 variable (.xy for one coordinate, .zw for the second coordinate).
  • Up to 10 interpolators: Shader model 3.0 (#pragma target 3.0).
  • 最多 16 个插值器:OpenGL ES 3.0 (Android) 和 Metal (iOS)。
  • 最多 32 个插值器:Direct3D 10 着色器模型 4.0 (#pragma target 4.0)。

Regardless of your particular target hardware, it’s generally a good idea to use as few interpolators as possible for performance reasons.

其他特殊语义

屏幕空间像素位置:VPOS

片元着色器可以接收渲染为特殊 VPOS 语义的像素的位置。 此功能仅从着色器模型 3.0 开始存在,因此着色器需要具有 #pragma target 3.0 编译指令。

On different platforms the underlying type of the screen space position input varies, so for maximum portability use the UNITY_VPOS_TYPE type for it, which is float4 on most platforms.

Additionally, using the pixel position semantic makes it hard to have both the clip space position (SV_POSITION) and VPOS in the same vertex-to-fragment structure. Therefore, the vertex shader should output the clip space position as a separate “out” variable. See the example shader below:

Shader "Unlit/Screen Position"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0

            // 注意:此结构中没有 SV_POSITION
            struct v2f {
                float2 uv : TEXCOORD0;
            };

            v2f vert (
                float4 vertex : POSITION, // 顶点位置输入
                float2 uv : TEXCOORD0, // 纹理坐标输入
                out float4 outpos : SV_POSITION // 裁剪空间位置输出
                )
            {
                v2f o;
                o.uv = uv;
                outpos = UnityObjectToClipPos(vertex);
                return o;
            }

            sampler2D _MainTex;

            fixed4 frag (v2f i, UNITY_VPOS_TYPE screenPos : VPOS) : SV_Target
            {
                // screenPos.xy 将包含像素整数坐标。
                // 使用它们来实现一个跳过渲染 4x4 像素块的
                // 棋盘图案

                // 棋盘图案中 4x4 像素块的 checker 值
                // 为负
                screenPos.xy = floor(screenPos.xy * 0.25) * 0.5;
                float checker = -frac(screenPos.r + screenPos.g);

                // 如果值为负,则 clip HLSL 指令停止渲染像素
                clip(checker);

                // 对于保留的像素,读取纹理并将其输出
                fixed4 c = tex2D (_MainTex, i.uv);
                return c;
            }
            ENDCG
        }
    }
}

面对方向:VFACE

片元着色器可以接收一种指示渲染表面是面向摄像机还是背对摄像机的变量。这在渲染应从两侧可见的几何体时非常有用 - 通常用于树叶和类似的薄型物体。VFACE 语义输入变量将包含表示正面三角形的正值,以及表示背面三角形的负值。

此功能从着色器模型 3.0 开始才存在,因此着色器需要具有 #pragma target 3.0 编译指令。

Shader "Unlit/Face Orientation"
{
    Properties
    {
        _ColorFront ("Front Color", Color) = (1,0.7,0.7,1)
        _ColorBack ("Back Color", Color) = (0.7,1,0.7,1)
    }
    SubShader
    {
        Pass
        {
            Cull Off // 关闭背面剔除

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0

            float4 vert (float4 vertex : POSITION) : SV_POSITION
            {
                return UnityObjectToClipPos(vertex);
            }

            fixed4 _ColorFront;
            fixed4 _ColorBack;

            fixed4 frag (fixed facing : VFACE) : SV_Target
            {
                // 正面的 VFACE 输入为正,
                // 背面的为负。根据这种情况
                // 输出两种颜色中的一种。
                return facing > 0 ?_ColorFront : _ColorBack;
            }
            ENDCG
        }
    }
}

The shader above uses the Cull state to disable back-face culling (by default back-facing triangles aren’t rendered at all). Here is the shader applied to a bunch of Quad meshes, rotated at different orientations:

顶点 ID:SV_VertexID

顶点着色器可以接收具有“顶点编号”(为无符号整数)的变量。当您想要从纹理或 ComputeBuffers 中 获取额外的每顶点数据时,这非常有用。

此功能从 DX10(着色器模型 4.0)和 GLCore/OpenGL ES 3 开始才存在,因此着色器需要具有 #pragma target 3.5 编译指令。

Shader "Unlit/VertexID"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.5

            struct v2f {
    fixed4 color : TEXCOORD0;
                float4 pos : SV_POSITION;
            };

            v2f vert (
                float4 vertex : POSITION, // 顶点位置输入
                uint vid : SV_VertexID // 顶点 ID,必须为 uint
                )
            {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex);
                // 基于顶点 ID 输出时髦颜色
                float f = (float)vid;
                o.color = half4(sin(f/10),sin(f/100),sin(f/1000),0) * 0.5 + 0.5;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    return i.color;
            }
            ENDCG
        }
    }
}
Declaring and using shader keywords in HLSL
使用 Cg/HLSL 访问着色器属性