Eevee-Next: World Reflective Light #108149

Merged
Jeroen Bakker merged 33 commits from Jeroen-Bakker/blender:eevee-next-world-shader into main 2023-06-29 15:25:04 +02:00
2 changed files with 148 additions and 5 deletions
Showing only changes of commit 38e9938fac - Show all commits

View File

@ -1,12 +1,71 @@
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
Jeroen-Bakker marked this conversation as resolved Outdated

Do we also need to update out_diffuse

Do we also need to update out_diffuse
void light_world_eval(ClosureReflection reflection, vec3 P, vec3 V, inout vec3 out_specular)
{
float linear_roughness = fast_sqrt(reflection.roughness);
ivec3 texture_size = textureSize(reflectionProbes, 0);
/* TODO: This should be based by actual resolution. Currently the resolution is fixed but
* eventually this should based on a user setting. */
float lod_cube_max = 12.0;
float lod = linear_roughness * lod_cube_max;
vec3 R = -reflect(V, reflection.N);
vec3 world_light = textureLod_cubemapArray(reflectionProbes, vec4(R, 0.0), lod).rgb;
out_specular += world_light;
vec3 T, B;
Jeroen-Bakker marked this conversation as resolved Outdated

Check lightprobe_filter_glossy_frag

Check `lightprobe_filter_glossy_frag`
make_orthonormal_basis(reflection.N, T, B);
float weight = 0.0;
vec3 out_radiance = vec3(0.0);
/* Note: this was dynamic based on the mipmap level that was read from. */
/* lod 0 => 1
* 1 => 32
* 2 => 40
* 3 => 64
* else => 128
* multiplied by the filter quality. */
const int max_sample_count = 32;
/* Note this is dynamically based on the mip map level.
pinfo->lodfactor = bias + 0.5f * log(square_f(target_size) / pinfo->samples_len) / log(2);
*/
/* Pow4f for Disney Roughness distributed across lod more evenly */
float roughness = clamp(pow4f(reflection.roughness), 1e-4f, 0.9999f); /* Avoid artifacts */
const float lod_factor = 1.0 +
0.5 * log(float(square_i(texture_size.x)) / float(max_sample_count)) /
log(2);
/* We should find a math formular that would approx max_sample_count and bias. */
for (int i = 0; i < max_sample_count; i++) {
vec3 Xi = sample_cylinder(hammersley_2d(float(i), max_sample_count));
/* Microfacet normal */
float pdf;
vec3 H = sample_ggx(Xi, roughness, V, reflection.N, T, B, pdf);
vec3 L = -reflect(V, H);
float NL = dot(reflection.N, L);
if (NL > 0.0) {
float NH = max(1e-8, dot(reflection.N, H)); /*cosTheta */
/* Coarse Approximation of the mapping distortion
* Unit Sphere -> Cubemap Face */
const float dist = 4.0 * M_PI / 6.0;
/* http://http.developer.nvidia.com/GPUGems3/gpugems3_ch20.html : Equation 13 */
float lod = clamp(lod_factor - 0.5 * log2(pdf * dist), 0.0, lod_cube_max);
vec3 l_col = textureLod_cubemapArray(reflectionProbes, vec4(L, 0.0), lod).rgb;
/* Clamped brightness. */
float luma = max(1e-8, max_v3(l_col));
/* For artistic freedom this should be read from the scene/reflection probe.
* Note: Eevee-legacy read the firefly_factor from gi_glossy_clamp. */
const float firefly_factor = 1e16;
l_col *= 1.0 - max(0.0, luma - firefly_factor) / luma;
out_radiance += l_col * NL;
weight += NL;
}
}
/* TODO: for artistic freedom want to read this from the reflection probe. That can be added as
* part of the reflection probe patch. */
const float intensity_factor = 1.0;
out_specular += vec3(intensity_factor * out_radiance / weight);
}

View File

@ -5,6 +5,7 @@
**/
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl)
/* -------------------------------------------------------------------- */
/** \name Sampling data.
@ -102,3 +103,86 @@ vec3 sample_cylinder(vec2 rand)
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Microfacet GGX distribution
* \{ */
#define USE_VISIBLE_NORMAL 1
float D_ggx_opti(float NH, float a2)
{
float tmp = (NH * a2 - NH) * NH + 1.0;
return M_PI * tmp * tmp; /* Doing RCP and mul a2 at the end */
}
float G1_Smith_GGX_opti(float NX, float a2)
{
/* Using Brian Karis approach and refactoring by NX/NX
* this way the (2*NL)*(2*NV) in G = G1(V) * G1(L) gets canceled by the brdf denominator 4*NL*NV
* Rcp is done on the whole G later
* Note that this is not convenient for the transmission formula */
return NX + sqrt(NX * (NX - NX * a2) + a2);
/* return 2 / (1 + sqrt(1 + a2 * (1 - NX*NX) / (NX*NX) ) ); /* Reference function */
}
float pdf_ggx_reflect(float NH, float NV, float VH, float alpha)
{
float a2 = sqr(alpha);
#if USE_VISIBLE_NORMAL
float D = a2 / D_ggx_opti(NH, a2);
float G1 = NV * 2.0 / G1_Smith_GGX_opti(NV, a2);
return G1 * VH * D / NV;
#else
return NH * a2 / D_ggx_opti(NH, a2);
#endif
}
vec3 sample_ggx(vec3 rand, float alpha, vec3 Vt)
{
#if USE_VISIBLE_NORMAL
/* From:
* "A Simpler and Exact Sampling Routine for the GGXDistribution of Visible Normals"
* by Eric Heitz.
* http://jcgt.org/published/0007/04/01/slides.pdf
* View vector is expected to be in tangent space. */
/* Stretch view. */
vec3 Th, Bh, Vh = normalize(vec3(alpha * Vt.xy, Vt.z));
make_orthonormal_basis(Vh, Th, Bh);
/* Sample point with polar coordinates (r, phi). */
float r = sqrt(rand.x);
float x = r * rand.y;
float y = r * rand.z;
float s = 0.5 * (1.0 + Vh.z);
y = (1.0 - s) * sqrt(1.0 - x * x) + s * y;
float z = sqrt(saturate(1.0 - x * x - y * y));
/* Compute normal. */
vec3 Hh = x * Th + y * Bh + z * Vh;
/* Unstretch. */
vec3 Ht = normalize(vec3(alpha * Hh.xy, saturate(Hh.z)));
/* Microfacet Normal. */
return Ht;
#else
/* Theta is the cone angle. */
float z = sqrt((1.0 - rand.x) / (1.0 + sqr(alpha) * rand.x - rand.x)); /* cos theta */
float r = sqrt(max(0.0, 1.0 - z * z)); /* sin theta */
float x = r * rand.y;
float y = r * rand.z;
/* Microfacet Normal */
return vec3(x, y, z);
#endif
}
vec3 sample_ggx(vec3 rand, float alpha, vec3 V, vec3 N, vec3 T, vec3 B, out float pdf)
{
vec3 Vt = world_to_tangent(V, N, T, B);
vec3 Ht = sample_ggx(rand, alpha, Vt);
float NH = saturate(Ht.z);
float NV = saturate(Vt.z);
float VH = saturate(dot(Vt, Ht));
pdf = pdf_ggx_reflect(NH, NV, VH, alpha);
return tangent_to_world(Ht, N, T, B);
}
/** \} */