最近《3D游戏与计算机图形学中的数学方法》看到双向反射分布函数,开头的各种术语把爷整懵了,看完才发现竟然前面只是介绍了原理!!!
果然我还是安安分分地套公式好了。
BRDF函数:
k表示表示入射光中被漫反射出去的部分光能,1-k则表示剩余的光能,被物体吸收或成为镜面反射。可以差不多把1-k看做金属值。
D表示漫反射颜色,我们简单的用兰伯特光照模型计算即可。
重点就在于镜面反射函数
我们使用Cook-Torrance的BRDF光照模型
它的镜面反射分量如下:
F 为Fresnel因子,是反射光线的强度和颜色与入射角度之间的关系函数。
D 为微平面分布函数,返回某一给定方向微平面比例。
G 为微平面的自屏蔽情况。
我们提供了视角方向V,光源方向L,中间向量H=(V+L)/2 ,通过D筛选得到了该点微平面中法向量N=H的比例。(这里的N=H仅是说明微平面的筛选条件,公式中的N指的是该点的法向量,不一定等于H)
Cook-Torrance光照模型将物体表面分解成多个微小平面,即微平面的集合,可以产生真实感很强的镜面反射。每个微平面都被当做符合电磁反射定律的理想反射平面,微平面的斜度反映了表面粗糙度。
所以我们要做的就是选择合适的F、 D、G函数,将它们组合在一起。
我找到了这篇文章,它有总结FDG的各种公式,可以看一下(其中π的倒数算在D中了)。
书中讲解的是Cook-Torrance 的公式:
但是这个公式感觉好复杂。。。。所以我选择使用看起来比较简单的Schlick公式:
关于:
这两个公式中的表示光线方向和法线方向一致时的折射率,你可以直接把它当做一个菲涅尔的调节值。
在公式1中,的目的就是为了得到,可能是F0比较直观吧,表示的是两个介质之间的折射率之比
当光线从法线法线照射时 L·N=1 也就是L·H=1,因为H就是L和V的中间向量,所以V·H=1.
就可以得到
创建一个单独的cginc文件来放置这些函数
#ifndef MY_BRDF_TERM
#define MY_BRDF_TERM
float Fresnel(float F0,float3 viewDir,float3 halfDir)
{//schlick
return F0+(1-F0)*pow((1-dot(viewDir,halfDir)),5);
}
//2019.3.29
//突然发现更多的是这个schlick公式 而且感觉这个比较对
//- -||可能我理解错原式的v h了?
F0+(1-F0)*pow((1-dot(normal,view)),5);
#endif
我们使用书中的 backmann分布函数
m表示粗糙度
m表示二维粗糙度向量,T表示表面的切向量,P是中间向量H到切平面的规范化投影
当mx=my时,该式等价于同向异性公式。
在顶点的切线空间计算微平面分布值会很方便,因为在切线空间中,法向量为(0,0,1),切向量为(1,0,0)公式可简化为:
//Normal Distribution Function
//in Tangent Space
float tangentNDC(float2 roughness,float3 normal,float3 tangent,float3 halfDir)
{
float3 P=normalize(halfDir-dot(halfDir,normal)*normal);
float px2=P.x*P.x;
float hz2=halfDir.z*halfDir.z;
float2 r2=roughness*roughness;
return 0.25/(roughness.x*roughness.y*halfDir.z)*exp((px2/r2.x+(1-px2)/r2.y)*(hz2-1)/hz2);
}
光线在反射过程中会受到其余微平面的遮挡,共有两种情况(图我是偷来的( ̄ε(# ̄),第三种是反射了两次的,不用管)
一是入射光线在射向目标微平面是受到路径中其余微平面的遮挡
二是反射光线在射向观察方向时受到微平面的遮挡。
取最小值作为几何衰减系数
float GeometricShadowing(float3 normal,float3 lightDir,float3 viewDir)
{
float3 halfDir=normalize(lightDir+viewDir);
float3 nh=dot(normal,halfDir);
float3 nl=dot(normal,lightDir);
float3 lh=dot(lightDir,halfDir);
float3 nv=dot(normal,viewDir);
float3 vh=dot(viewDir,halfDir);
float g1=2*nh*nl/lh;
float g2=2*nh*nv/vh;
return min(1,min(g1,g2));
}
Shader "Custom/MyBRDF"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_smoothnessX("smoothnessX", Range(0,0.99)) = 0.5
_smoothnessY ("smoothnessY", Range(0,0.99)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
_Fresnel("Fresnel",Range(0,1))=0.5
}
SubShader
{
Pass
{
Tags{"RenderType"="ForwardBase"}
CGPROGRAM
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "Assets/Shader/Library/MyBRDFterm.cginc"
#pragma vertex vert
#pragma fragment frag
#define PI 3.1415926
struct a2v{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 tangent:TANGENT;
};
struct v2f{
float4 pos:SV_POSITION;
float3 tangentViewDir:TEXCOORD0;
float3 tangentLightDir:TEXCOORD1;
};
float4 _Color;
float _smoothnessX;
float _smoothnessY;
float _Metallic;
float _Fresnel;
v2f vert(a2v v){
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
//次切线
float3 bitangent=cross(v.tangent.xyz,v.normal)*v.tangent.w;
//模型-切线空间矩阵
float3x3 rotation=float3x3(v.tangent.xyz,bitangent,v.normal);
//转换光源方向和观察方向至切线空间
o.tangentLightDir=mul(rotation,ObjSpaceLightDir(v.vertex));
o.tangentViewDir=mul(rotation,ObjSpaceViewDir(v.vertex));
return o;
}
float4 frag(v2f i):SV_Target{
//不使用额外的法线贴图,切线空间下,N(0,0,1)T(1,0,0)
float3 tangentNormal=float3(0,0,1);
float3 tangentT=float3(1,0,0);
float3 tangentViewDir=normalize(i.tangentViewDir);
float3 tangentLightDir=normalize(i.tangentLightDir);
float3 tangentHalfDir=normalize(tangentViewDir+tangentLightDir);
fixed3 diffuse=_LightColor0.rgb*_Color*saturate(dot(tangentNormal,tangentLightDir));
//菲涅尔
float F=Fresnel(_Fresnel,tangentViewDir,tangentHalfDir);
//微平面分布
float2 roughness=float2(1-_smoothnessX,1-_smoothnessY);
float D=tangentNDC(roughness,tangentNormal,tangentT,tangentHalfDir);
//几何衰减
float G=GeometricShadowing(tangentNormal,tangentLightDir,tangentViewDir);
fixed3 specular=_LightColor0.rgb*F*D*G/(PI*dot(tangentNormal,tangentViewDir)*dot(tangentNormal,tangentLightDir));
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*_Color;
float3 finalCol=(1-_Metallic)*diffuse+_Metallic*specular+ambient;
return float4(finalCol,1);
}
ENDCG
}
}
FallBack "Diffuse"
}
参数的可配置范围可能不太合理,另外金属值高时,只会有高光,因为我只简单加了环境光懒得弄环境反射了