# 前言

BRDF (Bidirectional Reflectance Distribution Function) 光照模型,是一种描述入射光线在物体表面反射的一种函数,用于模拟各向异性反射的表面(至于效果,比如大家可以拿着光盘对着灯光看看,或者观察下不锈钢杯子之类的)。因此作为函数实际上指的不是一种,而是一类 (怎么感觉有一种 “... 指的不是一个人,而是一族人的即视感?”)。

在这篇 Blog 中,我已经没有加上 “简析” 两个字儿了。因为,我目前我并不确定自己究竟明不明白它的原理?并且目前也并无推导出公式的能力,简单来说,就目前而言,也只是 “会用” 而已。

所以在这儿,我就简单地介绍并实现两种 BRDF 的经验模型:Bank BRDF 以及 Ward BRDF,这两种均属于经验模型。相信大家都知道经验模型的意义了:看着差不多就好,就不要较真了。

注意 BRDF 计算的结果,只是影响 Specialer,即高光效果。

# Ward BRDF

# 公式

Colorspecialer=ρs×1(LN)(VN)×14παxαy×exp(2×(((HT)αx)2+((HB)αy)2)1+HN)Color_{specialer}=\rho_s \times {1 \over \sqrt{(L \cdot N)(V \cdot N)}} \times {1 \over 4\pi\alpha_x\alpha_y} \times exp\left(-2 \times {(({(H \cdot T) \over \alpha_x})^2 + ({(H \cdot B) \over \alpha_y})^2) \over 1+H \cdot N} \right)

其中:
L:光源方向
N:法线方向
V:视线方向
H:半角向量
T:切线方向
B:垂直于法线与切线的 binormal
ρs\rho_s: 镜面高光强度及其颜色
αx\alpha_xαy\alpha_y 都是由外部进行设置的固定变量,一般取值在 - 1~1 之间

直接使用这个公式进行计算也是可以的,不过由此我们可以发现,ρs\rho_s14παxαy{1 \over 4\pi\alpha_x\alpha_y} 都是固定的常量。因为14παxαy{1 \over 4\pi\alpha_x\alpha_y} 的计算中,只要设置的两个αxαy\alpha_x \alpha_y 固定,那么同样是一个固定的值。所以如果将这两个计算合并,直接全由外部设置,那么就可以进行一些优化:

Colorspecialer=Speccolor×1(LN)(VN)×exp(2×(((HT)αx)2+((HB)αy)2)1+HN)Color_{specialer}=Spec_{color} \times {1 \over \sqrt{(L \cdot N)(V \cdot N)}} \times exp\left(-2 \times {(({(H \cdot T) \over \alpha_x})^2 + ({(H \cdot B) \over \alpha_y})^2) \over 1+H \cdot N} \right)

# 实现

有了公式,实现就比较简单了,只要不是搞错了计算步骤,一般来说都是没问题的。

计算法线切线之类的顶点程序我就不过多解释了,直接看片段程序吧。
首先将顶点程序计算完成,并经插值处理后的向量归一化,然后在片段程序中计算光源、视线方向等:

float3 N = normalize(i.N);
float3 T = normalize(i.T);
float3 B = normalize(cross(N,T));
float3 L = normalize(_WorldSpaceLightPos0);
float3 V = normalize(_WorldSpaceCameraPos-i.wPos);
float3 H = normalize(V+L);

接着采样纹理,并计算环境光及漫反射:

float dotLN = dot(L,N);

fixed4 col = tex2D(_MainTex, i.uv);

float4 ambientColor = UNITY_LIGHTMODEL_AMBIENT*col;
float4 diffuseColor = max(0,dotLN)*col;

然后是高光,也就是主要实现的地方:

float4 specialerColor = float4(0,0,0,0);
if (dotLN > 0)
{
	float dotVN = dot(V,N);
	float dotHT = dot(H,T);
	float dotHB = dot(H,B);
	float dotHN = dot(H,N);

	specialerColor = _SpecColor*(/*1/ */ sqrt(max(0,dotLN*dotVN))*exp(-2 * (pow(dotHT/_AlphaX, 2) + pow(dotHB/_AlphaY, 2)) / (1 + dotHN)));
}

注意这儿限制了计算步骤只出现在 “可照射” 到光源的地方,即光源与法线夹角大于 0。

效果如下:

Ward BRDF

更多详情可参考维基百科

# 完整源码

//Author:CWHISME
//Date:2016.6.9
//Decription:
Shader "CWH/WardBRDF"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_SpecColor("Specialer Color", COLOR) = (1,1,1,1)
		_AlphaX("AlphaX",RANGE(-1,1))=0.1
		_AlphaY("AlphaY", RANGE(-1, 1)) = 0.1
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			Tags{ "LightMode" = "ForwardBase" }
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"
			sampler2D _MainTex;
			float4 _MainTex_ST;
			float4 _SpecColor;
			float _AlphaX;
			float _AlphaY;

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float3 normal : NORMAL;
				float3 tangent : TANGENT;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
				float3 N : TEXCOORD1;
				float3 T : TEXCOORD2;
				float3 wPos:TEXCOORD3;
			};
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				o.N = mul(v.normal,(float3x3)_World2Object);
				o.T = mul((float3x3)_Object2World,v.tangent);
				o.wPos = mul(_Object2World,v.vertex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				float3 N = normalize(i.N);
				float3 T = normalize(i.T);
				float3 B = normalize(cross(N,T));
				float3 L = normalize(_WorldSpaceLightPos0);
				float3 V = normalize(_WorldSpaceCameraPos-i.wPos);
				float3 H = normalize(V+L);

				float dotLN = dot(L,N);

				fixed4 col = tex2D(_MainTex, i.uv);

				float4 ambientColor = UNITY_LIGHTMODEL_AMBIENT*col;
				float4 diffuseColor = max(0,dotLN)*col;

				float4 specialerColor = float4(0,0,0,0);
				if (dotLN > 0)
				{
					float dotVN = dot(V,N);
					float dotHT = dot(H,T);
					float dotHB = dot(H,B);
					float dotHN = dot(H,N);

					specialerColor = _SpecColor*(/*1/ */ sqrt(max(0,dotLN*dotVN))*exp(-2 * (pow(dotHT/_AlphaX, 2) + pow(dotHB/_AlphaY, 2)) / (1 + dotHN)));
				}

				return ambientColor + diffuseColor +specialerColor;
			}
			ENDCG
		}
	}
}

# Bank BRDF

Bank BRDF 是比 Ward BRDF 更为简单的一种经验模型,因为计算量更少,所以性能上更好。当然效果嘛... 就差了那么一点。

# 公式

Colorspecialer=Speccolor×(1(LT)2×1(VT)2(LT)(VT))SpecPowerColor_{specialer}=Spec_{color} \times (\sqrt{1-(L \cdot T)^2} \times \sqrt{1-(V \cdot T)^2}-(L \cdot T)(V \cdot T))^{SpecPower}

其中 SpecColor 是镜面反射颜色,SpecPower 则是镜面高光系数 —— 跟镜面高光的两个参数一样。其它几个参数意义与上面 Ward BRDF 中代表一致,这儿就不进行重复解释了。

# 实现

   			float3 L = normalize(_WorldSpaceLightPos0);
   			float3 N = normalize(i.N);
   			float3 T = normalize(i.T);
   			float3 V = normalize(_WorldSpaceCameraPos - i.wPos);
   			float3 H = normalize(L+V);
   			float dotLN = dot(L,N);

   			float3 ambientColor = UNITY_LIGHTMODEL_AMBIENT*_Color.xyz;
   			float3 diffuseColor = max(0, dotLN)*_Color.xyz;

   			float3 specialerColor = float3(0, 0, 0);
   			if (dotLN>0&&dot(V,N)>0)
   			{
   				float dotLT = dot(L, T);
   				float dotVT = dot(V, T);
   				specialerColor =_LightColor0* _SpecColor.xyz*pow((sqrt(1 - pow(dotLT, 2)*sqrt(1 - pow(dotVT, 2) - dotLT*dotVT))), _SpecialerPower)*max(0,dotLN);
   			}

   			return float4(ambientColor+diffuseColor+specialerColor,1);

效果:

Bank BRDF

不过我实现这个 Bank BRDF 有点问题.... 或者说巨大的问题:当相机转到一定角度时,模型有些部分就全黑了!
如图:

Bank BRDF 问题

即使加上了光源及视线的反面判断,依然同样的效果,网上查了许久,也是无解.... 我也只好先放这儿了。希望啥时候能搞清楚怎么回事儿。

# 完整源码

//Author:CWHISME
//Date:
//Decription:
Shader "CWH/BankBRDF"
{
	Properties
	{
			_Color("Diffuse Color", COLOR) = (1,1,1,1)
			_SpecColor("Specialer Color", COLOR) = (1,1,1,1)
			_SpecialerPower("Specialer Power",FLOAT)=20
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			Tags{"LightMode"="ForwardBase"}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			float4 _Color;
			float4 _SpecColor;
			float _SpecialerPower;
			float4 _LightColor0;

			struct appdata
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float3 tangent : TANGENT;
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
				float3 N:TEXCOORD0;
				float3 T:TEXCOORD1;
				float3 V:TEXCOORD2;
				float3 wPos : TEXCOORD3;
			};
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.N = mul(float4(v.normal,0),_World2Object);
				o.T = mul((float3x3)_Object2World,v.tangent);
				o.wPos = mul(_Object2World,v.vertex);
				o.V = _WorldSpaceCameraPos - o.wPos;
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				float3 L = normalize(_WorldSpaceLightPos0);
				float3 N = normalize(i.N);
				float3 T = normalize(i.T);
				float3 V = normalize(_WorldSpaceCameraPos - i.wPos);
				float dotLN = dot(L,N);

				float3 ambientColor = UNITY_LIGHTMODEL_AMBIENT*_Color.xyz;
				float3 diffuseColor = max(0, dotLN)*_Color.xyz;

				float3 specialerColor = float3(0, 0, 0);
				if (dotLN>0&&dot(V,N)>0)
				{
					float dotLT = dot(L, T);
					float dotVT = dot(V, T);
					specialerColor =_LightColor0* _SpecColor.xyz*pow((sqrt(1 - pow(dotLT, 2)*sqrt(1 - pow(dotVT, 2) - dotLT*dotVT))), _SpecialerPower)*max(0,dotLN);
				}

				return float4(ambientColor+diffuseColor+specialerColor,1);
			}
			ENDCG
		}
	}
}