与Intel的古老显卡的战斗----Ward光照模型的高光部分的Unity Shader的编写

7 篇文章 0 订阅
4 篇文章 0 订阅

      概要

   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方向上的粗糙程度)。当αxy的时候,该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运行太慢,没看出来。
      以上是脚本的所有重要代码,如果懒得码字的话,可以用这个

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值