Unity Shader - multi_compile 变体测试

本文围绕Unity Shader变体测试展开。介绍了主要测试脚本、编辑器Inspector扩展及Shader代码。探讨了20个变体的生成方式,还提及运行时环境光照设置、关键字开关情况查看,最后指出开启特定关键字出现花屏及亮度设置方法。

Code

脚本的注释可以按看,有些说明,与技巧

Main Testing script - 主要测试的脚本

// jave.lin 2019.06.24
using UnityEngine;

public enum AMBIENT_TOGGLE
{
    On,
    Off
}

public enum AMBIENT_COLOR_TYPE
{
    LightModel,
    Sky,
    Equator,
    Ground
}

public enum AMBIENT_APPLY_ONLY_R
{
    On,
    Off
}

public class TestMultiCompile : MonoBehaviour
{
    public AMBIENT_TOGGLE ambientToggle = AMBIENT_TOGGLE.On;
    public AMBIENT_COLOR_TYPE ambientColorType = AMBIENT_COLOR_TYPE.LightModel;
    public AMBIENT_APPLY_ONLY_R ambientApplyOnlyR = AMBIENT_APPLY_ONLY_R.Off;
    public string[] enabledLocalKeywords;

    private MeshRenderer[] mrs;
    // Start is called before the first frame update
    void Start()
    {
        mrs = GetComponentsInChildren<MeshRenderer>();
        //避免有时你在退出unity运行时,有些开关的状态还是退出钱的设置,所以这里需要初始化一下
        //string[] allGlobalKeywords = { ... }; // 所有你想控制的全局关键字数组
        //for (int i = 0; i < allGlobalKeywords.Length; i++)
        //{
        //  Shader.DisableKeyword(allGlobalKeywords[i]);
        //}
        //然后有些需要Enable的,可以在下面这里再Enable
        //对局部的关键字就不需要上面的处理,因为你可以从Material.shaderKeywords里很容易看出来
        //对关键字的维护,可以参考TestMultiCompileEditor.cs的写法(目前是简单的写法)
    }

    // Update is called once per frame
    void Update()
    {
        // 应用优先级会被Shader.material.Enable/DisableKeyword覆盖
        if (ambientToggle == AMBIENT_TOGGLE.On)
            Shader.EnableKeyword("AMBIENT_ON");
        else
            Shader.DisableKeyword("AMBIENT_ON");

        // 下面的Enable/DisableKeyword都可以封装一下,用分组的方式,让同一组的互斥就好了
        switch (ambientColorType)
        {
            case AMBIENT_COLOR_TYPE.LightModel:
                Shader.EnableKeyword("LIGHTMODEL_AMBIENT");
                Shader.DisableKeyword("SKY_COLOR");
                Shader.DisableKeyword("EQUATOR_COLOR");
                Shader.DisableKeyword("GROUND_COLOR");
                break;
            case AMBIENT_COLOR_TYPE.Sky:
                Shader.DisableKeyword("LIGHTMODEL_AMBIENT");
                Shader.EnableKeyword("SKY_COLOR");
                Shader.DisableKeyword("EQUATOR_COLOR");
                Shader.DisableKeyword("GROUND_COLOR");
                break;
            case AMBIENT_COLOR_TYPE.Equator:
                Shader.DisableKeyword("LIGHTMODEL_AMBIENT");
                Shader.DisableKeyword("SKY_COLOR");
                Shader.EnableKeyword("EQUATOR_COLOR");
                Shader.DisableKeyword("GROUND_COLOR");
                break;
            case AMBIENT_COLOR_TYPE.Ground:
                Shader.DisableKeyword("LIGHTMODEL_AMBIENT");
                Shader.DisableKeyword("SKY_COLOR");
                Shader.DisableKeyword("EQUATOR_COLOR");
                Shader.EnableKeyword("GROUND_COLOR");
                break;
            default:
                break;
        }

        SetApplyOnlyR(ambientApplyOnlyR);

        // material.shaderKeywrods只会返回局部的关键字,所以Shader.EnableKeyword设置的是不会在这增加的
        enabledLocalKeywords = mrs[0].material.shaderKeywords;
    }

    private void SetApplyOnlyR(AMBIENT_APPLY_ONLY_R v)
    {

        for (int i = 0; i < mrs.Length; i++)
        {
            var mr = mrs[i];
            switch (v)
            {
                case AMBIENT_APPLY_ONLY_R.On:
                    mr.sharedMaterial.EnableKeyword("AMBIENT_ONLY_R");
                    break;
                case AMBIENT_APPLY_ONLY_R.Off:
                    mr.sharedMaterial.DisableKeyword("AMBIENT_ONLY_R");
                    break;
                default:
                    break;
            }
        }
    }
}

Editor - 编辑器Inspector扩展

// jave.lin 2019.06.24
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(TestMultiCompile))]
public class TestMultiCompileEditor : Editor
{
    // 可以从配置中读取
    private string[][] globalKeywordList =
    {
        new string[]{ "AMBIENT_ON", },  // group one
        new string[]{                   // group two
        "LIGHTMODEL_AMBIENT",
        "SKY_COLOR",
        "EQUATOR_COLOR",
        "GROUND_COLOR",
        }
    };
    // 可以从配置中读取
    private string[][] localKeywordList =
    {
        new string[] {"AMBIENT_ONLY_R", }   // group
    };

    private Material m;

    private bool globalKeywordFoldSummary = true;
    private bool localKeywordFoldSummary = true;

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        
        if (m == null)
            m = AssetDatabase.LoadAssetAtPath<Material>("Assets/Materials/TestMulitCompile.mat");
        
        int idx = 0;
        const int indent = 1;
        // global
        globalKeywordFoldSummary = EditorGUILayout.Foldout(globalKeywordFoldSummary, "Global Keyword Summary");
        if (globalKeywordFoldSummary)
        {
            EditorGUI.indentLevel += indent;
            idx = 0;
            foreach (var kws in globalKeywordList)
            {
                EditorGUILayout.Foldout(true, $"Global Keyword_Group{idx++}");
                foreach (var kw in kws)
                {
                    EditorGUILayout.Toggle(kw, Shader.IsKeywordEnabled(kw));
                }
            }
            EditorGUI.indentLevel -= indent;
        }

        // local
        localKeywordFoldSummary = EditorGUILayout.Foldout(localKeywordFoldSummary, "Local Keyword Summary");
        if (localKeywordFoldSummary)
        {
            EditorGUI.indentLevel += indent;
            idx = 0;
            foreach (var kws in localKeywordList)
            {
                EditorGUILayout.Foldout(true, $"Local Keyword_Group{idx++}");
                foreach (var kw in kws)
                {
                    EditorGUILayout.Toggle(kw, m.IsKeywordEnabled(kw));
                }
            }
            EditorGUI.indentLevel -= indent;
        }
    }
}

Shader Code

// jave.lin 2019.06.23
Shader "Test/TestMultiCompile"{
    SubShader{
        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // ====global keyword start====
            #pragma multi_compile __ AMBIENT_ON // 2个选项,定义multi_compile keyword 开关,_ 表示未选
            #pragma multi_compile __ LIGHTMODEL_AMBIENT SKY_COLOR EQUATOR_COLOR GROUND_COLOR // 5个选项,定义颜色选用的关键字
            // ====global keyword end====
            // ====local keyword start====
            #pragma multi_compile_local __ AMBIENT_ONLY_R // 2个选项,环境光颜色只有R通道的开关
            // ====local keyword end====
            #include "UnityCG.cginc"
            float4 vert (float4 vertex : POSITION) : SV_POSITION { return UnityObjectToClipPos(vertex); }
            fixed4 frag () : SV_Target{
                fixed4 col = fixed4(1,1,1,1);
                #if AMBIENT_ON // 判断开关而使用环境色调
                    #if AMBIENT_ONLY_R // 仅使用R通道的
                        #if LIGHTMODEL_AMBIENT // 光照综合处理后的系数
                        col.rgb *= UNITY_LIGHTMODEL_AMBIENT.r;
                        #elif SKY_COLOR
                        col.rgb *= unity_AmbientSky.r;
                        #elif EQUATOR_COLOR
                        col.rgb *= unity_AmbientEquator.r;
                        #elif GROUND_COLOR
                        col.rgb *= unity_AmbientGround.r;
                        #else
                        // nops
                        #endif
                    #else
                        #if LIGHTMODEL_AMBIENT // 光照综合处理后的系数
                        col.rgb *= UNITY_LIGHTMODEL_AMBIENT.rgb;
                        #elif SKY_COLOR
                        col.rgb *= unity_AmbientSky.rgb;
                        #elif EQUATOR_COLOR
                        col.rgb *= unity_AmbientEquator.rgb;
                        #elif GROUND_COLOR
                        col.rgb *= unity_AmbientGround.rgb;
                        #else
                        // nops
                        #endif
                    #endif
                #endif
                return col;
            }
            ENDCG
        }
    }
}

Shader Variants

在这里插入图片描述
图中variants included有20个

那么20个变体是怎么生成的呢?
我们才定义了那么几个关键字,生成就这么多了

compile and show code 右边有个下拉按钮,点击后有个小面板,再点击小面板中的 show 按钮。

Shader Variants Combines

点击面板中的 show 按钮后,我们在脚本中看到它是如何确定20个变体的

// Total snippets: 1
// -----------------------------------------
// Snippet #0 platforms ffffffff:
Keywords always included into build: AMBIENT_ON LIGHTMODEL_AMBIENT SKY_COLOR EQUATOR_COLOR GROUND_COLOR AMBIENT_ONLY_R // 我们定义的关键字列表,该shader中的全局,局部关键字都列在这

20 keyword variants used in scene: // 共有20个关键字

// 下面是所有可能的变体组合情况,这里就是变体的数量
// 我们就不要一个一个数了
// 它是通过每行的 multi_compile 的数量相乘的总数
// 例如:有3行multi_compile,每行分别有2,3,4个,那么总数就有2*3*4=24
// 所以我们的multi_compile行定义越多,影响的系数越大。
<no keywords defined> // 我们手写无定义的:__或是_,或是不写,Unity也会默认生出的项
AMBIENT_ONLY_R
LIGHTMODEL_AMBIENT
AMBIENT_ONLY_R LIGHTMODEL_AMBIENT
SKY_COLOR
AMBIENT_ONLY_R SKY_COLOR
EQUATOR_COLOR
AMBIENT_ONLY_R EQUATOR_COLOR
GROUND_COLOR
AMBIENT_ONLY_R GROUND_COLOR
AMBIENT_ON
AMBIENT_ON AMBIENT_ONLY_R
AMBIENT_ON LIGHTMODEL_AMBIENT
AMBIENT_ON AMBIENT_ONLY_R LIGHTMODEL_AMBIENT
AMBIENT_ON SKY_COLOR
AMBIENT_ON AMBIENT_ONLY_R SKY_COLOR
AMBIENT_ON EQUATOR_COLOR
AMBIENT_ON AMBIENT_ONLY_R EQUATOR_COLOR
AMBIENT_ON GROUND_COLOR
AMBIENT_ON AMBIENT_ONLY_R GROUND_COLOR



Runtime

Lighting - Environment Lighting

菜单:Windows->Rendering->Lighting Settings->Scene Tab

Scene Tab->Environment->Environment Lighting
在这里插入图片描述
将场景的环境光的Source设置为:Gradient,分别设置Sky,Equator,GroundColor颜色为:红,绿,蓝,因为我们的shader用到

Inspector Informations

在这里插入图片描述
在Global Keyword Summary与Local Keyword Summary都可以很方便看到关键字的的开关情况

  • Ambient Toggle 是控制Shader里头使用是否使用AMBIENT_ON 变体代码。
  • Ambient Color Type 对应就是环境光的颜色
  • Ambient Apply Only R 是否对环境观值应用R通道,On:是,那么环境光R通道将影响整体亮度;Off:否,那么环境光RGB通道将影响整体色调

其中 Ambient Toggle、Amibent Color Type都是全局关键字,Amibent Apply Only R是局部关键字

某个shader有多少个全局、局部关键字,都可以通过在Shader Inspector->…按钮,然后弹出的面板中可以看到

如下图:
Global Keywords:
在这里插入图片描述
Local Keywords:
在这里插入图片描述

Scene View

在这里插入图片描述
最后开启 AmbientApplyOnlyR就花屏了,然后用Ground Color的R通道就可以设置亮度了

Project

MultiCompileTest 提取码: gdck

References

Making multiple shader program variants 制作shader程序多变体

<think>我们已经在之前的回答中实现了通过脚本控制URP管线设置来启用高精度深度纹理(SampleHighPrecisionDepth)。现在,用户特别询问了如何使用`#pragmamulti_compile`来实现最高画质深度设置的着色器代码部分。根据用户提供的引用内容,特别是引用[1][3][4][5],我们知道`#pragmamulti_compile`用于生成多个着色器变体,每个变体对应不同的关键字组合。这样,在运行时可以通过启用或禁用关键字来切换不同的功能。在之前的着色器代码中,我们已经使用了`#pragmamulti_compile__HIGH_PRECISION_DEPTH`来生成两个变体:一个没有定义_HIGH_PRECISION_DEPTH(即普通精度),另一个定义了_HIGH_PRECISION_DEPTH(即高精度深度)。这样,在脚本中通过`Shader.EnableKeyword("_HIGH_PRECISION_DEPTH")`来启用高精度深度采样路径。下面,我将提供一个更完整的示例,展示如何在Unity着色器中使用`#pragmamulti_compile`来支持高精度深度纹理,并给出如何从脚本中控制该关键字的代码。###着色器代码示例(重点展示multi_compile的使用)```hlslShader"Custom/HighPrecisionDepthSampler"{Properties{_MainTex("Texture",2D)="white"{}_DepthSensitivity("DepthSensitivity",Range(0.001,0.1))=0.01}SubShader{Tags{"RenderType"="Opaque""RenderPipeline"="UniversalPipeline"}Pass{HLSLPROGRAM#pragmavertexvert#pragmafragmentfrag//关键点:使用multi_compile生成两个变体:一个带_HIGH_PRECISION_DEPTH,一个不带#pragmamulti_compile__HIGH_PRECISION_DEPTH#include"Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"#include"Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"structAttributes{float4positionOS:POSITION;float2uv:TEXCOORD0;};structVaryings{float2uv:TEXCOORD0;float4positionCS:SV_POSITION;};TEXTURE2D(_MainTex);SAMPLER(sampler_MainTex);float_DepthSensitivity;Varyingsvert(Attributesinput){Varyingsoutput;output.positionCS=TransformObjectToHClip(input.positionOS.xyz);output.uv=input.uv;returnoutput;}//根据是否启用_HIGH_PRECISION_DEPTH关键字,选择不同的深度采样方式floatSampleDepth(float2uv){#ifdefined(_HIGH_PRECISION_DEPTH)//高精度路径:使用更高精度的深度值计算//注意:这里假设已经通过脚本设置了高精度的深度纹理格式(如RFloat)floatdepth=SampleSceneDepth(uv);//可以在这里进行更高精度的计算,例如使用更大的范围或者更复杂的解码returnLinear01Depth(depth,_ZBufferParams)*_DepthSensitivity;#else//普通精度路径floatdepth=SampleSceneDepth(uv);returnLinear01Depth(depth,_ZBufferParams);#endif}half4frag(Varyingsinput):SV_Target{half4color=SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex,input.uv);floatdepth=SampleDepth(input.uv);//这里仅作为示例,将深度值叠加到颜色上returncolor*depth;}ENDHLSL}}}```###脚本控制关键字的代码在之前的脚本中,我们已经包含了启用关键字的代码:```csharp//启用高精度深度关键字Shader.EnableKeyword("_HIGH_PRECISION_DEPTH");//禁用则使用//Shader.DisableKeyword("_HIGH_PRECISION_DEPTH");```###注意事项1.**变体数量控制**:使用`#pragmamulti_compile`会生成多个着色器变体。如果变体过多,会增加包体大小和内存占用。因此,应仅对必要的功能使用。2.**关键字命名**:确保关键字(如`_HIGH_PRECISION_DEPTH`)在整个项目中唯一,避免与其他着色器冲突。3.**硬件支持**:在启用高精度深度之前,应检查硬件是否支持高精度深度纹理格式(如`RenderTextureFormat.RFloat`),如不支持,则不应启用该关键字。###性能优化建议如果同时存在多个影响画质的关键字,可以考虑使用`shader_feature`代替`multi_compile`。`shader_feature`在最终打包时只会包含实际使用的变体,而`multi_compile`会包含所有变体。但是,如果关键字需要在运行时动态切换,则必须使用`multi_compile`。例如,如果高精度深度设置只在编辑器中调整,并且不会在运行时改变,则可以使用:```hlsl#pragmashader_feature__HIGH_PRECISION_DEPTH```###相关设置整合在URP管线的配置脚本中,我们不仅要设置着色器关键字,还需要设置管线资源以支持高精度深度纹理。这包括:-启用深度纹理:`urpAsset.supportsCameraDepthTexture=true;`-设置深度纹理的格式(在URP中,深度纹理的格式通常由管线内部管理,但我们可以通过设置`depthPrimingMode`为`Disabled`来避免深度预通道,从而可能获得更高精度的深度纹理)###总结通过结合`#pragmamulti_compile`在着色器中定义高精度深度路径,并在脚本中根据画质设置启用或禁用对应的关键字,我们可以实现最高画质的深度设置。同时,注意硬件兼容性和性能优化。§§相关问题§§1.如何减少由multi_compile导致的着色器变体数量过多的问题?2.在URP管线中,如何设置深度纹理的格式为32位浮点数?3.如何通过图形设置界面(GraphicsSettings)来管理多个画质等级的高精度深度开关?</think>以下是根据您的需求,在 Unity 中通过 `#pragma multi_compile` 实现最高画质深度设置的完整代码示例: ### 着色器代码(使用 multi_compile 控制高精度深度) ```hlsl Shader "Custom/HighPrecisionDepth" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalRenderPipeline" } Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag // 核心:定义高精度深度变体 #pragma multi_compile _ _HIGH_PRECISION_DEPTH #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl" struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; }; struct Varyings { float2 uv : TEXCOORD0; float4 positionCS : SV_POSITION; }; TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); Varyings vert(Attributes input) { Varyings output; output.positionCS = TransformObjectToHClip(input.positionOS.xyz); output.uv = input.uv; return output; } // 高精度深度采样函数 float SampleHighPrecisionDepth(float2 uv) { #if defined(_HIGH_PRECISION_DEPTH) // 高精度路径:使用32位浮点深度 return SampleSceneDepth(uv); #else // 默认路径:使用16位深度 return Linear01Depth(SampleSceneDepth(uv), _ZBufferParams); #endif } half4 frag(Varyings input) : SV_Target { float depth = SampleHighPrecisionDepth(input.uv); return half4(depth, depth, depth, 1.0); } ENDHLSL } } } ``` ### C# 控制脚本(启用高精度深度) ```csharp using UnityEngine; using UnityEngine.Rendering; public class DepthQualityController : MonoBehaviour { [Header("深度精度设置")] public bool enableHighPrecisionDepth = true; void Start() { ConfigureDepthQuality(); } void ConfigureDepthQuality() { // 设置全局关键字 if (enableHighPrecisionDepth) { // 启用高精度深度变体 Shader.EnableKeyword("_HIGH_PRECISION_DEPTH"); // 配置渲染管线使用32位深度纹理 ConfigureRenderPipelineForHighPrecision(); } else { Shader.DisableKeyword("_HIGH_PRECISION_DEPTH"); } } void ConfigureRenderPipelineForHighPrecision() { // URP 配置 var urpAsset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset; if (urpAsset != null) { urpAsset.supportsCameraDepthTexture = true; // 确保使用高精度深度格式 #if UNITY_2022_1_OR_NEWER urpAsset.depthPrimingMode = DepthPrimingMode.Disabled; urpAsset.depthTextureMode = DepthTextureMode.Depth; #endif } // 内置管线配置 #if !UNITY_2022_1_OR_NEWER Camera.main.depthTextureMode |= DepthTextureMode.Depth; #endif } // 运行时切换质量 public void SetHighPrecisionDepth(bool enabled) { enableHighPrecisionDepth = enabled; ConfigureDepthQuality(); Debug.Log($"高精度深度模式: {enabled}"); } } ``` ### 关键点说明 1. **多编译变体控制**: ```hlsl #pragma multi_compile _ _HIGH_PRECISION_DEPTH ``` 这行代码会生成两个着色器变体:一个包含 `_HIGH_PRECISION_DEPTH` 关键字的版本(高精度),和一个不包含的版本(默认精度)[^3][^4]。 2. **条件深度采样**: ```hlsl #if defined(_HIGH_PRECISION_DEPTH) return SampleSceneDepth(uv); // 32位精度 #else return Linear01Depth(...); // 16位精度 #endif ``` 3. **运行时控制**: ```csharp // 启用高精度 Shader.EnableKeyword("_HIGH_PRECISION_DEPTH"); // 禁用高精度 Shader.DisableKeyword("_HIGH_PRECISION_DEPTH"); ``` 4. **变体管理建议**: ```hlsl // 替代方案:使用shader_feature减少变体数量 #pragma shader_feature _HIGH_PRECISION_DEPTH ``` 使用 `shader_feature` 而非 `multi_compile` 可以确保最终构建只包含实际使用的变体[^3]。 ### 性能优化建议 1. **变体数量控制**: ```csharp // 在质量设置中定义变体组合 public enum DepthQuality { Low, // _LOW_PRECISION Medium, // _MEDIUM_PRECISION High // _HIGH_PRECISION_DEPTH } ``` 2. **按平台自动配置**: ```csharp void AutoConfigureDepthQuality() { if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D12 || SystemInfo.graphicsDeviceType == GraphicsDeviceType.Metal) { Shader.EnableKeyword("_HIGH_PRECISION_DEPTH"); } else { Shader.DisableKeyword("_HIGH_PRECISION_DEPTH"); } } ``` ### 使用示例 ```csharp // 在游戏设置菜单中调用 public void OnQualitySettingChanged(int qualityLevel) { var depthController = FindObjectOfType<DepthQualityController>(); depthController.SetHighPrecisionDepth(qualityLevel == 2); } ``` > **注意**:高精度深度会显著增加显存使用和带宽需求,特别是在移动设备上。建议在高端设备上启用,在低端设备上回退到默认精度[^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值