# 前言
BRDF (Bidirectional Reflectance Distribution Function) 光照模型,是一种描述入射光线在物体表面反射的一种函数,用于模拟各向异性反射的表面(至于效果,比如大家可以拿着光盘对着灯光看看,或者观察下不锈钢杯子之类的)。因此作为函数实际上指的不是一种,而是一类 (怎么感觉有一种 “... 指的不是一个人,而是一族人的即视感?”)。
在这篇 Blog 中,我已经没有加上 “简析” 两个字儿了。因为,我目前我并不确定自己究竟明不明白它的原理?并且目前也并无推导出公式的能力,简单来说,就目前而言,也只是 “会用” 而已。
所以在这儿,我就简单地介绍并实现两种 BRDF 的经验模型:Bank BRDF 以及 Ward BRDF,这两种均属于经验模型。相信大家都知道经验模型的意义了:看着差不多就好,就不要较真了。
注意 BRDF 计算的结果,只是影响 Specialer,即高光效果。
# Ward BRDF
# 公式
其中:
L:光源方向
N:法线方向
V:视线方向
H:半角向量
T:切线方向
B:垂直于法线与切线的 binormal
: 镜面高光强度及其颜色
与 都是由外部进行设置的固定变量,一般取值在 - 1~1 之间
直接使用这个公式进行计算也是可以的,不过由此我们可以发现, 与 都是固定的常量。因为 的计算中,只要设置的两个 固定,那么同样是一个固定的值。所以如果将这两个计算合并,直接全由外部设置,那么就可以进行一些优化:
# 实现
有了公式,实现就比较简单了,只要不是搞错了计算步骤,一般来说都是没问题的。
计算法线切线之类的顶点程序我就不过多解释了,直接看片段程序吧。
首先将顶点程序计算完成,并经插值处理后的向量归一化,然后在片段程序中计算光源、视线方向等:
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。
效果如下:
更多详情可参考维基百科。
# 完整源码
//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 更为简单的一种经验模型,因为计算量更少,所以性能上更好。当然效果嘛... 就差了那么一点。
# 公式
其中 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 有点问题.... 或者说巨大的问题:当相机转到一定角度时,模型有些部分就全黑了!
如图:
即使加上了光源及视线的反面判断,依然同样的效果,网上查了许久,也是无解.... 我也只好先放这儿了。希望啥时候能搞清楚怎么回事儿。
# 完整源码
//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
}
}
}