Unity3D :创建支持 GPU 实例化的着色器

推荐:将NSDT场景编辑器加入你的3D工具链
3D工具集:NSDT简石数字孪生

创建支持 GPU 实例化的着色器

本页包含有关如何向自定义 Unity 着色器添加 GPU 实例化支持的信息。它首先介绍了自定义 Unity 着色器支持 GPU 实例化所需的着色器关键字、变量和函数。然后,它包括如何将每个实例的数据添加到表面着色器和顶点/片段着色器的示例。

渲染管线兼容性

功能内置渲染管线通用渲染管线 (URP)高清渲染管线 (HDRP)自定义可编程渲染管线 (SRP)
自定义 GPU 实例化着色器

着色器修改

本节包含有关与 GPU 实例化相关的着色器添加的信息。

添加描述
#pragma multi_compile_instancing生成实例化变体。这对于片段和顶点着色器是必需的。对于表面着色器,它是可选的。
#pragma instancing_options指定 Unity 用于实例的选项。有关可用选项开关的信息,请参阅 #pragma instancing_options
UNITY_VERTEX_INPUT_INSTANCE_ID在顶点着色器输入/输出结构中定义实例 ID。若要使用此宏,请启用 INSTANCING_ON 着色器关键字。否则,Unity 不会设置实例 ID。要访问实例 ID,请在 #ifdef INSTANCING_ON 块中使用。
如果不使用此块,则变体将无法编译。
vertexInput.instanceID
UNITY_INSTANCING_BUFFER_START(bufferName)声明名为 的每实例常量缓冲区的开始。使用此宏来包装希望对每个实例唯一的属性的声明。使用 声明缓冲区内的属性。bufferNameUNITY_INSTANCING_BUFFER_ENDUNITY_DEFINE_INSTANCED_PROP
UNITY_INSTANCING_BUFFER_END(bufferName)声明名为 的每实例常量缓冲区的结束。使用此宏来包装希望对每个实例唯一的属性的声明。使用 声明缓冲区内的属性。bufferNameUNITY_INSTANCING_BUFFER_STARTUNITY_DEFINE_INSTANCED_PROP
UNITY_DEFINE_INSTANCED_PROP(type, propertyName)定义具有指定类型和名称的每实例着色器属性。在下面的示例中,该属性是唯一的。_Color
UNITY_SETUP_INSTANCE_ID(v);允许着色器函数访问实例 ID。对于顶点着色器,在开始时需要此宏。对于片段着色器,此添加是可选的。有关示例,请参阅顶点和片段着色器
UNITY_TRANSFER_INSTANCE_ID(v, o);将实例 ID 从输入结构复制到顶点着色器中的输出结构。如果需要访问片段着色器中每个实例的数据,请使用此宏。
UNITY_ACCESS_INSTANCED_PROP(bufferName, propertyName)访问实例化常量缓冲区中的每实例着色器属性。Unity 使用实例 ID 索引到实例数据数组中。 必须与包含指定属性的常量缓冲区的名称匹配。此宏针对INSTANCING_ON和非实例化变体的编译方式不同。bufferName

使用多个每个实例的属性时,无需在对象中填充所有这些属性。此外,如果一个实例缺少属性,Unity 将从引用的材料中获取默认值。如果材质没有属性的默认值,Unity 会将该值设置为 0。不要将非实例化属性放在 中,因为这会禁用实例化。相反,为他们创建不同的材料。MaterialPropertyBlockMaterialPropertyBlock

Instancing_options开关

[#pragma instancing_options](#pragma-instancing_options) 指令可以使用以下开关:

开关描述
forcemaxcount:batchSize  maxcount:batchSize在大多数平台上,Unity 会自动计算实例化数据数组大小。它将目标设备上的最大常量缓冲区大小除以包含所有每个实例属性的结构的大小。通常,您无需担心批量大小。但是,某些平台需要固定的阵列大小。若要指定这些平台的批大小,请使用该选项。其他平台会忽略此选项。如果要强制所有平台使用批大小,请使用 。例如,当您的项目使用 DrawMeshInstanced 发出包含 256 个实例化精灵的绘制调用时,这很有用。这两个选项的默认值为 500。maxcountforcemaxcount
assumeuniformscaling指示 Unity 假定所有实例都具有统一的缩放比例(所有 X、Y 和 Z 轴的缩放比例相同)。
nolodfade使 Unity 不将 GPU 实例化应用于 LOD 淡入淡出值。
nolightprobe防止 Unity 将 GPU 实例化应用于光照探针值及其遮挡数据。如果项目不包含同时使用 GPU 实例化和光照探针的游戏对象,则设置此选项可以提高性能。ON
nolightmap防止 Unity 将 GPU 实例化应用于光照贴图集信息值。如果项目不包含同时使用 GPU 实例化和光照贴图的游戏对象,则设置此选项可以提高性能。ON
procedural:FunctionName生成用于 Graphics.DrawMeshInstancedIndirect 的附加变体。在顶点着色器阶段开始时,Unity 调用冒号后指定的函数。若要手动设置实例数据,请向此函数添加每个实例的数据,方法与通常将每个实例数据添加到着色器的方式相同。如果片段着色器中包含任何提取的实例属性,Unity 还会在片段着色器的开头调用此函数。

将着色器变体与 GPU 实例化结合使用

默认情况下,Unity 会生成具有实例化变体的 Surface 着色器,除非您在指令中指定。Unity 会忽略在曲面着色器中使用#pragma multi_compile_instancing。noinstancing#pragma

Unity 的标准和标准镜面着色器默认具有实例化支持,但除了转换之外没有每个实例的属性。

如果您的场景不包含启用了 GPU 实例化的游戏对象,则 Unity 会去除实例化着色器变体。要覆盖剥离行为:

  1. 打开“项目设置”(菜单:“编辑>项目设置”)。
  2. 转到图形
  3. 在“着色器剥离”部分中,将“实例化变体”设置为“全部保留”。

将每个实例的属性添加到 GPU 实例化着色器

默认情况下,Unity GPU 在每个实例化绘制调用中实例化具有不同变换的游戏对象。若要向实例添加更多变体,请修改着色器以添加每个实例的属性,例如颜色。您可以在表面着色器和顶点/片段着色器中执行此操作。

自定义着色器不需要每个实例的数据,但它们确实需要一个实例 ID,因为世界矩阵需要一个实例 ID 才能正常运行。表面着色器会自动设置实例 ID,但自定义顶点和片段着色器不会。要设置自定义顶点和片段着色器的 ID,请在着色器的开头使用 UNITY_SETUP_INSTANCE_ID。有关如何执行此操作的示例,请参阅顶点和片段着色器。

声明实例化属性时,Unity 会将游戏对象上设置的 MaterialPropertyBlock 对象中的所有属性值收集到单个绘制调用中。有关如何使用 MaterialPropertyBlock 对象在运行时设置每个实例的数据的示例,请参阅在运行时更改每个实例的数据。

将每个实例的数据添加到多通道着色器时,请记住以下几点:

  • 如果多通道着色器具有两个以上的通道,则 Unity 仅实例化第一个通道。这是因为 Unity 稍后会为每个对象一起渲染通道,这会强制更改材质。
  • 如果在内置渲染管线中使用前向渲染路径,Unity 将无法有效地实例化受多个光源影响的对象。Unity 只能有效地将实例化用于基本通道,而不能用于其他通道。有关光照通道的详细信息,请参阅有关前向渲染和通道标记的文档。

表面着色器示例

下面的示例演示如何为每个实例创建具有不同颜色值的实例化曲面着色器。

Shader "Custom/InstancedColorSurfaceShader" 
{
    Properties 
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200
        CGPROGRAM
        // Uses the physically based standard lighting model with shadows enabled for all light types.
        #pragma surface surf Standard fullforwardshadows
        // Use Shader model 3.0 target
        #pragma target 3.0
        sampler2D _MainTex;
        struct Input 
        {
            float2 uv_MainTex;
        };
        half _Glossiness;
        half _Metallic;
        UNITY_INSTANCING_BUFFER_START(Props)
           UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
        UNITY_INSTANCING_BUFFER_END(Props)
        void surf (Input IN, inout SurfaceOutputStandard o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

顶点和片段着色器示例

下面的示例演示如何为每个实例创建具有不同颜色值的实例化顶点和片段着色器。与曲面着色器不同,创建顶点和片段着色器时,必须使用 UNITY_SETUP_INSTANCE_ID 手动设置实例 ID。

Shader "Custom/SimplestInstancedShader"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID // use this to access instanced properties in the fragment shader.
            };

            UNITY_INSTANCING_BUFFER_START(Props)
                UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
            UNITY_INSTANCING_BUFFER_END(Props)

            v2f vert(appdata v)
            {
                v2f o;

                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o);
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i);
                return UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
            }
            ENDCG
        }
    }
}

在运行时更改每个实例的数据示例

下面的示例演示如何使用 MaterialPropertyBlock 对象在运行时为一组游戏对象设置每个实例的数据。它将上述着色器示例中的属性设置为随机颜色。_Color

重要提示:MaterialPropertyBlocks破坏了SRP批处理器的兼容性。有关更多信息,请参阅 GPU 实例化:要求和兼容性。

using UnityEngine;

public class MaterialPropertyBlockExample : MonoBehaviour
{
    public GameObject[] objects;

    void Start()
    {
        MaterialPropertyBlock props = new MaterialPropertyBlock();
        MeshRenderer renderer;

        foreach (GameObject obj in objects)
        {
            float r = Random.Range(0.0f, 1.0f);
            float g = Random.Range(0.0f, 1.0f);
            float b = Random.Range(0.0f, 1.0f);
            props.SetColor("_Color", new Color(r, g, b));

            renderer = obj.GetComponent<MeshRenderer>();
            renderer.SetPropertyBlock(props);
        }
    }
}

3D建模学习工作室整理翻译,转载请注明出处!

上一篇:Unity3D :GPU 实例化 (mvrlink.com)

下一篇:Unity3D :绘制调用批处理 (mvrlink.com)

NSDT场景编辑器 | NSDT 数字孪生 | GLTF在线编辑器 | 3D模型在线转换 | UnrealSynth虚幻合成数据生成器 | 3D模型自动纹理化工具
2023 power by nsdt©鄂ICP备2023000829号