env.glsl 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. /* env.glsl -- Contains everything you need to manage environment
  2. *
  3. * Copyright (c) 2025-2026 Le Juez Victor
  4. *
  5. * This software is provided 'as-is', without any express or implied warranty.
  6. * For conditions of distribution and use, see accompanying LICENSE file.
  7. */
  8. /* === Includes === */
  9. #include "../math.glsl"
  10. /* === Structures === */
  11. struct EnvAmbient {
  12. vec4 rotation;
  13. vec4 color;
  14. float energy;
  15. int irradiance;
  16. int prefilter;
  17. };
  18. struct EnvProbe {
  19. vec3 position;
  20. float falloff;
  21. float range;
  22. int irradiance;
  23. int prefilter;
  24. };
  25. /* === Uniform Block === */
  26. layout(std140) uniform EnvBlock {
  27. EnvProbe uProbes[NUM_PROBES];
  28. EnvAmbient uAmbient;
  29. int uNumPrefilterLevels;
  30. int uNumProbes;
  31. };
  32. /* === IBL Functions === */
  33. float IBL_GetSpecularOcclusion(float NdotV, float ao, float roughness)
  34. {
  35. // Lagarde method: https://seblagarde.wordpress.com/wp-content/uploads/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
  36. return clamp(pow(NdotV + ao, exp2(-16.0 * roughness - 1.0)) - 1.0 + ao, 0.0, 1.0);
  37. }
  38. vec3 IBL_SampleIrradiance(samplerCubeArray irradiance, int index, vec3 N)
  39. {
  40. return texture(irradiance, vec4(N, float(index))).rgb;
  41. }
  42. vec3 IBL_SampleIrradiance(samplerCubeArray irradiance, int index, vec3 N, vec4 rotation)
  43. {
  44. return texture(irradiance, vec4(M_Rotate3D(N, rotation), float(index))).rgb;
  45. }
  46. vec3 IBL_SamplePrefilter(samplerCubeArray prefilter, int index, vec3 V, vec3 N, float roughness)
  47. {
  48. float mipLevel = roughness * float(uNumPrefilterLevels - 1);
  49. return textureLod(prefilter, vec4(reflect(-V, N), float(index)), mipLevel).rgb;
  50. }
  51. vec3 IBL_SamplePrefilter(samplerCubeArray prefilter, int index, vec3 V, vec3 N, vec4 rotation, float roughness)
  52. {
  53. float mipLevel = roughness * float(uNumPrefilterLevels - 1);
  54. return textureLod(prefilter, vec4(M_Rotate3D(reflect(-V, N), rotation), float(index)), mipLevel).rgb;
  55. }
  56. void IBL_MultiScattering(inout vec3 irradiance, inout vec3 radiance, vec3 diffuse, vec3 F0, vec2 brdf, float NdotV, float roughness)
  57. {
  58. // Adapted from Fdez-Aguera method without the roughness-dependent Fresnel
  59. // See: https://jcgt.org/published/0008/01/03/paper.pdf
  60. // Single scattering with standard Fresnel
  61. vec3 FssEss = F0 * brdf.x + brdf.y;
  62. // Multiple scattering, from Fdez-Aguera
  63. float Ess = brdf.x + brdf.y;
  64. float Ems = 1.0 - Ess;
  65. vec3 Favg = F0 + (1.0 - F0) / 21.0;
  66. vec3 FmsD = max(1.0 - (1.0 - Ess) * Favg, vec3(1e-6)); //< avoids division by zero in extreme F0/low roughness cases
  67. vec3 Fms = FssEss * Favg / FmsD;
  68. vec3 kD = diffuse * (1.0 - FssEss);
  69. // Compute final irradiance / radiance
  70. irradiance *= (Fms * Ems + kD);
  71. radiance *= FssEss;
  72. }
  73. void IBL_SampleProbe(inout vec3 irr, inout vec3 rad, inout float wIrr, inout float wRad, int probeIndex, float roughness, vec3 P, vec3 N, vec3 V)
  74. {
  75. EnvProbe probe = uProbes[probeIndex];
  76. float dist = length(P - probe.position);
  77. float weight = pow(clamp(1.0 - dist / probe.range, 0.0, 1.0), probe.falloff);
  78. if (weight < 1e-6) return;
  79. if (probe.irradiance >= 0) {
  80. vec3 probeIrr = IBL_SampleIrradiance(uIrradianceTex, probe.irradiance, N);
  81. irr += probeIrr.rgb * weight;
  82. wIrr += weight;
  83. }
  84. if (probe.prefilter >= 0) {
  85. vec3 probeRad = IBL_SamplePrefilter(uPrefilterTex, probe.prefilter, V, N, roughness);
  86. rad += probeRad.rgb * weight;
  87. wRad += weight;
  88. }
  89. }
  90. /* === Environment Functions === */
  91. void E_ComputeAmbientAndProbes(inout vec3 diffuse, inout vec3 specular, vec3 kD, vec3 orm, vec3 F0, vec3 P, vec3 N, vec3 V, float NdotV)
  92. {
  93. float occlusion = orm.x;
  94. float roughness = orm.y;
  95. float metalness = orm.z;
  96. vec3 irradiance = vec3(0.0);
  97. float wIrradiance = 0.0;
  98. vec3 radiance = vec3(0.0);
  99. float wRadiance = 0.0;
  100. for (int i = 0; i < uNumProbes; ++i) {
  101. IBL_SampleProbe(irradiance, radiance, wIrradiance, wRadiance, i, roughness, P, N, V);
  102. }
  103. if (wIrradiance > 1.0) {
  104. float invTotalWeight = 1.0 / wIrradiance;
  105. irradiance *= invTotalWeight;
  106. wIrradiance = 1.0;
  107. }
  108. if (wRadiance > 1.0) {
  109. float invTotalWeight = 1.0 / wRadiance;
  110. radiance *= invTotalWeight;
  111. wRadiance = 1.0;
  112. }
  113. if (wIrradiance < 1.0) {
  114. vec3 ambientIrr = vec3(0.0);
  115. if (uAmbient.irradiance < 0) ambientIrr = uAmbient.color.rgb;
  116. else ambientIrr = IBL_SampleIrradiance(uIrradianceTex, uAmbient.irradiance, N, uAmbient.rotation);
  117. irradiance += ambientIrr * (1.0 - wIrradiance);
  118. }
  119. if (wRadiance < 1.0 && uAmbient.prefilter >= 0) {
  120. vec3 ambientRad = IBL_SamplePrefilter(uPrefilterTex, uAmbient.prefilter, V, N, uAmbient.rotation, roughness);
  121. radiance += ambientRad * (1.0 - wRadiance);
  122. }
  123. irradiance *= occlusion * uAmbient.energy;
  124. radiance *= IBL_GetSpecularOcclusion(NdotV, occlusion, roughness);
  125. vec2 brdf = texture(uBrdfLutTex, vec2(NdotV, roughness)).xy;
  126. IBL_MultiScattering(irradiance, radiance, kD, F0, brdf, NdotV, roughness);
  127. diffuse += irradiance;
  128. specular += radiance;
  129. }
  130. void E_ComputeAmbientOnly(inout vec3 diffuse, inout vec3 specular, vec3 kD, vec3 orm, vec3 F0, vec3 P, vec3 N, vec3 V, float NdotV)
  131. {
  132. float occlusion = orm.x;
  133. float roughness = orm.y;
  134. float metalness = orm.z;
  135. vec3 irradiance = (uAmbient.irradiance >= 0)
  136. ? IBL_SampleIrradiance(uIrradianceTex, uAmbient.irradiance, N, uAmbient.rotation).rgb
  137. : uAmbient.color.rgb;
  138. irradiance *= occlusion * uAmbient.energy;
  139. vec3 radiance = vec3(0.0);
  140. if (uAmbient.prefilter >= 0) {
  141. radiance = IBL_SamplePrefilter(uPrefilterTex, uAmbient.prefilter, V, N, uAmbient.rotation, roughness).rgb;
  142. radiance *= IBL_GetSpecularOcclusion(NdotV, occlusion, roughness);
  143. }
  144. vec2 brdf = texture(uBrdfLutTex, vec2(NdotV, roughness)).xy;
  145. IBL_MultiScattering(irradiance, radiance, kD, F0, brdf, NdotV, roughness);
  146. diffuse += irradiance;
  147. specular += radiance;
  148. }
  149. void E_ComputeAmbientColor(inout vec3 diffuse, vec3 kD, float occlusion)
  150. {
  151. diffuse += kD * uAmbient.color.rgb * uAmbient.energy * occlusion;
  152. }