最近研究了一下车漆的shader,然后在互联网的浪潮中,发现了毛星云大佬之前的写的一个基于matcap的车漆shader。然后我就在大佬的基础之上又增加了一部分。
原文链接:【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader_mb61c46a7ab1eee的技术博客_51CTO博客
首先讲一下整个shader的实现思路。通过matcap代替光照计算,直接获得“光照信息”。再基于漫反射和立方体贴图来实现车漆的效果。
我试了一下毛星云大佬的Shader,效果如下图
效果看着其实不错,但是总感觉差点什么。我寻觅了半天以后,感觉现实车漆其实高光会多一点,像下图,车的边缘都会有一些高光存在。这个我觉得是因为我们在观察车漆的时候,光线都是自然光,都是从头顶打光下来,并且通过清漆的折射,从而导致车身边缘都会有一部分高光。但是我们看上图就会发现,边缘其实是有点发黑的。不过这个效果实现起来就比较容易,直接用菲涅尔边缘光就可以完成了。
//菲涅尔边缘光
half3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world);
half NdotV = saturate(dot(normal_world, viewDir));
half fresnel = pow((1.0 - NdotV), 5.0f) _FresnelIntensity 5;
那么加入了边缘光后,我们就可以对比一下效果了
(右边是增加了菲涅尔边缘光效果)
所有的代码如下:
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float3 normal_viewspace : TEXCOORD1;
float3 pos_world : TEXCOORD2;
float3 viewDir : TEXCOORD3;
float3 worldSpaceIncident : TEXCOORD4;
float3 normal_world : TEXCOORD5;
float4 vertex : SV_POSITION;
};
float4 _MainColor;
float4 _DetailColor;
sampler2D _DetailMap;
float4 _DetailMap_ST;
float _DetailMapDepthOffset;
float4 _DiffuseColor;
sampler2D _DiffuseMap;
float _DiffuseIntensity;
float4 _DiffuseMap_ST;
sampler2D _Matcap;
float _MatcapIntensity;
float4 _ReflectionColor;
samplerCUBE _ReflectionMap;
float _ReflectionIntensity;
float _FresnelIntensity;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _DiffuseMap);
o.uv = TRANSFORM_TEX(v.uv, _DetailMap);
//将法线从模型空间转换到世界空间
float3 normal_world = mul(float4(v.normal, 0.0), unity_WorldToObject);
o.normal_world = normal_world;
//将法线从世界空间转换到相机空间
float3 normal_viewspace = mul(UNITY_MATRIX_V, float4(normal_world, 0.0)).xyz;
o.normal_viewspace = normal_viewspace;
//世界空间位置
float3 pos_world = mul(unity_ObjectToWorld, v.vertex);
o.pos_world = pos_world;
//世界空间反射向量
float3 worldSpaceIncident = reflect((pos_world - _WorldSpaceCameraPos), normal_world);
o.worldSpaceIncident = worldSpaceIncident;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//镜面反射颜色
float3 worldSpaceReflection = normalize(i.worldSpaceIncident);
float3 reflectionColor = texCUBE(_ReflectionMap, worldSpaceReflection).rgb * _ReflectionColor.rgb * _ReflectionIntensity;
//matcap
half3 normal_world = normalize(i.normal_world);
half3 normal_viewspace = normalize(i.normal_viewspace);
half2 uv_matcap = (normal_viewspace.xy + float2(1.0, 1.0)) * 0.5;
half4 matcap_color = tex2D(_Matcap, uv_matcap) * _MatcapIntensity;
half4 diffuse_color = tex2D(_DiffuseMap, i.uv) * _DiffuseColor * _DiffuseIntensity;
//菲涅尔
half3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world);
half NdotV = saturate(dot(normal_world, viewDir));
half fresnel = pow((1.0 - NdotV), 5.0f) * _FresnelIntensity * 5;
half4 matcap_fresnel = matcap_color + fresnel;
//细节颜色
half4 Detail_Color = tex2D(_DetailMap, i.uv);
Detail_Color = Detail_Color * _DetailColor;
//车漆颜色
//half3 main_color = lerp(lerp(_MainColor, diffuse_color.rgb, diffuse_color.a), reflectionColor, _ReflectionIntensity);
//half3 main_reflect_color = main_color * reflectionColor;
//half4 main_color = matcap_fresnel * diffuse_color + half4(reflectionColor, 1.0) * _MainColor + Detail_Color;
//float3 final_Color = lerp(diffuse_color, matcap_color.rgb, fresnel) * _MainColor.rgb * _DiffuseColor + reflectionColor;
half3 final_color = lerp( matcap_color, reflectionColor, fresnel) * _MainColor.rgb + diffuse_color;
return half4(final_color, 1.0);
}
ENDCG
}
}
看着代码来讲一下关于Matcap的实现方法,
Matcap主要的效果就是,将Matcap贴图使用在模型上以后,模型所展现的贴图始终是基于相机空间不变的。(讲的好像有点抽象),下面这个是使用的Matcap贴图。
我们将这张图贴到一个球体上以后,我们可以看到两个高光的方块始终是在屏幕中不动的
所以首先要做的就是将法线从模型空间转到相机空间。我用了两步,先从模型空间转到世界空间,再从世界空间转到相机空间。因为在后面的计算中,还要用到世界空间下的法线数据,所以就通过两步去做了。在这里有个点要强调一下就是,Matcap之所以要转换的是法线数据而不是顶点数据,是因为MatCap材质是基于表面法线方向的反射颜色来渲染的,Matcap材质不受光照影响,只能提供相对于表面法线方向的反射颜色。
我们将得到的相机空间下的法线数据加1除以2,是为了保证法线数据能够在0到1内。然后我们采样Matcap贴图即可。
后面的就很简单了,采样了Diffuse贴图和镜面反射贴图。通过光照探针来烘焙出一张立方体贴图即可。后面还采样了一个细节贴图,这个是想加一些车漆的橘皮效果,但是我手上没找到好的橘皮的贴图资源,所以就在Shader里面写了但是没有加。最后在计算最终颜色的时候,可以看到我算了好几次,都没找到一个合适的计算方法......最后也是Chat了一下,然后浅浅修改了一下就用了。(得去学一下这种最后颜色的计算方法去)
最后的效果其实还可以,但是我感觉还是差点。我继续研究一下,舍弃Matcap,直接采用光照计算,用法线贴图把橘皮效果做出来以后,试一下效果怎么样。如果还不行就写一套PBR了。