// Physically Based Rendering // Copyright (c) 2017-2018 MichaƂ Siejak // Pre-integrates Cook-Torrance specular BRDF for varying roughness and viewing directions. // Results are saved into 2D LUT texture in the form of DFG1 and DFG2 split-sum approximation terms, // which act as a scale and bias to F0 (Fresnel reflectance at normal incidence) during rendering. static const float PI = 3.141592; static const float TwoPI = 2 * PI; static const float Epsilon = 0.001; // This program needs larger eps. static const uint NumSamples = 1024; static const float InvNumSamples = 1.0 / float(NumSamples); RWTexture2D LUT : register(u0); // Compute Van der Corput radical inverse // See: http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html float radicalInverse_VdC(uint bits) { bits = (bits << 16u) | (bits >> 16u); bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); return float(bits) * 2.3283064365386963e-10; // / 0x100000000 } // Sample i-th point from Hammersley point set of NumSamples points total. float2 sampleHammersley(uint i) { return float2(i * InvNumSamples, radicalInverse_VdC(i)); } // Importance sample GGX normal distribution function for a fixed roughness value. // This returns normalized half-vector between Li & Lo. // For derivation see: http://blog.tobias-franke.eu/2014/03/30/notes_on_importance_sampling.html float3 sampleGGX(float u1, float u2, float roughness) { float alpha = roughness * roughness; float cosTheta = sqrt((1.0 - u2) / (1.0 + (alpha*alpha - 1.0) * u2)); float sinTheta = sqrt(1.0 - cosTheta*cosTheta); // Trig. identity float phi = TwoPI * u1; // Convert to Cartesian upon return. return float3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta); } // Single term for separable Schlick-GGX below. float gaSchlickG1(float cosTheta, float k) { return cosTheta / (cosTheta * (1.0 - k) + k); } // Schlick-GGX approximation of geometric attenuation function using Smith's method (IBL version). float gaSchlickGGX_IBL(float cosLi, float cosLo, float roughness) { float r = roughness; float k = (r * r) / 2.0; // Epic suggests using this roughness remapping for IBL lighting. return gaSchlickG1(cosLi, k) * gaSchlickG1(cosLo, k); } [numthreads(32, 32, 1)] void main(uint2 ThreadID : SV_DispatchThreadID) { // Get output LUT dimensions. float outputWidth, outputHeight; LUT.GetDimensions(outputWidth, outputHeight); // Get integration parameters. float cosLo = ThreadID.x / outputWidth; float roughness = ThreadID.y / outputHeight; // Make sure viewing angle is non-zero to avoid divisions by zero (and subsequently NaNs). cosLo = max(cosLo, Epsilon); // Derive tangent-space viewing vector from angle to normal (pointing towards +Z in this reference frame). float3 Lo = float3(sqrt(1.0 - cosLo*cosLo), 0.0, cosLo); // We will now pre-integrate Cook-Torrance BRDF for a solid white environment and save results into a 2D LUT. // DFG1 & DFG2 are terms of split-sum approximation of the reflectance integral. // For derivation see: "Moving Frostbite to Physically Based Rendering 3.0", SIGGRAPH 2014, section 4.9.2. float DFG1 = 0; float DFG2 = 0; for(uint i=0; i 0.0) { float G = gaSchlickGGX_IBL(cosLi, cosLo, roughness); float Gv = G * cosLoLh / (cosLh * cosLo); float Fc = pow(1.0 - cosLoLh, 5); DFG1 += (1 - Fc) * Gv; DFG2 += Fc * Gv; } } LUT[ThreadID] = float4(float2(DFG1, DFG2) * InvNumSamples, 0, 1) ; }