概要
Wake me up when Augest End,始写于八月末,对于Lambert和Blinn来说,Ward光照模型属于一款BRDF光照模型,比起同是BRDF的Cook-Torrance光照模型的镜面反射来说,更难得的是它能调节自己材质各向异性。
虽然既是BRDF又自带各向异性,但Ward光照模型并没有受到Unity程序员的青睐,对比于它实现的效果,它过于复杂的计算导致效率实在令人诟病。所以本文纯属学习性质,工作中不会用这么耗的。本文试写一下Ward模型的高光部分,但是最后并没有遵守BRDF模型中漫反射与镜面反射能量守恒这个观点,只是很简单地把Lambert光照和Ward的高光部分加起来。
本文章出自http://blog.csdn.net/blinkseed
最终效果
右边的正方体为Ward模型,对比于左边的正方体模型为来自《Unity Shader And Effects Cook Book》中自带的各向异性模型,你可以在附带资源里找到,又或者你不想下载,你也可以看看这篇文章来实现各向异性(下面第三张图片为别人渲染Ward结果)

源理论以及公式
文章理论描述
有不少文章专门讲述了Ward的理论与实现,其中比较著名的是Bruce Walter在2005年写的《Notes on the Ward BRDF》,该文章从理论到实现都讲了一遍。我在做Ward实现的时候就主要参考了这篇文章。Ward的BRDF定义用到了half vector的概念,half vector定义为h=(wi+wo)/|wi+wo|,就是入射光向量与出射光向量求和再标准化。按照ward本人的描述,Ward BRDF有两个组成部分,一个是经典的漫反射部分ρd/π,另外一个部分有3个参数ρs,αx与αy。其实ρs就跟phong模型中的镜面反射系数是差不多的,而αx与αy控制了高光在x和y方向上的范围(也可以理解成表面在x和y方向上的粗糙程度)。当αx=αy的时候,该brdf描述的是一个各向同性的反射模型,其效果和phong很类似,反之则描述各向异性反射模型。接下来出场的是一个非常重要的函数:
f函数描述的是该表面上已知入射角向量i,那么得到出射角向量为o的概率是多少。由于BRDF具有对称性,所以反过来也是一样的,即已知出射角向量o,得到入射角向量为i的概率就是这个函数要求的东西。这其中的θi,θo,θh,Φh都是在表面局部坐标系下计算的,见下图:
(图中的向量v与z轴夹角写为θv,Φv则是v在平面xOy上的投影与x方向的夹角)
上面的图片可能描述的并不一定令你觉得清楚,我们试一试使用《Advanced Lighting And Materials With Shaders》中Ward模型的公式来理解上面到底说了什么gui.... ..
Unity Shader实现代码
Properties部分
<span style="font-size:14px;">Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}//主纹理
_Bump ("Bump", 2D) = "bump" {}//法线纹理
_Specular ("Specular", Range(1.0, 10000.0)) = 250.0//高光指数
_SpecularColor ("Specular Color", Color) = (1,1,1,1)//高光颜色
_AlphaX("Alpha X",Range(0.001,1)) = 0.1//X方向的向异指数
_AlphaY("Alpha Y",Range(0.001,1)) = 0.1//Y方向的向异指数
_LightAtten("Light Atten",Float) = 1//高光强度
}</span>
自定义结构体部分
<span style="font-size:14px;">struct a2v
{
float4 vertex : POSITION; // 输入的模型顶点信息
fixed3 normal : NORMAL; // 输入的法线信息
fixed4 texcoord : TEXCOORD0; // 输入的坐标纹理集
fixed4 tangent : TANGENT; // 切线信息
};
struct v2f
{
float4 pos : POSITION; // 输出的顶点信息
fixed2 uv : TEXCOORD0; // 输出的UV信息
fixed3 lightDir: TEXCOORD1; // 输出的光照方向
fixed3 viewDir : TEXCOORD2; // 输出的摄像机方向
fixed4 tangent : TANGENT;
//LIGHTING_COORDS(3,4) // 封装了下面的写法
fixed3 _LightCoord : TEXCOORD3; // 光照坐标
fixed4 _ShadowCoord : TEXCOORD4; // 阴影坐标
};</span>
顶点着色器以及片段着色器
v2f vert(a2v v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
o.tangent = v.tangent;
// 创建一个正切空间的旋转矩阵,TANGENT_SPACE_ROTATION由下面两行组成
//TANGENT_SPACE_ROTATION;
float3 binormal = cross( v.normal, v.tangent.xyz ) * v.tangent.w;
float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal );
// 将顶点的光方向,转到切线空间
// 该顶点在对象坐标中的光方向向量,乘以切线空间旋转矩阵
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));
// 该顶点在摄像机坐标中的方向向量,乘以切线空间旋转矩阵
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex));
// 将照明信息给像素着色器,应该是用于下面片段中光衰弱atten的计算
// TRANSFER_VERTEX_TO_FRAGMENT(o); // 由下面两行组成
// 顶点转到世界坐标,再转到光坐标
o._LightCoord = mul(_LightMatrix0, mul(_Object2World, v.vertex)).xyz;
// 顶点转到世界坐标,再从世界坐标转到阴影坐标
o._ShadowCoord = mul(unity_World2Shadow[0], mul(_Object2World, v.vertex));
// 注:把上面两行代码注释掉,也看不出上面效果,或许我使用的是平行光
return o;
}
fixed4 frag(v2f i) : COLOR
{
// 对主纹理进行采样
fixed4 texColor = tex2D(_MainTex, i.uv);
// 对法线图进行采样
fixed3 norm = normalize(UnpackNormal(tex2D(_Bump, i.uv)));
// 求漫反射
// 公式:漫反射色 = 光颜色*N,L的余弦值(取大于0的),所以夹角越小亮度越小
fixed Diff=dot (norm, normalize(i.lightDir));
//半角向量
fixed3 halfVector = normalize ( i.lightDir + i.viewDir);
//切线方向
fixed3 Tangent = normalize(i.tangent.rgb);
//反射公式
// 光衰弱
fixed atten = LIGHT_ATTENUATION(i);
// 环境光,Unity内置
//fixed3 ambi = UNITY_LIGHTMODEL_AMBIENT.xyz;
//光线的反射向量
fixed3 reflectVector = normalize(2 * dot(halfVector,-i.lightDir) * halfVector - i.lightDir);
fixed NdotH = dot (norm , halfVector);
fixed Sigma = atan(NdotH);
//fixed NdotL = max (0, Diff);
fixed NdotL = Diff;
//fixed NdotV = max (0,dot(norm , normalize(i.viewDir)));
fixed NdotV = dot(norm , normalize(i.viewDir));
//法向量与半角向量的夹角
fixed angle = acos(NdotH);
//法向量与半角向量的夹角的正切值
fixed tanangle = tan(angle);
//计算垂直于反射向量与半角向量的共有平面的向量
fixed3 vertical1 = cross(reflectVector,norm);
//fixed3 vertical1 = cross(norm,reflectVector);
//vertical2垂直于vertical1与法线的共有平面的向量,即是反射向量投射在切线与副法线所共有的平面
fixed3 vertical2 = normalize(cross(norm,vertical1));
//vertical2的cos值
fixed cosTheta = dot(Tangent,vertical2);
fixed cos2Theta = cosTheta * cosTheta;
fixed sin2Theta = 1 - cos2Theta;
//计算高光公式的分子部分
fixed Molecular = exp(-tanangle*tanangle*(cos2Theta/(_AlphaX * _AlphaX)+sin2Theta/(_AlphaY * _AlphaY)));
//计算高光公式的分母部分
fixed Denominator = 4 * 3.14159 * _AlphaX * _AlphaY * sqrt(NdotL * NdotV);
fixed specularpower;
specularpower = pow(saturate(Molecular/Denominator),_Specular);
Diff = saturate (Diff);
// 最终颜色
// 公式: (漫反射 + 反射高光) * 光衰弱 ) * 材质主色
fixed4 cfinal;
cfinal.rgb = (texColor.rgb + (texColor.rgb * specularpower * _LightAtten)) * Diff *_LightColor0.rgb;
cfinal.a = texColor.a;
return cfinal;
}
写在最后
上面代码中由于出现差乘,本来以为交换两个向量会对最终效果出现影响,但最后结果却没有出现:-)。可能是我GPU运行太慢,没看出来。 以上是脚本的所有重要代码,如果懒得码字的话,可以用这个