Unity Shader - 使用multi_compile对Shader渲染质量控制

141 篇文章 35 订阅

使用Shader的[KeywordEnum]编辑器来控制

对应项目中场景:TestMultiCompileByShaderKeywordEnum.unity

Code

大多数的一些说明,与技巧,都写在注释

Shader code

// jave.lin 2019.06.25
Shader "Test/TestMultiCompile"{
	Properties{
        // 如果在Material Inspector中设置KeywordEnum提供给用户使用的话,建议不用multi_compile定义,而用shader_feature
        // 因为在Material Inspector编辑的指令一般是确定发布后就不会变得
        // 而什么时候用multi_compile,什么时候用shader_feature呢,遵守以下几点就可以了:
        // 用shader_feature:
        // - 编辑器中使用KeywordEnum来编辑的(这是我们用的是multi_compile,测试用而已)
        // - 或是发布后确定不变得
        // 用multi_compile:
        // - 发布后,运行时,可能需要变化的,如:游戏的画面设置
        // - 通常不会在Properties定义[KeywordEnum]的方式来编辑的,因为会你项目编辑器下开发过程中,脚本中控制的关键字开关会有冲突的
        //      如:
        //          你在Unity Editor Player下运行项目,打开了游戏画面设置,会对该材质的一个关键字控制启用或禁用,然后又选中了Project视图下该材质
        //          该材质的Material Inspector显示的编辑器界面实际上有时实时的对该材质使用了[KeywordEnum]的关键字的控制启用或禁用,那么就会与你上面的脚本有冲突
		[KeywordEnum(Low,Medium,High,Best)] _Q("Quality mode", Float) = 0               // 质量模式
		[KeywordEnum(ReflectDotView,HalfAngle)] _SPECULAR("Specular Model", Float) = 0  // 高光模型
        _MainTex ("Main Tex", 2D) = "white" {}                                          // 主纹理
		_Color ("Main Color", Color) = (1,1,1,1)                                        // 主色调
		_HalfLambertIntensity ("Half-Lambert Itensity", Range(0, 1)) = 0.5              // Diffuse的半Lambert强度
        _RimIntensity ("Rim Intensity", Range(0, 2)) = 0.5                              // 使用法线检测边缘光的强度
        _SpecularIntensity ("Specular Intensity", Range(0, 100)) = 1                    // 高光模型强度系数1
        _SpecularStrengthen ("Specular Strengthen", Range(0, 1)) = 1                    // 高光模型强度系数2
    }
	SubShader{
		Pass{
			Tags { "Queue" = "Opaque" "LightMode" = "ForwardBase" } // shadow setup:"LightMode"="ForwardBase"
			CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                // ====global keyword start====
				#pragma multi_compile_fwdbase 	// shadow setup:fwdbase // 这里会启用11个内置关键字
                // multi_compile_fwdbase 包含的11个内置关键字分别是:
                // DIRECTIONAL,SHADOWS_DEPTH,SHADOWS_SCREEN,
                // SHADOWS_CUBE,LIGHTMAP_ON,DIRLIGHTMAP_COMBINED,
                // DYNAMICLIGHTMAP_ON,LIGHTMAP_SHADOW_MIXING,
                // SHADOWS_SHADOWMASK,LIGHTPROBE_SH,VERTEXLIGHT_ON
                #pragma multi_compile __ AMBIENT_ON // 2个选项,定义环境光应用开关,_ 表示未选
                #pragma multi_compile __ LIGHTMODEL_AMBIENT SKY_COLOR EQUATOR_COLOR GROUND_COLOR // 5个选项,定义颜色选用的关键字
                #pragma multi_compile __ _Q_LOW _Q_MEDIUM _Q_HIGH _Q_BEST // 5个选项,定义质量选用关键字
                #pragma shader_feature __ _SPECULAR_REFLECTDOTVIEW _SPECULAR_HALFANGLE // 3个选项,定义高光模型,这里我们用shader_feaature定义
                #pragma shader_feature __ TEST TEST2 // 这两个测试的shader_feature全局关键字,只要不启用就不会有额外的变体生成下面有使用的测试代码
                // ====global keyword end====
                // ====local keyword start====
                #pragma multi_compile_local __ AMBIENT_ONLY_R // 2个选项,环境光颜色只有R通道的开关
                // 所以我们这个shader变体数量为:11*2*5*5*2*3=3300(3.3k),但是在Shader Inspector中看到只有2.4k个,
                // 估计是否有些全局关键字是使用shader_feature的编译指令来定义的给编译优化检测未使用就剔除了
                // ====local keyword end====
                #include "UnityCG.cginc"
                #include "Lighting.cginc" 		// shadow setup:#include "Lighting.cginc"
                #if _Q_BEST
                #include "AutoLight.cginc" 		// shadow setup:#include "AutoLight.cginc"
                #endif
                struct app { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; };
                struct v2f { float4 pos : POSITION; float3 normal : NORMAL; float3 worldPos : TEXCOORD0;
                #if _Q_BEST
                    LIGHTING_COORDS(1,2)		// shadow setup:LIGHTING_COORDS(n,n+1)
                    float2 uv : TEXCOORD3;
                #else
                    float2 uv : TEXCOORD1;
                #endif
                };
                sampler2D _MainTex;
                float4 _MainTex_ST;
				fixed4 _Color;
                float _RimIntensity;
                float _SpecularIntensity;
                float _SpecularStrengthen;
				float _HalfLambertIntensity;
				bool _HalfLambert;
                v2f vert(app v) {
                    v2f o;
                    o.pos = UnityObjectToClipPos(v.vertex);
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                    o.normal = normalize(UnityObjectToWorldNormal(v.normal));
                    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                    #if _Q_BEST
                    TRANSFER_VERTEX_TO_FRAGMENT(o);	// shadow setup:TRANSFER_VERTEX_TO_FRAGMENT(o)
                    #endif
                    return o;
                }
                fixed4 frag(v2f v) : SV_Target{
                    _HalfLambertIntensity = 1 - _HalfLambertIntensity;
                    #if _Q_LOW
						fixed4 col = _Color * tex2D(_MainTex, v.uv);
                        #if TEST
                        col = fixed4(0,0,0,0);
                        #endif
                    #elif _Q_MEDIUM
						fixed4 col = _Color * tex2D(_MainTex, v.uv);
                        col.rgb *= dot(normalize(_WorldSpaceLightPos0.xyz), v.normal) * _HalfLambertIntensity * 0.5 + (1 - _HalfLambertIntensity) * 0.5; //halfLambert
                    #elif _Q_HIGH
						fixed4 col = _Color * tex2D(_MainTex, v.uv);
                        col.rgb *= dot(normalize(_WorldSpaceLightPos0.xyz), v.normal) * _HalfLambertIntensity * 0.5 + (1 - _HalfLambertIntensity) * 0.5; //halfLambert
                        fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - v.worldPos);
                    #elif _Q_BEST
                        half3 lightDir = normalize(_WorldSpaceLightPos0.xyz); // 这里我们只测试方向光,该变量如果是方向光的话,它的前3三分量就是方向
						fixed4 col = _Color * tex2D(_MainTex, v.uv);
                        col.rgb *= dot(lightDir, v.normal) * _HalfLambertIntensity * 0.5 + (1 - _HalfLambertIntensity) * 0.5; //halfLambert
						// 添加高光
						fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - v.worldPos);
                        #if _SPECULAR_HALFANGLE
                            fixed3 LPlusV = lightDir + viewDir;
                            float3 reflectDir = normalize(LPlusV / dot(LPlusV, LPlusV));//normalize(LPlusV/length(LPlusV));
                        #else
						    float3 reflectDir = reflect(-lightDir, v.normal);
                        #endif
                        // 计算光照和阴影之后的阴影衰减因子
                        UNITY_LIGHT_ATTENUATION(atten, v, v.worldPos); // shadow setup:UNITY_LIGHT_ATTENUATION(atten,v,v.worldPos)
                        atten *= saturate(dot(v.normal, lightDir));
                        #if _SPECULAR_HALFANGLE
                            col.rgb += fixed3(_LightColor0.rgb * pow(dot(reflectDir, v.normal) * 0.5 + 0.5, 100 - _SpecularIntensity)) * _SpecularStrengthen * atten; // specular
                        #else
                            col.rgb += fixed3(_LightColor0.rgb * pow(dot(reflectDir, viewDir) * 0.5 + 0.5, 100 - _SpecularIntensity)) * _SpecularStrengthen * atten; // specular
                        #endif
                    #else
                        fixed4 col = _Color * tex2D(_MainTex, v.uv);
                    #endif

                    // rim light
					#if _Q_HIGH || _Q_BEST
						col.rgb += _LightColor0.rgb * (1 - max(0, dot(viewDir, v.normal))) * _RimIntensity;
					#endif

                    #if AMBIENT_ON // 判断开关而使用环境色调
                        fixed3 ambientColor;
                        #if AMBIENT_ONLY_R // 仅使用R通道的
                            #if LIGHTMODEL_AMBIENT // 光照综合处理后的系数
                            ambientColor = UNITY_LIGHTMODEL_AMBIENT.rrr;
                            #elif SKY_COLOR
                            ambientColor = unity_AmbientSky.rrr;
                            #elif EQUATOR_COLOR
                            ambientColor = unity_AmbientEquator.rrr;
                            #elif GROUND_COLOR
                            ambientColor = unity_AmbientGround.rrr;
                            #else // nops
                            #endif
                        #else
                            #if LIGHTMODEL_AMBIENT // 光照综合处理后的系数
                            ambientColor = UNITY_LIGHTMODEL_AMBIENT.rgb;
                            #elif SKY_COLOR
                            ambientColor = unity_AmbientSky.rgb;
                            #elif EQUATOR_COLOR
                            ambientColor = unity_AmbientEquator.rgb;
                            #elif GROUND_COLOR
                            ambientColor = unity_AmbientGround.rgb;
                            #else // nops
                            #endif
                        #endif
                        col.rgb += ambientColor;
                        #if _Q_BEST // 应用阴影因子
                            //col.rgb = col.rgb * atten + col.rgb * 0.5; // 后面的 * 0.5,这个值,可以去ambient的灰度值来替代,而不是写死的值,效果会好一些
                            //Gray = R*0.299 + G*0.587 + B*0.114
                            col.rgb = col.rgb * atten + col.rgb * dot(ambientColor.rgb, fixed3(0.299, 0.587, 0.114));
                        #endif
                    #else
                        #if _Q_BEST // 应用阴影因子
                            // 没有应用环境光我们就写死个0.5系数吧
                            col.rgb = col.rgb * atten + col.rgb * 0.5; // ambient的灰度值来替代,而不是写死的值,效果会好一些
                        #endif
                    #endif

					return col;
				}
            ENDCG
        }

        Pass // 投影用的pass
        {
            Tags {"LightMode"="ShadowCaster"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadowcaster
            #include "UnityCG.cginc"

            struct v2f { 
                V2F_SHADOW_CASTER;
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }
	}
}

使用KeywordEnum方式的运行控制效果

在这里插入图片描述

使用自定义脚本控制

对应项目中场景:TestMultiCompileByScript.unity

该场景使用的shader与使用KeywordEnum场景的不同之处在于,将Shader Properties中控制质量属性注释了

// -- 注意这里质量控制我们在Properties在注释了 -- [KeywordEnum(Low,Medium,High,Best)] _Q("Quality mode", Float) = 0               // 质量模式

使用自定义脚本方式的运行控制效果

在这里插入图片描述

总结

我看还是将一些需要注意的从注释里拿出总结里说,突出以下重点

什么时候用multi_compile,什么时候用shader_feature呢?

遵守以下几点就可以了:

用shader_feature:

  • 编辑器中使用KeywordEnum来编辑的(这是我们用的是multi_compile,测试用而已)
  • 或是发布后确定不变得 (这里需要这么理解:发布后,变体是安收集方式来确定不便了,比如,你收集了某个变体的开,那么就只有开的状态的变体)
  • 反正就是 shader 变体的开关由 material 材质中,使用到的变体的开关来决定的

用multi_compile:

  • 发布后,运行时,可能需要变化的,如:游戏的画面设置
  • 通常不会在Properties定义[KeywordEnum]的方式来编辑的,因为项目编辑器下开发过程中,脚本中控制的关键字开关会有冲突的
    如:
    你在Unity Editor Player下运行项目,打开了游戏画面设置,会对该材质的一个关键字控制启用或禁用,然后又选中了Project视图下该材质
    该材质的Material Inspector显示的编辑器界面实际上有时实时的对该材质使用了[KeywordEnum]的关键字的控制启用或禁用,那么就会与你上面的脚本有冲突

另外注意 multi_compile 不要滥用,容易导致 变体数量爆炸

变体多了就会导致 shader 变体编译时长增加,所以会影响打包时的 shader 变体时长,导致打包时长增加,有些项目没有变体管理经验的,可能将一个 shader 的所有变体都弄上去的,甚至变体数量达到了 成千上万个,那么这个 shader 可能就要变体 30~60 分钟,也就说你打包的时间就要增加 30~60 中明显就不能忍啊

除了增加编译时间,shader 变体还会增加包体(虽然不多,甚至可以忽略)但是 运行时内存大小,显存大小 大小是无法接受的

如果要在 BRP 管线,使用了 内存的 multi_compile_xxxx 的内置变体,想要优化变体是非常难的,具体可以参考我的另一篇:Unity Shader - Built-in管线下优化变体

如果重写了渲染管线、shader 都不使用内置的变体申明开关,基本上变体优化会非常容易,写个工具就可以处理好
但是如果是没有unity引擎源码的话,官方岂能让你如此容易维护shader变体,因为没有unity引擎源码的话,在一些变体管理的黑盒,你无法修改,比如,fog, lightmap 的,都是依赖 LightingSetting中的开关,或是 烘焙,如果你可以自己实现一套fog开关(这个简单),或是烘焙系统(要自己写比较困难,特别是还要有 GPU 加速烘焙,和 UV2 生成),都是很大的挑战

还有另一个种方式来控制shader质量

  • 那就是将shader文件保存不一样的质量代码
  • 然后用脚本将所有运行时用到的Material的shader都换掉
  • 或是换掉材质也可以,看你喜好来处理
  • ShaderLOD

Project

ControlShaderQualityByMultiCompile 提取码: mauk

References

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值