【OpenGL】Dota2 Shader分析

发表于2018-10-18
评论0 4.4k浏览
最近在总结OpenGL知识,想到了以前搜索到Dota2相关的文章:《Dota2-Character Shader Masks Guide》,而且dota2的模型在网上也可以下载到,所以非常值得用来作为光照相关知识的总结。

我用Ogre模型做实验,效果如下:

这个Shader只是为了总结shader的知识点,对于实现可能比较粗糙了点,所以见谅。

我准备分两部分来讲这个Shader的实现:
  1. 用Surface Shader的方法来展示《Dota2-Character Shader Masks Guide》的算法。
  2. 用Vertex/Fragment的方法来解释,会更加偏向于Surface Shader为我们节省的繁琐事务(比如怎么正确计算法线等)。
另外,本文所涉及的资源的下载链接如下:Dota2 Shader for Unity。好的,让我们来进入第一部分吧。

Shader需要四张贴图,如下图:

本文主要介绍两张Mask贴图所对应的算法。

图解算法

先用图解的方式来体会效果,主要是为了强调:
  1. 可以用图层来包含特殊意义的信息;
  2. 要调好效果,光照模型很重要;这里的效果是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

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