PISCOnoob

导航

用Unity实现Dota2角色Shader(以Ogre Magi为例)

写在前面:

本文章为个人学习笔记,方便以后自己复习,也希望能帮助到他人。

由于本人水平有限难免出现错误,还请评论区指出,多多指教。

部分图元和素材来源于网络,如有侵权请联系本人删除。

参考资料与链接会在文章末尾贴出。

=======================================================================

本文通过编写DOTA2角色shader来巩固一下之前所学光照模型的基础知识,因为V社有在官网放出DOTA2角色的模型和贴图,比较方便。

以下是DOTA2资源地址:

但是V社是没有放出过官方的Shader,只有一些贴图的相关用法(下面的网址),因此网上各种DOTA2 Shader都是各位大牛自己研究的,本文也是参考了很多大牛的分享与教学,在此是为了巩固自己所学并与大家分享,并非“唯一正确”。

下载完资源包打开一看吓了一跳,一共80张贴图,分为base,arm,back,belt,head,weapon六个部分,每个部分也有十多张贴图,打开一看有一些是灰度图,或许可以把几张压在一张里(后面会提到)。

接下来浏览一下官网的页面看看每个贴图大概的用处是啥。

  • Color贴图,透明贴图,法线贴图,自发光贴图
  • Detail Mask与不同的角色特殊效果相关,Diffuse Mask极少使用
  • Metalness Mask金属度贴图,后面会对金属部分做一些处理
  • Rimlight边缘光效果
  • Base Tint Mask高光染色遮罩,控制高光颜色与bask color颜色的混合程度(高光从base color拿了多少颜色?)
  • Specular Exponent高光次幂

还有一些条状的Warp图,我们看看哪些贴图可以做一下资源优化:

  • RGB:Color与A:opacity(图名为translucency)合并为一张
  • R:SpecInt G:RimInt B:TintMask A:SpecPow合并为一张
  • R:FresnelCol G:FresnelRim A:FresnelSpec合并为一张

我们先来看一下大概的光照模型和最终效果:

首先我们在片元着色器中准备后续计算用的向量:

解码法线贴图获取切线空间下的法线,再构建TBN矩阵计算出世界空间下的法线。

计算出世界空间下的视方向与光方向(平行光)的反射方向。

由于我们做了一些纹理资源优化,把可以合并的贴图合并起来,此时我们采样纹理的次数就能减少,避免更多性能消耗。

但是要记得每个通道中的贴图起的是什么作用。

比如这里,MaskTex的alpha通道是SpecExponent(SpecPow)高光次幂,我们用lerp后的结果作为采样cubemap的第四维,可以得到身体不同的部分有不同的光滑度。

纹理中越亮代表越光滑,反射内容则越清晰;反之,越暗代表越粗糙,反射内容越模糊。

如果只是单用mipmap等级采样cubemap则是全身同一程度的光滑度,效果比较死板。

采样过后把纹理结果整理好方便之后使用:

接下来是主光漫反射部分:

除了计算半兰伯特模型外还有两个额外操作。

Metalness Mask越亮金属度越高,暗部则是非金属。我们用金属度纹理做lerp,实际上是压暗了金属部分,意思是让金属部分的漫反射(diffuse)减弱(模拟出PBR的效果,能量守恒)。

用半兰伯特结果构建一个二维向量采样下面这张DiffuseWarpTex会得到类似3S的效果,暗紫色的皮肤非常符合怪物角色的设定:

 

同样用金属度做lerp,让金属部分的菲涅尔效果减弱:

然后除了计算phong模型外,还用到了specTint这张纹理:

此纹理亮部代表非金属,暗部代表金属。如此计算后会发现金属部分的高光会“染上”base color的颜色,在我们制作金子,黄铜,蓝钢,绿铜等金属时就会十分有效果;

同时在非金属的部分也会因为0.3的三维向量带有一点高光,这个值完全是自己觉得怎么好看怎么来,美术的经验值。

然后用spec跟fresnel做max,得到了比原先更亮的效果,全身更加油亮,也是很符合怪物的设定:

接下来部分就并没有什么特殊的地方:

环境漫反射用了简单的单色光,用球谐函数的效果会更好,但是考虑到moba类型游戏实际渲染出来只占到屏幕很小的地方,且环境变化不大,用单色光降低消耗是可以接受的。

轮廓光乘上了世界法线的g通道,模拟除了天光撒在了角色身上的效果。

最后再处理一下角色的透贴部分(披肩):

黑透白不透

要关闭背面剔除,否则效果出错:

最终效果:

以下是参考代码

  1 Shader "Custom/Dota2" {
  2     Properties
  3     {
  4     [Header(Texture)]
  5         //注意某些图塞进了不同的纹理
  6         _MainTex        ("RGB:颜色 A:透贴", 2d) = "white"{}
  7         _MaskTex        ("R:高光强度 G:边缘光强度 B:高光染色 A:高光次幂", 2d) = "black"{}
  8         _NormTex        ("RGB:法线贴图", 2d) = "bump"{}
  9         _MatelnessMask  ("金属度遮罩", 2d) = "black"{}
 10         _EmissionMask   ("自发光遮罩", 2d) = "black"{}
 11         _DiffWarpTex    ("颜色Warp图", 2d) = "gray"{}
 12         _FresWarpTex    ("菲涅尔Warp图", 2d) = "gray"{}
 13         _Cubemap        ("环境球", cube) = "_Skybox"{}
 14  
 15         [Header(DirDiff)]
 16         _LightCol       ("光颜色", color) = (1.0, 1.0, 1.0, 1.0)
 17         [Header(DirSpec)]
 18         _SpecPow        ("高光次幂", range(0.0, 99.0)) = 5
 19         _SpecInt        ("高光强度", range(0.0, 10.0)) = 5
 20         [Header(EnvDiff)]
 21         _EnvCol         ("环境光颜色", color) = (1.0, 1.0, 1.0, 1.0)
 22         [Header(EnvSpec)]
 23         _EnvSpecInt     ("环境镜面反射强度", range(0.0, 30.0)) = 0.5
 24         [Header(RimLight)]
 25         [HDR]_RimCol    ("轮廓光颜色", color) = (1.0, 1.0, 1.0, 1.0)
 26         _RimInt        ("轮廓光强度", range(0.0, 5.0)) = 1.0
 27         [Header(Emission)]
 28         _EmitInt        ("自发光强度", range(0.0, 10.0)) = 1.0
 29         [HideInInspector]
 30         _Cutoff         ("Alpha cutoff", Range(0,1)) = 0.5
 31         [HideInInspector]
 32         _Color          ("Main Color", Color) = (1.0, 1.0, 1.0, 1.0)
 33     }
 34     SubShader
 35     {
 36         Tags
 37         {
 38             "RenderType"="Opaque"
 39         }
 40         Pass
 41         {
 42             Name "FORWARD"
 43             Tags
 44             {
 45                 "LightMode"="ForwardBase"
 46             }
 47             Cull off
 48              
 49             CGPROGRAM
 50             #pragma vertex vert
 51             #pragma fragment frag
 52             #include "UnityCG.cginc"
 53             //追加unity投影CG
 54             #include "AutoLight.cginc"
 55             #include "Lighting.cginc"
 56  
 57             #pragma multi_compile_fwdbase_fullshadows
 58             #pragma target 3.0
 59             //声明变量
 60             uniform sampler2D _MainTex;
 61             uniform sampler2D _MaskTex;
 62             uniform sampler2D _NormTex;
 63             uniform sampler2D _MatelnessMask;
 64             uniform sampler2D _EmissionMask;
 65             uniform sampler2D _DiffWarpTex;
 66             uniform sampler2D _FresWarpTex;
 67             uniform samplerCUBE _Cubemap;
 68             // DirDiff
 69             uniform float3 _LightCol;
 70             // DirSpec
 71             uniform half _SpecPow;
 72             uniform half _SpecInt;
 73             // EnvDiff
 74             uniform half3 _EnvCol;
 75             // EnvSpec
 76             uniform half _EnvSpecInt;
 77             // RimLight
 78             uniform half3 _RimCol;
 79             uniform half  _RimInt;
 80             // Emission
 81             uniform half _EmitInt;
 82             // Other
 83             uniform half _Cutoff;
 84             //输入结构
 85             struct VertexInput
 86              {
 87                 float4 vertex   : POSITION;   // 顶点信息
 88                 float2 uv0      : TEXCOORD0;  // UV信息
 89                 float4 normal   : NORMAL;     // 法线信息
 90                 float4 tangent  : TANGENT;    // 切线信息
 91  
 92             };
 93             //输出结构
 94             struct VertexOutput
 95             {
 96                 float4 pos    : SV_POSITION;  // 屏幕顶点位置
 97                 float2 uv0      : TEXCOORD0;  // UV0
 98                 float4 posWS    : TEXCOORD1;  // 世界空间顶点位置
 99                 float3 nDirWS   : TEXCOORD2;  // 世界空间法线方向
100                 float3 tDirWS   : TEXCOORD3;  // 世界空间切线方向
101                 float3 bDirWS   : TEXCOORD4;  // 世界空间副切线方向
102                 LIGHTING_COORDS(5,6)          // 投影相关
103             };
104             //顶点shader
105             VertexOutput vert (VertexInput v)
106             {
107                  VertexOutput o = (VertexOutput)0;                   // 新建输出结构
108                     o.pos = UnityObjectToClipPos( v.vertex );       // 顶点位置 OS>CS
109                     o.uv0 = v.uv0;                                  // 传递UV
110                     o.posWS = mul(unity_ObjectToWorld, v.vertex);   // 顶点位置 OS>WS
111                     o.nDirWS = UnityObjectToWorldNormal(v.normal);  // 法线方向 OS>WS
112                     o.tDirWS = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0.0)).xyz); // 切线方向 OS>WS
113                     o.bDirWS = normalize(cross(o.nDirWS, o.tDirWS) * v.tangent.w);  // 副切线方向
114                     TRANSFER_VERTEX_TO_FRAGMENT(o)                  // 投影相关
115                 return o;                                           // 返回输出结构                             //将输出结构 输出
116             }
117             //片元着色器
118             float4 frag(VertexOutput i) : COLOR
119             {
120                 // 向量准备
121                 half3 nDirTS = UnpackNormal(tex2D(_NormTex, i.uv0));
122                 half3x3 TBN = half3x3(i.tDirWS, i.bDirWS, i.nDirWS);
123                 half3 nDirWS = normalize(mul(nDirTS, TBN));
124                 half3 vDirWS = normalize(_WorldSpaceCameraPos.xyz - i.posWS);
125                 half3 vrDirWS = reflect(-vDirWS, nDirWS);
126                 half3 lDirWS = _WorldSpaceLightPos0.xyz;
127                 half3 lrDirWS = reflect(-lDirWS, nDirWS);
128                 // 中间量计算,为后续光照模型准备
129                 half ndotl = dot(nDirWS, lDirWS);
130                 half ndotv = dot(nDirWS, vDirWS);
131                 half vdotr = dot(vDirWS, lrDirWS);
132                 //采样纹理
133                 half4 var_MainTex = tex2D(_MainTex, i.uv0);
134                 half4 var_MaskTex = tex2D(_MaskTex, i.uv0);
135                 half var_MatelnessMask = tex2D(_MatelnessMask, i.uv0).r;
136                 half var_EmissionMask = tex2D(_EmissionMask, i.uv0).r;
137                 half3 var_FresWarpTex = tex2D(_FresWarpTex, ndotv);
138                 //cubemap用高光次幂做lerp,越亮的地方反射越清晰
139                 half3 var_Cubemap = texCUBElod(_Cubemap, float4(vrDirWS, lerp(8.0, 0.0, var_MaskTex.a))).rgb;
140                 //提取信息
141                  half3 baseCol = var_MainTex.rgb;
142                 half opacity = var_MainTex.a;
143                 half specInt = var_MaskTex.r;
144                 half rimInt = var_MaskTex.g;
145                 half specTint = var_MaskTex.b;
146                 half specPow = var_MaskTex.a;
147                 half matellic = var_MatelnessMask;
148                 half emitInt = var_EmissionMask;
149                 half3 envCube = var_Cubemap;
150                 half shadow = LIGHT_ATTENUATION(i);
151                 // 光照模型
152                     // 菲涅尔
153                     half3 fresnel = lerp(var_FresWarpTex, 0.0, matellic);
154                     half fresnelCol = fresnel.r;   
155                     half fresnelRim = fresnel.g;
156                     half fresnelSpec = fresnel.b;
157                     // 光源镜面反射
158                     half3 specCol = lerp(baseCol, half3(0.3, 0.3, 0.3), specTint) ;
159                     half phong = pow(max(0.0, vdotr),  _SpecPow);//specPow *
160                     half spec = phong * max(0.0, ndotl);
161                     spec = max(spec, fresnelSpec);
162                     spec = spec * _SpecInt;
163                     half3 dirSpec = specCol * spec * _LightCol;
164                      
165                     // 光源漫反射
166                     half3 diffCol = lerp(baseCol, half3(0.0, 0.0, 0.0), matellic);
167                     half halfLambert = ndotl * 0.5 + 0.5;
168                     half3 var_DiffWarpTex = tex2D(_DiffWarpTex, half2(halfLambert, 0.2));
169                     half3 dirDiff = diffCol * var_DiffWarpTex * _LightCol;
170  
171                     // 环境漫反射
172                     half3 envDiff = diffCol * _EnvCol;
173                     // 环境镜面反射
174                     half reflectInt = max(fresnelSpec, matellic) * specInt;
175                     half3 envSpec = specCol * reflectInt * envCube * _EnvSpecInt;
176                     // 轮廓光
177                     half3 rimLight = _RimCol * fresnelRim * rimInt *_RimInt* max(0.0, nDirWS.g);
178                     // 自发光
179                     half3 emission = diffCol * emitInt * _EmitInt;
180                     // 混合最终结果
181                     half3 finalRGB = (dirDiff + dirSpec) * shadow + envDiff + envSpec + rimLight + emission;
182  
183                     //透明剪裁(披风部分)
184                     clip(opacity-_Cutoff);
185                     //返回值
186                     return float4 (finalRGB,1.0);
187             }
188             ENDCG
189         }
190     }
191     //声明回退shader
192     FallBack "Legacy Shaders/Transparent/Cutout/VertexLit"
193      
194 }

 

posted on 2022-11-01 11:09  PISCOnoob  阅读(166)  评论(0编辑  收藏  举报