sBRDF空间双向反射分布函数完全解析

SBRDF空间双向反射分布函数解析

 

声明:本文大部分内容基于Ph.D David K. McAllister的博士论文《A GENERALIZED SURFACE APPEARANCE REPRESENTATION FOR COMPUTER GRAPHICS》以及《GPU Gems1》里他的文章。如果有兴趣推荐大家研究博士论文原文,其中关于用相机对材质进行采样的一段非常有趣。我重构他的代码生成了一个简单的命令行工具可以从他的SVB格式中抽取出基本的纹理。他的代码用VC8编译有不少问题,我修复了大部分,主要是在库的链接以及C++的语法上,完全修复用VC6编译一次看看。马上又要考试了,再不看书要挂科了。

 

一、悉数光照模型

       Phong光照模型是基于经验的局部光照模型,拥有漫反射Diffuse以及镜面反射Specular的效果,但是却省略了一切物理光学特性。可是目前的图形API和硬件都是按照Phong光照模型设计的。

       Torrance et al.提出的Microfacet-Based Equation微平面公式模拟了大部分材质表面的特性。具体形式如下:


D
代表任意一种分布函数,包括Gaussian分布函数或者是带指数项Cos函数。

G是几何衰减系数,可以由表面被遮挡的程度或者是阴影来表示。(PS:Ambient Occlusion?

F是菲Fresnel衰减系数。

MBE已经被Debevec et al.修正后用于人类皮肤模拟。

Ward 1992提出的Ward反射模型使用了椭圆型高斯锐化函数Elliptical Gaussian Sharpness Function在物体表面模拟了一个十字形的各项异性高光Anisotropic Highlights,可惜依旧不是基于物理特性的。(PS:有兴趣的朋友可以打开3dsmax等工具的材质编辑器试验一下)

 

二、 Lafortune BRDF 表达式的出现

       BRDF使用若干个基本函数表现物体表面的光学特性。这些函数主要包括,spherical harmonics球面调和函数,spherical wavelets球面波,Zernike多项式。不过这些函数都不是专门为BRDF设计的。所以在使用BRDF时总是需要很多数据。一般来说50200个系数才可以足够完美的表现。最麻烦的是,这些系数并不可以通过经验得来,必须要通过采集,所以设计一个通用的BRDF接口还很难做到。

       下面是McAllister制作的采集装配:(PS:我想应该可以找个ARM单片机以及几个步进电机完成自动采集


      
幸运的是,Lafortune, Foo et al. 1997引入了一个简单而十分有效的BRDF表达式,它的最大特点是用几个Lobe叶片就模拟了BRDF,而且最最重要的是,一切数值都可以简单得用纹理表示,所以现在我们可以简单的使用GPU进行实时渲染。入射向量以及反射向量都被转化到Local Coordinate局部坐标系中计算,避免了只有使用很多系数才能获得满意效果的窘境。还是让我们看一下Lafortune表达式:


       其中ρd是漫反射项,右边的求和式代表各种高光叶片Specular Lobe凸起形状的和,也就是表现了我们注视模型上那一点的时候所看到的高光形状。每一个叶片Lobej都有一个反照率ρs,j以及表达叶片形状Lobe Shape函数sj,还有最重要的入射出射向量ωiωrLobe Shape写成如下矩阵式:

我们当然可以直接写成:

       一切的“魔术”都在控制CxCyCz3个系数上。通过调整这三个系数,我们就可以实现各项异性Anisotropic材质表面的光照效果。

 

三、深入剖析

       我把Ph.D McAllister叙述的sBRDF的计算方法总结归纳了一下。

 

       ρ d 的计算比较繁琐。如果你安装了 NVIDIA SDK 9.5 ,其中的 HDR 演示程序就使用了 HDR Diffuse 贴图。( PS NVIDIA Geforce FX5900 的演示程序 Dawn 里仙女皮肤表面的 Diffuse 项是通过计算法线与入射光夹角的余弦加权平均值计算得到的 )。 Lafortun 与许多其他 Image-Based 基于图像的算法 Pulli, Cohen et al. 1997; Rushmeier, Bernardini et al. 1998 把这一项 当作 bi-directional reflectance samples 双向反射样本的最小值:

fr,k是当前像素处第k反射样本。

可是这仅仅是理论上的成立。真实情况下,用仪器测得的数据是会有错误的,因为我们无法保证样本总是完美的。Marschner (Marschner 1998)提出的方法就是上述被NVIDIA所使用的方法,其中的Weight权等于NωiN•ωr的乘积。

有条件的朋友还可以这样:跑到室外选择一个场景,用多个曝光度拍摄环景照片,使用Paul DebevecHDRShop每个定角度对环境光的颜色和强度进行编码,创建漫反射查找表十字型Cube Map立方体贴图。

样图如下

 

      
    然后让我们来处理求和式。当我们计算得到了 ρ d 后,系统将每个 Specular Lobe 高光叶片形成的高光进行削减,下面的公式表现了这个操作。其中 ω i,k ω r,k 依旧是入射和出射向量:

为了求得所有色彩通道都可以使用的数值,接下来,使用下式计算每一个luminance-weighted average of each fs,k经过亮度权调整过的fs,k的平均值:(PS:这是SONY Trinitron的数据,更加一般的系数是ITU HDTV标准 0.2125, 0.7154, 0.0721以及用于CRT显示器非线性色彩的0.299, 0.587, 0.114

       由于Lafortune表达式是一个非线性函数的叠加,所有的系数必须使用非线性方法进行重估。McAllister使用了Levenberg-Marquardt (L-M) method (Presse, Flannery et al. 1988),简称LM方法。(PS:具体L-M算法代码大家可以不必惊慌,我们可以用Tech-X Corporation的免费TxMath库。其中包含了L-M算法的实现,McAllister也使用了这个库。

 

       使用 L-M 需要一些已知的叶片系数。比如表现一个向前散射的叶片可以让 Cx = -1 Cy=-1 Cz = 1 n = 10 ,向后散射叶片 Cx=1 Cy=1 Cz=1 n=5 。我们也可以使用随机的数值。

       L-M还需要fs,k公式的Jacobian Matrix雅各比矩阵。计算方法如下:使用一个很小的Epsilon调整参数多次计算模型上的每一个点。由于计算时间过长,这部操作还无法变成图形软件中的插件使用与实时计算,只有预先离线计算好。McAllister指出这个优化方法非常适合一个或者两个叶片,更多的就不适合了。如果只有一个或者两个叶片预测的准确度高达90%以上。

       如果我们不使用L-M方法,McAllister还展示了一个只针对于一个叶片的搜索方法。将fs,k写成这样,使用Brent Line Search搜索算法搜索:

       返回使得误差数值最小的那一组Cx Cy Cz n

       啊哈,最后我们终于得到了镜面项:

 

四、渲染

       首先我们需要准备一些材料,包括模型表面的N法向量,T切向量。(PS:曾经看过许多关于切线T的计算,无一例外的都是说可以简单的使用纹理st的方向代替,这对于部分UV贴图的简单模型可能管用,但是对于复杂的有的物体来说肯定是不正确的)。T切向量的计算方式如下,假设一个三角形由3个顶点坐标P0 P1 P23个纹理坐标<s0,t0> <s1,t1> <s2,t2>


      
如何使用Tangent呢?对于NVIDIA Geforce6系列后的显卡你可以尝鲜使用Vertex TextureAti Radeon X1000系列的卡可以使用R2VB,或者更加直接的使用Vertex Attribute传入Shader。至于Binormal你可以直接在Vertex Shader中使用cross叉乘得到

       GPU GEMS 上他展示的是Cg Shader,这里我把它改成了GLSL的。等我完成了一个GL基础库后放上DEMO

       这是Fragment Shader:

ContractedBlock.gif ExpandedBlockStart.gif
#define NUM_LOBES 2
#define SCL (1.0 / (96.0 / 256.0))
#define BIAS (SCL / 2.0)
#define EXSCL 255.0

uniform 
float Expos;
uniform sampler2D Diffus;
uniform sampler2D LobeShape[NUM_LOBES];
uniform sampler2D Albedo[NUM_LOBES];
uniform samplerCube EnvDiffuse;
uniform samplerCube EnvSpec;

varying vec2 TexUV;
//Texcoords
varying vec3 EyeVec;//Vector to Eye in Local Space
varying vec3 TanVec;//Tangent In World Space
varying vec3 NrmVec;//Normal In World Space
varying vec3 BinVec;//Binormal In World Space

vec3 f3texCUBE_RGBE(samplerCube map,vec3 N)
ExpandedBlockStart.gifContractedBlock.gif
{
    vec4 rgbe 
= textureCube(map,N);
    
float f = ldexp(1.0,rgbe.w - 136.0);
    
return vec3(rgbe*f);
    
//return vec3(0.0,0.0,0.0);
}


vec3 f3texCUBE_RGBE_Conv(samplerCube map,vec3 normal,
float N)
ExpandedBlockStart.gifContractedBlock.gif
{
    vec4 rgbe 
= textureCubeLod(map,normal,N);
    
float f = ldexp(1.0,rgbe.w - 136.0);
    
return vec3(rgbe*f);
}


vec3 ApplyLobe(vec3 toeye,vec4 lobeshape,vec3 lobealbedo,vec3 T,vec3 B,vec3 N,samplerCube tex_s)
ExpandedBlockStart.gifContractedBlock.gif
{
    vec3 Cwr_local 
= lobeshape.xyz * toeye;
    
//transform lobe peak in world space
ExpandedSubBlockStart.gifContractedSubBlock.gif
    /**//*
    TBN Matrix
    Tx Ty Tz
    Bx By Bz
    Nx Ny Nz
    
    TBN InverseMatrix just is
    Tx Bx Nx
    Ty By Ny
    Tz Bz Nz
    
    Cwr_world.x = dot( vec3(T.x,B.x,N.x), Cwr_local);
    
*/

    mat3 TBNMatrixInverse 
= mat3(T,B,N);
    vec3 Cwr_world 
= Cwr_local*TBNMatrixInverse;
    
    
float sharpness = lobeshape.w;
    vec3 incrad 
= f3texCUBE_RGBE_Conv(tex_s,Cwr_world,sharpness);
    
    
float lobelen = pow(dot(Cwr_world,Cwr_world),sharpness*0.5);
    
    vec3 irrad 
= incrad*(Cwr_local.z*lobelen);
    vec3 radiance 
= irrad*lobealbedo;
    
return radiance;
    
}


void main()
ExpandedBlockStart.gifContractedBlock.gif
{
    vec4 lobe_shape[NUM_LOBES];
    vec4 lobe_albedo[NUM_LOBES];
ExpandedSubBlockStart.gifContractedSubBlock.gif    
for(int p=0; p<NUM_LOBES; p++){
        lobe_shape[p] 
= texture2D(LobeShape[p],TexUV.st) + vec4(SCL,SCL,SCL,EXSCL) - vec4(BIAS,BIAS,BIAS,0.0);
        lobe_albedo[p] 
= texture2D(Albedo[p],TexUV.st);
    }

    vec4 diff_albedo 
= texture2D(Diffus, TexUV.st);
    vec3 exrad 
= vec3(0.0,0.0,0.0);
ExpandedSubBlockStart.gifContractedSubBlock.gif    
for(int l = 0;l<NUM_LOBES; l++){
        exrad 
+= ApplyLobe(EyeVec,lobe_shape[l],lobe_albedo[l].xyz,TanVec,BinVec,NrmVec,EnvSpec);
    }

    vec3 irrad 
= f3texCUBE_RGBE(EnvDiffuse,NrmVec);
    exrad 
+= (diff_albedo.xyz*irrad);
    vec4 final_color 
= vec4(exrad*Expos,diff_albedo.w);

    
    gl_FragColor 
= final_color;
    
}

  这是Vertex Shader:

ContractedBlock.gif ExpandedBlockStart.gif
attribute vec3 Tangent;

uniform vec3 EyePosInWorld;
uniform mat4 ObjectToWorldMatrix;
uniform mat4 ObjectToWorldInverseTransposeMatrix;
//XXX!!!

varying vec2 TexUV;
//Texcoords
varying vec3 EyeVec;//Vector to Eye in Local Space
varying vec3 TanVec;//Tangent In World Space
varying vec3 NrmVec;//Normal In World Space
varying vec3 BinVec;//Binormal In World Space

void main()
ExpandedBlockStart.gifContractedBlock.gif
{
    TexUV 
= gl_MultiTexCoord0.st;
    vec3 VertexInWorld 
= vec3( gl_Vertex*ObjectToWorldMatrix );
    EyeVec 
= normalize( EyePosInWorld - VertexInWorld );
    NrmVec 
= normalize( vec3(vec4(gl_Normal,1.0)*ObjectToWorldInverseTransposeMatrix) );
    TanVec 
= normalize( vec3(vec4(Tangent,1.0)*ObjectToWorldInverseTransposeMatrix) );
    BinVec 
= cross(NrmVec,TanVec);
    gl_Position 
= ftransform();
    
}

  来自sBRDF网站的样本图片:


原文以及相关的资料大家可以在这里找到:

http://sbrdf.cs.unc.edu/index.html

  我编译好的命令行工具以及一个
SVB样本:


        http://webdisk.cech.com.cn/download/file_share_3252301.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值