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时总是需要很多数据。一般来说50到200个系数才可以足够完美的表现。最麻烦的是,这些系数并不可以通过经验得来,必须要通过采集,所以设计一个通用的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和ωr。Lobe 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•ωi与N•ωr的乘积。

有条件的朋友还可以这样:跑到室外选择一个场景,用多个曝光度拍摄环景照片,使用Paul Debevec的HDRShop在每个定角度对环境光的颜色和强度进行编码,创建漫反射查找表十字型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 P2和3个纹理坐标<s0,t0> <s1,t1> <s2,t2>:


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

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

这是Fragment Shader:


#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)
{
    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)
{
    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)
{
    vec3 Cwr_local = lobeshape.xyz * toeye;
    //transform lobe peak in world space
    /**//*
    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()
{
    vec4 lobe_shape[NUM_LOBES];
    vec4 lobe_albedo[NUM_LOBES];
    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);
    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:


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()
{
    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

转载于:https://www.cnblogs.com/Jedimaster/archive/2007/04/30/733581.html

sBRDF空间双向反射分布函数完全解析相关推荐

  1. PBR:双向反射分布函数(BRDF)介绍与Cook-Torrance模型的实现

    PBR:双向反射分布函数(BRDF)介绍与Cook-Torrance模型的实现 BRDF简介 再介绍BRDF之前我们要引入渲染方程这个东西: 其中L表示辐射率,其公式为: 它表示了一个拥有辐射强度Φ的 ...

  2. 图形学理论知识 BRDF 双向反射分布函数

    图形学理论知识 BRDF 双向反射分布函数 Bidirectional Reflectance Distribution Function BRDF理论 BRDF表示的是双向反射分布函数(Bidire ...

  3. 计算机图形学基础:双向反射分布函数 BRDF

    文章目录 光照.照明(Illumination) 预备知识 球面坐标(Spherical Coordinate) 立体角(Solid Angle) 投影面积(Foreshortened Area) 光 ...

  4. 图形学笔记(十三)光线追踪3——双向反射分布函数BRDF(反射方程、递归方程)、辐射度量学基础radiometry、立体角、Radiant Energy、Flux、Irrdiance、Radiance

    图形学笔记(十二)光线追踪2--使用AABB包围盒加速光线追踪.空间划分(八叉树.KD树.BSP树).物体划分(BVH加速结构).光线与物体求交 图形学笔记(十四)光线追踪4--蒙特卡洛(Monte ...

  5. Cell Genomics封面|北大吴华君组利用空间多组学技术解析肿瘤内空间异质性(附招聘)...

    Cell Genomics封面|吴华君课题组利用空间多组学技术解析肿瘤内空间异质性 肿瘤内异质性(intra-tumor heterogeneity,ITH)是癌症复发转移的重要驱动因素之一.随着单细 ...

  6. 2022年江西省中职组“网络空间安全”赛项模块A解析

    2022年山西省中职组"网络空间安全"赛项模块A解析 A模块基础设施设置/安全加固(200分) A-1:登录安全加固 A-2:Web安全加固(Web) A-3:流量完整性保护与事件 ...

  7. 2022年江西省中职组“网络空间安全”赛项模块B解析

    2022年江西省中职组"网络空间安全"赛项模块B解析 模块B 网络安全事件响应.数字取证调查和应用安全(400分) B-1:系统漏洞利用与提权 B-2:Linux操作系统渗透测试 ...

  8. 2016-12-29 DNS简介上 域名空间、域、迭代解析、递归解析、DNS服务器

    2016-12-29 DNS简介上 域名空间.域.迭代解析.递归解析.DNS服务器 Domain name system     域名系统(Domain Name System,DNS)是把名字映射到 ...

  9. 深入探索Java反射机制:解析原理与应用

    深入探索Java反射机制:解析原理与应用

最新文章

  1. C语言鸽巢排序pigeonhole sort算法(附完整源码)
  2. postfix 部署ssl后还是25_宝塔面板的邮局管理器Postfix无法启动解决办法
  3. 链队列的基本运算java_链式队列基本操作的实现问题
  4. LeetCode 219. 存在重复元素 II(哈希)
  5. c语言最长公共子序列_序列比对(二十四)——最长公共子序列
  6. 5.7和5.6的mysql_mysql5.6与5.7版本的区别
  7. IBM Machine Learning学习笔记(二)——Supervised Learning: Regression
  8. 《Adobe Illustrator CC经典教程》—第0课0.2节使用Adobe Creative Cloud进行同步设置
  9. 联通积分兑换的Q币怎么兑换到QQ上
  10. cmd长ping记录日志和时间_Ping记录时间的方法
  11. java代码混淆工具
  12. 项目工时估算PERT法
  13. word里怎么在右上角添加标注[1]
  14. 计算机专业人才培养评价意见,谈高职计算机专业人才培养综合评价.pdf
  15. 自我激励的100种方法
  16. Caffe中的损失函数
  17. 怎么删掉计算机云u盘,win10系统删除360云u盘图标的方法
  18. 颜色值透明度的百分数对应十六进制表
  19. C/C++编程:回车符和换行符
  20. java版溺尸掉三叉戟吗_溺尸 - Minecraft Wiki,最详细的官方我的世界百科

热门文章

  1. 单链表的插入删除以及逆转
  2. WinPhone 开发(6)-----获得手机设备的基本信息
  3. jquery图片预加载+自动等比例缩放插件
  4. Err.number错误号和错误说明(一)
  5. 框架通讯契约——接口
  6. 二维数组转datatable的代码
  7. Windows域策略设置 禁止客户端修改IP地址【全域策略生效】
  8. docker基础学习中遇到的一些问题
  9. commit git 删除文件夹_Git-git删除文件夹/文件(删除/不删除本地文件/文件夹)
  10. 线性回归与梯度下降算法