While the multiscattering GGX code is cool and solves the darkening problem at higher roughnesses, it's also currently buggy, hard to maintain and often impractical to use due to the higher noise and render time. In practice, though, having the exact correct directional distribution is not that important as long as the overall albedo is correct and we a) don't get the darkening effect and b) do get the saturation effect at higher roughnesses. This can simply be achieved by adding a second lobe (https://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_slides_v2.pdf) or scaling the single-scattering GGX lobe (https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf). Both approaches require the same precomputation and produce outputs of comparable quality, so I went for the simple albedo scaling since it's easier to implement and more efficient. Overall, the results are pretty good: All scenarios that I tested (Glossy BSDF, Glass BSDF, Principled BSDF with metallic or transmissive = 1) pass the white furnace test (a material with pure-white color in front of a pure-white background should be indistinguishable from the background if it preserves energy), and the overall albedo for non-white materials matches that produced by the real multi-scattering code (with the expected saturation increase as the roughness increases). In order to produce the precomputed tables, the PR also includes a utility that computes them. This is not built by default, since there's no reason for a user to run it (it only makes sense for documentation/reproducibility purposes and when making changes to the microfacet models). Pull Request: https://projects.blender.org/blender/blender/pulls/107958
61 lines
1.6 KiB
C
61 lines
1.6 KiB
C
/* SPDX-License-Identifier: Apache-2.0
|
|
* Copyright 2011-2022 Blender Foundation */
|
|
|
|
#pragma once
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
/* Interpolated lookup table access */
|
|
|
|
ccl_device float lookup_table_read(KernelGlobals kg, float x, int offset, int size)
|
|
{
|
|
x = saturatef(x) * (size - 1);
|
|
|
|
int index = min(float_to_int(x), size - 1);
|
|
int nindex = min(index + 1, size - 1);
|
|
float t = x - index;
|
|
|
|
float data0 = kernel_data_fetch(lookup_table, index + offset);
|
|
if (t == 0.0f)
|
|
return data0;
|
|
|
|
float data1 = kernel_data_fetch(lookup_table, nindex + offset);
|
|
return (1.0f - t) * data0 + t * data1;
|
|
}
|
|
|
|
ccl_device float lookup_table_read_2D(
|
|
KernelGlobals kg, float x, float y, int offset, int xsize, int ysize)
|
|
{
|
|
y = saturatef(y) * (ysize - 1);
|
|
|
|
int index = min(float_to_int(y), ysize - 1);
|
|
int nindex = min(index + 1, ysize - 1);
|
|
float t = y - index;
|
|
|
|
float data0 = lookup_table_read(kg, x, offset + xsize * index, xsize);
|
|
if (t == 0.0f)
|
|
return data0;
|
|
|
|
float data1 = lookup_table_read(kg, x, offset + xsize * nindex, xsize);
|
|
return (1.0f - t) * data0 + t * data1;
|
|
}
|
|
|
|
ccl_device float lookup_table_read_3D(
|
|
KernelGlobals kg, float x, float y, float z, int offset, int xsize, int ysize, int zsize)
|
|
{
|
|
z = saturatef(z) * (zsize - 1);
|
|
|
|
int index = min(float_to_int(z), zsize - 1);
|
|
int nindex = min(index + 1, zsize - 1);
|
|
float t = z - index;
|
|
|
|
float data0 = lookup_table_read_2D(kg, x, y, offset + xsize * ysize * index, xsize, ysize);
|
|
if (t == 0.0f)
|
|
return data0;
|
|
|
|
float data1 = lookup_table_read_2D(kg, x, y, offset + xsize * ysize * nindex, xsize, ysize);
|
|
return (1.0f - t) * data0 + t * data1;
|
|
}
|
|
|
|
CCL_NAMESPACE_END
|