本笔记首发知乎:https://zhuanlan.zhihu.com/p/260922654
时间: 2020-7到12月
修改:20201213
声明:此文为个人学习笔记,自己温习,也乐在交流。如果文章中有侵权,错误,或者有不同的理解,烦请大家多多留言:指正,指教,鄙人会及时纠正。此致崇高敬意!
认知:微表面理论,能量守理论等等(见参考资料)......
PBR系列笔记共三篇,本篇为系列第一篇:基于 Disney BRDF 算法分析
目录
- 前言
- Disney BRDF 源码及对应实现
前言
PBR:前PBR与现PBR。前PBR:不方便使用,渲染不尽人意,非艺术为导向。 现PBR:算法优化,艺术为导向,节省时间成本。在主观(艺术效果)与客观(物理真实)上找到新的平横。牺牲物理,提高艺术效果,又或者是牺牲艺术效果,提升物理准确等。
从迪斯尼发表的演讲与开源,给了业界标准:总是以艺术为导向(艺术至上);核心可控参数简洁;物质的采样数据的共享......等等(慷慨的分享,让人敬佩)。
如彼得-德鲁克大师说的一样,要看荣誉,就要看贡献;看对整个社会的贡献,对整人类群体,和整个文明的贡献。迪斯尼的这次演讲与开源对于物理渲染技术有着划时代的意义。
浅谈的前提知识:色彩空间(见前文《浅谈-色彩空间》常见图形学里面的数据操控,系统笔记,前篇笔记是后篇笔记所需的基础知识)。
https://zhuanlan.zhihu.com/p/203847313zhuanlan.zhihu.com
Disney-BRDF源码
https://github.com/wdas/brdf/blob/master/src/brdfs/disney.brdfgithub.com
analytic
# Copyright Disney Enterprises, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License
# and the following modification to it: Section 6 Trademarks.
# deleted and replaced with:
#
# 6. Trademarks. This License does not grant permission to use the
# trade names, trademarks, service marks, or product names of the
# Licensor and its affiliates, except as required for reproducing
# the content of the NOTICE file.
#
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# variables go here...
# [type] [name] [min val] [max val] [default val]
::begin parameters
color baseColor .82 .67 .16
//固有色
float metallic 0 1 0
//金属度(金属(0 = 电介质,1 =金属,0-1之间半导体)非特殊需要切勿使用半导体调效果
float subsurface 0 1 0
//次表面,控制漫反射形状
float specular 0 1 .5
//镜面反射强度,取代折射率
float roughness 0 1 .5
//粗糙度,控制漫反射和镜面反射
float specularTint 0 1 0
//镜面反射颜色,物理向美术的让步
float anisotropic 0 1 0
//各向异性,高光的纵横比
float sheen 0 1 0
//光泽度,用于布料
float sheenTint 0 1 .5
//光泽颜色,sheen颜色控制
float clearcoat 0 1 0
//清漆,第二层高光
float clearcoatGloss 0 1 1
//清漆光泽度,0 = “缎面(satin)”,1 = “光泽(gloss)”
::end parameters
::begin shader
const float PI = 3.14159265358979323846;
//圆周率
float sqr(float x) { return x*x; }
//平方
float SchlickFresnel(float u)
{//SchlickFresnel 菲涅耳
float m = clamp(1-u, 0, 1);
//反向,并限制在0-1范围内
float m2 = m*m;
//平方
return m2*m2*m; // 5次方
}
float GTR1(float NdotH, float a)
{
if (a >= 1) return 1/PI;
//如果a大于等于1,就返回圆周率倒数
float a2 = a*a;
float t = 1 + (a2-1)*NdotH*NdotH;
return (a2-1) / (PI*log(a2)*t);
}
float GTR2(float NdotH, float a)
{
float a2 = a*a;
float t = 1 + (a2-1)*NdotH*NdotH;
return a2 / (PI * t*t);
}
float GTR2_aniso(float NdotH, float HdotX, float HdotY, float ax, float ay)
{
return 1 / (PI * ax*ay * sqr( sqr(HdotX/ax) + sqr(HdotY/ay) + NdotH*NdotH ));
}
float smithG_GGX(float NdotV, float alphaG)
{
float a = alphaG*alphaG;
float b = NdotV*NdotV;
return 1 / (NdotV + sqrt(a + b - a*b));
}
float smithG_GGX_aniso(float NdotV, float VdotX, float VdotY, float ax, float ay)
{
return 1 / (NdotV + sqrt( sqr(VdotX*ax) + sqr(VdotY*ay) + sqr(NdotV) ));
}
vec3 mon2lin(vec3 x)
{
return vec3(pow(x[0], 2.2), pow(x[1], 2.2), pow(x[2], 2.2));
}
vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y )
{
float NdotL = dot(N,L);
float NdotV = dot(N,V);
if (NdotL < 0 || NdotV < 0) return vec3(0);
//限制条件,如果小于0,则无意义
vec3 H = normalize(L+V);
float NdotH = dot(N,H);
float LdotH = dot(L,H);
vec3 Cdlin = mon2lin(baseColor);
float Cdlum = .3*Cdlin[0] + .6*Cdlin[1] + .1*Cdlin[2]; // luminance approx.
vec3 Ctint = Cdlum > 0 ? Cdlin/Cdlum : vec3(1); // normalize lum. to isolate hue+sat
vec3 Cspec0 = mix(specular*.08*mix(vec3(1), Ctint, specularTint), Cdlin, metallic);
vec3 Csheen = mix(vec3(1), Ctint, sheenTint);
// Diffuse fresnel - 从正常入射的1到放牧的0.5
// 并根据粗糙度混合漫反射回射
float FL = SchlickFresnel(NdotL), FV = SchlickFresnel(NdotV);
float Fd90 = 0.5 + 2 * LdotH*LdotH * roughness;
float Fd = mix(1.0, Fd90, FL) * mix(1.0, Fd90, FV);
//基于各向同性bssrdf的Hanrahan-Krueger brdf逼近
// 1.25刻度用于(大致)保留反照率
// Fss90用于“平整”基于粗糙度的回射
float Fss90 = LdotH*LdotH*roughness;
float Fss = mix(1.0, Fss90, FL) * mix(1.0, Fss90, FV);
float ss = 1.25 * (Fss * (1 / (NdotL + NdotV) - .5) + .5);
// specular
float aspect = sqrt(1-anisotropic*.9);
float ax = max(.001, sqr(roughness)/aspect);
float ay = max(.001, sqr(roughness)*aspect);
float Ds = GTR2_aniso(NdotH, dot(H, X), dot(H, Y), ax, ay);
float FH = SchlickFresnel(LdotH);
vec3 Fs = mix(Cspec0, vec3(1), FH);
float Gs;
Gs = smithG_GGX_aniso(NdotL, dot(L, X), dot(L, Y), ax, ay);
Gs *= smithG_GGX_aniso(NdotV, dot(V, X), dot(V, Y), ax, ay);
// sheen
vec3 Fsheen = FH * sheen * Csheen;
// clearcoat (ior = 1.5 -> F0 = 0.04)
float Dr = GTR1(NdotH, mix(.1,.001,clearcoatGloss));
float Fr = mix(.04, 1.0, FH);
float Gr = smithG_GGX(NdotL, .25) * smithG_GGX(NdotV, .25);
return ((1/PI) * mix(Fd, ss, subsurface)*Cdlin + Fsheen)
* (1-metallic)
+ Gs*Fs*Ds + .25*clearcoat*Gr*Fr*Dr;
}
::end shader
Disney-BRDF分解实现
路线与分析:
第一,数据资源,在方程计算前,接入数据的正确性。
配套的贴图资源要正确。区分Gamma空间Linear空间数据资源。PBR工作流有两个方向,Specular流,和Metallic工作流。近年游戏中主引用Metallic工作流,主要原因是通道的拆分利于迭代(参数更直观,数值计算也更准确),会节省更多时间成本。而本笔记也是基于Metallic流。不同的工作流方向贴图资源也不一样。
第二,Disney Function分析(源码分析):
方程简要说明:基于微观表面和能量守恒等理论。
- diffuse 漫反射,通常假定表示为兰伯特扩散项,是一个恒定不变的值。
- 右边分数形式为specular镜面反射项:
D 是微观表面法线方向分布。镜面反射的高光,高光形状;
F 为菲涅耳反射,系数;
G 为微观表面几何变化,微观几何项,微观表面的阴影和蒙板(遮罩),因子。
θh 是法线 n 和半矢量 h 之间的角度,NdotH;
θd 是 l 与 h 半矢量h之间的“差”角(或对称地为 v 和 h ),LdotH 或者 VdotH;
θl 是 l 向量相对于法线 n 的入射角,NdotL;
θv 是 v 向量相对于法线 n 的入射角,NdotV。
整个函数对于第一眼来说,太过复杂庞大。里面的参数也是复杂的让人不知所措。所以有一个更好的方法:拆分看。(函数为diffuse(漫反射)+specular(镜面反射))
第一部分:diffuse 次表面散射:漫反射项 (文献指出漫反射项是归类到次表面散射项内)
扩散模型。通常假设,并用 Lambert 扩散的一个恒定值来表示(漫反射在所有方向上的强度都是相同的,所以要除以π)所以普通Lambert与BRDF Lambert是不一样的(普通的Lambert lightmode ndl因子是在BRDF diffuse反射等式中的一部分)。闲扯:万物皆有散射,金属也不例外,只是在计算模拟时,宏观上刻意忽略微弱的计算(能量守恒,只有黑洞能完全吸收能量,无反射能量)。
三种基本BRDF diffuse模型:Lambert,Oren-Nayar,Hanrahan-Krueger
Disney diffuse
其中
参数:
baseColor 表面颜色(根据贴图metallic信息计算过后的diffColor,MetallicSetup内)
roughness 粗糙度
cosθl NdotL
cosθv NdotV
cosθd LdotH 或者 VdotH
cosθd 的两种不同写法,一种unity LdotH,迪斯尼 LdotH,一种是 UE VoH。
// Unity UnityStandardBRDF.cginc 文件内
// 注意:迪士尼漫反射必须乘以diffuse Albedo / PI。 这是在此功能之外完成的。
half DisneyDiffuse(half NdotV, half NdotL, half LdotH, half perceptualRoughness)
{
half fd90 = 0.5 + 2 * LdotH * LdotH * perceptualRoughness;
// 两个schlick菲涅耳术语
half lightScatter = (1 + (fd90 - 1) * Pow5(1 - NdotL));
half viewScatter = (1 + (fd90 - 1) * Pow5(1 - NdotV));
return lightScatter * viewScatter;
}
//BRDF1_Unity_PBS
// DisneyDiffuse 项
half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl;
// 在这里乘了 nl
// UE4 BRDF.ush文件内
// [Burley 2012, "Physically-Based Shading at Disney"]
float3 Diffuse_Burley( float3 DiffuseColor, float Roughness, float NoV, float NoL, float VoH )
{
float FD90 = 0.5 + 2 * VoH * VoH * Roughness;
float FdV = 1 + (FD90 - 1) * Pow5( 1 - NoV );
float FdL = 1 + (FD90 - 1) * Pow5( 1 - NoL );
return DiffuseColor * ( (1 / PI) * FdV * FdL );
}
第二部分:公式右侧部分(表面反射:高光反射项)
Unity实现与迪斯尼公式的有些不同:DFG(迪斯尼) ---->> DFV(unity),把G项替换成了V项,但整体框架一样。在unity URP LitShader的直接光照部分里有祥细的注释说明。
2.1、D(θh) 镜面D观测(微观表面法线方向分布)镜面反射的高光,高光形状。
引入并称为Generalized-Trowbridge-Reitz,或者GTR。
参数:
c 缩放系数(scaling constant)
α 粗糙度参数(roughness parameter俗称感性粗糙度,roughness map)在0-1之间
θh 为ndh
γ 指数值
在BRDF中,GTR 2(主镜面波瓣) 先用于基础层材质(Base Material)各向异性和或者各项同性的金属或非金属(俗称下层高光);GTR 1 (次镜面波瓣)次用于上层透明涂层材质(ClearCoat清漆材质,俗称上层高光),是各向同性且非金属的。(两层高光概念,例如车漆:金属材质层+清漆材质层,而清漆的厚度纳入计算,清漆在上金属在下,且两层都有对应不同的高光效果。发展趋势:更复杂的多层高光计算)
// UnityBuiltin UnityStandardBRDF.cginc 文件内
inline float GGXTerm (float NdotH, float roughness)
{ //对应迪斯尼GTR2 下层
float a2 = roughness * roughness;
float d = (NdotH * a2 - NdotH) * NdotH + 1.0f; // 2 mad
return UNITY_INV_PI * a2 / (d * d + 1e-7f); //此功能不适合在Mobile上运行,
//因此epsilon小于一半
}
// UE4 BRDF.ush文件内 Anisotropic GGX
// [Burley 2012, "Physically-Based Shading at Disney"]
float D_GGXaniso( float ax, float ay, float NoH, float3 H, float3 X, float3 Y )
{
float XoH = dot( X, H );
float YoH = dot( Y, H );
float d = XoH*XoH / (ax*ax) + YoH*YoH / (ay*ay) + NoH*NoH;
return 1 / ( PI * ax*ay * d*d );
}
Disney GTR(GTR1是γ=1,GTR2是γ=2)
float GTR1(float NdotH, float a)
{
if (a >= 1) return 1/PI;
//如果a大于等于1,就返回圆周率倒数
float a2 = a*a;
float t = 1 + (a2-1)*NdotH*NdotH;
return (a2-1) / (PI*log(a2)*t);
}
float GTR2(float NdotH, float a)
{
float a2 = a*a;
float t = 1 + (a2-1)*NdotH*NdotH;
return a2 / (PI * t*t);
}
//各项异性版本
float GTR2_aniso(float NdotH, float HdotX, float HdotY, float ax, float ay)
{
return 1 / (PI * ax*ay * sqr( sqr(HdotX/ax) + sqr(HdotY/ay) + NdotH*NdotH ));
}
设定圆的大小做为判断值,来确定需渲染的精度。
2.2、F(θd)镜面F观测(菲涅耳反射)
参数:
F0 镜面反射率,lerp (恒定值, albedo, metallic)所得。恒定值跟数据色彩空间直接关联,色彩空间不一样,值也不一样。
cosθd 的两种不同写法,一种hdl (unity LdotH,迪斯尼 LdotH),一种是hdv(UE VoH)。
闲扯:万物皆有菲涅耳反射。
//公式中使用的是dot(v,h)。而Unity默认传入的是dot(l,h)
//是因为BRDF大量的计算使用的是l,h的点积,而h是l和v的半角向量,所以lh和vh的夹角是一样的。
//不需要多来一个变量。
// UnityBuiltin UnityStandardBRDF.cginc 文件内
inline half Pow5 (half x) //性能优化
{
return x*x * x*x * x;
}
inline half3 FresnelTerm (half3 F0, half cosA) //对应迪斯尼F项
{
half t = Pow5 (1 - cosA); // ala Schlick插值
//公式中使用的是dot(v,h)。而Unity默认传入的是dot(l,h)
//是因为BRDF大量的计算使用的是l,h的点积,而h是l和v的半角向量,所以lh和vh的夹角是一样的。不需要多来一个变量。
return F0 + (1-F0) * t;
}
// UE4 BRDF.ush文件内
// [Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"]
float3 F_Schlick( float3 SpecularColor, float VoH )
{
float Fc = Pow5( 1 - VoH ); // 1 sub, 3 mul
//return Fc + (1 - Fc) * SpecularColor; // 1 add, 3 mad
// Anything less than 2% is physically impossible and is instead considered to be shadowing
return saturate( 50.0 * SpecularColor.g ) * Fc + (1 - Fc) * SpecularColor;
}
float3 F_Fresnel( float3 SpecularColor, float VoH )
{
float3 SpecularColorSqrt = sqrt( clamp( float3(0, 0, 0), float3(0.99, 0.99, 0.99), SpecularColor ) );
float3 n = ( 1 + SpecularColorSqrt ) / ( 1 - SpecularColorSqrt );
float3 g = sqrt( n*n + VoH*VoH - 1 );
return 0.5 * Square( (g - VoH) / (g + VoH) ) * ( 1 + Square( ((g+VoH)*VoH - 1) / ((g-VoH)*VoH + 1) ) );
}
2.3、G(θl,θv)镜面G微观表面几何变化,微观几何项,微观表面的阴影和蒙板(遮罩)(Unity V项等同) 最复杂的项
三种粗糙分布模型:Kurt的经验模型(数据拟合),沃尔特(Walter)的史密斯(Smith)G衍生品,什里克(Schlick)的更简单的衍生品粗糙度作为自由参数
// UnityBuiltin UnityStandardBRDF.cginc 文件内
// 低效果版本 V 项,但性能更好
//注意:可见性术语是Torrance-Sparrow模型的完整形式,其中包括几何术语:V = G /(N.L * N.V)
//这样一来,交换几何图形项变得更加容易,并且有更多的优化空间(也许在 CookTorrance geom项的情况下除外)
// 通用Smith-Schlick能见度术语
inline half SmithVisibilityTerm (half NdotL, half NdotV, half k)
{
half gL = NdotL * (1-k) + k;
half gV = NdotV * (1-k) + k;
return 1.0 / (gL * gV + 1e-5f); //此功能不适合在Mobile上运行,
//因此epsilon小于可以表示为一半的值
}
// Smith-Schlick 是贝克曼的作品
inline half SmithBeckmannVisibilityTerm (half NdotL, half NdotV, half roughness)
{
half c = 0.797884560802865h; // c = sqrt(2 / Pi)
half k = roughness * c;
return SmithVisibilityTerm (NdotL, NdotV, k) * 0.25f; // * 0.25是可见性项的1/4
}
// 高效果版本 V 项
// Ref: http://jcgt.org/published/0003/02/03/paper.pdf 2014年文献
// 可见性项(包括几何函数和配平系数一起)的计算
inline float SmithJointGGXVisibilityTerm (float NdotL, float NdotV, float roughness)
{
#if 0 //默认关闭,备注,这里是 Frostbite的GGX-Smith Joint方案(精确,但是需要开方两次,很不经济)
// 原始配方:
// lambda_v = (-1 + sqrt(a2 * (1 - NdotL2) / NdotL2 + 1)) * 0.5f;
// lambda_l = (-1 + sqrt(a2 * (1 - NdotV2) / NdotV2 + 1)) * 0.5f;
// G = 1 / (1 + lambda_v + lambda_l);
// 重新排序代码以使其更优化
half a = roughness;
half a2 = a * a;
half lambdaV = NdotL * sqrt((-NdotV * a2 + NdotV) * NdotV + a2);
half lambdaL = NdotV * sqrt((-NdotL * a2 + NdotL) * NdotL + a2);
// 简化可见性术语: (2.0f * NdotL * NdotV) / ((4.0f * NdotL * NdotV) * (lambda_v + lambda_l + 1e-5f));
return 0.5f / (lambdaV + lambdaL + 1e-5f); //此功能不适合在Mobile上运行,
//因此epsilon小于可以表示为一半的值
#else
// 走这个部分
// 近似值(简化sqrt,在数学上不正确,但足够接近)
// 这个部分是Respawn Entertainment的 GGX-Smith Joint近似方案
float a = roughness;
float lambdaV = NdotL * (NdotV * (1 - a) + a);
float lambdaL = NdotV * (NdotL * (1 - a) + a);
#if defined(SHADER_API_SWITCH)
return 0.5f / (lambdaV + lambdaL + 1e-4f); //解决hlslcc舍入错误的解决方法
#else
return 0.5f / (lambdaV + lambdaL + 1e-5f);
#endif
#endif
}
// UE
// Smith GGX G项,各项同性版本
float smithG_GGX(float NdotV, float alphaG)
{
float a = alphaG * alphaG;
float b = NdotV * NdotV;
return 1 / (NdotV + sqrt(a + b - a * b));
}
// Smith GGX G项,各项异性版本
// Derived G function for GGX
float smithG_GGX_aniso(float dotVN, float dotVX, float dotVY, float ax, float ay)
{
return 1.0 / (dotVN + sqrt(pow(dotVX * ax, 2.0) + pow(dotVY * ay, 2.0) + pow(dotVN, 2.0)));
}
// GGX清漆几何项
// G GGX function for clearcoat
float G_GGX(float dotVN, float alphag)
{
float a = alphag * alphag;
float b = dotVN * dotVN;
return 1.0 / (dotVN + sqrt(a + b - a * b));
}
2.31、1/4cosθlcosθv微观几何项的衍生项,用来矫正微观表面到宏观表面(整体)数量差异的校正因子
大多数没有以微表面形式具体描述的物理上可行的模型,仍可以用微表面模型描述,因为它们也具有分布函数,菲涅耳因子,以及一些可以考虑为几何阴影因子的其他因子。唯一的真正区别是微表面模型与其他模型之间是否包含显式矫正因子:
该显式矫正因子来自微表面模型的衍生。对于不包含此因素的模型,配平使用:隐含阴影可以通过将模型除以 D 和 F后乘以4 cosθlcosθv来确定因子因素。
Unity V项(等同G项):BRDF函数拟合优化项
见《PBR-BRDF-Disney-Unity-3》Fragment DirectBDRF 函数内的说明。
2.4、布料;2.5、虹彩(虹彩可以给美术一个五颜六色的黑:P,略过,有时间再深入学习)
必读参考资料:
Substance PBR 指导手册
https://link.zhihu.com/?target=https%3A//academy.substance3d.com/courses/pbrguide
八猴 PBR 指导手册
https://marmoset.co/posts/basic-theory-of-physically-based-rendering/marmoset.co
SIGGRAPH 2012年原版文章: 2012-Physically-Based Shading at Disney
https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdfmedia.disneyanimation.com
本人整理过后的中文版:
https://link.zhihu.com/?target=https%3A//github.com/MasterWangdaoyong/Shader-Graph/tree/main/Show/PBR-BRDF-Disney-Unity
SIGGRAPH 2017年原版文章: 2017-Reflectance Models (BRDF)
https://cgg.mff.cuni.cz/~pepca/lectures/pdf/pg2-05-brdf.en.pdfcgg.mff.cuni.cz
闫令琪(闫神):
https://link.zhihu.com/?target=http%3A//games-cn.org/
希望也有学101,102,201,202的朋友讨论沟通:)
个人学习注释笔记地址:
https://github.com/MasterWangdaoyong/Shader-Graph/tree/main/Unity_SourceCodegithub.com
MasterWangdaoyong/Shader-Graph
https://github.com/MasterWangdaoyong/Shader-Graph/tree/main/Unity_SourceCodegithub.com
毛星云:https://zhuanlan.zhihu.com/p/60977923
雨轩:https://zhuanlan.zhihu.com/p/137039291
熊新科: 源码解析 第10章节 第11章节
冯乐乐:入门精要 第18章节