【OpenGL】Dota2 Shader分析
发表于2018-10-18
最近在总结OpenGL知识,想到了以前搜索到Dota2相关的文章:《Dota2-Character Shader Masks Guide》,而且dota2的模型在网上也可以下载到,所以非常值得用来作为光照相关知识的总结。
我用Ogre模型做实验,效果如下:

这个Shader只是为了总结shader的知识点,对于实现可能比较粗糙了点,所以见谅。
我准备分两部分来讲这个Shader的实现:
- 用Surface Shader的方法来展示《Dota2-Character Shader Masks Guide》的算法。
- 用Vertex/Fragment的方法来解释,会更加偏向于Surface Shader为我们节省的繁琐事务(比如怎么正确计算法线等)。
Shader需要四张贴图,如下图:

本文主要介绍两张Mask贴图所对应的算法。
图解算法
先用图解的方式来体会效果,主要是为了强调:
- 可以用图层来包含特殊意义的信息;
- 要调好效果,光照模型很重要;这里的效果是Dota2专有的;
比较简单的光照模型还有Phong/Blinn模型、BRDF模型等……
我们以角色武器的材质球作为参考,原始贴图颜色:

a.漫反射部分( Mask1.g)
float3 albedo = tex2D(_MainTex, i.uv).rgb; float diff = max(0.05, dot(worldN, lightDirection)); float3 diffuseReflection = albedo * diff;


Diffuse wrap texture (相关概念:Half-Lambert):
float3 diffuseReflection = albedo * tex2D(_Diffuse, float2(diff, 0.2));


Diffuse的结果

b.高光相关(Mask2.a,Mask2.r)
不同的材质,高光点的大小是不一样的,越粗糙的,高光点越大但亮度越小,粗糙到一定程度就是漫反射(相反,就是镜面反射)。这里用两张贴图来表明高观点大小(Mask2.a)和强度(Mask2.r):


我们发现,同一种材质的高光强度也会有所不一样,这个是因为考虑了AO(Ambient Occlusion);
float3 specularReflection = pow(max(0.0, dot(reflect(-lightDirection, worldN), viewDirection)), shininess) * specIntensity;


加上漫反射:

漫反射+高光结果:

c.Rimlighting Mask2.g
Rimlighting用来亮化对象边缘,就像对象后面也有光源一样,使得对象更加凸显。不同物质的边缘光强度也不一样,金属的会弱一点,皮革皮肤等会强一些,这里用Mask2.g来控制边缘光的轻度。Mask如下:

float3 rim = rimIntensity * saturate(1 - saturate(dot(worldN, viewDirection))*1.8) ;



漫反射+高光+Rim结果:

d.自发光(Mask1.a)
自发光是用来模拟热辐射等的效果,mask如下:

diffuseReflection = albedo * lerp(tex2D(_Diffuse, float2(diff, 0.2)), float3(1, 1, 1) * 2, selfIllu);


漫反射+高光+Rim+自发光结果:

e.更加真实的金属 (Mask1.b, Mask2.b)
f.Detail Map
略
算法原理
这里Shader的原理和Phong/Blinn中的原理比较相似,Phone/Blinn模型主要是用三个向量(Light, View, Normal Direction)来计算环境光(ambient),漫反射(diffuse)和高光(specular)。而Dota2模型是在此基础上,用Mask的方式来对各种光进行细致地区分;再加上了rimLight, DetailMap等其它一些效果。另外,Blinn模型是Phong模型的优化方案(请参考文章:Shading Model);当然还有物理渲染模型(待我了解之后,在写文章吧 :)
下面介绍一下Phong/Blinn模型:
- 漫反射
- 高光
- rimLight
完整代码:
Shader"stalendp/dotaShaderSuf"{
Properties{
_MainTex("Albedo(RGB)",2D)="white"{}
_Normal("Normal",2D)="white"{}
_Mask1("Mask1",2D)="white"{}
_Mask2("Mask2",2D)="white"{}
_Diffuse("Diffuse",2D)="white"{}
}
SubShader{
Tags{"RenderType"="Opaque"}
LOD200
CGPROGRAM
//PhysicallybasedStandardlightingmodel,andenableshadowsonalllighttypes
#pragmasurfacesurfDotaSpecularnoforwardaddnoshadow
//Useshadermodel3.0target,togetnicerlookinglighting
#pragmatarget3.0
sampler2D_MainTex;
sampler2D_Normal;
sampler2D_Mask1;
sampler2D_Mask2;
sampler2D_Diffuse;
structInput{
float2uv_MainTex;
float2uv_Normal;
float2uv_Mask1;
float2uv_Mask2;
float2uv_Diffuse;
};
structMySurfaceOutput{
fixed3Albedo;
fixed3Normal;
fixed3Emission;
halfSpecular;
fixedGloss;
fixedAlpha;
float2myuv;
};
half4LightingDotaSpecular(MySurfaceOutputs,half3lightDir,half3viewDir,halfatten){
fixed4tex=tex2D(_MainTex,s.myuv);
s.Albedo=tex.rgb;
float4mask1=tex2D(_Mask1,s.myuv);
float4mask2=tex2D(_Mask2,s.myuv);
floatspecIntensity=mask2.r;
floatrimIntensity=mask2.g;
floattint=mask2.b;
floatshininess=mask2.a*10;
floatmetalness=mask1.b;
floatselfIllu=mask1.a;
metalness=1-metalness*0.7;
float3viewDirection=viewDir;//normalize(UnityWorldSpaceViewDir(worldPos.xyz));
float3lightDirection=lightDir;//normalize(_WorldSpaceLightPos0.xyz);//directionallight
floatdr=max(0.05,dot(s.Normal,lightDirection));
float3diffuseReflection=s.Albedo*metalness*lerp(tex2D(_Diffuse,float2(dr,0.2)),float3(1,1,1)*2,selfIllu);
float3rim=rimIntensity*saturate(1-saturate(dot(s.Normal,viewDirection))*1.8);
float3specularReflection=specIntensity*metalness*4*lerp(_LightColor0.rgb,_LightColor0.w*s.Albedo,tint)
*pow(max(0.0,dot(reflect(-lightDirection,s.Normal),viewDirection)),shininess*metalness);
returnhalf4((diffuseReflection+rim+specularReflection)*atten,1);
}
voidsurf(InputIN,inoutMySurfaceOutputo){
o.myuv=IN.uv_MainTex;
o.Normal=UnpackNormal(tex2D(_Normal,IN.uv_MainTex));
}
ENDCG
}
FallBack"Diffuse"
}
来自:https://blog.csdn.net/stalendp/article/details/50953869
