EEVEE: Add support for metallic F82 tint

This adds a new entry to the split sum LUT to isolate
the effect of the F82 tint.

The application of the tint part is similar to cycles
and uses the same way for precomputing the `b` factor.

Results matches almost perfectly to the extent of the
split sum approximation.

Note that this removes the unused LTC MAG LUT for
EEVEE next to make space for the new table. It can still
be added back if needed.

Pull Request: https://projects.blender.org/blender/blender/pulls/112881
This commit is contained in:
Clément Foucault 2023-09-26 10:16:39 +02:00 committed by Clément Foucault
parent 3514d6ed52
commit 0a4fa62f51
12 changed files with 2153 additions and 1435 deletions

View File

@ -154,14 +154,13 @@ static void eevee_init_util_texture()
memcpy(texels_layer, blender::eevee::lut::ltc_mat_ggx, sizeof(float[4]) * 64 * 64);
texels_layer += 64 * 64;
/* Copy brdf_ggx into 2nd layer red and green channels.
* Copy ltc_mag_ggx into 2nd layer blue and alpha channel. */
/* Copy brdf_ggx into 2nd layer red, green and blue channels. */
for (int x = 0; x < 64; x++) {
for (int y = 0; y < 64; y++) {
texels_layer[y * 64 + x][0] = blender::eevee::lut::brdf_ggx[y][x][0];
texels_layer[y * 64 + x][1] = blender::eevee::lut::brdf_ggx[y][x][1];
texels_layer[y * 64 + x][2] = blender::eevee::lut::ltc_mag_ggx[y][x][0];
texels_layer[y * 64 + x][3] = blender::eevee::lut::ltc_mag_ggx[y][x][1];
texels_layer[y * 64 + x][2] = blender::eevee::lut::brdf_ggx[y][x][2];
texels_layer[y * 64 + x][3] = 0.0f; /* UNUSED */
}
}
texels_layer += 64 * 64;
@ -177,13 +176,14 @@ static void eevee_init_util_texture()
}
texels_layer += 64 * 64;
/* Copy ltc_disk_integral in 4th layer. */
/* Copy ltc_disk_integral in 4th layer.
* Copy ltc_mag_ggx into blue and alpha channel. */
for (int x = 0; x < 64; x++) {
for (int y = 0; y < 64; y++) {
texels_layer[y * 64 + x][0] = blender::eevee::lut::ltc_disk_integral[y][x][0];
texels_layer[y * 64 + x][1] = 0.0; /* UNUSED */
texels_layer[y * 64 + x][2] = 0.0; /* UNUSED */
texels_layer[y * 64 + x][3] = 0.0; /* UNUSED */
texels_layer[y * 64 + x][2] = blender::eevee::lut::ltc_mag_ggx[y][x][0];
texels_layer[y * 64 + x][3] = blender::eevee::lut::ltc_mag_ggx[y][x][1];
}
}
texels_layer += 64 * 64;

View File

@ -104,6 +104,12 @@ void bsdf_lut(vec3 F0,
out vec3 reflectance,
out vec3 transmittance);
vec2 brdf_lut(float a, float b);
void brdf_f82_tint_lut(vec3 F0,
vec3 F82,
float cos_theta,
float roughness,
bool do_multiscatter,
out vec3 reflectance);
vec3 F_brdf_multi_scatter(vec3 a, vec3 b, vec2 c);
vec3 F_brdf_single_scatter(vec3 a, vec3 b, vec2 c);
float F_eta(float a, float b);

View File

@ -20,7 +20,7 @@ uniform sampler2DArray utilTex;
#define LUT_SIZE 64
#define LTC_MAT_LAYER 0
#define LTC_BRDF_LAYER 1
#define LTC_BRDF_LAYER 3
#define BRDF_LUT_LAYER 1
#define NOISE_LAYER 2
#define LTC_DISK_LAYER 3 /* UNUSED */
@ -51,6 +51,34 @@ vec2 brdf_lut(float cos_theta, float roughness)
return textureLod(utilTex, vec3(lut_coords(cos_theta, roughness), BRDF_LUT_LAYER), 0.0).rg;
}
void brdf_f82_tint_lut(vec3 F0,
vec3 F82,
float cos_theta,
float roughness,
bool do_multiscatter,
out vec3 reflectance)
{
vec2 uv = lut_coords(cos_theta, roughness);
vec3 split_sum = textureLod(utilTex, vec3(uv, BRDF_LUT_LAYER), 0.0).rgb;
reflectance = do_multiscatter ? F_brdf_multi_scatter(F0, vec3(1.0), split_sum.xy) :
F_brdf_single_scatter(F0, vec3(1.0), split_sum.xy);
/* Precompute the F82 term factor for the Fresnel model.
* In the classic F82 model, the F82 input directly determines the value of the Fresnel
* model at ~82°, similar to F0 and F90.
* With F82-Tint, on the other hand, the value at 82° is the value of the classic Schlick
* model multiplied by the tint input.
* Therefore, the factor follows by setting F82Tint(cosI) = FSchlick(cosI) - b*cosI*(1-cosI)^6
* and F82Tint(acos(1/7)) = FSchlick(acos(1/7)) * f82_tint and solving for b. */
const float f = 6.0 / 7.0;
const float f5 = (f * f) * (f * f) * f;
const float f6 = (f * f) * (f * f) * (f * f);
vec3 F_schlick = mix(F0, vec3(1.0), f5);
vec3 b = F_schlick * (7.0 / f6) * (1.0 - F82);
reflectance -= b * split_sum.z;
}
vec4 sample_3D_texture(sampler2DArray tex, vec3 coords)
{
float layer_floored;

View File

@ -63,6 +63,15 @@ vec2 brdf_lut(float a, float b)
return vec2(0.0);
}
void brdf_f82_tint_lut(vec3 F0,
vec3 F82,
float cos_theta,
float roughness,
bool do_multiscatter,
out vec3 reflectance)
{
}
vec3 F_brdf_multi_scatter(vec3 a, vec3 b, vec2 c)
{
return vec3(0.0);

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ extern const float ltc_mag_ggx[64][64][2];
/* Precomputed Disk integral for different elevation angles and solid angle. */
extern const float ltc_disk_integral[64][64][1];
/* Precomputed integrated split fresnel term of the GGX BRDF. */
extern const float brdf_ggx[64][64][2];
extern const float brdf_ggx[64][64][3];
/* Precomputed Schlick reflectance and transmittance factor of glass material with IOR < 1. */
extern const float bsdf_ggx[16][64][64][3];
/* Precomputed Schlick transmittance factor of glass material with IOR > 1. */

View File

@ -404,16 +404,15 @@ class UtilityTexture : public Texture {
memcpy(layer.data, lut::ltc_mat_ggx, sizeof(layer));
}
{
Layer &layer = data[UTIL_LTC_MAG_LAYER];
Layer &layer = data[UTIL_BSDF_LAYER];
for (auto x : IndexRange(lut_size)) {
for (auto y : IndexRange(lut_size)) {
layer.data[y][x][0] = lut::brdf_ggx[y][x][0];
layer.data[y][x][1] = lut::brdf_ggx[y][x][1];
layer.data[y][x][2] = lut::ltc_mag_ggx[y][x][0];
layer.data[y][x][3] = lut::ltc_mag_ggx[y][x][1];
layer.data[y][x][2] = lut::brdf_ggx[y][x][2];
layer.data[y][x][3] = 0.0f;
}
}
BLI_assert(UTIL_LTC_MAG_LAYER == UTIL_BSDF_LAYER);
}
{
for (auto layer_id : IndexRange(16)) {

View File

@ -1324,7 +1324,6 @@ BLI_STATIC_ASSERT_ALIGN(UniformData, 16)
#define UTIL_BLUE_NOISE_LAYER 0
#define UTIL_SSS_TRANSMITTANCE_PROFILE_LAYER 1
#define UTIL_LTC_MAT_LAYER 2
#define UTIL_LTC_MAG_LAYER 3
#define UTIL_BSDF_LAYER 3
#define UTIL_BTDF_LAYER 5
#define UTIL_DISK_INTEGRAL_LAYER UTIL_SSS_TRANSMITTANCE_PROFILE_LAYER

View File

@ -14,7 +14,9 @@
/* Generate BRDF LUT following "Real shading in unreal engine 4" by Brian Karis
* https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
* Parametrizing with `x = roughness` and `y = sqrt(1.0 - cos(theta))`.
* The result is interpreted as: `integral = F0 * scale + F90 * bias`. */
* The result is interpreted as: `integral = F0 * scale + F90 * bias - F82_tint * metal_bias`.
* with `F82_tint = mix(F0, vec3(1.0), pow5f(6.0 / 7.0)) * (7.0 / pow6f(6.0 / 7.0)) * (1.0 - F82)`
*/
vec4 ggx_brdf_split_sum(vec3 lut_coord)
{
/* Squaring for perceptually linear roughness, see [Physically Based Shading at Disney]
@ -29,6 +31,7 @@ vec4 ggx_brdf_split_sum(vec3 lut_coord)
/* Integrating BRDF. */
float scale = 0.0;
float bias = 0.0;
float metal_bias = 0.0;
const uint sample_count = 512u * 512u;
for (uint i = 0u; i < sample_count; i++) {
vec2 rand = hammersley_2d(i, sample_count);
@ -40,18 +43,23 @@ vec4 ggx_brdf_split_sum(vec3 lut_coord)
float NL = L.z;
if (NL > 0.0) {
float VH = saturate(dot(V, H));
/* Assuming sample visible normals, `weight = brdf * NV / (pdf * fresnel).` */
float weight = bxdf_ggx_smith_G1(NL, roughness_sq);
/* Schlick's Fresnel. */
float s = saturate(pow5f(1.0 - saturate(dot(V, H))));
float s = saturate(pow5f(1.0 - VH));
scale += (1.0 - s) * weight;
bias += s * weight;
/* F82 tint effect. */
float b = VH * saturate(pow6f(1.0 - VH));
metal_bias += b * weight;
}
}
scale /= float(sample_count);
bias /= float(sample_count);
metal_bias /= float(sample_count);
return vec4(scale, bias, 0.0, 0.0);
return vec4(scale, bias, metal_bias, 0.0);
}
/* Generate BSDF LUT for `IOR < 1` using Schlick's approximation. Returns the transmittance and the

View File

@ -300,6 +300,37 @@ vec2 brdf_lut(float cos_theta, float roughness)
#endif
}
void brdf_f82_tint_lut(vec3 F0,
vec3 F82,
float cos_theta,
float roughness,
bool do_multiscatter,
out vec3 reflectance)
{
#ifdef EEVEE_UTILITY_TX
vec3 split_sum = utility_tx_sample_lut(utility_tx, cos_theta, roughness, UTIL_BSDF_LAYER).rgb;
#else
vec3 split_sum = vec2(1.0, 0.0, 0.0);
#endif
reflectance = do_multiscatter ? F_brdf_multi_scatter(F0, vec3(1.0), split_sum.xy) :
F_brdf_single_scatter(F0, vec3(1.0), split_sum.xy);
/* Precompute the F82 term factor for the Fresnel model.
* In the classic F82 model, the F82 input directly determines the value of the Fresnel
* model at ~82°, similar to F0 and F90.
* With F82-Tint, on the other hand, the value at 82° is the value of the classic Schlick
* model multiplied by the tint input.
* Therefore, the factor follows by setting F82Tint(cosI) = FSchlick(cosI) - b*cosI*(1-cosI)^6
* and F82Tint(acos(1/7)) = FSchlick(acos(1/7)) * f82_tint and solving for b. */
const float f = 6.0 / 7.0;
const float f5 = (f * f) * (f * f) * f;
const float f6 = (f * f) * (f * f) * (f * f);
vec3 F_schlick = mix(F0, vec3(1.0), f5);
vec3 b = F_schlick * (7.0 / f6) * (1.0 - F82);
reflectance -= b * split_sum.z;
}
/* Return texture coordinates to sample BSDF LUT. */
vec3 lut_coords_bsdf(float cos_theta, float roughness, float ior)
{

View File

@ -1450,20 +1450,20 @@ static void test_eevee_lut_gen()
Manager manager;
/* Check if LUT generation matches the header version. */
auto brdf_ggx_gen = Precompute(manager, LUT_GGX_BRDF_SPLIT_SUM, {64, 64, 1}).data<float2>();
auto brdf_ggx_gen = Precompute(manager, LUT_GGX_BRDF_SPLIT_SUM, {64, 64, 1}).data<float3>();
auto btdf_ggx_gen = Precompute(manager, LUT_GGX_BTDF_IOR_GT_ONE, {64, 64, 16}).data<float1>();
auto bsdf_ggx_gen = Precompute(manager, LUT_GGX_BSDF_SPLIT_SUM, {64, 64, 16}).data<float3>();
auto burley_gen = Precompute(manager, LUT_BURLEY_SSS_PROFILE, {64, 1, 1}).data<float1>();
auto rand_walk_gen = Precompute(manager, LUT_RANDOM_WALK_SSS_PROFILE, {64, 1, 1}).data<float1>();
Span<float2> brdf_ggx_lut((const float2 *)&eevee::lut::brdf_ggx, 64 * 64);
Span<float3> brdf_ggx_lut((const float3 *)&eevee::lut::brdf_ggx, 64 * 64);
Span<float1> btdf_ggx_lut((const float1 *)&eevee::lut::btdf_ggx, 64 * 64 * 16);
Span<float3> bsdf_ggx_lut((const float3 *)&eevee::lut::bsdf_ggx, 64 * 64 * 16);
Span<float1> burley_sss_lut((const float1 *)&eevee::lut::burley_sss_profile, 64);
Span<float1> rand_walk_lut((const float1 *)&eevee::lut::random_walk_sss_profile, 64);
const float eps = 3e-3f;
EXPECT_NEAR_ARRAY_ND(brdf_ggx_lut.data(), brdf_ggx_gen.data(), brdf_ggx_gen.size(), 2, eps);
EXPECT_NEAR_ARRAY_ND(brdf_ggx_lut.data(), brdf_ggx_gen.data(), brdf_ggx_gen.size(), 3, eps);
EXPECT_NEAR_ARRAY_ND(btdf_ggx_lut.data(), btdf_ggx_gen.data(), btdf_ggx_gen.size(), 1, eps);
EXPECT_NEAR_ARRAY_ND(bsdf_ggx_lut.data(), bsdf_ggx_gen.data(), bsdf_ggx_gen.size(), 3, eps);
EXPECT_NEAR_ARRAY_ND(burley_gen.data(), burley_sss_lut.data(), burley_sss_lut.size(), 1, eps);

View File

@ -137,13 +137,12 @@ void node_bsdf_principled(vec4 base_color,
ClosureReflection reflection_data;
reflection_data.N = N;
reflection_data.roughness = roughness;
vec3 reflection_tint = specular_tint.rgb;
if (metallic > 0.0) {
vec3 F0 = base_color.rgb;
vec3 F90 = vec3(1.0);
vec2 split_sum = brdf_lut(NV, roughness);
vec3 metallic_brdf = (do_multiscatter != 0.0) ? F_brdf_multi_scatter(F0, F90, split_sum) :
F_brdf_single_scatter(F0, F90, split_sum);
vec3 F82 = reflection_tint;
vec3 metallic_brdf;
brdf_f82_tint_lut(F0, F82, NV, roughness, do_multiscatter != 0.0, metallic_brdf);
reflection_data.color = weight * metallic * metallic_brdf;
/* Attenuate lower layers */
weight *= (1.0 - metallic);
@ -157,7 +156,6 @@ void node_bsdf_principled(vec4 base_color,
refraction_data.N = N;
refraction_data.roughness = roughness;
refraction_data.ior = ior;
vec3 reflection_tint = specular_tint.rgb;
if (transmission_weight > 0.0) {
vec3 F0 = vec3(F0_from_ior(ior)) * reflection_tint;
vec3 F90 = vec3(1.0);