合并Shader

<转>我也忘了转自哪里,抱歉,感谢原作者

《合并Shader》系列旨在介绍一些在保证功能不打折的情况下精简Shader数量的方法,其遵循的原理就是把相似功能的Shader文件合并在一个文件里。在掌握了这些技能后,研发团队能在极致情况下做到把所有的Shader文件合并成一个,如Unity 5.x的Standard着色器。本系列会从简到繁的方式来依次遍历林林总总的合并方法,希望大家有所收获。


在数学中我们学习过:把多项式中的同类项合并成一项叫做合并同类项。同理,提取Shader的相似部分,把多个Shader合并成一个就叫做Shader的合并,也叫合并Shader,偶尔也会引用数学的名词来称呼他为Shader的合并同类项。

Shader的合并方式有很多,根据不同的合并技能和方法可以划分为不同的派系。今天优先介绍一种不太常见、但又很实用的派系,往下看。

在Shader的合并方法中,MaterialPropertyDrawer(属性定义)可以说是自成一派,但又与其他派系有着千丝万缕的关系,今天我们就先拿它来开刀。

在此之前我想补充一点:对于Shader的合并,首先让人想到的应该是宏定义,相信宏定义也是大家应用最广、最先接触的。(毕竟由于GPU的特殊性,Shader里常常通篇都充满了各种宏定义。)当然,该系列会对宏定义有所介绍,它可是Shader合并里功高盖世的重要角色,很多地方都会有它的身影。但对它的介绍不在这一篇,也许会是下一篇。因为我认为在合并Shader的众多方法中,最简单的不是它,而是使用Unity已经预先定制好的几种MaterialPropertyDrawer的方式。只用修改两行代码,就可以搞定一类Shader的合并,该方法主要用来合并那些只是渲染状态不一样的Shader。


◆◆◆
初次简单使用

一个完整的Shader,它的渲染状态变量有很多种,由于不是每一种状态的改变都能很明显地看到结果。那么作为初次使用,我们就优先选择一种最明显的、最易懂的状态作为测试用例—ZTest,即深度比较。对ZTest不太了解的朋友,可以看看官方的学习文档或者查看一下相关技术书籍,我就不在这里具体介绍这个状态了。

1.1 首先我们选用的是Unity官方提供的一个最常用Shader

Normal-Diffuse.shader(Legacy Shaders/Diffuse)

1.2 在属性列表(Properties)中添加一行

[Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 2

1.3 在SubShader中添加一行

ZTest [_ZTest]

1.4 完整的Shader

Shader "ShaderCombine/01.ShaderCombineSimpleZTest"{    Properties {        _Color ("Main Color", Color) = (1,1,1,1)        _MainTex ("Base (RGB)", 2D) = "white" {}        [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 2    }    SubShader {        Tags { "RenderType"="Opaque" }        LOD 200        ZTest [_ZTest]        CGPROGRAM        #pragma surface surf Lambert        sampler2D _MainTex;        fixed4 _Color;        struct Input {            float2 uv_MainTex;        };        void surf (Input IN, inout SurfaceOutput o) {            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;            o.Albedo = c.rgb;            o.Alpha = c.a;        }        ENDCG    }    Fallback "Legacy Shaders/VertexLit"}

只用添加这两行代码,我们就可以在Inspector面板中控制使用该Shader物体的深度测试方法。

1.5 直观展示
在Unity中的测试示例是这样的:

当然大家也可以通过这个示例试验下每一种深度测试的方法是否与你心中所想、或之前所学的有所冲突。


◆◆◆
再次深入使用

在上面的例子中,我们只使用了深度测试。但单单一个深度测试肯定满足不了我的需求,我们还需要更多、更多的状态,比如背面剔除、混合模式等等。

在这一节中,我列举出了一些常用的状态控制量,对于一些不常用的模板等,就不在这里列举。大家可以依葫芦画瓢,因为大部分从理论上来说都是可行的。

2.1 一个大而全的简单示例Shader如下:

Shader "ShaderCombine/02.ShaderCombineCommonState"{    Properties {        _Color ("Main Color", Color) = (1,1,1,1)        _MainTex ("Base (RGB)", 2D) = "white" {}        [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 1        [Enum(Off,0,On,1)] _ZWrite ("ZWrite", Float) = 1        [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 1        [Enum(UnityEngine.Rendering.BlendMode)] _SourceBlend ("Source Blend Mode", Float) = 2                [Enum(UnityEngine.Rendering.BlendMode)] _DestBlend ("Dest Blend Mode", Float) = 2    }    SubShader {        Tags { "RenderType"="Opaque" }        LOD 200        ZTest [_ZTest]        Cull [_Cull]        ZWrite [_ZWrite]        ZTest [_ZTest]        Blend [_SourceBlend] [_DestBlend]        CGPROGRAM        #pragma surface surf Lambert        sampler2D _MainTex;        fixed4 _Color;        struct Input {            float2 uv_MainTex;        };        void surf (Input IN, inout SurfaceOutput o) {            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;            o.Albedo = c.rgb;            o.Alpha = c.a;        }        ENDCG    }    Fallback "Legacy Shaders/VertexLit"}

2.2 直观展示
应用效果:


◆◆◆
有限自定义

在上面的示例中,我们都是使用Unity预先定义好的一些枚举类型,比如UnityEngine.Rendering.CompareFunction、UnityEngine.Rendering.BlendMode等。这些定义好的类型把每个状态可能的选项都一一列举了,但有时候我们并不需要这么多选项,或者说我们并不希望给美术列举出所有的可选项,毕竟很多选项我们可能做完整个项目或者几个项目都不会使用到,而选项过多也会带来很的麻烦。或者换一个说法,我希望我们的功能使用起来简单易懂、不易出错并且可控,那就需要我们开发做更多的工作,去掉那些"无用"的选项。其实说到底无非就是:我们能否自定义每个状态的选项呢?答案当然是可以的。

在给出自定义方式前,我们先来熟悉一下Unity给我们提供的这几个枚举类型。

3.1 剔除模式

UnityEngine.Rendering.CullMode:public enum CullMode{           Off   = 0,    //Disable culling.           Front = 1,    //Cull front-facing geometry.           Back  = 2     //Cull back-facing geometry.}

3.2 比较方式
该比较方式通用与深度比较和模板比较

UnityEngine.Rendering.CompareFunction//Depth or stencil comparison function.public enum CompareFunction{    Disabled     = 0,   //Depth or stencil test is disabled.    Never        = 1,   //Never pass depth or stencil test.    Less         = 2,   //Pass depth or stencil test when new value is less than old one.    Equal        = 3,   //Pass depth or stencil test when values are equal.    LessEqual    = 4,   //Pass depth or stencil test when new value is less or equal than old one.    Greater      = 5,   //Pass depth or stencil test when new value is greater than old one.    NotEqual     = 6,   //Pass depth or stencil test when values are different.    GreaterEqual = 7,   //Pass depth or stencil test when new value is greater or equal than old one.    Always       = 8    //Always pass depth or stencil test.}

3.3 混合模式

UnityEngine.Rendering.BlendMode//Blend mode for controlling the blending.public enum BlendMode{    Zero             = 0,   //Blend factor is (0, 0, 0, 0).    One              = 1,   //Blend factor is (1, 1, 1, 1).    DstColor         = 2,   //Blend factor is (Rd, Gd, Bd, Ad).    SrcColor         = 3,   //Blend factor is (Rs, Gs, Bs, As).    OneMinusDstColor = 4,   //Blend factor is (1 - Rd, 1 - Gd, 1 - Bd, 1 - Ad).    SrcAlpha         = 5,   //Blend factor is (As, As, As, As).    OneMinusSrcColor = 6,   //Blend factor is (1 - Rs, 1 - Gs, 1 - Bs, 1 - As).    DstAlpha         = 7,   //Blend factor is (Ad, Ad, Ad, Ad).    OneMinusDstAlpha = 8,   //Blend factor is (1 - Ad, 1 - Ad, 1 - Ad, 1 - Ad).    SrcAlphaSaturate = 9,   //Blend factor is (f, f, f, 1); where f = min(As, 1 - Ad).    OneMinusSrcAlpha = 10   //Blend factor is (1 - As, 1 - As, 1 - As, 1 - As).}

3.4 有限的自定义
在上面三个小小节中,我们了解了Unity自身提供的状态选项,而且每一个状态选项后都强制赋上了相应的数值,这是有原因的。因为我们写好的Shader不管怎样,都要首先经过Unity的编译等处理转换为目标平台的着色器语言。而Unity自己的Shader编译器,在没有源码的情况下我们是无法修改的,也就是说我们不能随意更改这些状态的数值。其实我们修改的这些状态值都是给Unity的Shader编译器看的,而编译器对状态的数值理解是固化好的。因此,虽然我们可以自定义这些状态选项,但编译器也不会任由我们随意定义,这就好比戴着镣铐跳舞,虽然有限制,但我们依然可以跳出优美的舞蹈。

自定义非常的简单,我们可以减少选项的数量,但是不能改变每一项的值,这就要求我们强行给每一个值赋上对应的值,依然还是用深度测试实验,如下所示:

这是之前的:

[Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 2

这是自定后的:

[Enum(Less,2,Greater,5)] _ZTest ("ZTest", Float) = 2

选项与数值全部使用逗号分隔,该示例中我只给出了两个选项,小于和大于,便于直观查看。

完整Shader如下:

Shader "ShaderCombine/03.ShaderCombineCustomState"{    Properties {        _Color ("Main Color", Color) = (1,1,1,1)        _MainTex ("Base (RGB)", 2D) = "white" {}        [Enum(Less,2,Greater,5)] _ZTest ("ZTest", Float) = 2    }    SubShader {        Tags { "RenderType"="Opaque"}        LOD 200        ZTest [_ZTest]        CGPROGRAM        #pragma surface surf Lambert        sampler2D _MainTex;        fixed4 _Color;        struct Input {            float2 uv_MainTex;        };        void surf (Input IN, inout SurfaceOutput o) {            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;            o.Albedo = c.rgb;            o.Alpha = c.a;        }        ENDCG    }    Fallback "Legacy Shaders/VertexLit"}

3.5 直观展示
在Unity中的样子是这样的:

细心的读者可能已经发现我们在第二章节中的完整示例中就有使用自定义,就是里面的那个写深度_ZWrite选项,因为没有在Unity里找到相应的枚举值,就直接使用了自定义,反正只要保证数值正确就可以任意发挥使用。更多的使用和应用场景就等你们去发现了,我这只是抛砖引玉。

PS:其实最初是想花一整章篇幅来讲解MaterialPropertyDrawer的各种使用,但其内部的扩展空间还比较广泛。写下来篇幅太长,而太长的篇幅,阅读起来也比较麻烦,所以还是拆成几章篇幅来慢慢絮叨吧,同时也遵从一次只讲一个问题。

以上便是MaterialPropertyDrawer的应用场景之一,对于MaterialPropertyDrawer的应用,在后续的篇章中也还会陆续出现。笔者计划把MaterialPropertyDrawer应用当成《合并Shader》系列中的一个分支,当然《合并Shader》系列不会仅且只有这一个分支的,O(∩_∩)O哈哈~,欢迎大家持续关注!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容