https://www.cnblogs.com/dojo-lzz/p/13237686.html
vec3 calculateFinalColor(PBRInfo pbrInputs, vec3 lightColor) { // Calculate the shading terms for the microfacet specular shading model vec3 F = specularReflection(pbrInputs); float G = geometricOcclusion(pbrInputs); float D = microfacetDistribution(pbrInputs); // Calculation of analytical lighting contribution vec3 diffuseContrib = (1.0 - F) * diffuse(pbrInputs); vec3 specContrib = F * G * D / (4.0 * pbrInputs.NdotL * pbrInputs.NdotV); // Obtain final intensity as reflectance (BRDF) scaled by the energy of the light (cosine law) return pbrInputs.NdotL * lightColor * (diffuseContrib + specContrib); }
float3 SpecularIBL( float3 SpecularColor , float Roughness, float3 N, float3 V ) { float3 SpecularLighting = 0; const uint NumSamples = 1024; // 使用了1024个采样点 for( uint i = 0; i < NumSamples; i++ ) { float2 Xi = Hammersley( i, NumSamples ); // 计算一个随机采样序列 float3 H = ImportanceSampleGGX( Xi, Roughness, N ); // 将一个二维采样序列转换成三维空间中的采样方向 // 下面是计算法线、采样方向、视线等各种方向的一堆夹角 // 看上图L是从一个随机采样方向计算出得到的环境入射光源的反方向 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 ) { // 计算环境光源颜色,envMap很可能是立方体贴图 float3 SampleColor = EnvMap.SampleLevel( EnvMapSampler , L, 0 ).rgb; // 下面是计算BRDF的specular部分的G和F,这里并没有计算D,因为在BRDF/pdf过程中,D被消除掉了。 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) // 上面pdf公式可以看出重要性跟粗糙度、法线与采样方向、视线与采样方向相关的。粗糙度是一个经过物理实验测量的值 SpecularLighting += SampleColor * F * G * VoH / (NoH * NoV); } } return SpecularLighting / NumSamples; // 求和再取均值就是蒙特卡罗积分的体现 }
float3 ImportanceSampleGGX( float2 Xi, float Roughness, float3 N ) { float a = Roughness * Roughness; float Phi = 2 * PI * Xi.x; // 水平方向的phi // theta不知道是怎么计算出来的,可能也是根据一个数学理论来计算的,这里可以看到有将粗糙度考虑进去 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 // 下面这个操作的前提是需要微分球的纵轴方向要与该点法向量的轴重合才行,而不是图片中说的镜面反射方向,这里可能是GPU GEM中文作者翻译错误了 // 因为微分球转换的三维坐标与且空间重合,所以这里是将微分三维坐标进行向量分解,最终得到一个三维空间坐标下的单位向量 return TangentX * H.x + TangentY * H.y + N * H.z; }
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; }
float2 IntegrateBRDF( float Roughness, float NoV ) { float3 V; V.x = sqrt( 1.0f - NoV * NoV ); // sin V.y = 0; V.z = NoV; // cos float A = 0; float B = 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( L.z ); float NoH = saturate( H.z ); float VoH = saturate( dot( V, H ) ); if( NoL > 0 ) { float G = G_Smith( Roughness, NoV, NoL ); float G_Vis = G * VoH / (NoH * NoV); float Fc = pow( 1 - VoH, 5 ); A += (1 - Fc) * G_Vis; B += Fc * G_Vis; } } return float2( A, B ) / NumSamples; }
float3 ApproximateSpecularIBL( float3 SpecularColor , float Roughness, float3 N, float3 V ) { float NoV = saturate( dot( N, V ) ); float3 R = 2 * dot( V, N ) * N - V; float3 PrefilteredColor = PrefilterEnvMap( Roughness, R ); float2 EnvBRDF = IntegrateBRDF( Roughness, NoV ); return PrefilteredColor * ( SpecularColor * EnvBRDF.x + EnvBRDF.y ); }