在Unity中复刻基于PBS的Dota2着色器

最近需求一个在人物模型低面数下也能达到良好渲染效果的办法,Dota2是一个不错的参考,所以从贴图着手研究了一下Dota2的着色器,使用Surface Shader进行复刻,同时兼容Unity的PBS管线。

Dota2的所有人物模型(包括贴图)可以在Dota2的WorkShop中下载到。

修复2017/3/29

效果展示

以食人魔法师(Ogre)为例。

Dota2中的原版效果:
dota2-ogre

Unity中复刻的效果:
unity-ogre
unity-ogre-gif

现在除了FresnelWarpColor、FresnelWarpRim、FresnelWarpSpec这3张贴图没有实现外,其余都已经实现。这三张贴图在官方解析中也没有提到,不确定该怎么处理。

分析

官方有对于Dota2贴图的解析 点击这里
下载下来的Dota2资源中有14张贴图,都为tga格式:
有一个奇怪的问题需要注意,下载下来的模型贴图中Color、Normal贴图是没问题的,但是其他贴图是垂直翻转过的,不能直接贴,我是在PS中翻转回去再贴就没有问题,原因不明。

  • RGB表示彩色图。Dota2没有贴图是利用到Alpha通道的。
  • Grey表示灰度图。遮罩贴图基本都是,纯黑表示0,纯白表示1。

Color(RGB)

Color
颜色贴图。对应到SurfaceOutputStandard中的Albedo。

TintByBaseMask(Grey)

TintByBaseMask
颜色染色遮罩。一般对Albedo染色是直接对其进行相乘运算的。Dota2的对染色额外制作了一张遮罩,作用是决定染色的作用域,值越大的部分染色效果越明显。

CubeMap

CubeMap
立方体环境贴图。讲道理,既然遵循PBS就不要使用CubeMap来做假的环境贴图了,请使用反射探针吧。而且下载下来的CubeMap效果也很奇怪,可能我的用法错了?

Normal(RGB)

Normal
法线贴图。对应到Normal。

MetalnessMask(Grey)

MetalnessMask
金属性遮罩。由于皮肤部分没有金属性,贴图只是一张1x1的纯灰色贴图。这张是披肩的贴图。数值对应到Metallic。

RimMask(Grey)

RimMask
边缘光遮罩。边缘光RimLight也是很常见的一种Trick效果,Dota2也是带一点Rim效果的。Dota2使用这张帖图来控制边缘光的强弱。

SelfIllumMask(Grey)

SelfIllumMask
自发光遮罩。Dota2的自发光做法不同于Unity原生,自发光的颜色信息取自Color贴图,而这张帖图只是控制自发光强弱的。

SpecularMask(Grey)

SpecularMask
高光遮罩。对应到Smoothness上,虽然效果不尽相同。和下面的高光指数配合使用。

SpecularExponent(Grey)

SpecularExponent
高光指数。和高光遮罩结合使用,我的做法是ldexp(SpecularMask,SpecularExponent),看起来效果没什么问题…应该是这样吧。

Translucency(Grey)

Translucency
透明性。我是做成Cut-out的效果,使用Cutoff值控制抠图程度。这张也是披肩的贴图,这个模型只有披肩用到了Cutout。

DiffuseWarp

DiffuseWarp
漫反射映射。应该是控制明暗渐变的,但是还没找到在PBS光照函数中修改的办法,未实现。

FresnelWarpColor

FresnelWarpColor
菲涅尔颜色映射。未实现。

FresnelWarpRim

FresnelWarpRim
菲涅尔内发光映射。未实现。

FresnelWarpSpec

FresnelWarpSpec
菲涅尔高光映射。未实现。

Shader代码

不多说了,看代码吧!
完整的项目在coding.net上:Dota2-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
158
Shader"Dota2/Standard"
{
Properties
{
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 2
/*
[Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend("Src Blend Mode", Float) = 1
[Enum(UnityEngine.Rendering.BlendMode)] _DstBlend("Dst Blend Mode", Float) = 1
[Enum(Off, 0, On, 1)] _ZWrite("ZWrite", Float) = 0
[Enum(UnityEngine.Rendering.CompareFunction)] _ZTest("ZTest", Float) = 0
*/

[Space(10)]
_ColorTex("Color(RGB)", 2D) = "white"{}
[Space(10)]
_Tint("Tint", Color) = (1,1,1,1)
_TintByBaseTex("Tint Mask(Grey)",2D) = "white"{}
[Space(10)]
_NormalTex("Normal(RGB)", 2D)="bump"{}
[Space(10)]
_MetalnessTex("Metalness Mask(Grey)", 2D) = "white"{}
_Metalness("Metalness Intensity", Range(0,1)) = 0.0
[Space(10)]
_SpeculerTex("Speculer Mask(Grey)", 2D) = "white"{}
_SpeculerExpTex("Speculer Exponent(Grey)", 2D) = "black"{}
_Speculer("Speculer Intensity", Range(0,1)) = 0.0
[Space(10)]
_SelfIllumTex("SelfIllum Mask(Grey)", 2D) = "black"{}
_SelfIllum("SelfIllum Intensity", Range(0,1)) = 0.0
[Space(10)]
_RimTex("Rim Mask(Grey)", 2D) = "black"{}
_RimColor("Rim Color", Color) = (1,1,1,1)
_Rim("Rim Intensity", Range(0,1)) = 0.0
[Space(10)]
_CubeMap("Cube Map(RGB)", CUBE) = "black"{}
_Cube("Cube Intensity",Range(0,1)) = 0.0
[Space(10)]
_TranslucencyTex("Translucency Mask(Grey)", 2D) = "white"{}
_Cutoff("Alpha Cutoff", Range(0,1)) = 0.1
}
SubShader
{
Tags
{
"Queue" = "AlphaTest"
"IgnoreProjector" = "True"
"RenderType" = "TransparentCutout"
}
Cull [_Cull]
/*
Blend [_SrcBlend] [_DstBlend]
ZWrite [_ZWrite]
ZTest [_ZTest]
*/


CGPROGRAM
#pragma surface surf CustomStandard fullforwardshadows alphatest:_Cutoff vertex:vert addshadow
#pragma target 3.0
#include "UnityPBSLighting.cginc"

//PBS光照
inline void LightingCustomStandard_GI(SurfaceOutputStandard s, UnityGIInput data, inout UnityGI gi)
{

gi = UnityGlobalIllumination(data, s.Occlusion, s.Smoothness, s.Normal);
}
inline half4 LightingCustomStandard(SurfaceOutputStandard s, half3 viewDir, UnityGI gi)
{

s.Normal = normalize(s.Normal);

half oneMinusReflectivity;
half3 specColor;
s.Albedo = DiffuseAndSpecularFromMetallic(s.Albedo, s.Metallic, specColor, oneMinusReflectivity);

// shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)
// this is necessary to handle transparency in physically correct way - only diffuse component gets affected by alpha
half outputAlpha;
s.Albedo = PreMultiplyAlpha(s.Albedo, s.Alpha, oneMinusReflectivity, outputAlpha);

half4 c = UNITY_BRDF_PBS(s.Albedo, specColor, oneMinusReflectivity, s.Smoothness, s.Normal, viewDir, gi.light, gi.indirect);
c.rgb += UNITY_BRDF_GI(s.Albedo, specColor, oneMinusReflectivity, s.Smoothness, s.Normal, viewDir, s.Occlusion, gi);
c.a = outputAlpha;

return c;
}

struct Input
{
float2 uv_ColorTex;
float3 viewDir;
float3 worldRefl;
INTERNAL_DATA
};

sampler2D _ColorTex;
sampler2D _TintByBaseTex;
sampler2D _NormalTex;
sampler2D _MetalnessTex;
sampler2D _SpeculerTex;
sampler2D _SpeculerExpTex;
sampler2D _SelfIllumTex;
sampler2D _RimTex;
sampler2D _TranslucencyTex;
samplerCUBE _CubeMap;

fixed _Normal;
fixed _Speculer;
fixed _Metalness;
fixed _SelfIllum;
fixed _Rim;
fixed3 _RimColor;
fixed _Cube;
fixed3 _Tint;

void vert(inout appdata_full v)
{


}

void surf(Input IN, inout SurfaceOutputStandard o)
{

//效果倍增器
fixed multipler = 2;
//颜色
fixed tintByBaseMask = tex2D(_TintByBaseTex, IN.uv_ColorTex);
half3 color = tex2D(_ColorTex, IN.uv_ColorTex);
half3 colorTinted = tex2D(_ColorTex, IN.uv_ColorTex) * _Tint;
half3 albedo = lerp(color, colorTinted, tintByBaseMask);
//法线
half3 normal = UnpackNormal(tex2D(_NormalTex,IN.uv_ColorTex));
//金属性
fixed metalnessTexed = tex2D(_MetalnessTex, IN.uv_ColorTex);
fixed metallic = lerp(0, metalnessTexed * multipler, _Metalness);
//高光
fixed speculerTexed = tex2D(_SpeculerTex, IN.uv_ColorTex);
fixed speculerExpTexed = tex2D(_SpeculerExpTex, IN.uv_ColorTex);
fixed smoothness = lerp(0, speculerTexed * exp(speculerExpTexed) * multipler, _Speculer);
//自发光
half3 emission = lerp(0, (albedo * tex2D(_SelfIllumTex, IN.uv_ColorTex) * multipler), _SelfIllum);
//边缘光
fixed rimTexed = tex2D(_RimTex, IN.uv_ColorTex);
fixed3 rim = lerp(0, _Rim * _RimColor * saturate(1 - saturate(dot(normal, IN.viewDir)) * 1.8), rimTexed);
//立方环境贴图
half3 cube = lerp(0, texCUBE(_CubeMap, WorldReflectionVector(IN, o.Normal)).rgb, _Cube);
//抠图
fixed alpha = tex2D(_TranslucencyTex, IN.uv_ColorTex);
//clip(alpha - _Cutout);

o.Albedo = albedo.rgb;
o.Normal = normal;
o.Metallic = metallic;
o.Smoothness = smoothness;
o.Emission = emission + rim + cube;
o.Alpha = alpha;
}
ENDCG
}
FallBack "Diffuse"
//CustomEditor "CustomEditor_Dota2_Standard"
}

Unity中材质编辑器的样子:
material-editor