Unity Shader编程之十四 边缘发光Shader(Rim Shader)的两种实现形态

发表于2016-07-22
评论0 5.1k浏览

       本系列文章由@浅墨_毛星云 出品,转载请注明出处。  
文章链接: http://blog.csdn.net/poem_qianmo/article/details/51764028
作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442
本文工程使用的Unity3D版本: 5.2.1 

         这篇文章主要讲解了如何在Unity3D中分别使用Surface Shader和Vertex & Fragment Shader来编写边缘发光Shader。

 

一、最终实现的效果

         边缘发光Shader比较直观的一个运用便是模拟宇宙中的星球效果。将本文实现的边缘发光Shader先赋一个Material,此Material再赋给一个球体,加上Galaxy Skybox, 实现的效果如下图:


 


当然,边缘发光Shader也可以实现例如暗黑三中精英怪的高亮效果,将一个怪物模型本身的Shader用边缘发光Shader替代,实现效果如下图:


 

以下是本文实现的Shader在编辑器中的一些效果图。


        

   


二、Shader实现思路分析

        思路方面,其实理解起来非常简单,就是在正常的漫反射Shader的基础之上,在最终的漫反射颜色值出来之后,再准备一个自发光颜色值,附加上去,即得到了最终的带自发光的颜色值。

按公式来表达,也就是这样:

最终颜色 = (漫反射系数 x 纹理颜色 x RGB颜色)+自发光颜色

按英文公式来表达,也就是这样:

FinalColor=(Diffuse x Texture x RGBColor)+Emissive

 

三、Surface Shader版边缘发光Shader实现

      如果读过这个系列第一篇文章的朋友,应该还记得这个系列的第一篇文章(传送门:http://blog.csdn.net/poem_qianmo/article/details/40723789),里面的TheFirstShader,它就是一个标准的使用Unity Surface Shader实现的边缘发光Shader。

      利用Unity自身的Shader封装,Surface Shader,也就是Shaderlab,算是新手或者想快速上手的童鞋学习Unity中Shader书写的一个非常好的切入点。

      这边贴出经过加强的第一期TheFirstShader详细注释后的源代码。可以发现里面关于主要框架的注释都是中英双语的,因为发现不少国外友人也关注了我在Github上的Shader repo(https://github.com/QianMo/Awesome-Unity-Shader),为了方便他们以及后面更多的国外友人,以后如果周末写博客的时间充裕,就干脆写中英双语的注释得了。

      OK,详细注释后的Surface Shader版可发光Shader源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
Shader "Learning Unity Shader/Lecture 14/Surface Rim Shader" 
    //-----------------------------------【属性 || Properties】------------------------------------------   
    Properties 
    
        //主颜色 || Main Color 
        _MainColor("【主颜色】Main Color", Color) = (0.5,0.5,0.5,1) 
        //漫反射纹理 || Diffuse Texture 
        _MainTex("【纹理】Texture", 2D) = "white" {} 
        //凹凸纹理 || Bump Texture 
        _BumpMap("【凹凸纹理】Bumpmap", 2D) = "bump" {} 
        //边缘发光颜色 || Rim Color 
        _RimColor("【边缘发光颜色】Rim Color", Color) = (0.17,0.36,0.81,0.0) 
        //边缘发光强度 ||Rim Power 
        _RimPower("【边缘颜色强度】Rim Power", Range(0.6,36.0)) = 8.0 
        //边缘发光强度系数 || Rim Intensity Factor 
        _RimIntensity("【边缘颜色强度系数】Rim Intensity", Range(0.0,100.0)) = 1.0 
    
   
    //----------------------------------【子着色器 || SubShader】---------------------------------------   
    SubShader 
    
        //渲染类型为Opaque,不透明 || RenderType Opaque 
        Tags 
        
            "RenderType" = "Opaque"  
        
   
        //-------------------------开启CG着色器编程语言段 || Begin CG Programming Part----------------------  
        CGPROGRAM 
   
            //【1】声明使用兰伯特光照模式 ||Using the Lambert light mode 
            #pragma surface surf Lambert   
   
            //【2】定义输入结构 ||  Input Struct 
            struct Input 
            
                //纹理贴图 || Texture 
                float2 uv_MainTex; 
                //法线贴图 || Bump Texture 
                float2 uv_BumpMap; 
                //观察方向 || Observation direction 
                float3 viewDir;   
            }; 
   
            //【3】变量声明 || Variable Declaration 
            //边缘颜色 
            float4 _MainColor; 
            //主纹理 
            sampler2D _MainTex;   
            //凹凸纹理   
            sampler2D _BumpMap; 
            //边缘颜色 
            float4 _RimColor; 
            //边缘颜色强度 
            float _RimPower; 
            //边缘颜色强度 
            float _RimIntensity; 
   
            //【4】表面着色函数的编写 || Writing the surface shader function 
            void surf(Input IN, inout SurfaceOutput o) 
            
                //表面反射颜色为纹理颜色   
                o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb*_MainColor.rgb; 
                //表面法线为凹凸纹理的颜色   
                o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap)); 
                //边缘颜色   
                half rim = 1.0 - saturate(dot(normalize(IN.viewDir), o.Normal)); 
                //计算出边缘颜色强度系数   
                o.Emission = _RimColor.rgb * pow(rim, _RimPower)*_RimIntensity; 
            
   
        //-------------------结束CG着色器编程语言段 || End CG Programming Part------------------   
        ENDCG 
    
   
        //后备着色器为普通漫反射 || Fallback use Diffuse 
        Fallback "Diffuse" 
}

       稍微琢磨一下就明白,此Shader其实就是用利用了Unity5中封装好的Standard Surface Output结构体中的Emission(自发光)属性,来达到这样的边缘光效果,技术含量很有限。这边附一下Unity5中的SurfaceOutputStandard原型:

1
2
3
4
5
6
7
8
9
10
11
/ Unity5 SurfaceOutputStandard原型: 
struct SurfaceOutputStandard 
    fixed3 Albedo;                  // 漫反射颜色 
    fixed3 Normal;                  // 切线空间法线 
    half3 Emission;                 //自发光 
    half Metallic;                    // 金属度;取0为非金属, 取1为金属 
    half Smoothness;             // 光泽度;取0为非常粗糙, 取1为非常光滑 
    half Occlusion;                 // 遮挡(默认值为1) 
    fixed Alpha;                      // 透明度 
};

将此Shader赋给Material后在编辑器效果图:





      里面有6个参数,包括主颜色、纹理、凹凸纹理、边缘发光颜色、边缘颜色强度、边缘颜色强度系数这六个参数可以定制与调节,只要贴图资源到位,就很容易可以调出自己满意的效果来。


四、可编程Shader版边缘发光Shader的实现

      这篇文章核心主要就是实现本节的这个可编程版(也就是Vertex & Fragment Shader)边缘发光Shader。大家知道,Vertex & Fragment Shader是比Surface Shader更高一段位的实现形态,有更大的可控性,更好的可编程性,可以实现更加丰富的效果,是更贴近CG着色语言的一种Shader形态。

OK,直接贴出经过详细注释的Vertex & Fragment Shader版边缘发光Shader实现源代码:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
Shader "Learning Unity Shader/Lecture 14/Basic Rim Shader"  
    //-----------------------------------【属性 || Properties】------------------------------------------   
    Properties 
    
        //主颜色 || Main Color 
        _MainColor("【主颜色】Main Color", Color) = (0.5,0.5,0.5,1) 
        //漫反射纹理 || Diffuse Texture 
        _TextureDiffuse("【漫反射纹理】Texture Diffuse", 2D) = "white" {}   
        //边缘发光颜色 || Rim Color 
        _RimColor("【边缘发光颜色】Rim Color", Color) = (0.5,0.5,0.5,1) 
        //边缘发光强度 ||Rim Power 
        _RimPower("【边缘发光强度】Rim Power", Range(0.0, 36)) = 0.1 
        //边缘发光强度系数 || Rim Intensity Factor 
        _RimIntensity("【边缘发光强度系数】Rim Intensity", Range(0.0, 100)) = 3 
    
   
    //----------------------------------【子着色器 || SubShader】---------------------------------------   
    SubShader 
    
        //渲染类型为Opaque,不透明 || RenderType Opaque 
        Tags 
        
            "RenderType" = "Opaque" 
        
   
        //---------------------------------------【唯一的通道 || Pass】------------------------------------ 
        Pass 
        
            //设定通道名称 || Set Pass Name 
            Name "ForwardBase" 
   
            //设置光照模式 || LightMode ForwardBase 
            Tags 
            
                "LightMode" = "ForwardBase" 
            
   
            //-------------------------开启CG着色器编程语言段 || Begin CG Programming Part----------------------   
            CGPROGRAM 
   
                //【1】指定顶点和片段着色函数名称 || Set the name of vertex and fragment shader function 
                #pragma vertex vert 
                #pragma fragment frag 
   
                //【2】头文件包含 || include 
                #include "UnityCG.cginc" 
                #include "AutoLight.cginc" 
   
                //【3】指定Shader Model 3.0 || Set Shader Model 3.0 
                #pragma target 3.0 
   
                //【4】变量声明 || Variable Declaration 
                //系统光照颜色 
                uniform float4 _LightColor0; 
                //主颜色 
                uniform float4 _MainColor; 
                //漫反射纹理 
                uniform sampler2D _TextureDiffuse;  
                //漫反射纹理_ST后缀版 
                uniform float4 _TextureDiffuse_ST; 
                //边缘光颜色 
                uniform float4 _RimColor; 
                //边缘光强度 
                uniform float _RimPower; 
                //边缘光强度系数 
                uniform float _RimIntensity; 
   
                //【5】顶点输入结构体 || Vertex Input Struct 
                struct VertexInput  
                
                    //顶点位置 || Vertex position 
                    float4 vertex : POSITION; 
                    //法线向量坐标 || Normal vector coordinates 
                    float3 normal : NORMAL; 
                    //一级纹理坐标 || Primary texture coordinates 
                    float4 texcoord : TEXCOORD0; 
                }; 
   
                //【6】顶点输出结构体 || Vertex Output Struct 
                struct VertexOutput  
                
                    //像素位置 || Pixel position 
                    float4 pos : SV_POSITION; 
                    //一级纹理坐标 || Primary texture coordinates 
                    float4 texcoord : TEXCOORD0; 
                    //法线向量坐标 || Normal vector coordinates 
                    float3 normal : NORMAL; 
                    //世界空间中的坐标位置 || Coordinate position in world space 
                    float4 posWorld : TEXCOORD1; 
                    //创建光源坐标,用于内置的光照 || Function in AutoLight.cginc to create light coordinates 
                    LIGHTING_COORDS(3,4) 
                }; 
   
                //【7】顶点着色函数 || Vertex Shader Function 
                VertexOutput vert(VertexInput v)  
                
                    //【1】声明一个顶点输出结构对象 || Declares a vertex output structure object 
                    VertexOutput o; 
   
                    //【2】填充此输出结构 || Fill the output structure 
                    //将输入纹理坐标赋值给输出纹理坐标 
                    o.texcoord = v.texcoord; 
                    //获取顶点在世界空间中的法线向量坐标   
                    o.normal = mul(float4(v.normal,0), _World2Object).xyz; 
                    //获得顶点在世界空间中的位置坐标   
                    o.posWorld = mul(_Object2World, v.vertex); 
                    //获取像素位置 
                    o.pos = mul(UNITY_MATRIX_MVP, v.vertex); 
   
                    //【3】返回此输出结构对象  || Returns the output structure 
                    return o; 
                
   
                //【8】片段着色函数 || Fragment Shader Function 
                fixed4 frag(VertexOutput i) : COLOR 
                
                    //【8.1】方向参数准备 || Direction 
                    //视角方向 
                    float3 ViewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz); 
                    //法线方向 
                    float3 Normalection = normalize(i.normal); 
                    //光照方向 
                    float3 LightDirection = normalize(_WorldSpaceLightPos0.xyz); 
   
                    //【8.2】计算光照的衰减 || Lighting attenuation 
                    //衰减值 
                    float Attenuation = LIGHT_ATTENUATION(i); 
                    //衰减后颜色值 
                    float3 AttenColor = Attenuation * _LightColor0.xyz; 
   
                    //【8.3】计算漫反射 || Diffuse 
                    float NdotL = dot(Normalection, LightDirection); 
                    float3 Diffuse = max(0.0, NdotL) * AttenColor + UNITY_LIGHTMODEL_AMBIENT.xyz; 
   
                    //【8.4】准备自发光参数 || Emissive 
                    //计算边缘强度 
                    half Rim = 1.0 - max(0, dot(i.normal, ViewDirection)); 
                    //计算出边缘自发光强度 
                    float3 Emissive = _RimColor.rgb * pow(Rim,_RimPower) *_RimIntensity; 
   
                    //【8.5】计在最终颜色中加入自发光颜色 || Calculate the final color 
                    //最终颜色 = (漫反射系数 x 纹理颜色 x rgb颜色)+自发光颜色 || Final Color=(Diffuse x Texture x rgbColor)+Emissive 
                    float3 finalColor = Diffuse * (tex2D(_TextureDiffuse,TRANSFORM_TEX(i.texcoord.rg, _TextureDiffuse)).rgb*_MainColor.rgb) + Emissive; 
                   
                    //【8.6】返回最终颜色 || Return final color 
                    return fixed4(finalColor,1); 
                
   
            //-------------------结束CG着色器编程语言段 || End CG Programming Part------------------   
            ENDCG 
        
    
   
    //后备着色器为普通漫反射 || Fallback use Diffuse 
    FallBack "Diffuse" 
}

       相信不少朋友已经看出来了,与普通的漫反射Shader相比,这个Shader的魔力就在于多出了“8.4准备自发光参数”和“8.5在最终颜色中加入自发光颜色"两个步骤而已,前面都是普通的Vertex & Fragment Shader常规写法。

       将此Shader赋给Material,得到的效果如下:


  

 

  


当然,你也可以将这两个Shader用于场景中各种模型,以下是一组效果图:


 








     OK,这篇文章的内容大致如此。我们下篇文章,再会。

      附: 本文配套源码下载链接

     【Github】本文Shader源码

   看了上面的文章 热爱游戏创作的你是不是已经开始热血沸腾了呢?是不是迫不及待的想加入游戏团队成为里面的一员呢?
        福利来啦~赶快加入腾讯GAD交流群,人满封群!每天分享游戏开发内部干货、教学视频、福利活动、和有相同梦想的人在一起,更有腾讯游戏专家手把手教你做游戏!
腾讯GAD游戏程序交流群:484290331

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引