A game about forced loneliness, made by TACStudios
at master 703 lines 29 kB view raw
1#ifndef UNITY_IMAGE_BASED_LIGHTING_HLSL_INCLUDED 2#define UNITY_IMAGE_BASED_LIGHTING_HLSL_INCLUDED 3 4#if SHADER_API_MOBILE || SHADER_API_GLES3 || SHADER_API_SWITCH || defined(UNITY_UNIFIED_SHADER_PRECISION_MODEL) 5#pragma warning (disable : 3205) // conversion of larger type to smaller 6#endif 7 8#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonLighting.hlsl" 9#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl" 10#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/BSDF.hlsl" 11#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Random.hlsl" 12#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Sampling/Sampling.hlsl" 13 14#ifndef UNITY_SPECCUBE_LOD_STEPS 15 // This is actuall the last mip index, we generate 7 mips of convolution 16 #define UNITY_SPECCUBE_LOD_STEPS 6 17#endif 18 19//----------------------------------------------------------------------------- 20// Util image based lighting 21//----------------------------------------------------------------------------- 22 23// The *approximated* version of the non-linear remapping. It works by 24// approximating the cone of the specular lobe, and then computing the MIP map level 25// which (approximately) covers the footprint of the lobe with a single texel. 26// Improves the perceptual roughness distribution. 27real PerceptualRoughnessToMipmapLevel(real perceptualRoughness, uint maxMipLevel) 28{ 29 perceptualRoughness = perceptualRoughness * (1.7 - 0.7 * perceptualRoughness); 30 31 return perceptualRoughness * maxMipLevel; 32} 33 34real PerceptualRoughnessToMipmapLevel(real perceptualRoughness) 35{ 36 return PerceptualRoughnessToMipmapLevel(perceptualRoughness, UNITY_SPECCUBE_LOD_STEPS); 37} 38 39// The *accurate* version of the non-linear remapping. It works by 40// approximating the cone of the specular lobe, and then computing the MIP map level 41// which (approximately) covers the footprint of the lobe with a single texel. 42// Improves the perceptual roughness distribution and adds reflection (contact) hardening. 43// TODO: optimize! 44real PerceptualRoughnessToMipmapLevel(real perceptualRoughness, real NdotR) 45{ 46 real m = PerceptualRoughnessToRoughness(perceptualRoughness); 47 48 // Remap to spec power. See eq. 21 in --> https://dl.dropboxusercontent.com/u/55891920/papers/mm_brdf.pdf 49 real n = (2.0 / max(REAL_EPS, m * m)) - 2.0; 50 51 // Remap from n_dot_h formulation to n_dot_r. See section "Pre-convolved Cube Maps vs Path Tracers" --> https://s3.amazonaws.com/docs.knaldtech.com/knald/1.0.0/lys_power_drops.html 52 n /= (4.0 * max(NdotR, REAL_EPS)); 53 54 // remap back to square root of real roughness (0.25 include both the sqrt root of the conversion and sqrt for going from roughness to perceptualRoughness) 55 perceptualRoughness = pow(2.0 / (n + 2.0), 0.25); 56 57 return perceptualRoughness * UNITY_SPECCUBE_LOD_STEPS; 58} 59 60// The inverse of the *approximated* version of perceptualRoughnessToMipmapLevel(). 61real MipmapLevelToPerceptualRoughness(real mipmapLevel) 62{ 63 real perceptualRoughness = saturate(mipmapLevel / UNITY_SPECCUBE_LOD_STEPS); 64 65 return saturate(1.7 / 1.4 - sqrt(2.89 / 1.96 - (2.8 / 1.96) * perceptualRoughness)); 66} 67 68//----------------------------------------------------------------------------- 69// Anisotropic image based lighting 70//----------------------------------------------------------------------------- 71 72// T is the fiber axis (hair strand direction, root to tip). 73float3 ComputeViewFacingNormal(float3 V, float3 T) 74{ 75 return Orthonormalize(V, T); 76} 77 78// Fake anisotropy by distorting the normal (non-negative anisotropy values only). 79// The grain direction (e.g. hair or brush direction) is assumed to be orthogonal to N. 80// Anisotropic ratio (0->no isotropic; 1->full anisotropy in tangent direction) 81real3 GetAnisotropicModifiedNormal(real3 grainDir, real3 N, real3 V, real anisotropy) 82{ 83 real3 grainNormal = ComputeViewFacingNormal(V, grainDir); 84 return normalize(lerp(N, grainNormal, anisotropy)); 85} 86 87// For GGX aniso and IBL we have done an empirical (eye balled) approximation compare to the reference. 88// We use a single fetch, and we stretch the normal to use based on various criteria. 89// result are far away from the reference but better than nothing 90// Anisotropic ratio (0->no isotropic; 1->full anisotropy in tangent direction) - positive use bitangentWS - negative use tangentWS 91// Note: returned iblPerceptualRoughness shouldn't be use for sampling FGD texture in a pre-integration 92void GetGGXAnisotropicModifiedNormalAndRoughness(real3 bitangentWS, real3 tangentWS, real3 N, real3 V, real anisotropy, real perceptualRoughness, out real3 iblN, out real iblPerceptualRoughness) 93{ 94 // For positive anisotropy values: tangent = highlight stretch (anisotropy) direction, bitangent = grain (brush) direction. 95 float3 grainDirWS = (anisotropy >= 0.0) ? bitangentWS : tangentWS; 96 // Reduce stretching depends on the perceptual roughness 97 float stretch = abs(anisotropy) * saturate(1.5 * sqrt(perceptualRoughness)); 98 // NOTE: If we follow the theory we should use the modified normal for the different calculation implying a normal (like NdotV) 99 // However modified normal is just a hack. The goal is just to stretch a cubemap, no accuracy here. Let's save performance instead. 100 iblN = GetAnisotropicModifiedNormal(grainDirWS, N, V, stretch); 101 iblPerceptualRoughness = perceptualRoughness * saturate(1.2 - abs(anisotropy)); 102} 103 104// Ref: "Moving Frostbite to PBR", p. 69. 105real3 GetSpecularDominantDir(real3 N, real3 R, real perceptualRoughness, real NdotV) 106{ 107 real p = perceptualRoughness; 108 real a = 1.0 - p * p; 109 real s = sqrt(a); 110 111#ifdef USE_FB_DSD 112 // This is the original formulation. 113 real lerpFactor = (s + p * p) * a; 114#else 115 // TODO: tweak this further to achieve a closer match to the reference. 116 real lerpFactor = (s + p * p) * saturate(a * a + lerp(0.0, a, NdotV * NdotV)); 117#endif 118 119 // The result is not normalized as we fetch in a cubemap 120 return lerp(N, R, lerpFactor); 121} 122 123// ---------------------------------------------------------------------------- 124// Importance sampling BSDF functions 125// ---------------------------------------------------------------------------- 126 127void SampleGGXDir(real2 u, 128 real3 V, 129 real3x3 localToWorld, 130 real roughness, 131 out real3 L, 132 out real NdotL, 133 out real NdotH, 134 out real VdotH, 135 bool VeqN = false) 136{ 137 // GGX NDF sampling 138 real cosTheta = sqrt(SafeDiv(1.0 - u.x, 1.0 + (roughness * roughness - 1.0) * u.x)); 139 real phi = TWO_PI * u.y; 140 141 real3 localH = SphericalToCartesian(phi, cosTheta); 142 143 NdotH = cosTheta; 144 145 real3 localV; 146 147 if (VeqN) 148 { 149 // localV == localN 150 localV = real3(0.0, 0.0, 1.0); 151 VdotH = NdotH; 152 } 153 else 154 { 155 localV = mul(V, transpose(localToWorld)); 156 VdotH = saturate(dot(localV, localH)); 157 } 158 159 // Compute { localL = reflect(-localV, localH) } 160 real3 localL = -localV + 2.0 * VdotH * localH; 161 NdotL = localL.z; 162 163 L = mul(localL, localToWorld); 164} 165 166// ref: http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf p26 167void SampleAnisoGGXDir(real2 u, 168 real3 V, 169 real3 N, 170 real3 tangentX, 171 real3 tangentY, 172 real roughnessT, 173 real roughnessB, 174 out real3 H, 175 out real3 L) 176{ 177 // AnisoGGX NDF sampling 178 H = sqrt(u.x / (1.0 - u.x)) * (roughnessT * cos(TWO_PI * u.y) * tangentX + roughnessB * sin(TWO_PI * u.y) * tangentY) + N; 179 H = normalize(H); 180 181 // Convert sample from half angle to incident angle 182 L = 2.0 * saturate(dot(V, H)) * H - V; 183} 184 185// Adapted from: "Sampling the GGX Distribution of Visible Normals", by E. Heitz 186// http://jcgt.org/published/0007/04/01/paper.pdf 187void SampleAnisoGGXVisibleNormal(float2 u, 188 float3 V, 189 float3x3 localToWorld, 190 float roughnessX, 191 float roughnessY, 192 out float3 localV, 193 out float3 localH, 194 out float VdotH) 195{ 196 localV = mul(V, transpose(localToWorld)); 197 198 // Construct an orthonormal basis around the stretched view direction 199 float3x3 viewToLocal; 200 viewToLocal[2] = normalize(float3(roughnessX * localV.x, roughnessY * localV.y, localV.z)); 201 viewToLocal[0] = (viewToLocal[2].z < 0.9999) ? normalize(cross(float3(0, 0, 1), viewToLocal[2])) : float3(1, 0, 0); 202 viewToLocal[1] = cross(viewToLocal[2], viewToLocal[0]); 203 204 // Compute a sample point with polar coordinates (r, phi) 205 float r = sqrt(u.x); 206 float phi = 2.0 * PI * u.y; 207 float t1 = r * cos(phi); 208 float t2 = r * sin(phi); 209 float s = 0.5 * (1.0 + viewToLocal[2].z); 210 t2 = (1.0 - s) * sqrt(1.0 - t1 * t1) + s * t2; 211 212 // Reproject onto hemisphere 213 localH = t1 * viewToLocal[0] + t2 * viewToLocal[1] + sqrt(max(0.0, 1.0 - t1 * t1 - t2 * t2)) * viewToLocal[2]; 214 215 // Transform the normal back to the ellipsoid configuration 216 localH = normalize(float3(roughnessX * localH.x, roughnessY * localH.y, max(0.0, localH.z))); 217 218 VdotH = saturate(dot(localV, localH)); 219} 220 221// GGX vsible normal sampling, isotropic variant 222void SampleGGXVisibleNormal(float2 u, 223 float3 V, 224 float3x3 localToWorld, 225 float roughness, 226 out float3 localV, 227 out float3 localH, 228 out float VdotH) 229{ 230 SampleAnisoGGXVisibleNormal(u, V, localToWorld, roughness, roughness, localV, localH, VdotH); 231} 232 233// weightOverPdf return the weight (without the diffuseAlbedo term) over pdf. diffuseAlbedo term must be apply by the caller. 234void ImportanceSampleLambert(real2 u, 235 real3x3 localToWorld, 236 out real3 L, 237 out real NdotL, 238 out real weightOverPdf) 239{ 240#if 0 241 real3 localL = SampleHemisphereCosine(u.x, u.y); 242 243 NdotL = localL.z; 244 245 L = mul(localL, localToWorld); 246#else 247 real3 N = localToWorld[2]; 248 249 L = SampleHemisphereCosine(u.x, u.y, N); 250 NdotL = saturate(dot(N, L)); 251#endif 252 253 // Importance sampling weight for each sample 254 // pdf = N.L / PI 255 // weight = fr * (N.L) with fr = diffuseAlbedo / PI 256 // weight over pdf is: 257 // weightOverPdf = (diffuseAlbedo / PI) * (N.L) / (N.L / PI) 258 // weightOverPdf = diffuseAlbedo 259 // diffuseAlbedo is apply outside the function 260 261 weightOverPdf = 1.0; 262} 263 264// weightOverPdf return the weight (without the Fresnel term) over pdf. Fresnel term must be apply by the caller. 265void ImportanceSampleGGX(real2 u, 266 real3 V, 267 real3x3 localToWorld, 268 real roughness, 269 real NdotV, 270 out real3 L, 271 out real VdotH, 272 out real NdotL, 273 out real weightOverPdf) 274{ 275 real NdotH; 276 SampleGGXDir(u, V, localToWorld, roughness, L, NdotL, NdotH, VdotH); 277 278 // Importance sampling weight for each sample 279 // pdf = D(H) * (N.H) / (4 * (L.H)) 280 // weight = fr * (N.L) with fr = F(H) * G(V, L) * D(H) / (4 * (N.L) * (N.V)) 281 // weight over pdf is: 282 // weightOverPdf = F(H) * G(V, L) * (L.H) / ((N.H) * (N.V)) 283 // weightOverPdf = F(H) * 4 * (N.L) * V(V, L) * (L.H) / (N.H) with V(V, L) = G(V, L) / (4 * (N.L) * (N.V)) 284 // Remind (L.H) == (V.H) 285 // F is apply outside the function 286 287 real Vis = V_SmithJointGGX(NdotL, NdotV, roughness); 288 weightOverPdf = 4.0 * Vis * NdotL * VdotH / NdotH; 289} 290 291// weightOverPdf return the weight (without the Fresnel term) over pdf. Fresnel term must be apply by the caller. 292void ImportanceSampleAnisoGGX(real2 u, 293 real3 V, 294 real3x3 localToWorld, 295 real roughnessT, 296 real roughnessB, 297 real NdotV, 298 out real3 L, 299 out real VdotH, 300 out real NdotL, 301 out real weightOverPdf) 302{ 303 real3 tangentX = localToWorld[0]; 304 real3 tangentY = localToWorld[1]; 305 real3 N = localToWorld[2]; 306 307 real3 H; 308 SampleAnisoGGXDir(u, V, N, tangentX, tangentY, roughnessT, roughnessB, H, L); 309 310 real NdotH = saturate(dot(N, H)); 311 // Note: since L and V are symmetric around H, LdotH == VdotH 312 VdotH = saturate(dot(V, H)); 313 NdotL = saturate(dot(N, L)); 314 315 // Importance sampling weight for each sample 316 // pdf = D(H) * (N.H) / (4 * (L.H)) 317 // weight = fr * (N.L) with fr = F(H) * G(V, L) * D(H) / (4 * (N.L) * (N.V)) 318 // weight over pdf is: 319 // weightOverPdf = F(H) * G(V, L) * (L.H) / ((N.H) * (N.V)) 320 // weightOverPdf = F(H) * 4 * (N.L) * V(V, L) * (L.H) / (N.H) with V(V, L) = G(V, L) / (4 * (N.L) * (N.V)) 321 // Remind (L.H) == (V.H) 322 // F is apply outside the function 323 324 // For anisotropy we must not saturate these values 325 real TdotV = dot(tangentX, V); 326 real BdotV = dot(tangentY, V); 327 real TdotL = dot(tangentX, L); 328 real BdotL = dot(tangentY, L); 329 330 real Vis = V_SmithJointGGXAniso(TdotV, BdotV, NdotV, TdotL, BdotL, NdotL, roughnessT, roughnessB); 331 weightOverPdf = 4.0 * Vis * NdotL * VdotH / NdotH; 332} 333 334// ---------------------------------------------------------------------------- 335// Pre-integration 336// ---------------------------------------------------------------------------- 337 338// Ref: Listing 18 in "Moving Frostbite to PBR" + https://knarkowicz.wordpress.com/2014/12/27/analytical-dfg-term-for-ibl/ 339real4 IntegrateGGXAndDisneyDiffuseFGD(real NdotV, real roughness, uint sampleCount = 4096) 340{ 341 // Note that our LUT covers the full [0, 1] range. 342 // Therefore, we don't really want to clamp NdotV here (else the lerp slope is wrong). 343 // However, if NdotV is 0, the integral is 0, so that's not what we want, either. 344 // Our runtime NdotV bias is quite large, so we use a smaller one here instead. 345 NdotV = max(NdotV, REAL_EPS); 346 real3 V = real3(sqrt(1 - NdotV * NdotV), 0, NdotV); 347 real4 acc = real4(0.0, 0.0, 0.0, 0.0); 348 349 real3x3 localToWorld = k_identity3x3; 350 351 for (uint i = 0; i < sampleCount; ++i) 352 { 353 real2 u = Hammersley2d(i, sampleCount); 354 355 real VdotH; 356 real NdotL; 357 real weightOverPdf; 358 359 real3 L; // Unused 360 ImportanceSampleGGX(u, V, localToWorld, roughness, NdotV, 361 L, VdotH, NdotL, weightOverPdf); 362 363 if (NdotL > 0.0) 364 { 365 // Integral{BSDF * <N,L> dw} = 366 // Integral{(F0 + (1 - F0) * (1 - <V,H>)^5) * (BSDF / F) * <N,L> dw} = 367 // (1 - F0) * Integral{(1 - <V,H>)^5 * (BSDF / F) * <N,L> dw} + F0 * Integral{(BSDF / F) * <N,L> dw}= 368 // (1 - F0) * x + F0 * y = lerp(x, y, F0) 369 370 acc.x += weightOverPdf * pow(1 - VdotH, 5); 371 acc.y += weightOverPdf; 372 } 373 374 // for Disney we still use a Cosine importance sampling, true Disney importance sampling imply a look up table 375 ImportanceSampleLambert(u, localToWorld, L, NdotL, weightOverPdf); 376 377 if (NdotL > 0.0) 378 { 379 real LdotV = dot(L, V); 380 real disneyDiffuse = DisneyDiffuseNoPI(NdotV, NdotL, LdotV, RoughnessToPerceptualRoughness(roughness)); 381 382 acc.z += disneyDiffuse * weightOverPdf; 383 } 384 } 385 386 acc /= sampleCount; 387 388 // Remap from the [0.5, 1.5] to the [0, 1] range. 389 acc.z -= 0.5; 390 391 return acc; 392} 393 394uint GetIBLRuntimeFilterSampleCount(uint mipLevel) 395{ 396 uint sampleCount = 0; 397 398 switch (mipLevel) 399 { 400 case 1: sampleCount = 21; break; 401 case 2: sampleCount = 34; break; 402#if defined(SHADER_API_MOBILE) || defined(SHADER_API_SWITCH) 403 case 3: sampleCount = 34; break; 404 case 4: sampleCount = 34; break; 405 case 5: sampleCount = 34; break; 406 case 6: sampleCount = 34; break; // UNITY_SPECCUBE_LOD_STEPS 407#else 408 case 3: sampleCount = 55; break; 409 case 4: sampleCount = 89; break; 410 case 5: sampleCount = 89; break; 411 case 6: sampleCount = 89; break; // UNITY_SPECCUBE_LOD_STEPS 412#endif 413 } 414 415 return sampleCount; 416} 417 418// Ref: Listing 19 in "Moving Frostbite to PBR" 419float4 IntegrateLD(TEXTURECUBE_PARAM(tex, sampl), 420 TEXTURE2D(ggxIblSamples), 421 real3 V, 422 real3 N, 423 real roughness, 424 real index, // Current MIP level minus one 425 real invOmegaP, 426 uint sampleCount, // Must be a Fibonacci number 427 bool prefilter, 428 bool usePrecomputedSamples) 429{ 430 real3x3 localToWorld = GetLocalFrame(N); 431 432#ifndef USE_KARIS_APPROXIMATION 433 real NdotV = 1; // N == V 434 real partLambdaV = GetSmithJointGGXPartLambdaV(NdotV, roughness); 435#endif 436 437 float3 lightInt = float3(0.0, 0.0, 0.0); 438 float cbsdfInt = 0.0; 439 440 for (uint i = 0; i < sampleCount; ++i) 441 { 442 real3 L; 443 real NdotL, NdotH, LdotH; 444 445 if (usePrecomputedSamples) 446 { 447 // Performance warning: using a texture LUT will generate a vector load, 448 // which increases both the VGPR pressure and the workload of the 449 // texture unit. A better solution here is to load from a Constant, Raw 450 // or Structured buffer, or perhaps even declare all the constants in an 451 // HLSL header to allow the compiler to inline everything. 452 real3 localL = LOAD_TEXTURE2D(ggxIblSamples, uint2(i, index)).xyz; 453 454 L = mul(localL, localToWorld); 455 NdotL = localL.z; 456 LdotH = sqrt(0.5 + 0.5 * NdotL); 457 } 458 else 459 { 460 real2 u = Fibonacci2d(i, sampleCount); 461 462 // Note: if (N == V), all of the microsurface normals are visible. 463 SampleGGXDir(u, V, localToWorld, roughness, L, NdotL, NdotH, LdotH, true); 464 465 if (NdotL <= 0) continue; // Note that some samples will have 0 contribution 466 } 467 468 real mipLevel; 469 470 if (!prefilter) // BRDF importance sampling 471 { 472 mipLevel = 0; 473 } 474 else // Prefiltered BRDF importance sampling 475 { 476 // Use lower MIP-map levels for fetching samples with low probabilities 477 // in order to reduce the variance. 478 // Ref: http://http.developer.nvidia.com/GPUGems3/gpugems3_ch20.html 479 // 480 // - OmegaS: Solid angle associated with the sample 481 // - OmegaP: Solid angle associated with the texel of the cubemap 482 483 real omegaS; 484 485 if (usePrecomputedSamples) 486 { 487 omegaS = LOAD_TEXTURE2D(ggxIblSamples, uint2(i, index)).w; 488 } 489 else 490 { 491 // real PDF = D * NdotH * Jacobian, where Jacobian = 1 / (4 * LdotH). 492 // Since (N == V), NdotH == LdotH. 493 real pdf = 0.25 * D_GGX(NdotH, roughness); 494 // TODO: improve the accuracy of the sample's solid angle fit for GGX. 495 omegaS = rcp(sampleCount) * rcp(pdf); 496 } 497 498 // 'invOmegaP' is precomputed on CPU and provided as a parameter to the function. 499 // real omegaP = FOUR_PI / (6.0 * cubemapWidth * cubemapWidth); 500 const real mipBias = roughness; 501 mipLevel = 0.5 * log2(omegaS * invOmegaP) + mipBias; 502 } 503 504 // TODO: use a Gaussian-like filter to generate the MIP pyramid. 505 real3 val = SAMPLE_TEXTURECUBE_LOD(tex, sampl, L, mipLevel).rgb; 506 507 // The goal of this function is to use Monte-Carlo integration to find 508 // X = Integral{Radiance(L) * CBSDF(L, N, V) dL} / Integral{CBSDF(L, N, V) dL}. 509 // Note: Integral{CBSDF(L, N, V) dL} is given by the FDG texture. 510 // CBSDF = F * D * G * NdotL / (4 * NdotL * NdotV) = F * D * G / (4 * NdotV). 511 // PDF = D * NdotH / (4 * LdotH). 512 // Weight = CBSDF / PDF = F * G * LdotH / (NdotV * NdotH). 513 // Since we perform filtering with the assumption that (V == N), 514 // (LdotH == NdotH) && (NdotV == 1) && (Weight == F * G). 515 // Therefore, after the Monte Carlo expansion of the integrals, 516 // X = Sum(Radiance(L) * Weight) / Sum(Weight) = Sum(Radiance(L) * F * G) / Sum(F * G). 517 518 #ifndef USE_KARIS_APPROXIMATION 519 // The choice of the Fresnel factor does not appear to affect the result. 520 real F = 1; // F_Schlick(F0, LdotH); 521 real G = V_SmithJointGGX(NdotL, NdotV, roughness, partLambdaV) * NdotL * NdotV; // 4 cancels out 522 523 lightInt += F * G * val; 524 cbsdfInt += F * G; 525 #else 526 // Use the approximation from "Real Shading in Unreal Engine 4": Weight ~ NdotL. 527 lightInt += NdotL * val; 528 cbsdfInt += NdotL; 529 #endif 530 } 531 532 return float4(lightInt / cbsdfInt, 1.0); 533} 534 535real4 IntegrateLDCharlie(TEXTURECUBE_PARAM(tex, sampl), 536 real3 N, 537 real roughness, 538 uint sampleCount, 539 real invFaceCenterTexelSolidAngle) 540{ 541 // ensure proper values 542 roughness = max(roughness, 0.001f); 543 sampleCount = max(1, sampleCount); 544 545 // filtered uniform sampling of the hemisphere 546 real3x3 localToWorld = GetLocalFrame(N); 547 real3 totalLight = real3(0.0, 0.0, 0.0); 548 real totalWeight = 0.0; 549 real rcpNumSamples = rcp(sampleCount); 550 real pdf = 1 / (2.0f * PI); 551 real lodBias = roughness; 552 real lodBase = 0.5f * log2((rcpNumSamples * 1.0f / pdf) * invFaceCenterTexelSolidAngle) + lodBias; 553 for (uint i = 0; i < sampleCount; ++i) 554 { 555 // generate sample on the normal oriented hemisphere (uniform sampling) 556 real3 localL = SampleConeStrata(i, rcpNumSamples, 0.0f); 557 real NdotL = localL.z; 558 real3 L = mul(localL, localToWorld); 559 560 // evaluate BRDF for the sample (assume V=N) 561 real NdotV = 1.0; 562 real LdotV, NdotH, LdotH, invLenLV; 563 GetBSDFAngle(N, L, NdotL, NdotV, LdotV, NdotH, LdotH, invLenLV); 564 real D = D_Charlie(NdotH, roughness); 565 566 // calculate texture LOD: 0.5*log2(omegaS/omegaP) as descriped in GPU Gems 3 "GPU-Based Importance Sampling" chapter 20.4: 567 // https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-20-gpu-based-importance-sampling 568 // omegaS = solid angle of the sample (i.e. 2pi/sampleCount for uniform hemisphere sampling) 569 // omegaP = solid angle of the texel in the sample direction. This is calculated by multiplying solid angle 570 // of the face center texel with texel cos(theta), where theta is angle between sample direction 571 // and center of the face, to account diminishing texel solid angles towards the edges of the cube. 572 real3 cubeCoord = L / max(abs(L.x), max(abs(L.y), abs(L.z))); // project sample direction to the cube face 573 real invDu2 = dot(cubeCoord, cubeCoord); // invDu2=1/cos^2(theta) of the sample texel 574 real lod = 0.5f * 0.5f * log2(invDu2) + lodBase; // extra 0.5f for sqrt(invDu2)=1/cos(theta) 575 real3 val = SAMPLE_TEXTURECUBE_LOD(tex, sampl, L, lod).rgb; 576 577 // accumulate lighting & weights 578 real w = D * NdotL; 579 totalLight += val * w; 580 totalWeight += w; 581 } 582 583 return real4(totalLight / totalWeight, 1.0); 584} 585 586// Searches the row 'j' containing 'n' elements of 'haystack' and 587// returns the index of the first element greater or equal to 'needle'. 588uint BinarySearchRow(uint j, real needle, TEXTURE2D(haystack), uint n) 589{ 590 uint i = n - 1; 591 real v = LOAD_TEXTURE2D(haystack, uint2(i, j)).r; 592 593 if (needle < v) 594 { 595 i = 0; 596 597 for (uint b = 1U << firstbithigh(n - 1); b != 0; b >>= 1) 598 { 599 uint p = i | b; 600 v = LOAD_TEXTURE2D(haystack, uint2(p, j)).r; 601 if (v <= needle) { i = p; } // Move to the right. 602 } 603 } 604 605 return i; 606} 607 608real4 IntegrateLD_MIS(TEXTURECUBE_PARAM(envMap, sampler_envMap), 609 TEXTURE2D(marginalRowDensities), 610 TEXTURE2D(conditionalDensities), 611 real3 V, 612 real3 N, 613 real roughness, 614 real invOmegaP, 615 uint width, 616 uint height, 617 uint sampleCount, 618 bool prefilter) 619{ 620 real3x3 localToWorld = GetLocalFrame(N); 621 622 real3 lightInt = real3(0.0, 0.0, 0.0); 623 real cbsdfInt = 0.0; 624 625/* 626 // Dedicate 50% of samples to light sampling at 1.0 roughness. 627 // Only perform BSDF sampling when roughness is below 0.5. 628 const int lightSampleCount = lerp(0, sampleCount / 2, saturate(2.0 * roughness - 1.0)); 629 const int bsdfSampleCount = sampleCount - lightSampleCount; 630*/ 631 632 // The value of the integral of intensity values of the environment map (as a 2D step function). 633 real envMapInt2dStep = LOAD_TEXTURE2D(marginalRowDensities, uint2(height, 0)).r; 634 // Since we are using equiareal mapping, we need to divide by the area of the sphere. 635 real envMapIntSphere = envMapInt2dStep * INV_FOUR_PI; 636 637 // Perform light importance sampling. 638 for (uint i = 0; i < sampleCount; i++) 639 { 640 real2 s = Hammersley2d(i, sampleCount); 641 642 // Sample a row from the marginal distribution. 643 uint y = BinarySearchRow(0, s.x, marginalRowDensities, height - 1); 644 645 // Sample a column from the conditional distribution. 646 uint x = BinarySearchRow(y, s.y, conditionalDensities, width - 1); 647 648 // Compute the coordinates of the sample. 649 // Note: we take the sample in between two texels, and also apply the half-texel offset. 650 // We could compute fractional coordinates at the cost of 4 extra texel samples. 651 real u = saturate((real)x / width + 1.0 / width); 652 real v = saturate((real)y / height + 1.0 / height); 653 real3 L = ConvertEquiarealToCubemap(u, v); 654 655 real NdotL = saturate(dot(N, L)); 656 657 if (NdotL > 0.0) 658 { 659 real3 val = SAMPLE_TEXTURECUBE_LOD(envMap, sampler_envMap, L, 0).rgb; 660 real pdf = (val.r + val.g + val.b) / envMapIntSphere; 661 662 if (pdf > 0.0) 663 { 664 // (N == V) && (acos(VdotL) == 2 * acos(NdotH)). 665 real NdotH = sqrt(NdotL * 0.5 + 0.5); 666 667 // ********************************************************************************* 668 // Our goal is to use Monte-Carlo integration with importance sampling to evaluate 669 // X(V) = Integral{Radiance(L) * CBSDF(L, N, V) dL} / Integral{CBSDF(L, N, V) dL}. 670 // CBSDF = F * D * G * NdotL / (4 * NdotL * NdotV) = F * D * G / (4 * NdotV). 671 // Weight = CBSDF / PDF. 672 // We use two approximations of Brian Karis from "Real Shading in Unreal Engine 4": 673 // (F * G ~ NdotL) && (NdotV == 1). 674 // Weight = D * NdotL / (4 * PDF). 675 // ********************************************************************************* 676 677 real weight = D_GGX(NdotH, roughness) * NdotL / (4.0 * pdf); 678 679 lightInt += weight * val; 680 cbsdfInt += weight; 681 } 682 } 683 } 684 685 // Prevent NaNs arising from the division of 0 by 0. 686 cbsdfInt = max(cbsdfInt, REAL_EPS); 687 688 return real4(lightInt / cbsdfInt, 1.0); 689} 690 691// Little helper to share code between sphere and box reflection probe. 692// This function will fade the mask of a reflection volume based on normal orientation compare to direction define by the center of the reflection volume. 693float InfluenceFadeNormalWeight(float3 normal, float3 centerToPos) 694{ 695 // Start weight from 0.6f (1 fully transparent) to 0.2f (fully opaque). 696 return saturate((-1.0f / 0.4f) * dot(normal, centerToPos) + (0.6f / 0.4f)); 697} 698 699#if SHADER_API_MOBILE || SHADER_API_GLES3 || SHADER_API_SWITCH 700#pragma warning (enable : 3205) // conversion of larger type to smaller 701#endif 702 703#endif // UNITY_IMAGE_BASED_LIGHTING_HLSL_INCLUDED