Unity随记(四) shader_feature 与 multi_compile的填坑过程

#pragma shader_feature 和 #pragma multi_compile相似,起作用就是在一个shader脚本中支持多个版本的分支选择,和C#里面使用的宏定义差不多,使得shader代码的编辑更加高效(不用因为一点处理分支不一样而编写多个shader,只用使用这个方式,同一个shader中就可以支持多个分支,unity会自动编译出多个对应分支处理的shader,这就是变种数的体现)。接下来分享分享自己对这个的使用心得。存在即合理,既然那么相似,道理上应该各有所长才对。

第一步,创建一个测试用的Shader,代码如下:

Shader "Test/shader_feature_test"
{
     Properties
	{
		[Toggle(_SHOW_RED_ON)] _DisplayColor ("显示红色?", float) = 0
	}
	SubShader
	{
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			#pragma shader_feature _SHOW_RED_ON
			//#pragma multi_compile __ _SHOW_RED_ON

			struct appdata
			{
				float4 vertex : POSITION;
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				//原始显示为白色
				fixed4 color = fixed4(1,1,1,1);

				//勾选上显示红色
				#if _SHOW_RED_ON
					color = fixed4(1,0,0,1);
				#endif

				return color;
			}
			ENDCG
		}
	}

	FallBack off
}

第二个shader是multi_compile版本,和上述的基本一样,唯一的改动就是这里

//#pragma shader_feature _SHOW_RED_ON
#pragma multi_compile __ _SHOW_RED_ON

 新建两个材质球分别使用上面两个shader,可以看到材质球面板上多了一个开关,控制shader处理的分支:

看shader的Inspector面板则可以看出两则的区别:

使用ShaderFeature的情况(#pragma shader_feature _SHOW_RED_ON):

使用MultiCompile的情况(#pragma multi_compile __ _SHOW_RED_ON):

 

可以看到其中的一个区别了,MultilCompile的情况下,变种数(variants)多了一个,简单说这个变种数就是Unity自动把带有关键字(比如这里的_NeedTex_ON)分支的Shader代码编译多个不同版本的shander个数.比如这里的MultilCompile的情况下就会生成两个shader文件,一个是走分支#if _SHOW_RED_ON中的代码,另一个是不走这个分支的代码。变种数越多,打包时自动编译出的shader不同版本的shader文件就越多,包体会更大(当然这里一两个的区别基本上可以忽略不计)。

再开看看测试效果:

 简单的测试场景,两张Image分别挂上连个版本的材质球,手动分别勾选两个材质球面板的开关,可以看到效果(两个版本其实是一样的这里就展示一个):

到这里其实看不出什么区别,仅仅是变种数不同,那么接下来再看看代码的控制:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ShaderFeatureAndMultiCompileTest : MonoBehaviour
{
    public Image shaderFeature;
    public Image multiCompile;

    bool _isRed = false;
    Material _matShaderFeature;
    Material _matMultiCompile;

    // Start is called before the first frame update
    void Start()
    {
        _matShaderFeature = shaderFeature.material;
        _matMultiCompile = multiCompile.material;
    }

    private void OnGUI()
    {
        if (GUI.Button(new Rect(100, 100, 100, 80), "Red"))
        {
            _matShaderFeature.EnableKeyword("_SHOW_RED_ON");
            _matMultiCompile.EnableKeyword("_SHOW_RED_ON");
        }
        if (GUI.Button(new Rect(250, 100, 100, 80), "White"))
        {
            _matShaderFeature.DisableKeyword("_SHOW_RED_ON");
            _matMultiCompile.DisableKeyword("_SHOW_RED_ON");
        }
    }
}

主要就是EnableKeyword和DisableKeyword的使用,效果如下:

 

到目前为止,可以发现这两个的区别仅仅在变种数上不一样,既然能达到同样的效果那就直接选择更加简洁轻量化的ShaderFeature呀,嗯,我之前就是这么想的,所以就偏向于用ShaderFeature,于是就悄悄的掉进一个坑里,那就是打包后发现有些地方(不是所有)使用shaderFeature的地方用代码控制不生效,关键是有些又可以,这就尴尬了,比如现在的这个测试环境直接打包出来是这个效果:

区别终于又出来了,问题其实就出在变种数上,在打包时,因为这里的shader_feature_test的变种数是1,打包过程中只编译了一个不开启开关的shader,如果用代码动态控制_SHOW_RED_ON的开启,这时候包中是找不到对应的shader的,所以就出现郁闷的不生效,但是如果在打包之前勾选上shaderFeature这个材质球面板上的这个开关

 可以看到这时候这个shader的变种数变为2了

那么这时候再次打包,代码动态控制就生效了,效果如下:

 

这就是前面说到的为什么工程中遇到的有些地方代码控制生效有些地方而不生效的原因,其实不是什么玄学问题,还是得相信科学。

个人使用心得总结:

如果完全不需要用代码动态控制开关的开起与关闭,仅仅是为了美术同学对材质球面板的编辑与使用,那么使用shader_feature是很合适的分支选择,但是如果有代码控制的需求,为了稳妥起见还是选用multi_compile好,不过还是要控制好变种数,毕竟太多的宏定义分支使用,会使变种数成倍的增长。以上仅仅是个人的一点理解与测试效果,如果有描述不当或错误的地方欢迎提出更正。

发布了23 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Vitens/article/details/105315474