[latex]f (\mathrm{l},\mathrm{v})=f_d(\mathrm{l},\mathrm{v})+\frac{F(\mathrm{v},\mathrm{h})D(\mathrm{h})G(\mathrm{l},\mathrm{v},\mathrm{h})}{4(\mathrm{n}\cdot \mathrm{l})(\mathrm{n}\cdot \mathrm{v})} [/latex]
[latex] L_R=R_F(\theta_i)L_l [/latex]
菲涅尔的计算公式[Schick,1994][latex] R_F(\theta_i)\approx R_F(0^{\circ})+(1-R_F(0^{\circ}))(1-\cos\theta_i)^5 [/latex]
[latex]R_F(0^{\circ})[/latex]表示入射光垂直于表面时菲涅尔的反射率的值,下图展示了菲涅尔在金属和非金属下不同角度的数值[latex] \alpha = Roughness^2 [/latex]
[latex] D(h)=\frac{\alpha ^2}{\pi ((\mathrm{n} \cdot \mathrm{h})^2(\alpha ^2-1)+1)^2} [/latex]
[latex] \alpha = Roughness^2 [/latex]
[latex] G_{GGX}(\mathrm{k})=\frac{2(\mathrm{n} \cdot \mathrm{k})}{(\mathrm{n} \cdot \mathrm{k}) + \sqrt{\alpha^2+(1-\alpha^2)(\mathrm{n} \cdot \mathrm{k})^2 }} [/latex]
[latex] G(\mathrm{l},\mathrm{v},\mathrm{h})=G_1(\mathrm{l})G_1(\mathrm{v}) [/latex]
[latex] f_d(\mathrm{l},\mathrm{v}) = \frac{c_\mathrm{diff}}{\pi} [/latex]
[latex] diffuseColor = baseColor*(1-metallic) [/latex]
[latex] specularColor = mix(0.08*specular,baseColor,metallic) [/latex]
[latex] f=f_d+f_r [/latex]
[latex] =\frac{c_{diff}}{\pi } + \frac{FDG}{4 (n\cdot l) (n \cdot v)} [/latex]
[latex] =\frac{c_{diff}}{\pi } + (F_0+(1-F_0)(1-v\cdot h)^5)(\frac{\alpha ^2}{\pi ((\mathrm{n} \cdot \mathrm{h})^2(\alpha ^2-1)+1)^2})((\frac{2(\mathrm{n} \cdot \mathrm{l})}{(\mathrm{n} \cdot \mathrm{l}) + \sqrt{\alpha^2+(1-\alpha^2)(\mathrm{n} \cdot \mathrm{l})^2 }})(\frac{2(\mathrm{n} \cdot \mathrm{v})}{(\mathrm{n} \cdot \mathrm{v}) + \sqrt{\alpha^2+(1-\alpha^2)(\mathrm{n} \cdot \mathrm{v})^2 }}))\frac{1}{4 (n\cdot l) (n \cdot v)} [/latex]
[latex] =\frac{c_{diff}}{\pi } + (F_0+(1-F_0)(1-v\cdot h)^5)(\frac{\alpha ^2}{\pi ((\mathrm{n} \cdot \mathrm{h})^2(\alpha ^2-1)+1)^2})((\frac{1}{(\mathrm{n} \cdot \mathrm{l}) + \sqrt{\alpha^2+(1-\alpha^2)(\mathrm{n} \cdot \mathrm{l})^2 }})(\frac{1}{(\mathrm{n} \cdot \mathrm{v}) + \sqrt{\alpha^2+(1-\alpha^2)(\mathrm{n} \cdot \mathrm{v})^2 }})) [/latex]
[latex] L_r=f*L_i*\cos\theta =f*\frac{E}{r^2}*(n \cdot l) [/latex]
precision highp float; varying vec3 vNormal;//法线 varying vec3 vPosition;//位置 uniform vec3 uLightPosition;//点灯光位置 uniform vec3 uLightColor;//点灯光颜色 uniform float uLightRadius;//点灯光半径 uniform vec3 uCamPosition;//摄像机位置 uniform vec3 uBaseColor;//基础色 uniform float uRoughness;//粗糙度 uniform float uMetallic;//金属 uniform float uSpecular;//高光 #define PI 3.14159265359 #define invPI 0.3183098861837697 #define invTWO_PI 0.15915494309 #define saturate(x) clamp(x, 0.0, 1.0) vec3 Diffuse_Lambert( vec3 DiffuseColor ) { return DiffuseColor * (1.0 / PI); } float D_GGX( float Roughness, float NoH ) { float m = Roughness * Roughness; float m2 = m * m; float d = ( NoH * m2 - NoH ) * NoH + 1.0; // 2 mad return m2 / ( PI*d*d ); // 4 mul, 1 rcp } float Vis_Smith( float Roughness, float NoV, float NoL ) { float a = Roughness * Roughness ; float a2 = a*a; float Vis_SmithV = NoV + sqrt( NoV * (NoV - NoV * a2) + a2 ); float Vis_SmithL = NoL + sqrt( NoL * (NoL - NoL * a2) + a2 ); return 1.0 / ( Vis_SmithV * Vis_SmithL ); } vec3 F_Schlick( vec3 SpecularColor, float VoH ) { float Fc = pow( 1.0 - VoH, 5.0 ); return Fc + (1.0 - Fc) * SpecularColor; } float getAttenuation( vec3 lightPosition, vec3 vertexPosition, float lightRadius ) { float r = lightRadius; vec3 L = lightPosition - vertexPosition; float dist = length(L); float d = max( dist - r, 0.0 ); L /= dist; float denom = d / r + 1.0; float attenuation = 1.0 / (denom*denom); float cutoff = 0.0052; attenuation = (attenuation - cutoff) / (1.0 - cutoff); attenuation = max(attenuation, 0.0); return attenuation; } void main(void){ vec3 N = normalize( vNormal ); vec3 V = normalize( uCamPosition - vPosition ); vec3 L = normalize( uLightPosition - vPosition ); vec3 H = normalize(V + L); float NoL = saturate( dot( N, L ) ); float NoV = saturate( dot( N, V ) ); float VoH = saturate( dot( V, H ) ); float NoH = saturate( dot( N, H ) ); vec3 diffuseColor = uBaseColor - uBaseColor * uMetallic; vec3 specularColor = mix( vec3( 0.08 * uSpecular ), uBaseColor, uMetallic ); float attenuation = getAttenuation( uLightPosition, vPosition, uLightRadius ); float D = D_GGX(uRoughness,NoH); float Vis = Vis_Smith(uRoughness,NoV,NoL); vec3 F = F_Schlick(specularColor,VoH); vec3 diffusePoint = Diffuse_Lambert(diffuseColor); vec3 specularPoint = D * Vis * F; vec3 colorPoint = uLightColor * ( diffusePoint + specularPoint ) * NoL * attenuation; vec4 infoUv = vec4(colorPoint * 1.0 ,1.0); gl_FragColor = infoUv; }
[latex] F=\int_{a}^{b}f(x)dx [/latex]
对应f的蒙特卡洛积分公式[latex] F^N=\frac{1}{N}\sum_{i=1}^{N}\frac{f(X_i)}{pdf(X_i)} [/latex]
[latex] \int _\Omega L_i(l)f(l,v)\cos\theta _i \mathrm{d}l \approx \frac{1}{N}\sum_{k=1}^{N}\frac{L_i(l_k)f(l_k,v)\cos\theta_l{_k}}{pdf(l_k,v)} [/latex]
[latex] pdf=\frac{D(n \cdot h)}{4(v \cdot h)} [/latex]
float3 ImportanceSampleGGX( float2 Xi, float Roughness , float3 N ) { float a = Roughness * Roughness; float Phi = 2 * PI * Xi.x; float CosTheta = sqrt( (1 - Xi.y) / ( 1 + (a*a - 1) * Xi.y ) ); float SinTheta = sqrt( 1 - CosTheta * CosTheta ); float3 H; H.x = SinTheta * cos( Phi ); H.y = SinTheta * sin( Phi ); H.z = CosTheta; float3 UpVector = abs(N.z) < 0.999 ? float3(0,0,1) : float3(1,0,0); float3 TangentX = normalize( cross( UpVector , N ) ); float3 TangentY = cross( N, TangentX ); // Tangent to world space return TangentX * H.x + TangentY * H.y + N * H.z; } float3 SpecularIBL( float3 SpecularColor , float Roughness , float3 N, float3 V ) { float3 SpecularLighting = 0; const uint NumSamples = 1024; for( uint i = 0; i < NumSamples; i++ ) { float2 Xi = Hammersley( i, NumSamples ); float3 H = ImportanceSampleGGX( Xi, Roughness , N ); float3 L = 2 * dot( V, H ) * H - V; float NoV = saturate( dot( N, V ) ); float NoL = saturate( dot( N, L ) ); float NoH = saturate( dot( N, H ) ); float VoH = saturate( dot( V, H ) ); if( NoL > 0 ) { float3 SampleColor = EnvMap.SampleLevel( EnvMapSampler , L, 0 ).rgb; float G = G_Smith( Roughness , NoV, NoL ); float Fc = pow( 1 - VoH, 5 ); float3 F = (1 - Fc) * SpecularColor + Fc; // Incident light = SampleColor * NoL // Microfacet specular = D*G*F / (4*NoL*NoV) // pdf = D * NoH / (4 * VoH) SpecularLighting += SampleColor * F * G * VoH / (NoH * NoV); } } return SpecularLighting / NumSamples; }
[latex] \frac{1}{N}\sum_{k=1}^{N}\frac{L_i(l_k)f(l_k,v)\cos\theta_l{_k}}{pdf(l_k,v)} \approx \left ( \frac{1}{N}\sum_{k=1}^{N}L_i(l_k) \right )\left ( \frac{1}{N}\sum_{k=1}^{N} \frac{f(l_k,v)\cos\theta_l{_k}}{pdf(l_k,v)}\right ) [/latex]
[latex] \frac{1}{N}\sum_{k=1}^{N}L_i(l_k) \approx \mathrm{Cubemap. Sample (r, mip) } [/latex]
float3 PrefilterEnvMap( float Roughness , float3 R ) { float3 N = R; float3 V = R; float3 PrefilteredColor = 0; const uint NumSamples = 1024; for( uint i = 0; i < NumSamples; i++ ) { float2 Xi = Hammersley( i, NumSamples ); float3 H = ImportanceSampleGGX( Xi, Roughness , N ); float3 L = 2 * dot( V, H ) * H - V; float NoL = saturate( dot( N, L ) ); if( NoL > 0 ) { PrefilteredColor += EnvMap.SampleLevel( EnvMapSampler , L, 0 ).rgb * NoL; TotalWeight += NoL; } } return PrefilteredColor / TotalWeight; }
[latex] \frac{1}{N}\sum_{k=1}^{N} \frac{f(l_k,v)\cos\theta_l{_k}}{pdf(l_k,v)} = \frac{1}{N}\sum_{k=1}^{N} S [/latex]
分解S[latex] S = \frac{DFV \cos\theta }{pdf}[/latex]
对F进行分解处理[latex] F = F_0 +(1-F_0)(1-\cos\theta)^5 = F_0(1-(1-\cos\theta)^5) + (1-\cos\theta)^5 [/latex]
[latex] = F_0(1-(1-v\cdot h)^5) + (1-v\cdot h)^5 = F_0(1-F_c) + F_c [/latex]
其中[latex] F_c = (1-v\cdot h)^5 [/latex] 处理pdf[latex] pdf = \frac{D(n \cdot h)}{4(v \cdot h)}[/latex]
对S最终分解处理[latex] S= \frac{4DFV(n \cdot l)(v \cdot h)}{D(n \cdot h)} = F \frac{4V(n \cdot l)(v \cdot h)}{(n \cdot h)}[/latex]
[latex] = F_0(1-F_c)\frac{4V(n \cdot l)(v \cdot h)}{(n \cdot h)} + F_c\frac{4V(n \cdot l)(v \cdot h)}{(n \cdot h)} [/latex]
此时给定[latex]NOV[/latex]和[latex]Roughness[/latex],[latex]Roughness[/latex]在重要性采样中确定[latex]h[/latex],那么[latex]n、v、h[/latex]都有即可推算出[latex]l[/latex],那么上述式子的结果就只有和[latex]NOV、Roughness、F_0[/latex]相关 通过数学技巧可以写成[latex]F_0*Scale+Offset,Scale和Offset[/latex]这种线性的和Roughness、NoV相关,可以通过查表或者LUT的方式快速计算float Vis_SmithJointApprox(float Roughness, float NoV, float NoL) { float a = Roughness * Roughness; float Vis_SmithV = NoL * (NoV * (1.0 - a) + a); float Vis_SmithL = NoV * (NoL * (1.0 - a) + a); return 0.5 / (Vis_SmithV + Vis_SmithL); } Vector2 IntegrateBRDF(Vector2 Random, float Roughness, float NoV){ Vector3 V; V.x = sqrt(1.0 - NoV * NoV);// sin; V.y = 0; V.z = NoV;// # cos float A = 0; float B = 0; int NumSamples = 128; for (int i = 0;i < NumSamples;i++){ Vector2 E = Hammersley(i, NumSamples, Random); Vector3 H = ImportanceSampleGGX(E, Roughness); Vector3 L = H * (2 * V.dot(H)) - V; float NoL = saturate(L.z); float NoH = saturate(H.z); float VoH = saturate(V.dot(H)); if (NoL > 0){ float Vis = Vis_SmithJointApprox(Roughness, NoV, NoL); //#Incident light = NoL //#pdf = D * NoH / (4 * VoH) //#NoL * Vis / pdf float NoL_Vis_PDF = NoL * Vis * (4 * VoH / NoH); float Fc = pow(1 - VoH, 5); A += (1 - Fc) * NoL_Vis_PDF; B += Fc * NoL_Vis_PDF; } } return Vector2(A / NumSamples, B / NumSamples); }最终生成的图片,运行时直接采样图片
Vector2 EnvBRDFApprox(float Roughness,float NoV){ //# [ Lazarov 2013, "Getting More Physical in Call of Duty: Black Ops II" ] //# Adaptation to fit our G term. Vector4 c0 = Vector4(-1, -0.0275, -0.572, 0.022); Vector4 c1 = Vector4(1, 0.0425, 1.04, -0.04); Vector4 r = c0 * (Roughness) + c1; float a004 = min(r.x * r.x, pow(2, -9.28 * NoV)) * r.x + r.y; Vector2 AB = Vector2(-1.04, 1.04)*(a004) + Vector2(r.z, r.w); return AB; }用上述的方式,拟合出来的图片可以和上图来对比
Vector4 DiffuseIBL(Vector2 Random, Vector3 N, BitmapData &img, bool hdr){ Vector4 DiffuseLighting; unsigned NumSamples = 4096 * 2; //for i in range(NumSamples) : for (unsigned int i = 0; i < NumSamples; i++){ Vector2 E = Hammersley(i, NumSamples, Random); Vector3 C = CosineSampleHemisphere(E); Vector3 L = TangentToWorld(C, N); float NoL = saturate(N.dot(L));; //#print("NoL", NoL); if (NoL > 0){ Vector2 uv2 = pos2uv(L); Vector4 result = img.getPixel32Int(uv2.x * img.width, uv2.y * img.height); //#lambert = DiffuseColor * NoL / PI //#pdf = NoL / PI float e = 1.0; if (hdr) { e = result.w - 128.0; e = pow(2, e); } DiffuseLighting.x += result.x * e; DiffuseLighting.y += result.y * e; DiffuseLighting.z += result.z * e; DiffuseLighting.w += 255; } } float Weight = 1.0 / NumSamples; DiffuseLighting = DiffuseLighting * (Weight); return DiffuseLighting; }