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:
parent
3514d6ed52
commit
0a4fa62f51
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
@ -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. */
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue