Matcap Shader 详解【3】 - 利用Matcap实现基本PBR光照模型

前言

第一篇文章中提到 基础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实现的肤质渲染(以后有可能会讲解哟):



猜你喜欢

转载自blog.csdn.net/weixin_36273312/article/details/80608262