前言
第一篇文章中提到 基础Matcap 的另一个缺陷是缺少经典BPR所具备的一些属性,本文的目的便是为了探讨如何解决这个缺陷。
第二篇文章中,我们看到在没有使用 固有色(Albedo)贴图 时,Matcap的确能呈现出PBR效果,但与固有色叠加后,便会显得暗淡――这源于漫反射衰减与高光的缺失。
另一方面,Matcap是完全不考虑光照影响的渲染方法,因此也不存在能量守恒,只能通过采样贴图的绘制做出能量守恒的效果,所以不是真正的PBR,也因此能做出很多PBR无法实现的效果。
漫反射补偿
很多已有的Matcap采样图都是BPR渲染的结果,我们获得的固有色贴图也是衰减后的状态(比真正的固有色暗)。当这两种贴图采样相乘后,自然会显得很暗,这个问题也很容易处理,便是补偿损失的漫反射强度――对Matcap Diffuse采样图乘以大于1的数值,如下图所示:
高光
补偿后就不存在亮度的问题了,但这里还存在一个问题――高光颜色受到固有色影响。
本质原因在于MatcapDiffuse贴图理论上只用于计算漫反射以及次表面散射等现象(乘法)。但高光的计算应使用加法。
所以只需要在现有着色器上再加入一个 高光Matcap采样图,并把它加到现有的结果上。(注:PS中的线性减淡就是加法)
上图中高光的颜色就正常了许多。这里还要说明一点,虽然上例中MatcapDiffuse和MatcapSpec用了同一张贴图,但理论上正确的方法是Diffuse只画漫反射部分,Spec只画高光部分。当然,有时错误的方法效果也不错~
下图展示了分开绘制漫反射和高光的效果:
镜面反射与菲涅尔现象
Spec项作为加法项,很适合用于镜面反射及菲涅尔现象。菲涅尔的具体定义大家可以自行查询,Matcap是基于视觉法线的,所以天然与菲涅尔切合。
这里分别给出一个镜面反射(彩色金属)和一个菲涅尔反射(车漆效果)的例子,大家可以参考例子中的采样图:
有了Diffuse贴图和Spec贴图的支持后,很容易实现不同金属度、光滑度的效果,这里就不一一举例了。
下面附上这一章的源码:
Shader "TJia/Matcap_PBR" { Properties { _Color ("Main Color", Color) = (1,1,1,1) _MainTex("Albedo Tex", 2D) = "white" {} _BumpMap ("Normal Tex", 2D) = "bump" {} _BumpValue ("Normal Value", Range(0,10)) = 1 _MatCapDiffuse ("MatCap Diffuse (RGB)", 2D) = "white" {} _DiffuseValue ("Diffuse Value", Range(0,5)) = 1 _MatCapSpec ("MatCap Spec (RGB)", 2D) = "white" {} _SpecValue ("Spec Value", Range(0,2)) = 0 } Subshader { Tags { "RenderType"="Opaque" } Pass { Tags { "LightMode" = "Always" } CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct v2f { float4 pos : SV_POSITION; float4 uv : TEXCOORD0; float3 TtoV0 : TEXCOORD1; float3 TtoV1 : TEXCOORD2; }; uniform float4 _BumpMap_ST; uniform float4 _MainTex_ST; v2f vert (appdata_tan v) { v2f o; o.pos = UnityObjectToClipPos (v.vertex); o.uv.xy = TRANSFORM_TEX(v.texcoord,_MainTex); o.uv.zw = TRANSFORM_TEX(v.texcoord,_BumpMap); TANGENT_SPACE_ROTATION; o.TtoV0 = normalize(mul(rotation, UNITY_MATRIX_IT_MV[0].xyz)); o.TtoV1 = normalize(mul(rotation, UNITY_MATRIX_IT_MV[1].xyz)); return o; } uniform fixed4 _Color; uniform sampler2D _BumpMap; uniform sampler2D _MatCapDiffuse; uniform sampler2D _MainTex; uniform sampler2D _MatCapSpec; uniform fixed _BumpValue; uniform fixed _DiffuseValue; uniform fixed _SpecValue; float4 frag (v2f i) : COLOR { fixed4 c = tex2D(_MainTex, i.uv.xy); float3 normal = UnpackNormal(tex2D(_BumpMap, i.uv.zw)); normal.xy *= _BumpValue; normal.z = sqrt(1.0- saturate(dot(normal.xy ,normal.xy))); normal = normalize(normal); half2 vn; vn.x = dot(i.TtoV0, normal); vn.y = dot(i.TtoV1, normal); vn = vn * 0.5 + 0.5; fixed4 matcapDiffuse = tex2D(_MatCapDiffuse, vn) * _DiffuseValue; fixed4 matcapSpec = tex2D(_MatCapSpec, vn) * _SpecValue; fixed4 finalColor = matcapDiffuse * c * _Color + matcapSpec; return finalColor; } ENDCG } } }
这段代码并不复杂,对比之前两章的说明很容易看懂。
系列链接(未完待续):
《Matcap Shader 详解【1】 - 基础思想与Unity中实现》
《Matcap Shader 详解【2】 - Matcap的固有色贴图与法线贴图》
《Matcap Shader 详解【3】 - 利用Matcap实现基本PBR光照模型》
――――――――这里是分割线――――――――――
С彩蛋!这是我最近用Matcap实现的肤质渲染(以后有可能会讲解哟):