暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ImageBasedLighting.hlsl 28KB

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