From 1ceed1519b08b4cbb5804433aee4864dc48900b1 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Mon, 10 Jan 2022 14:00:53 -0800 Subject: [PATCH 01/22] attempt to fix compilation failure with gcc fix #4990 --- filament/backend/include/backend/DriverEnums.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index b521f3a4e0..abd38cc77a 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -809,10 +809,10 @@ using AttributeArray = std::array; //! Raster state descriptor struct RasterState { - using CullingMode = CullingMode; - using DepthFunc = SamplerCompareFunc; - using BlendEquation = BlendEquation; - using BlendFunction = BlendFunction; + using CullingMode = backend::CullingMode; + using DepthFunc = backend::SamplerCompareFunc; + using BlendEquation = backend::BlendEquation; + using BlendFunction = backend::BlendFunction; RasterState() noexcept { // NOLINT static_assert(sizeof(RasterState) == sizeof(uint32_t), From 5e0615040d36c50c3c4f6f359f1e8873ce7b8921 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Mon, 10 Jan 2022 11:59:14 -0800 Subject: [PATCH 02/22] refactor our ostream The main change here is that we factorize all the interesting code in a single function. This should result in smaller code. --- libs/utils/include/utils/ostream.h | 31 +++--- libs/utils/src/ostream.cpp | 155 ++++++++++++----------------- 2 files changed, 80 insertions(+), 106 deletions(-) diff --git a/libs/utils/include/utils/ostream.h b/libs/utils/include/utils/ostream.h index 4d1a9b9035..723f61f8a0 100644 --- a/libs/utils/include/utils/ostream.h +++ b/libs/utils/include/utils/ostream.h @@ -18,12 +18,12 @@ #define TNT_UTILS_OSTREAM_H #include +#include #include #include // ssize_t is a POSIX type. -namespace utils { -namespace io { +namespace utils::io { class UTILS_PUBLIC ostream { public: @@ -70,19 +70,26 @@ protected: Buffer(const Buffer&) = delete; Buffer& operator=(const Buffer&) = delete; + const char* get() const noexcept { return buffer; } + + std::pair grow(size_t s) noexcept; + void advance(ssize_t n) noexcept; + void reset() noexcept; + + private: + void reserve(size_t newSize) noexcept; + char* buffer = nullptr; // buffer address char* curr = nullptr; // current pointer size_t size = 0; // size remaining size_t capacity = 0; // total capacity of the buffer - const char* get() const noexcept { return buffer; } - void advance(ssize_t n) noexcept; - void reset() noexcept; - void reserve(size_t newSize) noexcept; }; Buffer mData; Buffer& getBuffer() noexcept { return mData; } + ostream& print(const char* format, ...) noexcept; + private: virtual ostream& flush() noexcept = 0; @@ -96,13 +103,9 @@ private: LONG_DOUBLE }; - bool mShowHex = false; - const char* getFormat(type t) const noexcept; + inline const char* getFormat(type t) const noexcept; - /* - * Checks that the buffer has room for s additional bytes, growing the allocation if necessary. - */ - void growBufferIfNeeded(size_t s) noexcept; + bool mShowHex = false; }; // handles std::string @@ -129,8 +132,6 @@ inline ostream& dec(ostream& s) noexcept { return s.dec(); } inline ostream& endl(ostream& s) noexcept { s << "\n"; return s.flush(); } inline ostream& flush(ostream& s) noexcept { return s.flush(); } -} // namespace io - -} // namespace utils +} // namespace utils::io #endif // TNT_UTILS_OSTREAM_H diff --git a/libs/utils/src/ostream.cpp b/libs/utils/src/ostream.cpp index 5eeaf67f2c..0e5c37a9b4 100644 --- a/libs/utils/src/ostream.cpp +++ b/libs/utils/src/ostream.cpp @@ -18,11 +18,10 @@ #include #include +#include #include -namespace utils { - -namespace io { +namespace utils::io { ostream::~ostream() = default; @@ -35,7 +34,6 @@ ostream::Buffer::~Buffer() noexcept { free(buffer); } -UTILS_NOINLINE void ostream::Buffer::advance(ssize_t n) noexcept { if (n > 0) { size_t written = n < size ? size_t(n) : size; @@ -44,7 +42,6 @@ void ostream::Buffer::advance(ssize_t n) noexcept { } } -UTILS_NOINLINE void ostream::Buffer::reserve(size_t newSize) noexcept { size_t offset = curr - buffer; if (buffer == nullptr) { @@ -58,7 +55,6 @@ void ostream::Buffer::reserve(size_t newSize) noexcept { size = capacity - offset; } -UTILS_NOINLINE void ostream::Buffer::reset() noexcept { // aggressively shrink the buffer if (capacity > 1024) { @@ -70,6 +66,17 @@ void ostream::Buffer::reset() noexcept { size = capacity; } + +std::pair ostream::Buffer::grow(size_t s) noexcept { + if (UTILS_UNLIKELY(size < s)) { + size_t used = curr - buffer; + size_t newCapacity = std::max(size_t(32), used + (s * 3 + 1) / 2); // 32 bytes minimum + reserve(newCapacity); + assert(size >= s); + } + return { curr, size }; +} + const char* ostream::getFormat(ostream::type t) const noexcept { switch (t) { case type::SHORT: return mShowHex ? "0x%hx" : "%hd"; @@ -87,94 +94,80 @@ const char* ostream::getFormat(ostream::type t) const noexcept { } } -void ostream::growBufferIfNeeded(size_t s) noexcept { +UTILS_NOINLINE +ostream& ostream::print(const char* format, ...) noexcept { + va_list args0; + va_list args1; + + // figure out how much size to we need + va_start(args0, format); + va_copy(args1, args0); + ssize_t s = vsnprintf(nullptr, 0, format, args0); + va_end(args0); + + // grow the buffer to the needed size Buffer& buf = getBuffer(); - if (UTILS_UNLIKELY(buf.size < s)) { - size_t used = buf.curr - buf.buffer; - size_t newCapacity = std::max(size_t(32), used + (s * 3 + 1) / 2); // 32 bytes minimum - buf.reserve(newCapacity); - assert(buf.size >= s); - } + auto [curr, size] = buf.grow(s + 1); // +1 to include the null-terminator + + // print into the buffer + vsnprintf(curr, size, format, args1); + + // advance the buffer + buf.advance(s); + + va_end(args1); + + return *this; } ostream& ostream::operator<<(short value) noexcept { - Buffer& buf = getBuffer(); - size_t s = snprintf(nullptr, 0, getFormat(type::SHORT), value); - growBufferIfNeeded(s + 1); // +1 to include the null-terminator - buf.advance(snprintf(buf.curr, buf.size, getFormat(type::SHORT), value)); - return *this; + const char* format = getFormat(type::SHORT); + return print(format, value); } ostream& ostream::operator<<(unsigned short value) noexcept { - Buffer& buf = getBuffer(); - size_t s = snprintf(nullptr, 0, getFormat(type::USHORT), value); - growBufferIfNeeded(s + 1); // +1 to include the null-terminator - buf.advance(snprintf(buf.curr, buf.size, getFormat(type::USHORT), value)); - return *this; + const char* format = getFormat(type::USHORT); + return print(format, value); } ostream& ostream::operator<<(char value) noexcept { - Buffer& buf = getBuffer(); - size_t s = snprintf(nullptr, 0, getFormat(type::CHAR), value); - growBufferIfNeeded(s + 1); // +1 to include the null-terminator - buf.advance(snprintf(buf.curr, buf.size, getFormat(type::CHAR), value)); - return *this; + const char* format = getFormat(type::CHAR); + return print(format, value); } ostream& ostream::operator<<(unsigned char value) noexcept { - Buffer& buf = getBuffer(); - size_t s = snprintf(nullptr, 0, getFormat(type::UCHAR), value); - growBufferIfNeeded(s + 1); // +1 to include the null-terminator - buf.advance(snprintf(buf.curr, buf.size, getFormat(type::UCHAR), value)); - return *this; + const char* format = getFormat(type::UCHAR); + return print(format, value); } ostream& ostream::operator<<(int value) noexcept { - Buffer& buf = getBuffer(); - size_t s = snprintf(nullptr, 0, getFormat(type::INT), value); - growBufferIfNeeded(s + 1); // +1 to include the null-terminator - buf.advance(snprintf(buf.curr, buf.size, getFormat(type::INT), value)); - return *this; + const char* format = getFormat(type::INT); + return print(format, value); } ostream& ostream::operator<<(unsigned int value) noexcept { - Buffer& buf = getBuffer(); - size_t s = snprintf(nullptr, 0, getFormat(type::UINT), value); - growBufferIfNeeded(s + 1); // +1 to include the null-terminator - buf.advance(snprintf(buf.curr, buf.size, getFormat(type::UINT), value)); - return *this; + const char* format = getFormat(type::UINT); + return print(format, value); } ostream& ostream::operator<<(long value) noexcept { - Buffer& buf = getBuffer(); - size_t s = snprintf(nullptr, 0, getFormat(type::LONG), value); - growBufferIfNeeded(s + 1); // +1 to include the null-terminator - buf.advance(snprintf(buf.curr, buf.size, getFormat(type::LONG), value)); - return *this; + const char* format = getFormat(type::LONG); + return print(format, value); } ostream& ostream::operator<<(unsigned long value) noexcept { - Buffer& buf = getBuffer(); - size_t s = snprintf(nullptr, 0, getFormat(type::ULONG), value); - growBufferIfNeeded(s + 1); // +1 to include the null-terminator - buf.advance(snprintf(buf.curr, buf.size, getFormat(type::ULONG), value)); - return *this; + const char* format = getFormat(type::ULONG); + return print(format, value); } ostream& ostream::operator<<(long long value) noexcept { - Buffer& buf = getBuffer(); - size_t s = snprintf(nullptr, 0, getFormat(type::LONG_LONG), value); - growBufferIfNeeded(s + 1); // +1 to include the null-terminator - buf.advance(snprintf(buf.curr, buf.size, getFormat(type::LONG_LONG), value)); - return *this; + const char* format = getFormat(type::LONG_LONG); + return print(format, value); } ostream& ostream::operator<<(unsigned long long value) noexcept { - Buffer& buf = getBuffer(); - size_t s = snprintf(nullptr, 0, getFormat(type::ULONG_LONG), value); - growBufferIfNeeded(s + 1); // +1 to include the null-terminator - buf.advance(snprintf(buf.curr, buf.size, getFormat(type::ULONG_LONG), value)); - return *this; + const char* format = getFormat(type::ULONG_LONG); + return print(format, value); } ostream& ostream::operator<<(float value) noexcept { @@ -182,19 +175,13 @@ ostream& ostream::operator<<(float value) noexcept { } ostream& ostream::operator<<(double value) noexcept { - Buffer& buf = getBuffer(); - size_t s = snprintf(nullptr, 0, getFormat(type::DOUBLE), value); - growBufferIfNeeded(s + 1); // +1 to include the null-terminator - buf.advance(snprintf(buf.curr, buf.size, getFormat(type::DOUBLE), value)); - return *this; + const char* format = getFormat(type::DOUBLE); + return print(format, value); } ostream& ostream::operator<<(long double value) noexcept { - Buffer& buf = getBuffer(); - size_t s = snprintf(nullptr, 0, getFormat(type::LONG_DOUBLE), value); - growBufferIfNeeded(s + 1); // +1 to include the null-terminator - buf.advance(snprintf(buf.curr, buf.size, getFormat(type::LONG_DOUBLE), value)); - return *this; + const char* format = getFormat(type::LONG_DOUBLE); + return print(format, value); } ostream& ostream::operator<<(bool value) noexcept { @@ -202,27 +189,15 @@ ostream& ostream::operator<<(bool value) noexcept { } ostream& ostream::operator<<(const char* string) noexcept { - Buffer& buf = getBuffer(); - size_t s = snprintf(nullptr, 0, "%s", string); - growBufferIfNeeded(s + 1); // +1 to include the null-terminator - buf.advance(snprintf(buf.curr, buf.size, "%s", string)); - return *this; + return print("%s", string); } ostream& ostream::operator<<(const unsigned char* string) noexcept { - Buffer& buf = getBuffer(); - size_t s = snprintf(nullptr, 0, "%s", string); - growBufferIfNeeded(s + 1); // +1 to include the null-terminator - buf.advance(snprintf(buf.curr, buf.size, "%s", string)); - return *this; + return print("%s", string); } ostream& ostream::operator<<(const void* value) noexcept { - Buffer& buf = getBuffer(); - size_t s = snprintf(nullptr, 0, "%p", value); - growBufferIfNeeded(s + 1); // +1 to include the null-terminator - buf.advance(snprintf(buf.curr, buf.size, "%p", value)); - return *this; + return print("%p", value); } ostream& ostream::hex() noexcept { @@ -235,6 +210,4 @@ ostream& ostream::dec() noexcept { return *this; } -} // namespace io - -} // namespace utils +} // namespace utils::io From 047543a4a6fab6c32407a452e8413ccb075e9d03 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Mon, 10 Jan 2022 12:07:32 -0800 Subject: [PATCH 03/22] make our ostream thread safe ostream internals are now protected by a lock, which ensures the internal state stays consistant, however, this won't prevent multiple threads to have their output ominterfering with each other, which is no different from the stl behavior. This fixes #4992 --- libs/utils/include/utils/ostream.h | 2 ++ libs/utils/src/Log.cpp | 9 +++++++++ libs/utils/src/ostream.cpp | 20 +++++++++++++------- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/libs/utils/include/utils/ostream.h b/libs/utils/include/utils/ostream.h index 723f61f8a0..1c5e26029d 100644 --- a/libs/utils/include/utils/ostream.h +++ b/libs/utils/include/utils/ostream.h @@ -17,6 +17,7 @@ #ifndef TNT_UTILS_OSTREAM_H #define TNT_UTILS_OSTREAM_H +#include #include #include @@ -85,6 +86,7 @@ protected: size_t capacity = 0; // total capacity of the buffer }; + std::mutex mLock; Buffer mData; Buffer& getBuffer() noexcept { return mData; } diff --git a/libs/utils/src/Log.cpp b/libs/utils/src/Log.cpp index 4d3234193d..321707672a 100644 --- a/libs/utils/src/Log.cpp +++ b/libs/utils/src/Log.cpp @@ -46,6 +46,7 @@ private: }; ostream& LogStream::flush() noexcept { + std::lock_guard lock(mLock); Buffer& buf = getBuffer(); #ifdef __ANDROID__ switch (mPriority) { @@ -86,6 +87,14 @@ ostream& LogStream::flush() noexcept { return *this; } + +/* + * We can't use thread_local because on Android we're currently using several dynamic libraries + * including this .o (via libutils.a), which violates the ODR and ends-up with only one of + * the thread_local instance initialized. + * For this reason, ostream is protected by a mutex instead. + */ + static LogStream cout(LogStream::Priority::LOG_DEBUG); static LogStream cerr(LogStream::Priority::LOG_ERROR); static LogStream cwarn(LogStream::Priority::LOG_WARNING); diff --git a/libs/utils/src/ostream.cpp b/libs/utils/src/ostream.cpp index 0e5c37a9b4..ec154a8824 100644 --- a/libs/utils/src/ostream.cpp +++ b/libs/utils/src/ostream.cpp @@ -105,15 +105,21 @@ ostream& ostream::print(const char* format, ...) noexcept { ssize_t s = vsnprintf(nullptr, 0, format, args0); va_end(args0); - // grow the buffer to the needed size - Buffer& buf = getBuffer(); - auto [curr, size] = buf.grow(s + 1); // +1 to include the null-terminator - // print into the buffer - vsnprintf(curr, size, format, args1); + { // scope for the lock + std::lock_guard lock(mLock); - // advance the buffer - buf.advance(s); + Buffer& buf = getBuffer(); + + // grow the buffer to the needed size + auto[curr, size] = buf.grow(s + 1); // +1 to include the null-terminator + + // print into the buffer + vsnprintf(curr, size, format, args1); + + // advance the buffer + buf.advance(s); + } va_end(args1); From fb1eb35ba5557d161b5bc82a9f0979b3e2977942 Mon Sep 17 00:00:00 2001 From: Ankbzpx Date: Tue, 11 Jan 2022 07:06:23 +0800 Subject: [PATCH 04/22] Fix aabb misalignment for skinned gltf model (#4973) --- .../android/filament/utils/ModelViewer.kt | 3 +- .../src/main/cpp/ResourceLoader.cpp | 5 +- .../filament/gltfio/ResourceLoader.java | 9 +- docs/remote/filament.js | 6 +- docs/viewer/filament-viewer.js | 4 +- docs/webgl/filament.js | 6 +- libs/gltfio/include/gltfio/ResourceLoader.h | 4 + libs/gltfio/src/ResourceLoader.cpp | 107 ++++++++++++++++-- samples/gltf_viewer.cpp | 32 ++++-- web/filament-js/extensions.js | 6 +- web/filament-js/filament-viewer.js | 4 +- web/filament-js/jsbindings.cpp | 5 +- 12 files changed, 150 insertions(+), 41 deletions(-) diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt index b9eedd015b..a36c89501c 100644 --- a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt @@ -71,6 +71,7 @@ class ModelViewer(val engine: Engine) : android.view.View.OnTouchListener { var normalizeSkinningWeights = true var recomputeBoundingBoxes = false + var ignoreBindTransform = false var cameraFocalLength = 28f set(value) { @@ -111,7 +112,7 @@ class ModelViewer(val engine: Engine) : android.view.View.OnTouchListener { view.camera = camera assetLoader = AssetLoader(engine, UbershaderLoader(engine), EntityManager.get()) - resourceLoader = ResourceLoader(engine, normalizeSkinningWeights, recomputeBoundingBoxes) + resourceLoader = ResourceLoader(engine, normalizeSkinningWeights, recomputeBoundingBoxes, ignoreBindTransform) // Always add a direct light source since it is required for shadowing. // We highly recommend adding an indirect light as well. diff --git a/android/gltfio-android/src/main/cpp/ResourceLoader.cpp b/android/gltfio-android/src/main/cpp/ResourceLoader.cpp index 2795eb6e7f..c16ba604ed 100644 --- a/android/gltfio-android/src/main/cpp/ResourceLoader.cpp +++ b/android/gltfio-android/src/main/cpp/ResourceLoader.cpp @@ -35,10 +35,11 @@ static void destroy(void*, size_t, void *userData) { extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_gltfio_ResourceLoader_nCreateResourceLoader(JNIEnv*, jclass, - jlong nativeEngine, jboolean normalizeSkinningWeights, jboolean recomputeBoundingBoxes) { + jlong nativeEngine, jboolean normalizeSkinningWeights, jboolean recomputeBoundingBoxes, + jboolean ignoreBindTransform) { Engine* engine = (Engine*) nativeEngine; return (jlong) new ResourceLoader({ engine, {}, (bool) normalizeSkinningWeights, - (bool) recomputeBoundingBoxes }); + (bool) recomputeBoundingBoxes, (bool) ignoreBindTransform}); } extern "C" JNIEXPORT void JNICALL diff --git a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/ResourceLoader.java b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/ResourceLoader.java index bd39de2aaf..976a752caf 100644 --- a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/ResourceLoader.java +++ b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/ResourceLoader.java @@ -45,7 +45,7 @@ public class ResourceLoader { */ public ResourceLoader(@NonNull Engine engine) { long nativeEngine = engine.getNativeObject(); - mNativeObject = nCreateResourceLoader(nativeEngine, false, false); + mNativeObject = nCreateResourceLoader(nativeEngine, false, false, false); } /** @@ -54,14 +54,15 @@ public class ResourceLoader { * @param engine the engine that gets passed to all builder methods * @param normalizeSkinningWeights scale non-conformant skinning weights so they sum to 1 * @param recomputeBoundingBoxes use computed bounding boxes rather than the ones in the asset + * @param ignoreBindTransform ignore skinned primitives bind transform when compute bounding boxes * @throws IllegalAccessException * @throws InvocationTargetException */ public ResourceLoader(@NonNull Engine engine, boolean normalizeSkinningWeights, - boolean recomputeBoundingBoxes) { + boolean recomputeBoundingBoxes, boolean ignoreBindTransform) { long nativeEngine = engine.getNativeObject(); mNativeObject = nCreateResourceLoader(nativeEngine, normalizeSkinningWeights, - recomputeBoundingBoxes); + recomputeBoundingBoxes, ignoreBindTransform); } /** @@ -166,7 +167,7 @@ public class ResourceLoader { } private static native long nCreateResourceLoader(long nativeEngine, - boolean normalizeSkinningWeights, boolean recomputeBoundingBoxes); + boolean normalizeSkinningWeights, boolean recomputeBoundingBoxes, boolean ignoreBindTransform); private static native void nDestroyResourceLoader(long nativeLoader); private static native void nAddResourceData(long nativeLoader, String url, Buffer buffer, int remaining); diff --git a/docs/remote/filament.js b/docs/remote/filament.js index e70870f6f4..e101956fdd 100644 --- a/docs/remote/filament.js +++ b/docs/remote/filament.js @@ -713,7 +713,8 @@ Filament.loadClassExtensions = function() { const interval = asyncInterval || 30; const defaults = { normalizeSkinningWeights: true, - recomputeBoundingBoxes: false + recomputeBoundingBoxes: false, + ignoreBindTransform: false }; config = Object.assign(defaults, config || {}); @@ -736,7 +737,8 @@ Filament.loadClassExtensions = function() { // Construct a resource loader and start decoding after all textures are fetched. const resourceLoader = new Filament.gltfio$ResourceLoader(engine, config.normalizeSkinningWeights, - config.recomputeBoundingBoxes); + config.recomputeBoundingBoxes, + config.ignoreBindTransform); const onComplete = () => { resourceLoader.asyncBeginLoad(asset); diff --git a/docs/viewer/filament-viewer.js b/docs/viewer/filament-viewer.js index 594f6ca535..79f2f6b989 100644 --- a/docs/viewer/filament-viewer.js +++ b/docs/viewer/filament-viewer.js @@ -307,6 +307,7 @@ class FilamentViewer extends LitElement { const config = { normalizeSkinningWeights: true, recomputeBoundingBoxes: false, + ignoreBindTransform: false, asyncInterval: 30 }; @@ -333,7 +334,8 @@ class FilamentViewer extends LitElement { const resourceLoader = new Filament.gltfio$ResourceLoader(this.engine, config.normalizeSkinningWeights, - config.recomputeBoundingBoxes); + config.recomputeBoundingBoxes, + config.ignoreBindTransform); let remaining = Object.keys(this.srcBlobResources).length; for (const name in this.srcBlobResources) { diff --git a/docs/webgl/filament.js b/docs/webgl/filament.js index 1e65c86c3b..b2a6327518 100644 --- a/docs/webgl/filament.js +++ b/docs/webgl/filament.js @@ -713,7 +713,8 @@ Filament.loadClassExtensions = function() { const interval = asyncInterval || 30; const defaults = { normalizeSkinningWeights: true, - recomputeBoundingBoxes: false + recomputeBoundingBoxes: false, + ignoreBindTransform: false }; config = Object.assign(defaults, config || {}); @@ -736,7 +737,8 @@ Filament.loadClassExtensions = function() { // Construct a resource loader and start decoding after all textures are fetched. const resourceLoader = new Filament.gltfio$ResourceLoader(engine, config.normalizeSkinningWeights, - config.recomputeBoundingBoxes); + config.recomputeBoundingBoxes, + config.ignoreBindTransform); const onComplete = () => { resourceLoader.asyncBeginLoad(asset); diff --git a/libs/gltfio/include/gltfio/ResourceLoader.h b/libs/gltfio/include/gltfio/ResourceLoader.h index b69a16e565..a92a4f8517 100644 --- a/libs/gltfio/include/gltfio/ResourceLoader.h +++ b/libs/gltfio/include/gltfio/ResourceLoader.h @@ -52,6 +52,10 @@ struct ResourceConfiguration { //! If true, computes the bounding boxes of all \c POSITION attibutes. Well formed glTF files //! do not need this, but it is useful for robustness. bool recomputeBoundingBoxes; + + //! If true, ignore skinned primitives bind transform when compute bounding box. Implicitly true + //! for instanced asset. Only applicable when recomputeBoundingBoxes is set to true + bool ignoreBindTransform; }; /** diff --git a/libs/gltfio/src/ResourceLoader.cpp b/libs/gltfio/src/ResourceLoader.cpp index 179bea3e06..4d7142b9dd 100644 --- a/libs/gltfio/src/ResourceLoader.cpp +++ b/libs/gltfio/src/ResourceLoader.cpp @@ -86,13 +86,19 @@ struct ResourceLoader::Impl { mEngine = config.engine; mNormalizeSkinningWeights = config.normalizeSkinningWeights; mRecomputeBoundingBoxes = config.recomputeBoundingBoxes; + mIgnoreBindTransform = config.ignoreBindTransform; } Engine* mEngine; bool mNormalizeSkinningWeights; bool mRecomputeBoundingBoxes; + bool mIgnoreBindTransform; std::string mGltfPath; + // This is used to calculate skinIndex when updateBoundingBoxes, so that the correspondency between + // cgltf_node* and FFilamentInstance::Skin can be retrieved. This pointer doesn't need to be freed. + cgltf_skin* cgltfSkinBaseAddress; + // User-provided resource data with URI string keys, populated with addResourceData(). // This is used on platforms without traditional file systems, such as Android, iOS, and WebGL. UriDataCache mUriDataCache; @@ -458,6 +464,11 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) { } if (pImpl->mRecomputeBoundingBoxes) { + // asset->mSkins is unused for instanced assets + if (!pImpl->mIgnoreBindTransform) { + pImpl->mIgnoreBindTransform = asset->isInstanced(); + } + pImpl->cgltfSkinBaseAddress = &gltf->skins[0]; updateBoundingBoxes(asset); } @@ -1005,27 +1016,99 @@ void ResourceLoader::updateBoundingBoxes(FFilamentAsset* asset) const { *result = aabb; }; - // Collect all mesh primitives that we wish to find bounds for. - std::vector prims; + const size_t posAttrSize = cgltf_num_components(cgltf_type_vec3); + const size_t skinningAttrSize = cgltf_num_components(cgltf_type_vec4); + auto computeBoundingBoxSkinned = [&](const cgltf_primitive* prim, const Skin* skin, Aabb* result) { + Aabb aabb; + std::vector inverseGlobalTransforms(skin->targets.size()); + for (size_t i = 0; i < skin->targets.size(); i++) { + auto xformable = tm.getInstance(skin->targets[i]); + if (xformable) { + inverseGlobalTransforms[i] = inverse(tm.getWorldTransform(xformable)); + } + } + std::vector verts; + std::vector rawJoints; + std::vector weights; + for (cgltf_size slot = 0; slot < prim->attributes_count; slot++) { + const cgltf_attribute& attr = prim->attributes[slot]; + const cgltf_accessor* accessor = attr.data; + if (attr.type == cgltf_attribute_type_position && cgltf_num_components(accessor->type) >= posAttrSize) { + verts.resize(accessor->count * posAttrSize); + cgltf_accessor_unpack_floats(accessor, &verts[0], accessor->count * posAttrSize); + } + if (attr.type == cgltf_attribute_type_joints && cgltf_num_components(accessor->type) >= skinningAttrSize) { + rawJoints.resize(accessor->count * skinningAttrSize); + cgltf_accessor_unpack_floats(accessor, &rawJoints[0], accessor->count * skinningAttrSize); + } + if (attr.type == cgltf_attribute_type_weights && cgltf_num_components(accessor->type) >= skinningAttrSize) { + weights.resize(accessor->count * skinningAttrSize); + cgltf_accessor_unpack_floats(accessor, &weights[0], accessor->count * skinningAttrSize); + } + } + std::vector jointIndices(rawJoints.begin(), rawJoints.end()); + auto primitiveCount = static_cast(verts.size() / posAttrSize); + for (size_t i = 0; i < primitiveCount; i++) { + float3 point(verts[posAttrSize * i], verts[posAttrSize * i + 1], verts[posAttrSize * i + 2]); + mat4f tmp = mat4f(0.0f); + for (size_t j = 0; j < skinningAttrSize; j++) { + size_t jointIndex = jointIndices[skinningAttrSize * i + j]; + float jointWeight = weights[skinningAttrSize * i + j]; + Entity jointEntity = skin->joints[jointIndex]; + mat4f globalJointTransform = tm.getWorldTransform(tm.getInstance(jointEntity)); + mat4f inverseBindMatrix = skin->inverseBindMatrices[jointIndex]; + tmp += jointWeight * globalJointTransform * inverseBindMatrix; + } + for (const auto& inverseGlobalTransform: inverseGlobalTransforms) { + mat4f skinMatrix = inverseGlobalTransform * tmp; + if (!pImpl->mNormalizeSkinningWeights) { + skinMatrix /= skinMatrix[3].w; + } + float3 skinnedPoint = (point.x * skinMatrix[0] + point.y * skinMatrix[1] + point.z * skinMatrix[2] + skinMatrix[3]).xyz; + aabb.min = min(aabb.min, skinnedPoint); + aabb.max = max(aabb.max, skinnedPoint); + } + } + *result = aabb; + }; + + // Collect all mesh primitives that we wish to find bounds for. For each mesh primitive, we also + // collect the skin it bound to (nullptr if not skinned) for bounds computation. + std::vector> primitives; for (auto iter : nodeMap) { - const cgltf_mesh* mesh = iter.first->mesh; - if (mesh) { - for (cgltf_size index = 0, nprims = mesh->primitives_count; index < nprims; ++index) { - prims.push_back(&mesh->primitives[index]); + const Skin* skin = nullptr; + cgltf_skin* const cgltfSkin = iter.first->skin; + if (cgltfSkin) { + // importSkins unpacked cgltfSkin into FFilamentInstance::SkinVector bijectively so that + // the unpacked Skin can be retrieved given cgltfSkin index + int skinIndex = cgltfSkin - pImpl->cgltfSkinBaseAddress; + skin = &asset->mSkins[skinIndex]; + } + const cgltf_mesh* cgltfMesh = iter.first->mesh; + if (cgltfMesh) { + for (cgltf_size index = 0, nprims = cgltfMesh->primitives_count; index < nprims; ++index) { + primitives.push_back({&cgltfMesh->primitives[index], skin}); } } } // Kick off a bounding box job for every primitive. - std::vector bounds(prims.size()); + std::vector bounds(primitives.size()); JobSystem* js = &pImpl->mEngine->getJobSystem(); JobSystem::Job* parent = js->createJob(); - for (size_t i = 0; i < prims.size(); ++i) { - cgltf_primitive const* prim = prims[i]; + for (size_t i = 0; i < primitives.size(); ++i) { Aabb* result = &bounds[i]; - js->run(jobs::createJob(*js, parent, [prim, result, computeBoundingBox] { - computeBoundingBox(prim, result); - })); + if (pImpl->mIgnoreBindTransform || !primitives[i].second) { + cgltf_primitive const* prim = primitives[i].first; + js->run(jobs::createJob(*js, parent, [prim, result, computeBoundingBox] { + computeBoundingBox(prim, result); + })); + } else { + std::pair skinnedPrimitive = primitives[i]; + js->run(jobs::createJob(*js, parent, [skinnedPrimitive, result, computeBoundingBoxSkinned] { + computeBoundingBoxSkinned(skinnedPrimitive.first, skinnedPrimitive.second, result); + })); + } } js->runAndWait(parent); diff --git a/samples/gltf_viewer.cpp b/samples/gltf_viewer.cpp index 60d886ebb5..140271ec56 100644 --- a/samples/gltf_viewer.cpp +++ b/samples/gltf_viewer.cpp @@ -85,6 +85,7 @@ struct App { gltfio::ResourceLoader* resourceLoader = nullptr; bool recomputeAabb = false; + bool ignoreBindTransform = false; bool actualSize = false; @@ -133,6 +134,8 @@ static void printUsage(char* name) { " Do not scale the model to fit into a unit cube\n\n" " --recompute-aabb, -r\n" " Ignore the min/max attributes in the glTF file\n\n" + " --ignore-bind-transform, -g\n" + " Ignore bind transform when recomputing aabb\n\n" " --settings=, -t\n" " Apply the settings in the given JSON file\n\n" " --ubershader, -u\n" @@ -161,19 +164,20 @@ static std::ifstream::pos_type getFileSize(const char* filename) { } static int handleCommandLineArguments(int argc, char* argv[], App* app) { - static constexpr const char* OPTSTR = "ha:i:usc:rt:b:ev"; + static constexpr const char* OPTSTR = "ha:i:usc:rgt:b:ev"; static const struct option OPTIONS[] = { - { "help", no_argument, nullptr, 'h' }, - { "api", required_argument, nullptr, 'a' }, - { "batch", required_argument, nullptr, 'b' }, - { "headless", no_argument, nullptr, 'e' }, - { "ibl", required_argument, nullptr, 'i' }, - { "ubershader", no_argument, nullptr, 'u' }, - { "actual-size", no_argument, nullptr, 's' }, - { "camera", required_argument, nullptr, 'c' }, - { "recompute-aabb", no_argument, nullptr, 'r' }, - { "settings", required_argument, nullptr, 't' }, - { "split-view", no_argument, nullptr, 'v' }, + { "help", no_argument, nullptr, 'h' }, + { "api", required_argument, nullptr, 'a' }, + { "batch", required_argument, nullptr, 'b' }, + { "headless", no_argument, nullptr, 'e' }, + { "ibl", required_argument, nullptr, 'i' }, + { "ubershader", no_argument, nullptr, 'u' }, + { "actual-size", no_argument, nullptr, 's' }, + { "camera", required_argument, nullptr, 'c' }, + { "recompute-aabb", no_argument, nullptr, 'r' }, + { "ignore-bind-transform", no_argument, nullptr, 'g' }, + { "settings", required_argument, nullptr, 't' }, + { "split-view", no_argument, nullptr, 'v' }, { nullptr, 0, nullptr, 0 } }; int opt; @@ -220,6 +224,9 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) { case 'r': app->recomputeAabb = true; break; + case 'g': + app->ignoreBindTransform = true; + break; case 't': app->settingsFile = arg; break; @@ -412,6 +419,7 @@ int main(int argc, char** argv) { configuration.engine = app.engine; configuration.gltfPath = gltfPath.c_str(); configuration.recomputeBoundingBoxes = app.recomputeAabb; + configuration.ignoreBindTransform = app.ignoreBindTransform; configuration.normalizeSkinningWeights = true; if (!app.resourceLoader) { app.resourceLoader = new gltfio::ResourceLoader(configuration); diff --git a/web/filament-js/extensions.js b/web/filament-js/extensions.js index 1684e11b75..346ff848dd 100644 --- a/web/filament-js/extensions.js +++ b/web/filament-js/extensions.js @@ -578,7 +578,8 @@ Filament.loadClassExtensions = function() { const interval = asyncInterval || 30; const defaults = { normalizeSkinningWeights: true, - recomputeBoundingBoxes: false + recomputeBoundingBoxes: false, + ignoreBindTransform: false }; config = Object.assign(defaults, config || {}); @@ -601,7 +602,8 @@ Filament.loadClassExtensions = function() { // Construct a resource loader and start decoding after all textures are fetched. const resourceLoader = new Filament.gltfio$ResourceLoader(engine, config.normalizeSkinningWeights, - config.recomputeBoundingBoxes); + config.recomputeBoundingBoxes, + config.ignoreBindTransform); const onComplete = () => { resourceLoader.asyncBeginLoad(asset); diff --git a/web/filament-js/filament-viewer.js b/web/filament-js/filament-viewer.js index 594f6ca535..79f2f6b989 100644 --- a/web/filament-js/filament-viewer.js +++ b/web/filament-js/filament-viewer.js @@ -307,6 +307,7 @@ class FilamentViewer extends LitElement { const config = { normalizeSkinningWeights: true, recomputeBoundingBoxes: false, + ignoreBindTransform: false, asyncInterval: 30 }; @@ -333,7 +334,8 @@ class FilamentViewer extends LitElement { const resourceLoader = new Filament.gltfio$ResourceLoader(this.engine, config.normalizeSkinningWeights, - config.recomputeBoundingBoxes); + config.recomputeBoundingBoxes, + config.ignoreBindTransform); let remaining = Object.keys(this.srcBlobResources).length; for (const name in this.srcBlobResources) { diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index f2694ecd2b..1c29b48897 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -1910,12 +1910,13 @@ class_("gltfio$AssetLoader") class_("gltfio$ResourceLoader") .constructor(EMBIND_LAMBDA(ResourceLoader*, (Engine* engine, bool normalizeSkinningWeights, - bool recomputeBoundingBoxes), { + bool recomputeBoundingBoxes, bool ignoreBindTransform), { return new ResourceLoader({ .engine = engine, .gltfPath = nullptr, .normalizeSkinningWeights = normalizeSkinningWeights, - .recomputeBoundingBoxes = recomputeBoundingBoxes + .recomputeBoundingBoxes = recomputeBoundingBoxes, + .ignoreBindTransform = ignoreBindTransform }); }), allow_raw_pointers()) From b67963176bb7f0b9972774958acf37fa8b1e8559 Mon Sep 17 00:00:00 2001 From: Jared Fowler Date: Wed, 5 Jan 2022 12:00:57 -0700 Subject: [PATCH 05/22] Moving UiHelper to the constructor which allows for more composable control of the underlying views & surface --- .../android/filament/utils/ModelViewer.kt | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt index a36c89501c..f55d0100ba 100644 --- a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt +++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt @@ -58,7 +58,10 @@ private const val kSensitivity = 100f * * See `sample-gltf-viewer` for a usage example. */ -class ModelViewer(val engine: Engine) : android.view.View.OnTouchListener { +class ModelViewer( + val engine: Engine, + private val uiHelper: UiHelper +) : android.view.View.OnTouchListener { var asset: FilamentAsset? = null private set @@ -85,7 +88,6 @@ class ModelViewer(val engine: Engine) : android.view.View.OnTouchListener { val renderer: Renderer @Entity val light: Int - private val uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK) private lateinit var displayHelper: DisplayHelper private lateinit var cameraManipulator: Manipulator private lateinit var gestureDetector: GestureDetector @@ -130,7 +132,12 @@ class ModelViewer(val engine: Engine) : android.view.View.OnTouchListener { scene.addEntity(light) } - constructor(surfaceView: SurfaceView, engine: Engine = Engine.create(), manipulator: Manipulator? = null) : this(engine) { + constructor( + surfaceView: SurfaceView, + engine: Engine = Engine.create(), + uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK), + manipulator: Manipulator? = null + ) : this(engine, uiHelper) { cameraManipulator = manipulator ?: Manipulator.Builder() .targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z) .viewport(surfaceView.width, surfaceView.height) @@ -145,7 +152,12 @@ class ModelViewer(val engine: Engine) : android.view.View.OnTouchListener { } @Suppress("unused") - constructor(textureView: TextureView, engine: Engine = Engine.create(), manipulator: Manipulator? = null) : this(engine) { + constructor( + textureView: TextureView, + engine: Engine = Engine.create(), + uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK), + manipulator: Manipulator? = null + ) : this(engine, uiHelper) { cameraManipulator = manipulator ?: Manipulator.Builder() .targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z) .viewport(textureView.width, textureView.height) @@ -216,8 +228,8 @@ class ModelViewer(val engine: Engine) : android.view.View.OnTouchListener { fun transformToUnitCube(centerPoint: Float3 = kDefaultObjectPosition) { asset?.let { asset -> val tm = engine.transformManager - var center = asset.boundingBox.center.let { v-> Float3(v[0], v[1], v[2]) } - val halfExtent = asset.boundingBox.halfExtent.let { v-> Float3(v[0], v[1], v[2]) } + var center = asset.boundingBox.center.let { v -> Float3(v[0], v[1], v[2]) } + val halfExtent = asset.boundingBox.halfExtent.let { v -> Float3(v[0], v[1], v[2]) } val maxExtent = 2.0f * max(halfExtent) val scaleFactor = 2.0f / maxExtent center -= centerPoint / scaleFactor @@ -285,9 +297,9 @@ class ModelViewer(val engine: Engine) : android.view.View.OnTouchListener { private fun populateScene(asset: FilamentAsset) { val rcm = engine.renderableManager var count = 0 - val popRenderables = {count = asset.popRenderables(readyRenderables); count != 0} + val popRenderables = { count = asset.popRenderables(readyRenderables); count != 0 } while (popRenderables()) { - for (i in 0..count-1) { + for (i in 0..count - 1) { val ri = rcm.getInstance(readyRenderables[i]) rcm.setScreenSpaceContactShadows(ri, true) } @@ -384,4 +396,4 @@ class ModelViewer(val engine: Engine) : android.view.View.OnTouchListener { companion object { private val kDefaultObjectPosition = Float3(0.0f, 0.0f, -4.0f) } -} +} \ No newline at end of file From 6465b9ade8d8e7b2b9627a1a5b60bb9fa786c592 Mon Sep 17 00:00:00 2001 From: hzzengxiaoqi Date: Fri, 7 Jan 2022 11:50:10 +0800 Subject: [PATCH 06/22] fix bug of GLTFIO_LITE, the macro GLTFRESOURCES_LITE_LIT_BLEND_DATA is not defined anywhere, it should be GLTFRESOURCES_LITE_LIT_FADE_DATA. This solves the issue of rendering transparent objects using GLTFIO_LITE macro, i.e. using gltfresources_lite.bin. If not change this macro, the transparent objects will be rended using LIT_OPAQUE material instead of LIT_FADE material. --- libs/gltfio/src/UbershaderLoader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gltfio/src/UbershaderLoader.cpp b/libs/gltfio/src/UbershaderLoader.cpp index c7874462c1..b9323b2b88 100644 --- a/libs/gltfio/src/UbershaderLoader.cpp +++ b/libs/gltfio/src/UbershaderLoader.cpp @@ -131,7 +131,7 @@ Material* UbershaderLoader::getMaterial(const MaterialKey& config) const { case MATINDEX(LIT, AlphaMode::OPAQUE, false, false, false): mMaterials[matindex] = CREATE_MATERIAL(LIT_OPAQUE); break; #endif - #if !GLTFIO_LITE || defined(GLTFRESOURCES_LITE_LIT_BLEND_DATA) + #if !GLTFIO_LITE || defined(GLTFRESOURCES_LITE_LIT_FADE_DATA) case MATINDEX(LIT, AlphaMode::BLEND, false, false, false): mMaterials[matindex] = CREATE_MATERIAL(LIT_FADE); break; #endif From 954f9112e43f8aacab36e24e12a58a08909a9a59 Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Wed, 12 Jan 2022 09:55:35 -0800 Subject: [PATCH 07/22] Replace generic tonemap curve with a simpler one (#5041) * Replace generic tonemap curve with a simpler one This change removes the shoulder parameter which had inconsistent behaviors. The new curve is simpler and designed to still match by default the gray point and the contrast of ACES in a bright surround. * Fix build --- .../src/main/cpp/ToneMapper.cpp | 15 +---- .../google/android/filament/ToneMapper.java | 28 ++------ filament/include/filament/ToneMapper.h | 12 +--- filament/src/ColorGrading.cpp | 10 +-- filament/src/ColorSpace.h | 58 ++++++++++++----- filament/src/ToneMapper.cpp | 65 +++++-------------- libs/math/include/math/TVecHelpers.h | 7 ++ libs/viewer/include/viewer/Settings.h | 5 +- libs/viewer/src/Settings.cpp | 9 +-- libs/viewer/src/SimpleViewer.cpp | 2 - libs/viewer/tests/test_settings.cpp | 1 - 11 files changed, 82 insertions(+), 130 deletions(-) diff --git a/android/filament-android/src/main/cpp/ToneMapper.cpp b/android/filament-android/src/main/cpp/ToneMapper.cpp index 9318d450fd..ea040538cd 100644 --- a/android/filament-android/src/main/cpp/ToneMapper.cpp +++ b/android/filament-android/src/main/cpp/ToneMapper.cpp @@ -49,8 +49,8 @@ Java_com_google_android_filament_ToneMapper_nCreateFilmicToneMapper(JNIEnv*, jcl extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_ToneMapper_nCreateGenericToneMapper(JNIEnv*, jclass, - jfloat contrast, jfloat shoulder, jfloat midGrayIn, jfloat midGrayOut, jfloat hdrMax) { - return (jlong) new GenericToneMapper(contrast, shoulder, midGrayIn, midGrayOut, hdrMax); + jfloat contrast, jfloat midGrayIn, jfloat midGrayOut, jfloat hdrMax) { + return (jlong) new GenericToneMapper(contrast, midGrayIn, midGrayOut, hdrMax); } extern "C" JNIEXPORT jfloat JNICALL @@ -58,11 +58,6 @@ Java_com_google_android_filament_ToneMapper_nGenericGetContrast(JNIEnv*, jclass, return ((GenericToneMapper*) nativeObject)->getContrast(); } -extern "C" JNIEXPORT jfloat JNICALL -Java_com_google_android_filament_ToneMapper_nGenericGetShoulder(JNIEnv*, jclass, jlong nativeObject) { - return ((GenericToneMapper*) nativeObject)->getShoulder(); -} - extern "C" JNIEXPORT jfloat JNICALL Java_com_google_android_filament_ToneMapper_nGenericGetMidGrayIn(JNIEnv*, jclass, jlong nativeObject) { return ((GenericToneMapper*) nativeObject)->getMidGrayIn(); @@ -84,12 +79,6 @@ Java_com_google_android_filament_ToneMapper_nGenericSetContrast(JNIEnv*, jclass, ((GenericToneMapper*) nativeObject)->setContrast(contrast); } -extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_ToneMapper_nGenericSetShoulder(JNIEnv*, jclass, - jlong nativeObject, jfloat shoulder) { - ((GenericToneMapper*) nativeObject)->setShoulder(shoulder); -} - extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_ToneMapper_nGenericSetMidGrayIn(JNIEnv*, jclass, jlong nativeObject, jfloat midGrayIn) { diff --git a/android/filament-android/src/main/java/com/google/android/filament/ToneMapper.java b/android/filament-android/src/main/java/com/google/android/filament/ToneMapper.java index 71264c2d49..15800562e3 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/ToneMapper.java +++ b/android/filament-android/src/main/java/com/google/android/filament/ToneMapper.java @@ -109,7 +109,6 @@ public class ToneMapper { * The tone mapping curve is defined by 5 parameters: *
    *
  • contrast: controls the contrast of the curve
  • - *
  • shoulder: controls the shoulder of the curve, i.e. how quickly scene * referred values map to output white
  • *
  • midGrayIn: sets the input middle gray
  • *
  • midGrayOut: sets the output middle gray
  • @@ -123,15 +122,14 @@ public class ToneMapper { * the {@link ACESLegacy} tone mapper. The default values are: * *
      - *
    • contrast = 1.585f
    • - *
    • shoulder = 0.5f
    • + *
    • contrast = 1.55f
    • *
    • midGrayIn = 0.18f
    • - *
    • midGrayOut = 0.268f
    • + *
    • midGrayOut = 0.215f
    • *
    • hdrMax = 10.0f
    • *
    */ public Generic() { - this(1.585f, 0.5f, 0.18f, 0.268f, 10.0f); + this(1.55f, 0.18f, 0.215f, 10.0f); } /** @@ -139,16 +137,14 @@ public class ToneMapper { * * @param contrast: controls the contrast of the curve, must be > 0.0, values * in the range 0.5..2.0 are recommended. - * @param shoulder: controls the shoulder of the curve, i.e. how quickly scene - * referred values map to output white, between 0.0 and 1.0. * @param midGrayIn: sets the input middle gray, between 0.0 and 1.0. * @param midGrayOut: sets the output middle gray, between 0.0 and 1.0. * @param hdrMax: defines the maximum input value that will be mapped to * output white. Must be >= 1.0. */ public Generic( - float contrast, float shoulder, float midGrayIn, float midGrayOut, float hdrMax) { - super(nCreateGenericToneMapper(contrast, shoulder, midGrayIn, midGrayOut, hdrMax)); + float contrast, float midGrayIn, float midGrayOut, float hdrMax) { + super(nCreateGenericToneMapper(contrast, midGrayIn, midGrayOut, hdrMax)); } /** Returns the contrast of the curve as a strictly positive value. */ @@ -161,16 +157,6 @@ public class ToneMapper { nGenericSetContrast(getNativeObject(), contrast); } - /** Returns how fast scene referred values map to output white as a value between 0.0 and 1.0. */ - public float getShoulder() { - return nGenericGetShoulder(getNativeObject()); - } - - /** Sets how quickly scene referred values map to output white, between 0.0 and 1.0. */ - public void setShoulder(float shoulder) { - nGenericSetShoulder(getNativeObject(), shoulder); - } - /** Returns the middle gray point for input values as a value between 0.0 and 1.0. */ public float getMidGrayIn() { return nGenericGetMidGrayIn(getNativeObject()); @@ -209,17 +195,15 @@ public class ToneMapper { private static native long nCreateACESLegacyToneMapper(); private static native long nCreateFilmicToneMapper(); private static native long nCreateGenericToneMapper( - float contrast, float shoulder, float midGrayIn, float midGrayOut, float hdrMax); + float contrast, float midGrayIn, float midGrayOut, float hdrMax); // Generic tone mappper private static native float nGenericGetContrast(long nativeObject); - private static native float nGenericGetShoulder(long nativeObject); private static native float nGenericGetMidGrayIn(long nativeObject); private static native float nGenericGetMidGrayOut(long nativeObject); private static native float nGenericGetHdrMax(long nativeObject); private static native void nGenericSetContrast(long nativeObject, float contrast); - private static native void nGenericSetShoulder(long nativeObject, float shoulder); private static native void nGenericSetMidGrayIn(long nativeObject, float midGrayIn); private static native void nGenericSetMidGrayOut(long nativeObject, float midGrayOut); private static native void nGenericSetHdrMax(long nativeObject, float hdrMax); diff --git a/filament/include/filament/ToneMapper.h b/filament/include/filament/ToneMapper.h index c0aa38d153..0bbe06fe3c 100644 --- a/filament/include/filament/ToneMapper.h +++ b/filament/include/filament/ToneMapper.h @@ -123,8 +123,6 @@ struct UTILS_PUBLIC FilmicToneMapper final : public ToneMapper { * * The tone mapping curve is defined by 5 parameters: * - contrast: controls the contrast of the curve - * - shoulder: controls the shoulder of the curve, i.e. how quickly scene - * referred values map to output white * - midGrayIn: sets the input middle gray * - midGrayOut: sets the output middle gray * - hdrMax: defines the maximum input value that will be mapped to @@ -138,18 +136,15 @@ struct UTILS_PUBLIC GenericToneMapper final : public ToneMapper { * * @param contrast: controls the contrast of the curve, must be > 0.0, values * in the range 0.5..2.0 are recommended. - * @param shoulder: controls the shoulder of the curve, i.e. how quickly scene - * referred values map to output white, between 0.0 and 1.0. * @param midGrayIn: sets the input middle gray, between 0.0 and 1.0. * @param midGrayOut: sets the output middle gray, between 0.0 and 1.0. * @param hdrMax: defines the maximum input value that will be mapped to * output white. Must be >= 1.0. */ explicit GenericToneMapper( - float contrast = 1.585f, - float shoulder = 0.5f, + float contrast = 1.55f, float midGrayIn = 0.18f, - float midGrayOut = 0.268f, + float midGrayOut = 0.215f, float hdrMax = 10.0f ) noexcept; ~GenericToneMapper() noexcept final; @@ -179,9 +174,6 @@ struct UTILS_PUBLIC GenericToneMapper final : public ToneMapper { /** Sets the contrast of the curve, must be > 0.0, values in the range 0.5..2.0 are recommended. */ void setContrast(float contrast) noexcept; - /** Sets how quickly scene referred values map to output white, between 0.0 and 1.0. */ - void setShoulder(float shoulder) noexcept; - /** Sets the input middle gray, between 0.0 and 1.0. */ void setMidGrayIn(float midGrayIn) noexcept; diff --git a/filament/src/ColorGrading.cpp b/filament/src/ColorGrading.cpp index 67ae43a274..ba86dbfbd8 100644 --- a/filament/src/ColorGrading.cpp +++ b/filament/src/ColorGrading.cpp @@ -400,7 +400,7 @@ constexpr mat3f adaptationTransform(float2 whiteBalance) noexcept { float y = chromaticityCoordinateIlluminantD(x) + t * 0.066f; float3 lms = XYZ_to_CIECAT16 * xyY_to_XYZ({x, y, 1.0f}); - return LMS_CAT16_to_REC2020 * mat3f{ILLUMINANT_D65_LMS_CAT16 / lms} * REC2020_to_LMS_CAT16; + return LMS_CAT16_to_Rec2020 * mat3f{ILLUMINANT_D65_LMS_CAT16 / lms} * Rec2020_to_LMS_CAT16; } UTILS_ALWAYS_INLINE @@ -575,21 +575,21 @@ static mat3f selectColorGradingTransformIn(ColorGrading::ToneMapping toneMapping if (toneMapping == ColorGrading::ToneMapping::FILMIC) { return mat3f{}; } - return sRGB_to_REC2020; + return sRGB_to_Rec2020; } static mat3f selectColorGradingTransformOut(ColorGrading::ToneMapping toneMapping) noexcept { if (toneMapping == ColorGrading::ToneMapping::FILMIC) { return mat3f{}; } - return REC2020_to_sRGB; + return Rec2020_to_sRGB; } static float3 selectColorGradingLuminance(ColorGrading::ToneMapping toneMapping) noexcept { if (toneMapping == ColorGrading::ToneMapping::FILMIC) { - return LUMINANCE_REC709; + return LUMINANCE_Rec709; } - return LUMINANCE_REC2020; + return LUMINANCE_Rec2020; } #pragma clang diagnostic pop diff --git a/filament/src/ColorSpace.h b/filament/src/ColorSpace.h index 2e73f3e461..3ab62a70f1 100644 --- a/filament/src/ColorSpace.h +++ b/filament/src/ColorSpace.h @@ -52,13 +52,13 @@ constexpr mat3f sRGB_to_XYZ{ 0.1804380f, 0.0721750f, 0.9503040f }; -constexpr mat3f REC2020_to_XYZ{ +constexpr mat3f Rec2020_to_XYZ{ 0.6369530f, 0.2626983f, 0.0000000f, 0.1446169f, 0.6780088f, 0.0280731f, 0.1688558f, 0.0592929f, 1.0608272f }; -constexpr mat3f XYZ_to_REC2020{ +constexpr mat3f XYZ_to_Rec2020{ 1.7166634f, -0.6666738f, 0.0176425f, -0.3556733f, 1.6164557f, -0.0427770f, -0.2533681f, 0.0157683f, 0.9422433f @@ -130,6 +130,18 @@ constexpr mat3f sRGB_to_OkLab_LMS{ 0.0514459929f, 0.1073969566f, 0.6299787005f }; +constexpr mat3f XYZ_to_OkLab_LMS{ + 0.8189330101f, 0.3618667424f, -0.1288597137f, + 0.0329845436f, 0.9293118715f, 0.0361456387f, + 0.0482003018f, 0.2643662691f, 0.6338517070f +}; + +constexpr mat3f OkLab_LMS_to_XYZ{ + 1.227014f, -0.557800f, 0.281256f, + -0.040580f, 1.112257f, -0.071677f, + -0.076381f, -0.421482f, 1.586163f +}; + constexpr mat3f OkLab_LMS_to_OkLab{ 0.2104542553f, 1.9779984951f, 0.0259040371f, 0.7936177850f, -2.4285922050f, 0.7827717662f, @@ -148,21 +160,25 @@ constexpr mat3f OkLab_LMS_to_sRGB{ 0.2309699292f, -0.3413193965f, 1.7076147010f }; -constexpr mat3f sRGB_to_REC2020 = XYZ_to_REC2020 * sRGB_to_XYZ; +constexpr mat3f sRGB_to_Rec2020 = XYZ_to_Rec2020 * sRGB_to_XYZ; -constexpr mat3f REC2020_to_sRGB = XYZ_to_sRGB * REC2020_to_XYZ; +constexpr mat3f Rec2020_to_sRGB = XYZ_to_sRGB * Rec2020_to_XYZ; constexpr mat3f sRGB_to_LMS_CAT16 = XYZ_to_CIECAT16 * sRGB_to_XYZ; constexpr mat3f LMS_CAT16_to_sRGB = XYZ_to_sRGB * CIECAT16_to_XYZ; -constexpr mat3f REC2020_to_LMS_CAT16 = XYZ_to_CIECAT16 * REC2020_to_XYZ; +constexpr mat3f Rec2020_to_LMS_CAT16 = XYZ_to_CIECAT16 * Rec2020_to_XYZ; -constexpr mat3f LMS_CAT16_to_REC2020 = XYZ_to_REC2020 * CIECAT16_to_XYZ; +constexpr mat3f LMS_CAT16_to_Rec2020 = XYZ_to_Rec2020 * CIECAT16_to_XYZ; -constexpr mat3f REC2020_to_AP0 = AP1_to_AP0 * XYZ_to_AP1 * REC2020_to_XYZ; +constexpr mat3f Rec2020_to_AP0 = AP1_to_AP0 * XYZ_to_AP1 * Rec2020_to_XYZ; -constexpr mat3f AP1_to_REC2020 = XYZ_to_REC2020 * AP1_to_XYZ; +constexpr mat3f AP1_to_Rec2020 = XYZ_to_Rec2020 * AP1_to_XYZ; + +constexpr mat3f Rec2020_to_OkLab_LMS = XYZ_to_OkLab_LMS * Rec2020_to_XYZ; + +constexpr mat3f OkLab_LMS_to_Rec2020 = XYZ_to_Rec2020 * OkLab_LMS_to_XYZ; //------------------------------------------------------------------------------ // Constants @@ -175,17 +191,17 @@ constexpr float3 ILLUMINANT_D65_xyY{0.31271f, 0.32902f, 1.0f}; // Result of: XYZ_to_CIECAT16 * xyY_to_XYZ(ILLUMINANT_D65_xyY); constexpr float3 ILLUMINANT_D65_LMS_CAT16{0.975533f, 1.016483f, 1.084837f}; -// RGB to luminance coefficients for Rec.2020, from REC2020_to_XYZ -constexpr float3 LUMINANCE_REC2020{0.2627002f, 0.6779981f, 0.0593017f}; +// RGB to luminance coefficients for Rec.2020, from Rec2020_to_XYZ +constexpr float3 LUMINANCE_Rec2020{0.2627002f, 0.6779981f, 0.0593017f}; // RGB to luminance coefficients for ACEScg (AP1), from AP1_to_XYZ constexpr float3 LUMINANCE_AP1{0.272229f, 0.674082f, 0.0536895f}; // RGB to luminance coefficients for Rec.709, from sRGB_to_XYZ -constexpr float3 LUMINANCE_REC709{0.2126730f, 0.7151520f, 0.0721750f}; +constexpr float3 LUMINANCE_Rec709{0.2126730f, 0.7151520f, 0.0721750f}; // RGB to luminance coefficients for Rec.709 with HK-like weighting -constexpr float3 LUMINANCE_HK_REC709{0.13913043f, 0.73043478f, 0.13043478f}; +constexpr float3 LUMINANCE_HK_Rec709{0.13913043f, 0.73043478f, 0.13043478f}; constexpr float MIDDLE_GRAY_ACEScg = 0.18f; @@ -212,21 +228,29 @@ inline constexpr XYZ xyY_to_XYZ(xyY v) noexcept { } inline constexpr xyY XYZ_to_xyY(XYZ v) noexcept { - return float3(v.xy / max(v.x + v.y + v.z, 1e-5f), v.y); -} - -inline float3 sRGB_to_OkLab(float3 x) noexcept { - return OkLab_LMS_to_OkLab * cbrt(sRGB_to_OkLab_LMS * x); + return {v.xy / max(v.x + v.y + v.z, 1e-5f), v.y}; } inline constexpr float3 pow3(float3 x) noexcept { return x * x * x; } +inline float3 sRGB_to_OkLab(float3 x) noexcept { + return OkLab_LMS_to_OkLab * cbrt(sRGB_to_OkLab_LMS * x); +} + +inline float3 Rec2020_to_OkLab(float3 x) noexcept { + return OkLab_LMS_to_OkLab * cbrt(Rec2020_to_OkLab_LMS * x); +} + inline float3 OkLab_to_sRGB(float3 x) noexcept { return OkLab_LMS_to_sRGB * pow3(OkLab_to_OkLab_LMS * x); } +inline float3 OkLab_to_Rec2020(float3 x) noexcept { + return OkLab_LMS_to_Rec2020 * pow3(OkLab_to_OkLab_LMS * x); +} + //------------------------------------------------------------------------------ // Conversion functions and encoding/decoding //------------------------------------------------------------------------------ diff --git a/filament/src/ToneMapper.cpp b/filament/src/ToneMapper.cpp index cae64d9acd..75cbdd74b8 100644 --- a/filament/src/ToneMapper.cpp +++ b/filament/src/ToneMapper.cpp @@ -111,7 +111,7 @@ inline float3 darkSurround_to_dimSurround(float3 linearCV) { float3 XYZ = AP1_to_XYZ * linearCV; float3 xyY = XYZ_to_xyY(XYZ); - xyY.z = clamp(xyY.z, 0.0f, (float)std::numeric_limits::max()); + xyY.z = clamp(xyY.z, 0.0f, (float) std::numeric_limits::max()); xyY.z = std::pow(xyY.z, DIM_SURROUND_GAMMA); XYZ = xyY_to_XYZ(xyY); @@ -135,7 +135,7 @@ float3 ACES(float3 color, float brightness) noexcept { constexpr float RRT_SAT_FACTOR = 0.96f; constexpr float ODT_SAT_FACTOR = 0.93f; - float3 ap0 = REC2020_to_AP0 * color; + float3 ap0 = Rec2020_to_AP0 * color; // Glow module float saturation = rgb_2_saturation(ap0); @@ -177,7 +177,7 @@ float3 ACES(float3 color, float brightness) noexcept { // Apply desaturation to compensate for luminance difference linearCV = mix(float3(dot(linearCV, LUMINANCE_AP1)), linearCV, ODT_SAT_FACTOR); - return AP1_to_REC2020 * linearCV; + return AP1_to_Rec2020 * linearCV; } } // namespace aces @@ -261,7 +261,7 @@ float3 DisplayRangeToneMapper::operator()(math::float3 c) const noexcept { // The 5th color in the array (cyan) represents middle gray (18%) // Every stop above or below middle gray causes a color shift // TODO: This should depend on the working color grading color space - float v = log2(dot(c, LUMINANCE_REC2020) / 0.18f); + float v = log2(dot(c, LUMINANCE_Rec2020) / 0.18f); v = clamp(v + 5.0f, 0.0f, 15.0f); size_t index = size_t(v); @@ -272,71 +272,52 @@ float3 DisplayRangeToneMapper::operator()(math::float3 c) const noexcept { // Generic tone mapper //------------------------------------------------------------------------------ -// Lottes, 2016,"Advanced Techniques and Optimization of VDR Color Pipelines": -// https://gpuopen.com/wp-content/uploads/2016/03/GdcVdrLottes.pdf -// Includes fix from Bart Wronski: -// https://bartwronski.com/2016/09/01/dynamic-range-and-evs/ - struct GenericToneMapper::Options { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wshadow" void setParameters( float contrast, - float shoulder, float midGrayIn, float midGrayOut, float hdrMax ) { contrast = max(contrast, 1e-5f); - shoulder = saturate(shoulder); midGrayIn = clamp(midGrayIn, 1e-5f, 1.0f); midGrayOut = clamp(midGrayOut, 1e-5f, 1.0f); hdrMax = max(hdrMax, 1.0f); this->contrast = contrast; - this->shoulder = shoulder; this->midGrayIn = midGrayIn; this->midGrayOut = midGrayOut; this->hdrMax = hdrMax; - // remap shoulder - d = 0.8f + 0.4f * shoulder; + float a = pow(midGrayIn, contrast); + float b = pow(hdrMax, contrast); + float c = a - midGrayOut * b; - float mc = std::pow(midGrayIn, contrast); - float mcs = std::pow(mc, d); - - float hc = std::pow(hdrMax, contrast); - float hcs = std::pow(hc, d); - - float u = (hcs - mcs) * midGrayOut; - float v = mcs * midGrayOut; - - b = -((-mc + (midGrayOut * (hcs * mc - hc * v)) / u) / v); - c = (hcs * mc - hc * v) / u; + inputScale = (a * b * (midGrayOut - 1.0f)) / c; + outputScale = midGrayOut * (a - b) / c; } #pragma clang diagnostic pop float contrast; - float shoulder; float midGrayIn; float midGrayOut; float hdrMax; - // Computed fields, do not modify - float b; - float c; - float d; + // TEMP + float inputScale; + float outputScale; }; GenericToneMapper::GenericToneMapper( float contrast, - float shoulder, float midGrayIn, float midGrayOut, float hdrMax ) noexcept { mOptions = new Options(); - mOptions->setParameters(contrast, shoulder, midGrayIn, midGrayOut, hdrMax); + mOptions->setParameters(contrast, midGrayIn, midGrayOut, hdrMax); } GenericToneMapper::~GenericToneMapper() noexcept { @@ -354,12 +335,11 @@ GenericToneMapper& GenericToneMapper::operator=(GenericToneMapper&& rhs) noexcep } float3 GenericToneMapper::operator()(math::float3 x) const noexcept { - float3 xc = pow(clamp(x, 0.0f, mOptions->hdrMax), mOptions->contrast); - return saturate(xc / (pow(xc, mOptions->d) * mOptions->b + mOptions->c)); + x = pow(x, mOptions->contrast); + return mOptions->outputScale * x / (x + mOptions->inputScale); } float GenericToneMapper::getContrast() const noexcept { return mOptions->contrast; } -float GenericToneMapper::getShoulder() const noexcept { return mOptions->shoulder; } float GenericToneMapper::getMidGrayIn() const noexcept { return mOptions->midGrayIn; } float GenericToneMapper::getMidGrayOut() const noexcept { return mOptions->midGrayOut; } float GenericToneMapper::getHdrMax() const noexcept { return mOptions->hdrMax; } @@ -367,27 +347,14 @@ float GenericToneMapper::getHdrMax() const noexcept { return mOptions->hdrMax; void GenericToneMapper::setContrast(float contrast) noexcept { mOptions->setParameters( contrast, - mOptions->shoulder, mOptions->midGrayIn, mOptions->midGrayOut, mOptions->hdrMax ); } - -void GenericToneMapper::setShoulder(float shoulder) noexcept { - mOptions->setParameters( - mOptions->contrast, - shoulder, - mOptions->midGrayIn, - mOptions->midGrayOut, - mOptions->hdrMax - ); -} - void GenericToneMapper::setMidGrayIn(float midGrayIn) noexcept { mOptions->setParameters( mOptions->contrast, - mOptions->shoulder, midGrayIn, mOptions->midGrayOut, mOptions->hdrMax @@ -397,7 +364,6 @@ void GenericToneMapper::setMidGrayIn(float midGrayIn) noexcept { void GenericToneMapper::setMidGrayOut(float midGrayOut) noexcept { mOptions->setParameters( mOptions->contrast, - mOptions->shoulder, mOptions->midGrayIn, midGrayOut, mOptions->hdrMax @@ -407,7 +373,6 @@ void GenericToneMapper::setMidGrayOut(float midGrayOut) noexcept { void GenericToneMapper::setHdrMax(float hdrMax) noexcept { mOptions->setParameters( mOptions->contrast, - mOptions->shoulder, mOptions->midGrayIn, mOptions->midGrayOut, hdrMax diff --git a/libs/math/include/math/TVecHelpers.h b/libs/math/include/math/TVecHelpers.h index 43b346381b..f213dc714d 100644 --- a/libs/math/include/math/TVecHelpers.h +++ b/libs/math/include/math/TVecHelpers.h @@ -477,6 +477,13 @@ private: return v; } + friend inline VECTOR MATH_PURE sign(VECTOR v) { + for (size_t i = 0; i < v.size(); i++) { + v[i] = std::copysign(T(1), v[i]); + } + return v; + } + friend inline VECTOR MATH_PURE pow(VECTOR v, T p) { for (size_t i = 0; i < v.size(); i++) { v[i] = std::pow(v[i], p); diff --git a/libs/viewer/include/viewer/Settings.h b/libs/viewer/include/viewer/Settings.h index c17cbedbb5..272f213400 100644 --- a/libs/viewer/include/viewer/Settings.h +++ b/libs/viewer/include/viewer/Settings.h @@ -105,10 +105,9 @@ private: }; struct GenericToneMapperSettings { - float contrast = 1.585f; - float shoulder = 0.5f; + float contrast = 1.55f; float midGrayIn = 0.18f; - float midGrayOut = 0.268f; + float midGrayOut = 0.215f; float hdrMax = 10.0f; bool operator!=(const GenericToneMapperSettings &rhs) const { return !(rhs == *this); } bool operator==(const GenericToneMapperSettings &rhs) const; diff --git a/libs/viewer/src/Settings.cpp b/libs/viewer/src/Settings.cpp index 5f4621aa68..758aceae41 100644 --- a/libs/viewer/src/Settings.cpp +++ b/libs/viewer/src/Settings.cpp @@ -336,8 +336,6 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, GenericT CHECK_KEY(tok); if (compare(tok, jsonChunk, "contrast") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->contrast); - } else if (compare(tok, jsonChunk, "shoulder") == 0) { - i = parse(tokens, i + 1, jsonChunk, &out->shoulder); } else if (compare(tok, jsonChunk, "midGrayIn") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->midGrayIn); } else if (compare(tok, jsonChunk, "midGrayOut") == 0) { @@ -1101,7 +1099,6 @@ constexpr ToneMapper* createToneMapper(const ColorGradingSettings& settings) noe case ToneMapping::FILMIC: return new FilmicToneMapper; case ToneMapping::GENERIC: return new GenericToneMapper( settings.genericToneMapper.contrast, - settings.genericToneMapper.shoulder, settings.genericToneMapper.midGrayIn, settings.genericToneMapper.midGrayOut, settings.genericToneMapper.hdrMax @@ -1265,7 +1262,6 @@ static std::ostream& operator<<(std::ostream& out, const TemporalAntiAliasingOpt static std::ostream& operator<<(std::ostream& out, const GenericToneMapperSettings& in) { return out << "{\n" << "\"contrast\": " << (in.contrast) << ",\n" - << "\"shoulder\": " << (in.shoulder) << ",\n" << "\"midGrayIn\": " << (in.midGrayIn) << ",\n" << "\"midGrayOut\": " << (in.midGrayOut) << ",\n" << "\"hdrMax\": " << (in.hdrMax) << "\n" @@ -1544,9 +1540,8 @@ static std::ostream& operator<<(std::ostream& out, const Settings& in) { } bool GenericToneMapperSettings::operator==(const GenericToneMapperSettings &rhs) const { - static_assert(sizeof(GenericToneMapperSettings) == 20, "Please update Settings.cpp"); + static_assert(sizeof(GenericToneMapperSettings) == 16, "Please update Settings.cpp"); return contrast == rhs.contrast && - shoulder == rhs.shoulder && midGrayIn == rhs.midGrayIn && midGrayOut == rhs.midGrayOut && hdrMax == rhs.hdrMax; @@ -1555,7 +1550,7 @@ bool GenericToneMapperSettings::operator==(const GenericToneMapperSettings &rhs) bool ColorGradingSettings::operator==(const ColorGradingSettings &rhs) const { // If you had to fix the following codeline, then you likely also need to update the // implementation of operator==. - static_assert(sizeof(ColorGradingSettings) == 232, "Please update Settings.cpp"); + static_assert(sizeof(ColorGradingSettings) == 228, "Please update Settings.cpp"); return enabled == rhs.enabled && quality == rhs.quality && toneMapping == rhs.toneMapping && diff --git a/libs/viewer/src/SimpleViewer.cpp b/libs/viewer/src/SimpleViewer.cpp index a1eba1c066..f3feb2ad30 100644 --- a/libs/viewer/src/SimpleViewer.cpp +++ b/libs/viewer/src/SimpleViewer.cpp @@ -137,7 +137,6 @@ static void computeToneMapPlot(ColorGradingSettings& settings, float* plot) { case ToneMapping::GENERIC: mapper = new GenericToneMapper( settings.genericToneMapper.contrast, - settings.genericToneMapper.shoulder, settings.genericToneMapper.midGrayIn, settings.genericToneMapper.midGrayOut, settings.genericToneMapper.hdrMax @@ -197,7 +196,6 @@ static void colorGradingUI(Settings& settings, float* rangePlot, float* curvePlo if (ImGui::CollapsingHeader("Tonemap parameters")) { GenericToneMapperSettings& generic = colorGrading.genericToneMapper; ImGui::SliderFloat("Contrast##genericToneMapper", &generic.contrast, 1e-5f, 3.0f); - ImGui::SliderFloat("Shoulder##genericToneMapper", &generic.shoulder, 0.0f, 1.0f); ImGui::SliderFloat("Mid-gray in##genericToneMapper", &generic.midGrayIn, 0.0f, 1.0f); ImGui::SliderFloat("Mid-gray out##genericToneMapper", &generic.midGrayOut, 0.0f, 1.0f); ImGui::SliderFloat("HDR max", &generic.hdrMax, 1.0f, 64.0f); diff --git a/libs/viewer/tests/test_settings.cpp b/libs/viewer/tests/test_settings.cpp index 9ad89d27b2..fda3ff27e7 100644 --- a/libs/viewer/tests/test_settings.cpp +++ b/libs/viewer/tests/test_settings.cpp @@ -52,7 +52,6 @@ static const char* JSON_TEST_DEFAULTS = R"TXT( "toneMapping": "ACES_LEGACY", "genericToneMapper": { "contrast": 1.0, - "shoulder": 1.0, "midGrayIn": 1.0, "midGrayOut": 1.0, "hdrMax": 16.0 From 6805940bbfb13377e44f277910927af8c6a48e7b Mon Sep 17 00:00:00 2001 From: daemyung jang Date: Thu, 13 Jan 2022 04:06:47 +0900 Subject: [PATCH 08/22] Compute morphing on GPU (#4999) --- .../filament/filamat/MaterialBuilder.java | 16 +- .../src/main/cpp/RenderableManager.cpp | 4 +- .../android/filament/RenderableManager.java | 8 +- .../google/android/filament/VertexBuffer.java | 16 +- docs/webgl/reference.html | 8 - filament/CMakeLists.txt | 3 + filament/include/filament/Engine.h | 2 + filament/include/filament/MorphTargetBuffer.h | 119 +++++++++ filament/include/filament/RenderableManager.h | 17 +- filament/src/Allocators.h | 4 +- filament/src/Engine.cpp | 13 + filament/src/Material.cpp | 4 + filament/src/MorphTargetBuffer.cpp | 225 ++++++++++++++++++ filament/src/RenderPass.cpp | 15 ++ filament/src/RenderPass.h | 13 +- filament/src/RenderPrimitive.cpp | 4 + filament/src/RenderPrimitive.h | 5 + filament/src/Renderer.cpp | 3 + filament/src/Scene.cpp | 6 +- filament/src/ShadowMapManager.cpp | 2 + filament/src/View.cpp | 14 ++ filament/src/components/RenderableManager.cpp | 70 +++++- filament/src/components/RenderableManager.h | 39 ++- filament/src/details/Engine.h | 4 + filament/src/details/MorphTargetBuffer.h | 69 ++++++ filament/src/details/Scene.h | 4 +- filament/src/details/View.h | 4 + .../include/filament/MaterialEnums.h | 14 +- .../include/private/filament/EngineEnums.h | 22 +- .../include/private/filament/SibGenerator.h | 10 + .../include/private/filament/UibStructs.h | 10 +- libs/filabridge/src/SibGenerator.cpp | 15 ++ libs/filamat/src/UibGenerator.cpp | 10 +- libs/filamat/src/UibGenerator.h | 1 + libs/filamat/src/shaders/ShaderGenerator.cpp | 14 +- libs/gltfio/include/gltfio/FilamentAsset.h | 10 + libs/gltfio/src/Animator.cpp | 6 +- libs/gltfio/src/AssetLoader.cpp | 36 +-- libs/gltfio/src/FFilamentAsset.h | 9 +- libs/gltfio/src/FilamentAsset.cpp | 23 ++ libs/gltfio/src/MorphHelper.cpp | 142 ++++------- libs/gltfio/src/MorphHelper.h | 27 +-- libs/gltfio/src/ResourceLoader.cpp | 15 +- libs/viewer/include/viewer/SimpleViewer.h | 3 + libs/viewer/src/SimpleViewer.cpp | 22 ++ shaders/src/getters.vs | 43 +++- shaders/src/main.vs | 10 +- web/filament-js/filament.d.ts | 8 - web/filament-js/jsbindings.cpp | 7 +- web/filament-js/jsenums.cpp | 10 +- 50 files changed, 853 insertions(+), 305 deletions(-) create mode 100644 filament/include/filament/MorphTargetBuffer.h create mode 100644 filament/src/MorphTargetBuffer.cpp create mode 100644 filament/src/details/MorphTargetBuffer.h diff --git a/android/filamat-android/src/main/java/com/google/android/filament/filamat/MaterialBuilder.java b/android/filamat-android/src/main/java/com/google/android/filament/filamat/MaterialBuilder.java index 5017f18fc5..c52cd51e0c 100644 --- a/android/filamat-android/src/main/java/com/google/android/filament/filamat/MaterialBuilder.java +++ b/android/filamat-android/src/main/java/com/google/android/filament/filamat/MaterialBuilder.java @@ -114,14 +114,14 @@ public class MaterialBuilder { BONE_INDICES, // indices of 4 bones (uvec4) BONE_WEIGHTS, // weights of the 4 bones (normalized float4) UNUSED, // reserved for future use - CUSTOM0, // custom or MORPH_POSITION_0 - CUSTOM1, // custom or MORPH_POSITION_1 - CUSTOM2, // custom or MORPH_POSITION_2 - CUSTOM3, // custom or MORPH_POSITION_3 - CUSTOM4, // custom or MORPH_TANGENTS_0 - CUSTOM5, // custom or MORPH_TANGENTS_1 - CUSTOM6, // custom or MORPH_TANGENTS_2 - CUSTOM7 // custom or MORPH_TANGENTS_3 + CUSTOM0, // custom + CUSTOM1, // custom + CUSTOM2, // custom + CUSTOM3, // custom + CUSTOM4, // custom + CUSTOM5, // custom + CUSTOM6, // custom + CUSTOM7 // custom } public enum BlendingMode { diff --git a/android/filament-android/src/main/cpp/RenderableManager.cpp b/android/filament-android/src/main/cpp/RenderableManager.cpp index cba710637c..8d3d43dcdd 100644 --- a/android/filament-android/src/main/cpp/RenderableManager.cpp +++ b/android/filament-android/src/main/cpp/RenderableManager.cpp @@ -270,9 +270,9 @@ Java_com_google_android_filament_RenderableManager_nSetMorphWeights(JNIEnv* env, jlong nativeRenderableManager, jint instance, jfloatArray weights) { RenderableManager *rm = (RenderableManager *) nativeRenderableManager; jfloat* vec = env->GetFloatArrayElements(weights, NULL); - math::float4 floatvec(vec[0], vec[1], vec[2], vec[3]); + jsize count = env->GetArrayLength(weights); + rm->setMorphWeights((RenderableManager::Instance)instance, vec, count); env->ReleaseFloatArrayElements(weights, vec, JNI_ABORT); - rm->setMorphWeights((RenderableManager::Instance)instance, floatvec); } extern "C" JNIEXPORT void JNICALL diff --git a/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java b/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java index db4fdb8c83..1a5785ebe7 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java +++ b/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java @@ -402,9 +402,6 @@ public class RenderableManager { /** * Controls if the renderable has vertex morphing targets, false by default. * - *

    This is required to enable GPU morphing for up to 4 attributes. The attached VertexBuffer - * must provide data in the appropriate VertexAttribute slots (MORPH_POSITION_0 etc).

    - * *

    See also {@link RenderableManager#setMorphWeights}, which can be called on a per-frame basis * to advance the animation.

    */ @@ -497,14 +494,11 @@ public class RenderableManager { /** * Updates the vertex morphing weights on a renderable, all zeroes by default. * - *

    This is specified using a 4-tuple, one float per morph target. If the renderable has fewer - * than 4 morph targets, then clients should fill the unused components with zeroes.

    - * *

    The renderable must be built with morphing enabled.

    * * @see Builder#morphing */ - public void setMorphWeights(@EntityInstance int i, @NonNull @Size(min = 4) float[] weights) { + public void setMorphWeights(@EntityInstance int i, @NonNull float[] weights) { nSetMorphWeights(mNativeObject, i, weights); } diff --git a/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java b/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java index d3494e81ef..359505d369 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java +++ b/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java @@ -66,14 +66,14 @@ public class VertexBuffer { BONE_INDICES, // indices of 4 bones (uvec4) BONE_WEIGHTS, // weights of the 4 bones (normalized float4) UNUSED, // reserved for future use - CUSTOM0, // custom or MORPH_POSITION_0 - CUSTOM1, // custom or MORPH_POSITION_1 - CUSTOM2, // custom or MORPH_POSITION_2 - CUSTOM3, // custom or MORPH_POSITION_3 - CUSTOM4, // custom or MORPH_TANGENTS_0 - CUSTOM5, // custom or MORPH_TANGENTS_1 - CUSTOM6, // custom or MORPH_TANGENTS_2 - CUSTOM7 // custom or MORPH_TANGENTS_3 + CUSTOM0, // custom + CUSTOM1, // custom + CUSTOM2, // custom + CUSTOM3, // custom + CUSTOM4, // custom + CUSTOM5, // custom + CUSTOM6, // custom + CUSTOM7 // custom } public enum AttributeType { diff --git a/docs/webgl/reference.html b/docs/webgl/reference.html index 3fd55c7de6..6b7510906d 100644 --- a/docs/webgl/reference.html +++ b/docs/webgl/reference.html @@ -1438,14 +1438,6 @@ This defines the following functions:

  • CUSTOM5
  • CUSTOM6
  • CUSTOM7
  • -
  • MORPH_POSITION_0
  • -
  • MORPH_POSITION_1
  • -
  • MORPH_POSITION_2
  • -
  • MORPH_POSITION_3
  • -
  • MORPH_TANGENTS_0
  • -
  • MORPH_TANGENTS_1
  • -
  • MORPH_TANGENTS_2
  • -
  • MORPH_TANGENTS_3
diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index 4049d39130..c9c996a9d3 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -28,6 +28,7 @@ set(PUBLIC_HDRS include/filament/LightManager.h include/filament/Material.h include/filament/MaterialInstance.h + include/filament/MorphTargetBuffer.h include/filament/Options.h include/filament/RenderTarget.h include/filament/RenderableManager.h @@ -69,6 +70,7 @@ set(SRCS src/Material.cpp src/MaterialInstance.cpp src/MaterialParser.cpp + src/MorphTargetBuffer.cpp src/PerViewUniforms.cpp src/PostProcessManager.cpp src/RenderPass.cpp @@ -140,6 +142,7 @@ set(PRIVATE_HDRS src/details/IndirectLight.h src/details/Material.h src/details/MaterialInstance.h + src/details/MorphTargetBuffer.h src/details/RenderTarget.h src/details/Renderer.h src/details/Scene.h diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index 4db5ae7576..0a31013f66 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -39,6 +39,7 @@ class SkinningBuffer; class IndirectLight; class Material; class MaterialInstance; +class MorphTargetBuffer; class Renderer; class RenderTarget; class Scene; @@ -421,6 +422,7 @@ public: bool destroy(const Fence* p); //!< Destroys a Fence object. bool destroy(const IndexBuffer* p); //!< Destroys an IndexBuffer object. bool destroy(const SkinningBuffer* p); //!< Destroys a SkinningBuffer object. + bool destroy(const MorphTargetBuffer* p); //!< Destroys a MorphTargetBuffer object. bool destroy(const IndirectLight* p); //!< Destroys an IndirectLight object. /** diff --git a/filament/include/filament/MorphTargetBuffer.h b/filament/include/filament/MorphTargetBuffer.h new file mode 100644 index 0000000000..f3c0877039 --- /dev/null +++ b/filament/include/filament/MorphTargetBuffer.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_MORPHTARGETBUFFER_H +#define TNT_FILAMENT_MORPHTARGETBUFFER_H + +#include + +#include + +#include + + +namespace filament { + +/** + * MorphTargetBuffer is used to hold morphing data (positions and tangents). + */ +class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI { + struct BuilderDetails; + +public: + class Builder : public BuilderBase { + friend struct BuilderDetails; + public: + Builder() noexcept; + Builder(Builder const& rhs) noexcept; + Builder(Builder&& rhs) noexcept; + ~Builder() noexcept; + Builder& operator=(Builder const& rhs) noexcept; + Builder& operator=(Builder&& rhs) noexcept; + + /** + * Size of the morph targets in vertex counts. + * @param vertexCount Number of vertex counts the morph targets can hold. + * @return A reference to this Builder for chaining calls. + */ + Builder& vertexCount(size_t vertexCount) noexcept; + + /** + * Size of the morph targets in targets. + * @param count Number of targets the morph targets can hold. + * @return A reference to this Builder for chaining calls. + */ + Builder& count(size_t count) noexcept; + + /** + * Creates the MorphTargetBuffer object and returns a pointer to it. + * + * @param engine Reference to the filament::Engine to associate this MorphTargetBuffer with. + * + * @return pointer to the newly created object or nullptr if exceptions are disabled and + * an error occurred. + * + * @exception utils::PostConditionPanic if a runtime error occurred, such as running out of + * memory or other resources. + * @exception utils::PreConditionPanic if a parameter to a builder function was invalid. + */ + MorphTargetBuffer* build(Engine& engine); + private: + friend class FMorphTargetBuffer; + }; + + /** + * Updates the position of morph target at the index. + * @param engine Reference to the filament::Engine associated with this MorphTargetBuffer. + * @param targetIndex the index of morph target to be updated. + * @param weights pointer to at least count positions + * @param count number of position elements in positions + */ + void setPositionsAt(Engine& engine, size_t targetIndex, math::float3 const* positions, size_t count); + + /** + * Updates the position of morph target at the index. + * @param engine Reference to the filament::Engine associated with this MorphTargetBuffer. + * @param targetIndex the index of morph target to be updated. + * @param weights pointer to at least count positions + * @param count number of position elements in positions + */ + void setPositionsAt(Engine& engine, size_t targetIndex, math::float4 const* positions, size_t count); + + /** + * Updates the position of morph target at the index. + * @param engine Reference to the filament::Engine associated with this MorphTargetBuffer. + * @param targetIndex the index of morph target to be updated. + * @param tangents pointer to at least count tangents + * @param count number of tangent elements in tangents + */ + void setTangentsAt(Engine& engine, size_t targetIndex, math::short4 const* tangents, size_t count); + + /** + * Returns the vertex count of this MorphTargetBuffer. + * @return The number of vertices the MorphTargetBuffer holds. + */ + size_t getVertexCount() const noexcept; + + /** + * Returns the target count of this MorphTargetBuffer. + * @return The number of targets the MorphTargetBuffer holds. + */ + size_t getCount() const noexcept; +}; + +} // namespace filament + +#endif //TNT_FILAMENT_MORPHTARGETBUFFER_H diff --git a/filament/include/filament/RenderableManager.h b/filament/include/filament/RenderableManager.h index 46ef09e429..47f4fc56d4 100644 --- a/filament/include/filament/RenderableManager.h +++ b/filament/include/filament/RenderableManager.h @@ -41,9 +41,11 @@ class Engine; class IndexBuffer; class Material; class MaterialInstance; +class MorphTargetBuffer; class Renderer; class SkinningBuffer; class VertexBuffer; +class Texture; class FEngine; class FRenderPrimitive; @@ -300,9 +302,6 @@ public: /** * Controls if the renderable has vertex morphing targets, false by default. * - * This is required to enable GPU morphing for up to 4 attributes. The attached VertexBuffer - * must provide data in the appropriate VertexAttribute slots (\c MORPH_POSITION_0 etc). - * * See also RenderableManager::setMorphWeights(), which can be called on a per-frame basis * to advance the animation. */ @@ -456,12 +455,16 @@ public: /** * Updates the vertex morphing weights on a renderable, all zeroes by default. * - * This is specified using a 4-tuple, one float per morph target. If the renderable has fewer - * than 4 morph targets, then clients should fill the unused components with zeroes. - * * The renderable must be built with morphing enabled, see Builder::morphing(). */ - void setMorphWeights(Instance instance, math::float4 const& weights) noexcept; + void setMorphWeights(Instance instance, float const* weights, size_t count) noexcept; + + + /** + * Associates a MorphTargetBuffer to the given primitive. + */ + void setMorphTargetBufferAt(Instance instance, + size_t primitiveIndex, MorphTargetBuffer* morphTargetBuffer) noexcept; /** * Gets the bounding box used for frustum culling. diff --git a/filament/src/Allocators.h b/filament/src/Allocators.h index 7ee44286fc..25ccab736c 100644 --- a/filament/src/Allocators.h +++ b/filament/src/Allocators.h @@ -22,11 +22,11 @@ #include "private/backend/BackendUtils.h" #ifndef FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB -# define FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB 2 +# define FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB 3 #endif #ifndef FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB -# define FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB 1 +# define FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB 2 #endif namespace filament { diff --git a/filament/src/Engine.cpp b/filament/src/Engine.cpp index 4d7f3211bc..1edb08a520 100644 --- a/filament/src/Engine.cpp +++ b/filament/src/Engine.cpp @@ -333,6 +333,7 @@ void FEngine::shutdown() { cleanupResourceList(mBufferObjects); cleanupResourceList(mIndexBuffers); + cleanupResourceList(mMorphTargetBuffers); cleanupResourceList(mSkinningBuffers); cleanupResourceList(mVertexBuffers); cleanupResourceList(mTextures); @@ -567,6 +568,10 @@ FSkinningBuffer* FEngine::createSkinningBuffer(const SkinningBuffer::Builder& bu return create(mSkinningBuffers, builder); } +FMorphTargetBuffer* FEngine::createMorphTargetBuffer(const MorphTargetBuffer::Builder& builder) noexcept { + return create(mMorphTargetBuffers, builder); +} + FTexture* FEngine::createTexture(const Texture::Builder& builder) noexcept { return create(mTextures, builder); } @@ -753,6 +758,10 @@ bool FEngine::destroy(const FSkinningBuffer* p) { return terminateAndDestroy(p, mSkinningBuffers); } +bool FEngine::destroy(const FMorphTargetBuffer* p) { + return terminateAndDestroy(p, mMorphTargetBuffers); +} + inline bool FEngine::destroy(const FRenderer* p) { return terminateAndDestroy(p, mRenderers); } @@ -966,6 +975,10 @@ bool Engine::destroy(const SkinningBuffer* p) { return upcast(this)->destroy(upcast(p)); } +bool Engine::destroy(const MorphTargetBuffer* p) { + return upcast(this)->destroy(upcast(p)); +} + bool Engine::destroy(const IndirectLight* p) { return upcast(this)->destroy(upcast(p)); } diff --git a/filament/src/Material.cpp b/filament/src/Material.cpp index b534f93c8a..cc7341f782 100644 --- a/filament/src/Material.cpp +++ b/filament/src/Material.cpp @@ -378,6 +378,10 @@ Handle FMaterial::getSurfaceProgramSlow(uint8_t variantKey) const noe if (Variant(variantKey).hasSkinningOrMorphing()) { pb.setUniformBlock(BindingPoints::PER_RENDERABLE_BONES, PerRenderableUibBone::_name); + pb.setUniformBlock(BindingPoints::PER_RENDERABLE_MORPHING, PerRenderableMorphingUib::_name); + + addSamplerGroup(pb, BindingPoints::PER_RENDERABLE_MORPHING, + SibGenerator::getPerRenderPrimitiveMorphingSib(variantKey), mSamplerBindings); } addSamplerGroup(pb, BindingPoints::PER_VIEW, SibGenerator::getPerViewSib(variantKey), mSamplerBindings); diff --git a/filament/src/MorphTargetBuffer.cpp b/filament/src/MorphTargetBuffer.cpp new file mode 100644 index 0000000000..3e123a9df9 --- /dev/null +++ b/filament/src/MorphTargetBuffer.cpp @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "details/MorphTargetBuffer.h" + +#include "private/filament/SibGenerator.h" + +#include "details/Engine.h" + +#include "FilamentAPI-impl.h" + +#include + +#include + +namespace filament { + +using namespace backend; +using namespace math; + +struct MorphTargetBuffer::BuilderDetails { + size_t mVertexCount = 0; + size_t mCount = 0; +}; + +using BuilderType = MorphTargetBuffer; +BuilderType::Builder::Builder() noexcept = default; +BuilderType::Builder::~Builder() noexcept = default; +BuilderType::Builder::Builder(BuilderType::Builder const& rhs) noexcept = default; +BuilderType::Builder::Builder(BuilderType::Builder&& rhs) noexcept = default; +BuilderType::Builder& BuilderType::Builder::operator=(BuilderType::Builder const& rhs) noexcept = default; +BuilderType::Builder& BuilderType::Builder::operator=(BuilderType::Builder&& rhs) noexcept = default; + +MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::vertexCount(size_t vertexCount) noexcept { + mImpl->mVertexCount = vertexCount; + return *this; +} + +MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::count(size_t count) noexcept { + mImpl->mCount = count; + return *this; +} + +MorphTargetBuffer* MorphTargetBuffer::Builder::build(Engine& engine) { + return upcast(engine).createMorphTargetBuffer(*this); +} + +// ------------------------------------------------------------------------------------------------ + +// This value is limited by ES3.0, ES3.0 only guarantees 2048. +// When you change this value, you must change MAX_MORPH_TARGET_BUFFER_WIDTH at getters.vs +constexpr size_t MAX_MORPH_TARGET_BUFFER_WIDTH = 2048; + +static const auto FREE_CALLBACK = [](void* mem, size_t, void*) { ::free(mem); }; + +static inline size_t getWidth(size_t vertexCount) noexcept { + return std::min(vertexCount, MAX_MORPH_TARGET_BUFFER_WIDTH); +} + +static inline size_t getHeight(size_t vertexCount) noexcept { + return (vertexCount + MAX_MORPH_TARGET_BUFFER_WIDTH) / MAX_MORPH_TARGET_BUFFER_WIDTH; +} + +static inline size_t getDepth(size_t targetCount) noexcept { + return targetCount * 2; +} + +static inline size_t getSize(size_t vertexCount) noexcept { + const size_t stride = getWidth(vertexCount) * sizeof(float4); + return Texture::PixelBufferDescriptor::computeDataSize( + Texture::PixelBufferDescriptor::PixelDataFormat::RGBA, + Texture::PixelBufferDescriptor::PixelDataType::FLOAT, + stride, getHeight(vertexCount), 1); +} + +static inline size_t getPositionIndex(size_t targetIndex) noexcept { + return targetIndex * 2 + 0; +} + +static inline size_t getTangentIndex(size_t targetIndex) noexcept { + return targetIndex * 2 + 1; +} + +FMorphTargetBuffer::FMorphTargetBuffer(FEngine& engine, const Builder& builder) + : mSBuffer(PerRenderPrimitiveMorphingSib::SAMPLER_COUNT), + mVertexCount(builder->mVertexCount), + mCount(builder->mCount) { + FEngine::DriverApi& driver = engine.getDriverApi(); + + mSbHandle = driver.createSamplerGroup(PerRenderPrimitiveMorphingSib::SAMPLER_COUNT); + mTbHandle = driver.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1, TextureFormat::RGBA32F, 1, + getWidth(mVertexCount), getHeight(mVertexCount), getDepth(mCount), + TextureUsage::DEFAULT); + + SamplerParams samplerParams{}; + samplerParams.filterMin = SamplerMinFilter::NEAREST; + samplerParams.filterMag = SamplerMagFilter::NEAREST; + mSBuffer.setSampler(PerRenderPrimitiveMorphingSib::TARGETS, mTbHandle, samplerParams); +} + +void FMorphTargetBuffer::terminate(FEngine& engine) { + FEngine::DriverApi& driver = engine.getDriverApi(); + + driver.destroySamplerGroup(mSbHandle); + driver.destroyTexture(mTbHandle); +} + +void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t targetIndex, math::float3 const* positions, size_t count) { + ASSERT_PRECONDITION(targetIndex < mCount, "targetIndex must be < count"); + + auto size = getSize(mVertexCount); + + ASSERT_PRECONDITION((int)sizeof(math::float3) * count <= size, + "MorphTargetBuffer (size=%lu) overflow (size=%lu)", + size, sizeof(math::float3) * count); + + // We could use a pool instead of malloc() directly. + auto* out = (float4*) malloc(size); + std::transform(positions, positions + count, out, + [](const float3& p) { return float4(p, 1.0f); }); + + Texture::PixelBufferDescriptor buffer(out, size, Texture::Format::RGBA, Texture::Type::FLOAT, + FREE_CALLBACK); + + FEngine::DriverApi& driver = engine.getDriverApi(); + driver.update3DImage(mTbHandle, 0, 0, 0, getPositionIndex(targetIndex), + getWidth(mVertexCount), getHeight(mVertexCount), 1, + std::move(buffer)); +} + +void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t targetIndex, math::float4 const* positions, size_t count) { + ASSERT_PRECONDITION(targetIndex < mCount, "targetIndex must be < count"); + + auto size = getSize(mVertexCount); + + ASSERT_PRECONDITION((int)sizeof(math::float4) * count <= size, + "MorphTargetBuffer (size=%lu) overflow (size=%lu)", + size, sizeof(math::float4) * count); + + // We could use a pool instead of malloc() directly. + auto* out = (float4*) malloc(size); + memcpy(out, positions, sizeof(math::float4) * count); + + Texture::PixelBufferDescriptor buffer(out, size, Texture::Format::RGBA, Texture::Type::FLOAT, + FREE_CALLBACK); + + FEngine::DriverApi& driver = engine.getDriverApi(); + driver.update3DImage(mTbHandle, 0, 0, 0, getPositionIndex(targetIndex), + getWidth(mVertexCount), getHeight(mVertexCount), 1, + std::move(buffer)); +} + +void FMorphTargetBuffer::setTangentsAt(FEngine& engine, size_t targetIndex, math::short4 const* tangents, size_t count) { + ASSERT_PRECONDITION(targetIndex < mCount, "targetIndex must be < count"); + + auto size = getSize(mVertexCount); + + ASSERT_PRECONDITION((int)sizeof(math::short4) * count <= size, + "MorphTargetBuffer (size=%lu) overflow (size=%lu)", + size, sizeof(math::short4) * count); + + // We could use a pool instead of malloc() directly. + auto* out = (float4*) malloc(size); + std::transform(tangents, tangents + count, out, + [](const short4& t) { return unpackSnorm16(t); }); + + Texture::PixelBufferDescriptor buffer(out, size, Texture::Format::RGBA, Texture::Type::FLOAT, + FREE_CALLBACK); + + FEngine::DriverApi& driver = engine.getDriverApi(); + driver.update3DImage(mTbHandle, 0, 0, 0, getTangentIndex(targetIndex), + getWidth(mVertexCount), getHeight(mVertexCount), 1, + std::move(buffer)); +} + +void FMorphTargetBuffer::commit(FEngine& engine) const noexcept { + if (mSBuffer.isDirty()) { + FEngine::DriverApi& driver = engine.getDriverApi(); + driver.updateSamplerGroup(mSbHandle, std::move(mSBuffer.toCommandStream())); + } +} + +void FMorphTargetBuffer::bind(backend::DriverApi& driver) const noexcept { + driver.bindSamplers(BindingPoints::PER_RENDERABLE_MORPHING, mSbHandle); +} + +// ------------------------------------------------------------------------------------------------ +// Trampoline calling into private implementation +// ------------------------------------------------------------------------------------------------ + +void MorphTargetBuffer::setPositionsAt(Engine& engine, size_t targetIndex, math::float3 const* positions, size_t count) { + upcast(this)->setPositionsAt(upcast(engine), targetIndex, positions, count); +} + +void MorphTargetBuffer::setPositionsAt(Engine& engine, size_t targetIndex, math::float4 const* positions, size_t count) { + upcast(this)->setPositionsAt(upcast(engine), targetIndex, positions, count); +} + +void MorphTargetBuffer::setTangentsAt(Engine& engine, size_t targetIndex, math::short4 const* tangents, size_t count) { + upcast(this)->setTangentsAt(upcast(engine), targetIndex, tangents, count); +} + +size_t MorphTargetBuffer::getVertexCount() const noexcept { + return upcast(this)->getVertexCount(); +} + +size_t MorphTargetBuffer::getCount() const noexcept { + return upcast(this)->getCount(); +} + +} // namespace filament + diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index 708d563ff4..89a1d9d13c 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -381,6 +381,7 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags, FMaterialInstance const* const mi = primitive.getMaterialInstance(); if constexpr (isColorPass) { cmdColor.primitive.primitiveHandle = primitive.getHwHandle(); + cmdColor.primitive.morphTargetBuffer = primitive.getMorphTargetBuffer(); cmdColor.primitive.materialVariant = materialVariant; RenderPass::setupColorCommand(cmdColor, mi, inverseFrontFaces); @@ -470,6 +471,7 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags, // unconditionally write the command cmdDepth.primitive.primitiveHandle = primitive.getHwHandle(); cmdDepth.primitive.mi = mi; + cmdDepth.primitive.morphTargetBuffer = primitive.getMorphTargetBuffer(); cmdDepth.primitive.rasterState.culling = mi->getCullingMode(); // FIXME: should writeDepthForShadowCasters take precedence over mi->getDepthWrite()? @@ -529,6 +531,7 @@ void RenderPass::Executor::recordDriverCommands(backend::DriverApi& driver, SYSTRACE_VALUE32("commandCount", last - first); auto const* const UTILS_RESTRICT soaSkinning = soa.data(); + auto const* const UTILS_RESTRICT soaMorphing = soa.data(); PolygonOffset dummyPolyOffset; PipelineState pipeline{ .polygonOffset = mPolygonOffset }; @@ -577,6 +580,18 @@ void RenderPass::Executor::recordDriverCommands(backend::DriverApi& driver, skinning.offset * sizeof(PerRenderableUibBone), CONFIG_MAX_BONE_COUNT * sizeof(PerRenderableUibBone)); } + + auto morphTargetBuffer = info.morphTargetBuffer; + if (UTILS_UNLIKELY(morphTargetBuffer)) { + // Instead of using a UBO per primitive, we could also have a single UBO for all primitives + // and use bindUniformBufferRange which might be more efficient. + auto morphing = soaMorphing[info.index]; + assert_invariant(morphing.handle); + driver.bindUniformBuffer(BindingPoints::PER_RENDERABLE_MORPHING, morphing.handle); + assert_invariant(morphing.count <= morphTargetBuffer->getCount()); + morphTargetBuffer->bind(driver); + } + driver.draw(pipeline, info.primitiveHandle); } } diff --git a/filament/src/RenderPass.h b/filament/src/RenderPass.h index c1fa00eea2..5bd930f868 100644 --- a/filament/src/RenderPass.h +++ b/filament/src/RenderPass.h @@ -208,19 +208,20 @@ public: return boolish ? std::numeric_limits::max() : uint64_t(0); } - struct PrimitiveInfo { // 24 bytes + struct PrimitiveInfo { // 32 bytes FMaterialInstance const* mi = nullptr; // 8 bytes (4) + FMorphTargetBuffer const* morphTargetBuffer = nullptr; // 8 bytes (4) backend::Handle primitiveHandle; // 4 bytes backend::RasterState rasterState; // 4 bytes uint16_t index = 0; // 2 bytes Variant materialVariant; // 1 byte - uint8_t reserved[13 - sizeof(void*)] = {}; // 5 byte (9) + uint8_t reserved[21 - sizeof(void*) - sizeof(void*)] = {}; // 5 bytes (13) }; - static_assert(sizeof(PrimitiveInfo) == 24); + static_assert(sizeof(PrimitiveInfo) == 32); - struct alignas(8) Command { // 32 bytes + struct alignas(8) Command { // 40 bytes CommandKey key = 0; // 8 bytes - PrimitiveInfo primitive; // 24 bytes + PrimitiveInfo primitive; // 32 bytes bool operator < (Command const& rhs) const noexcept { return key < rhs.key; } // placement new declared as "throw" to avoid the compiler's null-check inline void* operator new (std::size_t, void* ptr) { @@ -228,7 +229,7 @@ public: return ptr; } }; - static_assert(sizeof(Command) == 32); + static_assert(sizeof(Command) == 40); static_assert(std::is_trivially_destructible_v, "Command isn't trivially destructible"); diff --git a/filament/src/RenderPrimitive.cpp b/filament/src/RenderPrimitive.cpp index a6eae017d2..469bbb3e1d 100644 --- a/filament/src/RenderPrimitive.cpp +++ b/filament/src/RenderPrimitive.cpp @@ -83,4 +83,8 @@ void FRenderPrimitive::set(FEngine& engine, RenderableManager::PrimitiveType typ mPrimitiveType = type; } +void FRenderPrimitive::set(FMorphTargetBuffer* morphTargetBuffer) noexcept { + mMorphTargetBuffer = morphTargetBuffer; +} + } // namespace filament diff --git a/filament/src/RenderPrimitive.h b/filament/src/RenderPrimitive.h index 70188c3238..cd7da72f0e 100644 --- a/filament/src/RenderPrimitive.h +++ b/filament/src/RenderPrimitive.h @@ -30,6 +30,7 @@ namespace filament { class FEngine; class FVertexBuffer; class FIndexBuffer; +class FMorphTargetBuffer; class FRenderer; class FRenderPrimitive { @@ -45,6 +46,8 @@ public: void set(FEngine& engine, RenderableManager::PrimitiveType type, size_t offset, size_t minIndex, size_t maxIndex, size_t count) noexcept; + void set(FMorphTargetBuffer* morphTargetBuffer) noexcept; + // frees driver resources, object becomes invalid void terminate(FEngine& engine); @@ -53,6 +56,7 @@ public: backend::PrimitiveType getPrimitiveType() const noexcept { return mPrimitiveType; } AttributeBitset getEnabledAttributes() const noexcept { return mEnabledAttributes; } uint16_t getBlendOrder() const noexcept { return mBlendOrder; } + FMorphTargetBuffer* getMorphTargetBuffer() const noexcept { return mMorphTargetBuffer; } void setMaterialInstance(FMaterialInstance const* mi) noexcept { mMaterialInstance = mi; } void setBlendOrder(uint16_t order) noexcept { @@ -65,6 +69,7 @@ private: backend::PrimitiveType mPrimitiveType = backend::PrimitiveType::NONE; AttributeBitset mEnabledAttributes; uint16_t mBlendOrder = 0; + FMorphTargetBuffer* mMorphTargetBuffer = nullptr; }; } // namespace filament diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp index 52d5628b2e..d7c805ad9e 100644 --- a/filament/src/Renderer.cpp +++ b/filament/src/Renderer.cpp @@ -416,6 +416,9 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { // of RenderPass::setCamera / RenderPass::setGeometry calls. view.updatePrimitivesLod(engine, cameraInfo, scene.getRenderableData(), view.getVisibleRenderables()); + // updatePrimitivesMorphTargetBuffer must be run after updatePrimitivesLod. + view.updatePrimitivesMorphTargetBuffer(engine, cameraInfo, + scene.getRenderableData(), view.getVisibleRenderables()); pass.setCamera(cameraInfo); pass.setGeometry(scene.getRenderableData(), view.getVisibleRenderables(), scene.getRenderableUBO()); diff --git a/filament/src/Scene.cpp b/filament/src/Scene.cpp index d5cca3c3c2..ca64d72c43 100644 --- a/filament/src/Scene.cpp +++ b/filament/src/Scene.cpp @@ -138,9 +138,9 @@ void FScene::prepare(const mat4& worldOriginTransform, bool shadowReceiversAreCa worldTransform, // WORLD_TRANSFORM visibility, // VISIBILITY_STATE rcm.getSkinningBufferInfo(ri), // SKINNING_BUFFER + rcm.getMorphingBufferInfo(ri), // MORPHING_BUFFER worldAABB.center, // WORLD_AABB_CENTER 0, // VISIBLE_MASK - rcm.getMorphWeights(ri), // MORPH_WEIGHTS rcm.getChannels(ri), // CHANNELS rcm.getLayerMask(ri), // LAYERS worldAABB.halfExtent, // WORLD_AABB_EXTENT @@ -254,8 +254,8 @@ void FScene::updateUBOs(utils::Range visibleRenderables, backend::Hand visibility.screenSpaceContactShadows)); UniformBuffer::setUniform(buffer, - offset + offsetof(PerRenderableUib, morphWeights), - sceneData.elementAt(i)); + offset + offsetof(PerRenderableUib, morphTargetCount), + sceneData.elementAt(i).count); UniformBuffer::setUniform(buffer, offset + offsetof(PerRenderableUib, channels), diff --git a/filament/src/ShadowMapManager.cpp b/filament/src/ShadowMapManager.cpp index 1614e231cc..49fe876d10 100644 --- a/filament/src/ShadowMapManager.cpp +++ b/filament/src/ShadowMapManager.cpp @@ -264,6 +264,8 @@ void ShadowMapManager::render(FrameGraph& fg, FEngine& engine, backend::DriverAp // updatePrimitivesLod must be run before RenderPass::appendCommands. view.updatePrimitivesLod(engine, cameraInfo, scene->getRenderableData(), entry.range); + // updatePrimitivesMorphTargetBuffer must be run after updatePrimitivesLod. + view.updatePrimitivesMorphTargetBuffer(engine, cameraInfo, scene->getRenderableData(), entry.range); // generate and sort the commands for rendering the shadow map RenderPass entryPass(pass); diff --git a/filament/src/View.cpp b/filament/src/View.cpp index 7b2a3ceacf..0e9cf8f43c 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -19,6 +19,7 @@ #include "Culler.h" #include "DFG.h" #include "Froxelizer.h" +#include "RenderPrimitive.h" #include "ResourceAllocator.h" #include "details/Engine.h" @@ -868,6 +869,19 @@ void FView::updatePrimitivesLod(FEngine& engine, const CameraInfo&, } } +void FView::updatePrimitivesMorphTargetBuffer(FEngine& engine, const CameraInfo&, + FScene::RenderableSoa& renderableData, Range visible) noexcept { + for (uint32_t index : visible) { + Slice primitives = renderableData.elementAt(index); + for (auto& primitive : primitives) { + auto morphTargetBuffer = primitive.getMorphTargetBuffer(); + if (morphTargetBuffer) { + morphTargetBuffer->commit(engine); + } + } + } +} + void FView::renderShadowMaps(FrameGraph& fg, FEngine& engine, FEngine::DriverApi& driver, RenderPass const& pass) noexcept { mShadowMapManager.render(fg, engine, driver, pass, *this); diff --git a/filament/src/components/RenderableManager.cpp b/filament/src/components/RenderableManager.cpp index 7208771bb6..d89cc4fb00 100644 --- a/filament/src/components/RenderableManager.cpp +++ b/filament/src/components/RenderableManager.cpp @@ -23,8 +23,11 @@ #include "details/Engine.h" #include "details/VertexBuffer.h" #include "details/IndexBuffer.h" +#include "details/Texture.h" #include "details/Material.h" +#include "private/filament/SibGenerator.h" + #include #include @@ -316,7 +319,6 @@ void FRenderableManager::create( setCulling(ci, builder->mCulling); setSkinning(ci, false); setMorphing(ci, builder->mMorphingEnabled); - setMorphWeights(ci, {0, 0, 0, 0}); mManager[ci].channels = builder->mChannels; const uint32_t count = builder->mSkinningBoneCount; @@ -372,6 +374,18 @@ void FRenderableManager::create( } } } + + if (builder->mMorphingEnabled) { + // Instead of using a UBO per primitive, we could also have a single UBO for all primitives + // and use bindUniformBufferRange which might be more efficient. + MorphWeights& morphWeights = manager[ci].morphWeights; + morphWeights = MorphWeights { + .handle = driver.createBufferObject( + sizeof(PerRenderableMorphingUib), + BufferObjectBinding::UNIFORM, + backend::BufferUsage::DYNAMIC), + .count = 0 }; + } } engine.flushIfNeeded(); } @@ -416,6 +430,12 @@ void FRenderableManager::destroyComponent(Instance ci) noexcept { if (bones.handle && !bones.skinningBufferMode) { driver.destroyBufferObject(bones.handle); } + + // destroy the weights structures if any + MorphWeights const& morphWeights = manager[ci].morphWeights; + if (morphWeights.handle) { + driver.destroyBufferObject(morphWeights.handle); + } } void FRenderableManager::destroyComponentPrimitives( @@ -562,9 +582,38 @@ void FRenderableManager::setSkinningBuffer(FRenderableManager::Instance ci, bones.offset = uint16_t(offset); } -void FRenderableManager::setMorphWeights(Instance ci, const float4& weights) noexcept { - if (ci) { - mManager[ci].morphWeights = weights; +static void updateMorphWeights(FEngine& engine, backend::Handle handle, + float const* weights, size_t count) noexcept { + auto& driver = engine.getDriverApi(); + auto size = sizeof(PerRenderableMorphingUib); + auto* UTILS_RESTRICT out = (PerRenderableMorphingUib*)driver.allocate(size); + memset(out, 0, size); + std::transform(weights, weights + count, out->weights, + [](float value) { return float4(value); }); + driver.updateBufferObject(handle, { out, size }, 0); +} + +void FRenderableManager::setMorphWeights(Instance instance, float const* weights, size_t count) noexcept { + if (instance) { + MorphWeights& morphWeights = mManager[instance].morphWeights; + morphWeights.count = count; + + ASSERT_PRECONDITION(count < CONFIG_MAX_MORPH_TARGET_COUNT, + "Only %d morph targets are supported (count=%d)", CONFIG_MAX_MORPH_TARGET_COUNT, count); + + if (morphWeights.handle) { + updateMorphWeights(mEngine, morphWeights.handle, weights, count); + } + } +} + +void FRenderableManager::setMorphTargetBufferAt(Instance instance, + size_t primitiveIndex, FMorphTargetBuffer* morphTargetBuffer) noexcept { + if (instance) { + Slice& primitives = getRenderPrimitives(instance, 0); + if (primitiveIndex < primitives.size()) { + primitives[primitiveIndex].set(morphTargetBuffer); + } } } @@ -693,15 +742,20 @@ void RenderableManager::setBones(Instance instance, upcast(this)->setBones(instance, transforms, boneCount, offset); } -void RenderableManager::setMorphWeights(Instance instance, float4 const& weights) noexcept { - upcast(this)->setMorphWeights(instance, weights); -} - void RenderableManager::setSkinningBuffer(Instance instance, SkinningBuffer* skinningBuffer, size_t count, size_t offset) noexcept { upcast(this)->setSkinningBuffer(instance, upcast(skinningBuffer), count, offset); } +void RenderableManager::setMorphWeights(Instance instance, float const* weights, size_t count) noexcept { + upcast(this)->setMorphWeights(instance, weights, count); +} + +void RenderableManager::setMorphTargetBufferAt(Instance instance, + size_t primitiveIndex, MorphTargetBuffer* morphTargetBuffer) noexcept { + upcast(this)->setMorphTargetBufferAt(instance, primitiveIndex, upcast(morphTargetBuffer)); +} + void RenderableManager::setLightChannel(Instance instance, unsigned int channel, bool enable) noexcept { upcast(this)->setLightChannel(instance, channel, enable); } diff --git a/filament/src/components/RenderableManager.h b/filament/src/components/RenderableManager.h index 364a2f3c05..38db7a3794 100644 --- a/filament/src/components/RenderableManager.h +++ b/filament/src/components/RenderableManager.h @@ -40,9 +40,11 @@ namespace filament { class FBufferObject; class FIndexBuffer; class FMaterialInstance; +class FMorphTargetBuffer; class FRenderPrimitive; class FSkinningBuffer; class FVertexBuffer; +class FTexture; class FRenderableManager : public RenderableManager { public: @@ -106,9 +108,11 @@ public: inline void setPrimitives(Instance instance, utils::Slice const& primitives) noexcept; inline void setBones(Instance instance, Bone const* transforms, size_t boneCount, size_t offset = 0) noexcept; inline void setBones(Instance instance, math::mat4f const* transforms, size_t boneCount, size_t offset = 0) noexcept; - inline void setMorphWeights(Instance instance, const math::float4& weights) noexcept; inline void setSkinningBuffer(Instance instance, FSkinningBuffer* skinningBuffer, size_t count, size_t offset) noexcept; + void setMorphWeights(Instance instance, float const* weights, size_t count) noexcept; + void setMorphTargetBufferAt(Instance instance, + size_t primitiveIndex, FMorphTargetBuffer* morphTargetBuffer) noexcept; inline void setLightChannel(Instance instance, unsigned int channel, bool enable) noexcept; inline bool getLightChannel(Instance instance, unsigned int channel) const noexcept; @@ -123,7 +127,6 @@ public: inline Visibility getVisibility(Instance instance) const noexcept; inline uint8_t getLayerMask(Instance instance) const noexcept; inline uint8_t getPriority(Instance instance) const noexcept; - inline math::float4 getMorphWeights(Instance instance) const noexcept; inline uint8_t getChannels(Instance instance) const noexcept; struct SkinningBindingInfo { @@ -134,6 +137,12 @@ public: inline SkinningBindingInfo getSkinningBufferInfo(Instance instance) const noexcept; inline uint32_t getBoneCount(Instance instance) const noexcept; + struct MorphingBindingInfo { + backend::Handle handle; + uint32_t count; + }; + inline MorphingBindingInfo getMorphingBufferInfo(Instance instance) const noexcept; + inline int getMorphTargetCount(Instance instance) const noexcept; utils::Entity getEntity(Instance instance) const noexcept { return mManager.getEntity(instance); @@ -165,13 +174,18 @@ private: uint16_t offset = 0; bool skinningBufferMode = false; }; - static_assert(sizeof(Bones) == 12); + struct MorphWeights { + backend::Handle handle; + uint32_t count = 0; + }; + static_assert(sizeof(MorphWeights) == 8); + enum { AABB, // user data LAYERS, // user data - MORPH_WEIGHTS, // user data + MORPH_WEIGHTS, // filament data, UBO storing a pointer to the morph weights information CHANNELS, // user data VISIBILITY, // user data PRIMITIVES, // user data @@ -181,7 +195,7 @@ private: using Base = utils::SingleInstanceComponentManager< Box, // AABB uint8_t, // LAYERS - math::float4, // MORPH_WEIGHTS + MorphWeights, // MORPH_WEIGHTS uint8_t, // CHANNELS Visibility, // VISIBILITY utils::Slice, // PRIMITIVES @@ -325,10 +339,6 @@ uint8_t FRenderableManager::getPriority(Instance instance) const noexcept { return getVisibility(instance).priority; } -math::float4 FRenderableManager::getMorphWeights(Instance instance) const noexcept { - return mManager[instance].morphWeights; -} - uint8_t FRenderableManager::getChannels(Instance instance) const noexcept { return mManager[instance].channels; } @@ -348,6 +358,17 @@ inline uint32_t FRenderableManager::getBoneCount(Instance instance) const noexce return bones.count; } +FRenderableManager::MorphingBindingInfo +FRenderableManager::getMorphingBufferInfo(Instance instance) const noexcept { + MorphWeights const& morphWeights = mManager[instance].morphWeights; + return { morphWeights.handle, morphWeights.count }; +} + +int FRenderableManager::getMorphTargetCount(Instance instance) const noexcept { + MorphWeights const& morphWeights = mManager[instance].morphWeights; + return morphWeights.count; +} + utils::Slice const& FRenderableManager::getRenderPrimitives( Instance instance, uint8_t level) const noexcept { return mManager[instance].primitives; diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index 6fd2777c45..9f6e1e1b0e 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -36,6 +36,7 @@ #include "details/IndexBuffer.h" #include "details/RenderTarget.h" #include "details/SkinningBuffer.h" +#include "details/MorphTargetBuffer.h" #include "details/Skybox.h" #include "private/backend/CommandBufferQueue.h" @@ -234,6 +235,7 @@ public: FVertexBuffer* createVertexBuffer(const VertexBuffer::Builder& builder) noexcept; FIndexBuffer* createIndexBuffer(const IndexBuffer::Builder& builder) noexcept; FSkinningBuffer* createSkinningBuffer(const SkinningBuffer::Builder& builder) noexcept; + FMorphTargetBuffer* createMorphTargetBuffer(const MorphTargetBuffer::Builder& builder) noexcept; FIndirectLight* createIndirectLight(const IndirectLight::Builder& builder) noexcept; FMaterial* createMaterial(const Material::Builder& builder) noexcept; FTexture* createTexture(const Texture::Builder& builder) noexcept; @@ -265,6 +267,7 @@ public: bool destroy(const FFence* p); bool destroy(const FIndexBuffer* p); bool destroy(const FSkinningBuffer* p); + bool destroy(const FMorphTargetBuffer* p); bool destroy(const FIndirectLight* p); bool destroy(const FMaterial* p); bool destroy(const FMaterialInstance* p); @@ -376,6 +379,7 @@ private: ResourceList mStreams{ "Stream" }; ResourceList mIndexBuffers{ "IndexBuffer" }; ResourceList mSkinningBuffers{ "SkinningBuffer" }; + ResourceList mMorphTargetBuffers{ "MorphTargetBuffer" }; ResourceList mVertexBuffers{ "VertexBuffer" }; ResourceList mIndirectLights{ "IndirectLight" }; ResourceList mMaterials{ "Material" }; diff --git a/filament/src/details/MorphTargetBuffer.h b/filament/src/details/MorphTargetBuffer.h new file mode 100644 index 0000000000..5ba5fe1fdd --- /dev/null +++ b/filament/src/details/MorphTargetBuffer.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_DETAILS_MORPHTARGETBUFFER_H +#define TNT_FILAMENT_DETAILS_MORPHTARGETBUFFER_H + +#include "upcast.h" + +#include + +#include "private/backend/DriverApiForward.h" + +#include "private/backend/SamplerGroup.h" + +#include + +#include + +#include + +namespace filament { + +class FEngine; + +class FMorphTargetBuffer : public MorphTargetBuffer { +public: + FMorphTargetBuffer(FEngine& engine, const Builder& builder); + + // frees driver resources, object becomes invalid + void terminate(FEngine& engine); + + void setPositionsAt(FEngine& engine, size_t targetIndex, math::float3 const* positions, size_t count); + void setPositionsAt(FEngine& engine, size_t targetIndex, math::float4 const* positions, size_t count); + void setTangentsAt(FEngine& engine, size_t targetIndex, math::short4 const* tangents, size_t count); + inline size_t getVertexCount() const noexcept { return mVertexCount; } + inline size_t getCount() const noexcept { return mCount; } + +private: + friend class FView; + friend class RenderPass; + + void commit(FEngine& engine) const noexcept; + void bind(backend::DriverApi& driver) const noexcept; + + backend::Handle mSbHandle; + backend::Handle mTbHandle; + backend::SamplerGroup mSBuffer; + size_t mVertexCount; + size_t mCount; +}; + +FILAMENT_UPCAST(MorphTargetBuffer) + +} // namespace filament + +#endif //TNT_FILAMENT_DETAILS_MORPHTARGETBUFFER_H diff --git a/filament/src/details/Scene.h b/filament/src/details/Scene.h index 03833a86dc..f8f66afbf6 100644 --- a/filament/src/details/Scene.h +++ b/filament/src/details/Scene.h @@ -105,9 +105,9 @@ public: WORLD_TRANSFORM, // 16 | instance of the Transform component VISIBILITY_STATE, // 1 | visibility data of the component SKINNING_BUFFER, // 8 | bones uniform buffer handle, count, offset + MORPHING_BUFFER, // 8 | weights uniform buffer handle, count WORLD_AABB_CENTER, // 12 | world-space bounding box center of the renderable VISIBLE_MASK, // 2 | each bit represents a visibility in a pass - MORPH_WEIGHTS, // 4 | floats for morphing CHANNELS, // 1 | currently light channels only // These are not needed anymore after culling @@ -127,9 +127,9 @@ public: math::mat4f, // WORLD_TRANSFORM FRenderableManager::Visibility, // VISIBILITY_STATE FRenderableManager::SkinningBindingInfo, // SKINNING_BUFFER + FRenderableManager::MorphingBindingInfo, // MORPHING_BUFFER math::float3, // WORLD_AABB_CENTER VisibleMaskType, // VISIBLE_MASK - math::float4, // MORPH_WEIGHTS uint8_t, // CHANNELS uint8_t, // LAYERS math::float3, // WORLD_AABB_EXTENT diff --git a/filament/src/details/View.h b/filament/src/details/View.h index 68b0447573..d474574bdf 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -198,6 +198,10 @@ public: FEngine& engine, const CameraInfo& camera, FScene::RenderableSoa& renderableData, Range visible) noexcept; + void updatePrimitivesMorphTargetBuffer( + FEngine& engine, const CameraInfo&, + FScene::RenderableSoa& renderableData, Range visible) noexcept; + void setShadowingEnabled(bool enabled) noexcept { mShadowingEnabled = enabled; } bool isShadowingEnabled() const noexcept { return mShadowingEnabled; } diff --git a/libs/filabridge/include/filament/MaterialEnums.h b/libs/filabridge/include/filament/MaterialEnums.h index 9e2ed51906..dbca04195f 100644 --- a/libs/filabridge/include/filament/MaterialEnums.h +++ b/libs/filabridge/include/filament/MaterialEnums.h @@ -27,7 +27,7 @@ namespace filament { // update this when a new version of filament wouldn't work with older materials -static constexpr size_t MATERIAL_VERSION = 16; +static constexpr size_t MATERIAL_VERSION = 17; /** * Supported shading models @@ -136,20 +136,10 @@ enum VertexAttribute : uint8_t { CUSTOM6 = 14, CUSTOM7 = 15, - // Aliases for vertex morphing. - MORPH_POSITION_0 = CUSTOM0, - MORPH_POSITION_1 = CUSTOM1, - MORPH_POSITION_2 = CUSTOM2, - MORPH_POSITION_3 = CUSTOM3, - MORPH_TANGENTS_0 = CUSTOM4, - MORPH_TANGENTS_1 = CUSTOM5, - MORPH_TANGENTS_2 = CUSTOM6, - MORPH_TANGENTS_3 = CUSTOM7, - // this is limited by driver::MAX_VERTEX_ATTRIBUTE_COUNT }; -static constexpr size_t MAX_MORPH_TARGETS = 4; +static constexpr size_t MAX_MORPH_TARGETS = 128; // this is limited by filament::CONFIG_MAX_MORPH_TARGET_COUNT static constexpr size_t MAX_CUSTOM_ATTRIBUTES = 8; /** diff --git a/libs/filabridge/include/private/filament/EngineEnums.h b/libs/filabridge/include/private/filament/EngineEnums.h index 30a3fb343f..ccdace432b 100644 --- a/libs/filabridge/include/private/filament/EngineEnums.h +++ b/libs/filabridge/include/private/filament/EngineEnums.h @@ -33,14 +33,15 @@ enum class PostProcessVariant : uint8_t { // Binding points for uniform buffers and sampler buffers. // Effectively, these are just names. namespace BindingPoints { - constexpr uint8_t PER_VIEW = 0; // uniforms/samplers updated per view - constexpr uint8_t PER_RENDERABLE = 1; // uniforms/samplers updated per renderable - constexpr uint8_t PER_RENDERABLE_BONES = 2; // bones data, per renderable - constexpr uint8_t LIGHTS = 3; // lights data array - constexpr uint8_t SHADOW = 4; // punctual shadow data - constexpr uint8_t FROXEL_RECORDS = 5; - constexpr uint8_t PER_MATERIAL_INSTANCE = 6; // uniforms/samplers updates per material - constexpr uint8_t COUNT = 7; + constexpr uint8_t PER_VIEW = 0; // uniforms/samplers updated per view + constexpr uint8_t PER_RENDERABLE = 1; // uniforms/samplers updated per renderable + constexpr uint8_t PER_RENDERABLE_BONES = 2; // bones data, per renderable + constexpr uint8_t PER_RENDERABLE_MORPHING = 3; // morphing uniform/sampler updated per render primitive + constexpr uint8_t LIGHTS = 4; // lights data array + constexpr uint8_t SHADOW = 5; // punctual shadow data + constexpr uint8_t FROXEL_RECORDS = 6; + constexpr uint8_t PER_MATERIAL_INSTANCE = 7; // uniforms/samplers updates per material + constexpr uint8_t COUNT = 8; // These are limited by Program::UNIFORM_BINDING_COUNT (currently 8) } @@ -66,6 +67,11 @@ constexpr size_t CONFIG_MAX_SHADOW_CASCADES = 4; // We store 64 bytes per bone. constexpr size_t CONFIG_MAX_BONE_COUNT = 256; +// The maximum number of morph target count. +// This value is limited by ES3.0, ES3.0 only guarantees 256 layers in an array texture. +// For morphing, 128 layers are used for the positions and others are used for tangents. +constexpr int CONFIG_MAX_MORPH_TARGET_COUNT = 128; + } // namespace filament #endif // TNT_FILAMENT_driver/EngineEnums.h diff --git a/libs/filabridge/include/private/filament/SibGenerator.h b/libs/filabridge/include/private/filament/SibGenerator.h index 25c9f7362c..610ee07191 100644 --- a/libs/filabridge/include/private/filament/SibGenerator.h +++ b/libs/filabridge/include/private/filament/SibGenerator.h @@ -30,6 +30,7 @@ class SamplerInterfaceBlock; class SibGenerator { public: static SamplerInterfaceBlock const& getPerViewSib(uint8_t variantKey) noexcept; + static SamplerInterfaceBlock const& getPerRenderPrimitiveMorphingSib(uint8_t variantKey) noexcept; static SamplerInterfaceBlock const* getSib(uint8_t bindingPoint, uint8_t variantKey) noexcept; // When adding a sampler block here, make sure to also update // FMaterial::getSurfaceProgramSlow and FMaterial::getPostProcessProgramSlow if needed @@ -48,5 +49,14 @@ struct PerViewSib { static constexpr size_t SAMPLER_COUNT = 7; }; +struct PerRenderPrimitiveMorphingSib { + // Morph positions and normals are packed into one texture. + // Because some materials exceed MAX_SAMPLER_COUNT(16) + // when morph positions and normals are bound to samplers separately. + static constexpr size_t TARGETS = 0; // morph positions and normals + + static constexpr size_t SAMPLER_COUNT = 1; +}; + } #endif // TNT_FILABRIDGE_SIBGENERATOR_H diff --git a/libs/filabridge/include/private/filament/UibStructs.h b/libs/filabridge/include/private/filament/UibStructs.h index 4a25bac3d0..aafbe10751 100644 --- a/libs/filabridge/include/private/filament/UibStructs.h +++ b/libs/filabridge/include/private/filament/UibStructs.h @@ -149,7 +149,7 @@ struct alignas(256) PerRenderableUib { // NOLINT(cppcoreguidelines-pro-type-memb static constexpr utils::StaticString _name{ "ObjectUniforms" }; math::mat4f worldFromModelMatrix; math::mat3f worldFromModelNormalMatrix; // this gets expanded to 48 bytes during the copy to the UBO - alignas(16) math::float4 morphWeights; // morph weights (we could easily have 8 using half) + alignas(16) uint32_t morphTargetCount; uint32_t flags; // see packFlags() below uint32_t channels; // 0x000000ll uint32_t objectId; // used for picking @@ -227,6 +227,14 @@ struct PerRenderableUibBone { // NOLINT(cppcoreguidelines-pro-type-member-init) static_assert(CONFIG_MAX_BONE_COUNT * sizeof(PerRenderableUibBone) <= 16384, "PerRenderableUibBone exceed max UBO size"); +struct alignas(16) PerRenderableMorphingUib { + static constexpr utils::StaticString _name{ "MorphingUniforms" }; + // The array stride(the bytes between array elements) is always rounded up to the size of a vec4 in std140. + math::float4 weights[CONFIG_MAX_MORPH_TARGET_COUNT]; +}; +static_assert(sizeof(PerRenderableMorphingUib) <= 16384, + "PerRenderableMorphingUib exceed max UBO size"); + } // namespace filament #endif // TNT_FILABRIDGE_UIBSTRUCTS_H diff --git a/libs/filabridge/src/SibGenerator.cpp b/libs/filabridge/src/SibGenerator.cpp index 14b1bab85f..a361f701c9 100644 --- a/libs/filabridge/src/SibGenerator.cpp +++ b/libs/filabridge/src/SibGenerator.cpp @@ -66,12 +66,27 @@ SamplerInterfaceBlock const& SibGenerator::getPerViewSib(uint8_t variantKey) noe return v.hasVsm() ? sibVsm : sibPcf; } +SamplerInterfaceBlock const& SibGenerator::getPerRenderPrimitiveMorphingSib(uint8_t variantKey) noexcept { + using Type = SamplerInterfaceBlock::Type; + using Format = SamplerInterfaceBlock::Format; + using Precision = SamplerInterfaceBlock::Precision; + + static SamplerInterfaceBlock sib = SamplerInterfaceBlock::Builder() + .name("Morphing") + .add("targets", Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::HIGH) + .build(); + + return sib; +} + SamplerInterfaceBlock const* SibGenerator::getSib(uint8_t bindingPoint, uint8_t variantKey) noexcept { switch (bindingPoint) { case BindingPoints::PER_VIEW: return &getPerViewSib(variantKey); case BindingPoints::PER_RENDERABLE: return nullptr; + case BindingPoints::PER_RENDERABLE_MORPHING: + return &getPerRenderPrimitiveMorphingSib(variantKey); case BindingPoints::LIGHTS: return nullptr; default: diff --git a/libs/filamat/src/UibGenerator.cpp b/libs/filamat/src/UibGenerator.cpp index 8708be1d1f..a64144603c 100644 --- a/libs/filamat/src/UibGenerator.cpp +++ b/libs/filamat/src/UibGenerator.cpp @@ -128,7 +128,7 @@ UniformInterfaceBlock const& UibGenerator::getPerRenderableUib() noexcept { .name(PerRenderableUib::_name) .add("worldFromModelMatrix", 1, UniformInterfaceBlock::Type::MAT4, Precision::HIGH) .add("worldFromModelNormalMatrix", 1, UniformInterfaceBlock::Type::MAT3, Precision::HIGH) - .add("morphWeights", 1, UniformInterfaceBlock::Type::FLOAT4, Precision::HIGH) + .add("morphTargetCount", 1, UniformInterfaceBlock::Type::UINT) .add("flags", 1, UniformInterfaceBlock::Type::UINT) .add("channels", 1, UniformInterfaceBlock::Type::UINT) .add("objectId", 1, UniformInterfaceBlock::Type::UINT) @@ -161,6 +161,14 @@ UniformInterfaceBlock const& UibGenerator::getPerRenderableBonesUib() noexcept { return uib; } +UniformInterfaceBlock const& UibGenerator::getPerRenderableMorphingUib() noexcept { + static UniformInterfaceBlock uib = UniformInterfaceBlock::Builder() + .name(PerRenderableMorphingUib::_name) + .add("weights", CONFIG_MAX_MORPH_TARGET_COUNT, UniformInterfaceBlock::Type::FLOAT4) + .build(); + return uib; +} + UniformInterfaceBlock const& UibGenerator::getFroxelRecordUib() noexcept { static UniformInterfaceBlock uib = UniformInterfaceBlock::Builder() .name(FroxelRecordUib::_name) diff --git a/libs/filamat/src/UibGenerator.h b/libs/filamat/src/UibGenerator.h index 4007c5c76b..8c39ebcc96 100644 --- a/libs/filamat/src/UibGenerator.h +++ b/libs/filamat/src/UibGenerator.h @@ -28,6 +28,7 @@ public: static UniformInterfaceBlock const& getLightsUib() noexcept; static UniformInterfaceBlock const& getShadowUib() noexcept; static UniformInterfaceBlock const& getPerRenderableBonesUib() noexcept; + static UniformInterfaceBlock const& getPerRenderableMorphingUib() noexcept; static UniformInterfaceBlock const& getFroxelRecordUib() noexcept; // When adding an UBO here, make sure to also update // FMaterial::getSurfaceProgramSlow and FMaterial::getPostProcessProgramSlow if needed diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index fa4449e2bb..37cc762a10 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -218,14 +218,6 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel, if (variant.hasSkinningOrMorphing()) { attributes.set(VertexAttribute::BONE_INDICES); attributes.set(VertexAttribute::BONE_WEIGHTS); - attributes.set(VertexAttribute::MORPH_POSITION_0); - attributes.set(VertexAttribute::MORPH_POSITION_1); - attributes.set(VertexAttribute::MORPH_POSITION_2); - attributes.set(VertexAttribute::MORPH_POSITION_3); - attributes.set(VertexAttribute::MORPH_TANGENTS_0); - attributes.set(VertexAttribute::MORPH_TANGENTS_1); - attributes.set(VertexAttribute::MORPH_TANGENTS_2); - attributes.set(VertexAttribute::MORPH_TANGENTS_3); } CodeGenerator::generateShaderInputs(vs, ShaderType::VERTEX, attributes, interpolation); @@ -247,6 +239,12 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel, cg.generateUniforms(vs, ShaderType::VERTEX, BindingPoints::PER_RENDERABLE_BONES, UibGenerator::getPerRenderableBonesUib()); + cg.generateUniforms(vs, ShaderType::VERTEX, + BindingPoints::PER_RENDERABLE_MORPHING, + UibGenerator::getPerRenderableMorphingUib()); + cg.generateSamplers(vs, + material.samplerBindings.getBlockOffset(BindingPoints::PER_RENDERABLE_MORPHING), + SibGenerator::getPerRenderPrimitiveMorphingSib(variantKey)); } cg.generateUniforms(vs, ShaderType::VERTEX, BindingPoints::PER_MATERIAL_INSTANCE, material.uib); diff --git a/libs/gltfio/include/gltfio/FilamentAsset.h b/libs/gltfio/include/gltfio/FilamentAsset.h index 87daa3c38e..2b0a65863b 100644 --- a/libs/gltfio/include/gltfio/FilamentAsset.h +++ b/libs/gltfio/include/gltfio/FilamentAsset.h @@ -203,6 +203,16 @@ public: */ Animator* getAnimator() noexcept; + /** + * Updates the morphing weights in the given entity. + */ + void setMorphWeights(utils::Entity entity, const float* weights, size_t count); + + /** + * Gets the number of morphing in the given entity. + */ + int getMorphTargetCount(utils::Entity entity) noexcept; + /** * Lazily creates a single LINES renderable that draws the transformed bounding-box hierarchy * for diagnostic purposes. The wireframe is owned by the asset so clients should not delete it. diff --git a/libs/gltfio/src/Animator.cpp b/libs/gltfio/src/Animator.cpp index 4879a637fc..0102f4c682 100644 --- a/libs/gltfio/src/Animator.cpp +++ b/libs/gltfio/src/Animator.cpp @@ -176,7 +176,6 @@ Animator::Animator(FFilamentAsset* asset, FFilamentInstance* instance) { mImpl->instance = instance; mImpl->renderableManager = &asset->mEngine->getRenderableManager(); mImpl->transformManager = &asset->mEngine->getTransformManager(); - mImpl->morpher = new MorphHelper(asset, instance); const cgltf_data* srcAsset = asset->mSourceAsset->hierarchy; const cgltf_animation* srcAnims = srcAsset->animations; @@ -222,6 +221,8 @@ Animator::Animator(FFilamentAsset* asset, FFilamentInstance* instance) { } } } + + mImpl->morpher = asset->mMorpher; } void Animator::addInstance(FFilamentInstance* instance) { @@ -235,7 +236,6 @@ void Animator::addInstance(FFilamentInstance* instance) { } Animator::~Animator() { - delete mImpl->morpher; delete mImpl; } @@ -461,7 +461,7 @@ void AnimatorImpl::applyAnimation(const Channel& channel, float t, size_t prevIn } } - morpher->applyWeights(channel.targetEntity, weights.data(), weights.size()); + morpher->setWeights(channel.targetEntity, weights.data(), weights.size()); return; } } diff --git a/libs/gltfio/src/AssetLoader.cpp b/libs/gltfio/src/AssetLoader.cpp index 13ef84b3c3..6a855aa2b0 100644 --- a/libs/gltfio/src/AssetLoader.cpp +++ b/libs/gltfio/src/AssetLoader.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -520,14 +521,15 @@ void FAssetLoader::createRenderable(const cgltf_node* node, Entity entity, const // node weights are provided, they override the ones specified on the mesh. if (numMorphTargets > 0) { RenderableManager::Instance renderable = mRenderableManager.getInstance(entity); - float4 weights(0, 0, 0, 0); - for (cgltf_size i = 0; i < std::min(MAX_MORPH_TARGETS, mesh->weights_count); ++i) { + const auto size = std::min(MAX_MORPH_TARGETS, std::max(mesh->weights_count, node->weights_count)); + std::vector weights(size); + for (cgltf_size i = 0, c = std::min(size, mesh->weights_count); i < c; ++i) { weights[i] = mesh->weights[i]; } - for (cgltf_size i = 0; i < std::min(MAX_MORPH_TARGETS, node->weights_count); ++i) { + for (cgltf_size i = 0, c = std::min(size, node->weights_count); i < c; ++i) { weights[i] = node->weights[i]; } - mRenderableManager.setMorphWeights(renderable, weights); + mRenderableManager.setMorphWeights(renderable, weights.data(), size); } } @@ -688,15 +690,11 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out cgltf_size targetsCount = inPrim->targets_count; - // There is no need to emit a warning if there are more than 4 targets. This is only the base - // VertexBuffer and more might be created by MorphHelper. if (targetsCount > MAX_MORPH_TARGETS) { + utils::slog.w << "WARNING: Exceeded max morph target count of " << MAX_MORPH_TARGETS << utils::io::endl; targetsCount = MAX_MORPH_TARGETS; } - constexpr int baseTangentsAttr = (int) VertexAttribute::MORPH_TANGENTS_0; - constexpr int basePositionAttr = (int) VertexAttribute::MORPH_POSITION_0; - for (cgltf_size targetIndex = 0; targetIndex < targetsCount; targetIndex++) { const cgltf_morph_target& morphTarget = inPrim->targets[targetIndex]; for (cgltf_size aindex = 0; aindex < morphTarget.attributes_count; aindex++) { @@ -705,17 +703,8 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out const cgltf_attribute_type atype = attribute.type; const int morphId = targetIndex + 1; - // The glTF tangent data is ignored here, but honored in ResourceLoader. - if (atype == cgltf_attribute_type_tangent) { - continue; - } - - if (atype == cgltf_attribute_type_normal) { - VertexAttribute attr = (VertexAttribute) (baseTangentsAttr + targetIndex); - vbb.attribute(attr, slot, VertexBuffer::AttributeType::SHORT4); - vbb.normalized(attr); - outPrim->morphTangents[targetIndex] = slot; - addBufferSlot({&mResult->mGenerateTangents, atype, slot++, morphId}); + // The glTF normal and tangent data are ignored here, but honored in ResourceLoader. + if (atype == cgltf_attribute_type_normal || atype == cgltf_attribute_type_tangent) { continue; } @@ -736,13 +725,6 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out slog.e << "Unsupported accessor type in " << name << io::endl; return false; } - const int stride = (fatype == actualType) ? accessor->stride : 0; - - VertexAttribute attr = (VertexAttribute) (basePositionAttr + targetIndex); - vbb.attribute(attr, slot, fatype, 0, stride); - vbb.normalized(attr, accessor->normalized); - outPrim->morphPositions[targetIndex] = slot; - addBufferSlot({accessor, atype, slot++, morphId}); } } diff --git a/libs/gltfio/src/FFilamentAsset.h b/libs/gltfio/src/FFilamentAsset.h index c548d20bfc..a4061fe524 100644 --- a/libs/gltfio/src/FFilamentAsset.h +++ b/libs/gltfio/src/FFilamentAsset.h @@ -65,13 +65,13 @@ namespace gltfio { class Animator; class Wireframe; +class MorphHelper; // Encapsulates VertexBuffer::setBufferAt() or IndexBuffer::setBuffer(). struct BufferSlot { const cgltf_accessor* accessor; cgltf_attribute_type attribute; int bufferIndex; // for vertex buffers only - int morphTarget; // 0 if no morphing, otherwise 1-based index filament::VertexBuffer* vertexBuffer; filament::IndexBuffer* indexBuffer; }; @@ -97,8 +97,6 @@ struct Primitive { filament::IndexBuffer* indices = nullptr; filament::Aabb aabb; // object-space bounding box UvMap uvmap; // mapping from each glTF UV set to either UV0 or UV1 (8 bytes) - uint8_t morphPositions[4] = {}; // Buffer indices for MORPH_POSITION_0, MORPH_POSITION_1 etc. - uint8_t morphTangents[4] = {}; // Buffer indices for MORPH_TANGENTS_0, MORPH_TANGENTS_1, etc. }; using MeshCache = tsl::robin_map>; @@ -194,6 +192,10 @@ struct FFilamentAsset : public FilamentAsset { Animator* getAnimator() noexcept; + void setMorphWeights(utils::Entity entity , const float* weights, size_t count) noexcept; + + int getMorphTargetCount(utils::Entity entity) noexcept; + utils::Entity getWireframe() noexcept; filament::Engine* getEngine() const noexcept { @@ -243,6 +245,7 @@ struct FFilamentAsset : public FilamentAsset { std::vector mInstances; SkinVector mSkins; // unused for instanced assets Animator* mAnimator = nullptr; + MorphHelper* mMorpher = nullptr; Wireframe* mWireframe = nullptr; bool mResourcesLoaded = false; DependencyGraph mDependencyGraph; diff --git a/libs/gltfio/src/FilamentAsset.cpp b/libs/gltfio/src/FilamentAsset.cpp index ed6a9a25ef..843a5aec2b 100644 --- a/libs/gltfio/src/FilamentAsset.cpp +++ b/libs/gltfio/src/FilamentAsset.cpp @@ -22,6 +22,7 @@ #include #include +#include "MorphHelper.h" #include "Wireframe.h" using namespace filament; @@ -41,6 +42,7 @@ FFilamentAsset::~FFilamentAsset() { delete mAnimator; delete mWireframe; + delete mMorpher; mEngine->destroy(mRoot); mEntityManager->destroy(mRoot); @@ -95,6 +97,19 @@ Animator* FFilamentAsset::getAnimator() noexcept { return mAnimator; } +void FFilamentAsset::setMorphWeights(utils::Entity entity, const float* weights, size_t count) noexcept { + if (mResourcesLoaded) { + mMorpher->setWeights(entity, weights, count); + } +} + +int FFilamentAsset::getMorphTargetCount(utils::Entity entity) noexcept { + if (mResourcesLoaded) { + return mMorpher->getTargetCount(entity); + } + return 0; +} + Entity FFilamentAsset::getWireframe() noexcept { if (!mWireframe) { mWireframe = new Wireframe(this); @@ -274,6 +289,14 @@ Animator* FilamentAsset::getAnimator() noexcept { return upcast(this)->getAnimator(); } +void FilamentAsset::setMorphWeights(utils::Entity entity, const float* weights, size_t count) { + return upcast(this)->setMorphWeights(entity, weights, count); +} + +int FilamentAsset::getMorphTargetCount(utils::Entity entity) noexcept { + return upcast(this)->getMorphTargetCount(entity); +} + Entity FilamentAsset::getWireframe() noexcept { return upcast(this)->getWireframe(); } diff --git a/libs/gltfio/src/MorphHelper.cpp b/libs/gltfio/src/MorphHelper.cpp index b2680607f7..abad68525c 100644 --- a/libs/gltfio/src/MorphHelper.cpp +++ b/libs/gltfio/src/MorphHelper.cpp @@ -16,11 +16,12 @@ #include "MorphHelper.h" -#include #include #include +#include +#include +#include -#include "GltfEnums.h" #include "TangentsJob.h" using namespace filament; @@ -45,14 +46,6 @@ static bool isSubsetOf(ubyte4 a, ubyte4 b) { return true; } -static int indexOf(int a, ubyte4 b) { - if (a == b.x) return 0; - if (a == b.y) return 1; - if (a == b.z) return 2; - if (a == b.w) return 3; - return -1; -} - MorphHelper::MorphHelper(FFilamentAsset* asset, FFilamentInstance* inst) : mAsset(asset), mInstance(inst) { NodeMap& sourceNodes = asset->isInstanced() ? asset->mInstances[0]->nodeMap : asset->mNodeMap; @@ -62,7 +55,7 @@ MorphHelper::MorphHelper(FFilamentAsset* asset, FFilamentInstance* inst) : mAsse if (mesh) { cgltf_primitive const* prims = mesh->primitives; for (cgltf_size pi = 0, count = mesh->primitives_count; pi < count; ++pi) { - addPrimitive(mesh, pi, &mMorphTable[pair.second]); + addPrimitive(mesh, pi, pair.second); } } } @@ -72,96 +65,50 @@ MorphHelper::~MorphHelper() { auto engine = mAsset->mEngine; for (auto& entry : mMorphTable) { for (auto& prim : entry.second.primitives) { - for (auto& target : prim.targets) { - engine->destroy(target.bufferObject); + if (prim.targets) { + engine->destroy(prim.targets); } } } } -void MorphHelper::applyWeights(Entity entity, float const* weights, size_t count) noexcept { +void MorphHelper::setWeights(Entity entity, float const* weights, int count) noexcept { auto& engine = *mAsset->mEngine; - auto renderableManager = &engine.getRenderableManager(); - auto renderable = renderableManager->getInstance(entity); + auto& rcm = engine.getRenderableManager(); + rcm.setMorphWeights(rcm.getInstance(entity), weights, count); +} - // If there are 4 or fewer targets, we can simply re-use the original VertexBuffer. - if (count <= 4) { - float4 vec{}; - for (size_t i = 0; i < count; i++) { - vec[i] = weights[i]; - } - renderableManager->setMorphWeights(renderable, vec); - return; - } - - // We honor up to 255 weights because our set representation is a 4-tuple of bytes, with one - // slot reserved for a sentinel value. Note that 255 is much more than the glTF min spec of 4. - count = std::min(count, size_t(255)); - - // Make a copy of the weights because we want to re-order them. - auto& sorted = mPartiallySortedWeights; - sorted.clear(); - sorted.insert(sorted.begin(), weights, weights + count); - - // Find the four highest weights in O(n) by doing a partial sort. - std::nth_element(sorted.begin(), sorted.begin() + 4, sorted.end(), [](float a, float b) { - return a > b; - }); - - // Find the "primary indices" which are the indices of the four highest weights. This is O(n). - ubyte4 primaryIndices = {kUnused, kUnused, kUnused, kUnused}; - for (size_t index = 0, primary = 0; index < count && primary < 4; ++index) { - const float w = weights[index]; - if (w > 0 && (w == sorted[0] || w == sorted[1] || w == sorted[2] || w == sorted[3])) { - primaryIndices[primary++] = index; +int MorphHelper::getTargetCount(Entity entity) const noexcept { + if (mMorphTable.count(entity)) { + auto& primitive = mMorphTable.at(entity).primitives; + if (!primitive.empty() && primitive[0].targets) { + return primitive[0].targets->getCount(); } } - - // Swap out the buffer objects for the primary indices. - for (const auto& prim : mMorphTable[entity].primitives) { - for (const auto& target : prim.targets) { - const int index = indexOf(target.morphTargetIndex, primaryIndices); - if (index > -1) { - assert_invariant(primaryIndices[index] == target.morphTargetIndex); - VertexBuffer* vb = prim.vertexBuffer; - const int bufferObjectSlot = target.type == cgltf_attribute_type_position ? - prim.positions[index] : prim.tangents[index]; - - // Slot 0 is always used for the base position so if it's getting clobbered, then - // something is seriously wrong. Should never occur, assert just in case. - assert_invariant(bufferObjectSlot != 0); - - vb->setBufferObjectAt(engine, bufferObjectSlot, target.bufferObject); - - // Do not break out early because there could be more than one target entry for this - // particular target index (e.g. positions + tangents). - } - } - } - - // Finally, set the 4-tuple uniform for the weight values by derefing the primary indices. - // Note that we first create a "safe set" by replacing the unused sentinel with zero. - float4 highest; - for (int i = 0; i < 4; i++) { - highest[i] = (primaryIndices[i] == kUnused) ? 0 : weights[primaryIndices[i]]; - } - renderableManager->setMorphWeights(renderable, highest); + return 0; } // This method copies various morphing-related data from the FilamentAsset MeshCache primitive // (which lives in transient memory) into the MorphHelper primitive (which will stay resident). -void MorphHelper::addPrimitive(cgltf_mesh const* mesh, int primitiveIndex, TableEntry* entry) { +void MorphHelper::addPrimitive(cgltf_mesh const* mesh, int primitiveIndex, Entity entity){ + auto& entry = mMorphTable[entity]; auto& engine = *mAsset->mEngine; const cgltf_primitive& prim = mesh->primitives[primitiveIndex]; const auto& gltfioPrim = mAsset->mMeshCache.at(mesh)[primitiveIndex]; VertexBuffer* vertexBuffer = gltfioPrim.vertices; + entry.primitives.push_back({ nullptr }); - entry->primitives.push_back({ vertexBuffer }); - auto& morphHelperPrim = entry->primitives.back(); + auto& morphHelperPrim = entry.primitives.back(); - for (int i = 0; i < 4; i++) { - morphHelperPrim.positions[i] = gltfioPrim.morphPositions[i]; - morphHelperPrim.tangents[i] = gltfioPrim.morphTangents[i]; + if (prim.targets_count) { + morphHelperPrim.targets = MorphTargetBuffer::Builder() + .vertexCount(vertexBuffer->getVertexCount()) + .count(prim.targets_count) + .build(engine); + + auto& rcm = engine.getRenderableManager(); + rcm.setMorphTargetBufferAt(rcm.getInstance(entity), + primitiveIndex, morphHelperPrim.targets); } const cgltf_accessor* previous = nullptr; @@ -175,19 +122,15 @@ void MorphHelper::addPrimitive(cgltf_mesh const* mesh, int primitiveIndex, Table continue; } if (atype == cgltf_attribute_type_normal) { - // TODO: use JobSystem for this, like what we do for non-morph tangents. TangentsJob job; TangentsJob::Params params = { .in = { &prim, targetIndex } }; TangentsJob::run(¶ms); if (params.out.results) { - const size_t size = params.out.vertexCount * sizeof(short4); - BufferObject* bufferObject = BufferObject::Builder().size(size).build(engine); - VertexBuffer::BufferDescriptor bd(params.out.results, size, FREE_CALLBACK); - bufferObject->setBuffer(engine, std::move(bd)); - params.out.results = nullptr; - morphHelperPrim.targets.push_back({bufferObject, targetIndex, atype}); + morphHelperPrim.targets->setTangentsAt(engine, targetIndex, + params.out.results, params.out.vertexCount); + free(params.out.results); } continue; } @@ -201,19 +144,16 @@ void MorphHelper::addPrimitive(cgltf_mesh const* mesh, int primitiveIndex, Table if (accessor->buffer_view) { auto bufferData = (const uint8_t*) accessor->buffer_view->buffer->data; assert_invariant(bufferData); - const uint8_t* data = computeBindingOffset(accessor) + bufferData; - const uint32_t size = computeBindingSize(accessor); + auto* data = computeBindingOffset(accessor) + bufferData; - // This creates a copy because we don't know when the user will free the cgltf - // source data. For non-morphed vertex buffers, we use a sharing mechanism to - // prevent copies, but here we just want to keep it as simple as possible. - uint8_t* clone = (uint8_t*) malloc(size); - memcpy(clone, data, size); - - BufferObject* bufferObject = BufferObject::Builder().size(size).build(engine); - VertexBuffer::BufferDescriptor bd(clone, size, FREE_CALLBACK); - bufferObject->setBuffer(engine, std::move(bd)); - morphHelperPrim.targets.push_back({bufferObject, targetIndex, atype}); + if (accessor->type == cgltf_type_vec3) { + morphHelperPrim.targets->setPositionsAt(engine, targetIndex, + (const float3*) data, vertexBuffer->getVertexCount()); + } else { + assert_invariant(accessor->type == cgltf_type_vec4); + morphHelperPrim.targets->setPositionsAt(engine, targetIndex, + (const float4*) data, vertexBuffer->getVertexCount()); + } } } } diff --git a/libs/gltfio/src/MorphHelper.h b/libs/gltfio/src/MorphHelper.h index 18f9a6fe48..45c51ba06f 100644 --- a/libs/gltfio/src/MorphHelper.h +++ b/libs/gltfio/src/MorphHelper.h @@ -14,9 +14,14 @@ * limitations under the License. */ +#ifndef GLTFIO_MORPHHELPER_H +#define GLTFIO_MORPHHELPER_H + #include "FFilamentAsset.h" #include "FFilamentInstance.h" +#include + #include #include @@ -44,35 +49,25 @@ public: MorphHelper(FFilamentAsset* asset, FFilamentInstance* inst); ~MorphHelper(); - /** - * Picks the 4 most influential weights and applies them to the target entity. - */ - void applyWeights(Entity targetEntity, float const* weights, size_t count) noexcept; + void setWeights(Entity entity, float const* weights, int count) noexcept; + int getTargetCount(Entity entity) const noexcept; private: - struct GltfTarget { - filament::BufferObject* bufferObject; - int morphTargetIndex; - cgltf_attribute_type type; - }; - struct GltfPrimitive { - filament::VertexBuffer* vertexBuffer; - uint8_t positions[4]; - uint8_t tangents[4]; - std::vector targets; // TODO: flatten this? + filament::MorphTargetBuffer* targets; }; struct TableEntry { std::vector primitives; // TODO: flatten this? }; - void addPrimitive(cgltf_mesh const* mesh, int primitiveIndex, TableEntry* entry); + void addPrimitive(cgltf_mesh const* mesh, int primitiveIndex, Entity entity); - std::vector mPartiallySortedWeights; tsl::robin_map mMorphTable; const FFilamentAsset* mAsset; const FFilamentInstance* mInstance; }; } // namespace gltfio + +#endif // GLTFIO_MORPHHELPER_H diff --git a/libs/gltfio/src/ResourceLoader.cpp b/libs/gltfio/src/ResourceLoader.cpp index 4d7142b9dd..e98785f205 100644 --- a/libs/gltfio/src/ResourceLoader.cpp +++ b/libs/gltfio/src/ResourceLoader.cpp @@ -20,6 +20,7 @@ #include "GltfEnums.h" #include "FFilamentAsset.h" #include "TangentsJob.h" +#include "MorphHelper.h" #include "upcast.h" #include @@ -872,15 +873,10 @@ void ResourceLoader::Impl::computeTangents(FFilamentAsset* asset) { // Collect all TANGENT vertex attribute slots that need to be populated. tsl::robin_map baseTangents; - tsl::robin_map morphTangents[4]; for (auto slot : asset->mBufferSlots) { if (slot.accessor != kGenerateTangents && slot.accessor != kGenerateNormals) { continue; } - if (slot.morphTarget) { - morphTangents[slot.morphTarget - 1][slot.vertexBuffer] = slot.bufferIndex; - continue; - } baseTangents[slot.vertexBuffer] = slot.bufferIndex; } @@ -896,13 +892,6 @@ void ResourceLoader::Impl::computeTangents(FFilamentAsset* asset) { if (iter != baseTangents.end()) { jobParams.emplace_back(Params {{ pair.first }, {vb, iter->second }}); } - for (int morphTarget = 0; morphTarget < 4; morphTarget++) { - const auto& tangents = morphTangents[morphTarget]; - auto iter = tangents.find(vb); - if (iter != tangents.end()) { - jobParams.emplace_back(Params {{ pair.first, morphTarget }, {vb, iter->second }}); - } - } } // Kick off jobs for computing tangent frames. @@ -923,6 +912,8 @@ void ResourceLoader::Impl::computeTangents(FFilamentAsset* asset) { params.out.results, bo->getByteCount(), FREE_CALLBACK)); params.context.vb->setBufferObjectAt(*mEngine, params.context.slot, bo); } + + asset->mMorpher = new MorphHelper(asset, nullptr); } ResourceLoader::Impl::~Impl() { diff --git a/libs/viewer/include/viewer/SimpleViewer.h b/libs/viewer/include/viewer/SimpleViewer.h index f3787f2d8f..859942e231 100644 --- a/libs/viewer/include/viewer/SimpleViewer.h +++ b/libs/viewer/include/viewer/SimpleViewer.h @@ -35,6 +35,8 @@ #include #include +#include + namespace filagui { class ImGuiHelper; } @@ -241,6 +243,7 @@ private: Settings mSettings; int mSidebarWidth; uint32_t mFlags; + std::vector mMorphWeights; // 0 is the default "free camera". Additional cameras come from the gltf file (1-based index). int mCurrentCamera = 0; diff --git a/libs/viewer/src/SimpleViewer.cpp b/libs/viewer/src/SimpleViewer.cpp index f3feb2ad30..65613e17b6 100644 --- a/libs/viewer/src/SimpleViewer.cpp +++ b/libs/viewer/src/SimpleViewer.cpp @@ -34,6 +34,7 @@ #include #include +#include using namespace filagui; using namespace filament::math; @@ -406,6 +407,13 @@ void SimpleViewer::populateScene(FilamentAsset* asset, FilamentInstance* instan mAnimator = instanceToAnimate ? instanceToAnimate->getAnimator() : asset->getAnimator(); updateRootTransform(); mScene->addEntities(asset->getLightEntities(), asset->getLightEntityCount()); + + int morphCount = 0; + for (size_t i = 0, c = asset->getEntityCount(); i < c; ++i) { + auto entity = asset->getEntities()[i]; + morphCount = std::max(morphCount, asset->getMorphTargetCount(entity)); + } + mMorphWeights.resize(std::min(morphCount, 128), 0); } auto& tcm = mEngine->getRenderableManager(); @@ -427,6 +435,7 @@ void SimpleViewer::removeAsset() { } mAsset = nullptr; mAnimator = nullptr; + mMorphWeights.clear(); } void SimpleViewer::setIndirectLight(filament::IndirectLight* ibl, @@ -967,6 +976,19 @@ void SimpleViewer::updateUserInterface() { ImGui::Unindent(); } + if (!mMorphWeights.empty() && ImGui::CollapsingHeader("Morphing")) { + for (int i = 0; i != mMorphWeights.size(); ++i) { + std::stringstream ss; + ss << i + 1 << " Weight"; + std::string label = ss.str(); + ImGui::SliderFloat(label.data(), &mMorphWeights[i], 0.0f, 1.0); + } + for (size_t i = 0, c = mAsset->getEntityCount(); i != c; ++i) { + mAsset->setMorphWeights(mAsset->getEntities()[i], + mMorphWeights.data(), mMorphWeights.size()); + } + } + if (mEnableWireframe) { mScene->addEntity(mAsset->getWireframe()); } else { diff --git a/shaders/src/getters.vs b/shaders/src/getters.vs index 4ba481a8da..cde22bfee9 100644 --- a/shaders/src/getters.vs +++ b/shaders/src/getters.vs @@ -20,6 +20,15 @@ mat3 getWorldFromModelNormalMatrix() { // Attributes access //------------------------------------------------------------------------------ +/** @public-api */ +int getVertexIndex() { +#if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT) + return gl_VertexIndex; +#else + return gl_VertexID; +#endif +} + #if defined(HAS_SKINNING_OR_MORPHING) vec3 mulBoneNormal(vec3 n, uint i) { @@ -63,6 +72,26 @@ void skinPosition(inout vec3 p, const uvec4 ids, const vec4 weights) { + mulBoneVertex(p, ids.z) * weights.z + mulBoneVertex(p, ids.w) * weights.w; } + +#define MAX_MORPH_TARGET_BUFFER_WIDTH 2048 + +void morphPosition(inout vec4 p) { + ivec3 texcoord = ivec3(getVertexIndex() % MAX_MORPH_TARGET_BUFFER_WIDTH, getVertexIndex() / MAX_MORPH_TARGET_BUFFER_WIDTH, 0); + for (uint i = 0u; i < objectUniforms.morphTargetCount; ++i) { + texcoord.z = int(i) * 2 + 0; + p += morphingUniforms.weights[i] * texelFetch(morphing_targets, texcoord, 0); + } +} + +void morphNormal(inout vec3 n) { + ivec3 texcoord = ivec3(getVertexIndex() % MAX_MORPH_TARGET_BUFFER_WIDTH, getVertexIndex() / MAX_MORPH_TARGET_BUFFER_WIDTH, 0); + for (uint i = 0u; i < objectUniforms.morphTargetCount; ++i) { + texcoord.z = int(i) * 2 + 1; + vec3 normal; + toTangentFrame(texelFetch(morphing_targets, texcoord, 0), normal); + n += morphingUniforms.weights[i].xyz * normal; + } +} #endif /** @public-api */ @@ -72,10 +101,7 @@ vec4 getPosition() { #if defined(HAS_SKINNING_OR_MORPHING) if ((objectUniforms.flags & FILAMENT_OBJECT_MORPHING_ENABLED_BIT) != 0u) { - pos += objectUniforms.morphWeights.x * mesh_custom0; - pos += objectUniforms.morphWeights.y * mesh_custom1; - pos += objectUniforms.morphWeights.z * mesh_custom2; - pos += objectUniforms.morphWeights.w * mesh_custom3; + morphPosition(pos); } if ((objectUniforms.flags & FILAMENT_OBJECT_SKINNING_ENABLED_BIT) != 0u) { @@ -112,15 +138,6 @@ vec4 getCustom6() { return mesh_custom6; } vec4 getCustom7() { return mesh_custom7; } #endif -/** @public-api */ -int getVertexIndex() { -#if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT) - return gl_VertexIndex; -#else - return gl_VertexID; -#endif -} - //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ diff --git a/shaders/src/main.vs b/shaders/src/main.vs index 32637d1c71..42b652a354 100644 --- a/shaders/src/main.vs +++ b/shaders/src/main.vs @@ -36,15 +36,7 @@ void main() { #if defined(HAS_SKINNING_OR_MORPHING) if ((objectUniforms.flags & FILAMENT_OBJECT_MORPHING_ENABLED_BIT) != 0u) { - vec3 normal0, normal1, normal2, normal3; - toTangentFrame(mesh_custom4, normal0); - toTangentFrame(mesh_custom5, normal1); - toTangentFrame(mesh_custom6, normal2); - toTangentFrame(mesh_custom7, normal3); - material.worldNormal += objectUniforms.morphWeights.x * normal0; - material.worldNormal += objectUniforms.morphWeights.y * normal1; - material.worldNormal += objectUniforms.morphWeights.z * normal2; - material.worldNormal += objectUniforms.morphWeights.w * normal3; + morphNormal(material.worldNormal); material.worldNormal = normalize(material.worldNormal); } diff --git a/web/filament-js/filament.d.ts b/web/filament-js/filament.d.ts index 9163ca7c7c..46e6b3ee55 100644 --- a/web/filament-js/filament.d.ts +++ b/web/filament-js/filament.d.ts @@ -992,14 +992,6 @@ export enum VertexAttribute { CUSTOM5 = 13, CUSTOM6 = 14, CUSTOM7 = 15, - MORPH_POSITION_0 = CUSTOM0, - MORPH_POSITION_1 = CUSTOM1, - MORPH_POSITION_2 = CUSTOM2, - MORPH_POSITION_3 = CUSTOM3, - MORPH_TANGENTS_0 = CUSTOM4, - MORPH_TANGENTS_1 = CUSTOM5, - MORPH_TANGENTS_2 = CUSTOM6, - MORPH_TANGENTS_3 = CUSTOM7, } export enum VertexBuffer$AttributeType { diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index 1c29b48897..6a54edcc54 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -1045,11 +1045,10 @@ class_("RenderableManager") } self->setBones(instance, bones.data(), bones.size(), offset); }), allow_raw_pointers()) - - // NOTE: this cannot take a float4 due to a binding issue. + .function("setMorphWeights", EMBIND_LAMBDA(void, (RenderableManager* self, - RenderableManager::Instance instance, float x, float y, float z, float w), { - self->setMorphWeights(instance, {x, y, z, w}); + RenderableManager::Instance instance, float* weights, size_t count), { + self->setMorphWeights(instance, weights, count); }), allow_raw_pointers()) .function("getAxisAlignedBoundingBox", &RenderableManager::getAxisAlignedBoundingBox) diff --git a/web/filament-js/jsenums.cpp b/web/filament-js/jsenums.cpp index ed9d16fe55..45647ba262 100644 --- a/web/filament-js/jsenums.cpp +++ b/web/filament-js/jsenums.cpp @@ -61,15 +61,7 @@ enum_("VertexAttribute") .value("CUSTOM4", CUSTOM4) .value("CUSTOM5", CUSTOM5) .value("CUSTOM6", CUSTOM6) - .value("CUSTOM7", CUSTOM7) - .value("MORPH_POSITION_0", MORPH_POSITION_0) - .value("MORPH_POSITION_1", MORPH_POSITION_1) - .value("MORPH_POSITION_2", MORPH_POSITION_2) - .value("MORPH_POSITION_3", MORPH_POSITION_3) - .value("MORPH_TANGENTS_0", MORPH_TANGENTS_0) - .value("MORPH_TANGENTS_1", MORPH_TANGENTS_1) - .value("MORPH_TANGENTS_2", MORPH_TANGENTS_2) - .value("MORPH_TANGENTS_3", MORPH_TANGENTS_3); + .value("CUSTOM7", CUSTOM7); enum_("BufferObject$BindingType") .value("VERTEX", BufferObject::BindingType::VERTEX); From 00d9c6921d2e5cefa696ad860443d3c056cdb931 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Wed, 12 Jan 2022 13:59:11 -0800 Subject: [PATCH 09/22] Add initial glossy screen-space reflections (#4946) --- RELEASE_NOTES.md | 3 + filament/include/filament/Material.h | 5 +- filament/include/filament/Options.h | 12 + filament/include/filament/View.h | 15 ++ filament/src/FrameHistory.h | 6 +- filament/src/Material.cpp | 5 + filament/src/MaterialParser.cpp | 6 + filament/src/MaterialParser.h | 1 + filament/src/PerViewUniforms.cpp | 25 ++ filament/src/PerViewUniforms.h | 5 + filament/src/PostProcessManager.cpp | 4 - filament/src/Renderer.cpp | 102 ++++++- filament/src/View.cpp | 21 ++ filament/src/details/Material.h | 2 + filament/src/details/Renderer.h | 5 + filament/src/details/View.h | 17 ++ filament/src/materials/ssao/depthUtils.fs | 2 +- .../include/filament/MaterialChunkType.h | 1 + .../include/filament/MaterialEnums.h | 8 + .../include/private/filament/UibStructs.h | 10 +- .../filamat/include/filamat/MaterialBuilder.h | 5 + libs/filamat/src/MaterialBuilder.cpp | 7 + libs/filamat/src/UibGenerator.cpp | 10 +- libs/filamat/src/shaders/CodeGenerator.cpp | 1 + libs/filamat/src/shaders/MaterialInfo.h | 1 + libs/filamat/src/shaders/ShaderGenerator.cpp | 13 + libs/gltfio/materials/gltflite.mat.in | 1 + libs/gltfio/materials/sheen.mat.in | 1 + libs/gltfio/materials/transmission.mat.in | 1 + libs/gltfio/materials/ubershader.mat.in | 1 + libs/gltfio/materials/volume.mat.in | 1 + libs/gltfio/src/MaterialGenerator.cpp | 1 + libs/viewer/include/viewer/Settings.h | 2 + libs/viewer/src/Settings.cpp | 40 +++ libs/viewer/src/SimpleViewer.cpp | 9 + samples/rendertarget.cpp | 26 +- shaders/CMakeLists.txt | 1 + shaders/src/light_indirect.fs | 64 ++++- shaders/src/light_reflections.fs | 255 ++++++++++++++++++ tools/matc/src/matc/ParametersProcessor.cpp | 15 ++ 40 files changed, 664 insertions(+), 46 deletions(-) create mode 100644 shaders/src/light_reflections.fs diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 3c4c248a0a..b2f909e627 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,6 +7,9 @@ A new header is inserted each time a *tag* is created. ## v1.16.1 +- engine: add experimental glossy screen-space reflections [⚠️ **Materials need to be rebuilt to + access this new feature**]. + ## v1.16.0 - engine: Fixes skinning calculations (#4887) [⚠️ **Material breakage**]. diff --git a/filament/include/filament/Material.h b/filament/include/filament/Material.h index a8722804b7..572de84454 100644 --- a/filament/include/filament/Material.h +++ b/filament/include/filament/Material.h @@ -193,9 +193,12 @@ public: //! Returns the refraction mode used by this material. RefractionMode getRefractionMode() const noexcept; - // Return the refraction type used by this material. + //! Return the refraction type used by this material. RefractionType getRefractionType() const noexcept; + //! Returns the reflection mode used by this material. + ReflectionMode getReflectionMode() const noexcept; + /** * Returns the number of parameters declared by this material. * The returned value can be 0. diff --git a/filament/include/filament/Options.h b/filament/include/filament/Options.h index 5904bc1e34..b43a5f349b 100644 --- a/filament/include/filament/Options.h +++ b/filament/include/filament/Options.h @@ -316,6 +316,18 @@ struct TemporalAntiAliasingOptions { bool enabled = false; //!< enables or disables temporal anti-aliasing }; +/** + * Options for Screen-space Reflections. + * @see setScreenSpaceReflectionsOptions() + */ +struct ScreenSpaceReflectionsOptions { + float thickness = 0.5f; //!< ray thickness, in world units + float bias = 0.01f; //!< bias, in world units, to prevent self-intersections + float maxDistance = 3.0f; //!< maximum distance, in world units, to raycast + float stride = 1.0f; //!< stride, in texels, for samples along the ray. + bool enabled = false; +}; + /** * List of available post-processing anti-aliasing techniques. * @see setAntiAliasing, getAntiAliasing, setSampleCount diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h index ef13813eeb..f8fdf23af5 100644 --- a/filament/include/filament/View.h +++ b/filament/include/filament/View.h @@ -83,6 +83,7 @@ public: using MultiSampleAntiAliasingOptions = MultiSampleAntiAliasingOptions; using VsmShadowOptions = VsmShadowOptions; using SoftShadowOptions = SoftShadowOptions; + using ScreenSpaceReflectionsOptions = ScreenSpaceReflectionsOptions; /** * Sets the View's name. Only useful for debugging. @@ -330,6 +331,20 @@ public: */ TemporalAntiAliasingOptions const& getTemporalAntiAliasingOptions() const noexcept; + /** + * Enables or disable screen-space reflections. Disabled by default. + * + * @param options screen-space reflections options + */ + void setScreenSpaceReflectionsOptions(ScreenSpaceReflectionsOptions options) noexcept; + + /** + * Returns screen-space reflections options. + * + * @return screen-space reflections options + */ + ScreenSpaceReflectionsOptions const& getScreenSpaceReflectionsOptions() const noexcept; + /** * Enables or disable multi-sample anti-aliasing (MSAA). Disabled by default. * diff --git a/filament/src/FrameHistory.h b/filament/src/FrameHistory.h index 3a654cdf81..208bf5e903 100644 --- a/filament/src/FrameHistory.h +++ b/filament/src/FrameHistory.h @@ -28,7 +28,7 @@ namespace filament { struct FrameHistoryEntry { FrameGraphTexture color; FrameGraphTexture::Descriptor colorDesc; - math::mat4f projection; + math::mat4f projection; // world space to clip space math::float2 jitter{}; uint32_t frameId = 0; @@ -60,6 +60,10 @@ public: return mCurrentEntry; } + const T& getCurrent() const noexcept { + return mCurrentEntry; + } + // This pushes the current frame info to the FIFO, effectively destroying // the oldest state (note: only the structure is destroyed, handles stored in it may // have to be destroyed prior to calling this). diff --git a/filament/src/Material.cpp b/filament/src/Material.cpp index cc7341f782..78dad235f9 100644 --- a/filament/src/Material.cpp +++ b/filament/src/Material.cpp @@ -189,6 +189,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder) parser->getRequiredAttributes(&mRequiredAttributes); parser->getRefractionMode(&mRefractionMode); parser->getRefractionType(&mRefractionType); + parser->getReflectionMode(&mReflectionMode); if (mBlendingMode == BlendingMode::MASKED) { parser->getMaskThreshold(&mMaskThreshold); @@ -653,6 +654,10 @@ RefractionType Material::getRefractionType() const noexcept { return upcast(this)->getRefractionType(); } +ReflectionMode Material::getReflectionMode() const noexcept { + return upcast(this)->getReflectionMode(); +} + bool Material::hasParameter(const char* name) const noexcept { return upcast(this)->hasParameter(name); } diff --git a/filament/src/MaterialParser.cpp b/filament/src/MaterialParser.cpp index 3339e19d80..fe34fa39f7 100644 --- a/filament/src/MaterialParser.cpp +++ b/filament/src/MaterialParser.cpp @@ -269,6 +269,12 @@ bool MaterialParser::getRefractionType(RefractionType* value) const noexcept { return mImpl.getFromSimpleChunk(ChunkType::MaterialRefractionType, (uint8_t*)value); } +bool MaterialParser::getReflectionMode(ReflectionMode* value) const noexcept { + static_assert(sizeof(ReflectionMode) == sizeof(uint8_t), + "ReflectionMode expected size is wrong"); + return mImpl.getFromSimpleChunk(ChunkType::MaterialReflectionMode, (uint8_t*)value); +} + bool MaterialParser::getShader(ShaderBuilder& shader, ShaderModel shaderModel, uint8_t variant, ShaderType stage) noexcept { return mImpl.mMaterialChunk.getShader(shader, diff --git a/filament/src/MaterialParser.h b/filament/src/MaterialParser.h index d55acdad6c..a16a1d47fb 100644 --- a/filament/src/MaterialParser.h +++ b/filament/src/MaterialParser.h @@ -85,6 +85,7 @@ public: bool getRequiredAttributes(AttributeBitset*) const noexcept; bool getRefractionMode(RefractionMode* value) const noexcept; bool getRefractionType(RefractionType* value) const noexcept; + bool getReflectionMode(ReflectionMode* value) const noexcept; bool hasCustomDepthShader(bool* value) const noexcept; bool hasSpecularAntiAliasing(bool* value) const noexcept; bool getSpecularAntiAliasingVariance(float* value) const noexcept; diff --git a/filament/src/PerViewUniforms.cpp b/filament/src/PerViewUniforms.cpp index 2e93cd3908..f9c71d58e9 100644 --- a/filament/src/PerViewUniforms.cpp +++ b/filament/src/PerViewUniforms.cpp @@ -177,6 +177,31 @@ void PerViewUniforms::prepareSSR(Handle ssr, float refractionLodOffse s.refractionLodOffset = refractionLodOffset; } +void PerViewUniforms::prepareSSReflections(TextureHandle ssr, math::mat4f const& historyProjection, + math::mat4f const& projectToPixelMatrix, + ScreenSpaceReflectionsOptions const& ssrOptions) noexcept { + mPerViewSb.setSampler(PerViewSib::SSR, ssr, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR + }); + auto& s = mPerViewUb.edit(); + s.ssrReprojection = historyProjection; + s.ssrProjectToPixelMatrix = projectToPixelMatrix; + s.ssrThickness = ssrOptions.thickness; + s.ssrBias = ssrOptions.bias; + s.ssrDistance = ssrOptions.maxDistance; + s.ssrStride = ssrOptions.stride; +} + +void PerViewUniforms::disableSSReflections() noexcept { + // We must bind a placeholder texture, even if reflections are disabled, to avoid a + // "no texture unit bound" warning. + Handle placeholder = mEngine.getPostProcessManager().getOneTexture(); + auto& s = mPerViewUb.edit(); + mPerViewSb.setSampler(PerViewSib::SSR, placeholder, {}); + s.ssrDistance = 0.0f; +} + void PerViewUniforms::prepareStructure(Handle structure) noexcept { // sampler must be NEAREST mPerViewSb.setSampler(PerViewSib::STRUCTURE, structure, {}); diff --git a/filament/src/PerViewUniforms.h b/filament/src/PerViewUniforms.h index 63d7ac17f7..76bc872ca3 100644 --- a/filament/src/PerViewUniforms.h +++ b/filament/src/PerViewUniforms.h @@ -35,6 +35,7 @@ namespace filament { struct AmbientOcclusionOptions; struct DynamicResolutionOptions; struct FogOptions; +struct ScreenSpaceReflectionsOptions; struct SoftShadowOptions; struct TemporalAntiAliasingOptions; struct VsmShadowOptions; @@ -72,6 +73,10 @@ public: void prepareStructure(TextureHandle structure) noexcept; void prepareSSAO(TextureHandle ssao, AmbientOcclusionOptions const& options) noexcept; void prepareSSR(TextureHandle ssr, float refractionLodOffset) noexcept; + void prepareSSReflections(TextureHandle ssr, math::mat4f const& historyProjection, + math::mat4f const& projectToPixelMatrix, + ScreenSpaceReflectionsOptions const& ssrOptions) noexcept; + void disableSSReflections() noexcept; void prepareShadowMapping(ShadowMappingUniforms const& shadowMappingUniforms, VsmShadowOptions const& options) noexcept; diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 4fbef7c253..c680367385 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -2167,12 +2167,8 @@ void PostProcessManager::prepareTaa(FrameHistory& frameHistory, auto& current = frameHistory.getCurrent(); // get sample position within a pixel [-0.5, 0.5] const float2 jitter = halton(previous.frameId) - 0.5f; - // compute the world-space to clip-space matrix for this frame - current.projection = cameraInfo.projection * (cameraInfo.view * cameraInfo.worldOrigin); // save this frame's sample position current.jitter = jitter; - // update frame id - current.frameId = previous.frameId + 1; } FrameGraphId PostProcessManager::taa(FrameGraph& fg, diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp index d7c805ad9e..6fc0981834 100644 --- a/filament/src/Renderer.cpp +++ b/filament/src/Renderer.cpp @@ -240,6 +240,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { auto taaOptions = view.getTemporalAntiAliasingOptions(); auto vignetteOptions = view.getVignetteOptions(); auto colorGrading = view.getColorGrading(); + auto ssReflectionsOptions = view.getScreenSpaceReflectionsOptions(); if (!hasPostProcess) { // disable all effects that are part of post-processing dofOptions.enabled = false; @@ -384,7 +385,8 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { .msaa = msaaSampleCount, .clearFlags = clearFlags, .clearColor = clearColor, - .hasContactShadows = scene.hasContactShadows() + .hasContactShadows = scene.hasContactShadows(), + .hasScreenSpaceReflections = ssReflectionsOptions.enabled }; // asSubpass is disabled with TAA (although it's supported) because performance was degraded @@ -470,6 +472,17 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { }); } + // Store this frame's camera projection in the frame history. + if (UTILS_UNLIKELY(taaOptions.enabled || ssReflectionsOptions.enabled)) { + auto projection = cameraInfo.projection * (cameraInfo.view * cameraInfo.worldOrigin); + setHistoryProjection(view, projection); + // update frame id + auto& history = view.getFrameHistory(); + auto const& previous = history[0]; + auto& current = history.getCurrent(); + current.frameId = previous.frameId + 1; + } + // Apply the TAA jitter to everything after the structure pass, starting with the color pass. if (taaOptions.enabled) { auto& history = view.getFrameHistory(); @@ -599,7 +612,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { struct ExportColorHistoryData { FrameGraphId color; }; - if (taaOptions.enabled) { + if (taaOptions.enabled || ssReflectionsOptions.enabled) { fg.addPass("Export color history", [&](FrameGraph::Builder& builder, auto& data) { // We need to use sideEffect here to ensure this pass won't be culled. The "output" of @@ -832,12 +845,25 @@ FrameGraphId FRenderer::colorPass(FrameGraph& fg, const char* FrameGraphId output; FrameGraphId depth; FrameGraphId ssao; - FrameGraphId ssr; + FrameGraphId ssr; // either screen-space reflections or refractions FrameGraphId structure; float4 clearColor{}; TargetBufferFlags clearFlags{}; }; + FrameGraphId colorHistory; + mat4f historyProjection; + if (config.hasScreenSpaceReflections) { + colorHistory = getColorHistory(fg, view.getFrameHistory()); + if (UTILS_UNLIKELY(!colorHistory)) { + // if we don't have a history yet, don't render reflections this frame + } else { + const FrameHistory& frameHistory = view.getFrameHistory(); + FrameHistoryEntry const& entry = frameHistory[0]; + historyProjection = entry.projection; + } + } + auto& colorPass = fg.addPass(name, [&](FrameGraph::Builder& builder, ColorPassData& data) { @@ -846,12 +872,24 @@ FrameGraphId FRenderer::colorPass(FrameGraph& fg, const char* TargetBufferFlags clearColorFlags = config.clearFlags & TargetBufferFlags::COLOR; data.shadows = blackboard.get("shadows"); - data.ssr = blackboard.get("ssr"); data.ssao = blackboard.get("ssao"); data.color = blackboard.get("color"); data.depth = blackboard.get("depth"); - if (config.hasContactShadows) { + // TODO: we currently only allow either screen-space reflections or refractions, + // but not both. If SS reflections are enabled, we prefer those. + if (config.hasScreenSpaceReflections && colorHistory) { + // Screen-space reflections + data.ssr = builder.sample(colorHistory); + } else { + // Screen-space refractions + data.ssr = blackboard.get("ssr"); + if (data.ssr) { + data.ssr = builder.sample(data.ssr); + } + } + + if (config.hasContactShadows || config.hasScreenSpaceReflections) { data.structure = blackboard.get("structure"); assert_invariant(data.structure); data.structure = builder.sample(data.structure); @@ -861,10 +899,6 @@ FrameGraphId FRenderer::colorPass(FrameGraph& fg, const char* data.shadows = builder.sample(data.shadows); } - if (data.ssr) { - data.ssr = builder.sample(data.ssr); - } - if (data.ssao) { data.ssao = builder.sample(data.ssao); } @@ -942,8 +976,32 @@ FrameGraphId FRenderer::colorPass(FrameGraph& fg, const char* view.prepareStructure(data.structure ? resources.getTexture(data.structure) : ppm.getOneTexture()); + // set screen-space reflections or screen-space refractions if (data.ssr) { - view.prepareSSR(resources.getTexture(data.ssr), config.refractionLodOffset); + // We currently only allow either SS reflections or refractions. + auto const& ssrOptions = view.getScreenSpaceReflectionsOptions(); + const bool reflections = config.hasScreenSpaceReflections && ssrOptions.enabled; + const bool refractions = !config.hasScreenSpaceReflections; + + if (reflections) { + const auto& cameraInfo = view.getCameraInfo(); + auto reprojection = + getClipSpaceToTextureSpaceMatrix() * + historyProjection * + inverse(cameraInfo.view * cameraInfo.worldOrigin); + + auto projectToPixelMatrix = + getClipSpaceToTextureSpaceMatrix() * + cameraInfo.projection; + + view.prepareSSReflections(resources.getTexture(data.ssr), reprojection, + projectToPixelMatrix, ssrOptions); + } else if (refractions) { // TODO: support both + view.prepareSSR(resources.getTexture(data.ssr), config.refractionLodOffset); + } + } else { + // Screen-space reflections must be explicitly disabled. + view.disableSSReflections(); } view.prepareViewport(static_cast(out.params.viewport)); @@ -1269,6 +1327,30 @@ void FRenderer::initializeClearFlags() { | TargetBufferFlags::DEPTH_AND_STENCIL; } +math::mat4f FRenderer::getClipSpaceToTextureSpaceMatrix() const noexcept { + // Compute a clip-space [-1 to 1] to texture space [0 to 1] matrix, taking into account + // API-level differences. + const bool textureSpaceYFlipped = + mEngine.getBackend() == Backend::METAL || mEngine.getBackend() == Backend::VULKAN; + return mat4f(textureSpaceYFlipped ? mat4f::row_major_init{ + 0.5f, 0.0f, 0.0f, 0.5f, + 0.0f, -0.5f, 0.0f, 0.5f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + } : mat4f::row_major_init{ + 0.5f, 0.0f, 0.0f, 0.5f, + 0.0f, 0.5f, 0.0f, 0.5f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }); +} + +void FRenderer::setHistoryProjection(FView& view, math::mat4f const& projection) { + auto& history = view.getFrameHistory(); + auto& current = history.getCurrent(); + current.projection = projection; +} + FrameGraphId FRenderer::getColorHistory(FrameGraph& fg, FrameHistory const& frameHistory) noexcept { // Here we import the previous frame's color output and cache it in the blackboard. This ensures diff --git a/filament/src/View.cpp b/filament/src/View.cpp index 0e9cf8f43c..f3825eec05 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -659,6 +659,19 @@ void FView::prepareSSAO(Handle ssao) const noexcept { void FView::prepareSSR(Handle ssr, float refractionLodOffset) const noexcept { mPerViewUniforms.prepareSSR(ssr, refractionLodOffset); + // If screen-space refractions are enabled, make sure to disable screen-space reflections. + // TODO: support simultaneous screen-space refractions and reflections. + mPerViewUniforms.disableSSReflections(); +} + +void FView::disableSSReflections() const noexcept { + mPerViewUniforms.disableSSReflections(); +} + +void FView::prepareSSReflections(backend::Handle ssr, + math::mat4f historyProjection, math::mat4f projectToPixelMatrix, + ScreenSpaceReflectionsOptions const& ssrOptions) const noexcept { + mPerViewUniforms.prepareSSReflections(ssr, historyProjection, projectToPixelMatrix, ssrOptions); } void FView::prepareStructure(Handle structure) const noexcept { @@ -1030,6 +1043,14 @@ const View::MultiSampleAntiAliasingOptions& View::getMultiSampleAntiAliasingOpti return upcast(this)->getMultiSampleAntiAliasingOptions(); } +void View::setScreenSpaceReflectionsOptions(ScreenSpaceReflectionsOptions options) noexcept { + upcast(this)->setScreenSpaceReflectionsOptions(options); +} + +const View::ScreenSpaceReflectionsOptions& View::getScreenSpaceReflectionsOptions() const noexcept { + return upcast(this)->getScreenSpaceReflectionsOptions(); +} + void View::setColorGrading(ColorGrading* colorGrading) noexcept { return upcast(this)->setColorGrading(upcast(colorGrading)); } diff --git a/filament/src/details/Material.h b/filament/src/details/Material.h index b75ecfefee..64031690b9 100644 --- a/filament/src/details/Material.h +++ b/filament/src/details/Material.h @@ -118,6 +118,7 @@ public: AttributeBitset getRequiredAttributes() const noexcept { return mRequiredAttributes; } RefractionMode getRefractionMode() const noexcept { return mRefractionMode; } RefractionType getRefractionType() const noexcept { return mRefractionType; } + ReflectionMode getReflectionMode() const noexcept { return mReflectionMode; } bool hasSpecularAntiAliasing() const noexcept { return mSpecularAntiAliasing; } float getSpecularAntiAliasingVariance() const noexcept { return mSpecularAntiAliasingVariance; } @@ -188,6 +189,7 @@ private: AttributeBitset mRequiredAttributes; RefractionMode mRefractionMode = RefractionMode::NONE; RefractionType mRefractionType = RefractionType::SOLID; + ReflectionMode mReflectionMode = ReflectionMode::DEFAULT; uint64_t mMaterialProperties = 0; float mMaskThreshold = 0.4f; diff --git a/filament/src/details/Renderer.h b/filament/src/details/Renderer.h index 026915e8ad..ffbb37e91f 100644 --- a/filament/src/details/Renderer.h +++ b/filament/src/details/Renderer.h @@ -152,6 +152,7 @@ private: math::float4 clearColor = {}; float refractionLodOffset; bool hasContactShadows; + bool hasScreenSpaceReflections; }; FrameGraphId colorPass(FrameGraph& fg, const char* name, @@ -187,6 +188,10 @@ private: return clock::now() - getUserEpoch(); } + math::mat4f getClipSpaceToTextureSpaceMatrix() const noexcept; + + void setHistoryProjection(FView& view, math::mat4f const& projection); + static FrameGraphId getColorHistory(FrameGraph& fg, FrameHistory const& frameHistory) noexcept; diff --git a/filament/src/details/View.h b/filament/src/details/View.h index d474574bdf..639192e2ec 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -172,6 +172,10 @@ public: void prepareSSAO(backend::Handle ssao) const noexcept; void prepareSSR(backend::Handle ssr, float refractionLodOffset) const noexcept; + void disableSSReflections() const noexcept; + void prepareSSReflections(backend::Handle ssr, + math::mat4f historyProjection, math::mat4f projectToPixelMatrix, + ScreenSpaceReflectionsOptions const& ssrOptions) const noexcept; void prepareStructure(backend::Handle structure) const noexcept; void prepareShadow(backend::Handle structure) const noexcept; void prepareShadowMap() const noexcept; @@ -259,6 +263,18 @@ public: return mMultiSampleAntiAliasingOptions; } + void setScreenSpaceReflectionsOptions(ScreenSpaceReflectionsOptions options) noexcept { + options.thickness = std::max(0.0f, options.thickness); + options.bias = std::max(0.0f, options.bias); + options.maxDistance = std::max(0.0f, options.maxDistance); + options.stride = std::max(1.0f, options.stride); + mScreenSpaceReflectionsOptions = options; + } + + const ScreenSpaceReflectionsOptions& getScreenSpaceReflectionsOptions() const noexcept { + return mScreenSpaceReflectionsOptions; + } + void setColorGrading(FColorGrading* colorGrading) noexcept { mColorGrading = colorGrading == nullptr ? mDefaultColorGrading : colorGrading; } @@ -574,6 +590,7 @@ private: VignetteOptions mVignetteOptions; TemporalAntiAliasingOptions mTemporalAntiAliasingOptions; MultiSampleAntiAliasingOptions mMultiSampleAntiAliasingOptions; + ScreenSpaceReflectionsOptions mScreenSpaceReflectionsOptions; BlendMode mBlendMode = BlendMode::OPAQUE; const FColorGrading* mColorGrading = nullptr; const FColorGrading* mDefaultColorGrading = nullptr; diff --git a/filament/src/materials/ssao/depthUtils.fs b/filament/src/materials/ssao/depthUtils.fs index 1aaa4a61ed..235fe4f87f 100644 --- a/filament/src/materials/ssao/depthUtils.fs +++ b/filament/src/materials/ssao/depthUtils.fs @@ -21,7 +21,7 @@ highp float linearizeDepth(highp float depth) { // Our far plane is at infinity, which causes a division by zero below, which in turn // causes some issues on some GPU. We workaround it by replacing "infinity" by the closest // value representable in a 24 bit depth buffer. - const float preventDiv0 = 1.0 / 16777216.0; + const highp float preventDiv0 = 1.0 / 16777216.0; mat4 p = getViewFromClipMatrix(); // this works with perspective and ortho projections, for a perspective projection // this resolves to -near/depth, for an ortho projection this resolves to depth*(far - near) - far diff --git a/libs/filabridge/include/filament/MaterialChunkType.h b/libs/filabridge/include/filament/MaterialChunkType.h index 56d6eb50d5..b80e635451 100644 --- a/libs/filabridge/include/filament/MaterialChunkType.h +++ b/libs/filabridge/include/filament/MaterialChunkType.h @@ -62,6 +62,7 @@ enum UTILS_PUBLIC ChunkType : uint64_t { MaterialDomain = charTo64bitNum("MAT_DOMN"), MaterialRefraction = charTo64bitNum("MAT_REFM"), MaterialRefractionType = charTo64bitNum("MAT_REFT"), + MaterialReflectionMode = charTo64bitNum("MAT_REFL"), MaterialRequiredAttributes = charTo64bitNum("MAT_REQA"), MaterialDepthWriteSet = charTo64bitNum("MAT_DEWS"), diff --git a/libs/filabridge/include/filament/MaterialEnums.h b/libs/filabridge/include/filament/MaterialEnums.h index dbca04195f..c4ef79b5d4 100644 --- a/libs/filabridge/include/filament/MaterialEnums.h +++ b/libs/filabridge/include/filament/MaterialEnums.h @@ -176,6 +176,14 @@ enum class RefractionType : uint8_t { THIN = 1, //!< refraction through thin objects (e.g. window) }; +/** + * Reflection mode + */ +enum class ReflectionMode : uint8_t { + DEFAULT = 0, //! reflections sample from the scene's IBL only + SCREEN_SPACE = 1, //! reflections sample from screen space, and fallback to the scene's IBL +}; + // can't really use std::underlying_type::type because the driver takes a uint32_t using AttributeBitset = utils::bitset32; diff --git a/libs/filabridge/include/private/filament/UibStructs.h b/libs/filabridge/include/private/filament/UibStructs.h index aafbe10751..42c2edcfc0 100644 --- a/libs/filabridge/include/private/filament/UibStructs.h +++ b/libs/filabridge/include/private/filament/UibStructs.h @@ -136,8 +136,16 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init) float nearOverFarMinusNear; // n / (f-n), always positive float temporalNoise; // noise [0,1] when TAA is used, 0 otherwise + // Screen-space reflections + math::mat4f ssrReprojection; + math::mat4f ssrProjectToPixelMatrix; + float ssrThickness; // ssr thickness, in world units + float ssrBias; // ssr bias, in world units + float ssrDistance; // ssr world raycast distance, 0 when ssr is off + float ssrStride; // ssr texel stride, >= 1.0 + // bring PerViewUib to 2 KiB - math::float4 padding2[58]; + math::float4 padding3[49]; }; // 2 KiB == 128 float4s diff --git a/libs/filamat/include/filamat/MaterialBuilder.h b/libs/filamat/include/filamat/MaterialBuilder.h index 6a7077fe00..1650fab63a 100644 --- a/libs/filamat/include/filamat/MaterialBuilder.h +++ b/libs/filamat/include/filamat/MaterialBuilder.h @@ -190,6 +190,7 @@ public: using MaterialDomain = filament::MaterialDomain; using RefractionMode = filament::RefractionMode; using RefractionType = filament::RefractionType; + using ReflectionMode = filament::ReflectionMode; using VertexAttribute = filament::VertexAttribute; using ShaderQuality = filament::ShaderQuality; @@ -461,6 +462,9 @@ public: //! Specify the refraction type MaterialBuilder& refractionType(RefractionType refractionType) noexcept; + //! Specifies how reflections should be rendered (default is DEFAULT). + MaterialBuilder& reflectionMode(ReflectionMode mode) noexcept; + //! Specifies how transparent objects should be rendered (default is DEFAULT). MaterialBuilder& transparencyMode(TransparencyMode mode) noexcept; @@ -702,6 +706,7 @@ private: MaterialDomain mMaterialDomain = MaterialDomain::SURFACE; RefractionMode mRefractionMode = RefractionMode::NONE; RefractionType mRefractionType = RefractionType::SOLID; + ReflectionMode mReflectionMode = ReflectionMode::DEFAULT; Interpolation mInterpolation = Interpolation::SMOOTH; VertexDomain mVertexDomain = VertexDomain::OBJECT; TransparencyMode mTransparencyMode = TransparencyMode::DEFAULT; diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index 93f46a5b42..5df7f6894b 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -374,6 +374,11 @@ MaterialBuilder& MaterialBuilder::transparencyMode(TransparencyMode mode) noexce return *this; } +MaterialBuilder& MaterialBuilder::reflectionMode(ReflectionMode mode) noexcept { + mReflectionMode = mode; + return *this; +} + MaterialBuilder& MaterialBuilder::platform(Platform platform) noexcept { mPlatform = platform; return *this; @@ -480,6 +485,7 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept { info.specularAOSet = mSpecularAOSet; info.refractionMode = mRefractionMode; info.refractionType = mRefractionType; + info.reflectionMode = mReflectionMode; info.quality = mShaderQuality; info.hasCustomSurfaceShading = mCustomSurfaceShading; } @@ -1026,6 +1032,7 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& container.addSimpleChild(ChunkType::MaterialBlendingMode, static_cast(mBlendingMode)); container.addSimpleChild(ChunkType::MaterialTransparencyMode, static_cast(mTransparencyMode)); + container.addSimpleChild(ChunkType::MaterialReflectionMode, static_cast(mReflectionMode)); container.addSimpleChild(ChunkType::MaterialDepthWriteSet, mDepthWriteSet); container.addSimpleChild(ChunkType::MaterialColorWrite, mColorWrite); container.addSimpleChild(ChunkType::MaterialDepthWrite, mDepthWrite); diff --git a/libs/filamat/src/UibGenerator.cpp b/libs/filamat/src/UibGenerator.cpp index a64144603c..2af505d3be 100644 --- a/libs/filamat/src/UibGenerator.cpp +++ b/libs/filamat/src/UibGenerator.cpp @@ -117,8 +117,16 @@ UniformInterfaceBlock const& UibGenerator::getPerViewUib() noexcept { .add("nearOverFarMinusNear", 1, UniformInterfaceBlock::Type::FLOAT, Precision::HIGH) .add("temporalNoise", 1, UniformInterfaceBlock::Type::FLOAT, Precision::HIGH) + // Screen-space reflection parameters + .add("ssrReprojection", 1, UniformInterfaceBlock::Type::MAT4, Precision::HIGH) + .add("ssrProjectToPixelMatrix", 1, UniformInterfaceBlock::Type::MAT4, Precision::HIGH) + .add("ssrThickness", 1, UniformInterfaceBlock::Type::FLOAT) + .add("ssrBias", 1, UniformInterfaceBlock::Type::FLOAT) + .add("ssrDistance", 1, UniformInterfaceBlock::Type::FLOAT) + .add("ssrStride", 1, UniformInterfaceBlock::Type::FLOAT) + // bring PerViewUib to 2 KiB - .add("padding2", 58, UniformInterfaceBlock::Type::FLOAT4) + .add("padding3", 49, UniformInterfaceBlock::Type::FLOAT4) .build(); return uib; } diff --git a/libs/filamat/src/shaders/CodeGenerator.cpp b/libs/filamat/src/shaders/CodeGenerator.cpp index 28bf33c1de..f4334b4639 100644 --- a/libs/filamat/src/shaders/CodeGenerator.cpp +++ b/libs/filamat/src/shaders/CodeGenerator.cpp @@ -611,6 +611,7 @@ io::sstream& CodeGenerator::generateShaderLit(io::sstream& out, ShaderType type, if (shading != Shading::UNLIT) { out << SHADERS_AMBIENT_OCCLUSION_FS_DATA; + out << SHADERS_LIGHT_REFLECTIONS_FS_DATA; out << SHADERS_LIGHT_INDIRECT_FS_DATA; } if (variant.hasDirectionalLighting()) { diff --git a/libs/filamat/src/shaders/MaterialInfo.h b/libs/filamat/src/shaders/MaterialInfo.h index 0ff7eae638..bb66c99eda 100644 --- a/libs/filamat/src/shaders/MaterialInfo.h +++ b/libs/filamat/src/shaders/MaterialInfo.h @@ -48,6 +48,7 @@ struct UTILS_PUBLIC MaterialInfo { filament::SpecularAmbientOcclusion specularAO; filament::RefractionMode refractionMode; filament::RefractionType refractionType; + filament::ReflectionMode reflectionMode; filament::AttributeBitset requiredAttributes; filament::BlendingMode blendingMode; filament::BlendingMode postLightingBlendingMode; diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index 37cc762a10..8f7f72bb5e 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -308,6 +308,11 @@ std::string ShaderGenerator::createFragmentProgram(ShaderModel shaderModel, auto specularAO = material.specularAOSet ? material.specularAO : defaultSpecularAO; CodeGenerator::generateDefine(fs, "SPECULAR_AMBIENT_OCCLUSION", uint32_t(specularAO)); + // Currently, we only support either screen-space refractions or reflections. + // The HAS_REFRACTION/HAS_REFLECTIONS defines signify if refraction/reflections are supported by + // the material, yet only one technique will be selected at runtime. + // TODO: support simultaneous screen-space refractions and reflections. + CodeGenerator::generateDefine(fs, "HAS_REFRACTION", material.refractionMode != RefractionMode::NONE); if (material.refractionMode != RefractionMode::NONE) { CodeGenerator::generateDefine(fs, "REFRACTION_MODE_CUBEMAP", uint32_t(RefractionMode::CUBEMAP)); @@ -335,6 +340,14 @@ std::string ShaderGenerator::createFragmentProgram(ShaderModel shaderModel, } } + CodeGenerator::generateDefine(fs, "HAS_REFLECTIONS", material.reflectionMode != ReflectionMode::DEFAULT); + if (material.reflectionMode != ReflectionMode::DEFAULT) { + CodeGenerator::generateDefine(fs, "REFLECTION_MODE_SCREEN_SPACE", uint32_t(ReflectionMode::SCREEN_SPACE)); + if (material.reflectionMode == ReflectionMode::SCREEN_SPACE) { + CodeGenerator::generateDefine(fs, "REFLECTION_MODE", "REFLECTION_MODE_SCREEN_SPACE"); + } + } + bool multiBounceAO = material.multiBounceAOSet ? material.multiBounceAO : !isMobileTarget(shaderModel); CodeGenerator::generateDefine(fs, "MULTI_BOUNCE_AMBIENT_OCCLUSION", multiBounceAO ? 1u : 0u); diff --git a/libs/gltfio/materials/gltflite.mat.in b/libs/gltfio/materials/gltflite.mat.in index ea7a9cec7a..ef91f582b8 100644 --- a/libs/gltfio/materials/gltflite.mat.in +++ b/libs/gltfio/materials/gltflite.mat.in @@ -8,6 +8,7 @@ material { flipUV : false, specularAmbientOcclusion : simple, specularAntiAliasing : true, + reflections : screenspace, parameters : [ // Base Color diff --git a/libs/gltfio/materials/sheen.mat.in b/libs/gltfio/materials/sheen.mat.in index e621e4b966..2fc3562e57 100644 --- a/libs/gltfio/materials/sheen.mat.in +++ b/libs/gltfio/materials/sheen.mat.in @@ -9,6 +9,7 @@ material { flipUV : false, specularAmbientOcclusion : simple, specularAntiAliasing : true, + reflections : screenspace, parameters : [ // Base Color diff --git a/libs/gltfio/materials/transmission.mat.in b/libs/gltfio/materials/transmission.mat.in index 210bfe896d..0b7fd40d26 100644 --- a/libs/gltfio/materials/transmission.mat.in +++ b/libs/gltfio/materials/transmission.mat.in @@ -10,6 +10,7 @@ material { specularAntiAliasing : true, refractionMode: screenspace, refractionType: thin, + reflections: screenspace, parameters : [ // Base Color diff --git a/libs/gltfio/materials/ubershader.mat.in b/libs/gltfio/materials/ubershader.mat.in index 62fedd0ab6..e525e0b8b2 100644 --- a/libs/gltfio/materials/ubershader.mat.in +++ b/libs/gltfio/materials/ubershader.mat.in @@ -9,6 +9,7 @@ material { specularAmbientOcclusion : simple, specularAntiAliasing : true, clearCoatIorChange : false, + reflections : screenspace, parameters : [ { type : float3, name : specularFactor }, diff --git a/libs/gltfio/materials/volume.mat.in b/libs/gltfio/materials/volume.mat.in index 37baf7b233..570d41e68c 100644 --- a/libs/gltfio/materials/volume.mat.in +++ b/libs/gltfio/materials/volume.mat.in @@ -10,6 +10,7 @@ material { specularAntiAliasing : true, refractionMode: screenspace, refractionType: solid, + reflections: screenspace, parameters : [ // Base Color diff --git a/libs/gltfio/src/MaterialGenerator.cpp b/libs/gltfio/src/MaterialGenerator.cpp index 2f6aea933b..c832fe3893 100644 --- a/libs/gltfio/src/MaterialGenerator.cpp +++ b/libs/gltfio/src/MaterialGenerator.cpp @@ -324,6 +324,7 @@ static Material* createMaterial(Engine* engine, const MaterialKey& config, const .transparencyMode(config.doubleSided ? MaterialBuilder::TransparencyMode::TWO_PASSES_TWO_SIDES : MaterialBuilder::TransparencyMode::DEFAULT) + .reflectionMode(MaterialBuilder::ReflectionMode::SCREEN_SPACE) .targetApi(filamat::targetApiFromBackend(engine->getBackend())); if (!optimizeShaders) { diff --git a/libs/viewer/include/viewer/Settings.h b/libs/viewer/include/viewer/Settings.h index 272f213400..248553f15a 100644 --- a/libs/viewer/include/viewer/Settings.h +++ b/libs/viewer/include/viewer/Settings.h @@ -59,6 +59,7 @@ enum class ToneMapping : uint8_t { }; using AmbientOcclusionOptions = filament::View::AmbientOcclusionOptions; +using ScreenSpaceReflectionsOptions = filament::View::ScreenSpaceReflectionsOptions; using AntiAliasing = filament::View::AntiAliasing; using BloomOptions = filament::View::BloomOptions; using DepthOfFieldOptions = filament::View::DepthOfFieldOptions; @@ -160,6 +161,7 @@ struct ViewSettings { // View Options (sorted) AmbientOcclusionOptions ssao; + ScreenSpaceReflectionsOptions screenSpaceReflections; BloomOptions bloom; DepthOfFieldOptions dof; DynamicResolutionOptions dsr; diff --git a/libs/viewer/src/Settings.cpp b/libs/viewer/src/Settings.cpp index 758aceae41..cd9471f149 100644 --- a/libs/viewer/src/Settings.cpp +++ b/libs/viewer/src/Settings.cpp @@ -465,6 +465,33 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, return i; } +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, + ScreenSpaceReflectionsOptions* out) { + CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i++].size; + for (int j = 0; j < size; ++j) { + const jsmntok_t tok = tokens[i]; + CHECK_KEY(tok); + if (compare(tok, jsonChunk, "enabled") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->enabled); + } else if (compare(tok, jsonChunk, "thickness") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->thickness); + } else if (compare(tok, jsonChunk, "bias") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->bias); + } else if (compare(tok, jsonChunk, "maxDistance") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->maxDistance); + } else { + slog.w << "Invalid screen-space reflections key: '" << STR(tok, jsonChunk) << "'" << io::endl; + i = parse(tokens, i + 1); + } + if (i < 0) { + slog.e << "Invalid screen-space reflections value: '" << STR(tok, jsonChunk) << "'" << io::endl; + return i; + } + } + return i; +} + static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, AmbientOcclusionOptions* out) { CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); @@ -736,6 +763,8 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ViewSett i = parse(tokens, i + 1, jsonChunk, &out->colorGrading); } else if (compare(tok, jsonChunk, "ssao") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->ssao); + } else if (compare(tok, jsonChunk, "screenSpaceReflections") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->screenSpaceReflections); } else if (compare(tok, jsonChunk, "bloom") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->bloom); } else if (compare(tok, jsonChunk, "fog") == 0) { @@ -1009,6 +1038,7 @@ void applySettings(const ViewSettings& settings, View* dest) { dest->setMultiSampleAntiAliasingOptions(settings.msaa); dest->setDynamicResolutionOptions(settings.dsr); dest->setAmbientOcclusionOptions(settings.ssao); + dest->setScreenSpaceReflectionsOptions(settings.screenSpaceReflections); dest->setBloomOptions(settings.bloom); dest->setFogOptions(settings.fog); dest->setDepthOfFieldOptions(settings.dof); @@ -1315,6 +1345,15 @@ static std::ostream& operator<<(std::ostream& out, const AmbientOcclusionOptions << "}"; } +static std::ostream& operator<<(std::ostream& out, const ScreenSpaceReflectionsOptions& in) { + return out << "{\n" + << "\"enabled\": " << to_string(in.enabled) << ",\n" + << "\"thickness\": " << in.thickness << ",\n" + << "\"bias\": " << in.bias << ",\n" + << "\"maxDistance\": " << in.maxDistance << ",\n" + << "}"; +} + static std::ostream& operator<<(std::ostream& out, const AmbientOcclusionOptions& in) { return out << "{\n" << "\"radius\": " << (in.radius) << ",\n" @@ -1517,6 +1556,7 @@ static std::ostream& operator<<(std::ostream& out, const ViewSettings& in) { << "\"dsr\": " << in.dsr << ",\n" << "\"colorGrading\": " << (in.colorGrading) << ",\n" << "\"ssao\": " << (in.ssao) << ",\n" + << "\"screenSpaceReflections\": " << (in.screenSpaceReflections) << ",\n" << "\"bloom\": " << (in.bloom) << ",\n" << "\"fog\": " << (in.fog) << ",\n" << "\"dof\": " << (in.dof) << ",\n" diff --git a/libs/viewer/src/SimpleViewer.cpp b/libs/viewer/src/SimpleViewer.cpp index 65613e17b6..111840c412 100644 --- a/libs/viewer/src/SimpleViewer.cpp +++ b/libs/viewer/src/SimpleViewer.cpp @@ -737,6 +737,15 @@ void SimpleViewer::updateUserInterface() { ssao.ssct.sampleCount = sampleCount; } } + + ImGui::Checkbox("Screen-space reflections", &mSettings.view.screenSpaceReflections.enabled); + if (ImGui::CollapsingHeader("Screen-space reflections Options")) { + auto& ssrefl = mSettings.view.screenSpaceReflections; + ImGui::SliderFloat("Ray thickness", &ssrefl.thickness, 0.001f, 0.2f); + ImGui::SliderFloat("Bias", &ssrefl.bias, 0.001f, 0.5f); + ImGui::SliderFloat("Max distance", &ssrefl.maxDistance, 0.1, 10.0f); + ImGui::SliderFloat("Stride", &ssrefl.stride, 1.0, 10.0f); + } ImGui::Unindent(); } diff --git a/samples/rendertarget.cpp b/samples/rendertarget.cpp index 36af1ff4d2..60dc9f1416 100644 --- a/samples/rendertarget.cpp +++ b/samples/rendertarget.cpp @@ -51,11 +51,6 @@ struct Vertex { float2 uv; }; -enum class ReflectionMode { - RENDERABLES, - CAMERA, -}; - struct App { utils::Entity lightEntity; Material* meshMaterial; @@ -71,6 +66,11 @@ struct App { Scene* offscreenScene = nullptr; Camera* offscreenCamera = nullptr; + enum class ReflectionMode { + RENDERABLES, + CAMERA, + }; + ReflectionMode mode = ReflectionMode::CAMERA; Config config; @@ -105,14 +105,14 @@ static mat4f reflectionMatrix(float4 plane) { return transpose(m); } -static void setReflectionMode(App& app, ReflectionMode mode) { +static void setReflectionMode(App& app, App::ReflectionMode mode) { switch (mode) { - case ReflectionMode::RENDERABLES: + case App::ReflectionMode::RENDERABLES: app.offscreenScene->addEntity(app.reflectedMonkey); app.offscreenScene->remove(app.monkeyMesh.renderable); app.offscreenView->setFrontFaceWindingInverted(false); break; - case ReflectionMode::CAMERA: + case App::ReflectionMode::CAMERA: app.offscreenScene->addEntity(app.monkeyMesh.renderable); app.offscreenScene->remove(app.reflectedMonkey); app.offscreenView->setFrontFaceWindingInverted(true); @@ -173,9 +173,9 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) { break; case 'm': if (arg == "camera") { - app->mode = ReflectionMode::CAMERA; + app->mode = App::ReflectionMode::CAMERA; } else if (arg == "renderables") { - app->mode = ReflectionMode::RENDERABLES; + app->mode = App::ReflectionMode::RENDERABLES; } else { std::cerr << "Unrecognized mode. Must be 'camera'|'renderables'.\n"; exit(1); @@ -286,7 +286,7 @@ int main(int argc, char** argv) { rcm.setCastShadows(rcm.getInstance(app.monkeyMesh.renderable), false); scene->addEntity(app.monkeyMesh.renderable); - // Create a reflected monkey, which is used only for ReflectionMode::RENDERABLES. + // Create a reflected monkey, which is used only for App::ReflectionMode::RENDERABLES. app.reflectedMonkey = em.create(); RenderableManager::Builder(1) .boundingBox({{ -2, -2, -2 }, { 2, 2, 2 }}) @@ -362,11 +362,11 @@ int main(int argc, char** argv) { const auto cullingProjection = camera.getCullingProjectionMatrix(); app.offscreenCamera->setCustomProjection(renderingProjection, cullingProjection, camera.getNear(), camera.getCullingFar()); switch (app.mode) { - case ReflectionMode::RENDERABLES: + case App::ReflectionMode::RENDERABLES: tcm.setTransform(tcm.getInstance(app.reflectedMonkey), reflection * xform); app.offscreenCamera->setModelMatrix(model); break; - case ReflectionMode::CAMERA: + case App::ReflectionMode::CAMERA: app.offscreenCamera->setModelMatrix(reflection * model); break; } diff --git a/shaders/CMakeLists.txt b/shaders/CMakeLists.txt index 6695237285..32c4a4459d 100644 --- a/shaders/CMakeLists.txt +++ b/shaders/CMakeLists.txt @@ -29,6 +29,7 @@ set(SHADERS src/inputs.vs src/light_directional.fs src/light_indirect.fs + src/light_reflections.fs src/light_punctual.fs src/main.fs src/main.vs diff --git a/shaders/src/light_indirect.fs b/shaders/src/light_indirect.fs index fdfd848c71..68efd64875 100644 --- a/shaders/src/light_indirect.fs +++ b/shaders/src/light_indirect.fs @@ -539,20 +539,30 @@ void applyRefraction( // which is not the case when we'll read from the screen-space buffer vec3 Ft = prefilteredRadiance(ray.direction, perceptualRoughness) * frameUniforms.iblLuminance; #else - // compute the point where the ray exits the medium, if needed - vec4 p = vec4(frameUniforms.clipFromWorldMatrix * vec4(ray.position, 1.0)); - p.xy = uvToRenderTargetUV(p.xy * (0.5 / p.w) + 0.5); + vec3 Ft; +#if defined(HAS_REFLECTIONS) && REFLECTION_MODE == REFLECTION_MODE_SCREEN_SPACE + if (frameUniforms.ssrDistance > 0.0f) { + // if screen-space reflections is enabled, we can't read from the screen-space buffer, so + // fall back to cubemap refractions + Ft = prefilteredRadiance(ray.direction, perceptualRoughness) * frameUniforms.iblLuminance; + } else +#endif + { + // compute the point where the ray exits the medium, if needed + vec4 p = vec4(frameUniforms.clipFromWorldMatrix * vec4(ray.position, 1.0)); + p.xy = uvToRenderTargetUV(p.xy * (0.5 / p.w) + 0.5); - // perceptualRoughness to LOD - // Empirical factor to compensate for the gaussian approximation of Dggx, chosen so - // cubemap and screen-space modes match at perceptualRoughness 0.125 - // TODO: Remove this factor temporarily until we find a better solution - // This overblurs many scenes and needs a more principled approach - // float tweakedPerceptualRoughness = perceptualRoughness * 1.74; - float tweakedPerceptualRoughness = perceptualRoughness; - float lod = max(0.0, 2.0 * log2(tweakedPerceptualRoughness) + frameUniforms.refractionLodOffset); + // perceptualRoughness to LOD + // Empirical factor to compensate for the gaussian approximation of Dggx, chosen so + // cubemap and screen-space modes match at perceptualRoughness 0.125 + // TODO: Remove this factor temporarily until we find a better solution + // This overblurs many scenes and needs a more principled approach + // float tweakedPerceptualRoughness = perceptualRoughness * 1.74; + float tweakedPerceptualRoughness = perceptualRoughness; + float lod = max(0.0, 2.0 * log2(tweakedPerceptualRoughness) + frameUniforms.refractionLodOffset); - vec3 Ft = textureLod(light_ssr, p.xy, lod).rgb; + Ft = textureLod(light_ssr, p.xy, lod).rgb; + } #endif // base color changes the amount of light passing through the boundary @@ -586,13 +596,39 @@ void combineDiffuseAndSpecular( void evaluateIBL(const MaterialInputs material, const PixelParams pixel, inout vec3 color) { // specular layer vec3 Fr; + + // screen-space reflections +#if defined(HAS_REFLECTIONS) && REFLECTION_MODE == REFLECTION_MODE_SCREEN_SPACE + vec4 ssr = vec4(0.0f); + // evaluateScreenSpaceReflections will set the value of ssr if there's a hit. + // ssr.a contains the reflection's contribution. + // TODO: do we want iblLuminance to control screen-space reflections? + if (pixel.roughness <= 0.01f && frameUniforms.ssrDistance > 0.0f) { + vec3 r = getReflectedVector(pixel, shading_view, shading_normal); + evaluateScreenSpaceReflections(r, ssr); + } +#else + const vec4 ssr = vec4(0.0f); +#endif + + // If screen-space reflections are turned on and have full contribution (ssr.a == 1.0f), then we + // skip sampling the IBL down below. + #if IBL_INTEGRATION == IBL_INTEGRATION_PREFILTERED_CUBEMAP vec3 E = specularDFG(pixel); vec3 r = getReflectedVector(pixel, shading_normal); - Fr = E * prefilteredRadiance(r, pixel.perceptualRoughness); + vec3 ibl = vec3(0.0f); + if (ssr.a < 1.0f) { + ibl = E * prefilteredRadiance(r, pixel.perceptualRoughness); + } + Fr = (1.0f - ssr.a) * ibl + ssr.rgb; #elif IBL_INTEGRATION == IBL_INTEGRATION_IMPORTANCE_SAMPLING vec3 E = vec3(0.0); // TODO: fix for importance sampling - Fr = isEvaluateSpecularIBL(pixel, shading_normal, shading_view, shading_NoV); + vec3 ibl = vec3(0.0f); + if (ssr.a < 1.0f) { + ibl = isEvaluateSpecularIBL(pixel, shading_normal, shading_view, shading_NoV); + } + Fr = (1.0f - ssr.a) * ibl + ssr.rgb; #endif SSAOInterpolationCache interpolationCache; diff --git a/shaders/src/light_reflections.fs b/shaders/src/light_reflections.fs new file mode 100644 index 0000000000..a69474872f --- /dev/null +++ b/shaders/src/light_reflections.fs @@ -0,0 +1,255 @@ +//------------------------------------------------------------------------------ +// Screen-space reflections +//------------------------------------------------------------------------------ + +#if defined(HAS_REFLECTIONS) && REFLECTION_MODE == REFLECTION_MODE_SCREEN_SPACE + +// Copied from depthUtils.fs +highp float linearizeDepth(highp float depth) { + // Our far plane is at infinity, which causes a division by zero below, which in turn + // causes some issues on some GPU. We workaround it by replacing "infinity" by the closest + // value representable in a 24 bit depth buffer. + const highp float preventDiv0 = 1.0 / 16777216.0; + highp mat4 p = getViewFromClipMatrix(); + // this works with perspective and ortho projections, for a perspective projection + // this resolves to -near/depth, for an ortho projection this resolves to depth*(far - near) - far + return (depth * p[2].z + p[3].z) / max(depth * p[2].w + p[3].w, preventDiv0); +} + +// Code adapted from McGuire and Mara, Efficient GPU Screen-Space Ray Tracing, Journal of Computer +// Graphics Techniques, 2014 +// +// Copyright (c) 2014, Morgan McGuire and Michael Mara +// All rights reserved. +// +// This software is open source under the "BSD 2-clause license": +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +void swap(inout highp float a, inout highp float b) { + highp float temp = a; + a = b; + b = temp; +} + +highp float distanceSquared(highp vec2 a, highp vec2 b) { + a -= b; + return dot(a, a); +} + +// Note: McGuire and Mara use the "cs" prefix to stand for "camera space", equivalent to Filament's +// "view space". "cs" has been replaced with "vs" to avoid confusion. +bool traceScreenSpaceRay(const highp vec3 vsOrigin, const highp vec3 vsDirection, + const highp mat4 projectToPixelMatrix, const highp sampler2D vsZBuffer, + const float vsZThickness, const highp float nearPlaneZ, const float stride, + const float jitterFraction, const highp float maxSteps, const float maxRayTraceDistance, + out highp vec2 hitPixel, out highp vec3 vsHitPoint) { + // Clip ray to a near plane in 3D (doesn't have to be *the* near plane, although that would be a + // good idea) + highp float rayLength = ((vsOrigin.z + vsDirection.z * maxRayTraceDistance) > nearPlaneZ) ? + (nearPlaneZ - vsOrigin.z) / vsDirection.z : maxRayTraceDistance; + highp vec3 vsEndPoint = vsDirection * rayLength + vsOrigin; + + // Project into screen space + highp vec4 H0 = mulMat4x4Float3(projectToPixelMatrix, vsOrigin); + highp vec4 H1 = mulMat4x4Float3(projectToPixelMatrix, vsEndPoint); + + // There are a lot of divisions by w that can be turned into multiplications at some minor + // precision loss...and we need to interpolate these 1/w values anyway. + // + // Because the caller was required to clip to the near plane, this homogeneous division + // (projecting from 4D to 2D) is guaranteed to succeed. + highp float k0 = 1.0 / H0.w; + highp float k1 = 1.0 / H1.w; + + // Switch the original points to values that interpolate linearly in 2D + highp vec3 Q0 = vsOrigin * k0; + highp vec3 Q1 = vsEndPoint * k1; + + // Screen-space endpoints + highp vec2 P0 = H0.xy * k0; + highp vec2 P1 = H1.xy * k1; + + // TODO: + // [Optional clipping to frustum sides here] + + // Initialize to off screen + hitPixel = vec2(-1.0, -1.0); + + // If the line is degenerate, make it cover at least one pixel to avoid handling zero-pixel + // extent as a special case later + P1 += vec2((distanceSquared(P0, P1) < 0.0001) ? 0.01 : 0.0); + + highp vec2 delta = P1 - P0; + + // Permute so that the primary iteration is in x to reduce large branches later + bool permute = false; + if (abs(delta.x) < abs(delta.y)) { + // More-vertical line. Create a permutation that swaps x and y in the output + permute = true; + // Directly swizzle the inputs + delta = delta.yx; + P1 = P1.yx; + P0 = P0.yx; + } + + // From now on, "x" is the primary iteration direction and "y" is the secondary one + + float stepDirection = sign(delta.x); + highp float invdx = stepDirection / delta.x; + highp vec2 dP = vec2(stepDirection, invdx * delta.y); + + // Track the derivatives of Q and k + highp vec3 dQ = (Q1 - Q0) * invdx; + highp float dk = (k1 - k0) * invdx; + + // Scale derivatives by the desired pixel stride + dP *= stride; dQ *= stride; dk *= stride; + + // Offset the starting values by the jitter fraction + P0 += dP * jitterFraction; Q0 += dQ * jitterFraction; k0 += dk * jitterFraction; + + // Slide P from P0 to P1, (now-homogeneous) Q from Q0 to Q1, and k from k0 to k1 + highp vec3 Q = Q0; + highp float k = k0; + + // We track the ray depth at +/- 1/2 pixel to treat pixels as clip-space solid voxels. Because + // the depth at -1/2 for a given pixel will be the same as at +1/2 for the previous iteration, + // we actually only have to compute one value per iteration. + highp float prevZMaxEstimate = vsOrigin.z; + highp float stepCount = 0.0; + highp float rayZMax = prevZMaxEstimate; + highp float rayZMin = prevZMaxEstimate; + highp float sceneZMax = rayZMax + 1e4; + + // P1.x is never modified after this point, so pre-scale it by the step direction for a signed + // comparison + highp float end = P1.x * stepDirection; + + // We only advance the z field of Q in the inner loop, since Q.xy is never used until after the + // loop terminates. + + for (highp vec2 P = P0; + ((P.x * stepDirection) <= end) && + (stepCount < maxSteps) && + ((rayZMax < sceneZMax - vsZThickness) || + (rayZMin > sceneZMax)) && + (sceneZMax != 0.0); + P += dP, Q.z += dQ.z, k += dk, stepCount += 1.0) { + + hitPixel = permute ? P.yx : P; + + // The depth range that the ray covers within this loop iteration. Assume that the ray is + // moving in increasing z and swap if backwards. Because one end of the interval is shared + // between adjacent iterations, we track the previous value and then swap as needed to + // ensure correct ordering + rayZMin = prevZMaxEstimate; + + // Compute the value at 1/2 pixel into the future + rayZMax = (dQ.z * 0.5 + Q.z) / (dk * 0.5 + k); + prevZMaxEstimate = rayZMax; + if (rayZMin > rayZMax) { swap(rayZMin, rayZMax); } + + // View-space z of the background + // TODO: explore if we can avoid the linearization of the depth sample here. + sceneZMax = linearizeDepth(texelFetch(vsZBuffer, int2(hitPixel), 0).r); + } // pixel on ray + + Q.xy += dQ.xy * stepCount; + vsHitPoint = Q * (1.0 / k); + // Matches the new loop condition: + return (rayZMax >= sceneZMax - vsZThickness) && (rayZMin <= sceneZMax); +} + +// -- end "BSD 2-clause license" ------------------------------------------------------------------- + +highp mat4 scaleMatrix(const highp float x, const highp float y) { + mat4 m = mat4(1.0f); + m[0].x = x; + m[1].y = y; + m[2].z = 1.0f; + m[3].w = 1.0f; + return m; +} + +/** + * Evaluates screen-space reflections, storing a premultiplied color in Fr.rgb if there's a hit. + * r is the desired reflected vector. + * + * Fr.a is set to a value between [0, 1] representing the "opacity" of the reflection. 1.0f is full + * screen-space reflection. Values < 1.0f should be blended with the scene's IBL. Fr.rgb is + * premultiplied by Fr.a. + * + * If there is no hit, Fr is unmodified. + */ +void evaluateScreenSpaceReflections(vec3 r, inout vec4 Fr) { + vec3 wsRayDirection = r; + highp vec3 wsRayStart = shading_position + frameUniforms.ssrBias * wsRayDirection; + + // ray start/end in view space + highp vec4 vsRayStart = mulMat4x4Float3(getViewFromWorldMatrix(), wsRayStart); + highp vec4 vsRayDirection = getViewFromWorldMatrix() * vec4(wsRayDirection, 0.0); + + highp vec3 vsOrigin = vsRayStart.xyz; + highp vec3 vsDirection = vsRayDirection.xyz; + float vsZThickness = frameUniforms.ssrThickness; + highp float nearPlaneZ = -frameUniforms.nearOverFarMinusNear / frameUniforms.oneOverFarMinusNear; + float stride = frameUniforms.ssrStride; + // TODO: jitterFraction should be between 0 and 1, but anything < 1 gives banding artifacts. + const float jitterFraction = 1.0f; + float maxRayTraceDistance = frameUniforms.ssrDistance; + + highp vec2 res = vec2(textureSize(light_structure, 0).xy); + highp mat4 projectToPixelMatrix = + scaleMatrix(res.x, res.y) * + frameUniforms.ssrProjectToPixelMatrix; + + highp float maxSteps = float(max(res.x, res.y)); + + // Outputs from the traceScreenSpaceRay function. + highp vec2 hitPixel; // not currently used + highp vec3 vsHitPoint; + + if (traceScreenSpaceRay(vsOrigin, vsDirection, projectToPixelMatrix, light_structure, + vsZThickness, nearPlaneZ, stride, jitterFraction, maxSteps, + maxRayTraceDistance, hitPixel, vsHitPoint)) { + highp vec4 reprojected = frameUniforms.ssrReprojection * vec4(vsHitPoint, 1.0f); + reprojected *= (1.0 / reprojected.w); + + // Compute the screen-space reflection's contribution. + // TODO: parameterize fadeRate. + const float fadeRate = 12.0f; + + // Fade the reflections out near the edges. + vec2 edgeFactor = max(fadeRate * abs(reprojected.xy - 0.5f) - (fadeRate / 2.0f - 1.0f), 0.0f); + float edgeFade = saturate(1.0 - dot(edgeFactor, edgeFactor)); + + // Fade the reflections out near maxRayTraceDistance. + float t = distance(vsOrigin, vsHitPoint) / maxRayTraceDistance; + float distFactor = max(fadeRate * (t - 0.5f) - (fadeRate / 2.0f - 1.0f), 0.0f); + float distanceFade = saturate(1.0 - (distFactor, distFactor)); + + float fade = edgeFade * distanceFade; + Fr = vec4(textureLod(light_ssr, reprojected.xy, 0.0f).rgb * fade, fade); + } +} + +#endif // screen-space reflections diff --git a/tools/matc/src/matc/ParametersProcessor.cpp b/tools/matc/src/matc/ParametersProcessor.cpp index 29e68a6b3f..c75a28faa4 100644 --- a/tools/matc/src/matc/ParametersProcessor.cpp +++ b/tools/matc/src/matc/ParametersProcessor.cpp @@ -659,6 +659,20 @@ static bool processRefractionMode(MaterialBuilder& builder, const JsonishValue& return true; } +static bool processReflectionMode(MaterialBuilder& builder, const JsonishValue& value) { + static const std::unordered_map strToEnum { + { "default", MaterialBuilder::ReflectionMode ::DEFAULT }, + { "screenspace", MaterialBuilder::ReflectionMode::SCREEN_SPACE }, + }; + auto jsonString = value.toJsonString(); + if (!isStringValidEnum(strToEnum, jsonString->getString())) { + return logEnumIssue("reflection_mode", *jsonString, strToEnum); + } + + builder.reflectionMode(stringToEnum(strToEnum, jsonString->getString())); + return true; +} + static bool processRefractionType(MaterialBuilder& builder, const JsonishValue& value) { static const std::unordered_map strToEnum { { "solid", MaterialBuilder::RefractionType::SOLID }, @@ -728,6 +742,7 @@ ParametersProcessor::ParametersProcessor() { mParameters["depthCulling"] = { &processDepthCull, Type::BOOL }; mParameters["doubleSided"] = { &processDoubleSided, Type::BOOL }; mParameters["transparency"] = { &processTransparencyMode, Type::STRING }; + mParameters["reflections"] = { &processReflectionMode, Type::STRING }; mParameters["maskThreshold"] = { &processMaskThreshold, Type::NUMBER }; mParameters["shadowMultiplier"] = { &processShadowMultiplier, Type::BOOL }; mParameters["transparentShadow"] = { &processTransparentShadow, Type::BOOL }; From 352acff3968194f3947a3c95f4cf9c260960c6e1 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Wed, 12 Jan 2022 15:44:24 -0800 Subject: [PATCH 10/22] improve documentation regarding post-processing and render targets Fixes #5015 --- .../java/com/google/android/filament/View.java | 15 ++++++++++----- filament/include/filament/RenderTarget.h | 14 ++++++++++++++ filament/include/filament/View.h | 11 +++++++---- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/android/filament-android/src/main/java/com/google/android/filament/View.java b/android/filament-android/src/main/java/com/google/android/filament/View.java index 9565a3f2bc..6c47e67062 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/View.java +++ b/android/filament-android/src/main/java/com/google/android/filament/View.java @@ -1337,21 +1337,26 @@ public class View { * *

Post-processing includes:

*
    - *
  • Tone-mapping & gamma encoding
  • + *
  • Depth-of-field
  • + *
  • Bloom
  • + *
  • Vignetting
  • + *
  • Temporal Anti-aliasing (TAA)
  • + *
  • Color grading & gamma encoding
  • *
  • Dithering
  • - *
  • MSAA
  • *
  • FXAA
  • *
  • Dynamic scaling
  • *
* *

- * Disabling post-processing forgoes color correctness as well as anti-aliasing and - * should only be used experimentally (e.g., for UI overlays). + * Disabling post-processing forgoes color correctness as well as some anti-aliasing techniques + * and should only be used for debugging, UI overlays or when using custom render targets + * (see RenderTarget). *

* * @param enabled true enables post processing, false disables it * - * @see #setToneMapping + * @see #setBloomOptions + * @see #setColorGrading * @see #setAntiAliasing * @see #setDithering * @see #setSampleCount diff --git a/filament/include/filament/RenderTarget.h b/filament/include/filament/RenderTarget.h index cc4ae38a1c..950bbb8dba 100644 --- a/filament/include/filament/RenderTarget.h +++ b/filament/include/filament/RenderTarget.h @@ -37,6 +37,12 @@ class Texture; * An offscreen render target that can be associated with a View and contains * weak references to a set of attached Texture objects. * + * RenderTarget is intended to be used with the View's post-processing disabled for the most part. + * especially when a DEPTH attachment is also used (see Builder::texture()). + * + * Custom RenderTarget are ultimately intended to render into textures that might be used during + * the main render pass. + * * Clients are responsible for the lifetime of all associated Texture attachments. * * @see View @@ -87,6 +93,14 @@ public: * * All RenderTargets must have a non-null COLOR attachment. * + * When using a DEPTH attachment, it is important to always disable post-processing + * in the View. Failing to do so will cause the DEPTH attachment to be ignored in most + * cases. + * + * When the intention is to keep the content of the DEPTH attachment after rendering, + * Usage::SAMPLEABLE must be set on the DEPTH attachment, otherwise the content of the + * DEPTH buffer may be discarded. + * * @param attachment The attachment point of the texture. * @param texture The associated texture object. * @return A reference to this Builder for chaining calls. diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h index f8fdf23af5..1083560c1e 100644 --- a/filament/include/filament/View.h +++ b/filament/include/filament/View.h @@ -579,15 +579,18 @@ public: * Enables or disables post processing. Enabled by default. * * Post-processing includes: + * - Depth-of-field * - Bloom - * - Tone-mapping & gamma encoding + * - Vignetting + * - Temporal Anti-aliasing (TAA) + * - Color grading & gamma encoding * - Dithering - * - MSAA * - FXAA * - Dynamic scaling * - * Disabling post-processing forgoes color correctness as well as anti-aliasing and - * should only be used experimentally (e.g., for UI overlays). + * Disabling post-processing forgoes color correctness as well as some anti-aliasing techniques + * and should only be used for debugging, UI overlays or when using custom render targets + * (see RenderTarget). * * @param enabled true enables post processing, false disables it. * From dead7ab2ac18db42be8c09fa90ef0920a72992c0 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Thu, 13 Jan 2022 13:10:56 -0800 Subject: [PATCH 11/22] Use NDK29 new (and experimental) way to access NDK functions This uses compile time guarded calls to weak symbols, and allows us to call NDK APIs without having to dlsym() them. --- CMakeLists.txt | 6 ++- .../android/ExternalTextureManagerAndroid.cpp | 41 +++++-------------- .../android/ExternalTextureManagerAndroid.h | 11 ----- .../ExternalStreamManagerAndroid.cpp | 30 ++++---------- .../platforms/ExternalStreamManagerAndroid.h | 7 ---- third_party/spirv-cross/spirv_glsl.cpp | 22 +++++++--- 6 files changed, 40 insertions(+), 77 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cc5db2dba8..6db3af0533 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,10 @@ if (ANDROID OR WEBGL OR IOS OR FILAMENT_LINUX_IS_MOBILE) set(IS_MOBILE_TARGET TRUE) endif() +if (ANDROID) + add_definitions(-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__) +endif() + if (NOT ANDROID AND NOT WEBGL AND NOT IOS AND NOT FILAMENT_LINUX_IS_MOBILE) set(IS_HOST_PLATFORM TRUE) endif() @@ -295,7 +299,7 @@ if (LINUX) endif() if (ANDROID) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Werror=unguarded-availability") endif() if (CYGWIN) diff --git a/filament/backend/src/android/ExternalTextureManagerAndroid.cpp b/filament/backend/src/android/ExternalTextureManagerAndroid.cpp index a3ccb93d5b..1892a666a3 100644 --- a/filament/backend/src/android/ExternalTextureManagerAndroid.cpp +++ b/filament/backend/src/android/ExternalTextureManagerAndroid.cpp @@ -95,33 +95,25 @@ void ExternalTextureManagerAndroid::destroy(ExternalTextureManagerAndroid* pExte // called on gl thread ExternalTextureManagerAndroid::ExternalTextureManagerAndroid() noexcept : mVm(VirtualMachineEnv::get()) { - // if we compile for API 26 (Oreo) and above, we're guaranteed to have AHardwareBuffer - // in all other cases, we need to get them at runtime. -#ifndef PLATFORM_HAS_HARDWAREBUFFER - // the following dlsym() calls don't work on API 19 - if (api_level() >= 21) { - loadSymbol(AHardwareBuffer_allocate, "AHardwareBuffer_allocate"); - loadSymbol(AHardwareBuffer_release, "AHardwareBuffer_release"); - } -#endif } // not quite sure on which thread this is going to be called ExternalTextureManagerAndroid::~ExternalTextureManagerAndroid() noexcept { -#ifndef PLATFORM_HAS_HARDWAREBUFFER - if (mGraphicBufferClass) { - JNIEnv* env = VirtualMachineEnv::getThreadEnvironment(); - if (env) { - env->DeleteGlobalRef(mGraphicBufferClass); + if (__builtin_available(android 26, *)) { + } else { + if (mGraphicBufferClass) { + JNIEnv* env = VirtualMachineEnv::getThreadEnvironment(); + if (env) { + env->DeleteGlobalRef(mGraphicBufferClass); + } } } -#endif } // called on gl thread backend::Platform::ExternalTexture* ExternalTextureManagerAndroid::createExternalTexture() noexcept { -#ifndef PLATFORM_HAS_HARDWAREBUFFER - if (!AHardwareBuffer_allocate) { + if (__builtin_available(android 26, *)) { + } else { // initialize java stuff on-demand if (!mGraphicBufferClass) { JNIEnv* env = mVm.getEnvironment(); @@ -134,7 +126,6 @@ backend::Platform::ExternalTexture* ExternalTextureManagerAndroid::createExterna mGraphicBufferClass = static_cast(env->NewGlobalRef(mGraphicBufferClass)); } } -#endif EGLExternalTexture* ets = new EGLExternalTexture; return ets; } @@ -181,9 +172,7 @@ void ExternalTextureManagerAndroid::alloc( return; } -#ifndef PLATFORM_HAS_HARDWAREBUFFER - if (AHardwareBuffer_allocate) { -#endif + if (__builtin_available(android 26, *)) { // allocate new storage... AHardwareBuffer* buffer = nullptr; if (AHardwareBuffer_allocate(&desc, &buffer) < 0) { @@ -191,7 +180,6 @@ void ExternalTextureManagerAndroid::alloc( return; } ets->hardwareBuffer = buffer; -#ifndef PLATFORM_HAS_HARDWAREBUFFER } else { // note: This is called on the application thread (not the GL thread) JNIEnv* env = VirtualMachineEnv::getThreadEnvironment(); @@ -209,23 +197,17 @@ void ExternalTextureManagerAndroid::alloc( ets->clientBuffer = static_cast(gb); } } -#endif } // called on gl thread void ExternalTextureManagerAndroid::destroyStorage(backend::Platform::ExternalTexture* t) noexcept { EGLExternalTexture* ets = static_cast(t); - -#ifndef PLATFORM_HAS_HARDWAREBUFFER - if (AHardwareBuffer_allocate) { -#endif + if (__builtin_available(android 26, *)) { // destroy the current storage if any if (ets->hardwareBuffer) { AHardwareBuffer_release(ets->hardwareBuffer); ets->hardwareBuffer = nullptr; } - -#ifndef PLATFORM_HAS_HARDWAREBUFFER } else { if (ets->graphicBufferWrapper) { JNIEnv* env = VirtualMachineEnv::get().getEnvironment(); @@ -235,7 +217,6 @@ void ExternalTextureManagerAndroid::destroyStorage(backend::Platform::ExternalTe ets->graphicBufferWrapper = nullptr; } } -#endif } } // namespace filament diff --git a/filament/backend/src/android/ExternalTextureManagerAndroid.h b/filament/backend/src/android/ExternalTextureManagerAndroid.h index 925a7960b6..cfe1e6af34 100644 --- a/filament/backend/src/android/ExternalTextureManagerAndroid.h +++ b/filament/backend/src/android/ExternalTextureManagerAndroid.h @@ -25,10 +25,6 @@ #include #include -#if __ANDROID_API__ >= 26 -# define PLATFORM_HAS_HARDWAREBUFFER -#endif - namespace filament { /* @@ -78,16 +74,9 @@ private: VirtualMachineEnv& mVm; -#ifndef PLATFORM_HAS_HARDWAREBUFFER - // if we compile for API 26 (Oreo) and above, we're guaranteed to have AHardwareBuffer - // in all other cases, we need to get them at runtime. - int (*AHardwareBuffer_allocate)(const AHardwareBuffer_Desc*, AHardwareBuffer**) = nullptr; - void (*AHardwareBuffer_release)(AHardwareBuffer*) = nullptr; - jclass mGraphicBufferClass = nullptr; jmethodID mGraphicBuffer_nCreateGraphicBuffer = nullptr; jmethodID mGraphicBuffer_nDestroyGraphicBuffer = nullptr; -#endif }; diff --git a/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.cpp b/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.cpp index c8e1aca6c3..c74b8d311d 100644 --- a/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.cpp +++ b/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.cpp @@ -47,18 +47,7 @@ void ExternalStreamManagerAndroid::destroy(ExternalStreamManagerAndroid* pExtern ExternalStreamManagerAndroid::ExternalStreamManagerAndroid() noexcept : mVm(VirtualMachineEnv::get()) { - - // the following dlsym() calls don't work on API 19 - if (api_level() >= 21) { - loadSymbol(ASurfaceTexture_fromSurfaceTexture, "ASurfaceTexture_fromSurfaceTexture"); - loadSymbol(ASurfaceTexture_release, "ASurfaceTexture_release"); - loadSymbol(ASurfaceTexture_attachToGLContext, "ASurfaceTexture_attachToGLContext"); - loadSymbol(ASurfaceTexture_detachFromGLContext, "ASurfaceTexture_detachFromGLContext"); - loadSymbol(ASurfaceTexture_updateTexImage, "ASurfaceTexture_updateTexImage"); - loadSymbol(ASurfaceTexture_getTimestamp, "ASurfaceTexture_getTimestamp"); - } - - if (ASurfaceTexture_fromSurfaceTexture) { + if (__builtin_available(android 28, *)) { slog.d << "Using ASurfaceTexture" << io::endl; } } @@ -95,7 +84,7 @@ Stream* ExternalStreamManagerAndroid::acquire(jobject surfaceTexture) noexcept { } EGLStream* stream = new EGLStream(); stream->jSurfaceTexture = env->NewGlobalRef(surfaceTexture); - if (ASurfaceTexture_fromSurfaceTexture) { + if (__builtin_available(android 28, *)) { stream->nSurfaceTexture = ASurfaceTexture_fromSurfaceTexture(env, surfaceTexture); } return stream; @@ -103,7 +92,7 @@ Stream* ExternalStreamManagerAndroid::acquire(jobject surfaceTexture) noexcept { void ExternalStreamManagerAndroid::release(Stream* handle) noexcept { EGLStream* stream = static_cast(handle); - if (ASurfaceTexture_fromSurfaceTexture) { + if (__builtin_available(android 28, *)) { ASurfaceTexture_release(stream->nSurfaceTexture); } JNIEnv* const env = getEnvironment(); @@ -114,7 +103,7 @@ void ExternalStreamManagerAndroid::release(Stream* handle) noexcept { void ExternalStreamManagerAndroid::attach(Stream* handle, intptr_t tname) noexcept { EGLStream* stream = static_cast(handle); - if (ASurfaceTexture_fromSurfaceTexture) { + if (__builtin_available(android 28, *)) { // associate our GL texture to the SurfaceTexture ASurfaceTexture* const aSurfaceTexture = stream->nSurfaceTexture; if (UTILS_UNLIKELY(ASurfaceTexture_attachToGLContext(aSurfaceTexture, (uint32_t)tname))) { @@ -152,7 +141,7 @@ void ExternalStreamManagerAndroid::attach(Stream* handle, intptr_t tname) noexce void ExternalStreamManagerAndroid::detach(Stream* handle) noexcept { EGLStream* stream = static_cast(handle); - if (ASurfaceTexture_fromSurfaceTexture) { + if (__builtin_available(android 28, *)) { ASurfaceTexture_detachFromGLContext(stream->nSurfaceTexture); } else { JNIEnv* const env = mVm.getEnvironment(); @@ -164,14 +153,9 @@ void ExternalStreamManagerAndroid::detach(Stream* handle) noexcept { void ExternalStreamManagerAndroid::updateTexImage(Stream* handle, int64_t* timestamp) noexcept { EGLStream* stream = static_cast(handle); - if (ASurfaceTexture_fromSurfaceTexture) { + if (__builtin_available(android 28, *)) { ASurfaceTexture_updateTexImage(stream->nSurfaceTexture); - if (ASurfaceTexture_getTimestamp) { - *timestamp = ASurfaceTexture_getTimestamp(stream->nSurfaceTexture); - } else { - // if we're not at least on API 28, we may not have getTimestamp() - *timestamp = (std::chrono::steady_clock::now().time_since_epoch().count()); - } + *timestamp = ASurfaceTexture_getTimestamp(stream->nSurfaceTexture); } else { JNIEnv* const env = mVm.getEnvironment(); assert_invariant(env); // we should have called attach() by now diff --git a/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.h b/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.h index 4bbc5687ca..dee6dd002e 100644 --- a/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.h +++ b/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.h @@ -84,13 +84,6 @@ private: jmethodID mSurfaceTextureClass_getTimestamp{}; jmethodID mSurfaceTextureClass_attachToGLContext{}; jmethodID mSurfaceTextureClass_detachFromGLContext{}; - - ASurfaceTexture* (*ASurfaceTexture_fromSurfaceTexture)(JNIEnv*, jobject){}; - void (*ASurfaceTexture_release)(ASurfaceTexture*){}; - int (*ASurfaceTexture_attachToGLContext)(ASurfaceTexture*, uint32_t){}; - int (*ASurfaceTexture_detachFromGLContext)(ASurfaceTexture*){}; - int (*ASurfaceTexture_updateTexImage)(ASurfaceTexture*){}; - int64_t (*ASurfaceTexture_getTimestamp)(ASurfaceTexture*){}; // available since api 28 }; } // namespace filament diff --git a/third_party/spirv-cross/spirv_glsl.cpp b/third_party/spirv-cross/spirv_glsl.cpp index 6feac01ad0..38d7f5ef5f 100644 --- a/third_party/spirv-cross/spirv_glsl.cpp +++ b/third_party/spirv-cross/spirv_glsl.cpp @@ -176,11 +176,23 @@ void CompilerGLSL::init() const struct lconv *conv = localeconv(); if (conv && conv->decimal_point) current_locale_radix_character = *conv->decimal_point; -#elif defined(__ANDROID__) && __ANDROID_API__ < 26 - // nl_langinfo is not supported on this platform, fall back to the worse alternative. - const struct lconv *conv = localeconv(); - if (conv && conv->decimal_point) - current_locale_radix_character = *conv->decimal_point; +#elif defined(__ANDROID__) + #if __ANDROID_API__ >= 26 + // nl_langinfo is not even defined if __ANDROID_API__ is not at least 26! + if (__builtin_available(android 26, *)) { + // localeconv, the portable function is not MT safe ... + const char *decimal_point = nl_langinfo(RADIXCHAR); + if (decimal_point && *decimal_point != '\0') + current_locale_radix_character = *decimal_point; + } else + #endif + if (__builtin_available(android 21, *)) { + const struct lconv *conv = localeconv(); + if (conv && conv->decimal_point) + current_locale_radix_character = *conv->decimal_point; + } else { + // we do nothing + } #else // localeconv, the portable function is not MT safe ... const char *decimal_point = nl_langinfo(RADIXCHAR); From 27a51581477c1f1da18460796420dd5186889109 Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Thu, 13 Jan 2022 15:38:19 -0800 Subject: [PATCH 12/22] Update tone mapping operators notebook --- docs/math/Tone mapping operators.nb | 3736 +++++++++++++-------------- docs/math/data/aces.csv | 1000 +++++++ 2 files changed, 2761 insertions(+), 1975 deletions(-) create mode 100644 docs/math/data/aces.csv diff --git a/docs/math/Tone mapping operators.nb b/docs/math/Tone mapping operators.nb index 3d8e66783a..fb0edd13b9 100644 --- a/docs/math/Tone mapping operators.nb +++ b/docs/math/Tone mapping operators.nb @@ -10,10 +10,10 @@ NotebookFileLineBreakTest NotebookFileLineBreakTest NotebookDataPosition[ 158, 7] -NotebookDataLength[ 464050, 8883] -NotebookOptionsPosition[ 460790, 8819] -NotebookOutlinePosition[ 461209, 8836] -CellTagsIndexPosition[ 461166, 8833] +NotebookDataLength[ 446956, 8669] +NotebookOptionsPosition[ 442828, 8595] +NotebookOutlinePosition[ 443191, 8611] +CellTagsIndexPosition[ 443148, 8608] WindowFrame->Normal*) (* Beginning of Notebook Content *) @@ -38,6 +38,31 @@ Cell[BoxData[ CellChangeTimes->{3.647273285923099*^9}, CellLabel->"In[1]:=",ExpressionUUID->"410c9ead-a5b2-4c59-a953-4ebe80279cd3"], +Cell[BoxData[ + RowBox[{ + RowBox[{"SetDirectory", "[", + RowBox[{"NotebookDirectory", "[", "]"}], "]"}], ";"}]], "Input", + CellLabel->"In[62]:=",ExpressionUUID->"506a33a4-caf2-4b9e-a76c-c210efda15ae"], + +Cell[BoxData[ + RowBox[{ + RowBox[{"(*", " ", + RowBox[{ + RowBox[{"Real", " ", "ACES", " ", "mapper"}], ",", " ", + RowBox[{ + "with", " ", "SDR", " ", "Rec", ".709", " ", "100", " ", "nits", " ", + "dim", " ", "surround", " ", "target"}], ",", " ", + RowBox[{"modified", " ", "for", " ", "brighter", " ", "surround"}]}], " ", + "*)"}], "\[IndentingNewLine]", + RowBox[{ + RowBox[{"acesData", ":=", + RowBox[{"Import", "[", "\"\\"", "]"}]}], ";"}]}]], "Input",\ + + CellChangeTimes->{{3.8511054997404757`*^9, 3.851105500145196*^9}, { + 3.851105830003015*^9, + 3.8511058687107487`*^9}},ExpressionUUID->"de6c33eb-c91c-4585-8f36-\ +e1faf95ae0de"], + Cell[BoxData[ RowBox[{ RowBox[{"(*", " ", @@ -213,68 +238,59 @@ Cell[BoxData[ "x", ",", " ", "15.8", ",", " ", "2.12", ",", " ", "1.2", ",", " ", "5.92", ",", " ", "1.9"}], "]"}]}], "\[IndentingNewLine]", "\[IndentingNewLine]", + RowBox[{"(*", " ", "Filament", " ", "*)"}], "\[IndentingNewLine]", RowBox[{"(*", " ", - RowBox[{"Siragusano", "-", "Smith"}], " ", "*)"}], "\[IndentingNewLine]", - + RowBox[{ + "See", " ", "\"\\"", " ", "below"}], " ", + "*)"}], "\[IndentingNewLine]", RowBox[{ - RowBox[{"linearSiragusanoSmithImpl", "[", + RowBox[{"linearFilamentImpl", "[", RowBox[{ - "x_", ",", "contrast_", ",", "middleGreyIn_", ",", "middleGreyOut_", ",", - "ev_"}], "]"}], ":=", - RowBox[{"Block", "[", "\[IndentingNewLine]", + "x_", ",", "contrast_", ",", "grayIn_", ",", "grayOut_", ",", + "hdrMax_"}], "]"}], ":=", + RowBox[{"Module", "[", "\[IndentingNewLine]", RowBox[{ - RowBox[{"{", "\[IndentingNewLine]", + RowBox[{"{", RowBox[{ - RowBox[{"radiometricMax", "=", - RowBox[{ - RowBox[{"2.0", "^", "ev"}], "*", "middleGreyIn"}]}], ",", - "\[IndentingNewLine]", - RowBox[{"inputScale", "=", + "a", ",", "b", ",", "c", ",", "inputScale", ",", "outputScale"}], + "}"}], ",", "\[IndentingNewLine]", + RowBox[{ + RowBox[{"a", ":=", + RowBox[{"grayIn", "^", "contrast"}]}], ";", "\[IndentingNewLine]", + RowBox[{"b", ":=", + RowBox[{"hdrMax", "^", "contrast"}]}], ";", "\[IndentingNewLine]", + RowBox[{"c", ":=", + RowBox[{"a", "-", + RowBox[{"grayOut", "*", "b"}]}]}], ";", "\[IndentingNewLine]", + RowBox[{"inputScale", ":=", + RowBox[{ + RowBox[{"(", + RowBox[{"a", "*", "b", "*", + RowBox[{"(", + RowBox[{"grayOut", "-", "1"}], ")"}]}], ")"}], "/", "c"}]}], ";", + "\[IndentingNewLine]", + RowBox[{"outputScale", ":=", + RowBox[{"grayOut", "*", RowBox[{ RowBox[{"(", - RowBox[{ - RowBox[{"(", - RowBox[{"radiometricMax", "*", "middleGreyIn"}], ")"}], "*", - RowBox[{"(", - RowBox[{ - RowBox[{"middleGreyOut", "^", - RowBox[{"(", - RowBox[{"1.0", "/", "contrast"}], ")"}]}], "-", "1.0"}], - ")"}]}], ")"}], "/", - RowBox[{"(", - RowBox[{"middleGreyIn", "-", - RowBox[{"(", - RowBox[{"radiometricMax", "*", - RowBox[{"middleGreyOut", "^", - RowBox[{"(", - RowBox[{"1.0", "/", "contrast"}], ")"}]}]}], ")"}]}], - ")"}]}]}], ",", "\[IndentingNewLine]", - RowBox[{"outputScale", "=", - RowBox[{ - RowBox[{"(", - RowBox[{"radiometricMax", "/", - RowBox[{"(", - RowBox[{"radiometricMax", "+", "inputScale"}], ")"}]}], ")"}], - "^", - RowBox[{"-", "contrast"}]}]}]}], "\[IndentingNewLine]", "}"}], ",", - "\[IndentingNewLine]", - RowBox[{"outputScale", "*", - RowBox[{ - RowBox[{"(", - RowBox[{"x", "/", - RowBox[{"(", - RowBox[{"x", "+", "inputScale"}], ")"}]}], ")"}], "^", - "contrast"}]}]}], "\[IndentingNewLine]", "]"}]}], - "\[IndentingNewLine]", + RowBox[{"a", "-", "b"}], ")"}], "/", "c"}]}]}], ";", + "\[IndentingNewLine]", + RowBox[{"outputScale", "*", + RowBox[{ + RowBox[{"x", "^", "contrast"}], "/", + RowBox[{"(", + RowBox[{ + RowBox[{"x", "^", "contrast"}], "+", "inputScale"}], ")"}]}]}]}]}], + "\[IndentingNewLine]", "]"}]}], "\[IndentingNewLine]", RowBox[{ - RowBox[{"linearSiragusanoSmith", "[", "x_", "]"}], ":=", + RowBox[{"linearFilament", "[", "x_", "]"}], ":=", RowBox[{"linearSiragusanoSmithImpl", "[", - RowBox[{"x", ",", "4.24", ",", "0.18", ",", "0.281", ",", "5.5"}], + RowBox[{"x", ",", "1.55", ",", "0.18", ",", "0.215", ",", "16.0"}], "]"}]}], "\[IndentingNewLine]", RowBox[{ - RowBox[{"SiragusanoSmith", "[", "x_", "]"}], ":=", + RowBox[{"Filament", "[", "x_", "]"}], ":=", RowBox[{ - RowBox[{"linearSiragusanoSmith", "[", "x", "]"}], "^", + RowBox[{"linearFilament", "[", "x", "]"}], "^", RowBox[{"(", RowBox[{"1", "/", "2.2"}], ")"}]}]}], "\[IndentingNewLine]", "\[IndentingNewLine]", @@ -293,7 +309,7 @@ Cell[BoxData[ "ACES", ",", "\[IndentingNewLine]", "unreal", ",", "\[IndentingNewLine]", "uncharted2", ",", "\[IndentingNewLine]", "filmicALU", ",", "\[IndentingNewLine]", "Reinhard", ",", "\[IndentingNewLine]", - "SiragusanoSmith", ",", "\[IndentingNewLine]", "noTonemap"}], + "Filament", ",", "\[IndentingNewLine]", "noTonemap"}], "\[IndentingNewLine]", "\[IndentingNewLine]", "}"}]}], "\[IndentingNewLine]", RowBox[{"linearToneMapOperators", " ", ":=", " ", @@ -302,16 +318,16 @@ Cell[BoxData[ "linearACES", ",", "\[IndentingNewLine]", "linearUnreal", ",", "\[IndentingNewLine]", "linearUncharted2", ",", "\[IndentingNewLine]", "linearFilmicALU", ",", "\[IndentingNewLine]", "linearReinhard", ",", - "\[IndentingNewLine]", "linearSiragusanoSmith", ",", - "\[IndentingNewLine]", "linearNoTonemap"}], "\[IndentingNewLine]", - "}"}]}], "\[IndentingNewLine]", + "\[IndentingNewLine]", "linearFilament", ",", "\[IndentingNewLine]", + "linearNoTonemap"}], "\[IndentingNewLine]", "}"}]}], + "\[IndentingNewLine]", RowBox[{"operatorNames", " ", ":=", " ", RowBox[{"{", " ", RowBox[{ "\"\\"", ",", " ", "\"\\"", ",", " ", "\"\\"", ",", " ", "\"\\"", ",", " ", - "\"\\"", ",", "\"\\"", ",", " ", - "\"\\""}], "}"}]}], "\[IndentingNewLine]", "\[IndentingNewLine]", + "\"\\"", ",", "\"\\"", ",", " ", "\"\\""}], + "}"}]}], "\[IndentingNewLine]", "\[IndentingNewLine]", RowBox[{"linearACESToneMapOperators", " ", ":=", " ", RowBox[{"{", "\[IndentingNewLine]", RowBox[{ @@ -360,8 +376,10 @@ Cell[BoxData[ 3.828214275791524*^9}, {3.828214387158256*^9, 3.828214391260779*^9}, { 3.8282159387720203`*^9, 3.8282159894788837`*^9}, {3.828216033197357*^9, 3.82821604093042*^9}, {3.8282162342269487`*^9, 3.828216234336994*^9}, { - 3.8290751162842712`*^9, 3.829075117401083*^9}}, - CellLabel->"In[2]:=",ExpressionUUID->"37bf4303-de95-435d-b24b-32a661b82f4c"] + 3.8290751162842712`*^9, 3.829075117401083*^9}, {3.851105332469427*^9, + 3.8511053904583597`*^9}, {3.851105775582765*^9, + 3.8511057868950577`*^9}},ExpressionUUID->"37bf4303-de95-435d-b24b-\ +32a661b82f4c"] }, Open ]], Cell[CellGroupData[{ @@ -524,7 +542,7 @@ Cell[BoxData[ 3.835449812254682*^9, 3.835449815965712*^9}, {3.835449854066792*^9, 3.835449890030797*^9}, {3.8354499345326567`*^9, 3.8354499549855556`*^9}, 3.835450007110338*^9, {3.8354500887817793`*^9, 3.835450297667041*^9}}, - CellLabel->"In[58]:=",ExpressionUUID->"64a40cba-fe9b-420c-bc1f-e2b70d44b247"], + CellLabel->"In[79]:=",ExpressionUUID->"64a40cba-fe9b-420c-bc1f-e2b70d44b247"], Cell[CellGroupData[{ @@ -614,8 +632,9 @@ Cell[BoxData[ 3.8282196532681303`*^9}, {3.829075192883951*^9, 3.8290751931232347`*^9}, { 3.835453429995984*^9, 3.83545343769248*^9}, {3.835458120849481*^9, 3.835458140489389*^9}, {3.8354598602280397`*^9, 3.8354598687779617`*^9}, { - 3.835459910575705*^9, 3.835459910670258*^9}}, - CellLabel->"In[87]:=",ExpressionUUID->"ed06a09c-d067-4495-8651-30989ac4c29c"], + 3.835459910575705*^9, 3.835459910670258*^9}, {3.851105821212246*^9, + 3.851105822107813*^9}}, + CellLabel->"In[82]:=",ExpressionUUID->"ed06a09c-d067-4495-8651-30989ac4c29c"], Cell[BoxData[ TagBox[ @@ -632,12 +651,19 @@ Cell[BoxData[ Hold[$CellContext`midIn$$], 0.18}, 0.01, 1.}, {{ Hold[$CellContext`midOut$$], 0.266}, 0.01, 1.}}, Typeset`size$$ = { 576., {175., 179.}}, Typeset`update$$ = 0, Typeset`initDone$$, - Typeset`skipInitDone$$ = True}, + Typeset`skipInitDone$$ = True, $CellContext`hdrMax$66135$$ = + 0, $CellContext`contrast$66136$$ = 0, $CellContext`shoulder$66137$$ = + 0, $CellContext`midIn$66138$$ = 0, $CellContext`midOut$66139$$ = 0}, DynamicBox[Manipulate`ManipulateBoxes[ 1, StandardForm, "Variables" :> {$CellContext`contrast$$ = 1.4, $CellContext`hdrMax$$ = 10., $CellContext`midIn$$ = 0.18, $CellContext`midOut$$ = - 0.266, $CellContext`shoulder$$ = 1.}, "ControllerVariables" :> {}, + 0.266, $CellContext`shoulder$$ = 1.}, "ControllerVariables" :> { + Hold[$CellContext`hdrMax$$, $CellContext`hdrMax$66135$$, 0], + Hold[$CellContext`contrast$$, $CellContext`contrast$66136$$, 0], + Hold[$CellContext`shoulder$$, $CellContext`shoulder$66137$$, 0], + Hold[$CellContext`midIn$$, $CellContext`midIn$66138$$, 0], + Hold[$CellContext`midOut$$, $CellContext`midOut$66139$$, 0]}, "OtherVariables" :> { Typeset`show$$, Typeset`bookmarkList$$, Typeset`bookmarkMode$$, Typeset`animator$$, Typeset`animvar$$, Typeset`name$$, @@ -689,21 +715,22 @@ $CellContext`midOut$$], 3.835449467742729*^9, 3.835450305028063*^9, 3.835450368746719*^9, 3.835453438064548*^9, 3.835458109583084*^9, 3.835458140802999*^9, 3.8354583348534727`*^9, {3.835459908223538*^9, 3.835459910993792*^9}, - 3.835460943063118*^9}, - CellLabel->"Out[87]=",ExpressionUUID->"6b1dd3b9-aead-47ff-a023-bad9bf13c501"] + 3.835460943063118*^9, {3.851105803558775*^9, 3.851105827218936*^9}}, + CellLabel->"Out[82]=",ExpressionUUID->"a47f1b0f-054e-4bc3-8d67-92066cc56e58"] }, Open ]] }, Open ]], Cell[CellGroupData[{ -Cell["\[LineSeparator]Siragusano-Smith operator", "Subtitle", +Cell["\[LineSeparator]Filament operator", "Subtitle", CellChangeTimes->{{3.647207023221693*^9, 3.6472070360684757`*^9}, 3.647207397940584*^9, {3.661181365842063*^9, 3.661181366361817*^9}, { 3.681572318557206*^9, 3.68157235028307*^9}, {3.681572390723999*^9, 3.681572391339576*^9}, {3.681572540140861*^9, 3.681572544130238*^9}, { 3.6815768035351458`*^9, 3.681576853718945*^9}, {3.6815769121281767`*^9, 3.681577066206182*^9}, 3.681577097399539*^9, {3.829074944620523*^9, - 3.829074950033181*^9}},ExpressionUUID->"d4e4cb94-73e2-4e2a-b1a5-\ + 3.829074950033181*^9}, {3.8511053224096613`*^9, + 3.8511053231728354`*^9}},ExpressionUUID->"d4e4cb94-73e2-4e2a-b1a5-\ b1d3555e58f5"], Cell[TextData[{ @@ -735,38 +762,57 @@ Cell[TextData[{ Cell[CellGroupData[{ Cell[BoxData[ - RowBox[{"Manipulate", "[", "\[IndentingNewLine]", " ", + RowBox[{"Manipulate", "[", "\[IndentingNewLine]", RowBox[{ - RowBox[{"LogLinearPlot", "[", "\[IndentingNewLine]", " ", - RowBox[{"(*", " ", - RowBox[{ - "We", " ", "plot", " ", "the", " ", "ACES", " ", "operator", " ", "for", - " ", "reference"}], " ", "*)"}], " ", "\[IndentingNewLine]", " ", + RowBox[{"Show", "[", " ", "\[IndentingNewLine]", RowBox[{ - RowBox[{"{", + RowBox[{"LogLinearPlot", "[", "\[IndentingNewLine]", " ", RowBox[{ - RowBox[{"linearSiragusanoSmithImpl", "[", + RowBox[{"{", + RowBox[{"linearFilamentImpl", "[", + RowBox[{ + "x", ",", "contrast", ",", "midIn", ",", "midOut", ",", "hdrMax"}], + "]"}], "}"}], ",", " ", + RowBox[{"{", + RowBox[{"x", ",", " ", "0.01", ",", " ", "hdrMax"}], "}"}], ",", + "\[IndentingNewLine]", " ", + RowBox[{"ImageSize", "\[Rule]", "Large"}], ",", + RowBox[{"PlotRange", "\[Rule]", + RowBox[{"{", + RowBox[{ + RowBox[{"{", + RowBox[{"0.001", ",", "hdrMax"}], "}"}], ",", + RowBox[{"{", + RowBox[{"0", ",", "1"}], "}"}]}], "}"}]}]}], "\[IndentingNewLine]", + " ", "]"}], ",", "\[IndentingNewLine]", + RowBox[{"ListLogLinearPlot", "[", + RowBox[{"acesData", ",", + RowBox[{"Joined", "\[Rule]", "True"}], ",", + RowBox[{"ImageSize", "\[Rule]", "Large"}], ",", + RowBox[{"PlotRange", "\[Rule]", + RowBox[{"{", + RowBox[{ + RowBox[{"{", + RowBox[{"0.001", ",", "hdrMax"}], "}"}], ",", + RowBox[{"{", + RowBox[{"0", ",", "1"}], "}"}]}], "}"}]}], ",", + RowBox[{"PlotStyle", "\[Rule]", RowBox[{ - RowBox[{"x", "*", "1.6"}], ",", "contrast", ",", "midIn", ",", - "midOut", ",", "hdrMax"}], "]"}], ",", - RowBox[{"linearACES", "[", "x", "]"}]}], "}"}], ",", " ", - RowBox[{"{", - RowBox[{"x", ",", " ", "0.01", ",", " ", - RowBox[{"hdrMax", "*", "1.5"}]}], "}"}], ",", "\[IndentingNewLine]", - " ", - RowBox[{"ImageSize", "\[Rule]", "Large"}]}], "\[IndentingNewLine]", " ", - "]"}], ",", "\[IndentingNewLine]", " ", + RowBox[{"ColorData", "[", + RowBox[{"97", ",", "\"\\""}], "]"}], "[", + RowBox[{"[", "3", "]"}], "]"}]}]}], "]"}]}], "\[IndentingNewLine]", + "]"}], ",", "\[IndentingNewLine]", " ", RowBox[{"{", RowBox[{ RowBox[{"{", - RowBox[{"hdrMax", ",", "10.0"}], "}"}], ",", " ", "1.0", ",", " ", - "16.0", ",", + RowBox[{"hdrMax", ",", " ", "10.0"}], "}"}], ",", " ", "1.0", ",", " ", + "64.0", ",", RowBox[{"Appearance", "\[Rule]", "\"\\""}]}], "}"}], ",", "\[IndentingNewLine]", " ", RowBox[{"{", RowBox[{ RowBox[{"{", - RowBox[{"contrast", ",", " ", "1.75"}], "}"}], ",", " ", "0.2", ",", + RowBox[{"contrast", ",", " ", "1.55"}], "}"}], ",", " ", "0.2", ",", " ", "8.0", ",", RowBox[{"Appearance", "\[Rule]", "\"\\""}]}], "}"}], ",", "\[IndentingNewLine]", " ", @@ -780,8 +826,8 @@ Cell[BoxData[ RowBox[{"{", RowBox[{ RowBox[{"{", - RowBox[{"midOut", ",", " ", "0.18"}], "}"}], ",", " ", "0.01", ",", " ", - "1.0", ",", + RowBox[{"midOut", ",", " ", "0.215"}], "}"}], ",", " ", "0.01", ",", + " ", "1.0", ",", RowBox[{"Appearance", "\[Rule]", "\"\\""}]}], "}"}]}], "\[IndentingNewLine]", "]"}]], "Input", CellChangeTimes->{{3.8282144101229486`*^9, 3.828214478627986*^9}, { @@ -791,44 +837,56 @@ Cell[BoxData[ 3.829075136377384*^9}, {3.829075186029532*^9, 3.829075190546693*^9}, { 3.835447447096184*^9, 3.835447447622985*^9}, {3.83544757775528*^9, 3.835447603585886*^9}, {3.8354476896906023`*^9, 3.83544768977808*^9}, { - 3.835451476405488*^9, 3.835451517961009*^9}}, - CellLabel->"In[76]:=",ExpressionUUID->"572cd540-59b1-4415-8369-fb210674631e"], + 3.835451476405488*^9, 3.835451517961009*^9}, {3.851105398807888*^9, + 3.851105413535009*^9}, {3.851105519213932*^9, 3.8511055412038794`*^9}}, + CellLabel->"In[65]:=",ExpressionUUID->"572cd540-59b1-4415-8369-fb210674631e"], Cell[BoxData[ TagBox[ StyleBox[ - DynamicModuleBox[{$CellContext`contrast$$ = 1.75, $CellContext`hdrMax$$ = - 10., $CellContext`midIn$$ = 0.18, $CellContext`midOut$$ = 0.18, + DynamicModuleBox[{$CellContext`contrast$$ = 1.55, $CellContext`hdrMax$$ = + 10., $CellContext`midIn$$ = 0.18, $CellContext`midOut$$ = 0.215, Typeset`show$$ = True, Typeset`bookmarkList$$ = {}, Typeset`bookmarkMode$$ = "Menu", Typeset`animator$$, Typeset`animvar$$ = 1, Typeset`name$$ = "\"untitled\"", Typeset`specs$$ = {{{ - Hold[$CellContext`hdrMax$$], 10.}, 1., 16.}, {{ - Hold[$CellContext`contrast$$], 1.75}, 0.2, 8.}, {{ + Hold[$CellContext`hdrMax$$], 10.}, 1., 64.}, {{ + Hold[$CellContext`contrast$$], 1.55}, 0.2, 8.}, {{ Hold[$CellContext`midIn$$], 0.18}, 0.01, 1.}, {{ - Hold[$CellContext`midOut$$], 0.18}, 0.01, 1.}}, Typeset`size$$ = { - 576., {175., 179.}}, Typeset`update$$ = 0, Typeset`initDone$$, - Typeset`skipInitDone$$ = True}, + Hold[$CellContext`midOut$$], 0.215}, 0.01, 1.}}, Typeset`size$$ = { + 576., {180., 185.}}, Typeset`update$$ = 0, Typeset`initDone$$, + Typeset`skipInitDone$$ = True, $CellContext`hdrMax$44814$$ = + 0, $CellContext`contrast$44815$$ = 0, $CellContext`midIn$44816$$ = + 0, $CellContext`midOut$44817$$ = 0}, DynamicBox[Manipulate`ManipulateBoxes[ 1, StandardForm, - "Variables" :> {$CellContext`contrast$$ = 1.75, $CellContext`hdrMax$$ = - 10., $CellContext`midIn$$ = 0.18, $CellContext`midOut$$ = 0.18}, - "ControllerVariables" :> {}, + "Variables" :> {$CellContext`contrast$$ = 1.55, $CellContext`hdrMax$$ = + 10., $CellContext`midIn$$ = 0.18, $CellContext`midOut$$ = 0.215}, + "ControllerVariables" :> { + Hold[$CellContext`hdrMax$$, $CellContext`hdrMax$44814$$, 0], + Hold[$CellContext`contrast$$, $CellContext`contrast$44815$$, 0], + Hold[$CellContext`midIn$$, $CellContext`midIn$44816$$, 0], + Hold[$CellContext`midOut$$, $CellContext`midOut$44817$$, 0]}, "OtherVariables" :> { Typeset`show$$, Typeset`bookmarkList$$, Typeset`bookmarkMode$$, Typeset`animator$$, Typeset`animvar$$, Typeset`name$$, Typeset`specs$$, Typeset`size$$, Typeset`update$$, Typeset`initDone$$, - Typeset`skipInitDone$$}, "Body" :> LogLinearPlot[{ - $CellContext`linearSiragusanoSmithImpl[$CellContext`x - 1.6, $CellContext`contrast$$, $CellContext`midIn$$, \ -$CellContext`midOut$$, $CellContext`hdrMax$$], - $CellContext`linearACES[$CellContext`x]}, {$CellContext`x, - 0.01, $CellContext`hdrMax$$ 1.5}, ImageSize -> Large], - "Specifications" :> {{{$CellContext`hdrMax$$, 10.}, 1., 16., Appearance -> - "Labeled"}, {{$CellContext`contrast$$, 1.75}, 0.2, 8., Appearance -> + Typeset`skipInitDone$$}, "Body" :> Show[ + LogLinearPlot[{ + $CellContext`linearFilamentImpl[$CellContext`x, \ +$CellContext`contrast$$, $CellContext`midIn$$, $CellContext`midOut$$, \ +$CellContext`hdrMax$$]}, {$CellContext`x, 0.01, $CellContext`hdrMax$$}, + ImageSize -> Large, + PlotRange -> {{0.001, $CellContext`hdrMax$$}, {0, 1}}], + ListLogLinearPlot[$CellContext`acesData, Joined -> True, ImageSize -> + Large, PlotRange -> {{0.001, $CellContext`hdrMax$$}, {0, 1}}, + PlotStyle -> Part[ + ColorData[97, "ColorList"], 3]]], + "Specifications" :> {{{$CellContext`hdrMax$$, 10.}, 1., 64., Appearance -> + "Labeled"}, {{$CellContext`contrast$$, 1.55}, 0.2, 8., Appearance -> "Labeled"}, {{$CellContext`midIn$$, 0.18}, 0.01, 1., Appearance -> - "Labeled"}, {{$CellContext`midOut$$, 0.18}, 0.01, 1., Appearance -> + "Labeled"}, {{$CellContext`midOut$$, 0.215}, 0.01, 1., Appearance -> "Labeled"}}, "Options" :> {}, "DefaultOptions" :> {}], - ImageSizeCache->{621., {262., 268.}}, + ImageSizeCache->{621., {267., 273.}}, SingleEvaluation->True], Deinitialization:>None, DynamicModuleValues:>{}, @@ -846,8 +904,9 @@ $CellContext`midOut$$, $CellContext`hdrMax$$], 3.828219572886441*^9, 3.829074723057993*^9, {3.829075127220955*^9, 3.829075141313972*^9}, 3.829075197767853*^9, 3.835447200861053*^9, 3.835447448168345*^9, {3.835447578239978*^9, 3.8354476038554688`*^9}, { - 3.835451469688581*^9, 3.835451518279397*^9}}, - CellLabel->"Out[76]=",ExpressionUUID->"07c8965f-0e25-48b0-9508-0543006a9e84"] + 3.835451469688581*^9, 3.835451518279397*^9}, 3.851105414131139*^9, { + 3.851105537060235*^9, 3.85110554154747*^9}}, + CellLabel->"Out[65]=",ExpressionUUID->"3744bea6-fd11-4b6f-a916-3680b3c52354"] }, Open ]] }, Open ]], @@ -935,11 +994,10 @@ Cell[BoxData[{ 3.6611825845991983`*^9, 3.661182623850226*^9}, {3.661182700881919*^9, 3.6611827058815527`*^9}, {3.661183995743362*^9, 3.6611840139014072`*^9}, { 3.6617916813195972`*^9, 3.661791691374861*^9}}, - CellLabel->"In[29]:=",ExpressionUUID->"68982d2b-cd3f-4394-b9b7-bc306b15c89c"], + CellLabel->"In[54]:=",ExpressionUUID->"68982d2b-cd3f-4394-b9b7-bc306b15c89c"], Cell[BoxData[ - TemplateBox[{ - GraphicsBox[{{{{}, {}, + TemplateBox[{GraphicsBox[{{{{}, {}, TagBox[{ Directive[ Opacity[1.], @@ -1018,7 +1076,7 @@ jfPI5IHcwRHi4rL/BxQnrzk= 2.0149261220954124`, 1.}, {2.0828887964840312`, 1.}, { 2.15085147087265, 1.}, {2.226718211446049, 1.}, { 2.3025849520194486`, 1.}}]}, - Annotation[#, "Charting`Private`Tag$3753#1"]& ], + Annotation[#, "Charting`Private`Tag$41156#1"]& ], TagBox[{ Directive[ Opacity[1.], @@ -1100,7 +1158,7 @@ mBHBgQSwz+3WITG7yjS0DkdxQDg3LrdCn8QMa1T8UbHmcCo7N9pwrd/Q5SYZ zQG/vvV5uqYk5o7WLxWcNS/q6ORJWZKYA+2hPmYxHPAocs1bsiExh5vt+J+t eUokNa/DnsT8EJpqqhDLAUfv5rwyJxJzsdf4Ytya/wciaVKM "]]}, - Annotation[#, "Charting`Private`Tag$3753#2"]& ], + Annotation[#, "Charting`Private`Tag$41156#2"]& ], TagBox[{ Directive[ Opacity[1.], @@ -1136,7 +1194,7 @@ aag9Tqy9LPTtLUeQDQFlKjec8Hy0xHFYimB+WWZZzS6cuJe8yyCkGkEKiywT 7MWJycCaNZoaBHz1YrG7N05s3q2KGa9HMOPmJl7pixMJUa9vn25EcLYiXDzr hxOZ7Ha7cRmC8YUF4t6A//6j2DisGUFwtEIsDcEJgXndtvctCP4Fc05nmw== - "]]}, Annotation[#, "Charting`Private`Tag$3753#3"]& ], + "]]}, Annotation[#, "Charting`Private`Tag$41156#3"]& ], TagBox[{ Directive[ Opacity[1.], @@ -1207,7 +1265,7 @@ pRFP0mk3ZHNICGUIsqL0aITaBBHb/Y+9P8lk7zahESf31a53ySVhksPJXmFB I1xNwxu35ZHgkueYPXWCRnQF7xERyidhaN7N7CYbGsFcql5d849tPWqyC3k0 4sPuK4uu3CXhf7rBYIg= "]]}, - Annotation[#, "Charting`Private`Tag$3753#4"]& ], + Annotation[#, "Charting`Private`Tag$41156#4"]& ], TagBox[{ Directive[ Opacity[1.], @@ -1243,97 +1301,64 @@ A+0/lD9mp9lEAkABSMfpVArtPw+nkqe0SQJAxUxZ1fUL7T+KDr+nj04CQHxA yPKVDe0/f90XqEVYAkDONG7E0xDtP/pERKggXQJA7NU9eXES7T90rHCo+2EC QLKmG2EOFO0/7xOdqNZmAkA391N8qhXtP2p7yaixawJAORczy0UX7T8xZ2Ms - "]]}, Annotation[#, "Charting`Private`Tag$3753#5"]& ], + "]]}, Annotation[#, "Charting`Private`Tag$41156#5"]& ], TagBox[{ Directive[ Opacity[1.], RGBColor[0.772079, 0.431554, 0.102387], AbsoluteThickness[1.6]], LineBox[CompressedData[" -1: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 -MDv/OPylIT4JZwMbc4pdOZg0tU63C2v+HzjHAm4= +1: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 +hC1t76P+E47D/wBs3zKy "]]}, - Annotation[#, "Charting`Private`Tag$3753#6"]& ], + Annotation[#, "Charting`Private`Tag$41156#6"]& ], TagBox[{ Directive[ Opacity[1.], @@ -1357,8 +1382,14 @@ eaKRKl7gv5/RmyOVXOk/t+S8/bgX2L+Fv9LHnffqPxgt6OeAcM2/XRkRYdPS 7D8G+I5IhOG3vx6Awgnnq+4/U7U5Xg3bpD99pb6Vi0zwP/fV5ZsB1Mc/jGJG cq1p8T+sKSbIBpnUP85PyjBBhfI/JFto5LWX2T8zMzMzMzPzP9x9Y74= "]]}, - Annotation[#, "Charting`Private`Tag$3753#7"]& ], {}}}, {}}, { - DisplayFunction -> Identity, + Annotation[#, + "Charting`Private`Tag$41156#7"]& ], {}}, {{}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}}, {{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}}, \ +{}}, {DisplayFunction -> Identity, Method -> { "DefaultBoundaryStyle" -> Automatic, "DefaultGraphicsInteraction" -> { @@ -1372,8 +1403,8 @@ cq1p8T+sKSbIBpnUP85PyjBBhfI/JFto5LWX2T8zMzMzMzPzP9x9Y74= None}, DisplayFunction -> Identity, DisplayFunction -> Identity, Ticks -> {Quiet[ Charting`ScaledTicks[{Log, Exp}][#, #2, {6, 6}]]& , Automatic}, - AxesOrigin -> {-4.605170045013494, 0}, - FrameTicks -> {{Automatic, Automatic}, {Quiet[ + AxesOrigin -> {-4.605170045013494, 0}, FrameTicks -> {{Automatic, + Charting`ScaledFrameTicks[{Identity, Identity}]}, {Quiet[ Charting`ScaledTicks[{Log, Exp}][#, #2, {6, 6}]]& , Charting`ScaledFrameTicks[{Log, Exp}]}}, GridLines -> {None, None}, DisplayFunction -> Identity, PlotRangePadding -> {{ @@ -1391,8 +1422,8 @@ cq1p8T+sKSbIBpnUP85PyjBBhfI/JFto5LWX2T8zMzMzMzPzP9x9Y74= "placement" -> {"x" -> "All", "y" -> "None"}}}}, "DefaultMeshStyle" -> AbsolutePointSize[6], "ScalingFunctions" -> None, "ClippingRange" -> {{{-4.605170045013494, 2.3025849520194486`}, { - 0.00020432465102697523`, 2.848035685935485}}, {{-4.605170045013494, - 2.3025849520194486`}, {0.00020432465102697523`, 1.2}}}}, + 0.0011304659973836435`, 2.848035685935485}}, {{-4.605170045013494, + 2.3025849520194486`}, {0.0011304659973836435`, 1.2}}}}, DisplayFunction -> Identity, AspectRatio -> NCache[GoldenRatio^(-1), 0.6180339887498948], Axes -> {True, True}, AxesLabel -> {None, None}, AxesOrigin -> {0, 0}, @@ -1418,17 +1449,15 @@ cq1p8T+sKSbIBpnUP85PyjBBhfI/JFto5LWX2T8zMzMzMzPzP9x9Y74= "freeformCursorMode" -> True, "placement" -> {"x" -> "All", "y" -> "None"}}}}, "DefaultMeshStyle" -> AbsolutePointSize[6], "ScalingFunctions" -> - None}, - PlotRange -> {{-4.605170045013494, 2.3025849520194486`}, {0, 1.2}}, - PlotRangeClipping -> True, PlotRangePadding -> {{ + None}, PlotRange -> {{-4.605170045013494, 2.3025849520194486`}, { + 0, 1.2}}, PlotRangeClipping -> True, PlotRangePadding -> {{ Scaled[0.02], Scaled[0.02]}, {Automatic, Automatic}}, - Ticks -> {Automatic, Automatic}}], - FormBox[ + Ticks -> {Automatic, Automatic}}],FormBox[ FormBox[ TemplateBox[{ "\"ACES\"", "\"Unreal\"", "\"Uncharted 2\"", "\"FilmicALU\"", - "\"Reinhard\"", "\"Siragusano-Smith\"", "\"None\""}, "LineLegend", + "\"Reinhard\"", "\"Filament\"", "\"None\""}, "LineLegend", DisplayFunction -> (FormBox[ StyleBox[ StyleBox[ @@ -1993,12 +2022,11 @@ cq1p8T+sKSbIBpnUP85PyjBBhfI/JFto5LWX2T8zMzMzMzPzP9x9Y74= 3.8282142008868*^9, {3.828214234665811*^9, 3.828214279564385*^9}, 3.828215966170525*^9, 3.828216048945711*^9, 3.828216333521703*^9, 3.828219573265332*^9, 3.82907472379275*^9, 3.829075127596117*^9, - 3.8354472012332478`*^9}, - CellLabel->"Out[30]=",ExpressionUUID->"6ef38163-1cbf-4b9b-86ca-ec90ebdeada7"], + 3.8354472012332478`*^9, 3.851105420333988*^9}, + CellLabel->"Out[55]=",ExpressionUUID->"8ba6c080-8fed-416f-a337-7010a1374821"], Cell[BoxData[ - TemplateBox[{ - GraphicsBox[{{{{}, {}, + TemplateBox[{GraphicsBox[{{{{}, {}, TagBox[{ Directive[ Opacity[1.], @@ -2034,7 +2062,7 @@ l+k/Xa1j32Llib/espX7qKLpP7uwHhe4qIa/nXr6mG2l6T8ZtNlODWyDv9Nu XlYxqOk/qnWffG/leb+0WZsxtq3pP2Z8FewZbHO/A3GwT3ew6T9FBhe3iOVp v7v1PI43s+k/eicGLLvlWb91WV/t9rXpP6WcmpB3Onm+YTQ2bbW46T9W33Fn - "]]}, Annotation[#, "Charting`Private`Tag$4119#1"]& ], + "]]}, Annotation[#, "Charting`Private`Tag$41514#1"]& ], TagBox[{ Directive[ Opacity[1.], @@ -2085,7 +2113,7 @@ nzAt8zMI0Kvq2qsZeBqFGlt6TWURID6RHxMii0SiK/nl+XkE+MRoj+oFclHR 0vKuyWsE7FDVrU2W+SH3uJQDtrcIiDJSXdUgc0Wig9orc0UE3LqcmxpuHtkk PrfeefQOAf8CVRir7A== "]]}, - Annotation[#, "Charting`Private`Tag$4119#2"]& ], + Annotation[#, "Charting`Private`Tag$41514#2"]& ], TagBox[{ Directive[ Opacity[1.], @@ -2121,7 +2149,7 @@ Ld8/Xa1j32Llib/kJDfe6EzfP7uwHhe4qIa/t+rrYshU3z8ZtNlODWyDv7S9 j6moXN8/qnWffG/leb8uqH56a2zfP2Z8FewZbHO/jj22A0503z9FBhe3iOVp vxzctUwxfN8/eicGLLvlWb+3MvNUFYTfP6WcmpB3Onm+6bbjG/qL3z/CwXcl - "]]}, Annotation[#, "Charting`Private`Tag$4119#3"]& ], + "]]}, Annotation[#, "Charting`Private`Tag$41514#3"]& ], TagBox[{ Directive[ Opacity[1.], @@ -2172,7 +2200,7 @@ H4TFTAkFUy1bpeT5aYgnypWsq6LAsdi134VKRE9N7qjZNVBQdVdLvUIcj7j7 VH26iwJzOfXys5QXihV5HE/ooSBcX86girJHBZabIi68ouD6tcyUENOwmn6n LbS0fgr+BY5ur6A= "]]}, - Annotation[#, "Charting`Private`Tag$4119#4"]& ], + Annotation[#, "Charting`Private`Tag$41514#4"]& ], TagBox[{ Directive[ Opacity[1.], @@ -2208,98 +2236,43 @@ st8/Xa1j32Llib9xYHpnNczfP7uwHhe4qIa/jJodrq7S3z8ZtNlODWyDv2x7 dvgn2d8/qnWffG/leb84kiqWGubfP2Z8FewZbHO/EXZ26JPs3z9FBhe3iOVp v+dcWTwN898/eicGLLvlWb8JnEuRhvnfP6WcmpB3Onm+b4jF5v//3z8UuWtE - "]]}, Annotation[#, "Charting`Private`Tag$4119#5"]& ], + "]]}, Annotation[#, "Charting`Private`Tag$41514#5"]& ], TagBox[{ Directive[ Opacity[1.], RGBColor[0.772079, 0.431554, 0.102387], AbsoluteThickness[1.6]], LineBox[CompressedData[" -1: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 - "]]}, - Annotation[#, "Charting`Private`Tag$4119#6"]& ], +1:eJwB4QQe+yFib1JlAgAAAE0AAAACAAAAMbdmtbFrEsDhlgzCVnlzP8JuOnU/ +ahLAEjz06eqDcz9SJg41zWgSwOn0yrGEjnM/dJW1tOhlEsAM4M0syaNzP7hz +BLQfYBLAWLEwIZbOcz8+MKKyjVQSwMB2MZBCJXQ/Sqndr2k9EsDRiv42+tZ0 +P2KbVKohDxLA1unh2I5Mdj+Xd2pwyKoRwOdldNFu0Hk/CK4qmBVNEcAZfysK +l5R9P/gdv0Y58RDAQzGNcfbkgD/rhXvGk40QwG5ylnI5gYM/Gkjip5QwEMDH +3P44K0qGP5oE4rSYlw/AkqCKWUy9iT/+66cntdEOwHoQw6H5no0/2ofCXR4Z +DsDJq0M11d6QP70TLTb1UA3AcWuzxTRpkz8YVOzRGJYMwCVxrEcUG5Y/cAdU +e+neC8CoFvDWDxaZP9CqC8cnGAvAPtglAxm+nD+oAhjWsl4KwEVYrTcmTKA/ +iEp0h6uVCcC/VttL8KaiP2UFeUZR0AjAG72Ycx1DpT+6dNLIQxgIwEF/1bFb +/ac/FtR77aNQB8AH8l8Mb0yrP+vnedVQlgbA8yURQsq+rj/H68dfa8wFwHjf +eNqwc7E/oGK+9zIGBcBIYz0agbmzP/GNCVNHTQTAki81LvYQtj9KqaRQyYQD +wE4oKLyt3Lg/G3mUEZjJAsBEQppxG7q7P+m7LOATEgLARk1PIx7Jvj+/7hRR +/UoBwBSgpBl5MsE/DdZRhTORAMDukLUTcwXDP8Ravbeuj/+/+NNKtXwnxT9o +7yeAUAT+v+YuwuxGaMc//Ow7z4uT/L+MVntDeqbJP5/K72KiA/u/mQr4+vw7 +zD8yEU19Uo75v+lGOUrOyM4/wD37slwg+L+GwGruJ7TQP11KSS1Ck/a/f7Pv +/wIx0j/qv0AuwSD1vznII8SQotM/hhXYcxuP879z6p2O80DVPxLUGEAPGPK/ +SzL1UAXP1j+ZeKonXajwv4mujQHdXdg/Xvq3pwwz7r9mB58H3hXaP2rVbQ2S +Suu/CGadmEe12z+UcGP8zSPov8n6TlQget0/s9f6Ib4L5b9hz6f05TbfP7MQ +5VThKOK/IZThBd9p4D+hEx4idg/ev98UvLRqR+E/nakXtY832L9uEopiGxPi +P4LXVLURfdK/hGXCTfrW4j9ICyOQAYzIv5TIiB1XpuM/GV7P1Vflub88Dd8b +SGLkP4S+xnzCfbm/p3r1J4Jl5D/wHr4jLRa5v1kwxry7aOQ/yN+scQJHuL9G +v9d/LW/kP3dhig2tqLa/s++MZgt85D/VZEVFAmyzv/zgSo2wleQ/I9d2aVnl +qb93TGIAn8jkP/qXZbcuFqm/Xww+ysnL5D/SWFQFBEeov2dMKBf0zuQ/gdox +oa6opr8M+Xw5R9XkP9/d7NgDbKO/yDKAm+fh5D82ycWQXOWZvxmoNrAQ++Q/ +5UqjLAdHmL9ywjaXM/7kP5TMgMixqJa/lZip/lUB5T/yzzsAB2yTvxDRRU6Z +B+U/Xa1j32Llib9XU/XrGRTlP7uwHhe4qIa/HR4n0jgX5T8ZtNlODWyDv0tC +jDdXGuU/qnWffG/leb/jwlN/kiDlP2Z8FewZbHO/sRVoYa8j5T9FBhe3iOVp +v6iuE8LLJuU/eicGLLvlWb9V6S+h5ynlP6WcmpB3Onm+vUeW/gIt5T8LzWqa + + "]]}, Annotation[#, "Charting`Private`Tag$41514#6"]& ], TagBox[{ Directive[ Opacity[1.], @@ -2335,7 +2308,7 @@ ue8/Xa1j32Llib8nipYkDdHvP7uwHhe4qIa/L4XBtufW7z8ZtNlODWyDv36b o1zD3O8/qnWffG/leb9RDFjkfejvP2Z8FewZbHO/YPePxlzu7z9FBhe3iOVp v8oeSr089O8/eicGLLvlWb80YrnIHfrvP6WcmpB3Onm+nKoQ6f//7z+Q63WU - "]]}, Annotation[#, "Charting`Private`Tag$4119#7"]& ]}}, {}}, { + "]]}, Annotation[#, "Charting`Private`Tag$41514#7"]& ]}}, {}}, { DisplayFunction -> Identity, Method -> { "DefaultBoundaryStyle" -> Automatic, @@ -2350,8 +2323,8 @@ v8oeSr089O8/eicGLLvlWb80YrnIHfrvP6WcmpB3Onm+nKoQ6f//7z+Q63WU None}, DisplayFunction -> Identity, DisplayFunction -> Identity, Ticks -> {Quiet[ Charting`ScaledTicks[{Log, Exp}][#, #2, {6, 6}]]& , Automatic}, - AxesOrigin -> {-4.605170092005026, 0}, - FrameTicks -> {{Automatic, Automatic}, {Quiet[ + AxesOrigin -> {-4.605170092005026, 0}, FrameTicks -> {{Automatic, + Charting`ScaledFrameTicks[{Identity, Identity}]}, {Quiet[ Charting`ScaledTicks[{Log, Exp}][#, #2, {6, 6}]]& , Charting`ScaledFrameTicks[{Log, Exp}]}}, GridLines -> {None, None}, DisplayFunction -> Identity, PlotRangePadding -> {{ @@ -2371,9 +2344,9 @@ v8oeSr089O8/eicGLLvlWb80YrnIHfrvP6WcmpB3Onm+nKoQ6f//7z+Q63WU "placement" -> {"x" -> "All", "y" -> "None"}}}}, "DefaultMeshStyle" -> AbsolutePointSize[6], "ScalingFunctions" -> None, "ClippingRange" -> {{{-4.605170092005026, -9.398306502016511*^-8}, { - 0.00020432461576227714`, + 0.0011304658185601814`, 0.9999999572804259}}, {{-4.605170092005026, -9.398306502016511*^-8}, { - 0.00020432461576227714`, 0.9999999572804259}}}}, DisplayFunction -> + 0.0011304658185601814`, 0.9999999572804259}}}}, DisplayFunction -> Identity, AspectRatio -> NCache[GoldenRatio^(-1), 0.6180339887498948], Axes -> {True, True}, AxesLabel -> {None, None}, AxesOrigin -> {0, 0}, CoordinatesToolOptions -> {"DisplayFunction" -> ({ @@ -2403,12 +2376,11 @@ v8oeSr089O8/eicGLLvlWb80YrnIHfrvP6WcmpB3Onm+nKoQ6f//7z+Q63WU Scaled[0.02], Scaled[0.02]}, { Scaled[0.02], - Scaled[0.02]}}, Ticks -> {Automatic, Automatic}}], - FormBox[ + Scaled[0.02]}}, Ticks -> {Automatic, Automatic}}],FormBox[ FormBox[ TemplateBox[{ "\"ACES\"", "\"Unreal\"", "\"Uncharted 2\"", "\"FilmicALU\"", - "\"Reinhard\"", "\"Siragusano-Smith\"", "\"None\""}, "LineLegend", + "\"Reinhard\"", "\"Filament\"", "\"None\""}, "LineLegend", DisplayFunction -> (FormBox[ StyleBox[ StyleBox[ @@ -2973,502 +2945,506 @@ v8oeSr089O8/eicGLLvlWb80YrnIHfrvP6WcmpB3Onm+nKoQ6f//7z+Q63WU 3.8282142008868*^9, {3.828214234665811*^9, 3.828214279564385*^9}, 3.828215966170525*^9, 3.828216048945711*^9, 3.828216333521703*^9, 3.828219573265332*^9, 3.82907472379275*^9, 3.829075127596117*^9, - 3.835447201356091*^9}, - CellLabel->"Out[31]=",ExpressionUUID->"ff04d788-1bb5-49c3-ab5f-f3b4468aaf5b"], + 3.8354472012332478`*^9, 3.851105420638258*^9}, + CellLabel->"Out[56]=",ExpressionUUID->"ef97bf93-cf17-45b5-89fa-a2841a6cb5a8"], Cell[BoxData[ - TemplateBox[{ - GraphicsBox[{{{{}, {}, + TemplateBox[{GraphicsBox[{{{{}, {}, TagBox[{ Directive[ Opacity[1.], RGBColor[0.368417, 0.506779, 0.709798], AbsoluteThickness[1.6]], LineBox[CompressedData[" -1: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 -L34Tt5M3V+Has0oPHR9Wwf8AaJWeuA== +1:eJwVlnc01o8Xx+29nmmP5/EYSVIkwufaiZBVoiKppEJWpUSFIjsyiqxQyNaS +bhokolChhBCiohQKv+/vr/d5nfO69/51z3kzPP0c9nOwsbHVsLOx/T+PCzmx +cnhU8G665A7vpccE31OVkdXcKujRVE8LEGgiMk79LbjHqYI8NmZbosSbiPuT ++Yo9bCpoEFWp1rKuiVhumWEK/VVG/ecHMvBAExEZlcgI/a6MbJZ1BufeNBFJ +y61y298r4/I0W0Fo1ROi5IexpMgtZbywxsBkKOEZIWnpWnu6RBnVpNeXv7n+ +jIi9HmD3tUgZIWbLkdeVz4jDtgVRLQXKSE+dFPvz5hmhUcY1ey5bGQU4VWVG +xJ8TdQebW/8kKWNx5vN028LnRNPA1tNDx5UxpHS5o6q5mfjYtuNTrbkyvmhW +mdHSbyXq239TCDNljKCsdg2ybSWSXqVZNpsoo1jEkfGmva2EWWdXVS8oo6ri +7pzYi61EWZdd5LKeMkaaJaQ9e9tKhPZtXmWpoYyeWYl/ZoNfEvTxjYH9dGUU +Pqi25/ijNsKWU4KXY1wJnw1MuOee7SBEnN+kpYwpYRz518+tGR3Eq6I4luKo +Eh6NUP/EX9FB2Fixm5gNK2H5kyOTtR86iK3Jk6ejPyjh4nRIkrdMJ2El/3BW +4LUSOkSYWdJDOwkLfc8Byn0ltMk6UzGs/5ogAsvqlOKUsCtz1/yNvjfEgFep +fHqsEp7fGRzgPvGGOLP9VgxfjBI+XbA5xpp/QzzUK9n9NUoJlb38GK9pXYQB +WyF3VbgSnmndYVVv30Xox191NghUwsCMwvY7L7sI3eLYOQdXJWzsqE/tbekm +1vV7b4hQVcJLn44qPZ54S2wK+aYrpKKE2zLfnAtefEuYkIIMMpSUUMZUkaoj ++I5w2hxuWslUQtdnbr/71d8RITVp9oMySphc5VX3wP8d0XCp6agRSQkt+27S +uhbfEZYGMkVsiyyMTnpwPl+il/DI7qCffcnClN2WgSOn+4kt7zPnpl+wUDVw +kPt6Sj+xnuLV7drCwj3HNnX6lPQTnDHzydrPWCjZSTXV6e4nioIYwuONLBQs +mFXMUf9ATFsHctpVs3CiPECwcfADEbpI/yGTycKFnMik/O0DxOUde17cPcBC +o4fhqoqNg4TnZ1OONftZuGqsRkXx5SCh6bdKP28fCzk4LliseT9IvIr+VRbj +wcK3U80cHrODhEB9bJKrKwuD91k4G6kMERHUOzv+2bAwSqU48GfKEHHktegY +bGCh8+VtIbsDhglz68cczZwsrJ1tbVRyHCFOR6zpceZgYVK0p/bg3hGiti6z +eISNhT/XfHO/4T9CsBQCtnIsK2L7vs3zdgkjBMcc84rhvCLqK4sJqrSOEJgT +uap2ShH9OF7UfjYdJQxmN2/L61FEF8mU9Z0wRmhlvso+VayI5ARxSUe3ccLK +N9z66w1FZBR/qw/1HSc8TTUXXAsV0eGkf3DV2XEiZTrZaVOeIrJ9K4/cWjJO +zBo7Cy5kKaKnZTNs+j1OVE72nwhJUESZtKyx5dQJQt1gwvFYsCKeuua7EDkw +SSh94hQ4aKqIupcm8UDhNFG0W99+xlgRtzEuBGvcnyaUPwRknDJSxIrYFeDp +nCZUe4eUkwwV8TwpzmLs7zSh1vXI9N5GRdQTtj1yyOkbodl8KkxQXRFvBWW1 +RvN9Jwwqfv6ooCriZjehvPvED8Ix/PO7+VEmXoy5vfvMqxnivq3OydgRJt65 +O7XWuX+GUJCLkZL5zMTu0QyHDeMzxLcGjd0wyEQ5h6xCEscscXHx5HBUHxMj +o//q6+vMEo3BYtPkDibezJKwc8mdJVYdMeRYc5eJKgai3V9P/SRWXNLV98Yy +sXpDbo+Ewxxh0aJxs+siE3+OsoW1ec4RcRufK1lcYOKS0zqH+MA5Qoo+J7c6 +koml7/UqddLmiA1dDqS5MCZ6WLp5reqdIw7biPy+eIyJ7GfMaRn7fhPvjKMe +VbowsVhOpvbLuT9EhVqgA5sKEycDbI+9m14g9pgme8crMfEgmd/nDdsiIbKr +4owUi4lZPQMH+imLhG/811vaDCbKRCxfEd+0SKj/2Md5SJqJKTkvF/QuLBK3 +6pxqX4syUXbxhpom6y9xw0iHXjDPwIgI238ah/4RV7cv9Jq3MlChx8Z0gbFC +qInuelnTwsB332do9zatEPebGxsYzQxMZgq3xjmuEH26kdeXnjBwWnZQ3D9q +hZCUFt1f95CBoYFRNNXJFSL9E+sHq4qBPDHFpHo3NrjsvY2HK4OBjgEBXz5q +ssOl0OL1TfsZqHvg9JHqfxzAFql0ZqMXA60cH+yNE+SEkPiCF2WeDFw2c5AK +kOIEz9zrHunuDGy7dO+vhy4n6D5PTziyk4EttXFx7UGcMEa6OEm3YaDzPobP +5HdOMLnpnX9Ym4Ev1O5+PfGdCxberSLTOBm4n6Z6WpvOC+9jNf4UsjNQzchj +ha7CC/WGWh+02RhotFwryKPLC0EFBkVOSwp4rYJrjN+VF7752m1K/aOAcTny +FTdzeGGEO9iTMqWAkaJj0g5qfPBKC6tJPQpoJ7jVIs6aHwoStzuKFCtg2MsF +mx93BMFz+Dol/IYCtoRm1iy3C4LChomu7wUKKJ8V/FtuRBBy+k45vc5VwBL/ +Brs0MSHIUMpzTs1SwEyj3b9dfYQg4cHXHVIJCmjzpd13B0MYQr9E7FIOVsDG +ceH6c9kiYA+l+wnT/3zyQL78hBic0D/16IWxAn4eMSI/mBOD3I3Wks5GCniU +U1Lag4MEP9Z+bTtsqIA/spPEm6VJkKCwWjtzowIO5/Qm89uRoI29lOPXagVU +lhJ2PHiHBBZPb12/SVXA0J2k6w8TyXA8xalmVZs8OtaIfS/xoEJ0a5gsxwt5 +JI5c71h3jApXOIov9D2Tx9QO5rGWs1SoDVhwu/RIHh8ufVGWL6DCrEMO13S1 +PKZPcVQEjlHBlzLuXJkpj+3LoZ+f+NFgf+qpeZ2D8phyV+O1SzwdHK/kG5px +yGPBba0Mgz8SoOHPsWv1ihye8V7tYiAoCQJWnqHkf3JoFDiUvlVeEnCJcWdw +Tg5fqo915GyWBI39eZphE3KYve59vWDmf752rmJ9pxx6cyevo4IU4OtsftXr +ctiaXWmxJUMa1IUzewQN5HAX990fj87JQa8ej4qqnhxOHnHwrs6Ug+gDgSfM +dOSwUe7Ow/pKORh6ZCMdpimHV2V/GMwNyMGVAI6931hyKD12do21gTywvT88 +1Sksh4cd0ni8F+WhO5/gvPJJFv0quH57NylAmO6IpsJ5WQwXGD3xz4cJk4V9 +usvhsjj/asJcNpQJLqTXRh9Oy2L8RwEO6xgmrJ98uC0jRBatNs6EtBczYfxq +ur+YjyzWB9Lz/EaZ4LRiVclmL4tDVtVxWvsUQe151dohOVm0yz+7JHaIBW8d +wzXy78vgrvk/W3jzlIFGOX+o7o4Mbq+sW4ipVganN9GFLbUy+PNtXb/UU2Xo +2pYg9f22DIraKXof/KIMnTbZPAYFMujeOMBWrqECrZsfDPRcksGWiC3Wc49U +oFH/TwL/bhlM3zKU1zOtCsWKft/9V6TxSPhIef/J1VAdeXMg5Z80xs1IZ48n +rIbG0c/ttQvSaKpLTuQpXA3dxS5l8z+l0ct8qT741WpgUzc5FDEujTfd7RqY +LHVw1aZ9TnwtjbrnSxaevlEHEfMHb28XSCMlt8o8w1ADgg/wNk5tlkau6YSk +xCJNMOJ9ua/TTBpz9051JNRqglBJAn+tsTR+NhhKyGrShPwJmvMpfWkUcw1o +eDegCZ1HWFP8a6XxtLzl2knxdaAWaCylQpfGaV/+Q6Fx62Aw/FTI3hEpfHqR +WZ8fvh6sM79rvI2QwjkrkRbxZG0Yj0Pe72FSuK5v8m1gnjZERyQP8p6SwnL1 +su6PVdqAB7VS9IKl8FGyzuv+N9qgrXN87tohKfTTGoKztA0g17X8YJ+DFDoK +a5eHZW+AWSGRLTOKUkjvUYnJvqsDmRHq+4SaJdHmz5ndVXJ68LsmenbsiSRS +DMODyrX1wOnL4NnHKIkhWgM2jVZ6IGKblhtyXxI/OdbnyB3Xg0jp5Y+D5ZLo +HOMq/6FTD3zrO1zqUiVRJZymvOfiJjCbCrDd4ymJCteL6Ap8BpAn3/5R110S +PQKGEgcVDGDZQeUoZZck/jsTWX9XzwDu3eu/1OIsieykNv/KwwagccHsxfot +kihaFrAXOw1Akkk349WUxDju3+9OXDeEbzvu6VUuSeC7z2vu8W4DEF9jFZa5 +KIHlayu3+h4GMOLox3N/JHBY8+2vb9EAl8v+bXaekUCxzMczRCOALrvR9sUR +CdTql9ouIm4E5289DzBrk8CtB+XZg2KMQGKpq/R9lgSu/ZUr+/yEMRi/8fr+ +OF0Ctc87NelcNAaf4rn1pakSGE7hsalPN4aH9uL3wxIkML7+qtNwvTF4Frm2 +MM9JYPL+HbPf54yhzG5o5MghCTzs3fT2e4gJGOd/k2XXlcAXdvrDC5GmYNAa +lyq/QQIrCVWGfpop6M6qCRLrJbCDr2DDpRumoGFycD5U/T9uaJd2eW4KUsOf +3vxSkMATaXEXi/jM4Bfj9YVxfgmMlq89qZhkBkV5NTMd/eLoMbZPRqTUHPJe +2Ht/ey+OYYKrKi0fmsO1me8DQm/FUfeZikJqhzlcNlZv29IpjrO11/p2/zKH +iKHCoidPxTGlyrGokbCAnYwrbnfKxXHS3t702VsLEMo78SwnXBx3Z7M2eFMt +oZkvd/bMaXHM+n17TbaaJZzzb5Z3PymOhiozD8aNLGEB6KHygeIoEBDA9vSo +JYwN1GjkHhDH4Rvb2RJaLAFlvqfn2YijUq/dSvS5LRCcsd+nUFocdQpqqO18 +1qDJFp8eKSGOicu5wvxMa5g6WPvUiyaOuSc/Utz1rcFzI6e8kuj/uRB3+lrD +tre5XTc4xPFzRNC3uB5rUKN+MCieoONLk4ga7ptbYSDRQfTWHTp+ULCeYuy3 +hcNWF/we1dLR/i/enA23hXmuho7uKjr6uHzN/5hlC+RQpaSVUjpKFsg84Hht +C5u95sWcc+mobl1XHkfYQaVuDpktho7hWfurd4lug/NDE7TtrnRUZFWPKi1u +A7FrciGHd9DxvYe5lB2/PeRsd3wb4UTH9jy66wUJe7j7suFKqS0dqxKlCKWN +9jBVlyjObkpH0VtFvhLB9rA9doNk2Wo6ppRKHP360x5Yx173LtDoGNSktTnS +zwFUtc7KcCzRcDNvsxguOcDZxClN5UUaBt23wzQRR+j7usPc6g8NJ1fXCJ6W +d4S4Qg3flBkacgiRHoUaO8IM7cMj5hgN5z/JXzOJdoT78zr7TDtouOuXRIYQ +xQm2Nk7djMyj4fIv43jSJme4IeXSWJJDwxBqwmN5G2dYDnnypu0qDat2nOU2 +9nCGyrVZf6lXaNidxyI9jHYGWt5mmxuxNJQZOpiQ3e0MA+fzfzwLpKFDz1YN +StB28NvispHXgoYWHEOTvk074OLoVo5pExqGlum9GurdAflnjdvfAA1FW6Ui +fGd2QPc9tX05ejTkOeHT3qPgArpqSwkb1tCQPejfxU8RLsAhmD+2j0rDVA/x +kb/mO+FK29c0HKbi6BMxKbNRV6jwHvQo+kTFd3M9i5psbtDC1bM67gMVb36+ +N6Uv7QZ/DRof73hLxZzXhhll9m7gUZ40/e0FFY8GJqksPXIDtYQN5rJVVKwM +/J52On8XNNqF/zoZTsWGANd7AhF74CZxdjb1NBWXDcn8M9f2QOqa8z8qTlLx +tkvs0Ny9PeAjeGFqNJCKLffv3Hf7tQfEmxNG7L2paNsQu3DVxx38iZzuVfZU +1J7ot76g4QHMNQ9re5lUJO84lOO5Zi8Iyzyq/iVPReMeac9hw70wL/C4UlSW +iu5Bi+2Btnvh1fjTMnNxKn56Lzzz0m8vnCpsK6wSpKL58Ylyx5q90C39ITXm +FwWFkb+gy9ATogT+Buk9p6C0hgm35t59oKkXvYr+hILkGomhG8H74MNB0sDs +IwoarNn0XCt2H6x/prK57B4Ff9DSVDJq9sFAuJOkfDkFq5NMb0fzeoHuXPlD +rlQKVpxYTKHXesHUJw+ezr0U9Le4MTmucADSRabul+2hYHCe4Msc3QNgYnjc +L8aNgttniof8tx2AzKz49ybOFORp1jb1iTgAFs73b9VZ/jffazRQN3QAclsp +dlfXUvChnb7N65sHwanuefqBJTJu38n0U7M/BK913MNPLJJRIc6k6qPPIbC9 +++dA7B8ybloyf18eeQgsH6jq3J4hI4c1a++Nu4fA8HFs99woGVs8WYZzDB9Q +brclRb0iIyV6d8nGRR+YH3kbW3CdjDxHi6g2TUf+6wd+AXXXyPhMQnnDyKcj +MPuF17U5k4yn3Ed/Xls6AlOTuqu+XiZj8STnj0C9ozD0I6t5/UUyOtLUDjVU +H4XWf3u4mvzJiF5thh9KfeEq9UvYoDEZyUY5d57u8oeRjfWS7EBG6hiV58xR +f9Bwi6pjGJDxhHiEotUZf8A8xWlPHTJq3PVRks79z9fw2DWqRsZD36Sf5I/4 +g/qW3k2TFDLGtrnf3nXsGDwMe/nn5ygJP/49G6SYFQA8eVmXqZ9JuG3B1der +PAC2PT20dsMgCad1P8MDDIBhAb6DIX0kDPnC23Xry39+htnb+VckrPPovJ6q +Ewi21Q9rl+6Q0E43wO/W+0D4NFZxjDeWhK/cY/PzNYJhfAvf04loEtLyiw3q +LYJhtsyD3naehLtfWtd+2RMMXIGkB0lhJPRXWf2+MDEYVFcCuKSOkXCtRbxx +3GwwBIjrpKu7kHD+6syj9Y0hwGXZ0GCvTMLJ9IupHL4nQLiUKqqtSELVVYs7 +GLEngC5ydC9dgYSUFB3YV3QCVnXL8vRLklDf+5qm/acTYOsRbuclTMLReK6q +PoeTkH7CZDjklxiyJ54/024UCmo3W3ivNYmh7jhbW4zeaeB214iveSSGXr2m +AxtdTsMgNZX8skEMtXh+fuc/fhquRLjLLtaLIddQA0rVnQYOl9/rXUrF0GD5 +0vTP9WHQz8PaQ00Vwz0e0FG64QzEeYXXxu0Xw9mJ7UP66hFwUGpUr9BTDDe/ +vG21YhgBJp1Wjx64i6FTvk/hO7sImNent07uFMMaUk5Cc2AEeFHKP1naiiGF +FixY3BABBk19AtwbxfDkZEvIKYezMC2vszeM77/710QXdRLPwUWaUvZ2bjGM +lenvibpxDhQFqb1rOcTQxWW39cyDc1Az9Tnv6R9RND+nnik3cQ7iMt3OJgyI +4om87qG3ZucBfloZMctEsdx8ddcn9kgoLFrVuGWzKLKctIt8LkdBXzKvzj8j +UYxkHH6RUR4FpLDR8tubRDG7SDh5vDkKzjjmZVM0RPGttUvD9L8o2MEmGfaR +KorrZOW2X/aOBgFXPoNjwyIoULV1OMDiAvgJf7mXESaCMUXD2i9kY4AwWJ/l +fVwE2Q2/Df3SjwGhw2GhusdE8PZgzQ0L1xgoaaHov/cSQSHdjnW702Ng8LxR +g/hWETQ021nqRokFh8XMxitSIqg1FerygnwJdL5sfZJ6Rxh3Vd7Y3K4ZD1z0 +jAKvKmEMXOtnKusUD2/MPp/XLhVGTfPalszj8eCXf9KsO0cYhXrnbCQfxUPJ +7qJnlGhhNNtJ36+7LQGkuleaU5yEkflOvEj1VCIsY3Vb0owQbimL/V6/mAT+ +aRKUl1+F8PrZ9Jq/wskwfOjMTu4xIQx4bEPewUiG52SrsZN9Qkg2ced1sEyG +BK+hFc8nQngl7mypW3oyyPGJaW1IE0KvrzMiPXopQNgdzezVFcKed6n+8smX +oVKx6xNFSwgdZhMfHi6+DMx5XWXbNUIYqL2N/ubh5f/+m6umiSGEIb2XPaYn +L8Or2attpQJCKN93LXWvRSrsudK6EvZBEPskzzx05UiDsI8qB5gRgnhaoGCt +d+IV8F+XKzl4UhC5zh/v2lN6BTyjJNqzAwXx2trgshPNV8BiDb+25EFBLKi4 +XURnSweRsEl2MVtBjLt/15MtMB1yZcqzl6QFMbgl4H6lewY8dVvf8/6OAMZl +rXQ+cM6C+opbF69UCaCkWg2Xb0gWlHAqGjiVCqAbi4NnS3oWxJdSCjpyBDDA +il0ztDcLtv/95f88SgCtE7T2vnK/CuNZd4RqHQXw2D3eF95B10Coz8As8Ts/ +ZvJFBD5pzIH8ro7ddyb48Yq8Z5HXcA7otnse//SZH+u+6Axr8FwHL7x4a+17 +fnRyV2CC7XVoKOoW60R+XApwv+g4eB2OBB75KJbCj93qA+Z60bnQJnQtJEWb +H1Mcte4NN+WBJ8/apPsa/Pi5zULV710ezK88vjms+t8+yaBZ8lQesH5++bBe +lh9ZSoF/cmn5cLpXy7SLmx83EQr7eg/lg3rRS1HqOz5MHJ+Z6aQXwCX4W5J2 +kg81hWD8wvlCuHv6r1p/IB8q5o5/uJNVCGP3/pYp+PJhBGMrsFcXgon2v8pS +Tz7sI6ZvDw4WwqLq0t3H1nx4e+7SeDfcAB/ySsu0LB9KrOM6WsJRBJajHBPm +TbzIqV33tfRaMYQwOQ9fauBFB8rGBb07xVDozjndWc+LEjp62SOvi4Gtj3PG +rZQXSa3HSEm8JXDnFdfCsVRefK4QFGsSVALKd3n4cw7woud40pi6403guiSw +6rcAL77STOfrUimF6s2yEo+4ePHPBvPLDZal4MGlyXtxmQfP8XQ1vDxUCg1h +zqOSszxIu9R20q2sFIL9c/MMe3mQL20+1EyrDMacdaSiinmwxFrtqZtlObQo +eApSzXhwzMfN/GpSBYR8DFr8YMiDvsXNvGfqK4CVdWHixkYeLC32mYz9UAER +lPLmjat5UKEk9XUytRL0eObPu5F5MM9sJVDNsxJufY3/lz/IjcTKyYjD7FUQ +V39vel0YN+okNVje3lYNAxJfTPYf50b8+nz+h1c1aJ6iZmQc40aG4wUOy5PV +0A1+Jiv7uXGY57K7Vn41rDFyIh/Yzo1MaKwY7K2G/wHjbr3c "]]}, - Annotation[#, "Charting`Private`Tag$4363#1"]& ], + Annotation[#, "Charting`Private`Tag$41965#1"]& ], TagBox[{ Directive[ Opacity[1.], RGBColor[0.880722, 0.611041, 0.142051], AbsoluteThickness[1.6]], LineBox[CompressedData[" -1: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 +1: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 "]]}, - Annotation[#, "Charting`Private`Tag$4363#2"]& ], + Annotation[#, "Charting`Private`Tag$41965#2"]& ], TagBox[{ Directive[ Opacity[1.], RGBColor[0.560181, 0.691569, 0.194885], AbsoluteThickness[1.6]], LineBox[CompressedData[" -1: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 -guJTh6cP4H9CYBJ9 +1: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 +Qio03mUMVMH/ADe0WQo= "]]}, - Annotation[#, "Charting`Private`Tag$4363#3"]& ], + Annotation[#, "Charting`Private`Tag$41965#3"]& ], TagBox[{ Directive[ Opacity[1.], @@ -3488,482 +3464,403 @@ f8fpPdNW+yH4b7b2izM6I/hhXaaSa7QR/McuWZUHNRD8gv/zbl5TRfC7Sjhm MSog+OL6tr/EZBH8JS8Lo3SkEPxZrtMP/ZZA8AHrEMxY "]], LineBox[CompressedData[" -1:eJwVVnk0lY3XlYz3XtzheWQsUZIKFVLpOTKHFxVRVEKRt1+mCtVNkkgokhAy -VTIUpSTiFKl0o1fKPJQxU4go4fP9cdb+45y119l7r7PWWe7sueswPx8f37eF -+n98kEQfm2JLYv5XhqykhAzlFhWw9SZdEkfWRnZ7iO+lJkwf3MoVkcSd3Pvv -cu8dpoIWd828FJREJSZVMlPtRYmXLXFs4JdEWqpbpd0Wf+qWv0Xp0DyJZR6l -+DT+PKWyMUiOf5bERek81paOECro6pCG8h8SB2MTyfAvV6jmQTsjsykSJ4dO -Lo+xjKY2mlbsPT5BYp2BYZWV3A0qIlPteMwYiUKHNgatsUugevgSLzwdIXEF -M2ykeFEKRe0XvNk8SKLWsf17tl1No8bI1nLFXhIZiWuOyTveo8x8TOqNu0ik -Vyf8nkjKpjJrHvV7dJK4IWDW5KbsA2pWdelsVBuJ5rFGstFD+dSe0Musx80k -2iaWpNkNPqIedk2sbGgg0dInnZZNe0KJ6Dltmakn8bZZlT/LvIh6Pq3tYlBL -Yp9acZ7u+lKKY5vu58Zb6P/I/ENvLKOOFYhFXHlHYgC/Ud3i/5Ba6tFd+KmC -xAP5Thvefqyg/Kqs3k0hibJ76nqYx15THxVL2mTLSNxJxmhMSL+hgluihVyf -kai8/cGrKw+qqdZNszJhT0iMpO4r4g0epR3rrp77iMTYX28drl6rofotwH4i -l8SlXbfPzDn9R1mUDd2/mEaiwjv0Dxb7Qt2RsS/LSiGxwkEokX93AzV3qqKO -d4vEjpoj8v9kNFL56okzRByJzsEqphWuLRSZZvLPnXASPxt5BNkVdVLHZx8d -ehe6oOf896lFoV+pt3uXnhq+SOJN/5Y70Y7fqNPsyRStwAU//LZPpkp3U+3B -6aOvfUlUEvuWbdDWR23qFBMc8CLRoCNysKWtn7qmGyAtfpzErL9HmS3d3yn9 -SSv9Pe4k3uUzKrcSGqLuHpm93utI4qd/jTu9LEap+VfuWbR9JOrQeEPQN0rZ -L6svVbMjMehcRevFkDGK1pjdc2onid1jKhoxvHHKc4f9JmFjEvFj6WRWyCQV -1mPBP6xPot6Bz2vaLH5R6UHbP9TBgv6vFd9PSk1R9cWqLimbSeyU/hCz6MU0 -paM6G6W1jsTE4rqrpbp/qZ1VY3tlVUnc4TORbqUyS3k4965YtIpEjbHWijSp -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 -QVwXqlxiU1IA/wdnq7a6 +1:eJwVVnk0lY3XLRmv6Q7PI2OJEipUSKXnyJTwoiKKEIq8/TJVqG6SREKRhJCp +kilTSYlThqIbvVLmoYyZoogSPt8fZ+0/zll7nb33OmudNU4e+49xLVu2rH+p +/h/zEgUnZ5himP9FSEpMVJJyjfTfeUdQDMc3RvS5ixyipozy7ubwi+E+9qPa +nIfHqMAVvXOveMRQnk69mKvzpETKV9o1c4khLcW1ynqHH3XXz7RsdJHEcvcy +fBp3iVLcGijNNU/i8jQOY0d3MBV4Y1RN4Q+JIzEJZNjn61TbiLWB8QyJ06Nn +1kSbRVFbjSoPnZoisVFPv8Zc+jYVnqFyKnqSRN6jWwM3WMdT/csSLj8dJ3Et +PXS8dHkyRR3hudM2QqLGySMHd91IpSbJjgq5ARKFEjaclLF7SBl772ky7CVR +sC7+91RiFpVRXzjk3kPiFv/5PXek8qh55VXzkZ0kmsQYSEWN5lMHQ64xitpI +tEp4kWo9Ukg97p1a19xMopl3Gi2L9oTi13HcMddE4j3jGj+GSQn1fFbTWa+B +xEGV0lztzWUUyyrN15Wz1P+e8UewpZw6WSAcfr2WRH8ug8YV/yG1yr2v+GMl +ifb5jlvefqikfGvMa2eQRKmDjf30k9XUB7kXnVLlJO4jo9WmJN5QQe1RvC7P +SFTYnff6el4d1bFtXjL0CYkR1CM5vM2hNGPcVHMKSYz59db2xs16asgUbKZy +SFzVe+/8guN/lGn56KMrqSTK1qJfkPBn6r6kTXlmMomVtrwJXAeaqYWzlY2c +uyR21x+X+Se9hcpXTZgjYkl0ClI0qnRpp8jUPf/cDyPxk4F7oHVJD3VqvvBo +bciSnkvfZpaHfKHeHlp1duwKiXf82u9H2X2lzjGnkzUClvzw3T2dItFHdQWl +TVT7kCgv/DVLr3OQ2tYjzDPsSaJed8RIe+cQdVPbX0LkFImZf0/Q2/u+UbrT +5roH3Uh8sMygwpx3lHpwfP7WgB2JH/817PE0naAWX7tl0g6TqEXjjMLgBGWz +uqlMxZrEwIuVHVeCJylaS1b/2X0k9k0qqkVzflAee2228RmSiB/KpjODp6nQ +flOuMV0Sdew/beg0/UWlBe5+3whL+r9UfjsjPkM1lSo7J28nsUfiffTyl7OU +lvJ8pMYmEhNKG2+Uaf+l9tVMHpJSJnGv91SaueI85e40sHb5ehLVJjsqU8UX +qKSEhuccWRLdnmmVtaxYBlyCaQPOBIliegNjTnQukHoYW7CXsZSX3QLBiecC +db3rF1RFSISQa+8q160A1/OnmXN8JDZs7Plz1oAbOCOGVPQcgWmtfg9iM3gh +ljNyG78S2Gh8rnP9oCA8dutxfNBN4Hy2x5RdohC85f60IbyDQL1fpfFXLIVh +Trv8lfVnAlf8lnxW/V4EHHNvjo3XErgu4YWyyw86KEdqGMgUELjaWK/6sBUB +esrK9BV5BArPlFyXqiXArmZV+1AWgb/ZvAvXKRJuLPB5FWcQqDawcV2YihhM +nWpLNIkj0IL929hRXhzKzQOm/AMINP21qzvLWRoeUYE/Yi4Q6MlwnHAYloaY +TUETj/0JdEz2XY/eMuAuGDLa70NgqMDmtMjQVbDyTWTfPjcCSzVOn7Z3kQVP +KrlJaR+B/jLMB5INcmC7KaVR34zAvkNE8Q1ZeTCUTvvgYELgk7XO56e85UH6 +z33ObQMCH1rw02Ik10Ltk9yq5TsIPJvp5mF7Zh3IbXpZ3CpHoG6Z9nDMYUUQ +lq4onFpNoGySIlfzC0WYpb3KF5Uh0NpTfv26VUpQP1SVY7Byyf/T+vOj/Upw +PoOTUSBI4K/ZFWscLm+AJqmOmGtTLEx1ObDPd0IFgmlzp7fXsJD93zSZYb0V +1LZfVRKrZOHsRRG12JSt0OHK6PpRwcKZzWLHTYa3wpbq9XtySlk4mpKdyX1J +HboCLCVW57Lw5/MLl1uLNUBrOvcldwwLGWVcl40NtKBXfrv3l5ssnIuiha28 +qwWR+6sUyiNYKM15GF0+qQX9j9tu+Iaw0K9nNDUldTtEu/M5DZ9jIZG0KNQt +uBNGux15Pxxl4YRtr16yIAV3REaf59izsOCCLHelBwW6u3w9rtmy0F2NSG5r +oiA+IaJF14qFnWLNHUOpAIZWz7OeGLHQ8mzYk+Z0HUipY5nfVWXh3zLr07lH +dcHySc2d4/NMfF7WwXyoZwj/aToE+P1horPd8KDkZUMwezZzPGyGiVxdhov0 +V4Zg9EJRM2+SifqvJIRCdPbArldhTdP9TJzuMPbONzAChfdmjOB6JrpNifNm +nTCGdLPB2dh3TOTusLiVkm8Msh8CejLfMvHqwgZFhVljkPyY/5jzmonKh7Y/ +/xJqAqKtTDOihImnnkuPby4whdm+z2Hp95go2nJBblrGHM4c9/B+ksjEzOmE +zN2nzOHHIN/hN/FM5JX7/k25whxGh7WURm4t7TdwLPbfzRbwZSLhzZZQJlrs +VBhqbrGAur/23K89mdjnJNYutm0/3CUG2T27mZjbz2H5q1tB37anEsuBielH +pWYsjluBim3wkzXaTFz+p6/ycpwVYKr8mJMmE9sybnH/ml+aV3G061de4jMv +y1R8fxA27m3dMcxiogBrx/6NbBt4yX4387OfgZPTj3w019sBb2rCLaKXgb+b +RAp0j9mBRdUJVY0eBnbI2K4tTbeDrzR+17NtDGwo4m8dWHMEeOP0P8/WMzDI +S6fBUt4ezApfFs+XMLDZaCD7vIwjdA889uILY+DnsXaXbEcnGNrLX/XtKgNT +9uuQsmFO8CPHUYwTxEBLlYXVXUVOwO3DeHGTzUAn696mbfzOoLjozS3pxcDu +9WT1yWJn8F6peWejDQMVfO5bakodA26jsrJ9Cgy8x/9FSnmdGwhnE6Lq8gy0 +mjdLumXjBmIi/zsqJsvAgp1HXh0IdwOlJhnedgkGmoiTT1un3MDMMcDcRZiB +y3Uq6vjrTsAdP92vZ6foeL6c671N8L+g/OgtX+JrOi6LcFFdpucBPA4qEUUV +dKTHJLsOsT2gh4hhviujL/0XKhJKpR4Qe8lB5s9TOh7aVPT6sLgncNn82mKT +vcRHPzXBFe0J7bxr7YkYOpJdV20nIrwg3CWgOPwYHd+MXXj17JEPuEr2b89w +ouOHlyOlUu99QPeDccULBzo+T9lYUjDhA7M7xeqGD9HR/dPHDF+t0+DCyu02 +MqNjs6zbHa+606D9uo3Gs42OFhZKVRazZ2BsteZRNj8dn37Xm7Bj+0EouS7p +IA8d69+6H5bJ9gN5QaJVlYuO3j3KAXKtflA02ptaNSOK7U6dWn81/CE83jYw +sksUnTN+7vr1wx/gp7GOXI4ovttjMGxy7jxkPFAq37tHFLeonf5TjRehLYpP +86+OKDrQ5gpezlwEBrs/N2+HKCa9VkjnVg2AiwdSk1gqojib5H0rMDkArJdJ +sDsJUQxWeJGfonMJaIf5tb2+iqBha1Aia0MgeAgPlsaxRRDsRht4ZYOA0t6S +4OYrglNXErrUdYJA6F/2OS0vERw6Np8Z6RgEmW9ZO1tcRPDnjnvra1KDoCdI +p2ylqQi2XTVr3KtwBfb/iS+PlRTBqnHSh7MtGDQHTStjSoTRckXuuMDFEOAW +i0t3KRBGQb4AXVpGCDTq9wapZwtj0uQ6Pe26EPBI89dvShZG3WWaDM2VoZB5 +5EE166owjm9f2H+1KBQkmxbfRFsK46XQfNfq6WuwgIWcm5NCqD3CvSCfFg6e +t8VZ70aEUDf/SdGLd+Hw9cTFQzwDQmjGid92ZTocapjGA/5tQrhG4Yh15t4I +iHT5suhUKYTpJZdLd09FwCp++laN20J4c3rPie79N4Ay/198q5YQ9qlYrgyQ +ioJ8+Y/drK1CSCzYZF/eGgVys1oKZpuE0P9ilVeqSdTSfXMXvV4jhDZ2Kj/U +LkRB/Y+7nGyaEG5OsDRO6IoC+9i6RXaHIFY3BVZlZUYDu3P9cblLghj67Wiw +2/4Y8NycItHjL4i/C4pqLT1jwClY/H2SjyAGOtwNcYmMAcNNAuoSroKoWtc7 +Ol4XAyLs4eV0M0GMMgmrLTS4DSnSuUnzUoJYj3x2TN1YqLLd8qmlhIa92xYt +6dZx8PRxVmhsAQ0FPjbqPvKPg8wV8tqW2TQcC1rMOpEYBxHZrPSGZBp+bSBO +Hf0aBwfnpjxrgmkoeqXfVs0zHoYSSoSKD9CwYj5owO1WAgi1aevf+C6A4Qz1 +JtXfiZD2seFIyTcBvCDwRvrAqiTQeu/k290rgM6pR30T9ZLABUOzVFsEUNyv +pTE1MgnKHjTRP6AA7vNv0TinkAwnfU520qMF0PS3jOO5I/eAI5R4NlpdAPMr +jd5UTKXAdZjLvO3Pj7fOco3OVKTDswtzyu0+/JjOlm3Wb06HgdK5HNlT/KjV +VBzycDwddNX/5mc78ePuXiOvPJkM+KM4/+yVCT9Sb96PV17IAHfm4tsxGX4M +tFfploD7YNTP9c3gNR82Jlwxa2p6ANzXaUq/aHyYJvp9XdKeLCjcIyNewc2H +8k5n7A1ds8CRW40vdIEXWerTzsIhWVDGtuqX+MGLnSyPVdM1WXDGMyV1Vysv +igWOmG4yyoYBK03J4Ie8KJHp4GZhkQNvZZ0ECX1efNVQP93tmwfhT0vHNrN5 +MLC4Pok3pgC6xAd1j/ny4MA/6n4hDwpA7TwRF+fFgzLFIztWlhZAE3joLh7j +QdfdG287dxWAVO3aOxwzHsxi2DvkKxVCVvsNHVdZHjwfo2EpW1kIXYW+Q9US +PCgH5Y97Wgvh/wDJS80c "]]}, - Annotation[#, "Charting`Private`Tag$4363#4"]& ], + Annotation[#, "Charting`Private`Tag$41965#4"]& ], TagBox[{ Directive[ Opacity[1.], RGBColor[0.528488, 0.470624, 0.701351], AbsoluteThickness[1.6]], LineBox[CompressedData[" -1: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 -6g== - "]]}, Annotation[#, "Charting`Private`Tag$4363#5"]& ], +1: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 +1A== + "]]}, Annotation[#, "Charting`Private`Tag$41965#5"]& ], TagBox[{ Directive[ Opacity[1.], RGBColor[0.772079, 0.431554, 0.102387], AbsoluteThickness[1.6]], LineBox[CompressedData[" -1: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 -hFAlblPD7UncooW9EdKNVo2V+P8AubECYQ== +1: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 +Z65NLsc6zzbY/xxjAjKuLxnuK8f/A1rqhVs= "]]}, - Annotation[#, "Charting`Private`Tag$4363#6"]& ], + Annotation[#, "Charting`Private`Tag$41965#6"]& ], TagBox[{ Directive[ Opacity[1.], @@ -3983,11 +3880,17 @@ Qpc/UT+rZT5DIMAHCFAys2qZP3eSVLMpzB/AbI+2zLybmz9TlqI9RAIfwE3v aFxmMp4/LA2Z1Qs8HsA6t5kdtHygP3045DAggx3Aic35gcbloT/WU38uoroc wC6xJg08kKM/pyNv73D/G8Bjzl3YKUKlP3VmB77sRxvA/yinKgwQpz9Lme8u 1oAawGesfSCPMak/mYAsYwzHGcDWd6aEAFyrP+5XuTmw/RjAWunudN3qrT9B -ou4dATgYwBXlKaymVLA/DKF4xZ5/F8Bn1IxZN7mxPwamS7idfhfALYcmu1S7 -sT9BOCGu +ou4dATgYwBXlKaymVLA/DKF4xZ5/F8Bn1IxZN7mxPwLgWdGP9hbAIj++r+DZ +sj9ObSNY "]]}, - Annotation[#, "Charting`Private`Tag$4363#7"]& ], {}}}, {}}, { - DisplayFunction -> Identity, + Annotation[#, + "Charting`Private`Tag$41965#7"]& ], {}}, {{}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}}, {{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}}, \ +{}}, {DisplayFunction -> Identity, Method -> { "DefaultBoundaryStyle" -> Automatic, "DefaultGraphicsInteraction" -> { @@ -4001,8 +3904,8 @@ sT9BOCGu None}, DisplayFunction -> Identity, DisplayFunction -> Identity, Ticks -> {Quiet[ Charting`ScaledTicks[{Log, Exp}][#, #2, {6, 6}]]& , Automatic}, - AxesOrigin -> {-11.512925277004099`, 0}, - FrameTicks -> {{Automatic, Automatic}, {Quiet[ + AxesOrigin -> {-11.512925277004099`, 0}, FrameTicks -> {{Automatic, + Charting`ScaledFrameTicks[{Identity, Identity}]}, {Quiet[ Charting`ScaledTicks[{Log, Exp}][#, #2, {6, 6}]]& , Charting`ScaledFrameTicks[{Log, Exp}]}}, GridLines -> {None, None}, DisplayFunction -> Identity, PlotRangePadding -> {{ @@ -4023,7 +3926,7 @@ sT9BOCGu "DefaultMeshStyle" -> AbsolutePointSize[6], "ScalingFunctions" -> None, "ClippingRange" -> {{{-11.512925277004099`, -2.3025852809601757`}, { 0., 0.3511191434221907}}, {{-11.512925277004099`, \ --2.3025852809601757`}, {0., 0.06926469391922004}}}}, DisplayFunction -> +-2.3025852809601757`}, {0., 0.07363704958538311}}}}, DisplayFunction -> Identity, AspectRatio -> NCache[GoldenRatio^(-1), 0.6180339887498948], Axes -> {True, True}, AxesLabel -> {None, None}, AxesOrigin -> {-11.512925277004099`, 0}, @@ -4050,16 +3953,15 @@ sT9BOCGu "placement" -> {"x" -> "All", "y" -> "None"}}}}, "DefaultMeshStyle" -> AbsolutePointSize[6], "ScalingFunctions" -> None}, PlotRange -> {{-11.512925277004099`, -2.3025852809601757`}, {0., - 0.06926469391922004}}, PlotRangeClipping -> True, PlotRangePadding -> {{ + 0.07363704958538311}}, PlotRangeClipping -> True, PlotRangePadding -> {{ Scaled[0.02], Scaled[0.02]}, { Scaled[0.02], - Scaled[0.02]}}, Ticks -> {Automatic, Automatic}}], - FormBox[ + Scaled[0.02]}}, Ticks -> {Automatic, Automatic}}],FormBox[ FormBox[ TemplateBox[{ "\"ACES\"", "\"Unreal\"", "\"Uncharted 2\"", "\"FilmicALU\"", - "\"Reinhard\"", "\"Siragusano-Smith\"", "\"None\""}, "LineLegend", + "\"Reinhard\"", "\"Filament\"", "\"None\""}, "LineLegend", DisplayFunction -> (FormBox[ StyleBox[ StyleBox[ @@ -4624,8 +4526,8 @@ sT9BOCGu 3.8282142008868*^9, {3.828214234665811*^9, 3.828214279564385*^9}, 3.828215966170525*^9, 3.828216048945711*^9, 3.828216333521703*^9, 3.828219573265332*^9, 3.82907472379275*^9, 3.829075127596117*^9, - 3.835447201605631*^9}, - CellLabel->"Out[32]=",ExpressionUUID->"c85963ab-1c7d-41ff-bc3a-2dd458ecbd3a"] + 3.8354472012332478`*^9, 3.8511054211682796`*^9}, + CellLabel->"Out[57]=",ExpressionUUID->"19ba943e-39f9-4cf1-b1e6-0725c2861382"] }, Open ]] }, Open ]], @@ -4703,11 +4605,10 @@ Cell[BoxData[{ 3.6611822457483683`*^9, 3.661182319186528*^9}, {3.6611824483634167`*^9, 3.661182462538444*^9}, {3.6611827846096888`*^9, 3.661182801081038*^9}, { 3.661184057215249*^9, 3.661184059245432*^9}}, - CellLabel->"In[33]:=",ExpressionUUID->"b2cdebdd-b060-471e-8ddd-416664a1997c"], + CellLabel->"In[58]:=",ExpressionUUID->"b2cdebdd-b060-471e-8ddd-416664a1997c"], Cell[BoxData[ - TemplateBox[{ - GraphicsBox[{{{{}, {}, + TemplateBox[{GraphicsBox[{{{{}, {}, TagBox[{ Directive[ Opacity[1.], @@ -4788,7 +4689,7 @@ atH/A55NUeQ= 1.99306016080119, 1.}, {2.0213779417964477`, 1.}, { 2.0496957227917054`, 1.}, {2.176140290414044, 1.}, { 2.3025848580363832`, 1.}}]}, - Annotation[#, "Charting`Private`Tag$4686#1"]& ], + Annotation[#, "Charting`Private`Tag$42297#1"]& ], TagBox[{ Directive[ Opacity[1.], @@ -4899,7 +4800,7 @@ qnwSv5uG9HBS6djnf54cuqe9IkVDagk0MecBCpwLu05mVKAhOTAZ9Tn9c6+i V9IeVRpSYONu5hf/3BxpRTE1oCEdTpmMNSVRQEXFKPmSGQ1J/mDseMY/l3/X 1Ak+S0N63iQ0tfTPUg+P/Eh0oCFNX+pKUdOgQCbIpJS50pDOMi/SR/zz/wCl lhm4 - "]]}, Annotation[#, "Charting`Private`Tag$4686#2"]& ], + "]]}, Annotation[#, "Charting`Private`Tag$42297#2"]& ], TagBox[{ Directive[ Opacity[1.], @@ -4938,7 +4839,7 @@ TGh+ZtOnEQeunl+GujEJUxAUKkY04dASa4/bnSJhN309SssRDkZGNpme50jY IidgX3szDpUT5ifu/UHCxq1X2z604D9+PvI93ZmEXd926/5MKw55oJlV4UXC QnVcqfw2HP4DbiWeGQ== "]]}, - Annotation[#, "Charting`Private`Tag$4686#3"]& ], + Annotation[#, "Charting`Private`Tag$42297#3"]& ], TagBox[{ Directive[ Opacity[1.], @@ -5003,7 +4904,7 @@ mGYnJGP7f84vZngQoNTeaHF8now9d7yw04xBwCrt0vTUNRTMQHa5JtKTgMGe 24SjLQUrnGqaDfYmwMTE7s5fThSsgaqWIuVDQMmwhVWUBwWT3bwzrfCXtVP2 /MjwoWCsZPYqC18CckDnLjeQgnW6Pujt+eX/AQz1IuU= "]]}, - Annotation[#, "Charting`Private`Tag$4686#4"]& ], + Annotation[#, "Charting`Private`Tag$42297#4"]& ], TagBox[{ Directive[ Opacity[1.], @@ -5043,161 +4944,57 @@ g8x0XRKW7WLLn7yOw3cpa7PSjoRtSqztovFw2H/p1KCeAwl7pJ6i87JxkDjG 8Ba6kLDYlExubw4ODSm+P3duJ2Eya6vaxDwcnJ3p2eE+JIw8WsVtzcfhfs/G ref3kbDXFzQ7LAX4n1/WDmUFkbCM3sa+4Js43IBlOeURJOzzSFdgaQEO/wHR Yc4f - "]]}, Annotation[#, "Charting`Private`Tag$4686#5"]& ], + "]]}, Annotation[#, "Charting`Private`Tag$42297#5"]& ], TagBox[{ Directive[ Opacity[1.], RGBColor[0.772079, 0.431554, 0.102387], AbsoluteThickness[1.6]], LineBox[CompressedData[" -1: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 -+bz4j6m2wXLyPvwdXxO9482LN41tCuVvh8DZ/sBb3e68WJXbvGKbGQJl7Rw9 -am68OGt9c7m7NQTuV0gkzDjzovC6lW/ErlDgSyBIdla8KLosNMbUFwq7zXy3 -6VF4UTyMy93JLAysC1Z3sm/mRX3dcyFa+eHgpp2bK8LAi8qpsn4WjeEQWG6v -un2NB2WDt9x4NhkO2d9rQW+BBwV15HSyZSJgZSTu6J1hHnx+jlFiLC4CooRU -PeereDCQ1MZFF/IYarxcy7uDNurVPtWfj4iEnnWy/oQ/z8b+53LfehcJ0/da -65Zv82AIy8yJ8tZIEHys1SbgyYN1p03+UUWiwD6Tbcz0PA8y+ypsL3sZBWs/ -0zkq9Xlw8Y4BQ1RtNKho9Z7KZuVBkeTjb/q04yCQvR0amHjw2of6Fi3HOOjo -bpCh0W/kl9Urm8Pi4JbPlxnFFW7U026TetcXB5Vfngfnj3NjisqN9xAUDyd0 -zhR+quXGnb7GbI29T+GmXqdQzUNupDn6FrdhAjQINK2NB3PjodHKTaIzCUAa -qR7cEsiN5uFil1PJiVAR9DHniC83Vgm5Tt31T4St32L0m1y50XcbFyfIJEGi -oYVnhwk3HlESOtw9mwTlps3tw9zcOMUQfDE/IwWeVT/VNuDgxup3R4Q9K1Lg -OsU+NZuNGw0E2ZuselNAVvGP6/VN3Cj1t2zgEX8q3OcU3MzwhwvNnooeeuyf -CkYNlnvFO7gwj9u2d5fjC2gzHAg/lsCF9NTBhFy9dHhTnrnwIZ4LGQwNu5cc -0iFI0+2MSAwXhhVauTneSwe1HYzy/Y+4cEBAXvN9RTo8ZZMtcb3LhQdIx7bI -HHoJ1t8uTAXbc2GHbsQTlyOvYExv5jAqcKH5GJPvk1uZcM14qLlVlgvjXD7/ -+JuaCSvmnVYTUlzIMl51I7o2E9gdSq8KiHJhZtjtM3air2GPf0ScyxYuPBIx -kGpd/BrGfO1+KHVwYveidAPXlmyguzLI9duSE/nYHioENLwFntt12ziOceIm -5/v0Yf/egnRYvpq6ISey7bQZcuLPAb2sAOtHmpxowzwzu6qbA6Gj23I0xTgR -mX1CfVJyQMjGwSSyhwPdSblMv+xzYc/R4YeHrDmwtcv99Of1PDDOuZz85yQH -rtgElb0QegcuHMv5aWYcWCHgM/xS6R28qtnSzaTLgfxjVa/W7N4BSVdJoUqG -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 -bPh/DBbI2Q== - "]]}, Annotation[#, "Charting`Private`Tag$4686#6"]& ], - +1:eJwV1Pk/1IkDx/GZCSGULilZZt2rw1Ersu9PukY0SWURKikmR1aly7rKUZSk +nKUoWkqkQjXWB6FGiRQrt3Kkmc8n44ocX98f3o/X4/kPvNVdj9oeYjEYjJMz ++3+L5tY/fuTJJk/e7P98rsgLK56mfO7isUnP1uMF7wVeSHd0W7DQg01+Cllt +tKLVC9EZw37+bmwy8KaYI8nyxn6zJYZmzmzynmZWgYW1N6TdnfNectnkx9gC +w7Eub/z5b++DRgM2yRPX8y4qH8WI10TG5Kg62WYfLhPY4AsTMmH55TPq5JE2 +KasVrOPYLMGJ8ZFUJ9/X/FjtFnISBldCdFOuqpFvdb+LE8PPQn3+Cku5xWpk +j3vJGec9wXgm01yRvPEXUuHY0r3luuewrhkSmVtVydBrsnpNr8Lhv74wztJm +ORlxiscRMKJgGWSvdImnQmp8y/0gcycGmg9bMxyOLiPvyRXGPq26ioR/o3Uq +w5aSdbserHWXjUelC3dCcFuZVNAsvrLyVRKm+gc/2f+zhHTJC1L763Iq3tiX +54STSuSn+8m6w91puJvvH2VesZhc2dS497Z5BkxurH8eXbOIrOQt58mqZsHV +Xb/1bN9CsjOkoeqAYw7iVRlzhqgFZKZvkcW08yPsL5zUl5NaQBreevjZkPsE +RuvbHXJk5pMJRmYNZVJFKLepb+xRVCRzCw5UH9vOx9ctA5ak3jxye5Du403+ +JFbZ9lzavF+BfKKYnV7tWI6iJnPfbdFyZITX31EbT1TiefDzoaBSWfJ01XQS +5+trvJueulz1TprkV5hpvNSswTyLBMUd01KkyTujc9OWdXBzGs3qmy1Jrgql +uG6D9TCID/iSrzyLzGtcpfa2tgF6cXtmF1szyY9nJq5tyGuCd60wKP/RVImg +KVV1erwFbYyG0iNyP0vGXQqYaxZ1YIOvDOf86dGSUX9nr7jsTghi3oyVJg6W ++PDnxBvu+ww7T58fU0N0yd+5qUK//i9wUzq4u0ylryRgjYymc3AP2LWZO8Ne +d5Q4R48OrWjvxQX5qYeL1taXhCrlu7KWfMXKZt7WKPmcEruGofk5m/vxbFx2 +jXtdBXw8W3/C7BsiTzyjI7I/QTEixsTtFyHOpL3W5/V9go3qsseJbCE83zbx +tmk1Y6yPHhFoCsHVHP8sl94MRenSVh19IRZ+NP0vNqkFHvxNEJgIkWbMJ1Mi +26A8kqZwd6cQrafbX/1p1InydbWiZyFCjJh0JXkqdmOLlqpTWrMQ/Pnd6zgr +u2HXhOXqbUKECnubNKy6Ubq/8tdbHUIo3BYpt53vRm6ZvkR8txBas8eSbH50 +Q2U0SecoLYTdx7nJa9p7ENhyL/8dS4TCv8yTpx/04WWktvcOHRFOZScmx3GE +uFMos+ykjwjFUlBudxdi45UNGS98RWAd7E7QixBivfEJtyk/EaKXGV4vrRBC +ddHs8MCTItyKro6hLUSQOG5/jBcsQqXP1Hkrcwr8nFdd/bEiXFTOL2Qqf4ex +/Idjdx6L0NiqlP5MXgybsWJZLbEIHd46rntUxCi9z83qGBShf9KEPaAnxnTp +xEDSsAgTyx3TdThiKERJhkmPiaDmciM9IVSMNfIKrz9Oi+DRpnbnxKgYFz6c +DjWQp/CjTfeuQecgEu8Xnh3UorC0wywz68kwtA/MelNjR0F7q1nnz7Jh3LbW +MjWyp2D80FSFWzcMa4XVsfEOFHYErLsqFg2j5V37hIMThfNLfg800x4Bb/Fo +W/0BCpSNod3bpBGc0rQ9es2LQlmpjtRA4CimxFv420MoeN5ZdNjEcgyG3Cph +1T0KRdWH55XZjyGNlO6RyKIgMVT43MpjDD25LQIim0LqJse5+yLGECCIcXz6 +gEJtd2pRWMUYpq/tzop9RGGtjs6c+g3jsG735Su9oMDIMc3zNv2JuT4bXfe9 +pRBfsG88/bdJ2GnLBkjTFF4TPhls00mwbpnfG57xhCDAJp0zibAGhnzXdwoH +25My0w5N4mBj9MUiMYXV0h9sb9+aRI2KhIvTCAWBA+f+zQVT6Kfie4MmKUxP +rHZK/DmF6tHIANM5NJ6oM9srDzGIm13pljc0aOjXDF9o82YQIdv9qv00adw9 +0288coJB9LdeC+Vo0Yirr7+oGc4gJjVS4ga0afiFZa49n8kgpN5I7jb9jcaq +r1aXN/QyCNdN/r2ZBjSy8+LN+R5MovhpyOwBcxps56i+el8mcWOBKDT3DxrJ +MsFx304xiQj7IY43aFxw5X1deoFJ9CW4FPYQNDwWrr9+OotJHKr9Y1XdRhpa +pzqFa/uZxPv7wYvDLGmkajQkcMVMoqUvOdxkG43FdQKLw+NMgsA6q28zltR7 +knhdlkUkx3t+4FrT+NIcvmlIj0Xk6vqMyu+g4RR5lp5jxCK6XxxT4c+43tg3 ++VczFpFClSfzbGiUXXL4bmvFIjwTUzPKdtIwNeWmHNnFImYt2WPkY0sjv8di +S+heFhFu+VBFeRc984e/DyQdZBGQgWv5jNOgf+ORJ4uILE6R8N5N43/aO8Bu + + "]]}, Annotation[#, "Charting`Private`Tag$42297#6"]& ], TagBox[{ Directive[ Opacity[1.], @@ -5345,8 +5142,14 @@ EFFhzk1XKoO2fP6nWfKLYDUy9VOjuwLc1tTfRfssQsm+nFNn+Gqg89WtOYrf i1C1waB+uusjBIYpOqiv/4Erj8v7Llo1gAVfLMMc4xLs03G5Et3WAENuU+pV IksgxchznsO+EVT//5bg/wAoBzs1 "]]}, - Annotation[#, "Charting`Private`Tag$4686#7"]& ], {}}}, {}}, { - DisplayFunction -> Identity, + Annotation[#, + "Charting`Private`Tag$42297#7"]& ], {}}, {{}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}}, {{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}}, \ +{}}, {DisplayFunction -> Identity, Method -> { "DefaultBoundaryStyle" -> Automatic, "DefaultGraphicsInteraction" -> { @@ -5360,8 +5163,8 @@ IksgxchznsO+EVT//5bg/wAoBzs1 None}, DisplayFunction -> Identity, DisplayFunction -> Identity, Ticks -> {Quiet[ Charting`ScaledTicks[{Log, Exp}][#, #2, {6, 6}]]& , Automatic}, - AxesOrigin -> {-9.21034013701852, 0}, - FrameTicks -> {{Automatic, Automatic}, {Quiet[ + AxesOrigin -> {-9.21034013701852, 0}, FrameTicks -> {{Automatic, + Charting`ScaledFrameTicks[{Identity, Identity}]}, {Quiet[ Charting`ScaledTicks[{Log, Exp}][#, #2, {6, 6}]]& , Charting`ScaledFrameTicks[{Log, Exp}]}}, GridLines -> {None, None}, DisplayFunction -> Identity, PlotRangePadding -> {{ @@ -5405,17 +5208,15 @@ IksgxchznsO+EVT//5bg/wAoBzs1 "freeformCursorMode" -> True, "placement" -> {"x" -> "All", "y" -> "None"}}}}, "DefaultMeshStyle" -> AbsolutePointSize[6], "ScalingFunctions" -> - None}, - PlotRange -> {{-9.21034013701852, 2.3025848580363832`}, {0, 1.2}}, - PlotRangeClipping -> True, PlotRangePadding -> {{ + None}, PlotRange -> {{-9.21034013701852, 2.3025848580363832`}, { + 0, 1.2}}, PlotRangeClipping -> True, PlotRangePadding -> {{ Scaled[0.02], Scaled[0.02]}, {Automatic, Automatic}}, - Ticks -> {Automatic, Automatic}}], - FormBox[ + Ticks -> {Automatic, Automatic}}],FormBox[ FormBox[ TemplateBox[{ "\"ACES\"", "\"Unreal\"", "\"Uncharted 2\"", "\"FilmicALU\"", - "\"Reinhard\"", "\"Siragusano-Smith\"", "\"None\""}, "LineLegend", + "\"Reinhard\"", "\"Filament\"", "\"None\""}, "LineLegend", DisplayFunction -> (FormBox[ StyleBox[ StyleBox[ @@ -5979,12 +5780,12 @@ IksgxchznsO+EVT//5bg/wAoBzs1 3.82821397022586*^9, 3.828214126107312*^9, 3.828214211992898*^9, { 3.8282142663632183`*^9, 3.828214282041113*^9}, 3.828215969020541*^9, 3.828216053649629*^9, 3.828216334183318*^9, 3.82821957417743*^9, - 3.829074725573061*^9, 3.829075128404272*^9, 3.8354472019954042`*^9}, - CellLabel->"Out[34]=",ExpressionUUID->"5f3a1ac2-1732-4cba-b9ab-1746b32f50c9"], + 3.829074725573061*^9, 3.829075128404272*^9, 3.8354472019954042`*^9, + 3.851105423989477*^9}, + CellLabel->"Out[59]=",ExpressionUUID->"29db7e14-81d3-4736-ad6e-420dc115fb7c"], Cell[BoxData[ - TemplateBox[{ - GraphicsBox[{{{{}, {}, + TemplateBox[{GraphicsBox[{{{{}, {}, TagBox[{ Directive[ Opacity[1.], @@ -6021,7 +5822,7 @@ Lpo9688dVxFAqx3cQwnNRj4jS1mdQwSUnyqMDVelIv3i1IM/jxNwPNaEoIVe QCcpNXd5UwRsI5nJUlQCxHCJptj+Q8BZG5J9oyoIWZNJa+f/JaDkan7mabfU puLqoi2dcwT8B+HsdSY= "]]}, - Annotation[#, "Charting`Private`Tag$5044#1"]& ], + Annotation[#, "Charting`Private`Tag$42858#1"]& ], TagBox[{ Directive[ Opacity[1.], @@ -6102,7 +5903,7 @@ zt8n7k0Xsmuq+WBbfOa9w0gasVNKf1GvjQ/VD+lyVQUpxMD4zJXhTj7Il/cb SZxPIoIt8ueCu/lQ5J0V4D4SQ1xXHxYp7eODcwB5Uv58OKHRbH1bb4APHGG5 yugRP6Jcs2jLm/d88GcLb6kecSWiBbkt5z7x4UFGevyVPTG1RPAjt8VRPvwP YvZWyQ== - "]]}, Annotation[#, "Charting`Private`Tag$5044#2"]& ], + "]]}, Annotation[#, "Charting`Private`Tag$42858#2"]& ], TagBox[{ Directive[ Opacity[1.], @@ -6139,7 +5940,7 @@ jXpnDuc/GbTZTg1sk7/5mB+ZsRPnP6p1n3xv5Ym/P8LXkUQe5z9mfBXsGWyD v1PdsGmNI+c/RQYXt4jleb9inVTS1SjnP3onBiy75Wm/n46cyh0u5z+lnJqQ dzqJvm9OYlFlM+c/LgtyQQ== "]]}, - Annotation[#, "Charting`Private`Tag$5044#3"]& ], + Annotation[#, "Charting`Private`Tag$42858#3"]& ], TagBox[{ Directive[ Opacity[1.], @@ -6182,7 +5983,7 @@ lr+yFS/IMNPqPxm02U4NbJO/x5X9XJ3W6j+qdZ98b+WJv0R39jRx3eo/ZnwV 7Blsg7/GKLB42ODqP0UGF7eI5Xm/dO9I9z3k6j96JwYsu+Vpv6eACrGh5+o/ pZyakHc6ib7sYT+mA+vqP7SSFPY= "]]}, - Annotation[#, "Charting`Private`Tag$5044#4"]& ], + Annotation[#, "Charting`Private`Tag$42858#4"]& ], TagBox[{ Directive[ Opacity[1.], @@ -6218,152 +6019,43 @@ Juc/Xa1j32Llmb9E2JPhjTfnP7uwHhe4qJa/ksIxYOA75z8ZtNlODWyTv3uD 6PExQOc/qnWffG/lib9K609L0kjnP2Z8FewZbIO/F8xZESFN5z9FBhe3iOV5 v+P4LuduUec/eicGLLvlab8Dm/3Lu1XnP6WcmpB3Oom+x3v0vgda5z/YXWr1 - "]]}, Annotation[#, "Charting`Private`Tag$5044#5"]& ], + "]]}, Annotation[#, "Charting`Private`Tag$42858#5"]& ], TagBox[{ Directive[ Opacity[1.], RGBColor[0.772079, 0.431554, 0.102387], AbsoluteThickness[1.6]], LineBox[CompressedData[" -1:eJwVVmk41Q8TJVF2Lpd7LXdRochaSPqNLJE2oh1ljaQoS9kSiSwRJZJ9V7aQ -kO4Uf6GQhAotlmyXkEoq3t5P8+E85zzznHnOzDDtz+53WsHBwRHDycHx/6pe -7VxV7iaHD4+q87VrvSKendH1hlNyqHboS7v7nlfEfnmhzW0ucvjzcSRto+Mr -wutWVeWYkxwe/hUrxB//inh0fmUl/YQc0oYDhHZ+fUUQalkPYqzksCzGwuB7 -aSdhVjhQ6rpNDpO6/a2P7Ooi+k6Un/25VQ43cCWeNHLuIk5TwlTDdOXQ9yl1 -wepyFxETrlyaoS2Hy+rxfqPVXcQrJ/+SXnU5/PqusK1X/g1xUI5abCwvh8e7 -JIfEBboJ+xSrIqawHDZdUiIiv/cQXXOnjeME5XC8Ym24EaWXMDIL+7TEL4fm -vq5mmlt7iXW/qiT6V8vhCdvVgaWXe4mxg5KhiSvkcE3oliNKom+Js6T3x/h/ -MNEzFiou6L4jAq4dF/zez8Q2fivqs0d9xNQn3wKHPibG6BZdLhnsI2x14gxf -v2Nitqo+vBfoJ/RH8WJpDxPrmzt0Zu36CS5juS+ur5j4RzylTkF4gIjkGHny -oYGJcdXiNwV9PhC3fU95NBcxUSO91cD08SfC4wJvrkUhE/m2G1wf7fpEmF0s -ePc+n4lppIsKBZOfiCW/LwZTOUz8m/VxKUb6M+EcZE8mpTNRs0nh3GzAZ2JT -2NHaYwlM/PVx88vtOwaJV/FmK7/6M3Hktn2W6/wQUZQwrnPBj4nbj0hnx4sM -E1duRrhzXmTiIV1hqyHlYUI7salHzIeJSz5GeX+chom0ZINCHQ8mWqGdLvn9 -MHE6Q3dviCMT2QfX3RVpHSFWl2xIIu9hopPmDqnu1lGieXlYPm8XExN0Cc/6 -yVEiwiK9SsuMidT421u7BMaIVd/Fug6ZMHHV8v51EfvGCB69v4J3tjNRu0hK -j9Y7Rqxs7QilaTGxR50iuunrOMHxxeusAp2JNIPRzgEjNoHaqkvVsv/8FPuv -et6NTQRfG482lWHiUJzHre0JbGJZ2bbIlfpvft5su6BBNrF03mTknhgTBR7y -+UWHThF/OKWOqa1m4lWnls5tr6aJBVnWji2zDGwfUzMjvZghkoSflBV9ZeBY -TN4Zg+EZQoezXkpmmoEfHuibJf2dIXyHa6eXJhgY7hW2+qHaLPGj6GFi4zAD -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 -qA== - "]]}, Annotation[#, "Charting`Private`Tag$5044#6"]& ], +1:eJwB4QQe+yFib1JlAgAAAE0AAAACAAAAMbdmtbFrIsD09vfTg7hsP8JuOnU/ +aiLAKL2CWijHbD9SJg41zWgiwH9bRFfU1Ww/dJW1tOhlIsB/Wp/CQvNsP7hz +BLQfYCLAxSaHy3kubT8+MKKyjVQiwLgTFQNUpm0/Sqndr2k9IsDhzTNY1Jtu +P2KbVKohDyLALOtp2HJPcD+Xd2pwyKohwIZAiGBwuXI/CK4qmBVNIcD4papO +ukx1P/gdv0Y58SDA/7XLMfQqeD/rhXvGk40gwMWzUBJ9t3s/Gkjip5QwIMDu +Ja6XNX9/P5oE4rSYlx/Ag4usVqMWgj/+66cntdEewBbYQjoWuYQ/2ofCXR4Z +HsBJRaQlRYaHP70TLTb1UB3AszWY45v9ij8YVOzRGJYcwLV6oq81r44/cAdU +e+neG8D5XlGmmGWRP9CqC8cnGBvAtxN8hN7vkz+oAhjWsl4awIv3Fnd5o5Y/ +iEp0h6uVGcDGwFdxnPqZP2UFeUZR0BjAbK5pPae7nT+6dNLIQxgYwGJ91Smu +26A/FtR77aNQF8DqfhkPK1GjP+vnedVQlhbAgVLX7QXupT/H68dfa8wVwO/3 +PlpXJ6k/oGK+9zIGFcCRlowc4MSsP/GNCVNHTRTAe8RP6ExMsD9KqaRQyYQT +wNb3GhnaprI/G3mUEZjJEsBBVMhiniS1P+m7LOATEhLA3ouB3Ynktz+/7hRR +/UoRwHIrvJwWQrs/DdZRhTOREMB+s8f3q8u+P8Ravbeujw/AHimFDpeNwT9o +7yeAUAQOwLjeE8i+7sM//Ow7z4uTDMA/eaPzI2jGP5/K72KiAwvAr0VwBSdj +yT8yEU19Uo4JwGut4rDFdsw/wD37slwgCMC84Z0CJsTPP11KSS1CkwbASlf5 +cXPW0T/qv0AuwSAFwKfjkT4q0NM/hhXYcxuPA8DE9iMaFx3WPxLUGEAPGALA +inKwSEZm2D+ZeKonXagAwC3g/gTWwNo/Xvq3pwwz/r+KL712XmjdP2rVbQ2S +Svu/nPJhEpDz3z+UcGP8zSP4v39NLtISX+E/s9f6Ib4L9b80RLUvFL3iP7MQ +5VThKPK/LCZYyBD+4z+hEx4idg/uv+KEEVQQT+U/nakXtY836L98Wnk3I3vm +P4LXVLURfeK/A98qbWuQ5z9ICyOQAYzYvxIbAzuYp+g/GV7P1Vflyb8Xdfzj +FJbpP4S+xnzCfcm/Meem9wua6T/wHr4jLRbJvywbR1IBnuk/yN+scQJHyL/B +1+vb5qXpP3dhig2tqMa/aX2bPZ216T/VZEVFAmzDv4sX6iS31Ok/I9d2aVnl +ub85ZfsPnxHqP/qXZbcuFrm/UEHj2l4V6j/SWFQFBEe4v9W+9OocGeo/gdox +oa6otr/D7KLakyDqP9/d7NgDbLO/4wnK+Gwv6j82ycWQXOWpvxH+jDjMTOo/ +5UqjLAdHqL+Z5dpZcFDqP5TMgMixqKa/7eQBwRJU6j/yzzsAB2yjv6sPKmFS +W+o/Xa1j32Llmb/DCULrvGnqP7uwHhe4qJa/Y7qwPVNt6j8ZtNlODWyTv2yW +r9bncOo/qnWffG/lib/JpsvcC3jqP2Z8FewZbIO/UiYiSpt76j9FBhe3iOV5 +v81le/4of+o/eicGLLvlab/HZvb5tILqP6WcmpB3Oom+sxqzPD+G6j/scWze + + "]]}, Annotation[#, "Charting`Private`Tag$42858#6"]& ], TagBox[{ Directive[ Opacity[1.], @@ -6495,8 +6187,15 @@ w9m7F8Gy1G7MduoeESXQNeWquAR1z0R4a4rSiDbmX99enF4C/sohA2aXFOLN Pl9XHsclKPHKCXCbiiHKJjgOst9bAscA7nl+l1sEy67bmaXlS6BFw/siesqX mCOTfG3VsQR+UjR766auEO+qKup3TS9BQXZmord6TKOlOE/Wzs4S/A+QpM73 - "]]}, Annotation[#, "Charting`Private`Tag$5044#7"]& ], {}}}, {}}, { - DisplayFunction -> Identity, + "]]}, + Annotation[#, + "Charting`Private`Tag$42858#7"]& ], {}}, {{}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}}, {{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}}, \ +{}}, {DisplayFunction -> Identity, Method -> { "DefaultBoundaryStyle" -> Automatic, "DefaultGraphicsInteraction" -> { @@ -6510,8 +6209,8 @@ mCOTfG3VsQR+UjR766auEO+qKup3TS9BQXZmord6TKOlOE/Wzs4S/A+QpM73 None}, DisplayFunction -> Identity, DisplayFunction -> Identity, Ticks -> {Quiet[ Charting`ScaledTicks[{Log, Exp}][#, #2, {6, 6}]]& , Automatic}, - AxesOrigin -> {-9.210340184010052, 0}, - FrameTicks -> {{Automatic, Automatic}, {Quiet[ + AxesOrigin -> {-9.210340184010052, 0}, FrameTicks -> {{Automatic, + Charting`ScaledFrameTicks[{Identity, Identity}]}, {Quiet[ Charting`ScaledTicks[{Log, Exp}][#, #2, {6, 6}]]& , Charting`ScaledFrameTicks[{Log, Exp}]}}, GridLines -> {None, None}, DisplayFunction -> Identity, PlotRangePadding -> {{ @@ -6562,12 +6261,11 @@ mCOTfG3VsQR+UjR766auEO+qKup3TS9BQXZmord6TKOlOE/Wzs4S/A+QpM73 Scaled[0.02], Scaled[0.02]}, { Scaled[0.02], - Scaled[0.02]}}, Ticks -> {Automatic, Automatic}}], - FormBox[ + Scaled[0.02]}}, Ticks -> {Automatic, Automatic}}],FormBox[ FormBox[ TemplateBox[{ "\"ACES\"", "\"Unreal\"", "\"Uncharted 2\"", "\"FilmicALU\"", - "\"Reinhard\"", "\"Siragusano-Smith\"", "\"None\""}, "LineLegend", + "\"Reinhard\"", "\"Filament\"", "\"None\""}, "LineLegend", DisplayFunction -> (FormBox[ StyleBox[ StyleBox[ @@ -7131,166 +6829,189 @@ mCOTfG3VsQR+UjR766auEO+qKup3TS9BQXZmord6TKOlOE/Wzs4S/A+QpM73 3.82821397022586*^9, 3.828214126107312*^9, 3.828214211992898*^9, { 3.8282142663632183`*^9, 3.828214282041113*^9}, 3.828215969020541*^9, 3.828216053649629*^9, 3.828216334183318*^9, 3.82821957417743*^9, - 3.829074725573061*^9, 3.829075128404272*^9, 3.835447202151905*^9}, - CellLabel->"Out[35]=",ExpressionUUID->"65440c48-1b97-41ac-b448-e197bec4a3cf"], + 3.829074725573061*^9, 3.829075128404272*^9, 3.8354472019954042`*^9, + 3.851105424363119*^9}, + CellLabel->"Out[60]=",ExpressionUUID->"a58ca3ff-9312-48fd-ad43-9c47269ba50c"], Cell[BoxData[ - TemplateBox[{ - GraphicsBox[{{{{}, {}, + TemplateBox[{GraphicsBox[{{{{}, {}, TagBox[{ Directive[ Opacity[1.], RGBColor[0.368417, 0.506779, 0.709798], AbsoluteThickness[1.6]], LineBox[CompressedData[" -1:eJwBcQOO/CFib1JlAgAAADYAAAACAAAAdwxUJJ4GJ8CaJA+2mbZlPwjEJ+Qr -BSfALuk7d7+9ZT+Ye/ujuQMnwCzRxZPnxGU/uuqiI9UAJ8A+lRHiPtNlP/7I -8SIM+ybA93x76QnwZT+EhY8heu8mwIoPDFQSKmY/kP7KHlbYJsAYC8Yp8p9m -P6jwQRkOqibA53JzSRuTZz/dzFfftEUmwHk+ex8Oxmk/TgMYBwLoJcCZUFPV -IwNsPz5zrLUljCXAH/iL5m9lbj8x22g1gCglwHa74zNIm3A/YJ3PFoHLJMCh -GbO6AgpyP5NXXsm4ZiTAZ3bJIXS7cz9FS8ECxwMkwBg1duvSjHU/M5nOnXun -I8C0H0fXz2V3PyXfAwpnQyPAgQ2twhqVeT9Tf+PX+OUiwPyOdeTYzns//1iX -LGGKIsADXCsgKy5+P68qc1IAJyLAmklR8XF+gD+bVvnZRcohwCJ23xt67IE/ -i3qnMsJlIcDOX4B3Ep6DP/nXKRIVAyHAQLHCMcdwhT+jj1ZTDqcgwKNQXXNx -TIc/UT+rZT5DIMCiSnWrE4GJP3eSVLMpzB/AUaHMSK3Ciz9TlqI9RAIfwI/T -5ajicY4/LA2Z1Qs8HsDkn7iy7KyQP3045DAggx3A8+eb2Jcqkj/WU38uoroc -wDPPE9/i8pM/pyNv73D/G8AevxNY4MmVP3VmB77sRxvApVKTmFPHlz9Lme8u -1oAawBU/gWnELJo/mYAsYwzHGcAt2cmha6qcP+5XuTmw/RjAiNi0NIqvnz9B -ou4dATgYwE9LbrBpg6E/DKF4xZ5/F8DWj3LOX0SjP96PUg+qtxbAV/ru1zJr -pT8kcdHZ+RUWwIzHCBOGZac/KDOBHAL9FcCRctm7tbOnP29JWDcHRhXAJpNI -URQ+qj++T3/0eX8UwMR6avu0Za0/hQr7dDnGE8A4CBs+4mOwP1S1xpdm/RLA -4aEL2NmBsj+bFOd94EESwBPUE4IazLQ/3+avcQeKEcCNX46QCWO3PyqpyAec -whDANdZScm6luj/tHzZhfQgQwObU9czrLb4/bw3nuZh9DsCiNfcxc1DBP/7A -ssyQ8QzAAraw+Mfbwz993SdmIoALwAYDQPPJmMY/DNo8RI/vCcAOjP8OxQLK -P4s/+6iVeQjAe+8d09unzT+7QRz5ORUHwM/5MOCiydA/WGCdOQ== - "]]}, - Annotation[#, "Charting`Private`Tag$5362#1"]& ], +1:eJwVxXk01AkcAHD9fjKMiRisco8rBi9Jaav56qWl9CrGw+6rba03jrIx4ybM +1No2d+PZMSkWWUVeEop29SVHSijNzE+y7LiWytGxlGh3//i8j8X3kb48QkVF +5dB//j+e4WdVrGaLF620G6WNPFBvtx1nr7VFp+md8ff+4EFh8nJ5E2mLDk86 +8qfaedA8U2YpU7FFToGOkjPAg9UHCyzGsg0unk6e1pjnwY/puRZJczaYoc3w +07UPgbzVh6b+lA0aanbavSkJgavzezZoVdnglCB6v1F+KAz3BIzU77PBRE6y +mVZnOBwiDWnE39YoPcKVWZIRwIm+3mCdZY1mS5mTkbxT4DwU5ircZI1xd8dc +vIqi4LvLfQaiR1ZYuuFWM0EXQH7At913QqwwNK7bIuZuNOzzbiW6SCs0dI17 ++TQrFlykvZeTKy2x3sxT0ceLB+sRkh661xJF62raNhYkAjdtTLE0wcJPObHj +VG8yfA6UOARlsJA8yjZnO6XCDftoXxVbFl7c77PATxVCkf+HwX0PLZAVN2KU +9EoEmUmVW9p4Fth7XBivW3wWPijsdPVJC3S/2ebcFZUO5bn+XK1Kcyw/ciya +f/Ac+EA1j7PXHMsOxFYzRechXux3y67HDH1Ke7xq2jKB+0vZbg/CDHMHJq4q ++TngsE4q09xliuMld+peXc+DFLfxzeZnTfDtVBElsReDnJvmVNZsjC59zBHl +m3yotIyci/pshKb3mIGj3QUQG0JreeVphBW++YXcLgl4S+ec5MKNqA+i8HaH +iyAVOgQzujag42B36cmqSzAb0LSjdsUQf6bqt2bcKIE9ZbMma9wM8bD3qdrw +s6XAKE3oKE77AuW5gt7jHuXwZ66vdtVtAwz4OKvYcrICrPhPBj/oG2BWlzrz +J14lbHIRGRMr+nhBMHi34VYlRO4P3E77Sh9t8r189DhV0HI47V1imh62J51Y +rufVQDp9OWZHJxPd1I04yuBa8GvolISs6OJfpZrUoKgOivSmUkb36CLzGfPM +xp56GJm8wadl6GD28Zw8jeDbYH/tAe1S23p0lfsKTlU0w2uzbUEp6utx8tn7 +npeOLRC5bqqpMEULy1wXnaOPtMIq1vXkLTCQaKxd6hq6DynDtiEsoSaqke5v +uws7gfF8l0funAZqZX3W6iAeQiYsXy1IVMfkOZOBocrHoJpJt/uHTkNP5VGh +AdUPWY1Nr51T1qKirMY+9eAAqHrr7qDRVbGX/UOEukIG9FbRWDapijOaHe4z +ShnobF/I1lshsU+QW90/KwNTy36lxTyJXO2vd1erycFtOTtrl5zEbkmCqWSb +HCKqNUb5ZSR6yko3eUnkINNcc+7FlyQ+FaVeUTmqgBeiKOfgrSR+ujl9jB+m +AOXiyNC0I4l+rJGwyRgFzI+1bF40J7EijBU+nK0A+u+nn+vQSOwPqOtevacA +TsSSo+cAgVvfP6piWFPgoQylHvcQeOiayc5WZwq8AxVn/DoJ/JQ9M5/IoSDQ +o1ER1ESgeFqzdTGAAoFxjOh0CYHSyvxiq0wKEsRjbFJKYN0EU+2jhII0da78 +vJjAbzpzkgauUJD1zpktSSew6H5DiriFAvGJUplJGoGuwwtrEx5RIB1dL7yS +QKD9Ba+cIIqCX/2F9mwBgatsPvPwBAW/9cw9u3mSQOUBY7H7Gwr+Bb41XJw= + + "]]}, Annotation[#, "Charting`Private`Tag$43179#1"]& ], TagBox[{ Directive[ Opacity[1.], RGBColor[0.880722, 0.611041, 0.142051], AbsoluteThickness[1.6]], LineBox[CompressedData[" -1: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 -/A8+TYC5 - "]]}, Annotation[#, "Charting`Private`Tag$5362#2"]& ], +1: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 + "]]}, + Annotation[#, "Charting`Private`Tag$43179#2"]& ], TagBox[{ Directive[ Opacity[1.], RGBColor[0.560181, 0.691569, 0.194885], AbsoluteThickness[1.6]], LineBox[CompressedData[" -1:eJwBkQNu/CFib1JlAgAAADgAAAACAAAAdwxUJJ4GJ8BKZc9byF1zPwjEJ+Qr -BSfA7O43Cydkcz+Ye/ujuQMnwLTL+9KHanM/uuqiI9UAJ8AeuVeuT3dzP/7I -8SIM+ybAcjC6p/iQcz+EhY8heu8mwOcGuUCwxHM/kP7KHlbYJsDmsyj0ui10 -P6jwQRkOqibAuuk2aWYGdT/dzFfftEUmwHL17EPq+3Y/TgMYBwLoJcB37hZX -TPp4Pz5zrLUljCXAF8/voZ8Zez8x22g1gCglwEMQ1CpTm30/YJ3PFoHLJMB3 -jhj10ROAP5NXXsm4ZiTA8xehyQ2VgT9FS8ECxwMkwOUJlthRMoM/M5nOnXun -I8Bs/PJl8tWEPyXfAwpnQyPA3yl994/Fhj9Tf+PX+OUiwFaSFQS6vYg//1iX -LGGKIsCk7Ck5RtaKP68qc1IAJyLAZGJBZfdPjT+bVvnZRcohwB5y+VPd048/ -i3qnMsJlIcDIdQ7cMGaRP/nXKRIVAyHAkK41DgH+kj+jj1ZTDqcgwGr9Gdfq -m5Q/UT+rZT5DIMA0z3UYyISWP3eSVLMpzB/Apfb7Vd51mD9TlqI9RAIfwGMi -/dIAwZo/LA2Z1Qs8HsCrmP1N+zadP3045DAggx3Ax4PTZta2nz/WU38uoroc -wNnaG0lKVaE/pyNv73D/G8Abmw/dydWiP3VmB77sRxvAodFsLRFvpD9Lme8u -1oAawLFvhiR2UqY/mYAsYwzHGcAvu8DcyD2oP+5XuTmw/RjA6QuAGh2Cqj9B -ou4dATgYwGsK9zaf8Kw/DKF4xZ5/F8Ag6BvIn2ivP96PUg+qtxbAi0dgp4op -sT8kcdHZ+RUWwEi/7fRecrI/KDOBHAL9FcCL6538JaWyP29JWDcHRhXA2GvJ -tBU5tD++T3/0eX8UwDEztW4gFrY/hQr7dDnGE8CUfB0zt/q3P1S1xpdm/RLA -Ypyz8PI2uj+bFOd94EESwLoLIb0qfbw/3+avcQeKEcCuhC5EtOi+PyqpyAec -whDAOGSmMu/hwD/tHzZhfQgQwOX+Jn8yVcI/bw3nuZh9DsACY+8xBgvEP/7A -ssyQ8QzA5oghKbLfxT993SdmIoALwHnBKOZAusc/DNo8RI/vCcAy23I5kujJ -P4s/+6iVeQjATgohqLEdzD8Eiwop9goHwNdVh3Rrc84/jLa57TF9BcCy/EQY -05fQPxUD8BWzSAXAz/kw4KLJ0D/yNLIF - "]]}, - Annotation[#, "Charting`Private`Tag$5362#3"]& ], +1:eJwVi2s0lAkYgPV9MswghEhk3MYlrUuJPerNMbFlYzNuZzvZIpPFWTFENDtj +o4tLpHJJsqMLq04iaWe27MsRoyyZL0bl0lKSlVErlwZt++M5z5/nYUbEB0YR +Kioq/l/43ymaQVaX1Vi4K6p7r3QfD9RbWa8cVrJwcmoLg3WQByXpyitikoUN +j5WykzE8kExUWvaqsNBMHHorIIUHy9L3FppKG0x0klTNF/AgMyufmaawwTdq +4u23W3lQsPzILKTfBsfu2M5IHJOgetrLWLvGBiVv3X7kqiXDYGfocMOOL//H +Se8J5RHwJ41oxLg1pkwZhn77KRW28W7etc61xjXdClGFcTo4v4jeLLS1Rm+d +p/acMj7sL+82zHhshSlnjD72rBbCudDwjt+5VvjvGlGH1oUM2OHXTLSTVvha +o6h/j8txcC3tKk+vssS4xQ9RH3ozwXqYpB/ytsRhO/5c/sMTwBGMyudfW2BI +oYGq5MEp+BxWvOFAtgVenbTz8OnLhlp7XqAKywIPRnhFzQXkQVnIwrMdj5ho +lrjAGaTyISetyqUlionSZO0BJ+5ZWJDb6RmQTCy4tVl7xXIhXMkP4WhXmWPM +krF8ouw87IEbUdu8zXFTd7KRNLMIUgqD7th1rsdrs8rgoeQS4BRVbmUT6zHS +Ykmm0nwRNmiV9jI8zfBGydJupVs58N1fOZkfN8X24xS3r7EC+jiCjZWSdfjs +ucnOXcEiqLKMVxz+bIImZasGO3orIZlLa5r0NUFRT5yjbvxV8CtVbOwTrsW6 ++BNWRwKvQ6lwQ6RmuzF+9QcOSLdWw1So2OP2khG+ZQjXmv5SA16VU6Yr3I0w +lj7nVjF9EzRFqQ8vC9ag+biJtCK6FobyA1fV3DPEHN/9Vdl2dWCV0PNswcAQ +v/nz3Ux4Yj3YumasI5YMMOef8kXra/UQvzNsC83HAPtjOxoMPBqgKUAwc1Sg +j86ud38yN2yELLoyyaNtNZ4/Zup679M9CLrbVsxd0sOIS3emP7hJoEz/Df+l +lx5KGMwH9vz7MDxWm0DL1sVbmRvZDeNNYP+blHapRQfdD153UfyN8G692wG+ +ug6+XrYRuAS3QLzWG3EJXxuJSIWzGqMVlrG+s+C9Jo6dYtrVDz8E/iCLayFk +4NFm27HtknbQfO7JzldooMuLRI/C8Q7IAWX1haPq6E9nVteZdoJqDt1ulk5D +efDJpFheF+Q2it8581di/SLbiCp+Aqp+eh40uipKWrRmyfkeoDdnjOaRqlgu +EJlHLPeA7pb3efpLJKa12/i1qMrAzPLJCHOaRKfcANEpXRm4K/NyPftILOBo +7nV0kEHcDY2XCZUk0ujSXfJwGfQyVpwc+JpEf5WfobZdBgMZh50jN5EYwNYn +/LtkMDI3/OKt45d+OqBr6qkMpkebnObMSZwVyzPdRmRAv3/suS6NxPCh2YSh +RRlsi5t39KUIjO6pP13gQgF75FD/X50E2nhW077zoMAvTP5LUBuBPrSL53S3 +UxDGbpQfEBPISU3vu7ibgsR1SRnHKgg089m3qi2agtTCUQeylMDdAz+oFsVT +IFDn9J0uJJBy4WpEH6Egd8bZoTiLQL3v09h6mRQUxoh6TQUExviuTJ/IpqD0 +pY7waiqBexzPtLSepeDXEKG9QyKBj4sDTUQlFFzvVDytiyWwvCY/S1BBwX/C +MldM + "]]}, Annotation[#, "Charting`Private`Tag$43179#3"]& ], TagBox[{ Directive[ Opacity[1.], @@ -7310,7 +7031,7 @@ f8fpPdNW+yH4b7b2izM6I/hhXaaSa7QR/McuWZUHNRD8gv/zbl5TRfC7Sjhm MSog+OL6tr/EZBH8JS8Lo3SkEPxZrtMP/ZZA8AHrEMxY "]], LineBox[CompressedData[" -1:eJwBkQFu/iFib1JlAgAAABgAAAACAAAArpwL8fcSFsCGEnAMUbIZP/RIrpus +1:eJwBAQP+/CFib1JlAgAAAC8AAAACAAAArpwL8fcSFsCGEnAMUbIZP/RIrpus CBbAdg5819GJPD8oM4EcAv0VwCl1d+zR6ko/gY/sMCb6FcDNrNvWQwlOP9rr V0VK9xXA07UDS9OUUD+MpC5ukvEVwAFihFchuFM/8RXcvyLmFcCHUv4ReQpa P7r4NmNDzxXAKTERekVvYz9MvuyphKEVwHOK7fr3UXA/b0lYNwdGFcCnrkCv @@ -7318,223 +7039,90 @@ Qk9+P75Pf/R5fxTAMahMR1ojkD+FCvt0OcYTwNYDKoJmdJk/VLXGl2b9EsCv 7tzkuZeiP5sU533gQRLA3OpLYo/rqD/f5q9xB4oRwMKXbSuv/68/KqnIB5zC EMBZ5ZhAj160P+0fNmF9CBDAbGqgpzP7uD9vDee5mH0OwBRY6JMUmL4//sCy zJDxDMDHUCtIg2DCP33dJ2YigAvArUFPuHeMxT8M2jxEj+8JwC+XvTSFUck/ -iz/7qJV5CMAe0Ql+BSbNPwSLCin2CgfA7hTeoE6Y0D+RIbuyTeoGwM/5MOCi -ydA/O3+7Dg== - "]]}, Annotation[#, "Charting`Private`Tag$5362#4"]& ], - +iz/7qJV5CMAe0Ql+BSbNPwSLCin2CgfA7hTeoE6Y0D+MtrntMX0FwDM1v1wf +8dI/BEsSOQcKBMBfZPcqjUTVPwrBgeONAwTAi7NiYzdP1T8RN/GNFP0DwCYf +iPbjWdU/HiPQ4iHwA8BcTYInRG/VPzj7jYw81gPA4d9OjSCa1T9sqwngcaID +wD13vypI8NU/1QsBh9w6A8Dcl6BZSJ7WP9yBcDFjNAPAjMHlGjup1j/i99/b +6S0DwDi3LwcwtNY/8OO+MPcgA8Cx0FVaIMrWPwq8fNoRBwPAJekTvRr21j8+ +bPgtR9MCwJlY9/50Ttc/ROJn2M3MAsArkcepiVnXP0tY14JUxgLAnhLAZKBk +1z9YRLbXYbkCwL5eKwTUetc/chx0gXyfAsAEQIK3U6fXP3iS4ysDmQLAizk6 +sniy1z9/CFPWiZICwHv5566fvdc/jPQxK5eFAsABfOSm89PXP5JqodUdfwLA +cfCNniDf1z+Z4BCApHgCwI+O4pBP6tc/oFaAKityAsCIiAp8gPXXP6bM79Sx +awLAuwAsXrMA2D9+sW29 + "]]}, + Annotation[#, "Charting`Private`Tag$43179#4"]& ], TagBox[{ Directive[ Opacity[1.], RGBColor[0.528488, 0.470624, 0.701351], AbsoluteThickness[1.6]], LineBox[CompressedData[" -1:eJwBYQOe/CFib1JlAgAAADUAAAACAAAAdwxUJJ4GJ8AfdQjn6Nt1PwjEJ+Qr -BSfAmTmydBnjdT+Ye/ujuQMnwPgnuV9M6nU/uuqiI9UAJ8AJIvtQufh1P/7I -8SIM+ybANEAmtq8Vdj+EhY8heu8mwHS/pjoPUHY/kP7KHlbYJsBEnFi2nsZ2 -P6jwQRkOqibA3WDupSy7dz/dzFfftEUmwO5Uqio78Xk/TgMYBwLoJcDv/sJl -SzF8Pz5zrLUljCXAr7+BR4mWfj8x22g1gCglwEHwDHtntYA/YJ3PFoHLJMAx -oisBhyWCP5NXXsm4ZiTAAb2MylLYgz9FS8ECxwMkwG4a/KLAqoU/M5nOnXun -I8Cdo2bKWoSHPyXfAwpnQyPAk6uFtrWziT9Tf+PX+OUiwBAkTpqz7Is//1iX -LGGKIsAKAZz/OUqOP68qc1IAJyLAp3/ujq6KkD+bVvnZRcohwDk9syD/9ZE/ -i3qnMsJlIcCKTY4kGKOTP/nXKRIVAyHAjD6L3kJvlT+jj1ZTDqcgwEyC50tI -Qpc/UT+rZT5DIMBlu/CC1GmZP3eSVLMpzB/AiEuNp5qamz9TlqI9RAIfwJv5 -kt7jMJ4/LA2Z1Qs8HsAjSCQLtHugP3045DAggx3Arn5riXnkoT/WU38uoroc -wHrMamqBjqM/pyNv73D/G8CB6BNy6D+lP3VmB77sRxvA/Z8M6R4Npz9Lme8u -1oAawMJCTCitLak/mYAsYwzHGcCcAGad8larP+5XuTmw/RjAm4pniyPkrT9B -ou4dATgYwH1olvEyULA/DKF4xZ5/F8BMN6vfbrOxP96PUg+qtxbAsNtcBKtW -sz8oM4EcAv0VwN9T4lmqALU/b0lYNwdGFcAvZlyAfMW2P75Pf/R5fxTA/rJu -TJPbuD+FCvt0OcYTwMrCtjhN+bo/VLXGl2b9EsCqJRbK4Xe9P5sU533gQRLA -Rehk/iMAwD/f5q9xB4oRwIz9R4lZWME/KqnIB5zCEMCXj0BHgu3CP+0fNmF9 -CBDAQRju0ruHxD9vDee5mH0OwJg5q/zcacY//sCyzJDxDMAZb0Xn7mvIP33d -J2YigAvAj2N8Hkdyyj8M2jxEj+8JwCMuYdE+0cw/iz/7qJV5CMDrY7REZTTP -P7LSrhR1HwfAz/kw4KLJ0D9GcJiy +1:eJwVxWs0lAkYAODxfdrRmEpKS7kPptxKVKbkjcZONbphpT2dXbKDaFNySUwz +oxzrfjvRuFUjZFREFG16c0o1O6WYj5ZC2KU2gz1242jY3R/PeSyORvoICBqN +tvc//x/H9LMq+4qNZjE6o2N9MaDzmD1it4iNUk59tNFwDFxKmCtvJtk4w27+ +wftjDLR8lLEoGhsXW84daJ6JgflnU5bMORt02WHTVGsQCxeSsy3OTthg9MPK +rcsOxELOvMLU/40NcksCmsraY+H6pIfRUrkNvgtUV9jfj4N3ykMDd7xsUO0n +t902FQ/7SEM6MWaNE/NtAr5TIrifvtFonWGNtQ8lvNSCc+DUF7ZJvNYaPSaZ +CaGNYggs7Vgl+dUKnWR2WinWSZB/6Pvn90KsUOtBhsLnzXnw4j8inpJWeGL1 +FxnKk8FZ+rI0oYqFpeUhiu8upID1AMkI3cnC/JrkpsaGVPAVDffM/G6Jelb7 +iho+pcNCQKF9UJolMrRKFjh7sqDW9rQPjW2JVSJ11q20HCj2n/3NS2GBnO0N +5gt/50L62aqNbQILTNubZWVYng+zPev0DUgLzHBP7/eMvAjl2f6+S6vM0Ttp +lL/LsxAOQo3Afac5Cu5PJqmOSSEuz69hndIMf+ZnVhUVFYNvgWw7lzDD4tm8 +/uENZWC/RErpupkia5eV7p2EKyB0Hdlgft4Eb52LSI0fuQrdviJHWYsxnlWG +h0uyyqGKFTlxcmENSsZWRI1BBcSE0Fs/8dag5jLzg+mSKuBLJxy7xauxzdN7 +7U2HapCK7YOZT42whBZS+pd/DagPNXPqNIZYnBaazhq5CR4ytYmWqyEKwwqm +Nh6oA+bVM0/KRF+j95aagRMNt6E/22eZ/O4qrOs7ol3j3wBrnSXGhMYAB3yH +DstpjRC5O2AL/RsDXB9yRJz4pAla94um40Urcb7+hHd+3z1IZsxFc9pXoKKt +yXXvbAv4NbYXhmj0UW69SvE+7gEUrxwVDnroo9fYj/MsGsLAH7Wn6GnLMUPD +Sz0c8Ahsq5/RS9r0sDB7By9pvA3GzTYHCXX00MNQ3Xk/5TFELhltviRcipc4 +NV/eHmuHeaxX5kwx0SjSa1Qd8QyE79ghlmJdzA5ONOVFKYDZ68bNnliMLMeg +1+6vlZAOc9cvxuvgn8F3uAKXDtBOZ6z7h0HHfOXgzaK+V5DR1DzuJFyETpNH +99892AnafH0OnaGNjv0Rz7992gWMR5LhTFIbf3qfk6v3sguWb5nKXKkh0XST +4dYXqi4wZb0aspgksWK60N57qAtc5zIz3LpJHJdwpYGaLjhes3jwlIxEtdvz +smFnFVC6Wilvt5I4NV943UemgreSk07BLiROz4cV7K5WwdDngb4PDiQ6bOsV +eNapYHK4dcNncxJ5laXVnFYVMH5J7F1OJ7HzYMpj914VuB+fceB1EaiZ6OCW +61PAHQp980JJYB4VyL9nRAE/oCfJr53AUL09zh3mFARwm3qCmglsMdJUEusp +iDKOliReJlAgrzAW8yk4kzdsR0oJTPr8wP6aDwUiHd/u1DwCS3OVJorDFGRM +O9kVJhN4dyZLtiaMgrzwq5SJiMDLWy038yIpkA7qia+dIXBZfbg8OpaCK/5i +W7soAnMF/gvlQgoqlROq2xEEsm90ulAXKPgX3iVW+A== "]]}, - Annotation[#, "Charting`Private`Tag$5362#5"]& ], + Annotation[#, "Charting`Private`Tag$43179#5"]& ], TagBox[{ Directive[ Opacity[1.], RGBColor[0.772079, 0.431554, 0.102387], AbsoluteThickness[1.6]], LineBox[CompressedData[" -1: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 -h/8H1qki4w== - "]]}, Annotation[#, "Charting`Private`Tag$5362#6"]& ], - +1:eJwVxXk01AkcAHB+v7GjcSwiI/cxhgYvlNJOvs8SeagYabb1Nir0Mo6aPCQ7 +RuwyWGE75jmSFLH7UElU9psa55Su3/zKEUWly7GvTR6yu3983sd6b2JoNKGm +phb8n/9P0Q6zq/yGiyNsmeHaJj/QvMud4Glw8a5phG3nDT84k75wvo3kYsTM +Zfbubj9of1dtS6lxcTJhvrPsuR987Zm10V6wRydx7sEYHX/IzimyPjptjwbQ +2mgq8ocTX/sswp/aI9vs4VVTl61QN+NtoltvjykeC+p2igAYUe4avbrFHrkF +5+SfTwbDNpLNJCY52Gxj0rT6yQ7wEv/RwingYKpndlwhWwCuQwfWZzpwsN8j +5t7s650QWTGwStpvh33xfw6HTAihdNdPvddj7BBu1VZwz0TAlsDbRDdph0Yq +KnswKhLc5fcr0mttcVnje0WTfC9wRklWrI8tih0TssuF0SCQjNNfXtlg79zK +i/Z+B2BZeNopSmaDXo+K68tz46BxjThUjWuDNpHvix2KE6EsfP7Zlj5rtP3H +ss976hDkH61164y2xkAXZ7by1BGYpx0NjEhrDJINpo78ngLni8IFurVWqChS ++nreOQoh0BDt5WOFOblj7XOXf4aUkrArjkpLDBa/UhceloLgVPVmX8ISrSTz +Fxla2eCkI6e0+Ba4+Hpof/bcL5CxcWKt1XFztLi90+TSShmoBBKX6nYz1AkK +H2r6UAi1tonTScumKArYeSyULIbkGGbHB39TzKL6c5MCSyFQPu2iylyNjL57 +vjnmp0Ce6bRPu9sEE5MvOFSay2FqV5tn0xIba2J9Atwel4N39ZS5+kY2Tq7w +eMSTVIH2uVRFpcQYqfi32aEt1fC8KPTb+tZVuHyl7Ipx0gVwcJeaEUtGKBK/ +ey8Or4PEAOEGpp8R/lCq4ZBe2AAd2yWf0iSG+L6GcVaR1Qg5rIUjnl0r8ekM +zyP9WTOEtXSdjlkywAnGYnCE6CqUGb7JGPM2QOLzZqNNnFYYfd14iCnTR61x +/fxKTjusudTDLO/UQ4vfBOtaH9yCj5YeURmaeqgnqJ3iUQiJOm/azmToIr9M +rSne6Q58xcvKE7PaGOvWUSBCBWSMcGNsMrUw/W1VMS3qAe1Bvm/R9AoUz4wb +J1b3Qz4s1J1M08Te9ZY3fKbvAyOf5fiZxcS4wDeTrO8eQsG1to+uGRoofBWs +n7ftMTACDTyZLAZy3aJVOfspYN2WjheSDByQ5iXbJ1Cgv2G20HCJxKyurs7e +FAosbB+8tJ4hURoUto+dT8HGhcICvopEg4ablVQzBaKGFWOHqkk03dHNHFim +gNJS/3V4E4mbWbfSWipUMCxNct23jsT6AWHdpVoVvJwbHXrrTGK8mfueqmYV +zIx3rJ2zItGQFx55UqEC1s1jg/pMEj85/yWr+KgCL9EXZ//HBBafcArieNHg ++zL26T0lge6L7B+9t9IQKKSzwroIDNXVG94TSoPQ9xod1UbgzKyPS00MDYfN +jkiPnSWwIH77u4AiGlJLxnmknMC/+c6RaXIaJJoCVV4JgSEKLr/hPA0Fn1x5 +p3MI3L0YYrXqOg0lB89R5hICs87GcbZ30iAf08usSSWwdCJZJlPSUBWeuYZ3 +mMDj/LHgHhUNF5XTT5rjCIx4YZyg+YKGfwGBckZd + "]]}, + Annotation[#, "Charting`Private`Tag$43179#6"]& ], TagBox[{ Directive[ Opacity[1.], @@ -7666,8 +7254,14 @@ m+B0xogySz4BdenKrSrczRDFuedDcgYBL1Gz9p6WbYbzPzSUc+MJOG9rsmRk 3gwZR4vfy0QR8Nqkgcj9/c2QPygQXXaagAkv415ZBzRDkVO0knIQAYsep6lP xTVDeddc951j/3piBizy85vhf2IYixE= "]]}, - Annotation[#, "Charting`Private`Tag$5362#7"]& ], {}}}, {}}, { - DisplayFunction -> Identity, + Annotation[#, + "Charting`Private`Tag$43179#7"]& ], {}}, {{}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}}, {{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, \ +{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}}, \ +{}}, {DisplayFunction -> Identity, Method -> { "DefaultBoundaryStyle" -> Automatic, "DefaultGraphicsInteraction" -> { @@ -7681,8 +7275,8 @@ xTVDeddc951j/3piBizy85vhf2IYixE= None}, DisplayFunction -> Identity, DisplayFunction -> Identity, Ticks -> {Quiet[ Charting`ScaledTicks[{Log, Exp}][#, #2, {6, 6}]]& , Automatic}, - AxesOrigin -> {-11.512925277004099`, 0}, - FrameTicks -> {{Automatic, Automatic}, {Quiet[ + AxesOrigin -> {-11.512925277004099`, 0}, FrameTicks -> {{Automatic, + Charting`ScaledFrameTicks[{Identity, Identity}]}, {Quiet[ Charting`ScaledTicks[{Log, Exp}][#, #2, {6, 6}]]& , Charting`ScaledFrameTicks[{Log, Exp}]}}, GridLines -> {None, None}, DisplayFunction -> Identity, PlotRangePadding -> {{ @@ -7703,7 +7297,7 @@ xTVDeddc951j/3piBizy85vhf2IYixE= "DefaultMeshStyle" -> AbsolutePointSize[6], "ScalingFunctions" -> None, "ClippingRange" -> {{{-11.512925277004099`, -2.3025852809601757`}, { 0., 0.38978590267758595`}}, {{-11.512925277004099`, \ --2.3025852809601757`}, {0., 0.26230689901049414`}}}}, DisplayFunction -> +-2.3025852809601757`}, {0., 0.38978590267758595`}}}}, DisplayFunction -> Identity, AspectRatio -> NCache[GoldenRatio^(-1), 0.6180339887498948], Axes -> {True, True}, AxesLabel -> {None, None}, AxesOrigin -> {-11.512925277004099`, 0}, @@ -7730,17 +7324,16 @@ xTVDeddc951j/3piBizy85vhf2IYixE= "placement" -> {"x" -> "All", "y" -> "None"}}}}, "DefaultMeshStyle" -> AbsolutePointSize[6], "ScalingFunctions" -> None}, PlotRange -> {{-11.512925277004099`, -2.3025852809601757`}, {0., - 0.26230689901049414`}}, PlotRangeClipping -> True, + 0.38978590267758595`}}, PlotRangeClipping -> True, PlotRangePadding -> {{ Scaled[0.02], Scaled[0.02]}, { Scaled[0.02], - Scaled[0.02]}}, Ticks -> {Automatic, Automatic}}], - FormBox[ + Scaled[0.02]}}, Ticks -> {Automatic, Automatic}}],FormBox[ FormBox[ TemplateBox[{ "\"ACES\"", "\"Unreal\"", "\"Uncharted 2\"", "\"FilmicALU\"", - "\"Reinhard\"", "\"Siragusano-Smith\"", "\"None\""}, "LineLegend", + "\"Reinhard\"", "\"Filament\"", "\"None\""}, "LineLegend", DisplayFunction -> (FormBox[ StyleBox[ StyleBox[ @@ -8304,8 +7897,9 @@ xTVDeddc951j/3piBizy85vhf2IYixE= 3.82821397022586*^9, 3.828214126107312*^9, 3.828214211992898*^9, { 3.8282142663632183`*^9, 3.828214282041113*^9}, 3.828215969020541*^9, 3.828216053649629*^9, 3.828216334183318*^9, 3.82821957417743*^9, - 3.829074725573061*^9, 3.829075128404272*^9, 3.8354472025216208`*^9}, - CellLabel->"Out[36]=",ExpressionUUID->"45b95cdd-213f-4412-9335-ca0c706aafea"] + 3.829074725573061*^9, 3.829075128404272*^9, 3.8354472019954042`*^9, + 3.851105424723609*^9}, + CellLabel->"Out[61]=",ExpressionUUID->"404ed04a-41a6-4d3a-ac7a-c48c85ee6358"] }, Open ]] }, Open ]], @@ -8813,16 +8407,197 @@ DfEUeRI9M1Q0uSX3c6o7DY0dPT6qNE9FbuK1Vw0f0VAl/s0V95uKnDabbjB7 3.8282195748144817`*^9, 3.829074726363484*^9, 3.829075129277383*^9, 3.835447202707675*^9}, CellLabel->"Out[38]=",ExpressionUUID->"e310f9b8-0a7f-4c02-a543-63a67c823baa"] +}, Open ]], + +Cell[BoxData[Cell[TextData[{ + StyleBox["Solving the Filament operator", "Subsection"], + StyleBox["\n", "Text"] + }], "Subsubsection", + CellChangeTimes->{{3.6472675182800293`*^9, 3.647267538647049*^9}, { + 3.647267585383754*^9, 3.647267585518792*^9}, {3.6472676525755367`*^9, + 3.6472676550384493`*^9}, {3.647267694055285*^9, 3.6472677102461643`*^9}, { + 3.66112151062202*^9, 3.66112152568454*^9}, {3.681738690906479*^9, + 3.68173869687422*^9}}, + FontWeight->"Plain",ExpressionUUID-> + "7be4d30e-9374-4db8-b47f-b6782a53042a"]], "Input", + CellChangeTimes->{{3.85110557431765*^9, 3.851105624716372*^9}, { + 3.8511056686684093`*^9, 3.8511057039788723`*^9}, {3.851105768244672*^9, + 3.851105768802436*^9}},ExpressionUUID->"7f835e78-9f1c-4cad-bda7-\ +4fe00e88eaec"], + +Cell[BoxData[ + RowBox[{ + RowBox[{"(*", " ", + RowBox[{"Core", " ", "compression", " ", "curve"}], " ", "*)"}], + "\[IndentingNewLine]", + RowBox[{ + RowBox[{"compressionCurve", "[", "x_", "]"}], ":=", + RowBox[{"a", "*", + RowBox[{ + RowBox[{"(", + RowBox[{"x", "^", "g"}], ")"}], "/", + RowBox[{"(", + RowBox[{ + RowBox[{"x", "^", "g"}], "+", "c"}], ")"}]}]}]}]}]], "Input", + CellChangeTimes->{{3.85110557431765*^9, 3.851105624716372*^9}, { + 3.8511056686684093`*^9, 3.8511056845106297`*^9}}, + CellLabel->"In[68]:=",ExpressionUUID->"bf5a4c21-8b04-449f-b2c1-bc70887faac4"], + +Cell[BoxData[ + RowBox[{ + RowBox[{"(*", " ", "\[IndentingNewLine]", + RowBox[{ + "Solve", " ", "tone", " ", "mapper", " ", "for", " ", "the", " ", "params", + " ", "we", " ", "are", " ", "interested", " ", + RowBox[{"in", ":", "\[IndentingNewLine]", " ", + RowBox[{ + RowBox[{ + RowBox[{"-", " ", "hdrMax"}], " ", "needs", " ", "to", " ", "produce", + " ", "1.0"}], "\[IndentingNewLine]", "-", " ", + RowBox[{ + "midGrayIn", " ", "needs", " ", "to", " ", "produce", " ", + "midGrayOut"}]}]}]}], "\[IndentingNewLine]", "*)"}], "\n", + RowBox[{"Solve", "[", + RowBox[{ + RowBox[{"{", + RowBox[{ + RowBox[{ + RowBox[{"compressionCurve", "[", "grayIn", "]"}], " ", "\[Equal]", " ", + "grayOut"}], ",", + RowBox[{ + RowBox[{"compressionCurve", "[", "max", "]"}], " ", "\[Equal]", " ", + "1"}]}], "}"}], ",", + RowBox[{"{", + RowBox[{"a", ",", "c"}], "}"}], ",", "Reals"}], "]"}]}]], "Input", + CellChangeTimes->{{3.85110557431765*^9, 3.8511056740723743`*^9}, { + 3.851105716859529*^9, 3.851105727527089*^9}}, + CellLabel->"In[75]:=",ExpressionUUID->"3b75ff62-1208-4b58-b655-68377d662acd"], + +Cell[BoxData[ + RowBox[{"{", + RowBox[{"{", + RowBox[{ + RowBox[{"a", "\[Rule]", + RowBox[{ + SuperscriptBox["grayIn", + RowBox[{"-", "g"}]], " ", + RowBox[{"(", + RowBox[{ + RowBox[{ + SuperscriptBox["grayIn", "g"], " ", "grayOut"}], "+", + FractionBox[ + RowBox[{"grayOut", " ", + RowBox[{"(", + RowBox[{ + RowBox[{ + SuperscriptBox["grayIn", "g"], " ", + SuperscriptBox["max", "g"]}], "-", + RowBox[{ + SuperscriptBox["grayIn", "g"], " ", "grayOut", " ", + SuperscriptBox["max", "g"]}]}], ")"}]}], + RowBox[{ + RowBox[{"-", + SuperscriptBox["grayIn", "g"]}], "+", + RowBox[{"grayOut", " ", + SuperscriptBox["max", "g"]}]}]]}], ")"}]}]}], ",", + RowBox[{"c", "\[Rule]", + FractionBox[ + RowBox[{ + RowBox[{ + SuperscriptBox["grayIn", "g"], " ", + SuperscriptBox["max", "g"]}], "-", + RowBox[{ + SuperscriptBox["grayIn", "g"], " ", "grayOut", " ", + SuperscriptBox["max", "g"]}]}], + RowBox[{ + RowBox[{"-", + SuperscriptBox["grayIn", "g"]}], "+", + RowBox[{"grayOut", " ", + SuperscriptBox["max", "g"]}]}]]}]}], "}"}], "}"}]], "Input", + CellChangeTimes->{{3.8511057429499683`*^9, + 3.8511057506927233`*^9}},ExpressionUUID->"f681219b-ae92-4ee1-9c72-\ +8f6230ae16b0"], + +Cell[CellGroupData[{ + +Cell[BoxData[{ + RowBox[{"FullSimplify", "[", + RowBox[{ + SuperscriptBox["grayIn", + RowBox[{"-", "g"}]], " ", + RowBox[{"(", + RowBox[{ + RowBox[{ + SuperscriptBox["grayIn", "g"], " ", "grayOut"}], "+", + FractionBox[ + RowBox[{"grayOut", " ", + RowBox[{"(", + RowBox[{ + RowBox[{ + SuperscriptBox["grayIn", "g"], " ", + SuperscriptBox["max", "g"]}], "-", + RowBox[{ + SuperscriptBox["grayIn", "g"], " ", "grayOut", " ", + SuperscriptBox["max", "g"]}]}], ")"}]}], + RowBox[{ + RowBox[{"-", + SuperscriptBox["grayIn", "g"]}], "+", + RowBox[{"grayOut", " ", + SuperscriptBox["max", "g"]}]}]]}], ")"}]}], "]"}], "\n", + RowBox[{"FullSimplify", "[", + FractionBox[ + RowBox[{ + RowBox[{ + SuperscriptBox["grayIn", "g"], " ", + SuperscriptBox["max", "g"]}], "-", + RowBox[{ + SuperscriptBox["grayIn", "g"], " ", "grayOut", " ", + SuperscriptBox["max", "g"]}]}], + RowBox[{ + RowBox[{"-", + SuperscriptBox["grayIn", "g"]}], "+", + RowBox[{"grayOut", " ", + SuperscriptBox["max", "g"]}]}]], "]"}]}], "Input", + CellChangeTimes->{{3.8511057429499683`*^9, 3.851105752118469*^9}}, + CellLabel->"In[76]:=",ExpressionUUID->"069b5290-c47c-4724-8235-a9fa7fcdbc40"], + +Cell[BoxData[ + FractionBox[ + RowBox[{"grayOut", " ", + RowBox[{"(", + RowBox[{ + SuperscriptBox["grayIn", "g"], "-", + SuperscriptBox["max", "g"]}], ")"}]}], + RowBox[{ + SuperscriptBox["grayIn", "g"], "-", + RowBox[{"grayOut", " ", + SuperscriptBox["max", "g"]}]}]]], "Output", + CellChangeTimes->{3.851105754160964*^9}, + CellLabel->"Out[76]=",ExpressionUUID->"0294b7da-008a-4542-af2e-54f5dda8db8e"], + +Cell[BoxData[ + FractionBox[ + RowBox[{ + SuperscriptBox["grayIn", "g"], " ", + RowBox[{"(", + RowBox[{ + RowBox[{"-", "1"}], "+", "grayOut"}], ")"}], " ", + SuperscriptBox["max", "g"]}], + RowBox[{ + SuperscriptBox["grayIn", "g"], "-", + RowBox[{"grayOut", " ", + SuperscriptBox["max", "g"]}]}]]], "Output", + CellChangeTimes->{3.851105754163471*^9}, + CellLabel->"Out[77]=",ExpressionUUID->"c56ed691-653e-4568-865d-36c65cd13c59"] }, Open ]] }, Open ]] }, Open ]] }, WindowSize->{1172, 1639}, -WindowMargins->{{639, Automatic}, {Automatic, 0}}, +WindowMargins->{{438, Automatic}, {Automatic, 0}}, WindowStatusArea->None, -FrontEndVersion->"12.1 for Mac OS X x86 (64-bit) (June 19, 2020)", -StyleDefinitions->"Default.nb", -ExpressionUUID->"56e3dcab-3015-4648-a622-1af0a6f38259" +FrontEndVersion->"12.0 for Mac OS X x86 (64-bit) (April 8, 2019)", +StyleDefinitions->"Default.nb" ] (* End of Notebook Content *) @@ -8838,50 +8613,61 @@ Notebook[{ Cell[CellGroupData[{ Cell[580, 22, 590, 11, 190, "Subtitle",ExpressionUUID->"6d88bde0-85f5-40c1-906f-e8c139fd8433"], Cell[1173, 35, 196, 3, 30, "Input",ExpressionUUID->"410c9ead-a5b2-4c59-a953-4ebe80279cd3"], -Cell[1372, 40, 13921, 323, 1627, "Input",ExpressionUUID->"37bf4303-de95-435d-b24b-32a661b82f4c"] +Cell[1372, 40, 202, 4, 30, "Input",ExpressionUUID->"506a33a4-caf2-4b9e-a76c-c210efda15ae"], +Cell[1577, 46, 680, 17, 52, "Input",ExpressionUUID->"de6c33eb-c91c-4585-8f36-e1faf95ae0de"], +Cell[2260, 65, 13695, 316, 1648, "Input",ExpressionUUID->"37bf4303-de95-435d-b24b-32a661b82f4c"] }, Open ]], Cell[CellGroupData[{ -Cell[15330, 368, 664, 12, 131, "Subtitle",ExpressionUUID->"d2415515-7fdb-4cf3-a9ec-12f84688170e"], -Cell[15997, 382, 1204, 27, 134, "Subtitle",ExpressionUUID->"37397695-a526-4ab0-a6b6-5b35d7bd1ba2"], -Cell[17204, 411, 4877, 115, 220, "Input",ExpressionUUID->"64a40cba-fe9b-420c-bc1f-e2b70d44b247"], +Cell[15992, 386, 664, 12, 131, "Subtitle",ExpressionUUID->"d2415515-7fdb-4cf3-a9ec-12f84688170e"], +Cell[16659, 400, 1204, 27, 134, "Subtitle",ExpressionUUID->"37397695-a526-4ab0-a6b6-5b35d7bd1ba2"], +Cell[17866, 429, 4877, 115, 220, "Input",ExpressionUUID->"64a40cba-fe9b-420c-bc1f-e2b70d44b247"], Cell[CellGroupData[{ -Cell[22106, 530, 4127, 87, 304, "Input",ExpressionUUID->"ed06a09c-d067-4495-8651-30989ac4c29c"], -Cell[26236, 619, 4376, 73, 625, "Output",ExpressionUUID->"6b1dd3b9-aead-47ff-a023-bad9bf13c501"] +Cell[22768, 548, 4177, 88, 304, "Input",ExpressionUUID->"ed06a09c-d067-4495-8651-30989ac4c29c"], +Cell[26948, 638, 4954, 80, 577, "Output",ExpressionUUID->"a47f1b0f-054e-4bc3-8d67-92066cc56e58"] }, Open ]] }, Open ]], Cell[CellGroupData[{ -Cell[30661, 698, 579, 8, 85, "Subtitle",ExpressionUUID->"d4e4cb94-73e2-4e2a-b1a5-b1d3555e58f5"], -Cell[31243, 708, 1095, 24, 111, "Subtitle",ExpressionUUID->"ec63d430-0dac-41fa-a4a6-191808e02e5d"], +Cell[31951, 724, 625, 9, 85, "Subtitle",ExpressionUUID->"d4e4cb94-73e2-4e2a-b1a5-b1d3555e58f5"], +Cell[32579, 735, 1095, 24, 111, "Subtitle",ExpressionUUID->"ec63d430-0dac-41fa-a4a6-191808e02e5d"], Cell[CellGroupData[{ -Cell[32363, 736, 2578, 58, 241, "Input",ExpressionUUID->"572cd540-59b1-4415-8369-fb210674631e"], -Cell[34944, 796, 3034, 53, 549, "Output",ExpressionUUID->"07c8965f-0e25-48b0-9508-0543006a9e84"] +Cell[33699, 763, 3388, 78, 304, "Input",ExpressionUUID->"572cd540-59b1-4415-8369-fb210674631e"], +Cell[37090, 843, 3762, 65, 559, "Output",ExpressionUUID->"3744bea6-fd11-4b6f-a916-3680b3c52354"] }, Open ]] }, Open ]], Cell[CellGroupData[{ -Cell[38027, 855, 200, 2, 85, "Subtitle",ExpressionUUID->"f52f3550-62ce-4c0a-8b3f-94898be33e6f"], +Cell[40901, 914, 200, 2, 85, "Subtitle",ExpressionUUID->"f52f3550-62ce-4c0a-8b3f-94898be33e6f"], Cell[CellGroupData[{ -Cell[38252, 861, 434, 9, 69, "Subsubsection",ExpressionUUID->"2698dbdd-8e80-456d-8c8a-00d3ce5f78e8"], +Cell[41126, 920, 434, 9, 69, "Subsubsection",ExpressionUUID->"2698dbdd-8e80-456d-8c8a-00d3ce5f78e8"], Cell[CellGroupData[{ -Cell[38711, 874, 3594, 63, 115, "Input",ExpressionUUID->"68982d2b-cd3f-4394-b9b7-bc306b15c89c"], -Cell[42308, 939, 55353, 1057, 394, "Output",ExpressionUUID->"6ef38163-1cbf-4b9b-86ca-ec90ebdeada7"], -Cell[97664, 1998, 50494, 978, 373, "Output",ExpressionUUID->"ff04d788-1bb5-49c3-ab5f-f3b4468aaf5b"], -Cell[148161, 2978, 91254, 1649, 371, "Output",ExpressionUUID->"c85963ab-1c7d-41ff-bc3a-2dd458ecbd3a"] +Cell[41585, 933, 3594, 63, 115, "Input",ExpressionUUID->"68982d2b-cd3f-4394-b9b7-bc306b15c89c"], +Cell[45182, 998, 53788, 1027, 394, "Output",ExpressionUUID->"8ba6c080-8fed-416f-a337-7010a1374821"], +Cell[98973, 2027, 47188, 921, 373, "Output",ExpressionUUID->"ef97bf93-cf17-45b5-89fa-a2841a6cb5a8"], +Cell[146164, 2950, 87242, 1579, 371, "Output",ExpressionUUID->"19ba943e-39f9-4cf1-b1e6-0725c2861382"] }, Open ]] }, Open ]], Cell[CellGroupData[{ -Cell[239464, 4633, 435, 9, 69, "Subsubsection",ExpressionUUID->"5e5d37fc-2c2d-4b58-a172-ec4edbd83393"], +Cell[233455, 4535, 435, 9, 69, "Subsubsection",ExpressionUUID->"5e5d37fc-2c2d-4b58-a172-ec4edbd83393"], Cell[CellGroupData[{ -Cell[239924, 4646, 3298, 59, 115, "Input",ExpressionUUID->"b2cdebdd-b060-471e-8ddd-416664a1997c"], -Cell[243225, 4707, 68517, 1275, 398, "Output",ExpressionUUID->"5f3a1ac2-1732-4cba-b9ab-1746b32f50c9"], -Cell[311745, 5984, 60914, 1150, 375, "Output",ExpressionUUID->"65440c48-1b97-41ac-b448-e197bec4a3cf"], -Cell[372662, 7136, 62092, 1171, 371, "Output",ExpressionUUID->"45b95cdd-213f-4412-9335-ca0c706aafea"] +Cell[233915, 4548, 3298, 59, 115, "Input",ExpressionUUID->"b2cdebdd-b060-471e-8ddd-416664a1997c"], +Cell[237216, 4609, 62688, 1175, 398, "Output",ExpressionUUID->"29db7e14-81d3-4736-ad6e-420dc115fb7c"], +Cell[299907, 5786, 54750, 1047, 375, "Output",ExpressionUUID->"a58ca3ff-9312-48fd-ad43-9c47269ba50c"], +Cell[354660, 6835, 55934, 1066, 374, "Output",ExpressionUUID->"404ed04a-41a6-4d3a-ac7a-c48c85ee6358"] }, Open ]] }, Open ]], Cell[CellGroupData[{ -Cell[434803, 8313, 489, 9, 69, "Subsubsection",ExpressionUUID->"51e1f0a3-988f-4818-8a5e-654f64dbb13b"], +Cell[410643, 7907, 489, 9, 69, "Subsubsection",ExpressionUUID->"51e1f0a3-988f-4818-8a5e-654f64dbb13b"], Cell[CellGroupData[{ -Cell[435317, 8326, 765, 18, 73, "Input",ExpressionUUID->"adff9626-1268-4d99-9c85-40a974589dd7"], -Cell[436085, 8346, 24665, 468, 396, "Output",ExpressionUUID->"e310f9b8-0a7f-4c02-a543-63a67c823baa"] +Cell[411157, 7920, 765, 18, 73, "Input",ExpressionUUID->"adff9626-1268-4d99-9c85-40a974589dd7"], +Cell[411925, 7940, 24665, 468, 396, "Output",ExpressionUUID->"e310f9b8-0a7f-4c02-a543-63a67c823baa"] +}, Open ]], +Cell[436605, 8411, 768, 14, 64, "Input",ExpressionUUID->"7f835e78-9f1c-4cad-bda7-4fe00e88eaec"], +Cell[437376, 8427, 605, 16, 52, "Input",ExpressionUUID->"bf5a4c21-8b04-449f-b2c1-bc70887faac4"], +Cell[437984, 8445, 1171, 28, 136, "Input",ExpressionUUID->"3b75ff62-1208-4b58-b655-68377d662acd"], +Cell[439158, 8475, 1432, 43, 54, InheritFromParent,ExpressionUUID->"f681219b-ae92-4ee1-9c72-8f6230ae16b0"], +Cell[CellGroupData[{ +Cell[440615, 8522, 1302, 39, 100, InheritFromParent,ExpressionUUID->"069b5290-c47c-4724-8235-a9fa7fcdbc40"], +Cell[441920, 8563, 419, 12, 57, "Output",ExpressionUUID->"0294b7da-008a-4542-af2e-54f5dda8db8e"], +Cell[442342, 8577, 446, 13, 55, "Output",ExpressionUUID->"c56ed691-653e-4568-865d-36c65cd13c59"] }, Open ]] }, Open ]] }, Open ]] diff --git a/docs/math/data/aces.csv b/docs/math/data/aces.csv new file mode 100644 index 0000000000..79a31f9183 --- /dev/null +++ b/docs/math/data/aces.csv @@ -0,0 +1,1000 @@ +0,0 +0.01,0.00348969 +0.02,0.00874231 +0.03,0.0156214 +0.04,0.0239703 +0.05,0.0336322 +0.06,0.0444388 +0.07,0.0562532 +0.08,0.0689327 +0.09,0.0823419 +0.1,0.0963541 +0.11,0.110851 +0.12,0.125725 +0.13,0.140876 +0.14,0.156216 +0.15,0.171665 +0.16,0.187157 +0.17,0.202625 +0.18,0.218012 +0.19,0.233272 +0.2,0.248365 +0.21,0.263256 +0.22,0.277916 +0.23,0.292322 +0.24,0.306454 +0.25,0.320298 +0.26,0.333842 +0.27,0.347077 +0.28,0.359997 +0.29,0.372599 +0.3,0.384881 +0.31,0.396845 +0.32,0.40849 +0.33,0.41982 +0.34,0.430839 +0.35,0.441552 +0.36,0.451964 +0.37,0.462081 +0.38,0.47191 +0.39,0.481457 +0.4,0.49073 +0.41,0.499735 +0.42,0.50848 +0.43,0.516972 +0.44,0.525219 +0.45,0.533228 +0.46,0.541006 +0.47,0.548561 +0.48,0.555899 +0.49,0.563027 +0.5,0.569953 +0.51,0.576682 +0.52,0.583222 +0.53,0.589578 +0.54,0.595756 +0.55,0.601764 +0.56,0.607605 +0.57,0.613286 +0.58,0.618812 +0.59,0.624189 +0.6,0.629421 +0.61,0.634514 +0.62,0.639471 +0.63,0.644297 +0.64,0.648998 +0.65,0.653576 +0.66,0.658037 +0.67,0.662383 +0.68,0.666619 +0.69,0.670748 +0.7,0.674774 +0.71,0.678701 +0.72,0.68253 +0.73,0.686266 +0.74,0.689912 +0.75,0.69347 +0.76,0.696943 +0.77,0.700334 +0.78,0.703645 +0.79,0.706879 +0.8,0.710038 +0.81,0.713125 +0.82,0.716142 +0.83,0.719091 +0.839999,0.721974 +0.849999,0.724793 +0.859999,0.727549 +0.869999,0.730246 +0.879999,0.732884 +0.889999,0.735465 +0.899999,0.737991 +0.909999,0.740464 +0.919999,0.742885 +0.929999,0.745255 +0.939999,0.747576 +0.949999,0.74985 +0.959999,0.752077 +0.969999,0.75426 +0.979999,0.756398 +0.989999,0.758494 +0.999999,0.760549 +1.01,0.762563 +1.02,0.764538 +1.03,0.766475 +1.04,0.768375 +1.05,0.770239 +1.06,0.772068 +1.07,0.773862 +1.08,0.775623 +1.09,0.777352 +1.1,0.779048 +1.11,0.780714 +1.12,0.78235 +1.13,0.783957 +1.14,0.785535 +1.15,0.787085 +1.16,0.788608 +1.17,0.790104 +1.18,0.791575 +1.19,0.79302 +1.2,0.79444 +1.21,0.795837 +1.22,0.79721 +1.23,0.79856 +1.24,0.799888 +1.25,0.801193 +1.26,0.802478 +1.27,0.803741 +1.28,0.804985 +1.29,0.806208 +1.3,0.807412 +1.31,0.808596 +1.32,0.809763 +1.33,0.81091 +1.34,0.812041 +1.35,0.813153 +1.36,0.814249 +1.37,0.815328 +1.38,0.816391 +1.39,0.817437 +1.4,0.818468 +1.41,0.819484 +1.42,0.820485 +1.43,0.821471 +1.44,0.822443 +1.45,0.823401 +1.46,0.824345 +1.47,0.825275 +1.48,0.826192 +1.49,0.827097 +1.5,0.827988 +1.51,0.828868 +1.52,0.829735 +1.53,0.83059 +1.54,0.831433 +1.55,0.832266 +1.56,0.833086 +1.57,0.833896 +1.58,0.834695 +1.59,0.835483 +1.6,0.836261 +1.61,0.837028 +1.62,0.837786 +1.63,0.838534 +1.64,0.839272 +1.65,0.84 +1.66,0.84072 +1.67,0.84143 +1.68,0.842131 +1.69,0.842823 +1.7,0.843507 +1.71,0.844182 +1.72,0.844849 +1.73,0.845508 +1.74,0.846159 +1.75,0.846801 +1.76,0.847436 +1.77,0.848063 +1.78,0.848683 +1.79,0.849296 +1.8,0.849901 +1.81,0.850499 +1.82,0.85109 +1.83,0.851674 +1.84,0.852252 +1.85,0.852823 +1.86,0.853387 +1.87,0.853945 +1.88,0.854496 +1.89,0.855041 +1.9,0.855581 +1.91,0.856114 +1.92,0.856641 +1.93,0.857162 +1.94,0.857678 +1.95,0.858188 +1.96,0.858692 +1.97,0.859191 +1.98,0.859684 +1.99,0.860173 +2,0.860656 +2.01,0.861134 +2.02,0.861606 +2.03,0.862074 +2.04,0.862537 +2.05,0.862995 +2.06,0.863449 +2.07,0.863897 +2.08,0.864341 +2.09,0.864781 +2.1,0.865215 +2.11,0.865646 +2.12,0.866072 +2.13,0.866494 +2.14,0.866912 +2.15,0.867325 +2.16,0.867735 +2.17,0.86814 +2.18,0.868542 +2.19,0.868939 +2.2,0.869333 +2.21,0.869723 +2.22,0.870109 +2.23,0.870491 +2.24,0.870869 +2.25,0.871244 +2.26,0.871616 +2.27,0.871984 +2.28,0.872348 +2.29,0.87271 +2.3,0.873067 +2.31,0.873422 +2.32,0.873773 +2.33,0.874121 +2.34,0.874466 +2.35,0.874807 +2.36,0.875146 +2.37,0.875481 +2.38,0.875814 +2.39,0.876143 +2.4,0.876469 +2.41,0.876793 +2.42,0.877113 +2.43,0.877431 +2.44,0.877746 +2.45,0.878059 +2.46,0.878368 +2.47,0.878675 +2.48,0.878979 +2.49,0.879281 +2.5,0.87958 +2.51,0.879876 +2.52,0.88017 +2.53,0.880462 +2.54,0.880751 +2.55,0.881037 +2.56,0.881321 +2.57,0.881603 +2.58,0.881883 +2.59,0.88216 +2.6,0.882435 +2.61,0.882707 +2.62,0.882977 +2.63,0.883246 +2.64,0.883512 +2.65,0.883776 +2.66,0.884037 +2.67,0.884297 +2.68,0.884554 +2.69,0.88481 +2.7,0.885063 +2.71,0.885314 +2.72,0.885564 +2.73,0.885811 +2.74,0.886057 +2.75,0.8863 +2.76,0.886542 +2.77,0.886782 +2.78,0.88702 +2.79,0.887256 +2.8,0.88749 +2.81,0.887723 +2.82,0.887954 +2.83,0.888183 +2.84,0.88841 +2.85,0.888636 +2.86,0.88886 +2.87,0.889082 +2.88,0.889302 +2.89,0.889521 +2.9,0.889739 +2.91,0.889955 +2.92,0.890168 +2.93,0.890381 +2.94,0.890592 +2.95,0.890802 +2.96,0.891009 +2.97,0.891216 +2.98,0.891421 +2.99,0.891625 +3,0.891827 +3.01,0.892027 +3.02,0.892226 +3.03,0.892424 +3.04,0.89262 +3.05,0.892816 +3.06,0.893009 +3.07,0.893202 +3.08,0.893393 +3.09,0.893582 +3.1,0.89377 +3.11,0.893957 +3.12,0.894143 +3.13,0.894328 +3.14,0.894511 +3.15,0.894693 +3.16,0.894874 +3.17,0.895053 +3.18,0.895232 +3.19,0.895409 +3.2,0.895585 +3.21,0.895759 +3.22,0.895933 +3.23,0.896105 +3.24,0.896277 +3.25,0.896447 +3.26,0.896616 +3.27,0.896784 +3.28,0.896951 +3.29,0.897117 +3.3,0.897281 +3.31,0.897445 +3.32,0.897607 +3.33,0.897769 +3.34,0.897929 +3.35,0.898089 +3.36,0.898247 +3.37,0.898405 +3.38,0.898561 +3.39,0.898717 +3.4,0.898871 +3.41,0.899025 +3.42,0.899177 +3.43,0.899329 +3.44,0.89948 +3.45,0.899629 +3.46,0.899778 +3.47,0.899926 +3.48,0.900073 +3.49,0.900219 +3.5,0.900364 +3.51,0.900509 +3.52,0.900652 +3.53,0.900795 +3.54,0.900936 +3.55,0.901077 +3.56,0.901217 +3.57,0.901356 +3.58,0.901495 +3.59,0.901632 +3.6,0.901769 +3.61,0.901905 +3.62,0.90204 +3.63,0.902174 +3.64,0.902308 +3.65,0.90244 +3.66,0.902572 +3.67,0.902703 +3.68,0.902834 +3.69,0.902964 +3.7,0.903092 +3.71,0.903221 +3.72,0.903348 +3.73,0.903475 +3.74,0.903601 +3.75,0.903726 +3.76,0.903851 +3.77,0.903975 +3.78,0.904098 +3.79,0.90422 +3.8,0.904342 +3.81,0.904463 +3.82,0.904583 +3.83,0.904703 +3.84,0.904822 +3.85,0.90494 +3.86,0.905058 +3.87,0.905176 +3.88,0.905292 +3.89,0.905408 +3.9,0.905523 +3.91,0.905638 +3.92,0.905751 +3.93,0.905865 +3.94,0.905978 +3.95,0.90609 +3.96,0.906201 +3.97,0.906312 +3.98,0.906422 +3.99,0.906532 +4,0.906641 +4.01,0.90675 +4.02,0.906858 +4.03,0.906965 +4.04,0.907072 +4.05,0.907178 +4.06,0.907284 +4.07,0.907389 +4.08,0.907494 +4.09,0.907598 +4.1,0.907701 +4.11,0.907805 +4.12,0.907907 +4.13,0.908009 +4.14,0.90811 +4.15,0.908211 +4.16,0.908312 +4.17,0.908412 +4.18,0.908511 +4.19,0.90861 +4.2,0.908708 +4.21,0.908806 +4.22,0.908903 +4.23,0.909 +4.24,0.909097 +4.25,0.909192 +4.26,0.909288 +4.27,0.909383 +4.28,0.909477 +4.29,0.909572 +4.3,0.909665 +4.31,0.909758 +4.32,0.909851 +4.33,0.909943 +4.34,0.910035 +4.35,0.910126 +4.36,0.910217 +4.37001,0.910307 +4.38001,0.910397 +4.39001,0.910487 +4.40001,0.910576 +4.41001,0.910664 +4.42001,0.910753 +4.43001,0.910841 +4.44001,0.910928 +4.45001,0.911015 +4.46001,0.911101 +4.47001,0.911187 +4.48001,0.911273 +4.49001,0.911359 +4.50001,0.911444 +4.51001,0.911528 +4.52001,0.911612 +4.53001,0.911696 +4.54001,0.911779 +4.55001,0.911862 +4.56001,0.911945 +4.57001,0.912027 +4.58001,0.912109 +4.59001,0.91219 +4.60001,0.912271 +4.61001,0.912352 +4.62001,0.912432 +4.63001,0.912512 +4.64001,0.912592 +4.65001,0.912671 +4.66001,0.91275 +4.67001,0.912828 +4.68001,0.912907 +4.69001,0.912984 +4.70001,0.913062 +4.71001,0.913139 +4.72001,0.913216 +4.73001,0.913292 +4.74001,0.913368 +4.75001,0.913444 +4.76001,0.913519 +4.77001,0.913594 +4.78001,0.913669 +4.79001,0.913743 +4.80001,0.913817 +4.81002,0.913891 +4.82002,0.913964 +4.83002,0.914037 +4.84002,0.91411 +4.85002,0.914183 +4.86002,0.914255 +4.87002,0.914327 +4.88002,0.914398 +4.89002,0.914469 +4.90002,0.91454 +4.91002,0.914611 +4.92002,0.914681 +4.93002,0.914751 +4.94002,0.914821 +4.95002,0.91489 +4.96002,0.914959 +4.97002,0.915028 +4.98002,0.915097 +4.99002,0.915165 +5.00002,0.915232 +5.01002,0.9153 +5.02002,0.915367 +5.03002,0.915434 +5.04002,0.915501 +5.05002,0.915568 +5.06002,0.915634 +5.07002,0.9157 +5.08002,0.915765 +5.09002,0.915831 +5.10002,0.915896 +5.11002,0.915961 +5.12002,0.916026 +5.13002,0.91609 +5.14002,0.916154 +5.15002,0.916218 +5.16002,0.916281 +5.17002,0.916344 +5.18002,0.916407 +5.19002,0.91647 +5.20002,0.916533 +5.21002,0.916595 +5.22002,0.916657 +5.23002,0.916719 +5.24003,0.91678 +5.25003,0.916842 +5.26003,0.916903 +5.27003,0.916963 +5.28003,0.917024 +5.29003,0.917084 +5.30003,0.917144 +5.31003,0.917204 +5.32003,0.917264 +5.33003,0.917323 +5.34003,0.917382 +5.35003,0.917441 +5.36003,0.917499 +5.37003,0.917558 +5.38003,0.917616 +5.39003,0.917674 +5.40003,0.917731 +5.41003,0.917789 +5.42003,0.917846 +5.43003,0.917903 +5.44003,0.91796 +5.45003,0.918016 +5.46003,0.918073 +5.47003,0.918129 +5.48003,0.918185 +5.49003,0.918241 +5.50003,0.918296 +5.51003,0.918351 +5.52003,0.918407 +5.53003,0.918462 +5.54003,0.918516 +5.55003,0.918571 +5.56003,0.918625 +5.57003,0.918679 +5.58003,0.918733 +5.59003,0.918787 +5.60003,0.91884 +5.61003,0.918893 +5.62003,0.918946 +5.63003,0.918999 +5.64003,0.919052 +5.65003,0.919104 +5.66003,0.919156 +5.67003,0.919209 +5.68004,0.91926 +5.69004,0.919312 +5.70004,0.919363 +5.71004,0.919415 +5.72004,0.919466 +5.73004,0.919517 +5.74004,0.919567 +5.75004,0.919618 +5.76004,0.919668 +5.77004,0.919719 +5.78004,0.919769 +5.79004,0.919818 +5.80004,0.919868 +5.81004,0.919917 +5.82004,0.919967 +5.83004,0.920016 +5.84004,0.920065 +5.85004,0.920114 +5.86004,0.920162 +5.87004,0.92021 +5.88004,0.920258 +5.89004,0.920307 +5.90004,0.920355 +5.91004,0.920402 +5.92004,0.92045 +5.93004,0.920497 +5.94004,0.920544 +5.95004,0.920591 +5.96004,0.920638 +5.97004,0.920685 +5.98004,0.920731 +5.99004,0.920778 +6.00004,0.920824 +6.01004,0.92087 +6.02004,0.920916 +6.03004,0.920961 +6.04004,0.921007 +6.05004,0.921052 +6.06004,0.921098 +6.07004,0.921143 +6.08004,0.921188 +6.09004,0.921232 +6.10004,0.921277 +6.11004,0.921322 +6.12005,0.921366 +6.13005,0.92141 +6.14005,0.921454 +6.15005,0.921498 +6.16005,0.921542 +6.17005,0.921585 +6.18005,0.921628 +6.19005,0.921672 +6.20005,0.921715 +6.21005,0.921758 +6.22005,0.921801 +6.23005,0.921843 +6.24005,0.921886 +6.25005,0.921928 +6.26005,0.921971 +6.27005,0.922013 +6.28005,0.922055 +6.29005,0.922096 +6.30005,0.922138 +6.31005,0.92218 +6.32005,0.922221 +6.33005,0.922262 +6.34005,0.922303 +6.35005,0.922345 +6.36005,0.922386 +6.37005,0.922426 +6.38005,0.922467 +6.39005,0.922507 +6.40005,0.922548 +6.41005,0.922588 +6.42005,0.922628 +6.43005,0.922668 +6.44005,0.922708 +6.45005,0.922748 +6.46005,0.922787 +6.47005,0.922826 +6.48005,0.922866 +6.49005,0.922905 +6.50005,0.922944 +6.51005,0.922983 +6.52005,0.923022 +6.53005,0.92306 +6.54005,0.923099 +6.55006,0.923137 +6.56006,0.923175 +6.57006,0.923214 +6.58006,0.923252 +6.59006,0.92329 +6.60006,0.923328 +6.61006,0.923365 +6.62006,0.923403 +6.63006,0.92344 +6.64006,0.923477 +6.65006,0.923515 +6.66006,0.923552 +6.67006,0.923589 +6.68006,0.923626 +6.69006,0.923662 +6.70006,0.923699 +6.71006,0.923735 +6.72006,0.923772 +6.73006,0.923808 +6.74006,0.923845 +6.75006,0.923881 +6.76006,0.923916 +6.77006,0.923952 +6.78006,0.923988 +6.79006,0.924024 +6.80006,0.924059 +6.81006,0.924094 +6.82006,0.92413 +6.83006,0.924165 +6.84006,0.9242 +6.85006,0.924235 +6.86006,0.92427 +6.87006,0.924305 +6.88006,0.924339 +6.89006,0.924374 +6.90006,0.924408 +6.91006,0.924443 +6.92006,0.924477 +6.93006,0.924511 +6.94006,0.924545 +6.95006,0.924579 +6.96006,0.924613 +6.97006,0.924647 +6.98006,0.92468 +6.99007,0.924714 +7.00007,0.924747 +7.01007,0.92478 +7.02007,0.924814 +7.03007,0.924847 +7.04007,0.92488 +7.05007,0.924913 +7.06007,0.924946 +7.07007,0.924978 +7.08007,0.925011 +7.09007,0.925043 +7.10007,0.925076 +7.11007,0.925108 +7.12007,0.92514 +7.13007,0.925173 +7.14007,0.925205 +7.15007,0.925237 +7.16007,0.925269 +7.17007,0.9253 +7.18007,0.925332 +7.19007,0.925364 +7.20007,0.925395 +7.21007,0.925427 +7.22007,0.925458 +7.23007,0.925489 +7.24007,0.92552 +7.25007,0.925551 +7.26007,0.925582 +7.27007,0.925613 +7.28007,0.925644 +7.29007,0.925675 +7.30007,0.925705 +7.31007,0.925736 +7.32007,0.925766 +7.33007,0.925797 +7.34007,0.925827 +7.35007,0.925857 +7.36007,0.925887 +7.37007,0.925917 +7.38007,0.925947 +7.39007,0.925977 +7.40007,0.926006 +7.41007,0.926036 +7.42007,0.926066 +7.43008,0.926095 +7.44008,0.926125 +7.45008,0.926154 +7.46008,0.926183 +7.47008,0.926212 +7.48008,0.926241 +7.49008,0.926271 +7.50008,0.9263 +7.51008,0.926328 +7.52008,0.926357 +7.53008,0.926386 +7.54008,0.926414 +7.55008,0.926443 +7.56008,0.926471 +7.57008,0.9265 +7.58008,0.926528 +7.59008,0.926556 +7.60008,0.926584 +7.61008,0.926612 +7.62008,0.92664 +7.63008,0.926668 +7.64008,0.926696 +7.65008,0.926724 +7.66008,0.926752 +7.67008,0.926779 +7.68008,0.926807 +7.69008,0.926834 +7.70008,0.926861 +7.71008,0.926889 +7.72008,0.926916 +7.73008,0.926943 +7.74008,0.92697 +7.75008,0.926997 +7.76008,0.927024 +7.77008,0.927051 +7.78008,0.927078 +7.79008,0.927105 +7.80008,0.927131 +7.81008,0.927158 +7.82008,0.927184 +7.83008,0.927211 +7.84008,0.927237 +7.85008,0.927263 +7.86009,0.92729 +7.87009,0.927316 +7.88009,0.927342 +7.89009,0.927368 +7.90009,0.927394 +7.91009,0.92742 +7.92009,0.927446 +7.93009,0.927471 +7.94009,0.927497 +7.95009,0.927523 +7.96009,0.927548 +7.97009,0.927574 +7.98009,0.927599 +7.99009,0.927624 +8.00009,0.92765 +8.01009,0.927675 +8.02009,0.9277 +8.03009,0.927725 +8.04009,0.92775 +8.05009,0.927775 +8.06009,0.9278 +8.07009,0.927825 +8.08009,0.927849 +8.09009,0.927874 +8.10009,0.927899 +8.11009,0.927923 +8.12009,0.927948 +8.13009,0.927973 +8.14009,0.927997 +8.15009,0.928021 +8.16009,0.928045 +8.17009,0.928069 +8.18009,0.928094 +8.19009,0.928118 +8.20009,0.928142 +8.21009,0.928166 +8.22009,0.92819 +8.23009,0.928213 +8.24009,0.928237 +8.25009,0.928261 +8.26009,0.928284 +8.27009,0.928308 +8.28009,0.928332 +8.29009,0.928355 +8.30009,0.928378 +8.31009,0.928402 +8.3201,0.928425 +8.3301,0.928448 +8.3401,0.928472 +8.3501,0.928494 +8.3601,0.928518 +8.3701,0.92854 +8.3801,0.928564 +8.3901,0.928586 +8.4001,0.928609 +8.4101,0.928632 +8.4201,0.928655 +8.4301,0.928677 +8.4401,0.9287 +8.4501,0.928723 +8.4601,0.928745 +8.4701,0.928768 +8.4801,0.92879 +8.4901,0.928812 +8.5001,0.928835 +8.5101,0.928857 +8.5201,0.928879 +8.5301,0.928901 +8.5401,0.928923 +8.5501,0.928945 +8.5601,0.928967 +8.5701,0.928989 +8.5801,0.929011 +8.5901,0.929033 +8.6001,0.929054 +8.6101,0.929076 +8.6201,0.929098 +8.6301,0.929119 +8.6401,0.929141 +8.6501,0.929162 +8.6601,0.929184 +8.6701,0.929205 +8.6801,0.929226 +8.6901,0.929248 +8.7001,0.929269 +8.7101,0.92929 +8.7201,0.929311 +8.7301,0.929332 +8.7401,0.929353 +8.7501,0.929374 +8.76011,0.929395 +8.77011,0.929416 +8.78011,0.929437 +8.79011,0.929458 +8.80011,0.929478 +8.81011,0.929499 +8.82011,0.92952 +8.83011,0.92954 +8.84011,0.929561 +8.85011,0.929581 +8.86011,0.929602 +8.87011,0.929622 +8.88011,0.929642 +8.89011,0.929663 +8.90011,0.929683 +8.91011,0.929703 +8.92011,0.929723 +8.93011,0.929743 +8.94011,0.929763 +8.95011,0.929783 +8.96011,0.929803 +8.97011,0.929823 +8.98011,0.929843 +8.99011,0.929863 +9.00011,0.929883 +9.01011,0.929903 +9.02011,0.929922 +9.03011,0.929942 +9.04011,0.929961 +9.05011,0.929981 +9.06011,0.93 +9.07011,0.93002 +9.08011,0.930039 +9.09011,0.930059 +9.10011,0.930078 +9.11011,0.930097 +9.12011,0.930117 +9.13011,0.930136 +9.14011,0.930155 +9.15011,0.930174 +9.16011,0.930193 +9.17011,0.930212 +9.18011,0.930231 +9.19011,0.93025 +9.20012,0.930269 +9.21012,0.930288 +9.22012,0.930306 +9.23012,0.930325 +9.24012,0.930344 +9.25012,0.930363 +9.26012,0.930381 +9.27012,0.9304 +9.28012,0.930418 +9.29012,0.930437 +9.30012,0.930456 +9.31012,0.930474 +9.32012,0.930492 +9.33012,0.930511 +9.34012,0.930529 +9.35012,0.930547 +9.36012,0.930566 +9.37012,0.930584 +9.38012,0.930602 +9.39012,0.93062 +9.40012,0.930638 +9.41012,0.930656 +9.42012,0.930674 +9.43012,0.930692 +9.44012,0.93071 +9.45012,0.930728 +9.46012,0.930746 +9.47012,0.930763 +9.48012,0.930781 +9.49012,0.930799 +9.50012,0.930816 +9.51012,0.930834 +9.52012,0.930852 +9.53012,0.930869 +9.54012,0.930887 +9.55012,0.930904 +9.56012,0.930922 +9.57012,0.930939 +9.58012,0.930956 +9.59012,0.930974 +9.60012,0.930991 +9.61012,0.931008 +9.62012,0.931026 +9.63013,0.931043 +9.64013,0.93106 +9.65013,0.931077 +9.66013,0.931094 +9.67013,0.931111 +9.68013,0.931128 +9.69013,0.931145 +9.70013,0.931162 +9.71013,0.931179 +9.72013,0.931196 +9.73013,0.931213 +9.74013,0.931229 +9.75013,0.931246 +9.76013,0.931263 +9.77013,0.931279 +9.78013,0.931296 +9.79013,0.931313 +9.80013,0.931329 +9.81013,0.931346 +9.82013,0.931362 +9.83013,0.931379 +9.84013,0.931395 +9.85013,0.931412 +9.86013,0.931428 +9.87013,0.931444 +9.88013,0.93146 +9.89013,0.931477 +9.90013,0.931493 +9.91013,0.931509 +9.92013,0.931526 +9.93013,0.931542 +9.94013,0.931558 +9.95013,0.931574 +9.96013,0.93159 +9.97013,0.931606 +9.98013,0.931622 +9.99013,0.931638 From afd657d6af0dc7ead4a083685a6e13815e0a9902 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Thu, 13 Jan 2022 16:54:47 -0800 Subject: [PATCH 13/22] Fix, incorrect refractions with screen-space reflections disabled (#5051) --- filament/src/View.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/filament/src/View.cpp b/filament/src/View.cpp index f3825eec05..f0bab476ab 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -658,10 +658,11 @@ void FView::prepareSSAO(Handle ssao) const noexcept { } void FView::prepareSSR(Handle ssr, float refractionLodOffset) const noexcept { - mPerViewUniforms.prepareSSR(ssr, refractionLodOffset); // If screen-space refractions are enabled, make sure to disable screen-space reflections. + // disableSSReflections binds a dummy texture to light_ssr, but this is overridden by prepareSSR. // TODO: support simultaneous screen-space refractions and reflections. mPerViewUniforms.disableSSReflections(); + mPerViewUniforms.prepareSSR(ssr, refractionLodOffset); } void FView::disableSSReflections() const noexcept { From 8d1402d37b0b87cc64ef64550b1f000b9b0a16fd Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 14 Jan 2022 13:46:33 -0800 Subject: [PATCH 14/22] Bring back the 3x3 filter for PCF shadows The same can be achieved with VSM + blur=3 however, this is noticeably slower. Also when using transparent shadows, VSM needs a much larger blur to iron out the dithering pattern. Fixes #5048, #5039 --- shaders/src/shadowing.fs | 70 +++++++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/shaders/src/shadowing.fs b/shaders/src/shadowing.fs index 11f80a4558..e4b02bf063 100644 --- a/shaders/src/shadowing.fs +++ b/shaders/src/shadowing.fs @@ -8,6 +8,11 @@ #define SHADOW_SAMPLING_RUNTIME_DPCF 2u #define SHADOW_SAMPLING_RUNTIME_PCSS 3u +// TODO: this should be user-settable, maybe at the material level +#define SHADOW_SAMPLING_PCF_HARD 0 +#define SHADOW_SAMPLING_PCF_LOW 1 +#define SHADOW_SAMPLING_METHOD SHADOW_SAMPLING_PCF_LOW + //------------------------------------------------------------------------------ // PCF Shadow Sampling //------------------------------------------------------------------------------ @@ -20,30 +25,70 @@ float sampleDepth(const mediump sampler2DArrayShadow map, const uint layer, return texture(map, vec4(uv, layer, saturate(depth))); } +#if SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_HARD // use hardware assisted PCF -float ShadowSample_PCF(const mediump sampler2DArrayShadow map, +float ShadowSample_PCF_Hard(const mediump sampler2DArrayShadow map, const uint layer, const highp vec4 shadowPosition) { highp vec3 position = shadowPosition.xyz * (1.0 / shadowPosition.w); // note: shadowPosition.z is in the [1, 0] range (reversed Z) return sampleDepth(map, layer, position.xy, position.z); } +#endif -// use manual PCF -float ShadowSample_PCF(const mediump sampler2DArray shadowMap, +#if SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_LOW +// use hardware assisted PCF + 3x3 gaussian filter +float ShadowSample_PCF_Low(const mediump sampler2DArrayShadow map, const uint layer, const highp vec4 shadowPosition) { highp vec3 position = shadowPosition.xyz * (1.0 / shadowPosition.w); // note: shadowPosition.z is in the [1, 0] range (reversed Z) - highp vec2 size = vec2(textureSize(shadowMap, 0)); + highp vec2 size = vec2(textureSize(map, 0)); + highp vec2 texelSize = vec2(1.0) / size; + + // Castaño, 2013, "Shadow Mapping Summary Part 1" + float depth = position.z; + + // clamp position to avoid overflows below, which cause some GPUs to abort + position.xy = clamp(position.xy, vec2(-1.0), vec2(2.0)); + + vec2 offset = vec2(0.5); + highp vec2 uv = (position.xy * size) + offset; + highp vec2 base = (floor(uv) - offset) * texelSize; + highp vec2 st = fract(uv); + + vec2 uw = vec2(3.0 - 2.0 * st.x, 1.0 + 2.0 * st.x); + vec2 vw = vec2(3.0 - 2.0 * st.y, 1.0 + 2.0 * st.y); + + highp vec2 u = vec2((2.0 - st.x) / uw.x - 1.0, st.x / uw.y + 1.0); + highp vec2 v = vec2((2.0 - st.y) / vw.x - 1.0, st.y / vw.y + 1.0); + + u *= texelSize.x; + v *= texelSize.y; + + float sum = 0.0; + sum += uw.x * vw.x * sampleDepth(map, layer, base + vec2(u.x, v.x), depth); + sum += uw.y * vw.x * sampleDepth(map, layer, base + vec2(u.y, v.x), depth); + sum += uw.x * vw.y * sampleDepth(map, layer, base + vec2(u.x, v.y), depth); + sum += uw.y * vw.y * sampleDepth(map, layer, base + vec2(u.y, v.y), depth); + return sum * (1.0 / 16.0); +} +#endif + +// use manual PCF +float ShadowSample_PCF(const mediump sampler2DArray map, + const uint layer, const highp vec4 shadowPosition) { + highp vec3 position = shadowPosition.xyz * (1.0 / shadowPosition.w); + // note: shadowPosition.z is in the [1, 0] range (reversed Z) + highp vec2 size = vec2(textureSize(map, 0)); highp vec2 st = position.xy * size - 0.5; vec4 d; #if defined(FILAMENT_HAS_FEATURE_TEXTURE_GATHER) - d = textureGather(shadowMap, vec3(position.xy, layer), 0); // 01, 11, 10, 00 + d = textureGather(map, vec3(position.xy, layer), 0); // 01, 11, 10, 00 #else highp ivec3 tc = ivec3(st, layer); - d[0] = texelFetchOffset(shadowMap, tc, 0, ivec2(0, 1)).r; - d[1] = texelFetchOffset(shadowMap, tc, 0, ivec2(1, 1)).r; - d[2] = texelFetchOffset(shadowMap, tc, 0, ivec2(1, 0)).r; - d[3] = texelFetchOffset(shadowMap, tc, 0, ivec2(0, 0)).r; + d[0] = texelFetchOffset(map, tc, 0, ivec2(0, 1)).r; + d[1] = texelFetchOffset(map, tc, 0, ivec2(1, 1)).r; + d[2] = texelFetchOffset(map, tc, 0, ivec2(1, 0)).r; + d[3] = texelFetchOffset(map, tc, 0, ivec2(0, 0)).r; #endif vec4 pcf = step(0.0, position.zzzz - d); highp vec2 grad = fract(st); @@ -454,8 +499,11 @@ float shadow(const bool DIRECTIONAL, shadowPosition = getSpotLightSpacePosition(index, zLight); #endif } - - return ShadowSample_PCF(shadowMap, layer, shadowPosition); +#if SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_HARD + return ShadowSample_PCF_Hard(shadowMap, layer, shadowPosition); +#elif SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_LOW + return ShadowSample_PCF_Low(shadowMap, layer, shadowPosition); +#endif } // Shadow requiring a sampler2D sampler (VSM, DPCF and PCSS) From 62bb25b26d7032ee565b363f0a3bd3b8130351ec Mon Sep 17 00:00:00 2001 From: daemyung jang Date: Thu, 13 Jan 2022 11:53:36 +0900 Subject: [PATCH 15/22] Fix, potential null dereferences in OpenGLProgram Morphing is enabled but no morph targets in some models. --- filament/backend/src/opengl/OpenGLProgram.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/filament/backend/src/opengl/OpenGLProgram.cpp b/filament/backend/src/opengl/OpenGLProgram.cpp index 1cd646a8ac..fc40db008a 100644 --- a/filament/backend/src/opengl/OpenGLProgram.cpp +++ b/filament/backend/src/opengl/OpenGLProgram.cpp @@ -272,6 +272,13 @@ void OpenGLProgram::updateSamplers(OpenGLDriver* gld) noexcept { for (uint8_t i = 0, tmu = 0, n = mUsedBindingsCount; i < n; i++) { BlockInfo blockInfo = blockInfos[i]; HwSamplerGroup const * const UTILS_RESTRICT hwsb = samplerBindings[blockInfo.binding]; + if (UTILS_UNLIKELY(!hwsb)) { +#ifndef NDEBUG + slog.w << "In material " << name.c_str() + << ": no sampler group to " << (uint32_t) blockInfo.binding << io::endl; +#endif + continue; + } SamplerGroup const& UTILS_RESTRICT sb = *(hwsb->sb); SamplerGroup::Sampler const* const UTILS_RESTRICT samplers = sb.getSamplers(); for (uint8_t j = 0, m = blockInfo.count ; j <= m; ++j, ++tmu) { // "<=" on purpose here From 2fa6e6d0310514f4349e2b5dd503bc80f643f6b3 Mon Sep 17 00:00:00 2001 From: daemyung jang Date: Mon, 17 Jan 2022 10:43:54 +0900 Subject: [PATCH 16/22] Remove an useless log --- filament/backend/src/opengl/OpenGLProgram.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/filament/backend/src/opengl/OpenGLProgram.cpp b/filament/backend/src/opengl/OpenGLProgram.cpp index fc40db008a..fdf78e1d5f 100644 --- a/filament/backend/src/opengl/OpenGLProgram.cpp +++ b/filament/backend/src/opengl/OpenGLProgram.cpp @@ -273,10 +273,6 @@ void OpenGLProgram::updateSamplers(OpenGLDriver* gld) noexcept { BlockInfo blockInfo = blockInfos[i]; HwSamplerGroup const * const UTILS_RESTRICT hwsb = samplerBindings[blockInfo.binding]; if (UTILS_UNLIKELY(!hwsb)) { -#ifndef NDEBUG - slog.w << "In material " << name.c_str() - << ": no sampler group to " << (uint32_t) blockInfo.binding << io::endl; -#endif continue; } SamplerGroup const& UTILS_RESTRICT sb = *(hwsb->sb); From caaa404d968743e22ecdffe210810b2d984fff18 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 14 Jan 2022 17:00:24 -0800 Subject: [PATCH 17/22] add basic support for Android Thermal Manager --- libs/utils/CMakeLists.txt | 3 + libs/utils/include/utils/ThermalManager.h | 26 ++++++++ .../include/utils/android/ThermalManager.h | 60 +++++++++++++++++++ .../include/utils/generic/ThermalManager.h | 56 +++++++++++++++++ libs/utils/src/android/ThermalManager.cpp | 55 +++++++++++++++++ 5 files changed, 200 insertions(+) create mode 100644 libs/utils/include/utils/ThermalManager.h create mode 100644 libs/utils/include/utils/android/ThermalManager.h create mode 100644 libs/utils/include/utils/generic/ThermalManager.h create mode 100644 libs/utils/src/android/ThermalManager.cpp diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index 468dd51fdd..f2cb243566 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -73,6 +73,9 @@ set(SRCS if (WIN32) list(APPEND SRCS src/win32/Path.cpp) endif() +if (ANDROID) + list(APPEND SRCS src/android/ThermalManager.cpp) +endif() if (LINUX OR ANDROID) list(APPEND SRCS src/linux/Condition.cpp) list(APPEND SRCS src/linux/Mutex.cpp) diff --git a/libs/utils/include/utils/ThermalManager.h b/libs/utils/include/utils/ThermalManager.h new file mode 100644 index 0000000000..fc38d88edf --- /dev/null +++ b/libs/utils/include/utils/ThermalManager.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_UTILS_THERMALMANAGER_H +#define TNT_UTILS_THERMALMANAGER_H + +#if defined(__ANDROID__) +#include +#else +#include +#endif + +#endif // TNT_UTILS_THERMALMANAGER_H diff --git a/libs/utils/include/utils/android/ThermalManager.h b/libs/utils/include/utils/android/ThermalManager.h new file mode 100644 index 0000000000..6c303b04bb --- /dev/null +++ b/libs/utils/include/utils/android/ThermalManager.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_UTILS_ANDROID_THERMALMANAGER_H +#define TNT_UTILS_ANDROID_THERMALMANAGER_H + +#include + +#include + +struct AThermalManager; + +namespace utils { + +class ThermalManager { +public: + enum class ThermalStatus : int8_t { + ERROR = -1, + NONE, + LIGHT, + MODERATE, + SEVERE, + CRITICAL, + EMERGENCY, + SHUTDOWN + }; + + ThermalManager(); + ~ThermalManager(); + + // Movable + ThermalManager(ThermalManager&& rhs) noexcept; + ThermalManager& operator=(ThermalManager&& rhs) noexcept; + + // not copiable + ThermalManager(ThermalManager const& rhs) = delete; + ThermalManager& operator=(ThermalManager const& rhs) = delete; + + ThermalStatus getCurrentThermalStatus() const noexcept; + +private: + AThermalManager* mThermalManager = nullptr; +}; + +} // namespace utils + +#endif // TNT_UTILS_ANDROID_THERMALMANAGER_H diff --git a/libs/utils/include/utils/generic/ThermalManager.h b/libs/utils/include/utils/generic/ThermalManager.h new file mode 100644 index 0000000000..2d0088e561 --- /dev/null +++ b/libs/utils/include/utils/generic/ThermalManager.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_UTILS_GENERIC_THERMALMANAGER_H +#define TNT_UTILS_GENERIC_THERMALMANAGER_H + +#include + +#include + +namespace utils { + +class ThermalManager { +public: + enum class ThermalStatus : int8_t { + ERROR = -1, + NONE, + LIGHT, + MODERATE, + SEVERE, + CRITICAL, + EMERGENCY, + SHUTDOWN + }; + + ThermalManager() = default; + + // Movable + ThermalManager(ThermalManager&& rhs) noexcept = default; + ThermalManager& operator=(ThermalManager&& rhs) noexcept = default; + + // not copiable + ThermalManager(ThermalManager const& rhs) = delete; + ThermalManager& operator=(ThermalManager const& rhs) = delete; + + ThermalStatus getCurrentThermalStatus() const noexcept { + return ThermalStatus::NONE; + } +}; + +} // namespace utils + +#endif // TNT_UTILS_GENERIC_THERMALMANAGER_H diff --git a/libs/utils/src/android/ThermalManager.cpp b/libs/utils/src/android/ThermalManager.cpp new file mode 100644 index 0000000000..6009abc927 --- /dev/null +++ b/libs/utils/src/android/ThermalManager.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include + +namespace utils { + +ThermalManager::ThermalManager() { + if (__builtin_available(android 30, *)) { + mThermalManager = AThermal_acquireManager(); + } +} + +ThermalManager::~ThermalManager() { + if (__builtin_available(android 30, *)) { + AThermal_releaseManager(mThermalManager); + } +} + +ThermalManager::ThermalManager(ThermalManager&& rhs) noexcept + : mThermalManager(rhs.mThermalManager) { + rhs.mThermalManager = nullptr; +} + +ThermalManager& ThermalManager::operator=(ThermalManager&& rhs) noexcept { + std::swap(mThermalManager, rhs.mThermalManager); + return *this; +} + +ThermalManager::ThermalStatus ThermalManager::getCurrentThermalStatus() const noexcept { + if (__builtin_available(android 30, *)) { + return (ThermalManager::ThermalStatus)AThermal_getCurrentThermalStatus(mThermalManager); + } else { + return ThermalStatus::NONE; + } +} + +} // namespace utils From 0fb04c576d3b8a1b5c7479a4a900ca0f41309091 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 14 Jan 2022 16:08:28 -0800 Subject: [PATCH 18/22] improve structure pass performance when not used Move the command generation of the structure pass into its execute block, meaning that if the structure pass is culled, the command generation is now skipped too. --- filament/src/PostProcessManager.cpp | 10 +++++++--- filament/src/PostProcessManager.h | 3 ++- filament/src/Renderer.cpp | 13 ++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index c680367385..e267e0a12e 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -317,7 +317,8 @@ void PostProcessManager::commitAndRender(FrameGraphResources::RenderPassInfo con // ------------------------------------------------------------------------------------------------ FrameGraphId PostProcessManager::structure(FrameGraph& fg, - RenderPass const& pass, uint32_t width, uint32_t height, + RenderPass const& pass, uint8_t structureRenderFlags, + uint32_t width, uint32_t height, StructurePassConfig const& config) noexcept { const float scale = config.scale; @@ -367,10 +368,13 @@ FrameGraphId PostProcessManager::structure(FrameGraph& fg, .clearFlags = TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH }); }, - [=](FrameGraphResources const& resources, + [=, renderPass = pass](FrameGraphResources const& resources, auto const& data, DriverApi& driver) mutable { auto out = resources.getRenderPassInfo(); - pass.execute(resources.getPassName(), out.target, out.params); + renderPass.setRenderFlags(structureRenderFlags); + renderPass.appendCommands(RenderPass::CommandTypeFlags::SSAO); + renderPass.sortCommands(); + renderPass.execute(resources.getPassName(), out.target, out.params); }); auto depth = structurePass->depth; diff --git a/filament/src/PostProcessManager.h b/filament/src/PostProcessManager.h index 74c626cc9a..078c981f36 100644 --- a/filament/src/PostProcessManager.h +++ b/filament/src/PostProcessManager.h @@ -70,7 +70,8 @@ public: // methods below are ordered relative to their position in the pipeline (as much as possible) // structure (depth) pass - FrameGraphId structure(FrameGraph& fg, RenderPass const& pass, + FrameGraphId structure(FrameGraph& fg, + RenderPass const& pass, uint8_t structureRenderFlags, uint32_t width, uint32_t height, StructurePassConfig const& config) noexcept; // SSAO diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp index 6fc0981834..4e29c0693e 100644 --- a/filament/src/Renderer.cpp +++ b/filament/src/Renderer.cpp @@ -438,14 +438,8 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { // Currently it consists of a simple depth pass. // This is normally used by SSAO and contact-shadows - // TODO: ideally this should be a FrameGraph pass to participate to automatic culling - RenderPass structurePass(pass); - structurePass.setRenderFlags(structureRenderFlags); - structurePass.appendCommands(RenderPass::CommandTypeFlags::SSAO); - structurePass.sortCommands(); - // TODO: the scaling should depends on all passes that need the structure pass - ppm.structure(fg, structurePass, svp.width, svp.height, { + ppm.structure(fg, pass, structureRenderFlags, svp.width, svp.height, { .scale = aoOptions.resolution, .picking = view.hasPicking() }); @@ -510,7 +504,8 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { // -------------------------------------------------------------------------------------------- // Color passes - // TODO: ideally this should be a FrameGraph pass to participate to automatic culling + // This one doesn't need to be a FrameGraph pass because it always happens by construction + // (i.e. it won't be culled, unless everything is culled), so no need to complexify things. pass.setRenderFlags(colorRenderFlags); pass.appendCommands(RenderPass::COLOR); pass.sortCommands(); @@ -549,7 +544,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { colorGradingConfigForColor.asSubpass = colorGradingConfigForColor.asSubpass && !taaOptions.enabled; if (colorGradingConfigForColor.asSubpass) { - // append colorgrading subpass after all other passes + // append color grading subpass after all other passes pass.appendCustomCommand( RenderPass::Pass::BLENDED, RenderPass::CustomCommand::EPILOG, From bf3f7e0aef22de505beebe4c8afb82f2f772f890 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Tue, 18 Jan 2022 13:44:48 -0800 Subject: [PATCH 19/22] Fix, assertion with shadowReceiver = false and VSM (#5055) --- libs/filabridge/include/private/filament/Variant.h | 4 ++++ libs/viewer/src/SimpleViewer.cpp | 3 +++ 2 files changed, 7 insertions(+) diff --git a/libs/filabridge/include/private/filament/Variant.h b/libs/filabridge/include/private/filament/Variant.h index 596acfbba3..efe12ddc39 100644 --- a/libs/filabridge/include/private/filament/Variant.h +++ b/libs/filabridge/include/private/filament/Variant.h @@ -185,6 +185,10 @@ namespace filament { if (isValidDepthVariant(variantKey)) { return variantKey; } + // if shadow receiver is disabled, turn off VSM + if (isLit && !(variantKey & SHADOW_RECEIVER)) { + return variantKey & ~VSM; + } // when the shading mode is unlit, remove all the lighting variants return isLit ? variantKey : (variantKey & UNLIT_MASK); } diff --git a/libs/viewer/src/SimpleViewer.cpp b/libs/viewer/src/SimpleViewer.cpp index 111840c412..a56f398c3c 100644 --- a/libs/viewer/src/SimpleViewer.cpp +++ b/libs/viewer/src/SimpleViewer.cpp @@ -586,8 +586,11 @@ void SimpleViewer::updateUserInterface() { } auto instance = rm.getInstance(entity); bool scaster = rm.isShadowCaster(instance); + bool sreceiver = rm.isShadowReceiver(instance); ImGui::Checkbox("casts shadows", &scaster); rm.setCastShadows(instance, scaster); + ImGui::Checkbox("receives shadows", &sreceiver); + rm.setReceiveShadows(instance, sreceiver); size_t numPrims = rm.getPrimitiveCount(instance); for (size_t prim = 0; prim < numPrims; ++prim) { const char* mname = rm.getMaterialInstanceAt(instance, prim)->getName(); From a8d72bb226df1c180eee15ab1bccd507caf51adc Mon Sep 17 00:00:00 2001 From: Benjamin Doherty Date: Tue, 18 Jan 2022 13:49:02 -0800 Subject: [PATCH 20/22] Release Filament 1.16.1 --- README.md | 4 ++-- RELEASE_NOTES.md | 8 ++++++-- android/gradle.properties | 2 +- ios/CocoaPods/Filament.podspec | 4 ++-- web/filament-js/package.json | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 65b0e4930e..d66791b606 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.16.0' + implementation 'com.google.android.filament:filament-android:1.16.1' } ``` @@ -52,7 +52,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ``` -pod 'Filament', '~> 1.16.0' +pod 'Filament', '~> 1.16.1' ``` ### Snapshots diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b2f909e627..3f7f1fc0d6 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -3,13 +3,17 @@ This file contains one line summaries of commits that are worthy of mentioning in release notes. A new header is inserted each time a *tag* is created. -## v1.16.2 (currently main branch) +## v1.17.1 (currently main branch) -## v1.16.1 +## v1.17.0 - engine: add experimental glossy screen-space reflections [⚠️ **Materials need to be rebuilt to access this new feature**]. +## v1.16.1 + +- engine: Added line/triangle strip support. + ## v1.16.0 - engine: Fixes skinning calculations (#4887) [⚠️ **Material breakage**]. diff --git a/android/gradle.properties b/android/gradle.properties index 8dd051c616..e3e89a33ee 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.16.0 +VERSION_NAME=1.16.1 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index 710d5a22d2..759d3047e7 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Filament" - spec.version = "1.16.0" + spec.version = "1.16.1" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.16.0/filament-v1.16.0-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.16.1/filament-v1.16.1-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/web/filament-js/package.json b/web/filament-js/package.json index b2a118e858..bd0ec5807c 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.16.0", + "version": "1.16.1", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js", From 8cd720b53ab1a8d8a4e03338e24aeaf49d19cd6d Mon Sep 17 00:00:00 2001 From: Benjamin Doherty Date: Tue, 18 Jan 2022 13:54:40 -0800 Subject: [PATCH 21/22] Bump version to 1.17.0 --- README.md | 4 ++-- android/gradle.properties | 2 +- ios/CocoaPods/Filament.podspec | 4 ++-- web/filament-js/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d66791b606..a581cabf28 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.16.1' + implementation 'com.google.android.filament:filament-android:1.17.0' } ``` @@ -52,7 +52,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ``` -pod 'Filament', '~> 1.16.1' +pod 'Filament', '~> 1.17.0' ``` ### Snapshots diff --git a/android/gradle.properties b/android/gradle.properties index e3e89a33ee..d446d4eeb9 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.16.1 +VERSION_NAME=1.17.0 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index 759d3047e7..88c9b84e2b 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Filament" - spec.version = "1.16.1" + spec.version = "1.17.0" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.16.1/filament-v1.16.1-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.17.0/filament-v1.17.0-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/web/filament-js/package.json b/web/filament-js/package.json index bd0ec5807c..bf1082bc9e 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.16.1", + "version": "1.17.0", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js", From a99c6959320dfcd9c72ddb92a6646a9b23a08415 Mon Sep 17 00:00:00 2001 From: Benjamin Doherty Date: Mon, 24 Jan 2022 12:45:05 -0800 Subject: [PATCH 22/22] Update RELEASE_NOTES for 1.17.0 --- RELEASE_NOTES.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 3f7f1fc0d6..3ef724b10f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,8 +7,11 @@ A new header is inserted each time a *tag* is created. ## v1.17.0 -- engine: add experimental glossy screen-space reflections [⚠️ **Materials need to be rebuilt to - access this new feature**]. +- engine: Add experimental glossy screen-space reflections. +- engine: Add support for GPU morphing and 128 morph targets. +- engine: Fix crash with non-shadow receiving renderables and VSM. +- engine: Bring back the 3x3 filter for PCF shadows. +- engine: Correct AABB calculation for skinned glTF models. ## v1.16.1