基于物理的渲染(4):Disney 原则
1 背景
2010年提出的PBR光照模型存在包含大量复杂而晦涩的物理参数,不利于美术人员理解、使用和快速产出等问题,2012年disney发表《Physically-based shading at Disney》,提出了Disney 原则的BRDF,在行业内制造了一场基于物理的渲染革命。之后主流游戏引擎都开始从传统的渲染工作流转移到基于物理的渲染工作流。
2 基础概念
2.1 控制BRDF的各项参数
参数 | 解释 | |
---|---|---|
baseColor | 固有色 | 表面颜色,通常由纹理贴图提供 |
subsurface | 次表面 | 使用次表面近似控制漫反射形状 |
metallic | 金属度 | 金属(0 = 电介质,1 =金属)。这是两种不同模型之间的线性混合。金属模型没有漫反射成分,并且还具有等于基础色的着色入射镜面反射 |
specular | 镜面反射强度 | 入射镜面反射量。用于取代折射率 |
specularTint | 镜面反射颜色 | 对美术控制的让步,用于对基础色(baseColor)的入射镜面反射进行颜色控制。掠射镜面反射仍然是非彩色的 |
roughness | 粗糙度 | 表面粗糙度,控制漫反射和镜面反射 |
anisotropic | 各向异性强度 | 各向异性程度。用于控制镜面反射高光的纵横比。(0 =各向同性,1 =最大各向异性。) |
sheen | 光泽度 | 一种额外的掠射分量(grazing component),主要用于布料 |
sheenTint | 光泽颜色 | 对sheen(光泽度)的颜色控制 |
clearcoat | 清漆强度 | 有特殊用途的第二个镜面波瓣(specular lobe) |
clearcoatGloss | 清漆光泽度 | 控制透明涂层光泽度,0 = “缎面(satin)”外观,1 = “光泽(gloss)”外观 |
其他参数保持为常数,变化其中一种参数显示的效果如下图所示:
2.2 Disney BRDF
基于微平面模型、能量守恒原则、菲涅尔反射方程的渲染方程如下所示:
其中
Cook-Torrance BRDF模型既有漫反射项又有高光反射项,在实时渲染中最为常用:
c为表面颜色。Cook-Torrance 高光反射模型如下
其中包含三个函数,D表示法线分布函数(Normal Distribution Function),F表示菲涅尔方程(Fresnel Equation),和G表示几何函数(Geometry Function)。
a. 漫反射项(Disney Diffuse)
Disney表示Lambert漫反射在模型边缘通常太暗,开发了新的经验模型,以在光滑表面的漫反射菲涅尔阴影和粗糙表面之间进行平滑过渡。公式为:
其中,
代码实现如下:
float SchlickFresnel(float u) { float m = clamp(1-u, 0, 1); float m2 = m*m; return m2*m2*m; // pow(m,5) } // Diffuse fresnel - go from 1 at normal incidence to .5 at grazing // and mix in diffuse retro-reflection based on roughness 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);
b. 法线分布项(Specular D)
Disney将Trowbridge-Reitz进行了N次幂的推广,并将其取名为Generalized-Trowbridge-Reitz,GTR:
其中,c为缩放系数,a为粗糙度系数取[0,1],当r=1时,GTR为Berry分布,当r=2时,GTR为Trowbridge-Reitz分布。如下图所示:
实现代码如下:
float GTR1(float NdotH, float a) { if (a >= 1) return 1/PI; 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 )); }
c. 菲涅尔项(Specular F)
采用Schlick Fresnel近似:
其中,常数F0表示垂直入射时的镜面反射率,
d. 几何遮蔽项(Specular G)
Disney参考了 Walter的近似方法,使用Smith GGX导出的G项,并将粗糙度参数进行重映射以减少光泽表面的极端增益,几何项的粗糙度变化更加平滑。
材料的粗糙度越高,微平面相互遮蔽的概率越高,SmithG-GGX几何函数
其中a由粗糙度计算而来:
实现的代码如下:
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) )); }
3 osg中实现
真实感渲染,顶点着色器:
const char* vertCode=R"( #version 330 layout(location=0) in vec3 vPosition; layout(location=1) in vec3 vColor; layout(location=2) in vec3 vNormal; layout(location=3) in vec3 vTexCoord; uniform mat4 osg_ModelViewProjectionMatrix; uniform mat4 osg_ModelViewMatrix; uniform mat4 osg_NormalMatrix; out vec2 texCoord; out vec3 cPos; out vec3 cNorm; void main() { texCoord=vTexCoord; cNorm=osg_NormalMatrix*(vNormal); gl_position=osg_ModelViewProjectionMatrix*vec4(Position,1.0); } )“;
片段着色器:
const char* fragCode=R"( #version 330 const vec3 lightPos=vec3(0.0,0.0,1.0); const vec3 lightColor=vec3(5.6,5.6,5.6); const float PI=3.1415926; uniform sample2D tex; uniform vec3 uBaseColor=vec3(0.82,0.67,0.16); uniform float uMetallic=0.0; uniform float uSubsurface=1.0; uniform float uSpecular=1.0; uniform float uSpecularTint=1.0; uniform float uRoughness=0.11; uniform float uAnisotropic=1.0; uniform float uSheen=0.0; uniform float uSheenTint=0.0; uniform float uClearcoat=1.0; uniform float uClearcoatGloss=0.76; uniform float uHue=0.5; uniform float uSaturation=0.5; uniform float uBrightness=0.5; uniform float alpha=1.0; in vec2 texCoord; in vec3 cPos; in vec3 cNorm; vec3 rgb2hsv(vec3 c) { vec4 k=vec4(0.0,-1.0/3.0,2.0/3.0,-1.0); vec4 p=mix(vec4(c.bg,k.wz),vec4(c.gb,k.xy),step(c.b,c.g)); vec4 q=mix(vec4(p.xyw,c.r),vec4(c.r,p.yzx),step(p.x,c.r)); float d=q.x-min(q.w,q.y); float e=1.e-10; return vec3(abs(q.z+(q.w-q.y)/(6.0*d+e)),d/(q.x+e),q.x); } vec3 hsv2rgb(vec3 c) { vec4 k=vec4(1.0,2.0/3.0,1.0/3.0,3.0); vec3 p=abs(frack(c.xxx+k.xyz)*6.0-k.www); return c.z*mix(k.xxx,clamp(p-k.xxx,0.0,1.0),c.y); } float calcSaturation(float s1,float s2) { if(s2<0.5)s1=s1*(s2*2.0); else if(s2<0.9)s1=s1*((s2-0.5)*4.0+1.0); else s1=s1*((s2-0.9)*32.0+2.6); s1=min(s1,1.0); return s1; } vec3 calcHsl(vec3 hsl,vec3 para) { float hue=hsl.x; float sat=hsl.y; float light=hsl.z; float hue1=para.x; float sat1=para.y; float light1=para.z; hue=mod(hue+(hue1-0.5),1.0); sat=calcSaturation(sat,sat1); light==light+light1*2.0-1.0; light=min(light,1.0); light=max(light,0.0); return vec3(hue,sat,light); } float sqr(float x) { return x*x; } float SchlickFresnel(float u) { float m = clamp(1-u, 0, 1); float m2 = m*m; return m2*m2*m; // pow(m,5) } float GTR1(float NdotH, float a) { if (a >= 1) return 1/PI; 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 calculateTangent(vec3 N) { vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); return normalize(cross(up, N)); } vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y,vec3 albedo) { float NdotL = dot(N,L); float NdotV = dot(N,V); if (NdotL < 0 || NdotV < 0) return vec3(0); vec3 H = normalize(L+V); float NdotH = dot(N,H); float LdotH = dot(L,H); vec3 Cdlin = mon2lin(albedo); 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 - go from 1 at normal incidence to .5 at grazing // and mix in diffuse retro-reflection based on roughness 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); // Based on Hanrahan-Krueger brdf approximation of isotropic bssrdf // 1.25 scale is used to (roughly) preserve albedo // Fss90 used to "flatten" retroreflection based on roughness 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; } void main() { vec3 albedo=texture2D(tex,texCoord); albedo=rgb2hsv(albedo); albedo=calcHsl(albedo); albedo=hsv2rgb(albedo); albedo=mon2lin(albedo); vec3 N=cNorm; vec3 V=vec3(0.0,0.0,1.0); vec3 X=calculateTangent(N); vec3 Y=cross(N,X); vec3 Lo=BRDF(normalize(LightPos-WorldPos),V,N,X,Y,albedo); // Light intensity calculation (assuming a simple point light model) float distance = length(lightPosition - WorldPos); float attenuation = 1.0 / (distance * distance); vec3 radiance = lightColour * attenuation; Lo *= radiance * max(dot(N, normalize(lightPosition - WorldPos)), 0.0); // HDR tonemapping and Gamma correction (basic) Lo = Lo / (Lo + vec3(1.0)); Lo = pow(Lo, vec3(1.0/2.2)); fragColor=vec4(Lo,alpha); } )“;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具