From dbda77b091300016d04a8d05972e4b93d1ba2e4d Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 6 Dec 2019 17:21:15 -0800 Subject: [PATCH] Improve low roughness significantly Filament will now allow incomplete mipmap chains for the IBL and map the roughness to the available chain. This allows to create roughness cubemaps with a minim size for roughness==1, instead of always mapping it to 1x1. cmgen will now use 16x16 cubemaps for roughness==1, which improves the quality of rough objects significantly. The beauty of this is that there is no asset or API change. Old assets will continue to work like before. --- RELEASE_NOTES.md | 2 ++ filament/src/IndirectLight.cpp | 6 ------ tools/cmgen/src/cmgen.cpp | 23 ++++++++++++++++++++--- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index abf2b2f513..7dd8179a57 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -5,6 +5,8 @@ A new header is inserted each time a *tag* is created. ## Next release +- Improve high roughness material rendering by default when regenerating environments maps + ## v1.4.3 - Fixed an assertion when a parameter array occurs last in a material definition. diff --git a/filament/src/IndirectLight.cpp b/filament/src/IndirectLight.cpp index c43e7643cc..021f302637 100644 --- a/filament/src/IndirectLight.cpp +++ b/filament/src/IndirectLight.cpp @@ -156,12 +156,6 @@ IndirectLight* IndirectLight::Builder::build(Engine& engine) { return nullptr; } - if (!ASSERT_POSTCONDITION_NON_FATAL(mImpl->mReflectionsMap->getLevels() == - upcast(mImpl->mReflectionsMap)->getMaxLevelCount(), - "reflection map must have %u mipmap levels", - upcast(mImpl->mReflectionsMap)->getMaxLevelCount())) { - return nullptr; - } if (IBL_INTEGRATION == IBL_INTEGRATION_IMPORTANCE_SAMPLING) { mImpl->mReflectionsMap->generateMipmaps(engine); } diff --git a/tools/cmgen/src/cmgen.cpp b/tools/cmgen/src/cmgen.cpp index 6a94bee740..9398cb6b95 100644 --- a/tools/cmgen/src/cmgen.cpp +++ b/tools/cmgen/src/cmgen.cpp @@ -58,6 +58,7 @@ enum class ShFile { static const size_t DFG_LUT_DEFAULT_SIZE = 128; static const size_t IBL_DEFAULT_SIZE = 256; +static const size_t IBL_DEFAULT_MIN_LOD_SIZE = 16; enum class OutputType { FACES, KTX, EQUIRECT, OCTAHEDRON @@ -71,6 +72,7 @@ static float g_extract_blur = 0.0; static utils::Path g_extract_dir; static size_t g_output_size = 0; +static size_t g_min_lod_size = 0; static bool g_quiet = false; static bool g_debug = false; @@ -210,6 +212,8 @@ static void printUsage(char* name) { " Diffuse irradiance into \n\n" " --ibl-no-prefilter\n" " Use importance sampling instead of prefiltered importance sampling\n\n" + " --ibl-min-lod-size\n" + " Minimum LOD size [default: 16]\n\n" " --sh=bands\n" " SH decomposition of input cubemap\n\n" " --sh-output=filename.[exr|hdr|psd|rgbm|rgb32f|png|dds|txt]\n" @@ -240,7 +244,7 @@ static void license() { } static int handleCommandLineArgments(int argc, char* argv[]) { - static constexpr const char* OPTSTR = "hqidt:f:c:s:x:w:"; + static constexpr const char* OPTSTR = "hqidt:f:c:s:x:w:S:"; static const struct option OPTIONS[] = { { "help", no_argument, nullptr, 'h' }, { "license", no_argument, nullptr, 'l' }, @@ -264,6 +268,7 @@ static int handleCommandLineArgments(int argc, char* argv[]) { { "ibl-dfg-multiscatter", no_argument, nullptr, 'u' }, { "ibl-dfg-cloth", no_argument, nullptr, 'C' }, { "ibl-no-prefilter", no_argument, nullptr, 'n' }, + { "ibl-min-lod-size", required_argument, nullptr, 'S' }, { "ibl-samples", required_argument, nullptr, 'k' }, { "deploy", required_argument, nullptr, 'x' }, { "no-mirror", no_argument, nullptr, 'm' }, @@ -353,6 +358,13 @@ static int handleCommandLineArgments(int argc, char* argv[]) { exit(0); } break; + case 'S': + g_min_lod_size = std::stoul(arg); + if (!isPOT(g_min_lod_size)) { + std::cerr << "min LOD size must be a power of two" << std::endl; + exit(0); + } + break; case 'z': g_sh_compute = 1; g_sh_output = true; @@ -877,8 +889,13 @@ void iblRoughnessPrefilter( const bool DEBUG_FULL_RESOLUTION = false; const size_t baseExp = utils::ctz(g_output_size ? g_output_size : IBL_DEFAULT_SIZE); + size_t minLod = utils::ctz(g_min_lod_size ? g_min_lod_size : IBL_DEFAULT_MIN_LOD_SIZE); + if (minLod >= baseExp) { + minLod = 0; + } + size_t numSamples = g_num_samples; - const size_t numLevels = baseExp + 1; + const size_t numLevels = (baseExp + 1) - minLod; // It's convenient to create an empty KTX bundle on the stack in this scope, regardless of // whether KTX is requested. It does not consume memory if empty. @@ -895,7 +912,7 @@ void iblRoughnessPrefilter( .pixelDepth = 0, }; - for (ssize_t i = baseExp; i >= 0; --i) { + for (ssize_t i = baseExp; i >= ssize_t((baseExp + 1) - numLevels) ; --i) { const size_t dim = 1U << (DEBUG_FULL_RESOLUTION ? baseExp : i); // NOLINT const size_t level = baseExp - i; if (level >= 2) {