是不是脚本中设置各种case,每个case加载对应的shader。这种方法可以用,但在实际开发中,根本不可取。因为可能会有大量Shader被编译,造成内存的大量占用。那么该怎么做呢?
1.使用multi_complie或shader_feature来定义宏,根据不同的宏指令编译出多套Shader,Unity内建shader大体也是这么做的。
2.有外部传入参数,在shader内部if判断,选择执行哪部分运算。
因为在shader种使用if、for很影响效率,所以第二种方法使用较少,用于case较少的时候。
接下来主要谈一下第一种方法的利与弊。
该指令可以达到上述效果,但他会无脑的进行组合编译,如果宏指令太多,会产生非常多的variant。
当你使用
#pragma multi_compile Red Green Blue
会产生三个variant,因为你定义了三个宏
当你使用
#pragma multi_compile Red Green Blue
#pragma multi_compile Pink Yellow
会产生6个variant(RedPink,RedYellow,GreenPink,GreenYellow,BluePink,BlueYellow),因为他们之间会两两组合。
当你使用
#pragma multi_compile Red Green Blue
#pragma multi_compile Pink Yellow
#pragma multi_complie Brown Purple Black
会产生323个variant。可见这个产生变体的数量规模是很大的。
如何查看一个shader产生的变体数量?
选中shader文件,点击compile and show code右边的小箭头就可以看到。
优势也很明显:
打ab包的时候,Unity会把shader中所有的multi_complie定义的变体全部打包,shader可以正常显示。但shader_feature就不那么尽人意了。
需要注意:
其中指定的第一个关键字是默认生效的。
使用multi_compile声明的都为全局关键字,一个项目种全局关键字最多只能有256个,内建shder大约已经用了60个。
注意FallBack最好不要FallBack内建shader,也会增加很多变体。
该指令的效果和用法基本都与上面的一样。同时它就是为了multi_compile打包时的爆炸编译的问题。
实现:
shader代码
Shader "Custom/NewSurfaceShader" {
Properties {
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
pass
{
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
//#pragma surface surf Standard fullforwardshadows
#include "UnityCG.cginc"
//#pragma shader_feature Red Green Blue
#pragma multi_complie_local __ Red Green Blue
//#pragma multi_compile __ Pink */
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 2.0
#pragma vertex vert
#pragma fragment frag
struct v2f
{
float4 vertex:POSITION;
fixed4 color:COLOR;
};
v2f vert(appdata_base i)
{
v2f o;
o.vertex=UnityObjectToClipPos(i.vertex);
o.color=fixed4(1,1,1,1);
return o;
}
fixed4 frag(v2f i):SV_Target
{
#if Red
return i.color=fixed4(1,0,0,1);
#elif Blue
return i.color=fixed4(0,0,1,1);
#else
return i.color=fixed4(0,1,0,1);
#endif
}
ENDCG
}
}
//FallBack "Diffuse"
}
C#代码进行外部控制:
/*
author:@
Last modified data:
funtion todo:
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shader_featureTest : MonoBehaviour {
public enum ColorMode
{
Red,
Blue,
Green
}
public ColorMode colorMode = ColorMode.Green;
Material mt;
// Use this for initialization
void Start () {
mt = GetComponent<MeshRenderer>().material;
}
// Update is called once per frame
void Update () {
print(colorMode.ToString());
switch (colorMode)
{
case ColorMode.Red:
mt.shaderKeywords = new string[1] { colorMode.ToString() };
break;
case ColorMode.Blue:
mt.shaderKeywords = new string[1] { colorMode.ToString() };
break;
case ColorMode.Green:
mt.shaderKeywords = new string[1] { colorMode.ToString() };
break;
default:
break;
}
}
}
把材质赋予随便一个物体上,运行时切换就可以达到效果。在multi_complie下使用EnableKeywords和DisableKeywords有事会失败,这里建议直接使用shaderKeywords属性来设置(希望有大神可以告诉为什么那两个方法会失败,很是疑惑
看到这么一条,还没有尝试:
在脚本这么用
Material.EnableKeyword 和 DisableKeyword
Shader.EnableKeyword 和 DisableKeyword 控制keyword起效
注意:
5.4中用Shader.EnableKeyword设置了全局使用默认的key, 用Material.EnableKeywor设置单个不使用默认值无效
用Shader.EnableKeyword设置了全局不使用默认的key, 用Material.EnableKeywor设置单个使用默认值起效
EnableKeyword 和 DisableKeyword 最好组合使用 比如一组有三个,必须写1个enable2个disable
不管有多少个关键字,总会产生一个variant,大大降低了编译出来的shader变体。
但是打ab包的时候,如果shader和所使用的材质不在一个ab包内,那么材质的shader中所有的变体都不会进入ab包中。在打包后代码中动态切换keywords,要把使用到的shader变体也打入包中才可以。
为了让变体在运行时可以更好的处理,Unity5.x之后引入了一个shader变体集合:ShaderVariantCollection,ShaderVariantCollection的使用之后会讲,今天简单给大家介绍一下这两种方式。