Ag·阿龟 发表于 2023-5-30 16:55:10

基于Mactcap实现车漆效果

最近研究了一下车漆的shader,然后在互联网的浪潮中,发现了毛星云大佬之前的写的一个基于matcap的车漆shader。然后我就在大佬的基础之上又增加了一部分。

原文链接:[【Unity Shader编程】之十六 基于MatCap实现适于移动平台的“次时代”车漆Shader_mb61c46a7ab1eee的技术博客_51CTO博客](https://blog.51cto.com/u_15469972/4895418)


首先讲一下整个shader的实现思路。通过matcap代替光照计算,直接获得“光照信息”。再基于漫反射和立方体贴图来实现车漆的效果。

我试了一下毛星云大佬的Shader,效果如下图

!(data/attachment/forum/202305/30/160318rlshpiyn2z0chnnu.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")



效果看着其实不错,但是总感觉差点什么。我寻觅了半天以后,感觉现实车漆其实高光会多一点,像下图,车的边缘都会有一些高光存在。这个我觉得是因为我们在观察车漆的时候,光线都是自然光,都是从头顶打光下来,并且通过清漆的折射,从而导致车身边缘都会有一部分高光。但是我们看上图就会发现,边缘其实是有点发黑的。不过这个效果实现起来就比较容易,直接用菲涅尔边缘光就可以完成了。

!(data/attachment/forum/202305/30/160727cxffd40wadcgs9sa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")


//菲涅尔边缘光
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;

那么加入了边缘光后,我们就可以对比一下效果了

!(data/attachment/forum/202305/30/161305z5h2jaa3ieaelksk.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

(右边是增加了菲涅尔边缘光效果)

所有的代码如下:

    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贴图。

!(data/attachment/forum/202305/30/162139wf9tjlw1tjhtwfll.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

我们将这张图贴到一个球体上以后,我们可以看到两个高光的方块始终是在屏幕中不动的

!(data/attachment/forum/202305/30/162657sasf4xf6f8dw48dc.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "817e549026ae6909ea69cc699bf888b8.gif")

所以首先要做的就是将法线从模型空间转到相机空间。我用了两步,先从模型空间转到世界空间,再从世界空间转到相机空间。因为在后面的计算中,还要用到世界空间下的法线数据,所以就通过两步去做了。在这里有个点要强调一下就是,Matcap之所以要转换的是法线数据而不是顶点数据,是因为MatCap材质是基于表面法线方向的反射颜色来渲染的,Matcap材质不受光照影响,只能提供相对于表面法线方向的反射颜色。

我们将得到的相机空间下的法线数据加1除以2,是为了保证法线数据能够在0到1内。然后我们采样Matcap贴图即可。

后面的就很简单了,采样了Diffuse贴图和镜面反射贴图。通过光照探针来烘焙出一张立方体贴图即可。后面还采样了一个细节贴图,这个是想加一些车漆的橘皮效果,但是我手上没找到好的橘皮的贴图资源,所以就在Shader里面写了但是没有加。最后在计算最终颜色的时候,可以看到我算了好几次,都没找到一个合适的计算方法......最后也是Chat了一下,然后浅浅修改了一下就用了。(得去学一下这种最后颜色的计算方法去)!(data/attachment/forum/202305/30/165323txpmhk3n0mzynxll.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

最后的效果其实还可以,但是我感觉还是差点。我继续研究一下,舍弃Matcap,直接采用光照计算,用法线贴图把橘皮效果做出来以后,试一下效果怎么样。如果还不行就写一套PBR了。

蓝橙熊 发表于 2023-5-31 15:20:00

学到了
页: [1]
查看完整版本: 基于Mactcap实现车漆效果