首页 > 代码库 > UnrealEngine4 PBR Shading Model 概述
UnrealEngine4 PBR Shading Model 概述
首先,PBR最大的特点还是引入了微平面概念
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;}
上面是计算Specular间接光的shader 伪代码,1024次对实时的GPU来说还是很难的,需要对公式做拆分
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;}
PrefilterEnvMap生成部分的shader代码。
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;}
最后把第一部分pre-fileter的cubemap和第2部分计算的部分相乘,就都出IBL的最终结果了
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 );}
这里需要注意一点 : EPIC在ppt里提供的shader代码,并不是实际运行的代码,也就是说PrefilterEnvMap和 IntegrateBRDF这两个函数还是ALU方式的实现,而实际上是应该用LUT的方式来替换的。也就是下面的shader代码
half3 EnvBRDF( half3 SpecularColor, half Roughness, half NoV ){ // Importance sampled preintegrated G * F float2 AB = Texture2DSampleLevel( PreIntegratedGF, PreIntegratedGFSampler, float2( NoV, Roughness ), 0 ).rg; // Anything less than 2% is physically impossible and is instead considered to be shadowing float3 GF = SpecularColor * AB.x + saturate( 50.0 * SpecularColor.g ) * AB.y; return GF;}
PreIntegratedGF就是我们前面提到的那张红绿的LUT图,这里最后算得的结果,才是UE4最终选择的近似方案,也是
floatMip=ComputeCubemapMipFromRoughness(GBuffer.Roughness,AmbientCubemapMipAdjust.w );float3SampleColor=TextureCubeSampleLevel(AmbientCubemap,AmbientCubemapSampler, R,Mip).rgb;SpecularContribution+=SampleColor*EnvBRDF(GBuffer.SpecularColor,GBuffer.Roughness,NoV);
再把结果相乘,就得到了最终的Specular的颜色。
float a0( float g, float NoV ){ float t1 = 11.4 * pow( g, 3 ) + 0.1; float t2 = NoV + ( 0.1 – 0.09 * g ); return (1 – exp( -t1 * t2 ) ) * 1.32 * exp2( -10.3 * NoV );}float a1( float g, gloat NoV ){ float t1 = max( 1.336 – 0.486 * g, 1); float t2 = 0.06 + 3.25 * g + 12.8 * pow( g, 3 ); float t3 = NoV + min( 0.125 – 0.1 * g, 0.1 ); return min( t1 – exp2( -t2 * t3 ), 1 );}
并进一步的做优化
float a0f( float g, float NoV ){ float t1 = 0.095 + g * ( 0.6 + 4.19 * g ); float t2 = NoV + 0.025; return t1 * t2 * exp2( 1 – 14 * NoV );}float a1f( float g, float NoV ){ float t1 = 9.5 * g * NoV; return 0.4 + 0.6 * (1 – exp2( -t1 ) );}
rf0(ground truth)是点线,a0是实线,a0f是线段
float a004( float g, float NoV ){ float t = min( 0.475 * g, exp2( -9.28 * NoV ) ); return ( t + 0.0275 ) * g + 0.015;}
ground truth rf0 =0.04是点线 a004是实线
float a1vf( float g ){ return 0.25 * g + 0.75;}
再用a004和a1vf算出新的a0r
float a0r( float g, float NoV ){ return ( a004( g, NoV ) - a1vf( g ) * 0.04 ) / 0.96;}
至此,a0和a1的最终近似版本也完成了,前面我们提到实际计算就是关于rf0的插值运算
这里我们把rf0提出来
rf0 * a1+ (1-rf0) * a0 = rf0 (a1 - a0) + a0 ,那么最后的Environment BRDF近似公式
float3 EnvironmentBRDF( float g, float NoV, float3 rf0 ){ float4 t = float4( 1/0.96, 0.475, (0.0275 - 0.25 * 0.04)/0.96, 0.25 ); t *= float4( g, g, g, g ); t += float4( 0, 0, (0.015 - 0.75 * 0.04)/0.96, 0.75 ); float a0 = t.x * min( t.y, exp2( -9.28 * NoV ) ) + t.z; float a1 = t.w; return saturate( a0 + rf0 * ( a1 - a0 ) );}
OP2的近似方法就先讲到这里了,PPT的公式推导还是太简单,建议还是看notebook的吧,如果有问题可以留言给我讨论
half3 EnvBRDFApprox( half3 SpecularColor, half Roughness, half NoV ){ const half4 c0 = { -1, -0.0275, -0.572, 0.022 }; const half4 c1 = { 1, 0.0425, 1.04, -0.04 }; half4 r = Roughness * c0 + c1; half a004 = min( r.x * r.x, exp2( -9.28 * NoV ) ) * r.x + r.y; half2 AB = half2( -1.04, 1.04 ) * a004 + r.zw; return SpecularColor * AB.x + AB.y;}
材质为非金属时的近似公式
half EnvBRDFApproxNonmetal( half Roughness, half NoV ){ // Same as EnvBRDFApprox( 0.04, Roughness, NoV ) const half2 c0 = { -1, -0.0275 }; const half2 c1 = { 1, 0.0425 }; half2 r = Roughness * c0 + c1; return min( r.x * r.x, exp2( -9.28 * NoV ) ) * r.x + r.y;}
在非金属的情况下,Specular没有颜色而只是一个亮度,这里就假设为0.04了
DiffuseColor+=SpecularColor*0.45;SpecularColor=0;
下面是和使用黑色行动2里的拟合方式的对比效果
UnrealEngine4 PBR Shading Model 概述