【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