diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ae40033f..933e1166e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -250,6 +250,7 @@ add_subdirectory (src/liboslcomp) add_subdirectory (src/liboslquery) add_subdirectory (src/liboslexec) add_subdirectory (src/liboslnoise) +add_subdirectory (src/libbsdl) add_subdirectory (src/oslc) add_subdirectory (src/oslinfo) diff --git a/src/cmake/cuda_macros.cmake b/src/cmake/cuda_macros.cmake index cf38234ef..00e2c77f7 100644 --- a/src/cmake/cuda_macros.cmake +++ b/src/cmake/cuda_macros.cmake @@ -15,7 +15,7 @@ if (CUDA_NO_FTZ) endif () # Compile a CUDA file to PTX using NVCC -function ( NVCC_COMPILE cuda_src extra_headers ptx_generated extra_nvcc_args ) +function ( NVCC_COMPILE cuda_src extra_headers ptx_generated extra_nvcc_args extra_libs) get_filename_component ( cuda_src_we ${cuda_src} NAME_WE ) get_filename_component ( cuda_src_dir ${cuda_src} DIRECTORY ) set (cuda_ptx "${CMAKE_CURRENT_BINARY_DIR}/${cuda_src_we}.ptx" ) @@ -35,6 +35,14 @@ function ( NVCC_COMPILE cuda_src extra_headers ptx_generated extra_nvcc_args ) set (NVCC_FTZ_FLAG "--ftz=true") endif () + set (EXTRA_INCLUDES "") + foreach (LIB ${extra_libs}) + get_target_property(INCLUDES ${LIB} INTERFACE_INCLUDE_DIRECTORIES) + foreach (INCLUDE_DIR ${INCLUDES}) + list (APPEND EXTRA_INCLUDES "-I${INCLUDE_DIR}") + endforeach() + endforeach() + add_custom_command ( OUTPUT ${cuda_ptx} COMMAND ${CUDA_NVCC_EXECUTABLE} "-I${OPTIX_INCLUDES}" @@ -43,6 +51,7 @@ function ( NVCC_COMPILE cuda_src extra_headers ptx_generated extra_nvcc_args ) "-I${CMAKE_BINARY_DIR}/include" "-I${PROJECT_SOURCE_DIR}/src/include" "-I${PROJECT_SOURCE_DIR}/src/cuda_common" + ${EXTRA_INCLUDES} ${ALL_OpenImageIO_INCLUDES} ${ALL_IMATH_INCLUDES} "-DFMT_DEPRECATED=\"\"" @@ -56,7 +65,7 @@ function ( NVCC_COMPILE cuda_src extra_headers ptx_generated extra_nvcc_args ) ${OSL_EXTRA_NVCC_ARGS} ${cuda_src} -o ${cuda_ptx} MAIN_DEPENDENCY ${cuda_src} - DEPENDS ${cuda_src} ${cuda_headers} oslexec + DEPENDS ${cuda_src} ${cuda_headers} ${extra_libs} oslexec WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" ) endfunction () diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index ad66a19a9..5b5b4327d 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -372,6 +372,7 @@ macro (osl_add_all_tests) render-mx-layer render-mx-sheen render-microfacet render-oren-nayar + render-spi-thinlayer render-uv render-veachmis render-ward render-raytypes select select-reg shaderglobals shortcircuit diff --git a/src/libbsdl/CMakeLists.txt b/src/libbsdl/CMakeLists.txt new file mode 100644 index 000000000..5e551b2bf --- /dev/null +++ b/src/libbsdl/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright Contributors to the Open Shading Language project. +# SPDX-License-Identifier: BSD-3-Clause +# https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + +include(bsdl.cmake) + +# BSDL has small tables for doing spectral render in sRGB +# but for other color spaces tell this function which ones +# and it will bake Jakob-Hanika coefficient tables. +add_bsdl_library(BSDL) # SPECTRAL_COLOR_SPACES "ACEScg") \ No newline at end of file diff --git a/src/libbsdl/README.md b/src/libbsdl/README.md new file mode 100644 index 000000000..7c3accbe5 --- /dev/null +++ b/src/libbsdl/README.md @@ -0,0 +1,94 @@ +# BSDL library + +BSDL is the working title (Bidirectional Scattering Distribution Library) for +this header only collection of BSDFs. It is self-contained depending only on +Imath and intended to be used in any renderer. This is a build only dependency. + +The basic idea is that you choose a BSDF from it and give it a thin wrapper to +integrate in your renderer. There is an example of this in OSL's testrender. The +key features are: + * BSDFs provide constructor, eval and sample methods. + * Everything is inlined (split among _decl.h and _imple.h headers) to be GPU friendly. + * There is a template based autiomatic switch/case virtual dispatch mechanism to be GPU firendly. + * It works both in RGB or spectral mode (via hero wavelength). + * It includes the Sony Pictures Imageworks set of BSDFs used for movie production. + +## Pseudo virtual methods + +The header static_virtual.h provides the ```StaticVirtual``` +template. If you inherit from it will implement a dispatch() method to execute +"virtual" methods covering ```type1, type2, ...``` using switch case statements. +The idea was borrowed from PBRT but the implementation is different. See the +use in testrender (shading.h/cpp). + +## The ```Power``` type + +To support both RGB and spectral render with the same code we use the +```Power``` type, which is just a float array. It has from RGB construction and +conversion. But when 'lambda_0', the hero wavelength is zero, these are no-ops +and pure RGB rendering is assueme. Take into account spectral support in BSDL +is in very early stages. + +## Lookup tables + +A bunch of BSDFs use albedo lookup tables. Those are generated at build time +very quickly and put in headers. You shouldn't have to do anything. + +## Usage + +Either ```include(bsdl.cmake)``` and call ```add_bsdl_library(my_name)``` to +create a 'my_name' INTERFACE library that you then link, or +```add_subdirectory (path_to_libbsdl)``` to get it as 'BSDL'. + +For the integration, a config header needs to be defined as you can see in +testrender's 'bsdl_config.h'. +```cpp +#define BSDL_INLINE static inline OSL_HOSTDEVICE +#define BSDL_INLINE_METHOD inline OSL_HOSTDEVICE +#define BSDL_DECL OSL_DEVICE +#define BSDL_UNROLL() // Do nothing + +#include + +#include + +struct BSDLConfig : public bsdl::BSDLDefaultConfig { + + // testrender won't do spectral render, just 3 channels covers RGB + static constexpr int HERO_WAVELENGTH_CHANNELS = 3; + + struct Fast { + static BSDL_INLINE_METHOD float cosf(float x) { return OIIO::fast_cos(x); } + static BSDL_INLINE_METHOD float sinf(float x) { return OIIO::fast_sin(x); } + static BSDL_INLINE_METHOD float asinf(float x) { return OIIO::fast_asin(x); } + static BSDL_INLINE_METHOD float acosf(float x) { return OIIO::fast_acos(x); } + static BSDL_INLINE_METHOD float atan2f(float y, float x) { return OIIO::fast_atan2(y, x); } + static BSDL_INLINE_METHOD void sincosf(float x, float* s, float* c) { return OIIO::fast_sincos(x, s, c); } + static BSDL_INLINE_METHOD float sinpif(float x) { return OIIO::fast_sinpi(x); } + static BSDL_INLINE_METHOD float cospif(float x) { return OIIO::fast_cospi(x); } + static BSDL_INLINE_METHOD float expf(float x) { return OIIO::fast_exp(x); } + static BSDL_INLINE_METHOD float exp2f(float x) { return OIIO::fast_exp2(x); } + static BSDL_INLINE_METHOD float logf(float x) { return OIIO::fast_log(x); } + static BSDL_INLINE_METHOD float log2f(float x) { return OIIO::fast_log2(x); } + static BSDL_INLINE_METHOD float log1pf(float x) { return OIIO::fast_log1p(x); } + static BSDL_INLINE_METHOD float powf(float x, float y) { return OIIO::fast_safe_pow(x, y); } + }; + + // Don't care for colorspaces/spectral + static BSDL_INLINE_METHOD ColorSpaceTag current_color_space() { return ColorSpaceTag::sRGB; } + static BSDL_INLINE_METHOD const JakobHanikaLut* get_jakobhanika_lut(ColorSpaceTag cs) { return nullptr; } +}; +``` + +And this header should be included before you include anything else from BSDL. + +If spectral rendering is desired there is ready to use sRGB upsampling via +spectral primaries by Mallett and Yuksel. For wide gamut color spaces like +ACEScg we use Jakob and Hanika approach, but you have to ask for the tables +in .cpp for from cmake: +``` +add_bsdl_library(BSDL SPECTRAL_COLOR_SPACES "ACEScg" "ACES2065") +``` + +which will bake tables to two cpp files and return them in 'BSDL_LUTS_CPP'. +Then you need to include that in your sources. \ No newline at end of file diff --git a/src/libbsdl/bsdl.cmake b/src/libbsdl/bsdl.cmake new file mode 100644 index 000000000..cd7908163 --- /dev/null +++ b/src/libbsdl/bsdl.cmake @@ -0,0 +1,44 @@ +# Copyright Contributors to the Open Shading Language project. +# SPDX-License-Identifier: BSD-3-Clause +# https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + +find_package(Threads REQUIRED) + +function(ADD_BSDL_LIBRARY NAME) + cmake_parse_arguments(PARSE_ARGV 1 bsdl "" "SUBDIR" "SPECTRAL_COLOR_SPACES") + # Bootstrap version of BSDL (without luts) + add_library(BSDL_BOOTSTRAP INTERFACE) + target_include_directories(BSDL_BOOTSTRAP INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/${bsdl_SUBDIR}/include) + target_link_libraries(BSDL_BOOTSTRAP INTERFACE ${ARNOLD_IMATH_TARGETS}) + + # LUT generation tool + set(BSDL_GEN_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/${bsdl_SUBDIR}/geninclude) + add_executable (genluts ${CMAKE_CURRENT_SOURCE_DIR}/${bsdl_SUBDIR}/src/genluts.cpp) + target_link_libraries(genluts PRIVATE BSDL_BOOTSTRAP Threads::Threads) + file(MAKE_DIRECTORY ${BSDL_GEN_HEADERS}/BSDL/SPI) + add_custom_command(TARGET genluts POST_BUILD USES_TERMINAL COMMAND $ ${BSDL_GEN_HEADERS}/BSDL/SPI + COMMENT "Generating BSDL lookup tables ...") + + if (DEFINED bsdl_SPECTRAL_COLOR_SPACES) + add_executable(jakobhanika_luts ${CMAKE_CURRENT_SOURCE_DIR}/${bsdl_SUBDIR}/src/jakobhanika_luts.cpp) + target_link_libraries(genluts PRIVATE Threads::Threads) + foreach(CS ${bsdl_SPECTRAL_COLOR_SPACES}) + set(JACOBHANIKA_${CS} ${CMAKE_CURRENT_BINARY_DIR}/jakobhanika_${CS}.cpp) + list(APPEND BSDL_LUTS_CPP ${JACOBHANIKA_${CS}}) + add_custom_command( + OUTPUT ${JACOBHANIKA_${CS}} + USES_TERMINAL + COMMAND $ 64 ${JACOBHANIKA_${CS}} ${CS} + DEPENDS jakobhanika_luts + COMMENT "Generating Jakob-Hanika RGB-Spectrum ${CS} tables") + endforeach() + set(${NAME}_LUTS_CPP ${BSDL_LUTS_CPP} PARENT_SCOPE) + endif() + + # Final BSDL library (with luts) + add_library(${NAME} INTERFACE) + target_include_directories(${NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/${bsdl_SUBDIR}/include) + target_link_libraries(${NAME} INTERFACE Imath::Imath) + target_include_directories(${NAME} INTERFACE ${BSDL_GEN_HEADERS}) + add_dependencies(${NAME} genluts) +endfunction() diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_backscatter_decl.h b/src/libbsdl/include/BSDL/SPI/bsdf_backscatter_decl.h new file mode 100644 index 000000000..ff30fd8cc --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_backscatter_decl.h @@ -0,0 +1,109 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +struct CharlieDist { + static constexpr float MIN_ROUGHNESS = 0.06f; + + static BSDL_INLINE_METHOD float common_roughness(float alpha); + BSDL_INLINE_METHOD CharlieDist(float rough) + : a(CLAMP(rough, MIN_ROUGHNESS, 1.0f)) + { + } + + BSDL_INLINE_METHOD float D(const Imath::V3f& Hr) const; + BSDL_INLINE_METHOD float get_lambda(float cosNv) const; + BSDL_INLINE_METHOD float G2(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD float roughness() const { return a; } + +private: + float a; +}; + +template struct SheenMicrofacet { + // describe how tabulation should be done + static constexpr int Nc = 16; + static constexpr int Nr = 16; + static constexpr int Nf = 1; + + static constexpr float get_cosine(int i) + { + return std::max(float(i) * (1.0f / (Nc - 1)), 1e-6f); + } + explicit BSDL_INLINE_METHOD SheenMicrofacet(float rough) : d(rough) {} + BSDL_INLINE_METHOD Sample eval(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD Sample sample(const Imath::V3f& wo, float randu, + float randv, float) const; + BSDL_INLINE_METHOD float roughness() const { return d.roughness(); } + +private: + Dist d; +}; + +struct CharlieSheen : public SheenMicrofacet { + explicit BSDL_INLINE_METHOD CharlieSheen(float, float rough, float) + : SheenMicrofacet(rough) + { + } + struct Energy { + float data[Nf * Nr * Nc]; + }; + static BSDL_INLINE_METHOD Energy& get_energy(); + + static const char* lut_header() { return "bsdf_backscatter_luts.h"; } + static const char* struct_name() { return "CharlieSheen"; } +}; + +template struct CharlieLobe : public Lobe { + using Base = Lobe; + struct Data : public LayeredData { + Imath::V3f N; + Imath::C3f tint; + float roughness; + int doublesided; + using lobe_type = CharlieLobe; + }; + template static typename LobeRegistry::Entry entry() + { + static_assert( + std::is_base_of::value); // Make no other assumptions + using R = LobeRegistry; + return { name(), + { R::param(&D::closure), R::param(&D::N), R::param(&D::tint), + R::param(&D::roughness), + R::param(&D::doublesided, "doublesided"), R::close() } }; + } + + template + BSDL_INLINE_METHOD CharlieLobe(T*, const BsdfGlobals& globals, + const Data& data); + static const char* name() { return "sheen"; } + + BSDL_INLINE_METHOD Power albedo_impl() const { return Power(1 - Eo, 1); } + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f& sample) const; + +private: + CharlieSheen sheen; + Power tint; + float Eo; + bool back; +}; + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_backscatter_impl.h b/src/libbsdl/include/BSDL/SPI/bsdf_backscatter_impl.h new file mode 100644 index 000000000..6097aa675 --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_backscatter_impl.h @@ -0,0 +1,166 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include +#include +#ifndef BAKE_BSDL_TABLES +# include +#endif + +BSDL_ENTER_NAMESPACE + +namespace spi { + +BSDL_INLINE_METHOD float +CharlieDist::common_roughness(float alpha) +{ + // Using the PDF we would have if we sampled the microfacet, one of + // the 1/2 comes from the cosine avg of 1/(4 cosMO). + // + // (2 + 1 / alpha) / 4 = 1 / (2 pi roughness^4) + // 1 / (pi (1 + 4 / alpha)) = roughness^4 + return sqrtf(sqrtf(ONEOVERPI / (1 + 0.5f / alpha))); +} + +BSDL_INLINE_METHOD float +CharlieDist::D(const Imath::V3f& Hr) const +{ + float cos_theta = Hr.z; + float sin_theta = sqrtf(1.0f - SQR(cos_theta)); + return BSDLConfig::Fast::powf(sin_theta, 1 / a) * (2 + 1 / a) * 0.5f + * ONEOVERPI; +} + +BSDL_INLINE_METHOD float +CharlieDist::get_lambda(float cosNv) const +{ + float rt = SQR(1 - a); + + // These params come from gnuplot fitting tool. Roughness = 1 and + // roughness = 0. We interpolate in between with (1 - roughness)^2 + // to get the best match. + const float a = LERP(rt, 21.5473f, 25.3245f); + const float b = LERP(rt, 3.82987f, 3.32435f); + const float c = LERP(rt, 0.19823f, 0.16801f); + const float d = LERP(rt, -1.97760f, -1.27393f); + const float e = LERP(rt, -4.32054f, -4.85967f); + // The curve is anti-symmetrical aroung 0.5 + const float x = cosNv > 0.5f ? 1 - cosNv : cosNv; + const float pivot = a / (1 + b * BSDLConfig::Fast::powf(0.5f, c)) + d * 0.5f + + e; + + float p = a / (1 + b * BSDLConfig::Fast::powf(x, c)) + d * x + e; + if (cosNv > 0.5f) // Mirror around 0.5f + p = 2 * pivot - p; + // That curve fits lambda in log scale, now exponentiate + return BSDLConfig::Fast::expf(p); +} + +BSDL_INLINE_METHOD float +CharlieDist::G2(const Imath::V3f& wo, const Imath::V3f& wi) const +{ + assert(wi.z > 0); + assert(wo.z > 0); + float cosNI = std::min(1.0f, wi.z); + float cosNO = std::min(1.0f, wo.z); + float Li = get_lambda(cosNI); + float Lo = get_lambda(cosNO); + // This makes the BSDF non-reciprocal. Cheat to hide the terminator + Li = BSDLConfig::Fast::powf(Li, 1.0f + 2 * SQR(SQR(SQR((1 - cosNI))))); + return 1 / (1 + Li + Lo); +} + +template +BSDL_INLINE_METHOD Sample +SheenMicrofacet::eval(const Imath::V3f& wo, const Imath::V3f& wi) const +{ + assert(wo.z >= 0); + assert(wi.z >= 0); + float cosNO = wo.z; + float cosNI = wi.z; + if (cosNI <= 1e-5f || cosNO <= 1e-5f) + return {}; + + const float D = d.D((wo + wi).normalized()); + if (D < 1e-6) + return {}; + const float G2 = d.G2(wo, wi); + return { wi, Power(D * G2 * 0.5f * PI / cosNO, 1), 0.5f * ONEOVERPI, 0 }; +} + +template +BSDL_INLINE_METHOD Sample +SheenMicrofacet::sample(const Imath::V3f& wo, float randu, float randv, + float randw) const +{ + Imath::V3f wi = sample_uniform_hemisphere(randu, randv); + return eval(wo, wi); +} + +template +template +BSDL_INLINE_METHOD +CharlieLobe::CharlieLobe(T* lobe, const BsdfGlobals& globals, + const CharlieLobe::Data& data) + : Base(lobe, globals.visible_normal(data.N), + CharlieDist::common_roughness( + globals.regularize_roughness(data.roughness)), + globals.lambda_0, false) + , sheen(0, + CLAMP(globals.regularize_roughness(data.roughness), + CharlieDist::MIN_ROUGHNESS, 1.0f), + 0) + , tint(globals.wave(data.tint)) + , back(data.doublesided ? false : globals.backfacing) +{ + Base::sample_filter = globals.get_sample_filter(Base::frame.Z, true); + TabulatedEnergyCurve curve(sheen.roughness(), 0); + // Get energy compensation taking tint into account + Eo = back ? 1 + : 1 + - MIN((1 - curve.Emiss_eval(Base::frame.Z.dot(globals.wo))) + * tint.max(), + 1.0f); +} + +template +BSDL_INLINE_METHOD Sample +CharlieLobe::eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const +{ + const float cosNO = wo.z; + const float cosNI = wi.z; + const bool isrefl = cosNI > 0 && cosNO >= 0; + const bool doself = isrefl && !back; + if (!doself) + return {}; + + Sample s = sheen.eval(wo, wi); + s.weight *= tint; + s.roughness = BSDF_ROOT::roughness(); + return s; +} + +template +BSDL_INLINE_METHOD Sample +CharlieLobe::sample_impl(const Imath::V3f& wo, + const Imath::V3f& sample) const +{ + const bool doself = !back; + if (!doself) + return {}; + + Sample ss = sheen.sample(wo, sample.x, sample.y, sample.z); + ss.weight *= tint; + ss.roughness = BSDF_ROOT::roughness(); + return ss; +} + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_clearcoat_decl.h b/src/libbsdl/include/BSDL/SPI/bsdf_clearcoat_decl.h new file mode 100644 index 000000000..29614005c --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_clearcoat_decl.h @@ -0,0 +1,116 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +struct PlasticFresnel { + BSDL_INLINE_METHOD PlasticFresnel() {} + BSDL_INLINE_METHOD PlasticFresnel(float eta); + + BSDL_INLINE_METHOD Power eval(const float c) const; + BSDL_INLINE_METHOD Power avg() const; + static BSDL_INLINE_METHOD PlasticFresnel from_table_index(float tx); + BSDL_INLINE_METHOD float table_index() const; + BSDL_INLINE_METHOD float get_ior() const { return eta; } + +private: + static constexpr float IOR_MIN = 1.00000012f; + static constexpr float IOR_MAX = 5.0f; + float eta; +}; + +struct PlasticGGX : public bsdl::MicrofacetMS { + explicit BSDL_INLINE_METHOD PlasticGGX(float cosNO, float roughness_index, + float fresnel_index); + BSDL_INLINE_METHOD PlasticGGX(const bsdl::GGXDist& dist, + const PlasticFresnel& fresnel, float cosNO, + float roughness); + struct Energy { + float data[Nf * Nr * Nc]; + }; + static BSDL_INLINE_METHOD Energy& get_energy(); + + static const char* lut_header() { return "bsdf_clearcoat_luts.h"; } + static const char* struct_name() { return "PlasticGGX"; } +}; + +template struct ClearCoatLobe : public Lobe { + using Base = Lobe; + struct Data : public LayeredData { + Imath::V3f N; + Imath::V3f U; + float IOR; + float roughness; + float anisotropy; + Imath::C3f spec_color; + Imath::C3f sigma_a; + int doublesided; + float force_eta; + int legacy_absorption; + float artistic_mix; + float absorption_bias; + float absorption_gain; + using lobe_type = ClearCoatLobe; + }; + template static typename LobeRegistry::Entry entry() + { + static_assert( + std::is_base_of::value); // Make no other assumptions + using R = LobeRegistry; + return { name(), + { R::param(&D::closure), R::param(&D::N), R::param(&D::U), + R::param(&D::IOR), R::param(&D::roughness), + R::param(&D::anisotropy), R::param(&D::spec_color), + R::param(&D::sigma_a), R::param(&D::doublesided), + R::param(&D::force_eta), + R::param(&D::artistic_mix, "artistic_mix"), + R::param(&D::absorption_bias, "absorption_bias"), + R::param(&D::absorption_gain, "absorption_gain"), + R::close() } }; + } + + template + BSDL_INLINE_METHOD ClearCoatLobe(T*, const BsdfGlobals& globals, + const Data& data); + + static const char* name() { return "clearcoat"; } + + BSDL_INLINE_METHOD Power albedo_impl() const + { + return spec_color * (1 - Eo); + } + + BSDL_INLINE_METHOD Power filter_o(const Imath::V3f& wo) const + { + if (backside) + return Power::UNIT(); + // wo is the same as when constructed, Eo is cached + return Eo * wo_absorption; + } + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const; + +private: + Imath::V3f U; + PlasticGGX spec; + Power spec_color; + Power wo_absorption; + float Eo; + bool backside; +}; + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_clearcoat_impl.h b/src/libbsdl/include/BSDL/SPI/bsdf_clearcoat_impl.h new file mode 100644 index 000000000..d6837444e --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_clearcoat_impl.h @@ -0,0 +1,164 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include + +#ifndef BAKE_BSDL_TABLES +# include +#endif + +BSDL_ENTER_NAMESPACE + +namespace spi { + +BSDL_INLINE_METHOD +PlasticFresnel::PlasticFresnel(float eta) : eta(CLAMP(eta, IOR_MIN, IOR_MAX)) {} + +BSDL_INLINE_METHOD Power +PlasticFresnel::eval(const float c) const +{ + assert(c >= 0); // slightly above 1.0 is ok + assert(eta > 1); // avoid singularity at eta==1 + // optimized for c in [0,1] and eta in (1,inf) + const float g = sqrtf(eta * eta - 1 + c * c); + const float A = (g - c) / (g + c); + const float B = (c * (g + c) - 1) / (c * (g - c) + 1); + return Power(0.5f * A * A * (1 + B * B), 1); +} + +BSDL_INLINE_METHOD +Power +PlasticFresnel::avg() const +{ + // see avg_fresnel -- but we know that eta >= 1 here + return Power((eta - 1) / (4.08567f + 1.00071f * eta), 1); +} + +BSDL_INLINE_METHOD PlasticFresnel +PlasticFresnel::from_table_index(float tx) +{ + return PlasticFresnel(LERP(SQR(SQR(tx)), IOR_MIN, IOR_MAX)); +} + +BSDL_INLINE_METHOD float +PlasticFresnel::table_index() const +{ + // turn the IOR value into something suitable for integrating + // this is the reverse of the method above + assert(eta >= IOR_MIN); + assert(eta <= IOR_MAX); + float x = (eta - IOR_MIN) * (1.0f / (IOR_MAX - IOR_MIN)); + assert(x >= 0); + assert(x <= 1); + x = sqrtf(sqrtf(x)); + assert(x >= 0); + assert(x <= 1); + return x; +} + +BSDL_INLINE_METHOD +PlasticGGX ::PlasticGGX(float cosNO, float roughness_index, float fresnel_index) + : MicrofacetMS(cosNO, roughness_index, fresnel_index) +{ +} + +BSDL_INLINE_METHOD +PlasticGGX ::PlasticGGX(const bsdl::GGXDist& dist, + const PlasticFresnel& fresnel, float cosNO, + float roughness) + : MicrofacetMS(dist, fresnel, cosNO, roughness) +{ +} + +template +template +BSDL_INLINE_METHOD +ClearCoatLobe::ClearCoatLobe(T* lobe, const BsdfGlobals& globals, + const Data& data) + : Base(lobe, globals.visible_normal(data.N), data.U, + globals.regularize_roughness(data.roughness), globals.lambda_0, + false) + , spec(GGXDist(Base::roughness(), CLAMP(data.anisotropy, 0.0f, 1.0f)), + PlasticFresnel(LERP(CLAMP(data.force_eta, 0.0f, 1.0f), + globals.relative_eta(data.IOR), data.IOR)), + Base::frame.Z.dot(globals.wo), Base::roughness()) + , spec_color(globals.wave(data.spec_color).clamped(0, 1)) + , wo_absorption(1.0f, globals.lambda_0) + , backside(data.doublesided ? false : globals.backfacing) +{ + Base::sample_filter = globals.get_sample_filter(Base::frame.Z, true); + bsdl::TabulatedEnergyCurve diff_curve( + Base::roughness(), spec.getFresnel().table_index()); + + const float cosNO = std::min(fabsf(Base::frame.Z.dot(globals.wo)), 1.0f); + Eo = !backside ? diff_curve.Emiss_eval(cosNO) : 1; + if (!data.legacy_absorption && MAX_RGB(data.sigma_a) > 0) { + float cos_p_artistic = cosNO; + float cos_p = cosNO; + if (data.artistic_mix > 0.0f) { + const float b = CLAMP(data.absorption_bias, 0.0f, 1.0f); + const float g = CLAMP(data.absorption_gain, 0.0f, 1.0f); + // First apply gamma curve + cos_p_artistic = bias_curve01(cos_p_artistic, b); + // Then apply sigma curve + cos_p_artistic = gain_curve01(cos_p_artistic, g); + } + // Take into account how the ray bends with the refraction to compute + // the traveled distance through absorption. + const float sinNO2 = 1 - SQR(cosNO); + const float inveta2 = SQR(1 / spec.getFresnel().get_ior()); + cos_p = sqrtf(1 - std::min(1.0f, inveta2 * sinNO2)); + cos_p = CLAMP(LERP(data.artistic_mix, cos_p, cos_p_artistic), 0.0f, + 1.0f); + const float dist = 1 / std::max(cos_p, FLOAT_MIN); + + constexpr auto fast_exp = BSDLConfig::Fast::expf; + + const Power sigma_a = globals.wave(data.sigma_a); + wo_absorption + = Power([&](int i) { return fast_exp(-sigma_a[i] * dist); }, + globals.lambda_0); + } else + wo_absorption = Power(1, globals.lambda_0); +} + +template +BSDL_INLINE_METHOD Sample +ClearCoatLobe::eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const +{ + const float cosNI = wi.z; + const bool isrefl = cosNI > 0; + const bool doself = isrefl && !backside; + if (!doself) + return {}; + + Sample sample = spec.eval(wo, wi); + sample.weight *= spec_color; + sample.roughness = Base::roughness(); + return sample; +} + +template +BSDL_INLINE_METHOD Sample +ClearCoatLobe::sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const +{ + const bool doself = !backside; + if (!doself) + return {}; + + Sample sample = spec.sample(wo, rnd.x, rnd.y, rnd.z); + sample.weight *= spec_color; + sample.roughness = Base::roughness(); + return sample; +} + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_dielectric_decl.h b/src/libbsdl/include/BSDL/SPI/bsdf_dielectric_decl.h new file mode 100644 index 000000000..05efd4b17 --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_dielectric_decl.h @@ -0,0 +1,165 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +struct DielectricFresnel { + BSDL_INLINE_METHOD DielectricFresnel(float _eta, bool backside); + BSDL_INLINE_METHOD float eval(const float c) const; + BSDL_INLINE_METHOD float avg() const; + static BSDL_INLINE_METHOD DielectricFresnel from_table_index(float tx, + int side); + BSDL_INLINE_METHOD float table_index() const; + + float eta; + +private: + static constexpr float IOR_MIN = 1.001f; + static constexpr float IOR_MAX = 5.0f; +}; + +// side template argument only matters for baking +template struct Dielectric { + // describe how tabulation should be done + static constexpr int Nc = 16; + static constexpr int Nr = 16; + static constexpr int Nf = 32; + + static constexpr float get_cosine(int i) + { + return std::max(float(i) * (1.0f / (Nc - 1)), 1e-6f); + } + + explicit BSDL_INLINE_METHOD Dielectric(float, float roughness_index, + float fresnel_index); + + BSDL_INLINE_METHOD + Dielectric(const Dist& dist, const DielectricFresnel& fresnel, + float prob_clamp) + : d(dist), f(fresnel), prob_clamp(prob_clamp) + { + } + + BSDL_INLINE_METHOD Sample eval(Imath::V3f wo, Imath::V3f wi, bool doreflect, + bool dorefract) const; + BSDL_INLINE_METHOD Sample sample(Imath::V3f wo, float randu, float randv, + float randw, bool doreflect, + bool dorefract) const; + + BSDL_INLINE_METHOD Sample sample(Imath::V3f wo, float randu, float randv, + float randw) const + { + return sample(wo, randu, randv, randw, true, true); + } + + BSDL_INLINE_METHOD float fresnel_prob(float f) const; + BSDL_INLINE_METHOD DielectricFresnel fresnel() const { return f; } + BSDL_INLINE_METHOD float fresnel(float c) const { return f.eval(c); } + BSDL_INLINE_METHOD float roughness() const { return d.roughness(); } + BSDL_INLINE_METHOD float eta() const { return f.eta; } + +private: + Dist d; + DielectricFresnel f; + float prob_clamp; +}; + +struct DielectricFront : public Dielectric { + explicit BSDL_INLINE_METHOD DielectricFront(float, float roughness_index, + float fresnel_index); + BSDL_INLINE_METHOD DielectricFront(const GGXDist& dist, + const DielectricFresnel& fresnel, + float prob_clamp); + struct Energy { + float data[Nf * Nr * Nc]; + }; + static BSDL_INLINE_METHOD Energy& get_energy(); + + static const char* lut_header() { return "bsdf_dielectric_front_luts.h"; } + static const char* struct_name() { return "DielectricFront"; } +}; + +struct DielectricBack : public Dielectric { + explicit BSDL_INLINE_METHOD DielectricBack(float, float roughness_index, + float fresnel_index); + BSDL_INLINE_METHOD DielectricBack(const bsdl::GGXDist& dist, + const DielectricFresnel& fresnel, + float prob_clamp); + struct Energy { + float data[Nf * Nr * Nc]; + }; + static BSDL_INLINE_METHOD Energy& get_energy(); + + static const char* lut_header() { return "bsdf_dielectric_back_luts.h"; } + static const char* struct_name() { return "DielectricBack"; } +}; + +template struct DielectricLobe : public Lobe { + using Base = Lobe; + struct Data { + Imath::V3f N; + Imath::V3f T; + float IOR; + float roughness; + float anisotropy; + Imath::C3f refl_tint; + Imath::C3f refr_tint; + float dispersion; + float force_eta; + float prob_clamp; // Not exposed + float wavelength; + using lobe_type = DielectricLobe; + }; + template static typename LobeRegistry::Entry entry() + { + static_assert( + std::is_base_of::value); // Make no other assumptions + using R = LobeRegistry; + return { name(), + { R::param(&D::N), R::param(&D::T), R::param(&D::IOR), + R::param(&D::roughness), R::param(&D::anisotropy), + R::param(&D::refl_tint), R::param(&D::refr_tint), + R::param(&D::dispersion), R::param(&D::force_eta), + R::close() } }; + } + + template + BSDL_INLINE_METHOD DielectricLobe(T*, const BsdfGlobals& globals, + const Data& data); + + static constexpr const char* name() { return "dielectric"; } + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi, bool doreflect, + bool dorefract) const; + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f& _rnd, + bool doreflect, bool dorefract) const; + +protected: + BSDL_INLINE_METHOD Power get_tint(float cosNI) const; + BSDL_INLINE_METHOD Sample eval_ec_lobe(Sample s) const; + + BSDL_INLINE_METHOD Imath::V3f sample_ec_lobe(float randu, float randv, + bool back) const; + + DielectricFront spec; // Also good for back eval + Power refl_tint; + Power refr_tint; + // energy compensation data (R and T lobes) + float RT_ratio, Eo; + bool backside; +}; + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_dielectric_impl.h b/src/libbsdl/include/BSDL/SPI/bsdf_dielectric_impl.h new file mode 100644 index 000000000..c3985592e --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_dielectric_impl.h @@ -0,0 +1,326 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include +#ifndef BAKE_BSDL_TABLES +# include +# include +#endif + +BSDL_ENTER_NAMESPACE + +namespace spi { + +BSDL_INLINE_METHOD +DielectricFresnel::DielectricFresnel(float _eta, bool backside) +{ + if (backside) + _eta = 1 / _eta; + eta = _eta >= 1 ? CLAMP(_eta, IOR_MIN, IOR_MAX) + : CLAMP(_eta, 1 / IOR_MAX, 1 / IOR_MIN); +} + +BSDL_INLINE_METHOD float +DielectricFresnel::eval(const float c) const +{ + assert(c >= 0); // slightly above 1.0 is ok + float g = (eta - 1.0f) * (eta + 1.0f) + c * c; + if (g > 0) { + g = sqrtf(g); + float A = (g - c) / (g + c); + float B = (c * (g + c) - 1) / (c * (g - c) + 1); + return 0.5f * A * A * (1 + B * B); + } + return 1.0f; // TIR (no refracted component) +} + +BSDL_INLINE_METHOD float +DielectricFresnel::avg() const +{ + return avg_fresnel_dielectric(eta); +} + +BSDL_INLINE_METHOD DielectricFresnel +DielectricFresnel::from_table_index(float tx, int side) +{ + const float eta = LERP(SQR(tx), IOR_MIN, IOR_MAX); + return DielectricFresnel(eta, side); +} + +// Note index of eta equals index of 1 / eta, so this function works for +// either side (both tables) +BSDL_INLINE_METHOD float +DielectricFresnel::table_index() const +{ + // turn the IOR value into something suitable for integrating + // this is the reverse of the method above + const float feta = eta; + const float seta = CLAMP(feta < 1 ? 1 / feta : feta, IOR_MIN, IOR_MAX); + const float x = (seta - IOR_MIN) * (1 / (IOR_MAX - IOR_MIN)); + assert(x >= 0); + assert(x <= 1); + return sqrtf(x); +} + +template +BSDL_INLINE_METHOD +Dielectric::Dielectric(float, float roughness_index, + float fresnel_index) + : d(roughness_index, 0) + , f(DielectricFresnel::from_table_index(fresnel_index, side)) + , prob_clamp(0) +{ +} + +// Compute a sampling probability based on fresnel. Returning f would be ideal +// if were not going to find >1 energy after a bounce. +template +BSDL_INLINE_METHOD float +Dielectric::fresnel_prob(float f) const +{ + const float safe_prob = 0.2f; + return LERP(prob_clamp, f, CLAMP(f, safe_prob, 1 - safe_prob)); +} + +template +BSDL_INLINE_METHOD Sample +Dielectric::eval(Imath::V3f wo, Imath::V3f wi, bool doreflect, + bool dorefract) const +{ + const float cosNO = wo.z; + const float cosNI = wi.z; + const bool both = doreflect && dorefract; + assert(cosNO >= 0); + if (cosNI > 0) { + const Imath::V3f m = (wo + wi).normalized(); + const float cosMO = m.dot(wo); + if (cosMO <= 0) + return {}; + const float D = d.D(m); + const float G1 = d.G1(wo); + const float F = f.eval(cosMO); + const float P = both ? fresnel_prob(F) : 1; + const float out = d.G2_G1(wi, wo) * (F / P); + const float pdf = (G1 * D) / (4.0f * cosNO) * (both ? P : 1); + return { wi, Power(out, 1), pdf, 0 }; + } else if (cosNI < 0) { + // flip to same side as N + const Imath::V3f Ht = (f.eta * wi + wo).normalized() + * ((f.eta > 1) ? -1 : 1); + // compute fresnel term + const float cosHO = Ht.dot(wo); + const float cosHI = Ht.dot(wi); + if (cosHO <= 0 || cosHI >= 0) + return {}; + const float Ft = 1.0f - f.eval(cosHO); + const float Pt = both ? fresnel_prob(Ft) : 1; + if (Ht.z <= 0 || cosHO <= 0 || cosHI >= 0 || Ft <= 0) + return {}; + const float D = d.D(Ht); + const float G1 = d.G1(wo); + const float out = d.G2_G1({ wi.x, wi.y, -wi.z }, wo) * (Ft / Pt); + + float pdf = (-cosHI * cosHO * SQR(f.eta) * (G1 * D)) + / (wo.z * SQR(cosHI * f.eta + cosHO)) * (both ? Pt : 1); + return { wi, Power(out, 1), pdf, 0 }; + + } else + return {}; +} + +template +BSDL_INLINE_METHOD Sample +Dielectric::sample(Imath::V3f wo, float randu, float randv, + float randw, bool doreflect, + bool dorefract) const +{ + const Imath::V3f m = d.sample(wo, randu, randv); + const float cosMO = wo.dot(m); + const bool both = doreflect && dorefract; + if (cosMO <= 0) + return {}; + const float F = both ? fresnel_prob(f.eval(cosMO)) : (dorefract ? 0 : 1); + bool choose_reflect = randw < F; + const Imath::V3f wi = choose_reflect ? reflect(wo, m) + : refract(wo, m, f.eta); + if ((choose_reflect && wi.z <= 0) || (!choose_reflect && wi.z >= 0)) + return {}; + return eval(wo, wi, doreflect, dorefract); +} + +BSDL_INLINE_METHOD +DielectricFront::DielectricFront(float, float roughness_index, + float fresnel_index) + : Dielectric(0, roughness_index, fresnel_index) +{ +} + +BSDL_INLINE_METHOD +DielectricFront::DielectricFront(const GGXDist& dist, + const DielectricFresnel& fresnel, + float prob_clamp) + : Dielectric(dist, fresnel, prob_clamp) +{ +} + +BSDL_INLINE_METHOD +DielectricBack::DielectricBack(float, float roughness_index, + float fresnel_index) + : Dielectric(0, roughness_index, fresnel_index) +{ +} + +BSDL_INLINE_METHOD +DielectricBack::DielectricBack(const bsdl::GGXDist& dist, + const DielectricFresnel& fresnel, + float prob_clamp) + : Dielectric(dist, fresnel, prob_clamp) +{ +} + +template +template +BSDL_INLINE_METHOD +DielectricLobe::DielectricLobe(T* lobe, const BsdfGlobals& globals, + const Data& data) + : Base(lobe, globals.visible_normal(data.N), data.T, + globals.regularize_roughness(data.roughness), globals.lambda_0, true) + , spec(GGXDist(globals.regularize_roughness(data.roughness), + CLAMP(data.anisotropy, 0.0f, 1.0f)), + DielectricFresnel(LERP(CLAMP(data.force_eta, 0.0f, 1.0f), + globals.relative_eta(data.IOR), data.IOR), + globals.backfacing), + data.prob_clamp) + , refl_tint(globals.wave(data.refl_tint)) + , refr_tint(globals.wave(data.refr_tint)) + , backside(spec.eta() < 1.0f) +{ + // Compiler should optimize all these calls to regularize_roughness + const float roughness = globals.regularize_roughness(data.roughness); + TabulatedEnergyCurve diff_curve_front( + roughness, spec.fresnel().table_index()); + TabulatedEnergyCurve diff_curve_back( + roughness, spec.fresnel().table_index()); + + const float ratio_F = avg_fresnel_dielectric(spec.eta()); + const float ratio_B = avg_fresnel_dielectric(1 / spec.eta()); + + const float cosNO = globals.wo.dot(Base::frame.Z); + assert(cosNO >= 0); + const float davg_front = backside ? diff_curve_back.get_Emiss_avg() + : diff_curve_front.get_Emiss_avg(); + const float davg_back = backside ? diff_curve_front.get_Emiss_avg() + : diff_curve_back.get_Emiss_avg(); + Eo = backside ? diff_curve_back.Emiss_eval(cosNO) + : diff_curve_front.Emiss_eval(cosNO); + // now compute RT_ratio such that the reciprocity constraint is obeyed: + // (1 - ratio_F) / m_cdf_Q[0] === (1 - ratio_B) / m_cdf_Q[1] * eta^2 + // since the equality does not hold in general, we adjust by x on the left + // and by (1-x) on the right until they do hold and recompute a new ratio_F + + // FIXME: figure out where the eta^2 factor should go after we straighten + // out the reverse PDF calculation + const float L = (1 - ratio_F) / davg_back; + const float R = (1 - ratio_B) / davg_front * SQR(spec.eta()); + const float x = R > 1e12f ? 1 : R / (L + R); + RT_ratio = 1 - x * (1 - ratio_F); +} + +template +BSDL_INLINE_METHOD Sample +DielectricLobe::eval_impl(const Imath::V3f& wo, const Imath::V3f& wi, + bool doreflect, bool dorefract) const +{ + const float cosNI = wi.z; + const bool both = doreflect && dorefract; + + if ((cosNI > 0 && !doreflect) || (cosNI < 0 && !dorefract) || cosNI == 0) + return {}; + Sample s = { wi }; + const float PE = Eo * (both ? 1 : (dorefract ? 1 - RT_ratio : RT_ratio)); + Sample ss = spec.eval(wo, wi, doreflect, dorefract); + s.update(ss.weight, ss.pdf, 1 - PE); + s = eval_ec_lobe(s); + s.weight *= get_tint(cosNI); + s.roughness = Base::roughness(); + return s; +} + +template +BSDL_INLINE_METHOD Sample +DielectricLobe::sample_impl(const Imath::V3f& wo, + const Imath::V3f& _rnd, bool doreflect, + bool dorefract) const +{ + const bool both = doreflect && dorefract; + + const float PE = Eo * (both ? 1 : (dorefract ? 1 - RT_ratio : RT_ratio)); + Sample s = {}; + s.roughness = Base::roughness(); + + Imath::V3f rnd = _rnd; + if (rnd.x < 1 - PE) { + // sample specular lobe + rnd.x = Sample::stretch(rnd.x, 0.0f, 1 - PE); + auto ss = spec.sample(wo, rnd.x, rnd.y, rnd.z, doreflect, dorefract); + if (MAX_ABS_XYZ(ss.wi) < EPSILON) + return {}; + s.wi = ss.wi; + s.update(ss.weight, ss.pdf, 1 - PE); + s = eval_ec_lobe(s); + if (s.wi.z < 0) + // From "Efficient Rendering of Layered Materials using an + // Atomic Decomposition with Statistical Operators" (Belcour) + // equation 10. Adjust roughness with the equivalent of a + // reflection. + s.roughness = std::min( + 1.0f, Base::roughness() + * SQR(0.5f * (1 + (wo.z / (s.wi.z * spec.eta()))))); + } else { + // sample diffuse lobe + rnd.x = Sample::stretch(rnd.x, 1 - PE, PE); + bool back = !(!dorefract || (both && rnd.z < RT_ratio)); + s.wi = sample_ec_lobe(rnd.x, rnd.y, back); + s = eval_ec_lobe(s); + auto ss = spec.eval(wo, s.wi, doreflect, dorefract); + s.update(ss.weight, ss.pdf, 1 - PE); + } + const float cosNI = s.wi.z; + s.weight *= get_tint(cosNI); + return s; +} + +template +BSDL_INLINE_METHOD Power +DielectricLobe::get_tint(float cosNI) const +{ + return cosNI > 0 ? refl_tint : refr_tint; +} + +template +BSDL_INLINE_METHOD Sample +DielectricLobe::eval_ec_lobe(Sample s) const +{ + const float dpdf = (Eo * fabsf(s.wi.z)) * ONEOVERPI + * (s.wi.z > 0 ? RT_ratio : 1 - RT_ratio); + s.update(Power::UNIT(), dpdf, 1); + return s; +} + +template +BSDL_INLINE_METHOD Imath::V3f +DielectricLobe::sample_ec_lobe(float randu, float randv, + bool back) const +{ + const Imath::V3f wi = sample_cos_hemisphere(randu, randv); + return back ? Imath::V3f { wi.x, wi.y, -wi.z } : wi; +} + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_diffuse_decl.h b/src/libbsdl/include/BSDL/SPI/bsdf_diffuse_decl.h new file mode 100644 index 000000000..2204634e2 --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_diffuse_decl.h @@ -0,0 +1,151 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +template +struct DiffuseLobeGen : public Lobe { + using Base = Lobe; + + struct Data { + Imath::V3f N; + using lobe_type = DiffuseLobeGen; + }; + + static const char* name() { return TR ? "translucent" : "diffuse"; } + + template static typename LobeRegistry::Entry entry() + { + static_assert( + std::is_base_of::value); // Make no other assumptions + using R = LobeRegistry; + return { name(), { R::param(&D::N), R::close() } }; + } + + template + BSDL_INLINE_METHOD DiffuseLobeGen(T*, const BsdfGlobals& globals, + const Data& data); + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const; +}; + +template +using DiffuseLobe = DiffuseLobeGen; +template +using TranslucentLobe = DiffuseLobeGen; + +template struct BasicDiffuseLobe : public Lobe { + using Base = Lobe; + struct Data { + // microfacet params + Imath::V3f N; + Imath::C3f color; + float diffuse_roughness; + float translucent; + using lobe_type = BasicDiffuseLobe; + }; + template static typename LobeRegistry::Entry entry() + { + static_assert( + std::is_base_of::value); // Make no other assumptions + using R = LobeRegistry; + return { name(), + { R::param(&D::N), R::param(&D::color), + R::param(&D::diffuse_roughness), R::param(&D::translucent), + R::close() } }; + } + + template + BSDL_INLINE_METHOD BasicDiffuseLobe(T*, const BsdfGlobals& globals, + const Data& data); + + static const char* name() { return "basic_diffuse"; } + + BSDL_INLINE_METHOD Power albedo_impl() const { return diff_color; } + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const; + +private: + float diff_rough; + float diff_trans; + Power diff_color; +}; + +template struct ChandrasekharLobe : public Lobe { + using Base = Lobe; + struct Data { + Imath::V3f N; + Imath::C3f albedo; + + using lobe_type = ChandrasekharLobe; + }; + template + BSDL_INLINE_METHOD ChandrasekharLobe(T*, const BsdfGlobals& globals, + const Data& data); + + const char* name() const { return "chandrasekhar"; } + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const; + + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const; + +private: + BSDL_INLINE_METHOD float H(float a, float u) const; + BSDL_INLINE_METHOD Power H(float u) const; + + Power m_a; +}; + +// Diffusion Transport BRDF: +//   https://www.researchgate.net/publication/333325137_The_Albedo_Problem_in_Nonexponential_Radiative_Transfer +template struct DiffuseTLobe : public Lobe { + using Base = Lobe; + struct Data { + Imath::V3f N; + Imath::C3f albedo; + + using lobe_type = DiffuseTLobe; + }; + + template + BSDL_INLINE_METHOD DiffuseTLobe(T*, const BsdfGlobals& globals, + const Data& data); + + const char* name() const { return "deon-diffusion"; } + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const; + // Solve for single-scattering albedo c given diffuse reflectance kD under uniform hemisphere illumination + static BSDL_INLINE_METHOD Power albedoInvert(const Power in_R); + +private: + // H-function for Gamma-2 flights in 3D with isotropic scattering + static BSDL_INLINE_METHOD float HFunctionGamma2(const float u, + const float sqrt1minusc); + static BSDL_INLINE_METHOD Power HFunctionGamma2(const float u, + const Power sqrt1minusc); + + Power m_c; +}; + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_diffuse_impl.h b/src/libbsdl/include/BSDL/SPI/bsdf_diffuse_impl.h new file mode 100644 index 000000000..2d5b0ca8c --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_diffuse_impl.h @@ -0,0 +1,268 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +template +template +BSDL_INLINE_METHOD +DiffuseLobeGen::DiffuseLobeGen( + T* lobe, const BsdfGlobals& globals, + const DiffuseLobeGen::Data& data) + : Base(lobe, globals.visible_normal(data.N), 1.0f, globals.lambda_0, TR) +{ + Base::sample_filter = globals.get_sample_filter(Base::frame.Z, !TR); +} + +template +BSDL_INLINE_METHOD Sample +DiffuseLobeGen::eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const +{ + float IdotN = wi.z; + // For normal diffuse both wi and wo have to be above N + if ((!TR && IdotN <= 0) || + // otherwise just in different sides + (TR && IdotN >= 0)) + return {}; + return { wi, Power::UNIT(), fabsf(IdotN) * ONEOVERPI, 1.0f }; +} + +template +BSDL_INLINE_METHOD Sample +DiffuseLobeGen::sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const +{ + Imath::V3f wi = sample_cos_hemisphere(rnd.x, rnd.y); + if (TR) + wi.z = -wi.z; + Sample s = eval_impl(wo, wi); + return s; +} + +template +template +BSDL_INLINE_METHOD +BasicDiffuseLobe::BasicDiffuseLobe( + T* lobe, const BsdfGlobals& globals, + const typename BasicDiffuseLobe::Data& data) + : Base(lobe, globals.visible_normal(data.N), + 0.84f, // Legacy roughness + globals.lambda_0, data.translucent > 0) + , diff_rough(CLAMP(data.diffuse_roughness, 0.0f, 1.0f)) + , diff_trans(CLAMP(data.translucent, 0.0f, 1.0f)) + , diff_color(globals.wave(CLAMP(data.color, 0.0f, 1.0f))) +{ + Base::sample_filter = globals.get_sample_filter(Base::frame.Z, + data.translucent == 0); +} + +template +BSDL_INLINE_METHOD Sample +BasicDiffuseLobe::eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const +{ + assert(wo.z >= 0); + + const bool trans = diff_trans > 0; + const bool front = wi.z > 0; + + Sample sample = { wi, Power::ZERO(), 0, Base::roughness() }; + if (front || trans) { + const float cosNO = wo.z; + const float cosNI = fabsf(wi.z); + + const float diff_scale = front ? 1 - diff_trans : diff_trans; + // oren-nayar adjustment + const float s = wo.dot(wi) - cosNO * cosNI; + const float stinv = s > 0 ? s / std::max(cosNO, cosNI) : s; + const float ON = (1 - 0.235f * diff_rough) + * MAX(1 + diff_rough * stinv, 0.0f); + sample.weight = diff_color * ON; + sample.pdf = diff_scale * cosNI * ONEOVERPI; + } + return sample; +} + +template +BSDL_INLINE_METHOD Sample +BasicDiffuseLobe::sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const +{ + const float cosNO = wo.z; + if (cosNO <= 0) + return {}; + + const bool trans = rnd.x < diff_trans; + const float x = trans ? Sample::stretch(rnd.x, 0, diff_trans) + : Sample::stretch(rnd.x, diff_trans, 1 - diff_trans); + Imath::V3f wi = sample_cos_hemisphere(x, rnd.y); + wi.z = trans ? -wi.z : wi.z; // Flip if transmissive + // evaluate brdf on outgoing direction + return eval_impl(wo, wi); +} + +template +template +BSDL_INLINE_METHOD +ChandrasekharLobe::ChandrasekharLobe(T* lobe, + const BsdfGlobals& globals, + const Data& data) + : Base(lobe, globals.visible_normal(data.N), 1.0f, globals.lambda_0, false) + , m_a(globals.wave(data.albedo).clamped(0, 1)) +{ + Base::sample_filter = globals.get_sample_filter(Base::frame.Z, true); +} + +template +BSDL_INLINE_METHOD Sample +ChandrasekharLobe::eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const +{ + /* When translucent we mirror wi to the other side of the normal and perform + * a regular oren-nayar BSDF. */ + const float NL = wi.z; + const float NV = wo.z; + if (NL > 0 && NV > 0) { + const Power HL = H(NL); + const Power HV = H(NV); + const float pdf = NL * ONEOVERPI; + + // A HITCHHIKER’S GUIDE TO MULTIPLE SCATTERING v0.1.3 + // 7.3.4 Emerging Distribution (BRDF) [Chandrasekhar 1960] + // Equation 272 + const Power b = m_a * HL * HV * (0.25f / (NL + NV)); + + return { wi, b, pdf, 1.0f }; + } + return {}; +} + +template +BSDL_INLINE_METHOD Sample +ChandrasekharLobe::sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const +{ + Imath::V3f wi = sample_cos_hemisphere(rnd.x, rnd.y); + return eval_impl(wo, wi); +} + +template +BSDL_INLINE_METHOD float +ChandrasekharLobe::H(float a, float u) const +{ + // this is an approximation accurate to within 1% for all albedos and inclinations + // A HITCHHIKER’S GUIDE TO MULTIPLE SCATTERING v0.1.3 + // 8.5.3 H-function Approximations, Approximation 2 [Hapke 2012] + const float y = sqrtf(1 - a); + const float n = (1 - y) / (1 + y); + return 1 + / (1 + - (1 - y) * u + * (n + (1 - n * 0.5f - n * u) * fast_log(1 / u + 1))); +} + +template +BSDL_INLINE_METHOD Power +ChandrasekharLobe::H(float u) const +{ + return Power([&](int i) { return H(m_a[i], u); }, 1); +} + +template +template +BSDL_INLINE_METHOD +DiffuseTLobe::DiffuseTLobe(T* lobe, const BsdfGlobals& globals, + const Data& data) + : Base(lobe, globals.visible_normal(data.N), 1.0f, globals.lambda_0, false) + , m_c(globals.wave(data.albedo).clamped(0, 1)) +{ + Base::sample_filter = globals.get_sample_filter(Base::frame.Z, true); +} + +template +BSDL_INLINE_METHOD Sample +DiffuseTLobe::eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const +{ + const float ui = wi.z; + const float uo = wo.z; + if (ui > 0 && uo > 0) { + const float pdf = ui * ONEOVERPI; + + Power sqrt1minusc([&](int i) { return sqrtf(1.0f - m_c[i]); }, 1); + + const Power Hi = HFunctionGamma2(ui, sqrt1minusc); + const Power Ho = HFunctionGamma2(uo, sqrt1minusc); + + const Power Hterm = Hi * Ho * (1.0f / (ui + uo)); + + const float math1 = ui * ui + 3.0f * ui * uo + uo * uo; + const float uiplus2 = (1.0f + ui) * (1.0f + ui); + const float uoplus2 = (1.0f + uo) * (1.0f + uo); + const float math2 = 1.0f / (uiplus2 * uoplus2); + const float math3 = (ui * ui + 2.0f * uo + 3.0f * ui * uo) + * (uo * uo + 2.0f * ui + 3.0f * ui * uo); + const float math4 = (ui * ui * ui + uo * uo * uo + + ui * uo + * (2.0f * (1.0f + ui * ui + uo * uo) + + 3.0f * (ui + uo) + 6.0f * ui * uo)) + * ui * uo / (ui + uo); + + Power b + = 0.25f * m_c * Hterm * Hterm + * (Power(math1 / (ui + uo), 1) + - 0.5f * math2 + * ((Power::UNIT() - sqrt1minusc) * math3 + m_c * math4)); + + return { wi, b, pdf, 1.0f }; + } + return {}; +} + +template +BSDL_INLINE_METHOD Sample +DiffuseTLobe::sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const +{ + Imath::V3f wi = sample_cos_hemisphere(rnd.x, rnd.y); + return eval_impl(wo, wi); +} + +// Solve for single-scattering albedo c given diffuse reflectance kD under uniform hemisphere illumination +template +BSDL_INLINE_METHOD Power +DiffuseTLobe::albedoInvert(const Power in_R) +{ + const Power white(1, 1); + const Power n = in_R * 4; + const Power d = (white + in_R) * (white + in_R); + return n / d; +} + +// H-function for Gamma-2 flights in 3D with isotropic scattering +template +BSDL_INLINE_METHOD float +DiffuseTLobe::HFunctionGamma2(const float u, const float sqrt1minusc) +{ + return (1.0f + u) / (1.0f + sqrt1minusc * u); +} + +template +BSDL_INLINE_METHOD Power +DiffuseTLobe::HFunctionGamma2(const float u, const Power sqrt1minusc) +{ + return Power([&](int i) { return HFunctionGamma2(u, sqrt1minusc[i]); }, 1); +} + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_metal_decl.h b/src/libbsdl/include/BSDL/SPI/bsdf_metal_decl.h new file mode 100644 index 000000000..1ed9e22e8 --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_metal_decl.h @@ -0,0 +1,86 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +struct MetalFresnel { + BSDL_INLINE_METHOD MetalFresnel() {} + + BSDL_INLINE_METHOD + MetalFresnel(Power c, Power edge, float artist_blend, float artist_power); + BSDL_INLINE_METHOD Power eval(float cosine) const; + + BSDL_INLINE_METHOD Power avg() const; + +private: + Power r, g; + float blend; // blend between physical and artistic + float p; // power of artistic mode +}; + +template struct MetalLobe : public Lobe { + using Base = Lobe; + struct Data { + // microfacet params + Imath::V3f N, U; + float roughness; + float anisotropy; + // fresnel params + Imath::C3f color; + Imath::C3f edge_tint; + float artist_blend; + float artist_power; + float force_eta; + using lobe_type = MetalLobe; + }; + template static typename LobeRegistry::Entry entry() + { + static_assert( + std::is_base_of::value); // Make no other assumptions + using R = LobeRegistry; + return { name(), + { R::param(&D::N), R::param(&D::U), R::param(&D::roughness), + R::param(&D::anisotropy), R::param(&D::color), + R::param(&D::edge_tint), R::param(&D::artist_blend), + R::param(&D::artist_power), R::param(&D::force_eta), + R::close() } }; + } + + static BSDL_INLINE_METHOD float adjust_reflection(float r, float outer_ior); + static BSDL_INLINE_METHOD Power adjust_reflection(float outer_ior, Power r, + float force_eta); + + template + BSDL_INLINE_METHOD MetalLobe(T*, const BsdfGlobals& globals, + const Data& data); + + static const char* name() { return "metal"; } + + BSDL_INLINE_METHOD Power albedo_impl() const + { + return spec.getFresnel().avg(); + } + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const; + +private: + typedef MicrofacetMS GGX; + Imath::V3f U; + GGX spec; +}; + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_metal_impl.h b/src/libbsdl/include/BSDL/SPI/bsdf_metal_impl.h new file mode 100644 index 000000000..d3f5ff278 --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_metal_impl.h @@ -0,0 +1,122 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +BSDL_INLINE_METHOD +MetalFresnel::MetalFresnel(Power c, Power edge, float artist_blend, + float artist_power) + : r(c.clamped(0.0f, 0.99f)) + , g(edge.clamped(0.0f, 1.00f)) + , blend(CLAMP(artist_blend, 0.0f, 1.00f)) + , p(CLAMP(artist_power, 0.0f, 1.00f)) +{ +} + +BSDL_INLINE_METHOD Power +MetalFresnel::eval(float cosine) const +{ + return LERP(blend, fresnel_metal(cosine, r, g, 1), + fresnel_schlick(cosine, r, g, p)); +} + +BSDL_INLINE_METHOD Power +MetalFresnel::avg() const +{ + // the following is a mathematica fit to the true integral as a function of r and g: + // max error is ~2.02% + // avg error is ~0.25% + Power metal_avg + = Power(0.087237f, 1) + + r + * (Power(0.782654f, 1) + 0.19744f * g + + r * (Power(-0.136432f, 1) - 0.2586f * g + r * 0.278708f)) + + g + * (Power(0.0230685f, 1) + + g + * (Power(-0.0864902f, 1) + 0.0360605f * r + + g * 0.0774594f)); + metal_avg = metal_avg.clamped(0, 1); + + // Integral[2*c*(r + (g-r) * (1-c)^(1/p),{c,0,1}] happens to have a closed form solution + const float A = 2 * SQR(p); + const float B = 1 + 3 * p; + Power schlick_avg = (g * A + r * B) * (1.0f / (A + B)); + + return LERP(blend, metal_avg, schlick_avg); +} + +template +BSDL_INLINE_METHOD float +MetalLobe::adjust_reflection(float r, float outer_ior) +{ + // Get IOR for this facing angle reflection, and divide by outer_ior + const float sqrtr = sqrtf(CLAMP(r, 0.0f, 1.0f)); + const float eta + = std::max(std::min((1 + sqrtr) / (1 - sqrtr), BIG) / outer_ior, 1.0f); + // Then map it back to a facing angle reflection + return SQR((eta - 1) / (1 + eta)); +} + +template +BSDL_INLINE_METHOD Power +MetalLobe::adjust_reflection(float outer_ior, Power r, + float force_eta) +{ + force_eta = CLAMP(force_eta, 0.0f, 1.0f); + if (outer_ior == 1.0f || force_eta == 1) + return r; + Power adjusted([&](int i) { return adjust_reflection(r[i], outer_ior); }, + 1); + return LERP(force_eta, adjusted, r); +} + +template +template +BSDL_INLINE_METHOD +MetalLobe::MetalLobe(T* lobe, const BsdfGlobals& globals, + const Data& data) + : Base(lobe, globals.visible_normal(data.N), data.U, + globals.regularize_roughness(data.roughness), globals.lambda_0, + false) +{ + Base::sample_filter = globals.get_sample_filter(Base::frame.Z, true); + Power color = adjust_reflection(globals.outer_ior, globals.wave(data.color), + data.force_eta); + spec = GGX(GGXDist(Base::roughness(), CLAMP(data.anisotropy, 0.0f, 1.0f)), + MetalFresnel(color, globals.wave(data.edge_tint), + data.artist_blend, data.artist_power), + Base::frame.Z.dot(globals.wo), Base::roughness()); +} + +template +BSDL_INLINE_METHOD Sample +MetalLobe::eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const +{ + Sample s = spec.eval(wo, wi); + s.roughness = Base::roughness(); + return s; +} + +template +BSDL_INLINE_METHOD Sample +MetalLobe::sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const +{ + Sample s = spec.sample(wo, rnd.x, rnd.y, rnd.z); + s.roughness = Base::roughness(); + return s; +} + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_oren_nayar_decl.h b/src/libbsdl/include/BSDL/SPI/bsdf_oren_nayar_decl.h new file mode 100644 index 000000000..b9096a239 --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_oren_nayar_decl.h @@ -0,0 +1,58 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +template +struct OrenNayarLobeGen : public Lobe { + using Base = Lobe; + + struct Data { + Imath::V3f N; + float sigma; + int improved; + using lobe_type = OrenNayarLobeGen; + }; + template static typename LobeRegistry::Entry entry() + { + static_assert( + std::is_base_of::value); // Make no other assumptions + using R = LobeRegistry; + return { name(), + { R::param(&D::N), R::param(&D::sigma), + R::param(&D::improved, "improved"), R::close() } }; + } + + template + BSDL_INLINE_METHOD OrenNayarLobeGen(T*, const BsdfGlobals& globals, + const Data& data); + + static const char* name() + { + return TR ? "oren_nayar_translucent" : "oren_nayar"; + } + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& _wi) const; + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f rnd) const; + float m_A, m_B; + bool m_improved; +}; + +template +using OrenNayarLobe = OrenNayarLobeGen; +template +using OrenNayarTranslucentLobe = OrenNayarLobeGen; + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_oren_nayar_impl.h b/src/libbsdl/include/BSDL/SPI/bsdf_oren_nayar_impl.h new file mode 100644 index 000000000..c1ce1b576 --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_oren_nayar_impl.h @@ -0,0 +1,72 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +template +template +BSDL_INLINE_METHOD +OrenNayarLobeGen::OrenNayarLobeGen(T* lobe, + const BsdfGlobals& globals, + const Data& data) + : Base(lobe, globals.visible_normal(data.N), 1.0f, globals.lambda_0, TR) + , m_improved(data.improved) +{ + Base::sample_filter = globals.get_sample_filter(Base::frame.Z, !TR); + if (m_improved) { + m_A = 1 - 0.235f * data.sigma; + m_B = data.sigma * m_A; + } else { + float s2 = SQR(data.sigma); + m_A = 1 - 0.50f * s2 / (s2 + 0.33f); + m_B = 0.45f * s2 / (s2 + 0.09f); + } +} + +template +BSDL_INLINE_METHOD Sample +OrenNayarLobeGen::eval_impl(const Imath::V3f& wo, + const Imath::V3f& _wi) const +{ + // When translucent we mirror wi to the other side of the normal and perform + // a regular oren-nayar BSDF. */ + const Imath::V3f N = Base::frame.Z; + const Imath::V3f wi = TR ? _wi - 2 * _wi.z * N : _wi; + const float NL = wi.z; + const float NV = wo.z; + if (NL > 0 && NV > 0) { + const float pdf = NL * ONEOVERPI; + // Simplified math from: A tiny improvement of Oren-Nayar reflectance model - Yasuhiro Fujii + // http://mimosa-pudica.net/improved-oren-nayar.html + const float LV = wi.dot(wo); + + const float s = LV - NL * NV; + const float stinv = s > 0 ? s / MAX(NL, NV) : (m_improved ? s : 0); + const float out = MAX(m_A + m_B * stinv, 0.0f); + return { _wi, Power(out, 1), pdf, 1.0f }; + } + return {}; +} + +template +BSDL_INLINE_METHOD Sample +OrenNayarLobeGen::sample_impl(const Imath::V3f& wo, + const Imath::V3f rnd) const +{ + Imath::V3f wi = sample_cos_hemisphere(rnd.x, rnd.y); + if (TR) + wi.z = -wi.z; + return eval_impl(wo, wi); +} + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_physicalhair_decl.h b/src/libbsdl/include/BSDL/SPI/bsdf_physicalhair_decl.h new file mode 100644 index 000000000..dc76fe465 --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_physicalhair_decl.h @@ -0,0 +1,220 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include + +#include +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +constexpr int PHYSICAL_HAIR_DEBUG_R = 1; +constexpr int PHYSICAL_HAIR_DEBUG_TT = 2; +constexpr int PHYSICAL_HAIR_DEBUG_TRT = 3; + +static constexpr int P_MAX = 3; + +// From "An Energy-Conserving Hair Reflectance Model" (Weta) +// and "Importance Sampling for Physically-Based Hair Fiber Models" d'Eon et al +// and "A Practical and Controllable Hair and Fur Model for Production Path +// Tracing" (Disney) +// A physically based hair BSDF with four lobes R, TT, TRT and a fourth +// simplified sum of the remaining bounces. +template struct PhysicalHairLobe : public Lobe { + using Base = Lobe; + struct Data { + Imath::V3f T; + float IOR; + float offset; + float lroughness; + float troughness; + float aroughness; + Imath::C3f R_tint; + Imath::C3f TT_tint; + Imath::C3f TRT_tint; + Imath::C3f absorption; + int flags; + float force_eta; + float scattering; + float h; // Offset in the curve + using lobe_type = PhysicalHairLobe; + }; + + template static typename LobeRegistry::Entry entry() + { + static_assert( + std::is_base_of::value); // Make no other assumptions + using R = LobeRegistry; + return { name(), + { R::param(&D::T), R::param(&D::IOR), R::param(&D::offset), + R::param(&D::lroughness), R::param(&D::troughness), + R::param(&D::aroughness), R::param(&D::R_tint), + R::param(&D::TT_tint), R::param(&D::TRT_tint), + R::param(&D::absorption), R::param(&D::scattering), + R::param(&D::force_eta), R::param(&D::flags), R::close() } }; + } + + static constexpr float SQRT_PI_OVER_8 = 0.626657069f; + + template + BSDL_INLINE_METHOD PhysicalHairLobe(T*, const BsdfGlobals& globals, + const Data& data); + + static const char* name() { return "physical_hair"; } + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f& _rnd) const; + + BSDL_INLINE_METHOD StaticCdf lobe_cdf() const; + + static BSDL_INLINE_METHOD std::array + Ap(float cosThetaO, float eta, float h, const Power T, float lambda_0); + static BSDL_INLINE_METHOD + std::pair, std::array> + sincos_alpha(float offset); + static BSDL_INLINE_METHOD + std::pair, std::array> + variances(float lrough, float trough, float arough, float scattering, + float cosThetaO); + static BSDL_INLINE_METHOD float log_bessi0(float x); + static BSDL_INLINE_METHOD float bessi0_time_exp(float x, float exponent); + static BSDL_INLINE_METHOD float Mp(float cosThetaI, float cosThetaO, + float sinThetaI, float sinThetaO, + float v); + static BSDL_INLINE_METHOD float Phi(int p, float gammaO, float gammaT); + static BSDL_INLINE_METHOD float TrimmedLogistic(float x, float s); + static BSDL_INLINE_METHOD float Np(float phi, int p, float s, float gammaO, + float gammaT); + static BSDL_INLINE_METHOD float SampleTrimmedLogistic(float u, float s); + static BSDL_INLINE_METHOD float RemapLongitudinalRoughness(float lr); + static BSDL_INLINE_METHOD float RemapAzimuthalRoughness(float ar); + + std::array ap; + float lrough; + float trough; + float arough; + float scattering; + // These are for angles from -pi/2 to pi/2 + float gammaO, gammaT; + float offset; +}; + +template struct HairDiffuseLobe : public Lobe { + using Base = Lobe; + struct Data { + Imath::V3f T; + float IOR; + Imath::C3f absorption; + float eccentricity; + float anisotropy; + float flatten_density; + using lobe_type = HairDiffuseLobe; + }; + template static typename LobeRegistry::Entry entry() + { + static_assert( + std::is_base_of::value); // Make no other assumptions + using R = LobeRegistry; + return { name(), + { R::param(&D::T), R::param(&D::IOR), R::param(&D::absorption), + R::param(&D::eccentricity), R::param(&D::anisotropy), + R::param(&D::flatten_density), R::close() } }; + } + + static constexpr float MIN_ROUGH = 0.025f; + + template + BSDL_INLINE_METHOD HairDiffuseLobe(T*, const BsdfGlobals& globals, + const Data& data); + + BSDL_INLINE_METHOD Power albedo_impl() const { return color; } + static const char* name() { return "hair_diffuse"; } + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const; + +private: + static BSDL_INLINE_METHOD float ecc2longrough(float ecc, float aniso); + static BSDL_INLINE_METHOD float ecc2s(float ecc, float aniso); + static BSDL_INLINE_METHOD float ecc2roughness(float ecc); + static BSDL_INLINE_METHOD float roughness2ecc(float rough, float ecc); + static BSDL_INLINE_METHOD float albedo2absorption(float x, float g); + static BSDL_INLINE_METHOD Power albedo2absorption(Power x, float lambda_0, + float g); + + Power color; + float eccentricity; + float anisotropy; +}; + +// This BSDF comes from PhysicalHairLobe but isolating either R or TRT +template struct HairSpecularLobe : public Lobe { + using Base = Lobe; + struct Data : public LayeredData { + Imath::V3f T; + float IOR; + float offset; + float lroughness; + float aroughness; + int trt; + Imath::C3f tint; + Imath::C3f absorption; + float force_eta; + float flatten_density; + float h; // Offset in the curve + using lobe_type = HairSpecularLobe; + }; + template static typename LobeRegistry::Entry entry() + { + static_assert( + std::is_base_of::value); // Make no other assumptions + using R = LobeRegistry; + return { name(), + { R::param(&D::closure), R::param(&D::T), R::param(&D::IOR), + R::param(&D::offset), R::param(&D::lroughness), + R::param(&D::aroughness), R::param(&D::trt), + R::param(&D::tint), R::param(&D::absorption), + R::param(&D::flatten_density), R::param(&D::force_eta), + R::close() } }; + } + + template + BSDL_INLINE_METHOD HairSpecularLobe(T*, const BsdfGlobals& globals, + const Data& data); + + static const char* name() { return "hair_specular"; } + BSDL_INLINE_METHOD Power albedo_impl() const { return color; } + + // Filter the BSDF under it + BSDL_INLINE_METHOD Power filter_o(const Imath::V3f& wo) const + { + return Power(1 - fresnel_term, 1); + } + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f& _rnd) const; + + Power color; + float long_v; // Variance arg for M distribution + float azim_s; // Scale arg for azimuthal distribution + float gammaO, gammaT; // -pi/2 to pi/2 angle + float sin2kAlpha, cos2kAlpha; // Precomputed scale rotation + float fresnel_term; + bool trt; +}; + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_physicalhair_impl.h b/src/libbsdl/include/BSDL/SPI/bsdf_physicalhair_impl.h new file mode 100644 index 000000000..42d8fb000 --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_physicalhair_impl.h @@ -0,0 +1,837 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include + +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +template +template +BSDL_INLINE_METHOD +PhysicalHairLobe::PhysicalHairLobe(T* lobe, + const BsdfGlobals& globals, + const Data& data) + : Base(lobe, data.T, globals.wo, + std::min(globals.regularize_roughness(data.lroughness), + globals.regularize_roughness(data.troughness)), + globals.lambda_0, true) + , offset(data.offset) +{ + Base::sample_filter = globals.get_sample_filter(Base::frame.Z, false); + + const float input_ior = CLAMP(data.IOR, 1.001f, BIG); + const float eta = LERP(CLAMP(data.force_eta, 0.0f, 1.0f), + globals.relative_eta(data.IOR), input_ior); + lrough = globals.regularize_roughness(data.lroughness); + trough = globals.regularize_roughness(data.troughness); + arough = CLAMP(data.aroughness, 0.01f, 1.0f); + scattering = CLAMP(SQR(data.scattering), 0.0f, 0.99f); + const int debug = globals.path_roughness > 0 ? 0 : data.flags; + + // Compute hair coordinate system terms related to _wo_ + const float sinThetaO = CLAMP(globals.wo.dot(Base::frame.Z), -1.0f, 1.0f); + const float cosThetaO = sqrtf(1 - SQR(sinThetaO)); + + constexpr auto fast_exp = BSDLConfig::Fast::expf; + constexpr auto fast_asin = BSDLConfig::Fast::asinf; + gammaO = fast_asin(data.h); + + // Compute $\cos \thetat$ for refracted ray + float sinThetaT = sinThetaO / eta; + float cosThetaT = sqrtf(MAX(1 - SQR(sinThetaT), 0.0f)); + + // Compute $\gammat$ for refracted ray + float etap = sqrtf(SQR(eta) - SQR(sinThetaO)) / cosThetaO; + float sinGammaT = data.h / etap; + float cosGammaT = sqrtf(1 - SQR(sinGammaT)); + gammaT = fast_asin(sinGammaT); + + // Compute the transmittance _T_ of a single path through the cylinder + float l = 2 * cosGammaT / cosThetaT; + Power absorption = globals.wave(data.absorption); + Power tau([&](int i) { return fast_exp(-absorption[i] * l); }, + globals.lambda_0); + + ap = Ap(cosThetaO, eta, data.h, tau, globals.lambda_0); + ap[0] *= globals.wave(data.R_tint).clamped(0.0f, 1.0f); + ap[1] *= globals.wave(data.TT_tint).clamped(0.0f, 1.0f); + ap[2] *= globals.wave(data.TRT_tint).clamped(0.0f, 1.0f); + + // If debugging isolate lobes + if (debug) { + for (int i = 0; i <= P_MAX; ++i) + if (i != (debug - 1)) + ap[i] = Power::ZERO(); + } + auto cdf = lobe_cdf(); + for (int i = 0; i <= P_MAX; ++i) + ap[i] = ap[i].clamped(0.0f, cdf.pdf(i)); +} + +template +BSDL_INLINE_METHOD StaticCdf +PhysicalHairLobe::lobe_cdf() const +{ + StaticCdf cdf; + for (int i = 0; i <= P_MAX; ++i) + cdf[i] = ap[i].max(); + const float total = cdf.build(); + // if lobes are all 0, just pretend like we have some chance of sampling the + // last lobe (even though its 0 just to avoid special cases) + if (total == 0) + cdf[P_MAX] = 1; + return cdf; +} + +template +BSDL_INLINE_METHOD Sample +PhysicalHairLobe::eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const +{ + const float sinThetaO = CLAMP(wo.z, -1.0f, 1.0f); + const float cosThetaO = sqrtf(1 - SQR(sinThetaO)); + const float sinThetaI = CLAMP(wi.z, -1.0f, 1.0f); + const float cosThetaI = sqrtf(1 - SQR(sinThetaI)); + const float phiI = fast_atan2(wi.y, wi.x); + std::array sin2kAlpha, cos2kAlpha; + // Compute alpha terms for hair scales + auto sincosa = sincos_alpha(offset); + sin2kAlpha = sincosa.first; + cos2kAlpha = sincosa.second; + + const float phiO = 0; + const float phi = phiI - phiO; + + auto cdf = lobe_cdf(); + Sample sample = { wi }; + BSDL_UNROLL() + for (int p = 0; p <= P_MAX; ++p) { + const float lobe_prob = cdf.pdf(p); + if (lobe_prob <= PDF_MIN) + continue; + + // Compute sin/cos theta to account for scales + float sinThetaOp, cosThetaOp; + switch (p) { + case 0: { + sinThetaOp = sinThetaO * cos2kAlpha[1] - cosThetaO * sin2kAlpha[1]; + cosThetaOp = cosThetaO * cos2kAlpha[1] + sinThetaO * sin2kAlpha[1]; + break; + } + case 1: { + sinThetaOp = sinThetaO * cos2kAlpha[0] + cosThetaO * sin2kAlpha[0]; + cosThetaOp = cosThetaO * cos2kAlpha[0] - sinThetaO * sin2kAlpha[0]; + break; + } + case 2: { + sinThetaOp = sinThetaO * cos2kAlpha[2] + cosThetaO * sin2kAlpha[2]; + cosThetaOp = cosThetaO * cos2kAlpha[2] - sinThetaO * sin2kAlpha[2]; + break; + } + default: { + sinThetaOp = sinThetaO; + cosThetaOp = cosThetaO; + break; + } + } + + // handle out of range from scale adjustment + cosThetaOp = fabsf(cosThetaOp); + + auto vs = variances(lrough, trough, arough, scattering, cosThetaO); + std::array v = vs.first, s = vs.second; + + const float lobe_pdf = Mp(cosThetaI, cosThetaOp, sinThetaI, sinThetaOp, + v[p]) + * Np(phi, p, s[p], gammaO, gammaT); + + sample.update(ap[p], lobe_pdf, lobe_prob); + + // NOTE: this should almost always be the case, but because the pdf is nudged during cdf normalization, we can end up with + // weights ever so slightly larger than 1.0 -- so just clamp + sample.weight = sample.weight.clamped(0, 1); + assert(sample.pdf >= 0); + } + + sample.roughness = Base::roughness(); + return sample; +} + +template +BSDL_INLINE_METHOD Sample +PhysicalHairLobe::sample_impl(const Imath::V3f& wo, + const Imath::V3f& _rnd) const +{ + const float sinThetaO = CLAMP(wo.z, -1.0f, 1.0f); + const float cosThetaO = sqrtf(1 - SQR(sinThetaO)); + int p = 0; + float pdf = 0; + Imath::V3f rnd = _rnd; + auto cdf = lobe_cdf(); + rnd.x = cdf.sample(rnd.x, &p, &pdf); + std::array sin2kAlpha, cos2kAlpha; + // Compute alpha terms for hair scales + auto sincosa = sincos_alpha(offset); + sin2kAlpha = sincosa.first; + cos2kAlpha = sincosa.second; + + // Update sin/cos thetao to account for scales + float sinThetaOp, cosThetaOp; + switch (p) { + case 0: { + sinThetaOp = sinThetaO * cos2kAlpha[1] - cosThetaO * sin2kAlpha[1]; + cosThetaOp = cosThetaO * cos2kAlpha[1] + sinThetaO * sin2kAlpha[1]; + break; + } + case 1: { + sinThetaOp = sinThetaO * cos2kAlpha[0] + cosThetaO * sin2kAlpha[0]; + cosThetaOp = cosThetaO * cos2kAlpha[0] - sinThetaO * sin2kAlpha[0]; + break; + } + case 2: { + sinThetaOp = sinThetaO * cos2kAlpha[2] + cosThetaO * sin2kAlpha[2]; + cosThetaOp = cosThetaO * cos2kAlpha[2] - sinThetaO * sin2kAlpha[2]; + break; + } + default: { + sinThetaOp = sinThetaO; + cosThetaOp = cosThetaO; + break; + } + } + + auto vs = variances(lrough, trough, arough, scattering, cosThetaO); + std::array v = vs.first, s = vs.second; + + constexpr auto fast_exp = BSDLConfig::Fast::expf; + constexpr auto fast_log = BSDLConfig::Fast::logf; + constexpr auto fast_cos = BSDLConfig::Fast::cosf; + constexpr auto fast_sincos = BSDLConfig::Fast::sincosf; + // Sample $M_p$ to compute $\thetai$ + rnd.x = MAX(rnd.x, 1e-5f); + float cosTheta + = 1 + v[p] * fast_log(rnd.x + (1 - rnd.x) * fast_exp(-2 / v[p])); + float sinTheta = sqrtf(MAX(1 - SQR(cosTheta), 0.0f)); + float cosPhi = fast_cos(2 * PI * rnd.y); + float sinThetaI = -cosTheta * sinThetaOp + sinTheta * cosPhi * cosThetaOp; + float cosThetaI = sqrtf(MAX(1 - SQR(sinThetaI), 0.0f)); + + // Sample N_p to compute delta_phi + float dphi; + if (p < P_MAX) + dphi = Phi(p, gammaO, gammaT) + SampleTrimmedLogistic(rnd.z, s[p]); + else + dphi = 2 * PI * rnd.z; + // Compute wi from sampled hair scattering angles + const float phiO = 0; + float phiI = phiO + dphi; + float cos_phi_i, sin_phi_i; + fast_sincos(phiI, &sin_phi_i, &cos_phi_i); + + Imath::V3f wi = { cos_phi_i * cosThetaI, sin_phi_i * cosThetaI, sinThetaI }; + return eval_impl(wo, wi); +} + +template +BSDL_INLINE_METHOD std::array +PhysicalHairLobe::Ap(float cosThetaO, float eta, float h, + const Power T, float lambda_0) +{ + std::array ap; + // Compute $p=0$ attenuation at initial cylinder intersection + float cosGammaO = sqrtf(MAX(1 - h * h, 0.0f)); + float cosTheta = cosThetaO * cosGammaO; + float f = fresnel_dielectric(cosTheta, eta); + ap[0] = Power(f, 1); + + // Compute $p=1$ attenuation term + ap[1] = SQR(1 - f) * T; + + // Compute attenuation terms up to $p=_pMax_$ + for (int p = 2; p < P_MAX; ++p) + ap[p] = ap[p - 1] * T * f; + + // Compute attenuation term accounting for remaining orders of scattering + ap[P_MAX] = ap[P_MAX - 1] * f * T; + ap[P_MAX].update([&](int i, + float v) { return v / MAX(1.0f - T[i] * f, 1e-5f); }, + lambda_0); + for (int p = 0; p <= P_MAX; p++) { + assert(ap[p].min(lambda_0) >= 0 && ap[p].max() <= 1); + } + return ap; +} + +template +BSDL_INLINE_METHOD std::pair, std::array> +PhysicalHairLobe::sincos_alpha(float offset) +{ + constexpr auto fast_sin = BSDLConfig::Fast::sinf; + std::array sin2kAlpha, cos2kAlpha; + sin2kAlpha[0] = fast_sin(offset); + cos2kAlpha[0] = sqrtf(1 - SQR(sin2kAlpha[0])); + for (int i = 1; i < P_MAX; ++i) { + sin2kAlpha[i] = 2 * cos2kAlpha[i - 1] * sin2kAlpha[i - 1]; + cos2kAlpha[i] = SQR(cos2kAlpha[i - 1]) - SQR(sin2kAlpha[i - 1]); + } + return { sin2kAlpha, cos2kAlpha }; +} + +template +BSDL_INLINE_METHOD + std::pair, std::array> + PhysicalHairLobe::variances(float lrough, float trough, + float arough, float scattering, + float cosThetaO) +{ + constexpr auto fast_exp = BSDLConfig::Fast::expf; + constexpr auto fast_log1p = BSDLConfig::Fast::log1pf; + std::array v, s; + const float sigma_s = -fast_log1p(-scattering); + // This is the amount of light that goes unscattered through the hair + // medium. We use this to boost exit roughness and fake scattering. + const float unscattered = fast_exp(-sigma_s / cosThetaO); + v[0] = RemapLongitudinalRoughness(lrough); // R lobe (primary spec) + v[1] = 0.25f + * RemapLongitudinalRoughness( + sum_max(trough, 1 - unscattered, + 1.0f)); // TT lobe (transmission) + v[2] = 4 + * RemapLongitudinalRoughness( + sum_max(lrough, 1 - SQR(unscattered), + 1.0f)); // TRT lobe (secondary spec) + for (int p = 3; p <= P_MAX; ++p) + v[p] = v[2]; // energy conservation lobe + + // Azimuthal roughness + s[0] = RemapAzimuthalRoughness(arough); + s[1] = RemapAzimuthalRoughness(sum_max(arough, 1 - unscattered, 1.0f)); + s[2] = RemapAzimuthalRoughness(sum_max(arough, 1 - SQR(unscattered), 1.0f)); + for (int p = 3; p <= P_MAX; ++p) + s[p] = s[2]; // energy conservation lobe + return { v, s }; +} + +template +BSDL_INLINE_METHOD float +PhysicalHairLobe::log_bessi0(float x) +{ + constexpr auto fast_log = BSDLConfig::Fast::logf; + constexpr auto fast_log1p = BSDLConfig::Fast::log1pf; + float ax = fabsf(x); + if (ax < 3.75f) { + float y = SQR(x / 3.75f); + return fast_log1p( + y + * (3.5156229f + + y + * (3.0899424f + + y + * (1.2067492f + + y + * (0.2659732f + + y + * (0.360768e-1f + + y * 0.45813e-2f)))))); + } else { + float y = 3.75f / ax; + return ax + + fast_log( + 1 / sqrtf(ax) + * (0.39894228f + + y + * (0.1328592e-1f + + y + * (0.225319e-2f + + y + * (-0.157565e-2f + + y + * (0.916281e-2f + + y + * (-0.2057706e-1f + + y + * (0.2635537e-1f + + y + * (-0.1647633e-1f + + y * 0.392377e-2f))))))))); + } +} + +template +BSDL_INLINE_METHOD float +PhysicalHairLobe::bessi0_time_exp(float x, float exponent) +{ + constexpr auto fast_exp = BSDLConfig::Fast::expf; + float ax = fabsf(x); + if (ax < 3.75f) { + float y = SQR(x / 3.75f); + return fast_exp(exponent) + * (1.0f + + y + * (3.5156229f + + y + * (3.0899424f + + y + * (1.2067492f + + y + * (0.2659732f + + y + * (0.360768e-1f + + y * 0.45813e-2f)))))); + } else { + float y = 3.75f / ax; + return (fast_exp(ax + exponent) / sqrtf(ax)) + * (0.39894228f + + y + * (0.1328592e-1f + + y + * (0.225319e-2f + + y + * (-0.157565e-2f + + y + * (0.916281e-2f + + y + * (-0.2057706e-1f + + y + * (0.2635537e-1f + + y + * (-0.1647633e-1f + + y * 0.392377e-2f)))))))); + } +} + +template +BSDL_INLINE_METHOD float +PhysicalHairLobe::Mp(float cosThetaI, float cosThetaO, + float sinThetaI, float sinThetaO, float v) +{ + constexpr auto fast_exp = BSDLConfig::Fast::expf; + constexpr auto fast_log = BSDLConfig::Fast::logf; + + float a = cosThetaI * cosThetaO / v; + float b = sinThetaI * sinThetaO / v; + assert(!std::isnan(a)); + assert(!std::isnan(b)); + float mp = v <= .1f ? fast_exp(log_bessi0(a) - b - 1 / v + 0.6931f + + fast_log(1 / (2 * v))) + : bessi0_time_exp(a, -b) / (fast_sinh(1 / v) * 2 * v); + assert(mp == mp); + return mp; +} + +template +BSDL_INLINE_METHOD float +PhysicalHairLobe::Phi(int p, float gammaO, float gammaT) +{ + if (p % 2 == 0) + return (2 * p) * gammaT - 2 * gammaO; + else + return (2 * p) * gammaT - 2 * gammaO - PI; +} + +template +BSDL_INLINE_METHOD float +PhysicalHairLobe::TrimmedLogistic(float x, float s) +{ + constexpr auto fast_exp = BSDLConfig::Fast::expf; + + assert(x >= -PI); + assert(x <= PI); + const float t = std::min(fast_exp(PI / s), 1 / FLOAT_MIN); + const float y = std::max(fast_exp(-fabsf(x) / s), FLOAT_MIN); + return (t + 1) * y / ((t - 1) * s * SQR(1 + y)); +} + +template +BSDL_INLINE_METHOD float +PhysicalHairLobe::Np(float phi, int p, float s, float gammaO, + float gammaT) +{ + if (p == P_MAX) + return ONEOVERPI * 0.5f; + float dphi = phi - Phi(p, gammaO, gammaT); + if (dphi > PI) + dphi -= 2 * PI; + if (dphi < -PI) + dphi += 2 * PI; + return TrimmedLogistic(dphi, s); +} + +template +BSDL_INLINE_METHOD float +PhysicalHairLobe::SampleTrimmedLogistic(float u, float s) +{ + constexpr auto fast_exp = BSDLConfig::Fast::expf; + constexpr auto fast_log = BSDLConfig::Fast::logf; + + const float t = std::min(fast_exp(PI / s), 1 / FLOAT_MIN); + const float x = -s * fast_log((1 + t) / (u * (1 - t) + t) - 1); + assert(!std::isnan(x)); + return CLAMP(x, -PI, PI); +} + +template +BSDL_INLINE_METHOD float +PhysicalHairLobe::RemapLongitudinalRoughness(float lr) +{ + // Roughness parametrization from http://pbrt.org/hair.pdf + const float lr_2 = SQR(lr); + const float lr_4 = SQR(lr_2); + const float lr_20 = SQR(SQR(lr_4)) * lr_4; + return SQR(0.726f * lr + 0.812f * lr_2 + 3.7f * lr_20); +} + +template +BSDL_INLINE_METHOD float +PhysicalHairLobe::RemapAzimuthalRoughness(float ar) +{ + // Roughness parametrization from http://pbrt.org/hair.pdf + const float ar_2 = SQR(ar); + const float ar_4 = SQR(ar_2); + const float ar_22 = SQR(SQR(ar_4)) * ar_4 * ar_2; + return SQRT_PI_OVER_8 * (.265f * ar + 1.194f * ar_2 + 5.372f * ar_22); +} + +template +BSDL_INLINE_METHOD float +HairDiffuseLobe::ecc2longrough(float ecc, float aniso) +{ + // We use d'Eon's M() longitudinal function and we map it to ecc this + // way so as the azimuthal side shrinks, this shrinks too. We square to + // match the visual linearity that ecc2s offers. + return SQR(LERP(fabsf(ecc), 1.0f, sqrtf(MIN_ROUGH))) * (1 - aniso); +} + +// Map eccentricity to logistic variance s +template +BSDL_INLINE_METHOD float +HairDiffuseLobe::ecc2s(float ecc, float aniso) +{ + // This mapping keeps it linear close to 0 without going too fast to + // infinity. In practice it gives a linear resonse in appearance. + const float roughness = LERP(fabsf(ecc) * (1 - aniso), 1.0f, MIN_ROUGH); + return roughness / sqrtf(1 - roughness); +} + +// These two functions map eccentricity to common BSDF roughness and back so +// we let roughness boost kick in modifying eccentricity. +template +BSDL_INLINE_METHOD float +HairDiffuseLobe::ecc2roughness(float ecc) +{ + return LERP(std::min(1.0f, fabsf(ecc)), 1.0f, MIN_ROUGH); +} + +template +BSDL_INLINE_METHOD float +HairDiffuseLobe::roughness2ecc(float rough, float ecc) +{ + return copysign(LERP(LINEARSTEP(MIN_ROUGH, 1.0f, rough), 1.0f, 0.0f), ecc); +} + +template +template +BSDL_INLINE_METHOD +HairDiffuseLobe::HairDiffuseLobe(T* lobe, const BsdfGlobals& globals, + const Data& data) + : Base(lobe, data.T, globals.wo, + globals.regularize_roughness(ecc2roughness(data.eccentricity)), + globals.lambda_0, true) +{ + constexpr auto fast_exp = BSDLConfig::Fast::expf; + + Base::sample_filter = globals.get_sample_filter(Base::frame.Z, false); + const float R = Base::roughness(); + eccentricity = roughness2ecc(R, data.eccentricity); + // Fade out anisotropy with ecc so it also goes away with depth + anisotropy = CLAMP(data.anisotropy, 0.0f, 1.0f) * fabsf(eccentricity); + const float cos_o = CLAMP(data.T.dot(globals.wo), -1.0f, 1.0f); + // Divide by IOR to get the refracted ray sine + const float sin_t = sqrtf(1 - SQR(cos_o / std::max(data.IOR, 1.001f))); + // We tried the sine weighted average for 1 / sin_t = 0.5f * PI but using + // the actual value gives more color variation at grazing angles + const float flatten = CLAMP(data.flatten_density, 0.0f, 1.0f); + const float d = LERP(flatten, 1 / sin_t, 1.0f); + + Power abs = globals.wave(data.absorption); + color = Power([&](int i) { return fast_exp(-abs[i] * d); }, + globals.lambda_0); +} + +template +BSDL_INLINE_METHOD Sample +HairDiffuseLobe::eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const +{ + const float cos_o = CLAMP(wo.z, -1.0f, 1.0f); + const float sin_o = sqrtf(1 - SQR(cos_o)); + const float cos_i = CLAMP(wi.z, -1.0f, 1.0f); + const float sin_i = sqrtf(1 - SQR(cos_i)); + // Flip the lobe depending on eccentricity sign + const float flip = eccentricity >= 0 ? -1 : 1; + const float phi = fast_atan2(wi.y, wi.x * flip); + const float s = ecc2s(eccentricity, anisotropy); + const float v = PhysicalHairLobe::RemapLongitudinalRoughness( + ecc2longrough(eccentricity, anisotropy)); + const float D_theta = PhysicalHairLobe::Mp(sin_i, sin_o, cos_i, + cos_o, v); + const float D_phi = fabsf(eccentricity) > 0.001f + ? PhysicalHairLobe::TrimmedLogistic(phi, + s) + : 0.5f * ONEOVERPI; // Isotropic + return { wi, color, D_theta * D_phi, Base::roughness() }; +} + +template +BSDL_INLINE_METHOD Sample +HairDiffuseLobe::sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const +{ + constexpr auto fast_exp = BSDLConfig::Fast::expf; + constexpr auto fast_log = BSDLConfig::Fast::logf; + constexpr auto fast_cos = BSDLConfig::Fast::cosf; + + const float cos_o = CLAMP(wo.z, -1.0f, 1.0f); + const float sin_o = sqrtf(1 - SQR(cos_o)); + + // Sample M + const float v = PhysicalHairLobe::RemapLongitudinalRoughness( + ecc2longrough(eccentricity, anisotropy)); + const float x = MAX(rnd.x, 1e-5f); + float sin_theta_m = 1 + v * fast_log(x + (1 - x) * fast_exp(-2 / v)); + float cos_theta_m = sqrtf(MAX(1 - SQR(sin_theta_m), 0.0f)); + float cos_p = fast_cos(2 * PI * rnd.y); + float cos_theta = -sin_theta_m * cos_o + cos_theta_m * cos_p * sin_o; + float sin_theta = sqrtf(MAX(1 - SQR(cos_theta), 0.0f)); + + // Flip scattered phi depending on ecc sign + const float flip = eccentricity >= 0 ? -1 : 1; + const float s = ecc2s(eccentricity, anisotropy); + const float phi + = fabsf(eccentricity) > 0.001f + ? PhysicalHairLobe::SampleTrimmedLogistic(rnd.z, s) + : (rnd.z * 2 - 1) * PI; // Isotropic + float sin_phi, cos_phi; + fast_sincos(phi, &sin_phi, &cos_phi); + const Imath::V3f wi = { sin_theta * cos_phi * flip, sin_theta * sin_phi, + cos_theta }; + return eval_impl(wo, wi); +} + +template +BSDL_INLINE_METHOD float +HairDiffuseLobe::albedo2absorption(float x, float g) +{ + // We simulate scattering in a hair cube and run it for 11 eccentricities from + // -1 to 1 and 32 absorption values from 0 to 1 (non uniform, log^2), then + // record the results tabulated as eccentricity, resulting albedo, absorption + // which for gnuplot will be x, y, z in srf.txt. Then fit with this script: + // + // set xrange[-1:1] # eccentricity + // set yrange[0.05:1] # ignore dark albedos, we don't care for accuracy there + // f(x, y) = ((log(y) + g * (x - 1)**2 * (1 - (2*y - 1)**2)) / + // abs(a + b * x + c * x**2 + d * x**3 + e * x**4 + f * x**5 + 1))**2 + // a=b=c=d=e=f=g=1 + // fit f(x, y) "srf.txt" using 1:2:3:(1) via a, b, c, d, e, f, g + // set view 60, 60, 1, 2 + // splot "srf.txt", f(x, y) + + constexpr auto fast_log = BSDLConfig::Fast::logf; + + const float B + = 1 - SQR(2 * x - 1); // Useful shift to the log curve for fitting + // Avoid returning inf for 0.0 albedo + return MIN( + BIG, + SQR((fast_log(x) - 0.0621599f * SQR(g - 1) * B) + / (2.3141f + + g + * (0.740211f + + g + * (-0.252978f + + g + * (0.910376f + + g * (2.79624f + g * 1.89986f))))))); +} + +template +BSDL_INLINE_METHOD Power +HairDiffuseLobe::albedo2absorption(Power x, float lambda_0, float g) +{ + return Power([&](int i) { return albedo2absorption(x[i], g); }, lambda_0); +} + +// This BSDF comes from PhysicalHairLobe but isolating either R or TRT +template +template +BSDL_INLINE_METHOD +HairSpecularLobe::HairSpecularLobe(T* lobe, + const BsdfGlobals& globals, + const Data& data) + : Base(lobe, data.T, globals.wo, + globals.regularize_roughness(data.lroughness), globals.lambda_0, + true) + , trt(data.trt) +{ + constexpr auto fast_exp = BSDLConfig::Fast::expf; + constexpr auto fast_sin = BSDLConfig::Fast::sinf; + constexpr auto fast_asin = BSDLConfig::Fast::asinf; + + Base::sample_filter = globals.get_sample_filter(Base::frame.Z, false); + const float input_ior = CLAMP(data.IOR, 1.001f, BIG); + const float eta = LERP(CLAMP(data.force_eta, 0.0f, 1.0f), + globals.relative_eta(input_ior), input_ior); + const float lrough = Base::roughness(); + const float arough = CLAMP(data.aroughness, 0.01f, 1.0f); + + // Compute hair coordinate system terms related to _wo_ + const float sinThetaO = CLAMP(globals.wo.dot(Base::frame.Z), -1.0f, 1.0f); + const float cosThetaO = sqrtf(1 - SQR(sinThetaO)); + + long_v = (trt ? 4 : 1) + * PhysicalHairLobe::RemapLongitudinalRoughness(lrough); + + // Azimuthal roughness + azim_s = PhysicalHairLobe::RemapAzimuthalRoughness(arough); + + // Compute alpha terms for hair scales + float sin2kAlpha_tmp[3], cos2kAlpha_tmp[3]; + sin2kAlpha_tmp[0] = fast_sin(data.offset); + cos2kAlpha_tmp[0] = sqrtf(1 - SQR(sin2kAlpha_tmp[0])); + for (int i = 1; i < 3; ++i) { + sin2kAlpha_tmp[i] = 2 * cos2kAlpha_tmp[i - 1] * sin2kAlpha_tmp[i - 1]; + cos2kAlpha_tmp[i] = SQR(cos2kAlpha_tmp[i - 1]) + - SQR(sin2kAlpha_tmp[i - 1]); + } + sin2kAlpha = sin2kAlpha_tmp[trt ? 2 : 1]; + cos2kAlpha = cos2kAlpha_tmp[trt ? 2 : 1]; + + gammaO = fast_asin(data.h); + + // Compute $\cos \thetat$ for refracted ray + float sinThetaT = sinThetaO / eta; + + // Compute $\gammat$ for refracted ray + float etap = sqrtf(SQR(eta) - SQR(sinThetaO)) / cosThetaO; + float sinGammaT = data.h / etap; + float cosGammaT = sqrtf(1 - SQR(sinGammaT)); + gammaT = fast_asin(sinGammaT); + + // Compute $p=0$ attenuation at initial cylinder intersection + const float cosGammaO = sqrtf(MAX(1 - SQR(data.h), 0.0f)); + const float cosTheta = cosThetaO * cosGammaO; + const float f = fresnel_dielectric(cosTheta, eta); + if (trt) { + float cosThetaT = sqrtf(MAX(1 - SQR(sinThetaT), 0.0f)); + // Compute the transmittance _T_ of a single path through the cylinder + const float flatten = CLAMP(data.flatten_density, 0.0f, 1.0f); + float l = LERP(flatten, 2 * cosGammaT / cosThetaT, 1.0f); + + Power absorption = globals.wave(data.absorption); + Power tau([&](int i) { return fast_exp(-absorption[i] * l); }, + globals.lambda_0); + + // First fresnel atten comes from layering if R is present. This makes + // putting R on top of TRT sensible, otherwise this would be SQR(1 - f) * f + color = (1 - f) * f * SQR(tau) * globals.wave(data.tint); + fresnel_term = (1 - f) * f; + } else { + color = f * globals.wave(data.tint); + fresnel_term = f; + } +} + +template +BSDL_INLINE_METHOD Sample +HairSpecularLobe::eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const +{ + constexpr auto fast_atan2 = BSDLConfig::Fast::atan2f; + + const float sinThetaI = CLAMP(wi.z, -1.0f, 1.0f); + const float cosThetaI = sqrtf(1 - SQR(sinThetaI)); + const float sinThetaO = CLAMP(wo.z, -1.0f, 1.0f); + const float cosThetaO = sqrtf(1 - SQR(sinThetaO)); + const float phiI = fast_atan2(wi.y, wi.x); + + const float phiO = 0; + const float phi = phiI - phiO; + + Power weight = color; + // Compute sin/cos theta to account for scales + float sinThetaOp, cosThetaOp; + if (trt) { + sinThetaOp = sinThetaO * cos2kAlpha + cosThetaO * sin2kAlpha; + cosThetaOp = cosThetaO * cos2kAlpha - sinThetaO * sin2kAlpha; + } else { + sinThetaOp = sinThetaO * cos2kAlpha - cosThetaO * sin2kAlpha; + cosThetaOp = cosThetaO * cos2kAlpha + sinThetaO * sin2kAlpha; + } + // handle out of range from scale adjustment + cosThetaOp = fabsf(cosThetaOp); + int p = trt ? 2 : 0; + + const float pdf + = PhysicalHairLobe::Mp(cosThetaI, cosThetaOp, sinThetaI, + sinThetaOp, long_v) + * PhysicalHairLobe::Np(phi, p, azim_s, gammaO, gammaT); + + return { wi, weight, pdf, Base::roughness() }; +} + +template +BSDL_INLINE_METHOD Sample +HairSpecularLobe::sample_impl(const Imath::V3f& wo, + const Imath::V3f& _rnd) const +{ + constexpr auto fast_log = BSDLConfig::Fast::logf; + constexpr auto fast_sincos = BSDLConfig::Fast::sincosf; + + const float sinThetaO = CLAMP(wo.z, -1.0f, 1.0f); + const float cosThetaO = sqrtf(1 - SQR(sinThetaO)); + Imath::V3f rnd = _rnd; + // Update sin/cos thetao to account for scales + float sinThetaOp, cosThetaOp; + if (trt) { + sinThetaOp = sinThetaO * cos2kAlpha + cosThetaO * sin2kAlpha; + cosThetaOp = cosThetaO * cos2kAlpha - sinThetaO * sin2kAlpha; + } else { + sinThetaOp = sinThetaO * cos2kAlpha - cosThetaO * sin2kAlpha; + cosThetaOp = cosThetaO * cos2kAlpha + sinThetaO * sin2kAlpha; + } + + // Sample $M_p$ to compute $\thetai$ + rnd.x = std::max(rnd.x, 1e-5f); + float cosTheta + = 1 + long_v * fast_log(rnd.x + (1 - rnd.x) * fast_exp(-2 / long_v)); + float sinTheta = sqrtf(MAX(1 - SQR(cosTheta), 0.0f)); + float cosPhi = fast_cos(2 * PI * rnd.y); + float sinThetaI = -cosTheta * sinThetaOp + sinTheta * cosPhi * cosThetaOp; + float cosThetaI = sqrtf(MAX(1 - SQR(sinThetaI), 0.0f)); + int p = trt ? 2 : 0; + + // Sample N_p to compute delta_phi + float dphi = PhysicalHairLobe::Phi(p, gammaO, gammaT) + + PhysicalHairLobe::SampleTrimmedLogistic(rnd.z, + azim_s); + // Compute wi from sampled hair scattering angles + const float phiO = 0; + float phiI = phiO + dphi; + float cos_phi_i, sin_phi_i; + fast_sincos(phiI, &sin_phi_i, &cos_phi_i); + + Imath::V3f wi = { cos_phi_i * cosThetaI, sin_phi_i * cosThetaI, sinThetaI }; + return eval_impl(wo, wi); +} + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_decl.h b/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_decl.h new file mode 100644 index 000000000..cbe6a2e2f --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_decl.h @@ -0,0 +1,125 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +struct SheenLTC { + // describe how tabulation should be done + static constexpr int Nc = 16; + static constexpr int Nr = 16; + static constexpr int Nf = 1; + static constexpr int ltcRes = 32; + + explicit BSDL_INLINE_METHOD SheenLTC(float rough) + : roughness(CLAMP(rough, 0.0f, 1.0f)) + { + } + // This constructor is just for baking albedo tables + explicit SheenLTC(float, float rough, float) : roughness(rough) {} + + static constexpr const char* name() { return "sheen_ltc"; } + + static constexpr float get_cosine(int i) + { + return std::max(float(i) * (1.0f / (Nc - 1)), 1e-6f); + } + + BSDL_INLINE_METHOD Sample eval(Imath::V3f wo, Imath::V3f wi) const; + BSDL_INLINE_METHOD Sample sample(Imath::V3f wo, float randu, float randv, + float randw) const; + + struct Energy { + float data[Nf * Nr * Nc]; + }; + struct Param { + Imath::V3f data[32][32]; + }; + + static BSDL_INLINE_METHOD Energy& get_energy(); + + typedef const Imath::V3f (*V32_array)[32]; + static BSDL_INLINE_METHOD V32_array param_ptr(); + + static const char* lut_header() { return "bsdf_sheenltc_luts.h"; } + static const char* struct_name() { return "SheenLTC"; } + + BSDL_INLINE_METHOD float calculate_phi(const Imath::V3f& v) const; + BSDL_INLINE_METHOD bool same_hemisphere(const Imath::V3f& wo, + const Imath::V3f& wi) const; + + BSDL_INLINE_METHOD float get_roughness() const { return roughness; } + + BSDL_INLINE_METHOD void albedo_range(float& min_albedo, + float& max_albedo) const; + BSDL_INLINE_METHOD void compute_scale(float& scale) const; + + // Fetch the LTC coefficients by bilinearly interpolating entries in a 32x32 + // lookup table. + BSDL_INLINE_METHOD Imath::V3f fetchCoeffs(const Imath::V3f& wo) const; + // Evaluate the LTC distribution in its local coordinate system. + BSDL_INLINE_METHOD float evalLTC(const Imath::V3f& wi, + const Imath::V3f& ltcCoeffs) const; + // Sample from the LTC distribution in its local coordinate system. + BSDL_INLINE_METHOD Imath::V3f sampleLTC(const Imath::V3f& ltcCoeffs, + float randu, float randv) const; + +private: + float roughness; +}; + +template struct SheenLTCLobe : public Lobe { + using Base = Lobe; + struct Data : public LayeredData { + Imath::V3f N; + Imath::C3f tint; + float roughness; + int doublesided; + using lobe_type = SheenLTCLobe; + }; + template static typename LobeRegistry::Entry entry() + { + static_assert( + std::is_base_of::value); // Make no other assumptions + using R = LobeRegistry; + return { name(), + { R::param(&D::closure), R::param(&D::N), R::param(&D::tint), + R::param(&D::roughness), + R::param(&D::doublesided, "doublesided"), R::close() } }; + } + + template + BSDL_INLINE_METHOD SheenLTCLobe(T*, const BsdfGlobals& globals, + const Data& data); + + static const char* name() { return "sheen_ltc"; } + + BSDL_INLINE_METHOD Power albedo_impl() const { return Power(1 - Eo, 1); } + BSDL_INLINE_METHOD Power filter_o(const Imath::V3f& wo) const + { + return Power(Eo, 1); + } + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f& sample) const; + +private: + SheenLTC sheenLTC; + Power tint; + float Eo; + bool back; +}; + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_impl.h b/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_impl.h new file mode 100644 index 000000000..0f3ec5ab7 --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_impl.h @@ -0,0 +1,245 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include +#ifndef BAKE_BSDL_TABLES +# include +#endif + +BSDL_ENTER_NAMESPACE + +namespace spi { + +BSDL_INLINE_METHOD Sample +SheenLTC::eval(Imath::V3f wo, Imath::V3f wi) const +{ + assert(wo.z >= 0); + assert(wi.z >= 0); + + // Rotate coordinate frame to align with incident direction wo. + float phiStd = calculate_phi(wo); + Imath::V3f wiStd = rotate(wi, { 0, 0, 1 }, -phiStd); + + // Evaluate the LTC distribution in aligned coordinates. + Imath::V3f ltcCoeffs = fetchCoeffs(wo); + float pdf = evalLTC(wiStd, ltcCoeffs); + float R = 1.333814f * ltcCoeffs.z; // reflectance + Power col = Power(R, 1); + + return { wi, col, pdf, roughness }; +} + +BSDL_INLINE_METHOD Sample +SheenLTC::sample(Imath::V3f wo, float randu, float randv, float randw) const +{ + // Sample from the LTC distribution in aligned coordinates. + Imath::V3f wiStd = sampleLTC(fetchCoeffs(wo), randu, randv); + + // Rotate coordinate frame based on incident direction wo. + float phiStd = calculate_phi(wo); + Imath::V3f wi = rotate(wiStd, { 0, 0, 1 }, +phiStd); + + if (!same_hemisphere(wo, wi)) + return {}; + + return eval(wo, wi); +} + +BSDL_INLINE_METHOD float +SheenLTC::calculate_phi(const Imath::V3f& v) const +{ + float p = BSDLConfig::Fast::atan2f(v.y, v.x); + if (p < 0) { + p += 2 * PI; + } + return p; +} + +BSDL_INLINE_METHOD bool +SheenLTC::same_hemisphere(const Imath::V3f& wo, const Imath::V3f& wi) const +{ + return wo.z * wi.z > 0; +} + +BSDL_INLINE_METHOD void +SheenLTC::albedo_range(float& min_albedo, float& max_albedo) const +{ + const V32_array param = param_ptr(); + min_albedo = max_albedo = 0; + for (int i = 0; i < ltcRes; ++i) + for (int j = 0; j < ltcRes; ++j) { + Imath::V3f v = param[i][j]; + float R = v.z; // reflectance + if (i == 0 && j == 0) { + min_albedo = max_albedo = R; + } else { + min_albedo = std::min(R, min_albedo); + max_albedo = std::max(R, max_albedo); + } + } +} + +BSDL_INLINE_METHOD void +SheenLTC::compute_scale(float& scale) const +{ + float min_albedo = 0, max_albedo = 0; + albedo_range(min_albedo, max_albedo); + + if (max_albedo == 0) { + scale = 0; + } else { + scale = 1.0f / max_albedo; + } +} + +// The following functions, and the data arrays ltcParamTableVolume and ltcParamTableApprox, +// are translated from the code repository https://github.com/tizian/ltc-sheen, and the +// paper "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", by Tizian +// Zeltner, Brent Burley, and Matt Jen-Yuan Chiang. + +// Fetch the LTC coefficients by bilinearly interpolating entries in a 32x32 +// lookup table. */ +BSDL_INLINE_METHOD Imath::V3f +SheenLTC::fetchCoeffs(const Imath::V3f& wo) const +{ + // Compute table indices and interpolation factors. + float row = CLAMP(roughness, 0.0f, ALMOSTONE) * (ltcRes - 1); + float col = CLAMP(wo.z, 0.0f, ALMOSTONE) * (ltcRes - 1); + float r = std::floor(row); + float c = std::floor(col); + float rf = row - r; + float cf = col - c; + int ri = (int)r; + int ci = (int)c; + + const V32_array param = param_ptr(); + // Bilinear interpolation + Imath::V3f coeffs; + const Imath::V3f v1 = param[ri][ci]; + const Imath::V3f v2 = param[ri][ci + 1]; + const Imath::V3f v3 = param[ri + 1][ci]; + const Imath::V3f v4 = param[ri + 1][ci + 1]; + coeffs = LERP(rf, LERP(cf, v1, v2), LERP(cf, v3, v4)); + return coeffs; +} + +// Evaluate the LTC distribution in its local coordinate system. +BSDL_INLINE_METHOD float +SheenLTC::evalLTC(const Imath::V3f& wi, const Imath::V3f& ltcCoeffs) const +{ + // The (inverse) transform matrix `M^{-1}` is given by: + // [[aInv 0 bInv] + // M^{-1} = [0 aInv 0 ] + // [0 0 1 ]] + // with `aInv = ltcCoeffs[0]`, `bInv = ltcCoeffs[1]` fetched from the + // table. The transformed direction `wiOriginal` is therefore: + // [[aInv * wi.x + bInv * wi.z] + // wiOriginal = M^{-1} * wi = [aInv * wi.y ] + // [wi.z ]] + // which is subsequently normalized. The determinant of the matrix is + // |M^{-1}| = aInv * aInv + // which is used to compute the Jacobian determinant of the complete + // mapping including the normalization. + // See the original paper [Heitz et al. 2016] for details about the LTC + // itself. + float aInv = ltcCoeffs.x, bInv = ltcCoeffs.y; + Imath::V3f wiOriginal = { aInv * wi.x + bInv * wi.z, aInv * wi.y, wi.z }; + const float length = wiOriginal.length(); + wiOriginal *= 1.0f / length; + float det = aInv * aInv; + float jacobian = det / (length * length * length); + + return wiOriginal.z * ONEOVERPI * jacobian; +} + +// Sample from the LTC distribution in its local coordinate system. +BSDL_INLINE_METHOD Imath::V3f +SheenLTC::sampleLTC(const Imath::V3f& ltcCoeffs, float randu, float randv) const +{ + // The (inverse) transform matrix `M^{-1}` is given by: + // [[aInv 0 bInv] + // M^{-1} = [0 aInv 0 ] + // [0 0 1 ]] + // with `aInv = ltcCoeffs[0]`, `bInv = ltcCoeffs[1]` fetched from the + // table. The non-inverted matrix `M` is therefore: + // [[1/aInv 0 -bInv/aInv] + // M = [0 1/aInv 0 ] + // [0 0 1 ]] + // and the transformed direction wi is: + // [[wiOriginal.x/aInv - wiOriginal.z*bInv/aInv] + // wi = M * wiOriginal = [wiOriginal.y/aInv ] + // [wiOriginal.z ]] + // which is subsequently normalized. + // See the original paper [Heitz et al. 2016] for details about the LTC + // itself. + Imath::V3f wiOriginal = sample_uniform_hemisphere(randu, randv); + + float aInv = ltcCoeffs.x, bInv = ltcCoeffs.y; + Imath::V3f wi = { wiOriginal.x / aInv - wiOriginal.z * bInv / aInv, + wiOriginal.y / aInv, wiOriginal.z }; + return wi.normalized(); +} + +template +template +BSDL_INLINE_METHOD +SheenLTCLobe::SheenLTCLobe(T* lobe, const BsdfGlobals& globals, + const Data& data) + : Base(lobe, globals.visible_normal(data.N), data.roughness, + globals.lambda_0, false) + , sheenLTC(CLAMP(globals.regularize_roughness(data.roughness), 0.0f, 1.0f)) + , tint(globals.wave(data.tint)) + , back(data.doublesided ? false : globals.backfacing) +{ + Base::sample_filter = globals.get_sample_filter(Base::frame.Z, true); + TabulatedEnergyCurve curve(sheenLTC.get_roughness(), 0); + // Get energy compensation taking tint into account + Eo = back ? 1 + : 1 + - std::min( + (1 - curve.Emiss_eval(Base::frame.Z.dot(globals.wo))) + * tint.max(), + 1.0f); +} + +template +BSDL_INLINE_METHOD Sample +SheenLTCLobe::eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const +{ + const float cosNO = wo.z; + const float cosNI = wi.z; + const bool isrefl = cosNI > 0 && cosNO >= 0; + const bool doself = isrefl && !back; + + Sample s = {}; + if (doself) { + s = sheenLTC.eval(wo, wi); // Return a grayscale sheen. + s.weight *= tint; + } + return s; +} + +template +BSDL_INLINE_METHOD Sample +SheenLTCLobe::sample_impl(const Imath::V3f& wo, + const Imath::V3f& sample) const +{ + const bool doself = !back; + + if (!doself) + return {}; + + Sample s = sheenLTC.sample(wo, sample.x, sample.y, sample.z); + s.weight *= tint; + return s; +} + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_param.h b/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_param.h new file mode 100644 index 000000000..4f09596ff --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_param.h @@ -0,0 +1,282 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +BSDL_INLINE_METHOD SheenLTC::V32_array +SheenLTC::param_ptr() +{ + static Param param = { { + // clang-format off + {{ 0.01415, 0.00060, 0.00001 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }}, + {{ 0.01941, -0.00232, 0.05839 }, { 0.01741, -0.00581, 0.00071 }, { 0.04610, -0.00769, 0.00007 }, { 0.10367, -0.00740, 0.00002 }, + { 0.06244, -0.02445, 0.00000 }, { 0.23927, -0.00242, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }}, + {{ 0.01927, -0.01424, 0.38834 }, { 0.01895, -0.00218, 0.09768 }, { 0.03002, -0.00194, 0.01072 }, { 0.03912, -0.00384, 0.00150 }, + { 0.04938, -0.00668, 0.00039 }, { 0.05239, -0.01107, 0.00012 }, { 0.06018, -0.00746, 0.00006 }, { 0.06520, -0.01591, 0.00003 }, + { 0.08253, -0.01052, 0.00002 }, { 0.21093, -0.01495, 0.00002 }, { 0.12785, -0.01530, 0.00001 }, { 0.19030, -0.01428, 0.00001 }, + { 0.15254, -0.01276, 0.00000 }, { 0.16585, -0.02071, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }}, + {{ 0.03084, -0.04909, 0.55348 }, { 0.03764, -0.00710, 0.29827 }, { 0.03952, -0.00236, 0.11755 }, { 0.04092, -0.00201, 0.03677 }, + { 0.04433, -0.00298, 0.00983 }, { 0.05014, -0.00546, 0.00288 }, { 0.05570, -0.00834, 0.00101 }, { 0.06215, -0.01121, 0.00046 }, + { 0.06660, -0.01294, 0.00023 }, { 0.07902, -0.01692, 0.00014 }, { 0.10099, -0.01639, 0.00010 }, { 0.10794, -0.01738, 0.00006 }, + { 0.10632, -0.02032, 0.00004 }, { 0.12623, -0.01947, 0.00003 }, { 0.13931, -0.02354, 0.00002 }, { 0.15353, -0.02910, 0.00002 }, + { 0.16109, -0.02565, 0.00001 }, { 0.14583, -0.02903, 0.00001 }, { 0.27891, -0.03066, 0.00001 }, { 0.22622, -0.03044, 0.00001 }, + { 0.18932, -0.04045, 0.00001 }, { 0.20219, -0.03226, 0.00000 }, { 0.30269, -0.03443, 0.00000 }, { 0.38379, -0.03023, 0.00000 }, + { 0.39038, -0.03610, 0.00000 }, { 0.46310, -0.02022, 0.00000 }, { 0.44663, -0.02590, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, + { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }, { 0.00000, 0.00000, 0.00000 }}, + {{ 0.04118, -0.10668, 0.63273 }, { 0.05152, -0.02772, 0.42999 }, { 0.05724, -0.00717, 0.24931 }, { 0.05863, -0.00421, 0.13040 }, + { 0.05952, -0.00351, 0.05910 }, { 0.06149, -0.00399, 0.02430 }, { 0.06448, -0.00522, 0.00981 }, { 0.07004, -0.00676, 0.00433 }, + { 0.07774, -0.00866, 0.00200 }, { 0.08632, -0.01099, 0.00105 }, { 0.09629, -0.01332, 0.00062 }, { 0.10592, -0.01590, 0.00039 }, + { 0.10718, -0.01724, 0.00024 }, { 0.12207, -0.02041, 0.00018 }, { 0.13413, -0.02237, 0.00013 }, { 0.13702, -0.02503, 0.00009 }, + { 0.15294, -0.02664, 0.00008 }, { 0.15121, -0.02803, 0.00006 }, { 0.17652, -0.03188, 0.00005 }, { 0.19532, -0.03147, 0.00004 }, + { 0.20831, -0.03346, 0.00003 }, { 0.19762, -0.03476, 0.00002 }, { 0.24202, -0.03464, 0.00002 }, { 0.32995, -0.03125, 0.00002 }, + { 0.30857, -0.03303, 0.00002 }, { 0.39596, -0.03009, 0.00002 }, { 0.38346, -0.03198, 0.00001 }, { 0.42503, -0.02518, 0.00001 }, + { 0.41592, -0.03195, 0.00001 }, { 0.42512, -0.01668, 0.00001 }, { 0.36714, -0.02978, 0.00001 }, { 0.46502, -0.00394, 0.00001 }}, + {{ 0.05088, -0.16006, 0.67021 }, { 0.06485, -0.05552, 0.50797 }, { 0.07697, -0.01274, 0.34475 }, { 0.08334, -0.00932, 0.22264 }, + { 0.08654, -0.00582, 0.13300 }, { 0.08950, -0.00507, 0.07375 }, { 0.09263, -0.00531, 0.03867 }, { 0.09711, -0.00616, 0.02006 }, + { 0.10113, -0.00792, 0.01032 }, { 0.10913, -0.00949, 0.00570 }, { 0.11586, -0.01167, 0.00324 }, { 0.12425, -0.01421, 0.00197 }, + { 0.12986, -0.01635, 0.00122 }, { 0.13530, -0.01858, 0.00080 }, { 0.15037, -0.02237, 0.00059 }, { 0.15165, -0.02450, 0.00040 }, + { 0.15858, -0.02714, 0.00030 }, { 0.16660, -0.03145, 0.00022 }, { 0.18051, -0.03321, 0.00018 }, { 0.18022, -0.03295, 0.00013 }, + { 0.19896, -0.03492, 0.00012 }, { 0.21095, -0.03365, 0.00009 }, { 0.21862, -0.03733, 0.00008 }, { 0.23861, -0.03930, 0.00007 }, + { 0.25384, -0.03879, 0.00006 }, { 0.27394, -0.03580, 0.00005 }, { 0.28563, -0.04089, 0.00004 }, { 0.29160, -0.03604, 0.00003 }, + { 0.29300, -0.03863, 0.00003 }, { 0.33458, -0.03575, 0.00002 }, { 0.36514, -0.02621, 0.00002 }, { 0.38746, 0.00124, 0.00002 }}, + {{ 0.06054, -0.18133, 0.68765 }, { 0.07710, -0.08871, 0.55387 }, { 0.09569, -0.02800, 0.41042 }, { 0.10917, -0.01456, 0.29613 }, + { 0.11813, -0.01052, 0.20481 }, { 0.12469, -0.00904, 0.13463 }, { 0.12958, -0.00864, 0.08432 }, { 0.13406, -0.00898, 0.05135 }, + { 0.13801, -0.01019, 0.03073 }, { 0.14324, -0.01110, 0.01861 }, { 0.14801, -0.01354, 0.01132 }, { 0.15359, -0.01614, 0.00706 }, + { 0.15945, -0.01922, 0.00454 }, { 0.16688, -0.02116, 0.00304 }, { 0.17552, -0.02363, 0.00212 }, { 0.17956, -0.02606, 0.00145 }, + { 0.18275, -0.02887, 0.00102 }, { 0.19293, -0.03270, 0.00078 }, { 0.20081, -0.03492, 0.00059 }, { 0.20817, -0.03607, 0.00046 }, + { 0.21658, -0.03714, 0.00036 }, { 0.22866, -0.03827, 0.00030 }, { 0.23912, -0.03832, 0.00025 }, { 0.24736, -0.03918, 0.00020 }, + { 0.26573, -0.04071, 0.00017 }, { 0.26821, -0.04382, 0.00013 }, { 0.28767, -0.03860, 0.00012 }, { 0.30592, -0.03690, 0.00010 }, + { 0.31228, -0.04032, 0.00008 }, { 0.35297, -0.03136, 0.00008 }, { 0.35570, -0.02365, 0.00006 }, { 0.37077, -0.00281, 0.00006 } }, + {{ 0.07075, -0.18042, 0.69478 }, { 0.08974, -0.10806, 0.58263 }, { 0.11306, -0.04959, 0.45689 }, { 0.13241, -0.02418, 0.35318 }, + { 0.14704, -0.01632, 0.26617 }, { 0.15763, -0.01333, 0.19342 }, { 0.16591, -0.01204, 0.13650 }, { 0.17258, -0.01177, 0.09391 }, + { 0.17781, -0.01198, 0.06299 }, { 0.18234, -0.01284, 0.04183 }, { 0.18700, -0.01408, 0.02782 }, { 0.19101, -0.01571, 0.01845 }, + { 0.19657, -0.01794, 0.01259 }, { 0.20165, -0.02041, 0.00864 }, { 0.20731, -0.02235, 0.00604 }, { 0.21150, -0.02516, 0.00421 }, + { 0.21692, -0.02822, 0.00303 }, { 0.22536, -0.03168, 0.00228 }, { 0.23235, -0.03631, 0.00172 }, { 0.23720, -0.03840, 0.00129 }, + { 0.24295, -0.04024, 0.00099 }, { 0.25154, -0.04645, 0.00079 }, { 0.26200, -0.04435, 0.00066 }, { 0.26907, -0.04644, 0.00052 }, + { 0.28040, -0.04369, 0.00044 }, { 0.28922, -0.05007, 0.00034 }, { 0.30452, -0.04809, 0.00029 }, { 0.31567, -0.04719, 0.00024 }, + { 0.33294, -0.04179, 0.00021 }, { 0.35084, -0.03537, 0.00019 }, { 0.37226, -0.02633, 0.00017 }, { 0.37956, 0.00196, 0.00013 } }, + {{ 0.08222, -0.17531, 0.69794 }, { 0.10394, -0.13135, 0.60100 }, { 0.13034, -0.08092, 0.49085 }, { 0.15361, -0.04065, 0.39802 }, + { 0.17244, -0.02431, 0.31773 }, { 0.18620, -0.01864, 0.24683 }, { 0.19671, -0.01620, 0.18787 }, { 0.20509, -0.01498, 0.13964 }, + { 0.21199, -0.01460, 0.10220 }, { 0.21726, -0.01487, 0.07319 }, { 0.22245, -0.01549, 0.05252 }, { 0.22702, -0.01665, 0.03747 }, + { 0.23174, -0.01802, 0.02685 }, { 0.23571, -0.01985, 0.01915 }, { 0.23966, -0.02185, 0.01375 }, { 0.24384, -0.02424, 0.00996 }, + { 0.24877, -0.02636, 0.00733 }, { 0.25548, -0.02871, 0.00552 }, { 0.26047, -0.03133, 0.00415 }, { 0.26863, -0.03455, 0.00322 }, + { 0.27404, -0.03705, 0.00247 }, { 0.28088, -0.03931, 0.00194 }, { 0.29010, -0.04546, 0.00157 }, { 0.29704, -0.04797, 0.00126 }, + { 0.30559, -0.04990, 0.00102 }, { 0.31519, -0.04903, 0.00082 }, { 0.32721, -0.04842, 0.00069 }, { 0.33828, -0.04495, 0.00059 }, + { 0.35424, -0.04310, 0.00048 }, { 0.36868, -0.03925, 0.00042 }, { 0.38570, -0.02709, 0.00037 }, { 0.39707, 0.00103, 0.00030 } }, + {{ 0.09597, -0.17397, 0.69862 }, { 0.11972, -0.15003, 0.61474 }, { 0.14796, -0.10394, 0.51662 }, { 0.17350, -0.06278, 0.43321 }, + { 0.19481, -0.03698, 0.35989 }, { 0.21136, -0.02667, 0.29352 }, { 0.22369, -0.02186, 0.23483 }, { 0.23330, -0.01962, 0.18491 }, + { 0.24109, -0.01854, 0.14298 }, { 0.24787, -0.01810, 0.10960 }, { 0.25359, -0.01829, 0.08309 }, { 0.25878, -0.01882, 0.06268 }, + { 0.26320, -0.01990, 0.04689 }, { 0.26831, -0.02103, 0.03539 }, { 0.27305, -0.02243, 0.02666 }, { 0.27810, -0.02425, 0.02015 }, + { 0.28206, -0.02611, 0.01519 }, { 0.28734, -0.02809, 0.01160 }, { 0.29228, -0.03045, 0.00888 }, { 0.29719, -0.03211, 0.00683 }, + { 0.30256, -0.03413, 0.00531 }, { 0.30857, -0.03666, 0.00417 }, { 0.31652, -0.03882, 0.00334 }, { 0.32438, -0.04150, 0.00270 }, + { 0.33291, -0.04323, 0.00220 }, { 0.34012, -0.04376, 0.00178 }, { 0.35087, -0.04590, 0.00148 }, { 0.36243, -0.04591, 0.00123 }, + { 0.37467, -0.04202, 0.00101 }, { 0.38986, -0.03859, 0.00087 }, { 0.40394, -0.03046, 0.00073 }, { 0.41719, 0.00025, 0.00061 } }, + {{ 0.11173, -0.17686, 0.69840 }, { 0.13694, -0.16229, 0.62456 }, { 0.16797, -0.12071, 0.53780 }, { 0.19358, -0.08778, 0.46227 }, + { 0.21571, -0.05746, 0.39486 }, { 0.23427, -0.03882, 0.33234 }, { 0.24900, -0.03044, 0.27724 }, { 0.26011, -0.02620, 0.22722 }, + { 0.26884, -0.02412, 0.18345 }, { 0.27668, -0.02290, 0.14737 }, { 0.28326, -0.02247, 0.11687 }, { 0.28928, -0.02235, 0.09223 }, + { 0.29445, -0.02283, 0.07219 }, { 0.29932, -0.02349, 0.05639 }, { 0.30454, -0.02444, 0.04424 }, { 0.30943, -0.02562, 0.03460 }, + { 0.31431, -0.02725, 0.02709 }, { 0.31861, -0.02859, 0.02113 }, { 0.32326, -0.03047, 0.01656 }, { 0.32881, -0.03199, 0.01309 }, + { 0.33479, -0.03417, 0.01041 }, { 0.34094, -0.03618, 0.00831 }, { 0.34705, -0.03771, 0.00665 }, { 0.35341, -0.03920, 0.00534 }, + { 0.36079, -0.04070, 0.00434 }, { 0.36863, -0.04138, 0.00355 }, { 0.37512, -0.04066, 0.00288 }, { 0.38607, -0.04125, 0.00241 }, + { 0.39611, -0.03916, 0.00200 }, { 0.41085, -0.03764, 0.00170 }, { 0.42341, -0.02956, 0.00142 }, { 0.43959, 0.00020, 0.00123 } }, + {{ 0.12869, -0.17952, 0.69766 }, { 0.15653, -0.16921, 0.63215 }, { 0.18847, -0.13634, 0.55391 }, { 0.21397, -0.10854, 0.48615 }, + { 0.23632, -0.08164, 0.42355 }, { 0.25652, -0.05663, 0.36707 }, { 0.27273, -0.04256, 0.31348 }, { 0.28564, -0.03522, 0.26549 }, + { 0.29568, -0.03124, 0.22185 }, { 0.30413, -0.02895, 0.18403 }, { 0.31142, -0.02760, 0.15141 }, { 0.31801, -0.02688, 0.12380 }, + { 0.32424, -0.02653, 0.10104 }, { 0.32941, -0.02669, 0.08149 }, { 0.33479, -0.02714, 0.06598 }, { 0.33933, -0.02784, 0.05291 }, + { 0.34444, -0.02878, 0.04275 }, { 0.34892, -0.02988, 0.03428 }, { 0.35415, -0.03136, 0.02771 }, { 0.35873, -0.03261, 0.02222 }, + { 0.36457, -0.03429, 0.01807 }, { 0.36975, -0.03567, 0.01456 }, { 0.37684, -0.03742, 0.01195 }, { 0.38258, -0.03792, 0.00969 }, + { 0.39038, -0.03954, 0.00798 }, { 0.39755, -0.04002, 0.00653 }, { 0.40428, -0.04014, 0.00534 }, { 0.41192, -0.03889, 0.00441 }, + { 0.42141, -0.03739, 0.00368 }, { 0.43074, -0.03386, 0.00307 }, { 0.44659, -0.02810, 0.00264 }, { 0.46013, 0.00013, 0.00224 } }, + {{ 0.14693, -0.17953, 0.69641 }, { 0.17828, -0.17294, 0.63746 }, { 0.20991, -0.14837, 0.56861 }, { 0.23513, -0.12458, 0.50592 }, + { 0.25809, -0.10127, 0.44888 }, { 0.27850, -0.07912, 0.39646 }, { 0.29576, -0.05884, 0.34628 }, { 0.30999, -0.04715, 0.30001 }, + { 0.32128, -0.04041, 0.25727 }, { 0.33053, -0.03637, 0.21872 }, { 0.33854, -0.03401, 0.18530 }, { 0.34549, -0.03244, 0.15569 }, + { 0.35212, -0.03165, 0.13073 }, { 0.35806, -0.03113, 0.10901 }, { 0.36371, -0.03104, 0.09059 }, { 0.36901, -0.03134, 0.07509 }, + { 0.37416, -0.03173, 0.06205 }, { 0.37907, -0.03225, 0.05112 }, { 0.38378, -0.03304, 0.04199 }, { 0.38887, -0.03406, 0.03458 }, + { 0.39366, -0.03515, 0.02839 }, { 0.39953, -0.03594, 0.02356 }, { 0.40534, -0.03728, 0.01946 }, { 0.41134, -0.03825, 0.01604 }, + { 0.41832, -0.03910, 0.01337 }, { 0.42583, -0.03960, 0.01115 }, { 0.43323, -0.03950, 0.00925 }, { 0.44084, -0.03877, 0.00766 }, + { 0.44897, -0.03647, 0.00639 }, { 0.45832, -0.03283, 0.00536 }, { 0.47095, -0.02659, 0.00456 }, { 0.48340, -0.00002, 0.00390 } }, + {{ 0.16755, -0.17802, 0.69548 }, { 0.20139, -0.17499, 0.64297 }, { 0.23191, -0.15708, 0.57917 }, { 0.25781, -0.13619, 0.52314 }, + { 0.28103, -0.11601, 0.47074 }, { 0.30124, -0.09763, 0.42115 }, { 0.31874, -0.07926, 0.37424 }, { 0.33379, -0.06262, 0.33015 }, + { 0.34648, -0.05277, 0.29017 }, { 0.35662, -0.04616, 0.25188 }, { 0.36536, -0.04217, 0.21825 }, { 0.37289, -0.03952, 0.18762 }, + { 0.37956, -0.03771, 0.16043 }, { 0.38585, -0.03664, 0.13705 }, { 0.39167, -0.03576, 0.11638 }, { 0.39736, -0.03561, 0.09898 }, + { 0.40272, -0.03531, 0.08360 }, { 0.40787, -0.03537, 0.07050 }, { 0.41311, -0.03577, 0.05948 }, { 0.41811, -0.03606, 0.04991 }, + { 0.42324, -0.03657, 0.04182 }, { 0.42871, -0.03725, 0.03523 }, { 0.43459, -0.03790, 0.02974 }, { 0.44015, -0.03830, 0.02485 }, + { 0.44634, -0.03850, 0.02085 }, { 0.45322, -0.03897, 0.01764 }, { 0.46073, -0.03886, 0.01489 }, { 0.46815, -0.03764, 0.01243 }, + { 0.47699, -0.03576, 0.01052 }, { 0.48579, -0.03199, 0.00882 }, { 0.49728, -0.02678, 0.00762 }, { 0.50776, -0.00003, 0.00638 } }, + {{ 0.19101, -0.17570, 0.69518 }, { 0.22471, -0.17630, 0.64677 }, { 0.25559, -0.16233, 0.58896 }, { 0.28173, -0.14459, 0.53726 }, + { 0.30495, -0.12739, 0.48924 }, { 0.32541, -0.11162, 0.44375 }, { 0.34337, -0.09667, 0.40045 }, { 0.35848, -0.08188, 0.35844 }, + { 0.37152, -0.06821, 0.31901 }, { 0.38267, -0.05887, 0.28296 }, { 0.39205, -0.05240, 0.24905 }, { 0.40013, -0.04793, 0.21813 }, + { 0.40731, -0.04499, 0.19041 }, { 0.41385, -0.04300, 0.16557 }, { 0.41996, -0.04151, 0.14360 }, { 0.42572, -0.04033, 0.12396 }, + { 0.43136, -0.03975, 0.10719 }, { 0.43675, -0.03947, 0.09240 }, { 0.44210, -0.03943, 0.07951 }, { 0.44729, -0.03917, 0.06811 }, + { 0.45254, -0.03889, 0.05809 }, { 0.45786, -0.03887, 0.04960 }, { 0.46336, -0.03909, 0.04242 }, { 0.46904, -0.03903, 0.03607 }, + { 0.47514, -0.03922, 0.03098 }, { 0.48162, -0.03917, 0.02650 }, { 0.48829, -0.03827, 0.02249 }, { 0.49548, -0.03702, 0.01911 }, + { 0.50339, -0.03465, 0.01630 }, { 0.51184, -0.03118, 0.01385 }, { 0.52150, -0.02485, 0.01187 }, { 0.53151, -0.00021, 0.01004 } }, + {{ 0.21608, -0.17314, 0.69404 }, { 0.24910, -0.17599, 0.65077 }, { 0.28069, -0.16514, 0.59738 }, { 0.30724, -0.15021, 0.55038 }, + { 0.33032, -0.13557, 0.50616 }, { 0.35096, -0.12161, 0.46415 }, { 0.36879, -0.10876, 0.42260 }, { 0.38457, -0.09665, 0.38393 }, + { 0.39755, -0.08454, 0.34548 }, { 0.40917, -0.07412, 0.31090 }, { 0.41915, -0.06463, 0.27757 }, { 0.42790, -0.05848, 0.24745 }, + { 0.43555, -0.05371, 0.21932 }, { 0.44253, -0.05046, 0.19400 }, { 0.44886, -0.04821, 0.17119 }, { 0.45491, -0.04588, 0.15001 }, + { 0.46058, -0.04438, 0.13153 }, { 0.46590, -0.04412, 0.11577 }, { 0.47137, -0.04304, 0.10099 }, { 0.47670, -0.04215, 0.08803 }, + { 0.48207, -0.04161, 0.07655 }, { 0.48730, -0.04144, 0.06667 }, { 0.49275, -0.04059, 0.05763 }, { 0.49832, -0.04032, 0.05000 }, + { 0.50411, -0.03974, 0.04326 }, { 0.51004, -0.03905, 0.03745 }, { 0.51646, -0.03800, 0.03233 }, { 0.52320, -0.03639, 0.02793 }, + { 0.53028, -0.03436, 0.02410 }, { 0.53801, -0.03031, 0.02070 }, { 0.54612, -0.02493, 0.01799 }, { 0.55540, 0.00004, 0.01521 } }, + {{ 0.24162, -0.17052, 0.69373 }, { 0.27454, -0.17439, 0.65370 }, { 0.30746, -0.16579, 0.60556 }, { 0.33408, -0.15358, 0.56229 }, + { 0.35697, -0.14112, 0.52099 }, { 0.37775, -0.12867, 0.48220 }, { 0.39587, -0.11735, 0.44396 }, { 0.41165, -0.10700, 0.40686 }, + { 0.42539, -0.09729, 0.37145 }, { 0.43716, -0.08817, 0.33754 }, { 0.44735, -0.07945, 0.30538 }, { 0.45639, -0.07137, 0.27528 }, + { 0.46453, -0.06390, 0.24688 }, { 0.47188, -0.05902, 0.22137 }, { 0.47840, -0.05627, 0.19867 }, { 0.48473, -0.05274, 0.17677 }, + { 0.49060, -0.05014, 0.15718 }, { 0.49627, -0.04809, 0.13948 }, { 0.50156, -0.04704, 0.12389 }, { 0.50672, -0.04617, 0.10988 }, + { 0.51205, -0.04471, 0.09688 }, { 0.51720, -0.04402, 0.08560 }, { 0.52258, -0.04284, 0.07528 }, { 0.52812, -0.04140, 0.06603 }, + { 0.53331, -0.04084, 0.05819 }, { 0.53939, -0.03922, 0.05082 }, { 0.54508, -0.03852, 0.04467 }, { 0.55110, -0.03648, 0.03901 }, + { 0.55773, -0.03362, 0.03404 }, { 0.56432, -0.02977, 0.02969 }, { 0.57204, -0.02257, 0.02565 }, { 0.58012, 0.00013, 0.02208 } }, + {{ 0.26781, -0.16726, 0.69374 }, { 0.30184, -0.17130, 0.65782 }, { 0.33546, -0.16494, 0.61300 }, { 0.36236, -0.15479, 0.57350 }, + { 0.38543, -0.14389, 0.53560 }, { 0.40587, -0.13313, 0.49892 }, { 0.42394, -0.12315, 0.46321 }, { 0.43980, -0.11396, 0.42810 }, + { 0.45355, -0.10555, 0.39357 }, { 0.46600, -0.09751, 0.36179 }, { 0.47658, -0.08994, 0.33055 }, { 0.48605, -0.08303, 0.30167 }, + { 0.49437, -0.07661, 0.27438 }, { 0.50188, -0.07012, 0.24851 }, { 0.50886, -0.06435, 0.22461 }, { 0.51521, -0.06079, 0.20331 }, + { 0.52114, -0.05751, 0.18338 }, { 0.52686, -0.05490, 0.16512 }, { 0.53234, -0.05237, 0.14824 }, { 0.53760, -0.05016, 0.13281 }, + { 0.54305, -0.04763, 0.11850 }, { 0.54802, -0.04636, 0.10604 }, { 0.55334, -0.04452, 0.09447 }, { 0.55850, -0.04311, 0.08417 }, + { 0.56350, -0.04186, 0.07499 }, { 0.56858, -0.04059, 0.06675 }, { 0.57426, -0.03828, 0.05906 }, { 0.57961, -0.03642, 0.05240 }, + { 0.58579, -0.03293, 0.04618 }, { 0.59207, -0.02837, 0.04061 }, { 0.59839, -0.02183, 0.03571 }, { 0.60588, 0.00014, 0.03099 } }, + {{ 0.29552, -0.16307, 0.69478 }, { 0.33034, -0.16735, 0.66097 }, { 0.36464, -0.16269, 0.62010 }, { 0.39162, -0.15439, 0.58318 }, + { 0.41468, -0.14491, 0.54812 }, { 0.43479, -0.13568, 0.51374 }, { 0.45273, -0.12677, 0.48033 }, { 0.46884, -0.11827, 0.44784 }, + { 0.48278, -0.11077, 0.41543 }, { 0.49535, -0.10366, 0.38456 }, { 0.50635, -0.09707, 0.35481 }, { 0.51611, -0.09088, 0.32656 }, + { 0.52482, -0.08505, 0.29983 }, { 0.53261, -0.07920, 0.27402 }, { 0.53981, -0.07391, 0.25049 }, { 0.54631, -0.06982, 0.22916 }, + { 0.55239, -0.06584, 0.20899 }, { 0.55818, -0.06111, 0.18974 }, { 0.56348, -0.05885, 0.17283 }, { 0.56875, -0.05560, 0.15672 }, + { 0.57406, -0.05216, 0.14161 }, { 0.57898, -0.04974, 0.12804 }, { 0.58395, -0.04738, 0.11552 }, { 0.58890, -0.04525, 0.10412 }, + { 0.59356, -0.04361, 0.09392 }, { 0.59866, -0.04122, 0.08435 }, { 0.60366, -0.03869, 0.07566 }, { 0.60857, -0.03626, 0.06785 }, + { 0.61392, -0.03267, 0.06058 }, { 0.61896, -0.02853, 0.05418 }, { 0.62446, -0.02146, 0.04812 }, { 0.63114, 0.00013, 0.04224 } }, + {{ 0.32493, -0.15800, 0.69636 }, { 0.36044, -0.16239, 0.66479 }, { 0.39500, -0.15918, 0.62662 }, { 0.42214, -0.15229, 0.59208 }, + { 0.44504, -0.14422, 0.55948 }, { 0.46513, -0.13589, 0.52832 }, { 0.48260, -0.12801, 0.49686 }, { 0.49819, -0.12058, 0.46577 }, + { 0.51220, -0.11368, 0.43550 }, { 0.52491, -0.10715, 0.40640 }, { 0.53607, -0.10121, 0.37791 }, { 0.54604, -0.09561, 0.35063 }, + { 0.55497, -0.09044, 0.32440 }, { 0.56304, -0.08553, 0.29963 }, { 0.57042, -0.08073, 0.27592 }, { 0.57716, -0.07643, 0.25430 }, + { 0.58338, -0.07244, 0.23411 }, { 0.58922, -0.06816, 0.21474 }, { 0.59463, -0.06506, 0.19748 }, { 0.59983, -0.06156, 0.18085 }, + { 0.60510, -0.05721, 0.16496 }, { 0.60997, -0.05394, 0.15071 }, { 0.61473, -0.05071, 0.13745 }, { 0.61957, -0.04764, 0.12513 }, + { 0.62409, -0.04512, 0.11401 }, { 0.62852, -0.04243, 0.10365 }, { 0.63251, -0.04049, 0.09444 }, { 0.63750, -0.03634, 0.08526 }, + { 0.64231, -0.03217, 0.07697 }, { 0.64613, -0.02798, 0.06969 }, { 0.65086, -0.02084, 0.06274 }, { 0.65651, -0.00002, 0.05589 } }, + {{ 0.35603, -0.15221, 0.69769 }, { 0.39159, -0.15659, 0.66832 }, { 0.42682, -0.15414, 0.63306 }, { 0.45440, -0.14815, 0.60175 }, + { 0.47706, -0.14135, 0.57114 }, { 0.49652, -0.13430, 0.54130 }, { 0.51370, -0.12720, 0.51227 }, { 0.52869, -0.12066, 0.48316 }, + { 0.54234, -0.11418, 0.45520 }, { 0.55442, -0.10835, 0.42705 }, { 0.56541, -0.10297, 0.39979 }, { 0.57520, -0.09810, 0.37306 }, + { 0.58426, -0.09325, 0.34799 }, { 0.59238, -0.08885, 0.32368 }, { 0.59985, -0.08462, 0.30051 }, { 0.60666, -0.08047, 0.27914 }, + { 0.61307, -0.07647, 0.25902 }, { 0.61890, -0.07278, 0.24017 }, { 0.62438, -0.06918, 0.22242 }, { 0.62953, -0.06581, 0.20569 }, + { 0.63458, -0.06234, 0.18950 }, { 0.63958, -0.05873, 0.17436 }, { 0.64422, -0.05531, 0.16053 }, { 0.64882, -0.05182, 0.14758 }, + { 0.65331, -0.04822, 0.13555 }, { 0.65733, -0.04527, 0.12469 }, { 0.66150, -0.04160, 0.11431 }, { 0.66582, -0.03748, 0.10458 }, + { 0.66990, -0.03270, 0.09545 }, { 0.67332, -0.02788, 0.08731 }, { 0.67675, -0.02071, 0.07957 }, { 0.68186, 0.00002, 0.07179 } }, + {{ 0.38874, -0.14547, 0.69990 }, { 0.42458, -0.14938, 0.67322 }, { 0.46012, -0.14757, 0.63986 }, { 0.48761, -0.14257, 0.61072 }, + { 0.51008, -0.13671, 0.58210 }, { 0.52938, -0.13046, 0.55452 }, { 0.54611, -0.12415, 0.52757 }, { 0.56050, -0.11845, 0.49993 }, + { 0.57333, -0.11284, 0.47308 }, { 0.58470, -0.10773, 0.44623 }, { 0.59503, -0.10264, 0.42075 }, { 0.60417, -0.09831, 0.39479 }, + { 0.61278, -0.09386, 0.37078 }, { 0.62064, -0.08976, 0.34736 }, { 0.62794, -0.08593, 0.32486 }, { 0.63473, -0.08203, 0.30393 }, + { 0.64102, -0.07852, 0.28339 }, { 0.64684, -0.07474, 0.26497 }, { 0.65232, -0.07134, 0.24680 }, { 0.65750, -0.06788, 0.23017 }, + { 0.66247, -0.06462, 0.21383 }, { 0.66730, -0.06134, 0.19836 }, { 0.67176, -0.05808, 0.18437 }, { 0.67590, -0.05475, 0.17126 }, + { 0.68040, -0.05106, 0.15847 }, { 0.68408, -0.04764, 0.14730 }, { 0.68813, -0.04367, 0.13611 }, { 0.69178, -0.03955, 0.12594 }, + { 0.69592, -0.03412, 0.11582 }, { 0.69962, -0.02813, 0.10669 }, { 0.70287, -0.02016, 0.09818 }, { 0.70573, 0.00006, 0.09005 } }, + {{ 0.42286, -0.13764, 0.70253 }, { 0.45893, -0.14091, 0.67808 }, { 0.49450, -0.13944, 0.64711 }, { 0.52171, -0.13527, 0.61953 }, + { 0.54404, -0.13016, 0.59306 }, { 0.56282, -0.12479, 0.56710 }, { 0.57891, -0.11949, 0.54128 }, { 0.59290, -0.11426, 0.51588 }, + { 0.60505, -0.10951, 0.48993 }, { 0.61587, -0.10470, 0.46536 }, { 0.62546, -0.10029, 0.44072 }, { 0.63399, -0.09638, 0.41598 }, + { 0.64186, -0.09229, 0.39289 }, { 0.64911, -0.08880, 0.36972 }, { 0.65567, -0.08519, 0.34812 }, { 0.66186, -0.08157, 0.32779 }, + { 0.66775, -0.07846, 0.30734 }, { 0.67323, -0.07504, 0.28886 }, { 0.67844, -0.07174, 0.27113 }, { 0.68333, -0.06833, 0.25452 }, + { 0.68809, -0.06521, 0.23828 }, { 0.69262, -0.06204, 0.22301 }, { 0.69697, -0.05878, 0.20869 }, { 0.70127, -0.05553, 0.19491 }, + { 0.70529, -0.05205, 0.18216 }, { 0.70896, -0.04825, 0.17048 }, { 0.71278, -0.04429, 0.15888 }, { 0.71635, -0.03988, 0.14822 }, + { 0.71997, -0.03495, 0.13785 }, { 0.72323, -0.02900, 0.12826 }, { 0.72673, -0.02079, 0.11891 }, { 0.72944, 0.00004, 0.11016 } }, + {{ 0.45873, -0.12835, 0.70614 }, { 0.49433, -0.13100, 0.68304 }, { 0.52949, -0.12981, 0.65400 }, { 0.55633, -0.12616, 0.62850 }, + { 0.57809, -0.12174, 0.60424 }, { 0.59634, -0.11714, 0.57985 }, { 0.61180, -0.11273, 0.55495 }, { 0.62517, -0.10827, 0.53092 }, + { 0.63669, -0.10407, 0.50671 }, { 0.64685, -0.09994, 0.48305 }, { 0.65584, -0.09624, 0.45902 }, { 0.66376, -0.09244, 0.43645 }, + { 0.67113, -0.08921, 0.41315 }, { 0.67765, -0.08575, 0.39173 }, { 0.68370, -0.08256, 0.37068 }, { 0.68930, -0.07941, 0.35059 }, + { 0.69443, -0.07619, 0.33162 }, { 0.69947, -0.07326, 0.31286 }, { 0.70415, -0.07019, 0.29534 }, { 0.70855, -0.06708, 0.27878 }, + { 0.71287, -0.06409, 0.26263 }, { 0.71661, -0.06080, 0.24796 }, { 0.72066, -0.05775, 0.23332 }, { 0.72451, -0.05456, 0.21962 }, + { 0.72821, -0.05117, 0.20663 }, { 0.73152, -0.04735, 0.19465 }, { 0.73501, -0.04351, 0.18279 }, { 0.73849, -0.03929, 0.17160 }, + { 0.74160, -0.03433, 0.16118 }, { 0.74481, -0.02845, 0.15104 }, { 0.74776, -0.02023, 0.14164 }, { 0.75105, 0.00006, 0.13208 } }, + {{ 0.49554, -0.11754, 0.70978 }, { 0.53027, -0.11957, 0.68862 }, { 0.56475, -0.11844, 0.66139 }, { 0.59075, -0.11545, 0.63717 }, + { 0.61170, -0.11182, 0.61414 }, { 0.62919, -0.10788, 0.59143 }, { 0.64398, -0.10410, 0.56846 }, { 0.65664, -0.10044, 0.54542 }, + { 0.66758, -0.09697, 0.52211 }, { 0.67700, -0.09338, 0.49989 }, { 0.68535, -0.09011, 0.47731 }, { 0.69283, -0.08701, 0.45504 }, + { 0.69943, -0.08398, 0.43352 }, { 0.70556, -0.08119, 0.41218 }, { 0.71100, -0.07822, 0.39227 }, { 0.71624, -0.07555, 0.37245 }, + { 0.72083, -0.07263, 0.35421 }, { 0.72523, -0.06975, 0.33639 }, { 0.72925, -0.06691, 0.31941 }, { 0.73339, -0.06419, 0.30275 }, + { 0.73715, -0.06130, 0.28712 }, { 0.74070, -0.05852, 0.27199 }, { 0.74402, -0.05539, 0.25799 }, { 0.74744, -0.05231, 0.24427 }, + { 0.75049, -0.04891, 0.23147 }, { 0.75399, -0.04558, 0.21879 }, { 0.75685, -0.04169, 0.20716 }, { 0.75973, -0.03736, 0.19611 }, + { 0.76249, -0.03256, 0.18534 }, { 0.76538, -0.02662, 0.17518 }, { 0.76785, -0.01865, 0.16553 }, { 0.77115, 0.00005, 0.15588 } }, + {{ 0.53272, -0.10493, 0.71478 }, { 0.56610, -0.10643, 0.69461 }, { 0.59929, -0.10534, 0.66938 }, { 0.62423, -0.10298, 0.64618 }, + { 0.64430, -0.10006, 0.62412 }, { 0.66087, -0.09686, 0.60285 }, { 0.67490, -0.09377, 0.58132 }, { 0.68693, -0.09079, 0.55910 }, + { 0.69725, -0.08792, 0.53711 }, { 0.70612, -0.08524, 0.51494 }, { 0.71373, -0.08232, 0.49425 }, { 0.72045, -0.07970, 0.47333 }, + { 0.72654, -0.07708, 0.45290 }, { 0.73215, -0.07461, 0.43265 }, { 0.73731, -0.07219, 0.41291 }, { 0.74188, -0.06976, 0.39410 }, + { 0.74597, -0.06728, 0.37624 }, { 0.74997, -0.06490, 0.35873 }, { 0.75337, -0.06230, 0.34249 }, { 0.75709, -0.05978, 0.32632 }, + { 0.76060, -0.05720, 0.31088 }, { 0.76378, -0.05462, 0.29607 }, { 0.76659, -0.05180, 0.28230 }, { 0.76955, -0.04891, 0.26889 }, + { 0.77230, -0.04571, 0.25622 }, { 0.77514, -0.04252, 0.24377 }, { 0.77793, -0.03908, 0.23184 }, { 0.78043, -0.03509, 0.22064 }, + { 0.78282, -0.03041, 0.21005 }, { 0.78468, -0.02457, 0.20018 }, { 0.78754, -0.01737, 0.19021 }, { 0.78995, 0.00006, 0.18089 } }, + {{ 0.56932, -0.09075, 0.71955 }, { 0.60103, -0.09168, 0.70070 }, { 0.63267, -0.09087, 0.67652 }, { 0.65651, -0.08897, 0.65469 }, + { 0.67543, -0.08664, 0.63411 }, { 0.69104, -0.08420, 0.61379 }, { 0.70419, -0.08174, 0.59365 }, { 0.71550, -0.07948, 0.57227 }, + { 0.72506, -0.07710, 0.55184 }, { 0.73326, -0.07491, 0.53105 }, { 0.74047, -0.07278, 0.51035 }, { 0.74675, -0.07074, 0.49002 }, + { 0.75230, -0.06867, 0.47031 }, { 0.75728, -0.06656, 0.45135 }, { 0.76209, -0.06453, 0.43250 }, { 0.76616, -0.06249, 0.41451 }, + { 0.76990, -0.06046, 0.39705 }, { 0.77334, -0.05835, 0.38038 }, { 0.77650, -0.05612, 0.36453 }, { 0.77934, -0.05390, 0.34929 }, + { 0.78214, -0.05159, 0.33447 }, { 0.78495, -0.04919, 0.32031 }, { 0.78732, -0.04660, 0.30694 }, { 0.78980, -0.04409, 0.29376 }, + { 0.79245, -0.04133, 0.28119 }, { 0.79502, -0.03835, 0.26903 }, { 0.79706, -0.03500, 0.25765 }, { 0.79938, -0.03151, 0.24642 }, + { 0.80116, -0.02721, 0.23605 }, { 0.80364, -0.02228, 0.22580 }, { 0.80529, -0.01553, 0.21630 }, { 0.80743, 0.00008, 0.20694 } }, + {{ 0.60446, -0.07481, 0.72541 }, { 0.63433, -0.07540, 0.70789 }, { 0.66433, -0.07476, 0.68481 }, { 0.68668, -0.07333, 0.66418 }, + { 0.70457, -0.07166, 0.64427 }, { 0.71943, -0.06986, 0.62480 }, { 0.73164, -0.06808, 0.60530 }, { 0.74200, -0.06634, 0.58563 }, + { 0.75088, -0.06465, 0.56577 }, { 0.75865, -0.06301, 0.54574 }, { 0.76531, -0.06140, 0.52594 }, { 0.77096, -0.05975, 0.50704 }, + { 0.77604, -0.05817, 0.48812 }, { 0.78047, -0.05654, 0.46984 }, { 0.78462, -0.05499, 0.45179 }, { 0.78846, -0.05337, 0.43440 }, + { 0.79171, -0.05168, 0.41775 }, { 0.79502, -0.04998, 0.40149 }, { 0.79763, -0.04815, 0.38649 }, { 0.80039, -0.04639, 0.37146 }, + { 0.80272, -0.04448, 0.35737 }, { 0.80522, -0.04252, 0.34354 }, { 0.80755, -0.04044, 0.33020 }, { 0.80954, -0.03811, 0.31798 }, + { 0.81129, -0.03571, 0.30606 }, { 0.81256, -0.03305, 0.29475 }, { 0.81479, -0.03031, 0.28330 }, { 0.81667, -0.02709, 0.27265 }, + { 0.81865, -0.02350, 0.26226 }, { 0.82066, -0.01932, 0.25205 }, { 0.82209, -0.01368, 0.24262 }, { 0.82367, 0.00002, 0.23366 } }, + {{ 0.63783, -0.05754, 0.73110 }, { 0.66580, -0.05784, 0.71450 }, { 0.69379, -0.05736, 0.69314 }, { 0.71493, -0.05642, 0.67318 }, + { 0.73184, -0.05529, 0.65409 }, { 0.74566, -0.05410, 0.63543 }, { 0.75715, -0.05290, 0.61665 }, { 0.76684, -0.05171, 0.59776 }, + { 0.77502, -0.05055, 0.57875 }, { 0.78192, -0.04938, 0.56005 }, { 0.78802, -0.04824, 0.54142 }, { 0.79346, -0.04716, 0.52249 }, + { 0.79805, -0.04606, 0.50438 }, { 0.80183, -0.04480, 0.48754 }, { 0.80557, -0.04364, 0.47040 }, { 0.80900, -0.04245, 0.45369 }, + { 0.81218, -0.04128, 0.43725 }, { 0.81485, -0.03999, 0.42211 }, { 0.81744, -0.03873, 0.40702 }, { 0.81962, -0.03727, 0.39305 }, + { 0.82179, -0.03580, 0.37945 }, { 0.82385, -0.03429, 0.36620 }, { 0.82598, -0.03268, 0.35338 }, { 0.82778, -0.03089, 0.34141 }, + { 0.82910, -0.02898, 0.33004 }, { 0.83055, -0.02688, 0.31907 }, { 0.83184, -0.02463, 0.30846 }, { 0.83322, -0.02206, 0.29842 }, + { 0.83509, -0.01925, 0.28818 }, { 0.83626, -0.01574, 0.27866 }, { 0.83803, -0.01128, 0.26919 }, { 0.83936, -0.00001, 0.26038 } }, + {{ 0.66881, -0.03902, 0.73750 }, { 0.69495, -0.03917, 0.72144 }, { 0.72127, -0.03890, 0.70116 }, { 0.74089, -0.03836, 0.68204 }, + { 0.75676, -0.03768, 0.66379 }, { 0.76971, -0.03701, 0.64562 }, { 0.78031, -0.03631, 0.62792 }, { 0.78909, -0.03559, 0.61014 }, + { 0.79688, -0.03488, 0.59193 }, { 0.80339, -0.03421, 0.57344 }, { 0.80909, -0.03354, 0.55536 }, { 0.81375, -0.03284, 0.53793 }, + { 0.81807, -0.03212, 0.52063 }, { 0.82174, -0.03140, 0.50374 }, { 0.82535, -0.03066, 0.48732 }, { 0.82861, -0.02992, 0.47141 }, + { 0.83083, -0.02915, 0.45633 }, { 0.83320, -0.02828, 0.44181 }, { 0.83559, -0.02745, 0.42730 }, { 0.83745, -0.02649, 0.41385 }, + { 0.84005, -0.02553, 0.40059 }, { 0.84070, -0.02440, 0.38881 }, { 0.84245, -0.02332, 0.37643 }, { 0.84401, -0.02210, 0.36506 }, + { 0.84503, -0.02076, 0.35431 }, { 0.84665, -0.01940, 0.34339 }, { 0.84777, -0.01782, 0.33324 }, { 0.84905, -0.01601, 0.32345 }, + { 0.84999, -0.01390, 0.31425 }, { 0.85072, -0.01143, 0.30523 }, { 0.85236, -0.00812, 0.29632 }, { 0.85341, 0.00002, 0.28782 } }, + {{ 0.69740, -0.01972, 0.74339 }, { 0.72178, -0.01980, 0.72804 }, { 0.74630, -0.01969, 0.70892 }, { 0.76475, -0.01946, 0.69082 }, + { 0.77931, -0.01916, 0.67367 }, { 0.79173, -0.01888, 0.65573 }, { 0.80171, -0.01860, 0.63826 }, { 0.81003, -0.01833, 0.62090 }, + { 0.81725, -0.01802, 0.60355 }, { 0.82283, -0.01768, 0.58677 }, { 0.82781, -0.01737, 0.56971 }, { 0.83251, -0.01712, 0.55229 }, + { 0.83628, -0.01681, 0.53567 }, { 0.83983, -0.01645, 0.51970 }, { 0.84284, -0.01610, 0.50407 }, { 0.84559, -0.01577, 0.48879 }, + { 0.84790, -0.01538, 0.47446 }, { 0.85013, -0.01498, 0.46044 }, { 0.85214, -0.01455, 0.44710 }, { 0.85384, -0.01407, 0.43403 }, + { 0.85537, -0.01356, 0.42174 }, { 0.85665, -0.01304, 0.41005 }, { 0.85860, -0.01251, 0.39844 }, { 0.85961, -0.01189, 0.38755 }, + { 0.86039, -0.01119, 0.37756 }, { 0.86098, -0.01040, 0.36805 }, { 0.86192, -0.00953, 0.35846 }, { 0.86278, -0.00857, 0.34911 }, + { 0.86425, -0.00751, 0.33962 }, { 0.86491, -0.00614, 0.33144 }, { 0.86581, -0.00433, 0.32300 }, { 0.86677, 0.00002, 0.31508 } }, + {{ 0.72363, 0.00000, 0.74973 }, { 0.74617, -0.00002, 0.73527 }, { 0.76908, 0.00000, 0.71668 }, { 0.78618, -0.00002, 0.69953 }, + { 0.79981, -0.00003, 0.68291 }, { 0.81110, -0.00001, 0.66623 }, { 0.82060, -0.00001, 0.64940 }, { 0.82853, -0.00004, 0.63212 }, + { 0.83504, -0.00002, 0.61545 }, { 0.84058, -0.00003, 0.59849 }, { 0.84521, -0.00005, 0.58226 }, { 0.84932, -0.00002, 0.56605 }, + { 0.85275, -0.00000, 0.55034 }, { 0.85605, -0.00001, 0.53461 }, { 0.85877, -0.00001, 0.51974 }, { 0.86129, -0.00001, 0.50561 }, + { 0.86320, -0.00005, 0.49191 }, { 0.86521, -0.00004, 0.47875 }, { 0.86654, -0.00002, 0.46632 }, { 0.86846, -0.00002, 0.45401 }, + { 0.86997, -0.00001, 0.44209 }, { 0.87153, 0.00003, 0.43044 }, { 0.87288, 0.00002, 0.42005 }, { 0.87352, 0.00003, 0.40990 }, + { 0.87465, 0.00004, 0.39997 }, { 0.87549, 0.00003, 0.39069 }, { 0.87626, 0.00006, 0.38171 }, { 0.87676, 0.00004, 0.37342 }, + { 0.87714, 0.00006, 0.36523 }, { 0.87859, 0.00006, 0.35675 }, { 0.87952, 0.00009, 0.34897 }, { 0.87958, 0.00003, 0.34187 }} + // clang-format on + } }; + return param.data; +} + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_thinlayer_decl.h b/src/libbsdl/include/BSDL/SPI/bsdf_thinlayer_decl.h new file mode 100644 index 000000000..98a62d982 --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_thinlayer_decl.h @@ -0,0 +1,172 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +struct ThinFresnel { + BSDL_INLINE_METHOD ThinFresnel(float eta); + + BSDL_INLINE_METHOD float eval(const float c) const; + BSDL_INLINE_METHOD float eval_inv(const float c) const; + BSDL_INLINE_METHOD Power avg() const; + BSDL_INLINE_METHOD float avgf() const; + BSDL_INLINE_METHOD float avg_invf() const; + + static BSDL_INLINE_METHOD ThinFresnel from_table_index(float tx); + + // Note index of eta equals index of 1 / eta, so this function works for + // either side (both tables) + BSDL_INLINE_METHOD float table_index() const; + + float eta; + +private: + static constexpr float IOR_MIN = 1.001f; + static constexpr float IOR_MAX = 5.0f; +}; + +template struct ThinMicrofacet { + // describe how tabulation should be done + static constexpr int Nc = 16; + static constexpr int Nr = 16; + static constexpr int Nf = 32; + // If we assume the reflected cosine follows a cosine distribution for rough + // microfacted, the average inverse cosine is 2 (analitically), but we run some + // similations with GGX and it turned out to be closer to 2.2 + static constexpr float AVG_INV_COS = 2.2f; + + static BSDL_INLINE_METHOD float sum_refl_series(float Rout, float Tin, + float Rin, float A); + static BSDL_INLINE_METHOD float sum_refr_series(float Rout, float Tin, + float Rin, float A); + static constexpr float get_cosine(int i) + { + return std::max(float(i) * (1.0f / (Nc - 1)), 1e-6f); + } + static constexpr const char* name() { return "Thinlayer"; } + + explicit BSDL_INLINE_METHOD ThinMicrofacet(float, float roughness_index, + float fresnel_index); + BSDL_INLINE_METHOD ThinMicrofacet(float roughness, float aniso, float eta, + float thickness, float prob_clamp, + Power sigma_t); + BSDL_INLINE_METHOD Sample eval(const Imath::V3f& wo, const Imath::V3f& wi, + bool doreflect, bool dorefract) const; + BSDL_INLINE_METHOD Sample sample(Imath::V3f wo, float randu, float randv, + float randw, bool doreflect, + bool dorefract) const; + BSDL_INLINE_METHOD Sample sample(Imath::V3f wo, float randu, float randv, + float randw) const + { + return sample(wo, randu, randv, randw, true, true); + }; + BSDL_INLINE_METHOD ThinFresnel fresnel() const { return f; } + BSDL_INLINE_METHOD float fresnel(float c) const { return f.eval(c); } + BSDL_INLINE_METHOD float eta() const { return f.eta; } + // Borrowed from dielectric + BSDL_INLINE_METHOD float fresnel_prob(float f) const; + BSDL_INLINE_METHOD Sample eval(const Imath::V3f& wo, const Imath::V3f& m, + const Imath::V3f& wi, const bool both, + const float refr_slope_scale, + Power refl_atten, Power refr_atten) const; + // This function computes the slope scale needed for a micronormal if we + // are going to fake the refraction with a flipped reflection + BSDL_INLINE_METHOD float refraction_slope_scale(float cosNO) const; + BSDL_INLINE_METHOD Imath::V3f scale_slopes(const Imath::V3f& m, + const float s) const; + BSDL_INLINE_METHOD void attenuation(Imath::V3f wo, Imath::V3f m, + Power* refl, Power* refr) const; + + Dist d; + Power sigma_t; + ThinFresnel f; + float thickness; + float roughness; + float prob_clamp; +}; + +struct Thinlayer : public ThinMicrofacet { + explicit BSDL_INLINE_METHOD Thinlayer(float, float roughness_index, + float fresnel_index); + BSDL_INLINE_METHOD Thinlayer(float roughness, float aniso, float eta, + float thickness, float prob_clamp, + Power sigma_t); + struct Energy { + float data[Nf * Nr * Nc]; + }; + static BSDL_INLINE_METHOD Energy& get_energy(); + + static const char* lut_header() { return "bsdf_thinlayer_luts.h"; } + static const char* struct_name() { return "Thinlayer"; } +}; + +template struct ThinLayerLobe : public Lobe { + using Base = Lobe; + struct Data { + Imath::V3f N; + Imath::V3f T; + float IOR; + float roughness; + float anisotropy; + float thickness; + float prob_clamp; // Not exposed + Imath::C3f refl_tint; // overall tint on reflected rays + Imath::C3f refr_tint; // overall tint on refracted rays + Imath::C3f sigma_t; + using lobe_type = ThinLayerLobe; + }; + template static typename LobeRegistry::Entry entry() + { + static_assert( + std::is_base_of::value); // Make no other assumptions + using R = LobeRegistry; + return { name(), + { R::param(&D::N), R::param(&D::T), R::param(&D::IOR), + R::param(&D::roughness), R::param(&D::anisotropy), + R::param(&D::thickness), R::param(&D::refl_tint), + R::param(&D::refr_tint), R::param(&D::sigma_t), + R::close() } }; + } + + template + BSDL_INLINE_METHOD ThinLayerLobe(T*, const BsdfGlobals& globals, + const Data& data); + + static const char* name() { return "thinlayer"; } + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi, bool doreflect, + bool dorefract) const; + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f& _rnd, + bool doreflect, bool dorefract) const; + +protected: + BSDL_INLINE_METHOD std::pair get_diff_trans() const; + BSDL_INLINE_METHOD Power get_tint(float cosNI) const; + BSDL_INLINE_METHOD void eval_ec_lobe(Sample* s, Imath::V3f wi_l, + const Power diff_tint, + const Power trans_tint, + float Tprob) const; + BSDL_INLINE_METHOD Imath::V3f sample_lobe(float randu, float randv, + bool back) const; + + Thinlayer spec; + Power refl_tint; + Power refr_tint; + // energy compensation data (R and T lobes) + float Eo; +}; + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_thinlayer_impl.h b/src/libbsdl/include/BSDL/SPI/bsdf_thinlayer_impl.h new file mode 100644 index 000000000..e8338e2fc --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_thinlayer_impl.h @@ -0,0 +1,487 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#ifndef BAKE_BSDL_TABLES +# include +#endif +#include +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +BSDL_INLINE_METHOD +ThinFresnel::ThinFresnel(float eta) : eta(CLAMP(eta, IOR_MIN, IOR_MAX)) {} + +BSDL_INLINE_METHOD float +ThinFresnel::eval(const float c) const +{ + assert(c >= 0); // slightly above 1.0 is ok + assert(eta > 1); // avoid singularity at eta==1 + // optimized for c in [0,1] and eta in (1,inf) + const float g = sqrtf(eta * eta - 1 + c * c); + const float A = (g - c) / (g + c); + const float B = (c * (g + c) - 1) / (c * (g - c) + 1); + return 0.5f * A * A * (1 + B * B); +} + +BSDL_INLINE_METHOD float +ThinFresnel::eval_inv(const float c) const +{ + return fresnel_dielectric(c, 1 / eta); +} + +BSDL_INLINE_METHOD float +ThinFresnel::avgf() const +{ + // see Avg Fresnel -- but we know that eta >= 1 here + return (eta - 1) / (4.08567f + 1.00071f * eta); +} + +BSDL_INLINE_METHOD Power +ThinFresnel::avg() const +{ + return Power(avgf(), 1); +} + +BSDL_INLINE_METHOD float +ThinFresnel::avg_invf() const +{ + return avg_fresnel_dielectric(1 / eta); +} + +BSDL_INLINE_METHOD ThinFresnel +ThinFresnel::from_table_index(float tx) +{ + const float eta = LERP(SQR(tx), IOR_MIN, IOR_MAX); + return ThinFresnel(eta); +} + +// Note index of eta equals index of 1 / eta, so this function works for +// either side (both tables) +BSDL_INLINE_METHOD float +ThinFresnel::table_index() const +{ + // turn the IOR value into something suitable for integrating + // this is the reverse of the method above + const float seta = CLAMP(eta < 1 ? 1 / eta : eta, IOR_MIN, IOR_MAX); + const float x = (seta - IOR_MIN) * (1 / (IOR_MAX - IOR_MIN)); + assert(x >= 0); + assert(x <= 1); + return sqrtf(x); +} + +template +BSDL_INLINE_METHOD float +ThinMicrofacet::sum_refl_series(float Rout, float Tin, float Rin, float A) +{ + // The amount of reflected energy is + // + // Rout + Tin (1 - Rin) Rin A^2 + Tin Tout Rin^3 A^4 ... + // Tin (1 - Rin) Rin^(2n-1) A^(2n) = + // Rout + Tin (1 - Rin) Rin A^2 / (1 - Rin^2 A^2) + // + // which without absorption adds up to 1 with the refracted component in + // the next function. + const float b = SQR(Rin * A); // Basis of the geometric series + return Rout + + (1 - b < EPSILON ? (A < 1 ? 0 : Tin * 0.5f) + : Tin * (1 - Rin) * SQR(A) * Rin / (1 - b)); +} + +template +BSDL_INLINE_METHOD float +ThinMicrofacet::sum_refr_series(float Rout, float Tin, float Rin, float A) +{ + // The amount of energy exiting the thin layer on the other side is + // + // Tin A (1 - Rin) (1 + Rin^2 A^2 + Rin^4 A^4 + ... Rin^(2n) A^(2n)) = + // Tin A (1 - Rin) / (1 - Rin^2 A^2) + // + // which we evaluate for each RGB channel guarding the singularity, when + // (1 - Rin^2 A^2) approaches 0 the limit is Tin * 0.5 if A is 1.0 or + // 0.0 if A < 1.0. Note A and Rin are always < 1.0. + const float b = SQR(Rin * A); // Basis of the geometric series + return 1 - b < EPSILON ? (A < 1 ? 0 : Tin * 0.5f) + : Tin * (1 - Rin) * A / (1 - b); +} +template +BSDL_INLINE_METHOD +ThinMicrofacet::ThinMicrofacet(float, float roughness_index, + float fresnel_index) + : d(roughness_index, 0) + , sigma_t(0.0f, 1) + , f(ThinFresnel::from_table_index(fresnel_index)) + , thickness(0) + , roughness(roughness_index) + , prob_clamp(0) +{ +} + +template +BSDL_INLINE_METHOD +ThinMicrofacet::ThinMicrofacet(float roughness, float aniso, float eta, + float thickness, float prob_clamp, + Power sigma_t) + : d(roughness, aniso) + , sigma_t(sigma_t) + , f(eta) + , thickness(thickness) + , roughness(roughness) + , prob_clamp(prob_clamp) +{ +} + +template +BSDL_INLINE_METHOD Sample +ThinMicrofacet::eval(const Imath::V3f& wo, const Imath::V3f& wi, + bool doreflect, bool dorefract) const +{ + const bool both = doreflect && dorefract; + const bool isrefl = wi.z > 0; + const Imath::V3f wif = { wi.x, wi.y, + fabsf(wi.z) }; // Flipped reflection trick; + // The micronormal we actually use for scattering, which may or may not + // have its slopes scaled + const Imath::V3f mt = (wo + wif).normalized(); + const float refr_slope_scale = refraction_slope_scale(wo.z); + // The micronormal that we pretend to use, but a lie with refraction + const Imath::V3f m = isrefl ? mt : scale_slopes(mt, 1 / refr_slope_scale); + if (wi.z == 0 || wo.z == 0 || m.dot(wo) <= 0 || mt.dot(wo) <= 0) + return {}; + Power refl_atten, refr_atten; + attenuation(wo, m, &refl_atten, &refr_atten); + // Call the common eval + return eval(wo, m, wi, both, refr_slope_scale, refl_atten, refr_atten); +} + +template +BSDL_INLINE_METHOD Sample +ThinMicrofacet::sample(Imath::V3f wo, float randu, float randv, + float randw, bool doreflect, bool dorefract) const +{ + const bool both = doreflect && dorefract; + const Imath::V3f m = d.sample(wo, randu, randv); + if (wo.dot(m) <= 0) + return {}; + Power refl_atten, refr_atten; + attenuation(wo, m, &refl_atten, &refr_atten); + // We compute a pseudo-fresnel factor from the attenuations, it will serve + // as a probability for choosing the lobe + const float R = refl_atten.max(), T = refr_atten.max(); + const float F = R / std::max(R + T, std::numeric_limits::min()); + const float P = both ? fresnel_prob(F) : (dorefract ? 0 : 1); + bool isrefl = randw < P; + const float refr_slope_scale = refraction_slope_scale(wo.z); + // If we are doing refraction fake a micronormal by scaling the slopes + // to account for the reduced blurring in refraction + const Imath::V3f mt = isrefl ? m : scale_slopes(m, refr_slope_scale); + if (wo.dot(mt) <= 0) + return {}; + const Imath::V3f wif = reflect(wo, mt); + // Flipped reflection trick for refraction + const Imath::V3f wi = isrefl ? wif : Imath::V3f(wif.x, wif.y, -wif.z); + if ((isrefl && wi.z <= 0) || (!isrefl && wi.z >= 0)) + return {}; + // Call the common eval + return eval(wo, m, wi, both, refr_slope_scale, refl_atten, refr_atten); +} + +// Borrowed from dielectric +template +BSDL_INLINE_METHOD float +ThinMicrofacet::fresnel_prob(float f) const +{ + const float safe_prob = 0.2f; + return LERP(prob_clamp, f, CLAMP(f, safe_prob, 1 - safe_prob)); +} + +template +BSDL_INLINE_METHOD Sample +ThinMicrofacet::eval(const Imath::V3f& wo, const Imath::V3f& m, + const Imath::V3f& wi, const bool both, + const float refr_slope_scale, Power refl_atten, + Power refr_atten) const +{ + const bool isrefl = wi.z > 0; + const float R = refl_atten.max(), T = refr_atten.max(); + const float F = R / std::max(R + T, std::numeric_limits::min()); + const float P = both ? fresnel_prob(isrefl ? F : 1 - F) : 1; + if (P < PDF_MIN) + return {}; + // Flipped reflection trick in case of refraction + const Imath::V3f wif = { wi.x, wi.y, fabsf(wi.z) }; + const float cosNO = wo.z; + const float cosNM = m.z; + const float sinNM = sqrtf(1 - std::min(SQR(cosNM), 1.0f)); + assert(cosNO >= 0); + // If we did the slope scaling there is a jacobian to account for + // cos^3 to go to slope space, the scale^2 and then 1 / cos^3 of the new + // angle. It boils down to this: + const float tmp = sqrtf(SQR(refr_slope_scale * sinNM) + SQR(cosNM)); + const float J = isrefl ? 1 : tmp * tmp * tmp / SQR(refr_slope_scale); + const float D = d.D(m); + const float G1 = d.G1(wo); + const float out = d.G2_G1(wif, wo) / P; + const float pdf = (G1 * D * J * P) / (4.0f * cosNO); + const Power w = (isrefl ? refl_atten : refr_atten) * out; + // P is computed so the weights never go over one, but due to rounding + // errors and mostly fresnel_prob(), weight can go over one. + assert(w.max() < 2.5f); + return { wi, w, pdf, 0 }; +} + +// This function computes the slope scale needed for a micronormal if we +// are going to fake the refraction with a flipped reflection +template +BSDL_INLINE_METHOD float +ThinMicrofacet::refraction_slope_scale(float cosNO) const +{ + cosNO = std::min(cosNO, 1.0f); + assert(cosNO >= 0); + const float inveta = 1 / eta(); + const float sinNI = inveta * sqrtf(1 - SQR(cosNO)); + if (sinNI > 1.0f) + return 1; + const float cosNI = -sqrtf(1 - SQR(sinNI)); + // From "Efficient Rendering of Layered Materials using an + // Atomic Decomposition with Statistical Operators" (Belcour) + // equation 10. This is the jacobian from half vector slope to + // refracted slope. + const float refr_jacobian_entry = (1 + inveta * (cosNO / cosNI)); + const float refr_jacobian_exit = (1 + eta() * (cosNI / cosNO)); + // And the trivial reflection one + const float refl_jacobian = 2; + // Without loss of generality, we assume roughness is 1.0 here. + // Take into account there is entry and exit events, so we have a + // convolution of two GGX. We know its pseudo variance is 2 alpha^2 + // but in this case using just alpha^2 works better. Add the two variances: + const float refr_variance = SQR(refr_jacobian_entry) + + SQR(refr_jacobian_exit); + // which we will divide by the reflection variance, the one actually used + const float refl_variance = SQR(refl_jacobian); + // Never scale the slope so it equates to a roughness higher than one + const float max_scale = 1 / roughness; + // And finally map from variance to slope the ratio of the two. There + // is a consistent visual effect that scales up the effective variance + // by the inverse of cosNO. I attribute it to the stretching of + // the projected surface but I still ignore the full explanation. + return std::min(sqrtf((refr_variance / refl_variance) / cosNO), max_scale); +} + +template +BSDL_INLINE_METHOD Imath::V3f +ThinMicrofacet::scale_slopes(const Imath::V3f& m, const float s) const +{ + return Imath::V3f(m.x * s, m.y * s, m.z).normalized(); +} + +template +BSDL_INLINE_METHOD void +ThinMicrofacet::attenuation(Imath::V3f wo, Imath::V3f m, Power* refl, + Power* refr) const +{ + const Imath::V3f wr = refract(wo, m, eta()); + const float cosNO = std::min(wo.dot(m), 1.0f); + const float cosNR = CLAMP(-wr.z, 0.0f, 1.0f); + assert(cosNO >= 0); + // Traveled distance inside the thin layer depends on the + // refracted angle. As roughness grows, the inverse cosine + // tends to the avg + const float d = thickness + * LERP(roughness, 1 / std::max(cosNR, 1e-6f), AVG_INV_COS); + // Fresnel coeficients + const float Rout = f.eval(cosNO); + const float Tin = 1.0f - Rout; + // And as roughness grows, the internal bounces tend to the avg too + const float Rin = LERP(roughness, f.eval_inv(cosNR), f.avg_invf()); + // Attenuation for one segment or bounce inside the layer + Power A = Power::UNIT(); + + constexpr auto fast_exp = BSDLConfig::Fast::expf; + if (d > 0) { + A = Power( + [&](int i) { + return sigma_t[i] > 0 ? fast_exp(-d * sigma_t[i]) : 1; + }, + 1); + } + + *refl = Power([&](int i) { return sum_refl_series(Rout, Tin, Rin, A[i]); }, + 1); + *refr = Power([&](int i) { return sum_refr_series(Rout, Tin, Rin, A[i]); }, + 1); + assert((*refl + *refr).max() < 1.0001f); +} + +BSDL_INLINE_METHOD +Thinlayer::Thinlayer(float, float roughness_index, float fresnel_index) + : ThinMicrofacet(0, roughness_index, fresnel_index) +{ +} + +BSDL_INLINE_METHOD +Thinlayer::Thinlayer(float roughness, float aniso, float eta, float thickness, + float prob_clamp, Power sigma_t) + : ThinMicrofacet(roughness, aniso, eta, thickness, prob_clamp, + sigma_t) +{ +} + +template +template +BSDL_INLINE_METHOD +ThinLayerLobe::ThinLayerLobe(T* lobe, const BsdfGlobals& globals, + const Data& data) + : + + Base(lobe, globals.visible_normal(data.N), data.T, + globals.regularize_roughness(CLAMP(data.roughness, 0.0f, 1.0f)), + globals.lambda_0, true) + , spec(Base::roughness(), data.anisotropy, data.IOR, data.thickness, + data.prob_clamp, globals.wave(data.sigma_t)) + , + // We also scale refl_tint to avoid secondary rays bouncing inside, which is + // bad when we have transparent shadows and produces energy explosion + refl_tint(globals.wave(data.refl_tint).clamped(0, 1)) + , refr_tint(globals.wave(data.refr_tint).clamped(0, 1)) +{ + TabulatedEnergyCurve diff_curve(Base::roughness(), + spec.fresnel().table_index()); + + float cosNO = CLAMP(globals.wo.dot(Base::frame.Z), 0.0f, 1.0f); + Eo = diff_curve.Emiss_eval(cosNO); +} + +template +BSDL_INLINE_METHOD Sample +ThinLayerLobe::eval_impl(const Imath::V3f& wo, const Imath::V3f& wi, + bool doreflect, bool dorefract) const +{ + const bool both = doreflect && dorefract; + const float cosNI = wi.z; + const auto diff_trans = get_diff_trans(); + const float R = diff_trans.first.max(), T = diff_trans.second.max(); + const float Tprob = T / std::max(R + T, std::numeric_limits::min()); + + assert(wo.z >= 0); + if ((cosNI > 0 && !doreflect) || (cosNI < 0 && !dorefract) || cosNI == 0) + return {}; + Sample s = { wi }; + const float PE = Eo * (both ? 1 : (dorefract ? Tprob : 1 - Tprob)); + Sample ss = spec.eval(wo, wi, doreflect, dorefract); + s.update(ss.weight, ss.pdf, 1 - PE); + eval_ec_lobe(&s, wi, diff_trans.first, diff_trans.second, Tprob); + s.weight *= get_tint(cosNI); + s.roughness = sum_max(Base::roughness(), cosNI < 0 ? Base::roughness() : 0, + 1.0f); + return s; +} + +template +BSDL_INLINE_METHOD Sample +ThinLayerLobe::sample_impl(const Imath::V3f& wo, + const Imath::V3f& _rnd, bool doreflect, + bool dorefract) const +{ + const bool both = doreflect && dorefract; + const auto diff_trans = get_diff_trans(); + const float R = diff_trans.first.max(), T = diff_trans.second.max(); + const float Tprob = T / std::max(R + T, std::numeric_limits::min()); + const float PE = Eo * (both ? 1 : (dorefract ? Tprob : 1 - Tprob)); + Sample s = {}; + + Imath::V3f rnd = _rnd; + if (rnd.x < 1 - PE) { + // sample specular lobe + rnd.x = Sample::stretch(rnd.x, 0.0f, 1 - PE); + auto ss = spec.sample(wo, rnd.x, rnd.y, rnd.z, doreflect, dorefract); + if (MAX_ABS_XYZ(ss.wi) == 0) + return {}; + s.wi = ss.wi; + s.update(ss.weight, ss.pdf, 1 - PE); + eval_ec_lobe(&s, s.wi, diff_trans.first, diff_trans.second, Tprob); + } else { + // sample diffuse lobe + rnd.x = Sample::stretch(rnd.x, 1 - PE, PE); + const bool back = !(!dorefract || (both && rnd.z >= Tprob)); + s.wi = sample_lobe(rnd.x, rnd.y, back); + eval_ec_lobe(&s, s.wi, diff_trans.first, diff_trans.second, Tprob); + const auto ss = spec.eval(wo, s.wi, doreflect, dorefract); + s.update(ss.weight, ss.pdf, 1 - PE); + } + const float cosNI = s.wi.z; + s.weight *= get_tint(cosNI); + s.roughness = sum_max(Base::roughness(), cosNI < 0 ? Base::roughness() : 0, + 1.0f); + return s; +} + +template +BSDL_INLINE_METHOD std::pair +ThinLayerLobe::get_diff_trans() const +{ + constexpr auto fast_exp = BSDLConfig::Fast::expf; + // Start with average fresnel + const float Tf = 1 - spec.fresnel().avgf(), + Tb = 1 - spec.fresnel().avg_invf(); + // And then correct to satisfy the refraction reciprocity + const float x = Tb * SQR(spec.eta()) / (Tf + Tb * SQR(spec.eta())); + const float Tout = Tb * (1 - x), Tin = Tf * x, + // And therefore reflection is ... + Rout = 1 - Tin, Rin = 1 - Tout; + + // Diffuse and Translucent lobes for energy compensation + const float avgd = spec.thickness * Thinlayer::AVG_INV_COS; + + Power A([&](int i) { return fast_exp(-avgd * spec.sigma_t[i]); }, 1); + // Sum up series for the tints + Power diff_tint( + [&](int i) { return Thinlayer::sum_refl_series(Rout, Tin, Rin, A[i]); }, + 1); + Power trans_tint( + [&](int i) { return Thinlayer::sum_refr_series(Rout, Tin, Rin, A[i]); }, + 1); + return { diff_tint, trans_tint }; +} + +template +BSDL_INLINE_METHOD Power +ThinLayerLobe::get_tint(float cosNI) const +{ + return cosNI > 0 ? refl_tint : refr_tint; +} + +template +BSDL_INLINE_METHOD void +ThinLayerLobe::eval_ec_lobe(Sample* s, Imath::V3f wi_l, + const Power diff_tint, + const Power trans_tint, + float Tprob) const +{ + const bool back = wi_l.z <= 0; + const float side_prob = back ? Tprob : 1 - Tprob; + if (side_prob == 0) + return; + const float dpdf = Eo * fabsf(wi_l.z) * ONEOVERPI; + const Power w = back ? trans_tint : diff_tint; + s->update(w, dpdf, side_prob); +} + +template +BSDL_INLINE_METHOD Imath::V3f +ThinLayerLobe::sample_lobe(float randu, float randv, bool back) const +{ + Imath::V3f wi = sample_cos_hemisphere(randu, randv); + return back ? Imath::V3f(wi.x, wi.y, -wi.z) : wi; +} + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_volume_decl.h b/src/libbsdl/include/BSDL/SPI/bsdf_volume_decl.h new file mode 100644 index 000000000..4ae397596 --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_volume_decl.h @@ -0,0 +1,59 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +template struct VolumeLobe : public Lobe { + using Base = Lobe; + struct Data { + float g1; + float g2; + float blend; + using lobe_type = VolumeLobe; + }; + template + BSDL_INLINE_METHOD VolumeLobe(T* lobe, Imath::V3f wo, float g1, float g2, + float l0, float blend) + : Base(lobe, -wo, 1.0f, l0, true) + , m_g1(CLAMP(g1, -0.99f, 0.99f)) + , m_g2(CLAMP(g2, -0.99f, 0.99f)) + , m_blend(blend) + { + } + template + BSDL_INLINE_METHOD VolumeLobe(T* lobe, const BsdfGlobals& globals, + const Data& data) + : VolumeLobe(lobe, globals.wo, data.g1, data.g2, globals.lambda_0, + data.blend) + { + } + + const char* name() const { return "volume"; } + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f& sample) const; + + // cos_theta is the angle between view and light vectors (both pointing away from shading point) + static BSDL_INLINE_METHOD float phase_func(float costheta, float g); + static BSDL_INLINE_METHOD float phase_func(float costheta, float g1, + float g2, float blend); + static BSDL_INLINE_METHOD Imath::V3f + sample_phase(float g1, float g2, float blend, const Imath::V2f& sample); + +private: + float m_g1, m_g2, m_blend; +}; + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_volume_impl.h b/src/libbsdl/include/BSDL/SPI/bsdf_volume_impl.h new file mode 100644 index 000000000..00ca296e7 --- /dev/null +++ b/src/libbsdl/include/BSDL/SPI/bsdf_volume_impl.h @@ -0,0 +1,95 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include + +BSDL_ENTER_NAMESPACE + +namespace spi { + +template +BSDL_INLINE_METHOD Sample +VolumeLobe::eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const +{ + // TODO: are we missing a minus sign here? + float OdotI = CLAMP(-wi.z, -1.0f, 1.0f); + float pdf = phase_func(OdotI, m_g1, m_g2, m_blend); + return { wi, Power::UNIT(), pdf, 1.0f }; +} + +template +BSDL_INLINE_METHOD Sample +VolumeLobe::sample_impl(const Imath::V3f& wo, + const Imath::V3f& sample) const +{ + Imath::V3f wi = sample_phase(m_g1, m_g2, m_blend, { sample.x, sample.y }); + return eval_impl(wo, wi); +} + +// cos_theta is the angle between view and light vectors (both pointing away from shading point) +template +BSDL_INLINE_METHOD float +VolumeLobe::phase_func(float costheta, float g) +{ + // NOTE: HG phase function has the nice property of having an exact importance + // importance sampling, so it is equal to its pdf + if (g == 0) + return 0.25f * ONEOVERPI; + else { + assert(fabsf(g) < 1.0f); + const float num = 0.25f * ONEOVERPI * (1 - g * g); + const float den = 1 + g * g + 2.0f * g * costheta; + assert(den > 0); + const float r = num / sqrtf(den * den * den); // ^1.5 + assert(r >= 0); + return r; + } +} + +template +BSDL_INLINE_METHOD float +VolumeLobe::phase_func(float costheta, float g1, float g2, + float blend) +{ + const float p1 = phase_func(costheta, g1); + const float p2 = phase_func(costheta, g2); + return LERP(blend, p1, p2); +} + +template +BSDL_INLINE_METHOD Imath::V3f +VolumeLobe::sample_phase(float g1, float g2, float blend, + const Imath::V2f& sample) +{ + float g, x; + if (sample.x < blend) { + g = g2; + x = sample.x / blend; + } else { + g = g1; + x = (sample.x - blend) / (1 - blend); + } + + float cosTheta; + if (fabsf(g) < 1e-3f) { + // avoid the singularity at g=0 and just use uniform sampling + cosTheta = 1 - 2 * x; + } else { + float k = (1 - g * g) / (1 - g + 2 * g * x); + cosTheta = (1 + g * g - k * k) / (2 * g); + } + float sinTheta = sqrtf(MAX(0.0f, 1.0f - cosTheta * cosTheta)); + float phi = 2 * sample.y; + float cosPhi = BSDLConfig::Fast::cospif(phi); + float sinPhi = BSDLConfig::Fast::sinpif(phi); + return { sinTheta * cosPhi, sinTheta * sinPhi, cosTheta }; +} + +} // namespace spi + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/bsdf_decl.h b/src/libbsdl/include/BSDL/bsdf_decl.h new file mode 100644 index 000000000..4ec16c985 --- /dev/null +++ b/src/libbsdl/include/BSDL/bsdf_decl.h @@ -0,0 +1,110 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +BSDL_ENTER_NAMESPACE + +struct Sample { + Imath::V3f wi = Imath::V3f(0); + Power weight = Power::ZERO(); + float pdf = 0; + float roughness = 0; + + BSDL_INLINE_METHOD bool null() const { return pdf == 0; } + BSDL_INLINE_METHOD void update(Power ow, float opdf, float cpdf); + static BSDL_INLINE_METHOD float stretch(float x, float min, float length); +}; + +struct LayeredData { + const void* closure; +}; + +struct BsdfGlobals { + BSDL_INLINE_METHOD BsdfGlobals(const Imath::V3f& wo, const Imath::V3f& Nf, + const Imath::V3f& Ngf, bool backfacing, + float path_roughness, float outer_ior, + float lambda_0) + : wo(wo) + , Nf(Nf) + , Ngf(Ngf) + , backfacing(backfacing) + , path_roughness(path_roughness) + , outer_ior(outer_ior) + , lambda_0(lambda_0) + { + } + struct Filter { + template + BSDL_INLINE_METHOD Power eval(const T& lobe, const Imath::V3f& wo, + const Imath::V3f& Nf, + const Imath::V3f& wi) const; + template + BSDL_INLINE_METHOD Power eval(const T& lobe, const Imath::V3f& wo, + const Imath::V3f& Nf, + const Imath::V3f& Ngf, + const Imath::V3f& wi) const; + + float bump_alpha2 = 0; + Power sigma_t = Power::ZERO(); + ThinFilm thinfilm = {}; + float lambda_0 = 0; + }; + + BSDL_INLINE_METHOD Filter get_sample_filter(const Imath::V3f& N, + bool bump_shadow) const; + static BSDL_INLINE_METHOD Imath::V3f visible_normal(const Imath::V3f& wo, + const Imath::V3f& Ngf, + const Imath::V3f& N); + BSDL_INLINE_METHOD Imath::V3f visible_normal(const Imath::V3f& N) const; + BSDL_INLINE_METHOD float regularize_roughness(float roughness) const; + BSDL_INLINE_METHOD float relative_eta(float IOR) const + { + return IOR / outer_ior; + } + BSDL_INLINE_METHOD Power wave(const Imath::C3f& c) const; + + Imath::V3f wo; + Imath::V3f Nf; + Imath::V3f Ngf; + bool backfacing; + float path_roughness; + float outer_ior; + float lambda_0; +}; + +template struct Lobe : public BSDF_ROOT { + using Sample = bsdl::Sample; // Prevent leaking from BSDF_ROOT + + template + BSDL_INLINE_METHOD Lobe(T* child, const Imath::V3f& Z, float r, float l0, + bool tr); + template + BSDL_INLINE_METHOD Lobe(T* child, const Imath::V3f& Z, const Imath::V3f& X, + float r, float l0, bool tr); + + BSDL_INLINE_METHOD void set_absorption(const Power a) + { + sample_filter.sigma_t = a; + } + BSDL_INLINE_METHOD void set_thinfilm(const ThinFilm& f) + { + sample_filter.thinfilm = f; + } + + Frame frame; + typename BsdfGlobals::Filter sample_filter; +}; + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/bsdf_impl.h b/src/libbsdl/include/BSDL/bsdf_impl.h new file mode 100644 index 000000000..32e64d657 --- /dev/null +++ b/src/libbsdl/include/BSDL/bsdf_impl.h @@ -0,0 +1,188 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include +#include +#include + +BSDL_ENTER_NAMESPACE + +// Update the sum in w (eval / pdf) so it includes ow and opdf from a different +// evaluation. cpdf is the probability of the new contribution (choice pdf). +BSDL_INLINE_METHOD void +Sample::update(Power ow, float opdf, float cpdf) +{ + assert(pdf >= 0); + assert(opdf >= 0); + assert(cpdf >= 0); + assert(cpdf <= 1); + + if (cpdf > PDF_MIN) { + opdf *= cpdf; + ow *= 1 / cpdf; + if (opdf == pdf) + weight = LERP(0.5f, weight, ow); + else if (opdf < pdf) + weight = LERP(1 / (1 + opdf / pdf), ow, weight); + else + weight = LERP(1 / (1 + pdf / opdf), weight, ow); + pdf += opdf; + } + + assert(pdf >= 0); +} + +BSDL_INLINE_METHOD float +Sample::stretch(float x, float min, float length) +{ + assert(x >= min); + assert(length > 0.0f); + return std::min((x - min) / length, ALMOSTONE); +} + +// Fix the shading normal for the viewing direction Rd. It returns a normal as +// close as possible to N without pointing away from the viewer. +BSDL_INLINE_METHOD Imath::V3f +BsdfGlobals::visible_normal(const Imath::V3f& wo, const Imath::V3f& Ngf, + const Imath::V3f& N) +{ + // assert(wo.dot(Ngf) >= 0.0f); + // Normal visible to the viewer? + if (wo.dot(N) > 0.0f) + return N; // Early exit to avoid wasting cycles + + // Normal of plane containing wo and N + Imath::V3f V = wo.cross(N); + if (MAX_ABS_XYZ(V) < 1e-4f) { + // Degenerate case, we fail to fix the normal, use the original one + V = wo.cross(Ngf); + if (MAX_ABS_XYZ(V) < 1e-4f) { + // Ngf must be too close to wo, pick some V orthogonal to wo, + // this is borrowed from the frame function + const float s = copysignf(1.0f, wo.z); + const float a = -1.0f / (s + wo.z); + V = { wo.x * wo.y * a, s + SQR(wo.y) * a, -wo.y }; + } else + V.normalize(); + } else + V.normalize(); + + // We just want a normal with a positive dot product, we take the one in + // the limit and turn it a bit towards the viewer. + return (V.cross(wo) + 1e-4f * wo).normalized(); +} + +BSDL_INLINE_METHOD Imath::V3f +BsdfGlobals::visible_normal(const Imath::V3f& N) const +{ + return BsdfGlobals::visible_normal(wo, Ngf, N); +} + +template +BSDL_INLINE_METHOD Power +BsdfGlobals::Filter::eval(const T& lobe, const Imath::V3f& wo, + const Imath::V3f& Nf, const Imath::V3f& wi) const +{ + constexpr auto fast_exp = BSDLConfig::Fast::expf; + + Power filter = Power(1, lambda_0); + if (bump_alpha2 > 0.0f) { + // This shadowing function is based in the GGX microfacet model + // I'm commenting out the outgoing shadow part intentionally so we + // don't change the look too much. That makes it non-symmetrical. + float light_cos = wi.dot(Nf); + float t = copysignf(SQR(light_cos), light_cos); + if (t > 0.0f /* && s > 0.0f */) { + float G1 = 2.0f + / (1.0f + sqrtf(1.0f + bump_alpha2 * (1.0f - t) / t)); + //float G2 = 2.0f / (1.0f + sqrtf(1.0f + bump_alpha2 * (1.0f - s)/s)); + filter *= G1 /* * G2 */; + } else + return Power::ZERO(); + } + if (sigma_t.max_abs() > 0) { + // Hacky way of receiving ignore_clearcoat_fix flag, since we don't + // have access to options here. + const bool legacy_absorption = sigma_t.min(1) < 0.0f; + // Only account for the incoming absorption, the one for wo is already + // applied via filter_o(). Unless ignore_clearcoat_fix is enabled. + const float dist + = 1 / fabsf(lobe.frame.Z.dot(wi)) + + (legacy_absorption ? 1 / fabsf(lobe.frame.Z.dot(wo)) : 0); + filter + *= Power([&](int i) { return fast_exp(-fabsf(sigma_t[i]) * dist); }, + lambda_0); + } + if (lambda_0 == 0) { + // TODO: how does this work when spectral? + Imath::C3f f = thinfilm.get(wo, wi, lobe.roughness()); + filter[0] *= f.x; + filter[1] *= f.y; + filter[2] *= f.z; + } + return filter; +} + +template +BSDL_INLINE_METHOD Power +BsdfGlobals::Filter::eval(const T& lobe, const Imath::V3f& wo, + const Imath::V3f& Nf, const Imath::V3f& Ngf, + const Imath::V3f& wi) const +{ + if (!lobe.transmissive() && Ngf.dot(wi) <= 0) + return Power::ZERO(); + return eval(lobe, wo, Nf, wi); +} + +BSDL_INLINE_METHOD float +BsdfGlobals::regularize_roughness(float roughness) const +{ + const float roughness_product = 1.0f - path_roughness; + return 1.0f - (1.0f - roughness) * roughness_product; +} + +BSDL_INLINE_METHOD Power +BsdfGlobals::wave(const Imath::C3f& c) const +{ + return Power(c, lambda_0); +} + +BSDL_INLINE_METHOD BsdfGlobals::Filter +BsdfGlobals::get_sample_filter(const Imath::V3f& N, bool bump_shadow) const +{ + const float cosNNs = N.dot(Nf); + float bump_alpha2 = 0; + if (bump_shadow && cosNNs < (1.0f - 1e-4f)) { + // If the shading normal differs from Nf, compute a plausible + // roughness for a normal distribution assuming the tangent is at 2 + // standard deviations. Knowing thar GGX tangent variance is about + // 2 alpha^2 we get this mappinga: + bump_alpha2 = CLAMP(0.125f * (1 - SQR(cosNNs)) / SQR(cosNNs), 0.0f, + 1.0f); + } + return { bump_alpha2, Power::ZERO(), {}, lambda_0 }; +} + +template +template +BSDL_INLINE_METHOD +Lobe::Lobe(T* child, const Imath::V3f& Z, float r, float l0, bool tr) + : BSDF_ROOT(child, r, l0, tr), frame(Z) +{ +} + +template +template +BSDL_INLINE_METHOD +Lobe::Lobe(T* child, const Imath::V3f& Z, const Imath::V3f& X, + float r, float l0, bool tr) + : BSDF_ROOT(child, r, l0, tr), frame(Z, X) +{ +} + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/cdf_decl.h b/src/libbsdl/include/BSDL/cdf_decl.h new file mode 100644 index 000000000..967f63a3f --- /dev/null +++ b/src/libbsdl/include/BSDL/cdf_decl.h @@ -0,0 +1,113 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include + +BSDL_ENTER_NAMESPACE + +template +BSDL_INLINE F +sample_cdf(const F* data, unsigned int n, F x, unsigned int* idx, F* pdf) +{ + assert(x >= 0); + assert(x < 1); + *idx = static_cast(asl::upper_bound(data, data + n, x) + - data); + assert(*idx < n); + assert(x < data[*idx]); + F scaled_sample; + if (*idx == 0) { + *pdf = data[0]; + scaled_sample = Sample::stretch(x, static_cast(0), data[0]); + } else { + assert(x >= data[*idx - 1]); + *pdf = data[*idx] - data[*idx - 1]; + scaled_sample = Sample::stretch(x, data[*idx - 1], *pdf); + } + return scaled_sample; +} + +// For small fixed size CDFs that we hope can fit in registers and where we +// avoid any random access. +template struct StaticCdf { + // To avoid a dynamic search with a for loop that can't be unrolled, we + // implement the binary search with a recursive template. + struct result { + int idx; + float lo, hi; + }; + // Recursive rule + template struct searcher { + // Search by a random number r + BSDL_INLINE_METHOD result operator()(const float* cdf, float r) const + { + constexpr int H = M >> 1; + return r < cdf[S + H - 1] ? searcher()(cdf, r) + : searcher()(cdf, r); + } + // Search by index, equivalent to [i] but trading random access for + // small branches + BSDL_INLINE_METHOD result operator()(const float* cdf, int i) const + { + constexpr int H = M >> 1; + return i < S + H ? searcher()(cdf, i) + : searcher()(cdf, i); + } + }; + // Basic cases + template struct searcher { + BSDL_INLINE_METHOD result operator()(const float* cdf, float r) const + { + return { S, S ? cdf[S - 1] : 0.0f, cdf[S] }; + } + BSDL_INLINE_METHOD result operator()(const float* cdf, int i) const + { + return { S, S ? cdf[S - 1] : 0.0f, cdf[S] }; + } + }; + + BSDL_INLINE_METHOD float build() + { + // These loops are easily unrolled + for (int i = 1; i < N; ++i) + cdf[i] += cdf[i - 1]; + const float total = cdf[N - 1]; + if (total > 0) { + for (int i = 0; i < N; ++i) + cdf[i] /= total; + // Watch out for fast math crushing denormals and not + // dividing correctly. + const float top = cdf[N - 1]; + for (int i = N - 1; i >= 0 && cdf[i] == top; --i) + cdf[i] = 1; + } + return total; + } + BSDL_INLINE_METHOD float sample(float x, int* idx, float* pdf) + { + // We have to do it this way, get idx, lo and hi from the search because + // if we get only idx and fetch lo and hi from it, that's random access + // and CUDA sends the whole CDF to local memory. + auto res = searcher<0, N>()(cdf, x); + *idx = res.idx; + *pdf = res.hi - res.lo; + return Sample::stretch(x, res.lo, *pdf); + } + BSDL_INLINE_METHOD float pdf(int i) const + { + auto res = searcher<0, N>()(cdf, i); + return res.hi - res.lo; + } + // These can potentially produce random access if not called with constants + // or unrollable loop variables. + BSDL_INLINE_METHOD float& operator[](int i) { return cdf[i]; } + BSDL_INLINE_METHOD const float& operator[](int i) const { return cdf[i]; } + + float cdf[N]; +}; + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/config.h b/src/libbsdl/include/BSDL/config.h new file mode 100644 index 000000000..07839f5e3 --- /dev/null +++ b/src/libbsdl/include/BSDL/config.h @@ -0,0 +1,123 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#ifndef BSDL_NS +# define BSDL_NS bsdl +#endif + +#define BSDL_ENTER_NAMESPACE namespace BSDL_NS { +#define BSDL_LEAVE_NAMESPACE } + +#ifndef BSDL_INLINE_METHOD +# define BSDL_INLINE_METHOD inline +#endif +#ifndef BSDL_INLINE +# define BSDL_INLINE inline +#endif +#ifndef BSDL_DECL +# define BSDL_DECL +#endif +#ifndef BSDL_UNROLL +# define BSDL_UNROLL() _Pragma("unroll") +#endif + +#include +#include + +#ifndef M_PI +# define M_PI 3.1415926535897932 +#endif + +BSDL_ENTER_NAMESPACE + +BSDL_DECL constexpr float EPSILON = 1e-4f; +BSDL_DECL constexpr float PI = float(M_PI); +BSDL_DECL constexpr float ONEOVERPI = 1 / PI; +BSDL_DECL constexpr float ALMOSTONE + = 0.999999940395355224609375f; // Max float < 1.0f +BSDL_DECL constexpr float FLOAT_MIN + = 1.17549435e-38f; // Minimum float normal value +BSDL_DECL constexpr float SQRT2 = 1.4142135623730951f; +BSDL_DECL constexpr float BIG = 1e12f; +BSDL_DECL constexpr float PDF_MIN = 1e-6f; + +struct BSDLDefaultConfig { + struct Fast { + static BSDL_INLINE_METHOD float cosf(float x) { return std::cos(x); } + static BSDL_INLINE_METHOD float sinf(float x) { return std::sin(x); } + static BSDL_INLINE_METHOD void sincosf(float x, float* s, float* c) + { + *s = std::sin(x); + *c = std::cos(x); + } + static BSDL_INLINE_METHOD float sinpif(float x) + { + return std::sin(x * float(M_PI)); + } + static BSDL_INLINE_METHOD float cospif(float x) + { + return std::cos(x * float(M_PI)); + } + static BSDL_INLINE_METHOD float asinf(float x) { return std::asin(x); } + static BSDL_INLINE_METHOD float acosf(float x) { return std::acos(x); } + static BSDL_INLINE_METHOD float atan2f(float x, float y) + { + return std::atan2(x, y); + } + static BSDL_INLINE_METHOD float expf(float x) { return std::exp(x); } + static BSDL_INLINE_METHOD float exp2f(float x) { return std::exp2(x); } + static BSDL_INLINE_METHOD float logf(float x) { return std::log(x); } + static BSDL_INLINE_METHOD float log2f(float x) { return std::log2(x); } + static BSDL_INLINE_METHOD float log1pf(float x) + { + return std::log1p(x); + } + static BSDL_INLINE_METHOD float powf(float x, float y) + { + return x < FLOAT_MIN ? 0 : std::pow(x, y); + } + static BSDL_INLINE_METHOD float erff(float x) { return std::erf(x); } + // No default implementation + // static BSDL_INLINE_METHOD float ierff(float x) { return std::erfinv(x); } + }; + + static constexpr int HERO_WAVELENGTH_CHANNELS = 4; + + enum class ColorSpaceTag { sRGB, ACEScg }; + + static BSDL_INLINE_METHOD ColorSpaceTag current_color_space() + { + return ColorSpaceTag::ACEScg; + } + + struct JakobHanikaLut { + struct Coeff { + static constexpr int N = 3; + static constexpr int NPAD = 4; // waste 1 to get SSE + float c[NPAD]; + }; + static constexpr int RGB_RES = 64; + + float scale[RGB_RES]; + Coeff coeff[3][RGB_RES][RGB_RES][RGB_RES]; + }; + + static BSDL_INLINE_METHOD const JakobHanikaLut* + get_jakobhanika_lut(ColorSpaceTag cs) + { + switch (cs) { + case ColorSpaceTag::ACEScg: return &JH_ACEScg_lut; + case ColorSpaceTag::sRGB: + return nullptr; // not handled by Jakob-Hanika + default: return nullptr; + } + } + + static const JakobHanikaLut JH_ACEScg_lut; +}; + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/jakobhanika_decl.h b/src/libbsdl/include/BSDL/jakobhanika_decl.h new file mode 100644 index 000000000..35bf394c0 --- /dev/null +++ b/src/libbsdl/include/BSDL/jakobhanika_decl.h @@ -0,0 +1,39 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once +#include + +BSDL_ENTER_NAMESPACE + +struct JakobHanikaUpsampler { + struct SigmoidPolynomial { + BSDL_INLINE_METHOD SigmoidPolynomial(float a, float b, float c) + : a(a), b(b), c(c) + { + } + static BSDL_INLINE_METHOD float sigmoid(float x) + { + return !std::isfinite(x) ? (x > 0 ? 1 : 0) + : 0.5f + x / (2 * std::sqrt(1 + x * x)); + } + BSDL_INLINE_METHOD float operator()(float x) const + { + return sigmoid((a * x + b) * x + c); + } + + float a, b, c; + }; + + BSDL_INLINE_METHOD + JakobHanikaUpsampler(const BSDLConfig::JakobHanikaLut* lut) : lut(lut) {} + + BSDL_INLINE_METHOD SigmoidPolynomial lookup(float c_r, float c_g, + float c_b) const; + + const BSDLConfig::JakobHanikaLut* lut; +}; + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/jakobhanika_impl.h b/src/libbsdl/include/BSDL/jakobhanika_impl.h new file mode 100644 index 000000000..17d03a6b7 --- /dev/null +++ b/src/libbsdl/include/BSDL/jakobhanika_impl.h @@ -0,0 +1,94 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once +#include +#include +#include + +BSDL_ENTER_NAMESPACE + +template +BSDL_INLINE int +find_interval(int sz, const Predicate& pred) +{ + int size = sz - 2, first = 1; + while (size > 0) { + // Evaluate predicate at midpoint and update _first_ and _size_ + int half = size >> 1, middle = first + half; + bool predResult = pred(middle); + first = predResult ? middle + 1 : first; + size = predResult ? size - (half + 1) : half; + } + return CLAMP(first - 1, 0, sz - 2); +} + +// Operators so we can LERP them +BSDL_INLINE BSDLConfig::JakobHanikaLut::Coeff +operator*(const BSDLConfig::JakobHanikaLut::Coeff c, float f) +{ + BSDLConfig::JakobHanikaLut::Coeff res; + for (int i = 0; i != BSDLConfig::JakobHanikaLut::Coeff::NPAD; ++i) + res.c[i] = c.c[i] * f; + return res; +} +BSDL_INLINE BSDLConfig::JakobHanikaLut::Coeff +operator*(float f, const BSDLConfig::JakobHanikaLut::Coeff c) +{ + return c * f; +} +BSDL_INLINE BSDLConfig::JakobHanikaLut::Coeff +operator+(const BSDLConfig::JakobHanikaLut::Coeff a, + const BSDLConfig::JakobHanikaLut::Coeff b) +{ + BSDLConfig::JakobHanikaLut::Coeff res; + for (int i = 0; i != BSDLConfig::JakobHanikaLut::Coeff::NPAD; ++i) + res.c[i] = a.c[i] + b.c[i]; + return res; +} + +BSDL_INLINE_METHOD JakobHanikaUpsampler::SigmoidPolynomial +JakobHanikaUpsampler::lookup(float c_r, float c_g, float c_b) const +{ + constexpr int RGB_RES = BSDLConfig::JakobHanikaLut::RGB_RES; + + assert(0 <= c_r && c_r <= 1.0f && 0 <= c_g && c_g <= 1.0f && 0 <= c_b + && c_b <= 1.0f); + + // Find maximum component and compute remapped component values + const int maxc = (c_r > c_g) ? ((c_r > c_b) ? 0 : 2) + : ((c_g > c_b) ? 1 : 2); + + const float z = maxc == 0 ? c_r : (maxc == 1 ? c_g : c_b); + const float x = (maxc == 0 ? c_g : (maxc == 1 ? c_b : c_r)) * (RGB_RES - 1) + / z; + const float y = (maxc == 0 ? c_b : (maxc == 1 ? c_r : c_g)) * (RGB_RES - 1) + / z; + + // Compute integer indices and offsets for coefficient interpolation + const int xi = std::min((int)x, RGB_RES - 2), + yi = std::min((int)y, RGB_RES - 2), + zi = find_interval(RGB_RES, + [&](int i) { return lut->scale[i] < z; }); + const float dx = x - xi, dy = y - yi, + dz = (z - lut->scale[zi]) + / (lut->scale[zi + 1] - lut->scale[zi]); + + // Trilinearly interpolate sigmoid polynomial coefficients + auto co = [&](int dx, int dy, int dz) { + return lut->coeff[maxc][zi + dz][yi + dy][xi + dx]; + }; + + const BSDLConfig::JakobHanikaLut::Coeff c + = LERP(dz, + LERP(dy, LERP(dx, co(0, 0, 0), co(1, 0, 0)), + LERP(dx, co(0, 1, 0), co(1, 1, 0))), + LERP(dy, LERP(dx, co(0, 0, 1), co(1, 0, 1)), + LERP(dx, co(0, 1, 1), co(1, 1, 1)))); + + return SigmoidPolynomial(c.c[0], c.c[1], c.c[2]); +} + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/microfacet_tools_decl.h b/src/libbsdl/include/BSDL/microfacet_tools_decl.h new file mode 100644 index 000000000..6e461dc6f --- /dev/null +++ b/src/libbsdl/include/BSDL/microfacet_tools_decl.h @@ -0,0 +1,130 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include +#include + +BSDL_ENTER_NAMESPACE + +struct GGXDist { + BSDL_INLINE_METHOD GGXDist() : ax(0), ay(0) {} + + BSDL_INLINE_METHOD GGXDist(float rough, float aniso) + : ax(SQR(rough)), ay(ax) + { + assert(rough >= 0 && rough <= 1); + assert(aniso >= 0 && aniso <= 1); + constexpr float ALPHA_MIN = 1e-5f; + ax = std::max(ax * (1 + aniso), ALPHA_MIN); + ay = std::max(ay * (1 - aniso), ALPHA_MIN); + } + + BSDL_INLINE_METHOD float D(const Imath::V3f& Hr) const; + BSDL_INLINE_METHOD float G1(Imath::V3f w) const; + BSDL_INLINE_METHOD float G2_G1(Imath::V3f wi, Imath::V3f wo) const; + BSDL_INLINE_METHOD Imath::V3f sample(const Imath::V3f& wo, float randu, + float randv) const; + + BSDL_INLINE_METHOD float roughness() const { return std::max(ax, ay); } + +private: + float ax, ay; +}; + +template struct TabulatedEnergyCurve { + BSDL_INLINE_METHOD TabulatedEnergyCurve(const float roughness, + const float fresnel_index) + : roughness(roughness), fresnel_index(fresnel_index) + { + } + + BSDL_INLINE_METHOD float interpolate_emiss(int i) const; + BSDL_INLINE_METHOD float get_Emiss_avg() const; + BSDL_INLINE_METHOD float Emiss_eval(float c) const; + +private: + float roughness, fresnel_index; +}; + +// Not a full BxDF, just enough implemented to allow baking tables +template struct MiniMicrofacet { + // describe how tabulation should be done + static constexpr int Nc = 16; + static constexpr int Nr = 16; + static constexpr int Nf = 1; + + static constexpr float get_cosine(int i) + { + // we don't use a uniform spacing of cosines because we want a bit more resolution near 0 + // where the energy compensation tables tend to vary more quickly + return std::max(SQR(float(i) * (1.0f / (Nc - 1))), 1e-6f); + } + + explicit MiniMicrofacet(float rough, float) : d(rough, 0.0f) {} + + BSDL_INLINE_METHOD Sample sample(Imath::V3f wo, float randu, float randv, + float randw) const; + +private: + Dist d; +}; + +namespace spi { + +struct MiniMicrofacetGGX : public MiniMicrofacet { + explicit MiniMicrofacetGGX(float, float rough, float) + : MiniMicrofacet(rough, 0.0f) + { + } + struct Energy { + float data[Nf * Nr * Nc]; + }; + static BSDL_INLINE_METHOD Energy& get_energy(); + + static const char* lut_header() { return "microfacet_tools_luts.h"; } + static const char* struct_name() { return "MiniMicrofacetGGX"; } +}; + +} // namespace spi + +template struct MicrofacetMS { + // describe how tabulation should be done + static constexpr int Nc = 16; + static constexpr int Nr = 16; + static constexpr int Nf = 32; + + static constexpr float get_cosine(int i) + { + // we don't use a uniform spacing of cosines because we want a bit more resolution near 0 + // where the energy compensation tables tend to vary more quickly + return std::max(SQR(float(i) * (1.0f / (Nc - 1))), 1e-6f); + } + static constexpr const char* name() { return "MicrofacetMS"; } + + BSDL_INLINE_METHOD MicrofacetMS() {} + + explicit BSDL_INLINE_METHOD MicrofacetMS(float cosNO, float roughness_index, + float fresnel_index); + BSDL_INLINE_METHOD MicrofacetMS(const GGXDist& dist, const Fresnel& fresnel, + float cosNO, float roughness); + BSDL_INLINE_METHOD Sample eval(Imath::V3f wo, Imath::V3f wi) const; + BSDL_INLINE_METHOD Sample sample(Imath::V3f wo, float randu, float randv, + float randw) const; + + BSDL_INLINE_METHOD const Fresnel& getFresnel() const { return f; } + +private: + BSDL_INLINE_METHOD Power computeFmiss() const; + + GGXDist d; + Fresnel f; + float Eo; + float Eo_avg; +}; + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/microfacet_tools_impl.h b/src/libbsdl/include/BSDL/microfacet_tools_impl.h new file mode 100644 index 000000000..ee91543c7 --- /dev/null +++ b/src/libbsdl/include/BSDL/microfacet_tools_impl.h @@ -0,0 +1,301 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include +#include + +#ifndef BAKE_BSDL_TABLES +# include +#endif + +#include + +BSDL_ENTER_NAMESPACE + +BSDL_INLINE_METHOD float +GGXDist::D(const Imath::V3f& Hr) const +{ + // Have these two multiplied by sinThetaM2 for convenience + const float cosPhi2st2 = SQR(Hr.x / ax); + const float sinPhi2st2 = SQR(Hr.y / ay); + const float cosThetaM2 = SQR(Hr.z); + const float sinThetaM2 = cosPhi2st2 + sinPhi2st2; + return 1.0f / (float(M_PI) * ax * ay * SQR(cosThetaM2 + sinThetaM2)); +} + +BSDL_INLINE_METHOD float +GGXDist::G1(Imath::V3f w) const +{ + assert(w.z > 0); + w = { w.x * ax, w.y * ay, w.z }; + return 2.0f * w.z / (w.z + w.length()); +} + +BSDL_INLINE_METHOD float +GGXDist::G2_G1(Imath::V3f wi, Imath::V3f wo) const +{ + assert(wi.z > 0); + assert(wo.z > 0); + wi = { wi.x * ax, wi.y * ay, wi.z }; + wo = { wo.x * ax, wo.y * ay, wo.z }; + const float nl = wi.length(); + const float nv = wo.length(); + return wi.z * (wo.z + nv) / (wo.z * nl + wi.z * nv); +} + +BSDL_INLINE_METHOD Imath::V3f +GGXDist::sample(const Imath::V3f& wo, float randu, float randv) const +{ + // Much simpler technique from Bruce Walter's tech report on Ellipsoidal NDF's (trac#4776) + const Imath::V3f V = Imath::V3f(ax * wo.x, ay * wo.y, wo.z).normalized(); + // NOTE: the orientation can "flip" as we approach normal incidence, but there doesn't seem to be a good way to solve this + const Imath::V3f T1 = V.z < 0.9999f ? Imath::V3f(V.y, -V.x, 0).normalized() + : Imath::V3f(1, 0, 0); + const Imath::V3f T2 = T1.cross(V); + + // Sample point in circle (concentric mapping) + Imath::V2f p = square_to_unit_disc({ randu, randv }); + // Offset + const float s = 0.5f * (1 + V.z); + const float p2o = s * p.y + (1 - s) * sqrtf(1 - p.x * p.x); + // Calculate third component (unit hemisphere) + const float p3 = sqrtf(std::max(1.0f - SQR(p.x) - SQR(p2o), 0.0f)); + + const Imath::V3f N = p.x * T1 + p2o * T2 + p3 * V; + return Imath::V3f(ax * N.x, ay * N.y, std::max(N.z, 0.0f)).normalized(); +} + +template +BSDL_INLINE_METHOD float +TabulatedEnergyCurve::interpolate_emiss(int i) const +{ + const float* storedE = BSDF::get_energy().data; + // interpolate a custom energy compensation curve for the chosen roughness + float rf = roughness * (BSDF::Nr - 1); + int ra = static_cast(rf); + int rb = std::min(ra + 1, BSDF::Nr - 1); + rf -= ra; + assert(ra >= 0 && ra < BSDF::Nr); + assert(rb >= 0 && rb < BSDF::Nr); + assert(rf >= 0 && rf <= 1); + + if (BSDF::Nf == 1) { + return LERP(rf, storedE[ra * BSDF::Nc + i], storedE[rb * BSDF::Nc + i]); + } else { + // bilinear interpolation for the chosen roughness and fresnel + assert(fresnel_index >= 0); + assert(fresnel_index <= 1); + float ff = fresnel_index * (BSDF::Nf - 1); + int fa = static_cast(ff); + int fb = std::min(fa + 1, BSDF::Nf - 1); + ff -= fa; + + assert(fa >= 0 && fa < BSDF::Nf); + assert(fb >= 0 && fb < BSDF::Nf); + assert(ff >= 0 && ff <= 1); + + return LERP(ff, + LERP(rf, storedE[(fa * BSDF::Nr + ra) * BSDF::Nc + i], + storedE[(fa * BSDF::Nr + rb) * BSDF::Nc + i]), + LERP(rf, storedE[(fb * BSDF::Nr + ra) * BSDF::Nc + i], + storedE[(fb * BSDF::Nr + rb) * BSDF::Nc + i])); + } +} + +template +BSDL_INLINE_METHOD float +TabulatedEnergyCurve::get_Emiss_avg() const +{ + // integrate 2*c*Emiss(c) + // note that the table is not uniformly spaced + // the first entry is a triangle, not a trapezoid, account for it seperately + float cos0 = BSDF::get_cosine(0); + float F0 = cos0 * interpolate_emiss(0); // skip factor of 2 here + float Emiss_avg + = F0 * cos0; // beacuse it cancels out with 0.5 of trapeze area formula + + for (int i = 1; i < BSDF::Nc; i++) { + const float Emiss = interpolate_emiss(i); + float cos1 = BSDF::get_cosine(i); + float F1 = cos1 * Emiss; + Emiss_avg += (F0 + F1) * (cos1 - cos0); + cos0 = cos1; + F0 = F1; + assert(Emiss >= 0); + assert(Emiss <= 1); + } + + assert(Emiss_avg >= 0); + assert(Emiss_avg <= 1); + return Emiss_avg; +} + +template +BSDL_INLINE_METHOD float +TabulatedEnergyCurve::Emiss_eval(float c) const +{ + assert(c >= 0); + + // The get_cosine call may not return uniformly spaced cosines, + // so we need to account for this in our evaluation + + float cos0 = BSDF::get_cosine(0); + if (c <= cos0) { + // lerp to 0 for first bin + float E = interpolate_emiss(0); + assert(E >= 0); + assert(E <= 1); + return E; + } + for (int i = 1; i < BSDF::Nc; i++) { + const float cos1 = BSDF::get_cosine(i); + if (c < cos1) { + float q = (c - cos0) / (cos1 - cos0); + assert(q >= 0); + assert(q <= 1); + float E = LERP(q, interpolate_emiss(i - 1), interpolate_emiss(i)); + assert(E >= 0); + assert(E <= 1); + return E; + } + cos0 = cos1; + } + // just a smidge over 1, just clamp + return interpolate_emiss(BSDF::Nc - 1); +} + +template +BSDL_INLINE_METHOD Sample +MiniMicrofacet::sample(Imath::V3f wo, float randu, float randv, + float randw) const +{ + // compute just enough to get the weight + const Imath::V3f m = d.sample(wo, randu, randv); + if (m.dot(wo) > 0) { + const Imath::V3f wi = reflect(wo, m); + if (wi.z > 0) { + const float weight = d.G2_G1(wi, wo); + // NOTE: we just care about the weight in this context, don't bother computing the PDF + return { wi, Power(weight, 1), 0, d.roughness() }; + } + } + return {}; +} + +template +BSDL_INLINE_METHOD +MicrofacetMS::MicrofacetMS(float cosNO, float roughness_index, + float fresnel_index) + : d(roughness_index, 0), f(Fresnel::from_table_index(fresnel_index)) +{ + TabulatedEnergyCurve curve( + roughness_index, fresnel_index); // Energy compensation helper + Eo = curve.Emiss_eval(cosNO); + Eo_avg = curve.get_Emiss_avg(); +} + +template +BSDL_INLINE_METHOD +MicrofacetMS::MicrofacetMS(const GGXDist& dist, const Fresnel& fresnel, + float cosNO, float roughness) + : d(dist), f(fresnel) +{ + TabulatedEnergyCurve curve( + roughness, + 0.0f); // Energy compensation helper + Eo = curve.Emiss_eval(cosNO); + Eo_avg = curve.get_Emiss_avg(); +} + +template +BSDL_INLINE_METHOD Sample +MicrofacetMS::eval(Imath::V3f wo, Imath::V3f wi) const +{ + const float cosNO = wo.z; + const float cosNI = wi.z; + if (cosNI <= 0 || cosNO <= 0) + return {}; + + // evaluate multiple scattering lobe: + Sample s = { wi, computeFmiss(), Eo * cosNI * ONEOVERPI }; + + // get half vector + Imath::V3f m = (wo + wi).normalized(); + float cosMO = m.dot(wo); + if (cosMO > 0) { + // eq. 20: (F*G*D)/(4*in*on) + // eq. 33: first we calculate D(m) with m=Hr: + const float D = d.D(m); + // eq. 34: now calculate G + const float G1 = d.G1(wo); + // eq. 24 (over the PDF below) + const float out = d.G2_G1(wi, wo); + // convert into pdf of the sampled direction + // eq. 38 - from the visible micronormal distribution in "Importance + // Sampling Microfacet-Based BSDFs using the Distribution of Visible + // Normals", Heitz and d'Eon (equation 2) + float s_pdf = (G1 * D) / (4.0f * cosNO); + // fresnel term between outgoing direction and microfacet + const Power F = f.eval(cosMO); + + // merge specular into + s.update(F * out, s_pdf, 1 - Eo); + } + return s; +} + +template +BSDL_INLINE_METHOD Sample +MicrofacetMS::sample(Imath::V3f wo, float randu, float randv, + float randw) const +{ + const float cosNO = wo.z; + if (cosNO <= 0) + return {}; + + // probability of choosing the energy compensation lobe + const float ec_prob = Eo; + Imath::V3f wi; + if (randu < ec_prob) { + // sample diffuse energy compensation lobe + randu = Sample::stretch(randu, 0.0f, ec_prob); + wi = sample_cos_hemisphere(randu, randv); + } else { + // sample microfacet (half vector) + // generate outgoing direction + wi = reflect(wo, + d.sample(wo, Sample::stretch(randu, ec_prob, 1 - ec_prob), + randv)); + } + // evaluate brdf on outgoing direction + return eval(wo, wi); +} + +template +BSDL_INLINE_METHOD Power +MicrofacetMS::computeFmiss() const +{ + // The following expression was derived after discussions with Stephen Hill. This is the biggest difference + // compared to our earlier implementation. + // The idea is to model the extra bounces using the average fresnel and average energy of a single bounce, and + // being careful to only include the energy _beyond_ the first bounce (ie: not double-counting the single-scattering) + const float Emiss_avg = Eo_avg; + const Power Favg = f.avg(); + assert(Favg.min(1) >= 0); + assert(Favg.max() <= 1); + Power Fmiss( + [&](int i) { + return SQR(Favg[i]) * (1 - Emiss_avg) / (1 - Favg[i] * Emiss_avg); + }, + 1); + assert(Fmiss.min(1) >= 0); + assert(Fmiss.max() <= 1); + return Fmiss; +} + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/params.h b/src/libbsdl/include/BSDL/params.h new file mode 100644 index 000000000..25348b323 --- /dev/null +++ b/src/libbsdl/include/BSDL/params.h @@ -0,0 +1,89 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include + +#include +#include + +BSDL_ENTER_NAMESPACE + +enum class ParamType : uint8_t { + NONE = 0, + VECTOR, + INT, + FLOAT, + COLOR, + STRING, + CLOSURE +}; + +// This can easily be converted to OSL::ClosureParam +struct LobeParam { + ParamType type; + int offset; + const char* key; + int type_size; // redundant? +}; + +template struct ParamTypeOf { + static BSDL_INLINE_METHOD constexpr ParamType get(); +}; + +// clang-format off +template <> BSDL_INLINE_METHOD constexpr ParamType ParamTypeOf::get() { return ParamType::VECTOR; } +template <> BSDL_INLINE_METHOD constexpr ParamType ParamTypeOf::get() { return ParamType::INT; } +template <> BSDL_INLINE_METHOD constexpr ParamType ParamTypeOf::get() { return ParamType::FLOAT; } +template <> BSDL_INLINE_METHOD constexpr ParamType ParamTypeOf::get() { return ParamType::COLOR; } +template <> BSDL_INLINE_METHOD constexpr ParamType ParamTypeOf::get() { return ParamType::STRING; } +template <> BSDL_INLINE_METHOD constexpr ParamType ParamTypeOf::get() { return ParamType::CLOSURE; } +// clang-format on + +// Borrowed from https://gist.github.com/graphitemaster/494f21190bb2c63c5516 a +// handy way of grabbing the offset of a member in a legal way. +// Note the orignal fix doesn't work with multiple inheritance since &derived::member +// returns a pointer like base::*member for inherited members. +template struct offset_in { + template + static BSDL_INLINE_METHOD constexpr size_t of(T1 T2::*member) + { + // When a caller passes &type::foo as member, if foo is in a parent class it + // will resolve as Parent::* (thanks C++) and T2 is NOT the intended struct we + // want. This breaks the offset when using multiple inheritance. + // To prevent the epic failure we cast the member pointer to be in T provided + // explicitly. + //constexpr T object{}; + auto&& object = T(); // Hoping to get a constexpr + return size_t(&(object.*member)) - size_t(&object); + } +}; + +template struct LobeRegistry { + using Data = D; + + struct Entry { + static const unsigned MAX_PARAMS = 64; + + const char* name; + LobeParam params[MAX_PARAMS]; + }; + template + static constexpr LobeParam param(T1 T2::*field, const char* key = nullptr) + { + return { ParamTypeOf::get(), (int)offset_in::of(field), key, + sizeof(T1) }; + } + static LobeParam close() + { + return { ParamType::NONE, sizeof(Data), nullptr, alignof(Data) }; + } +}; + +// This is how to get an entry +// ClosureEntry()(); + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/spectrum_decl.h b/src/libbsdl/include/BSDL/spectrum_decl.h new file mode 100644 index 000000000..39ac18799 --- /dev/null +++ b/src/libbsdl/include/BSDL/spectrum_decl.h @@ -0,0 +1,398 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include +#include +#include + +BSDL_ENTER_NAMESPACE + +struct Power; +struct sRGBColorSpace; +struct ACEScgColorSpace; +struct BypassColorSpace; + +using AbstractColorSpace + = StaticVirtual; + +struct ColorSpace : public AbstractColorSpace { + template + BSDL_INLINE_METHOD ColorSpace(const CS* cs) : AbstractColorSpace(cs) + { + } + + BSDL_INLINE_METHOD Power upsample(const Imath::C3f rgb, + float lambda_0) const; + BSDL_INLINE_METHOD Imath::C3f downsample(const Power wave, + float lambda_0) const; +}; + +struct sRGBColorSpace : public ColorSpace { + BSDL_INLINE_METHOD sRGBColorSpace() : ColorSpace(this) {} + BSDL_INLINE_METHOD Power upsample_impl(const Imath::C3f rgb, + float lambda_0) const; + BSDL_INLINE_METHOD Imath::C3f downsample_impl(const Power wave, + float lambda_0) const; + + static constexpr int BASIS_RES = 81; + +private: + struct Data { + // Basis for RGB -> spectrum conversion. It is a color table because + // they are three curves. Conversion is the linear combination of: + // + // spectrum = R * curve1 + G * curve2 + B * curve3 + // + // The sRGB basis roundtrips very well with its D65 illuminant. From + // Spectral Primary Decomposition for Rendering with sRGB Reflectance + // Agatha Mallett, Cem Yuksel + // https://graphics.geometrian.com/research/spectral-primaries.html + Imath::C3f rgb_basis_sRGB[BASIS_RES]; + }; + + static BSDL_INLINE_METHOD const Data& get_luts(); +}; + +struct ACEScgColorSpace : public ColorSpace { + BSDL_INLINE_METHOD ACEScgColorSpace() : ColorSpace(this) {} + BSDL_INLINE_METHOD Power upsample_impl(const Imath::C3f rgb, + float lambda_0) const; + BSDL_INLINE_METHOD Imath::C3f downsample_impl(const Power wave, + float lambda_0) const; +}; + +struct BypassColorSpace : public ColorSpace { + BSDL_INLINE_METHOD BypassColorSpace() : ColorSpace(this) {} + BSDL_INLINE_METHOD Power upsample_impl(const Imath::C3f rgb, + float lambda_0) const; + BSDL_INLINE_METHOD Imath::C3f downsample_impl(const Power wave, + float lambda_0) const; +}; + +// This struct is a proxy for lookup tables and spectral rendering constants +struct Spectrum { + // Wavelength range for our sampling and tables, in nanometers. + static constexpr int LAMBDA_MIN = 380; + static constexpr int LAMBDA_MAX = 780; + static constexpr int LAMBDA_RANGE = LAMBDA_MAX - LAMBDA_MIN; + // Tables will have entries spaced at this step size, in nanometers. + static constexpr int LAMBDA_STEP = 5; + // And therefore, thips is the table size, with an additional end point. + static constexpr int LAMBDA_RES = LAMBDA_RANGE / LAMBDA_STEP + 1; + + struct Data { + // CIE 1931 observer curves for going from spectrum to XYZ + Imath::C3f xyz_response[LAMBDA_RES]; + // White point illuminants + float D65_illuminant[LAMBDA_RES]; + float D60_illuminant[LAMBDA_RES]; + }; + + static BSDL_INLINE_METHOD float wrap(float lambda) + { + return fmodf(lambda - LAMBDA_MIN, LAMBDA_RANGE) + LAMBDA_MIN; + } + + static BSDL_INLINE_METHOD constexpr Data get_luts_ctxr(); + static BSDL_INLINE_METHOD const Data& get_luts(); + + BSDL_INLINE_METHOD static constexpr float + integrate_illuminant(const float* I, const Imath::C3f* xyz) + { + float s = 0; + for (auto i = 0; i != LAMBDA_RES - 1; ++i) { + const float a = I[i] * xyz[i].y; + const float b = I[i + 1] * xyz[i + 1].y; + s += (a + b) * 0.5f; + } + return s * LAMBDA_RANGE / (LAMBDA_RES - 1); + } + + template + static BSDL_INLINE_METHOD Imath::C3f spec_to_xyz(Power wave, + float lambda_0); + + static BSDL_INLINE_METHOD ColorSpace get_color_space(float lambda_0) + { + if (lambda_0 == 0) + return BypassColorSpace(); + else { + switch (BSDLConfig::current_color_space()) { + case BSDLConfig::ColorSpaceTag::sRGB: return sRGBColorSpace(); + case BSDLConfig::ColorSpaceTag::ACEScg: return ACEScgColorSpace(); + default: return sRGBColorSpace(); + } + } + } + + template + static BSDL_INLINE_METHOD T lookup(float lambda, const T array[LAMBDA_RES]); +}; + +static_assert(sRGBColorSpace::BASIS_RES == Spectrum::LAMBDA_RES); + +// Hero wavelength spectral representation. For every camera ray, a hero wavelength +// is chosen randomly. To reduce color noise, additional wavelengths, equaly spaced +// along the spectrum, are tracked. We use hero + 3, so 4 channels. And Power replaces +// the typical 3 channel RGB color representation. Otherwise is the same operations, +// + - * / that look the same as with old school RGB colors. +// +// Note this type also handles RGB by setting lambda_0 to 0.0, we just zero out +// the extra channels. This way the render can work in both RGB or spectral mode. +struct Power { + // We track 4 floats that fit in a single simd register for CPUs. Always pass + // by value for performance, even GPU. + static constexpr unsigned N = BSDLConfig::HERO_WAVELENGTH_CHANNELS; + // Wavelength spacing of the channels. + static constexpr float HERO_STEP = float(Spectrum::LAMBDA_RANGE) / N; + + // Hero wave length lambda_0 is considered external, tracked by the integrator. + // We only care about channel intensities. If a function needs lambda_0 it is + // passed as an argument. + float data[N]; + + // Basic initialization takes either a float or a lambda function, so the typical + // + // color.r = exp(-t * sigma.r); + // color.g = exp(-t * sigma.g); + // color.b = exp(-t * sigma.b); + // + // becomes + // + // wave = Power([&] (int i) { return exp(-t * sigma[i]); }, lambda_0); + // + // or just wave = Power(0.17f, lambda_0); Where lambda_0 is only used to handle + // the RGB mode. + template + BSDL_INLINE_METHOD constexpr Power(const F& f, float lambda_0) + { + if constexpr (std::is_invocable_r::value) { + BSDL_UNROLL() + for (int i = 0; i != N; ++i) { + data[i] = i < 3 || lambda_0 != 0 ? f(i) : 0; + } + } else { + BSDL_UNROLL() + for (int i = 0; i != N; ++i) + data[i] = f; + if (lambda_0 == 0) + for (int i = 3; i != N; ++i) + data[i] = 0; + } + } + + Power() = default; + Power(const Power& o) = default; + + // RGB to spectrum upsampling, lambda_0 is the hero wavelength (0 means RGB). + BSDL_INLINE_METHOD Power(const Imath::C3f rgb, float lambda_0); + template + BSDL_INLINE_METHOD void update(const F& f, float lambda_0) + { + BSDL_UNROLL() + for (int i = 0; i != N; ++i) + data[i] = i < 3 || lambda_0 != 0 ? f(i, data[i]) : 0; + } + + static constexpr Power ZERO() + { + return Power(0, 1); // Same for RGB or spectral + } + static constexpr Power UNIT() + { + return Power(1, 1); // Watch out RGB use of this + } + + // Convert back to RGB + BSDL_INLINE_METHOD Imath::C3f toRGB(float lambda_0) const; + // From wavelength from to to, including 0.0 for RGB + BSDL_INLINE_METHOD Power resample(float from, float to) const; + + BSDL_INLINE_METHOD float& operator[](int i) { return data[i]; } + BSDL_INLINE_METHOD float operator[](int i) const { return data[i]; } + + BSDL_INLINE_METHOD Power operator*=(const Power o); + BSDL_INLINE_METHOD Power operator*=(float f); + BSDL_INLINE_METHOD Power operator+=(const Power o); + + BSDL_INLINE_METHOD float max() const + { + float m = data[0]; + BSDL_UNROLL() + for (int i = 1; i != N; ++i) + m = std::max(m, data[i]); + return m; + } + BSDL_INLINE_METHOD float max_abs() const + { + float m = fabsf(data[0]); + BSDL_UNROLL() + for (int i = 1; i != N; ++i) + m = std::max(m, fabsf(data[i])); + return m; + } + BSDL_INLINE_METHOD float min(float lambda_0) const + { + float m = data[0]; + BSDL_UNROLL() + for (int i = 1; i != N; ++i) + m = std::min(m, i != 3 || lambda_0 != 0 ? data[i] : m); + return m; + } + BSDL_INLINE_METHOD float sum() const + { + float m = 0; + BSDL_UNROLL() + for (int i = 0; i != N; ++i) + m += data[i]; + return m; + } + BSDL_INLINE_METHOD float avg(float lambda_0) const + { + return sum() / (lambda_0 > 0 ? N : 3); + } + BSDL_INLINE_METHOD float luminance(float lambda_0) const + { + return lambda_0 > 0 + ? avg(lambda_0) + : data[0] * 0.3086f + data[1] * 0.6094f + data[2] * 0.0824f; + } + BSDL_INLINE_METHOD Power clamped(float a, float b) const + { + Power r; + BSDL_UNROLL() + for (int i = 0; i != N; ++i) + r[i] = std::max(a, std::min(b, data[i])); + return r; + } + BSDL_INLINE_METHOD Power scale_clamped(float maxv) const + { + const float scale = 1 / std::max(maxv, max()); + return Power([&](int i) { return data[i] * scale; }, 1); + } + BSDL_INLINE_METHOD bool is_zero(float eps = 0) const + { + for (int i = 0; i != N; ++i) + if (fabsf(data[i]) > eps) + return false; + return true; + } + BSDL_INLINE_METHOD bool is_corrupted() const + { + BSDL_UNROLL() + for (int i = 0; i != N; ++i) + if (!std::isfinite(data[i])) + return true; + return false; + } + BSDL_INLINE_METHOD bool is_illegal() const + { + BSDL_UNROLL() + for (int i = 0; i != N; ++i) + if (!std::isfinite(data[i]) || data[i] < 0) + return true; + return false; + } + BSDL_INLINE_METHOD Power cliped_rgb(float lambda_0) const + { + Power m = *this; + for (int i = lambda_0 > 0 ? N : 3; i != N; ++i) + m.data[i] = 0; + return m; + } +}; + +BSDL_INLINE Power +operator-(Power o) +{ + return Power([&](int i) { return -o[i]; }, 1); +} + +BSDL_INLINE_METHOD Power +operator*(const Power a, const Power o) +{ + Power n; + BSDL_UNROLL() + for (int i = 0; i != Power::N; ++i) + n.data[i] = a.data[i] * o.data[i]; + return n; +} + +BSDL_INLINE_METHOD Power +operator/(const Power a, const Power o) +{ + Power n; + BSDL_UNROLL() + for (int i = 0; i != Power::N; ++i) + n.data[i] = a.data[i] / o.data[i]; + return n; +} + +BSDL_INLINE_METHOD Power +operator*(const Power a, float f) +{ + Power n; + BSDL_UNROLL() + for (int i = 0; i != Power::N; ++i) + n.data[i] = a.data[i] * f; + return n; +} + +BSDL_INLINE_METHOD Power +operator+(const Power a, const Power o) +{ + Power n; + BSDL_UNROLL() + for (int i = 0; i != Power::N; ++i) + n.data[i] = a.data[i] + o.data[i]; + return n; +} + +BSDL_INLINE_METHOD Power +operator-(const Power a, const Power o) +{ + Power n; + BSDL_UNROLL() + for (int i = 0; i != Power::N; ++i) + n.data[i] = a.data[i] - o.data[i]; + return n; +} + +BSDL_INLINE_METHOD Power +Power::operator*=(const Power o) +{ + BSDL_UNROLL() + for (int i = 0; i != N; ++i) + data[i] *= o.data[i]; + return *this; +} + +BSDL_INLINE_METHOD Power +Power::operator*=(float f) +{ + for (int i = 0; i != N; ++i) + data[i] *= f; + return *this; +} + +BSDL_INLINE_METHOD Power +Power::operator+=(const Power o) +{ + BSDL_UNROLL() + for (int i = 0; i != N; ++i) + data[i] += o.data[i]; + return *this; +} + +BSDL_INLINE Power +operator*(float f, const Power o) +{ + return o * f; +} + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/spectrum_impl.h b/src/libbsdl/include/BSDL/spectrum_impl.h new file mode 100644 index 000000000..5882b4699 --- /dev/null +++ b/src/libbsdl/include/BSDL/spectrum_impl.h @@ -0,0 +1,186 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once +#include +#include +#include + +BSDL_ENTER_NAMESPACE + +template +BSDL_INLINE_METHOD T +Spectrum::lookup(float lambda, const T array[LAMBDA_RES]) +{ + const float lambda_wrap = fmodf(std::max(0.0f, lambda - LAMBDA_MIN), + LAMBDA_RANGE); + const int i = roundf(lambda_wrap / LAMBDA_STEP); + return array[i]; + const float lambda_fidx = fmodf(std::max(0.0f, lambda - LAMBDA_MIN), + LAMBDA_RANGE) + / LAMBDA_STEP; + const int lo = std::min(int(lambda_fidx), LAMBDA_RES - 2); + const int hi = lo + 1; + const float mix = lambda_fidx - lo; + return LERP(mix, array[lo], array[hi]); +} + +template +BSDL_INLINE_METHOD Imath::C3f +Spectrum::spec_to_xyz(Power wave, float lambda_0) +{ + constexpr float inv_norm + = 1 + / integrate_illuminant(I == 60 ? get_luts_ctxr().D60_illuminant + : get_luts_ctxr().D65_illuminant, + get_luts_ctxr().xyz_response); + const Data& data = get_luts(); + const float* illuminant = I == 60 ? data.D60_illuminant + : data.D65_illuminant; + + Imath::C3f total(0); + BSDL_UNROLL() + for (int i = 0; i != Power::N; ++i) { + const float lambda = lambda_0 + i * Power::HERO_STEP; + const Imath::C3f power = lookup(lambda, data.xyz_response) + * wave.data[i]; + total += power * (LAMBDA_RANGE * inv_norm / Power::N) + * lookup(lambda, illuminant); + } + return total; +} + +BSDL_INLINE_METHOD +Power::Power(const Imath::C3f rgb, float lambda_0) +{ + ColorSpace cs = Spectrum::get_color_space(lambda_0); + *this = cs.upsample(rgb, lambda_0); +} + +BSDL_INLINE_METHOD Imath::C3f +Power::toRGB(float lambda_0) const +{ + ColorSpace cs = Spectrum::get_color_space(lambda_0); + return cs.downsample(*this, lambda_0); +} + +BSDL_INLINE_METHOD Power +Power::resample(float from, float to) const +{ + if (from == to) + return *this; + Imath::C3f rgb = toRGB(from); + return Power(rgb, to); +} + +BSDL_INLINE_METHOD Power +sRGBColorSpace::upsample_impl(const Imath::C3f rgb, float lambda_0) const +{ + assert(lambda_0 != 0); + const auto& basis = get_luts().rgb_basis_sRGB; + Power w; + BSDL_UNROLL() + for (int i = 0; i != Power::N; ++i) { + const float lambda = lambda_0 + i * Power::HERO_STEP; + const Imath::C3f power = Spectrum::lookup(lambda, basis); + w.data[i] = power.x * rgb.x + power.y * rgb.y + power.z * rgb.z; + } + return w; +} + +BSDL_INLINE_METHOD Imath::C3f +sRGBColorSpace::downsample_impl(const Power wave, float lambda_0) const +{ + assert(lambda_0 != 0); + Imath::C3f total = Spectrum::spec_to_xyz<65>(wave, lambda_0); + Imath::V3f cv = { total.x, total.y, total.z }; + // This is BT709 aka sRGB + Imath::V3f XYZ_to_RGB[3] = { { 3.2405f, -1.5371f, -0.4985f }, + { -0.9693f, 1.8760f, 0.0416f }, + { 0.0556f, -0.2040f, 1.0572f } }; + + return { std::max(0.0f, XYZ_to_RGB[0].dot(cv)), + std::max(0.0f, XYZ_to_RGB[1].dot(cv)), + std::max(0.0f, XYZ_to_RGB[2].dot(cv)) }; +} + +BSDL_INLINE_METHOD Power +ACEScgColorSpace::upsample_impl(const Imath::C3f _rgb, float lambda_0) const +{ + assert(lambda_0 != 0); + const float maxrgb = std::max(_rgb.x, std::max(_rgb.y, _rgb.z)); + const float minrgb = std::min(_rgb.x, std::min(_rgb.y, _rgb.z)); + assert(minrgb >= 0); + if (maxrgb - minrgb < EPSILON) + return Power(maxrgb, lambda_0); + // Over this intensity, very saturated colors start losing saturation + // with the table for ACEScg. We scale down and boost the spectrum. + constexpr float safe = 0.7f; + const float scale = std::max(maxrgb, safe); + Imath::C3f rgb = _rgb * (safe / scale); + auto curve = JakobHanikaUpsampler(BSDLConfig::get_jakobhanika_lut( + BSDLConfig::ColorSpaceTag::ACEScg)) + .lookup(rgb.x, rgb.y, rgb.z); + Power w; + BSDL_UNROLL() + for (int i = 0; i != Power::N; ++i) { + const float lambda = Spectrum::wrap(lambda_0 + i * Power::HERO_STEP); + w.data[i] = curve(lambda) * scale * (1 / safe); + } + return w; +} + +BSDL_INLINE_METHOD Imath::C3f +ACEScgColorSpace::downsample_impl(const Power wave, float lambda_0) const +{ + assert(lambda_0 != 0); + constexpr float JH_range_correction = 1; //400.0f / 470.0f; + Imath::C3f total = Spectrum::spec_to_xyz<60>(wave, lambda_0) + * JH_range_correction; + Imath::V3f cv = { total.x, total.y, total.z }; + // This is ACEScg + Imath::V3f XYZ_to_RGB[3] = { { 1.641023f, -0.324803f, -0.236425f }, + { -0.663663f, 1.615332f, 0.016756f }, + { 0.011722f, -0.008284f, 0.988395f } }; + return { std::max(0.0f, XYZ_to_RGB[0].dot(cv)), + std::max(0.0f, XYZ_to_RGB[1].dot(cv)), + std::max(0.0f, XYZ_to_RGB[2].dot(cv)) }; +} + +BSDL_INLINE_METHOD Power +BypassColorSpace::upsample_impl(const Imath::C3f rgb, float lambda_0) const +{ + assert(lambda_0 == 0); + Power w; + w.data[0] = rgb.x; + w.data[1] = rgb.y; + w.data[2] = rgb.z; + BSDL_UNROLL() + for (int i = 3; i != Power::N; ++i) + w.data[i] = 0; + return w; +} + +BSDL_INLINE_METHOD Imath::C3f +BypassColorSpace::downsample_impl(const Power wave, float lambda_0) const +{ + assert(lambda_0 == 0); + return { wave.data[0], wave.data[1], wave.data[2] }; +} + +BSDL_INLINE_METHOD Power +ColorSpace::upsample(const Imath::C3f rgb, float lambda_0) const +{ + return dispatch([&](auto& cs) { return cs.upsample_impl(rgb, lambda_0); }); +} + +BSDL_INLINE_METHOD Imath::C3f +ColorSpace::downsample(const Power wave, float lambda_0) const +{ + return dispatch( + [&](auto& cs) { return cs.downsample_impl(wave, lambda_0); }); +} + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/spectrum_luts.h b/src/libbsdl/include/BSDL/spectrum_luts.h new file mode 100644 index 000000000..92943f681 --- /dev/null +++ b/src/libbsdl/include/BSDL/spectrum_luts.h @@ -0,0 +1,228 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once +#include + +BSDL_ENTER_NAMESPACE + +BSDL_INLINE_METHOD constexpr Spectrum::Data +Spectrum::get_luts_ctxr() +{ + return { + // XYZ response + { { 0.001368f, 0.000039f, 0.006450f }, + { 0.002236f, 0.000064f, 0.010550f }, + { 0.004243f, 0.000120f, 0.020050f }, + { 0.007650f, 0.000217f, 0.036210f }, + { 0.014310f, 0.000396f, 0.067850f }, + { 0.023190f, 0.000640f, 0.110200f }, + { 0.043510f, 0.001210f, 0.207400f }, + { 0.077630f, 0.002180f, 0.371300f }, + { 0.134380f, 0.004000f, 0.645600f }, + { 0.214770f, 0.007300f, 1.039050f }, + { 0.283900f, 0.011600f, 1.385600f }, + { 0.328500f, 0.016840f, 1.622960f }, + { 0.348280f, 0.023000f, 1.747060f }, + { 0.348060f, 0.029800f, 1.782600f }, + { 0.336200f, 0.038000f, 1.772110f }, + { 0.318700f, 0.048000f, 1.744100f }, + { 0.290800f, 0.060000f, 1.669200f }, + { 0.251100f, 0.073900f, 1.528100f }, + { 0.195360f, 0.090980f, 1.287640f }, + { 0.142100f, 0.112600f, 1.041900f }, + { 0.095640f, 0.139020f, 0.812950f }, + { 0.057950f, 0.169300f, 0.616200f }, + { 0.032010f, 0.208020f, 0.465180f }, + { 0.014700f, 0.258600f, 0.353300f }, + { 0.004900f, 0.323000f, 0.272000f }, + { 0.002400f, 0.407300f, 0.212300f }, + { 0.009300f, 0.503000f, 0.158200f }, + { 0.029100f, 0.608200f, 0.111700f }, + { 0.063270f, 0.710000f, 0.078250f }, + { 0.109600f, 0.793200f, 0.057250f }, + { 0.165500f, 0.862000f, 0.042160f }, + { 0.225750f, 0.914850f, 0.029840f }, + { 0.290400f, 0.954000f, 0.020300f }, + { 0.359700f, 0.980300f, 0.013400f }, + { 0.433450f, 0.994950f, 0.008750f }, + { 0.512050f, 1.000000f, 0.005750f }, + { 0.594500f, 0.995000f, 0.003900f }, + { 0.678400f, 0.978600f, 0.002750f }, + { 0.762100f, 0.952000f, 0.002100f }, + { 0.842500f, 0.915400f, 0.001800f }, + { 0.916300f, 0.870000f, 0.001650f }, + { 0.978600f, 0.816300f, 0.001400f }, + { 1.026300f, 0.757000f, 0.001100f }, + { 1.056700f, 0.694900f, 0.001000f }, + { 1.062200f, 0.631000f, 0.000800f }, + { 1.045600f, 0.566800f, 0.000600f }, + { 1.002600f, 0.503000f, 0.000340f }, + { 0.938400f, 0.441200f, 0.000240f }, + { 0.854450f, 0.381000f, 0.000190f }, + { 0.751400f, 0.321000f, 0.000100f }, + { 0.642400f, 0.265000f, 0.000050f }, + { 0.541900f, 0.217000f, 0.000030f }, + { 0.447900f, 0.175000f, 0.000020f }, + { 0.360800f, 0.138200f, 0.000010f }, + { 0.283500f, 0.107000f, 0.000000f }, + { 0.218700f, 0.081600f, 0.000000f }, + { 0.164900f, 0.061000f, 0.000000f }, + { 0.121200f, 0.044580f, 0.000000f }, + { 0.087400f, 0.032000f, 0.000000f }, + { 0.063600f, 0.023200f, 0.000000f }, + { 0.046770f, 0.017000f, 0.000000f }, + { 0.032900f, 0.011920f, 0.000000f }, + { 0.022700f, 0.008210f, 0.000000f }, + { 0.015840f, 0.005723f, 0.000000f }, + { 0.011359f, 0.004102f, 0.000000f }, + { 0.008111f, 0.002929f, 0.000000f }, + { 0.005790f, 0.002091f, 0.000000f }, + { 0.004109f, 0.001484f, 0.000000f }, + { 0.002899f, 0.001047f, 0.000000f }, + { 0.002049f, 0.000740f, 0.000000f }, + { 0.001440f, 0.000520f, 0.000000f }, + { 0.001000f, 0.000361f, 0.000000f }, + { 0.000690f, 0.000249f, 0.000000f }, + { 0.000476f, 0.000172f, 0.000000f }, + { 0.000332f, 0.000120f, 0.000000f }, + { 0.000235f, 0.000085f, 0.000000f }, + { 0.000166f, 0.000060f, 0.000000f }, + { 0.000117f, 0.000042f, 0.000000f }, + { 0.000083f, 0.000030f, 0.000000f }, + { 0.000059f, 0.000021f, 0.000000f }, + { 0.000042f, 0.000015f, 0.000000f } }, + // D65 illuminant + { 49.9755f, 52.3118f, 54.6482f, 68.7015f, 82.7549f, 87.1204f, 91.486f, + 92.4589f, 93.4318f, 90.057f, 86.6823f, 95.7736f, 104.865f, 110.936f, + 117.008f, 117.41f, 117.812f, 116.336f, 114.861f, 115.392f, 115.923f, + 112.367f, 108.811f, 109.082f, 109.354f, 108.578f, 107.802f, 106.296f, + 104.79f, 106.239f, 107.689f, 106.047f, 104.405f, 104.225f, 104.046f, + 102.023f, 100.0f, 98.1671f, 96.3342f, 96.0611f, 95.788f, 92.2368f, + 88.6856f, 89.3459f, 90.0062f, 89.8026f, 89.5991f, 88.6489f, 87.6987f, + 85.4936f, 83.2886f, 83.4939f, 83.6992f, 81.863f, 80.0268f, 80.1207f, + 80.2146f, 81.2462f, 82.2778f, 80.281f, 78.2842f, 74.0027f, 69.7213f, + 70.6652f, 71.6091f, 72.979f, 74.349f, 67.9765f, 61.604f, 65.7448f, + 69.8856f, 72.4863f, 75.087f, 69.3398f, 63.5927f, 55.0054f, 46.4182f, + 56.6118f, 66.8054f, 65.0941f, 63.3828f }, + // D60 illuminant + { 41.471500f, 44.046250f, 46.621000f, 59.460050f, 72.299100f, + 76.380650f, 80.462200f, 81.698900f, 82.935600f, 80.314750f, + 77.693900f, 86.696600f, 95.699300f, 101.835950f, 107.972600f, + 108.774000f, 109.575400f, 108.673750f, 107.772100f, 108.727900f, + 109.683700f, 106.700800f, 103.717900f, 104.479100f, 105.240300f, + 104.837300f, 104.434300f, 103.480850f, 102.527400f, 104.291650f, + 106.055900f, 104.686600f, 103.317300f, 103.428450f, 103.539600f, + 101.769800f, 100.000000f, 98.375300f, 96.750600f, 96.730800f, + 96.711000f, 93.315000f, 89.919000f, 90.957400f, 91.995800f, + 92.044750f, 92.093700f, 91.366900f, 90.640100f, 88.580050f, + 86.520000f, 87.045700f, 87.571400f, 85.769800f, 83.968200f, + 84.341600f, 84.715000f, 86.099000f, 87.483000f, 85.477800f, + 83.472600f, 78.818050f, 74.163500f, 75.386900f, 76.610300f, + 77.826350f, 79.042400f, 72.253000f, 65.463600f, 69.780600f, + 74.097600f, 76.807900f, 79.518200f, 73.408950f, 67.299700f, + 58.283750f, 49.267800f, 60.076000f, 70.884200f, 69.020300f, + 67.156400f } + }; +} + +BSDL_INLINE_METHOD const sRGBColorSpace::Data& +sRGBColorSpace::get_luts() +{ + static const Data data = { + // sRGB basis + { { 0.327457413827055f, 0.331861713085874f, 0.340680791548052f }, + { 0.323750578270541f, 0.329688187759399f, 0.346561186624852f }, + { 0.313439461251577f, 0.327860021624697f, 0.358700493140351f }, + { 0.288879382755265f, 0.319173580231756f, 0.391947026588195f }, + { 0.239205681158886f, 0.294322583694842f, 0.466471730587333f }, + { 0.189702036890535f, 0.258697064768736f, 0.551600895598602f }, + { 0.121746067959218f, 0.188894319254765f, 0.689359610948928f }, + { 0.074578270669466f, 0.125388381991689f, 0.800033346878607f }, + { 0.0444331586340337f, 0.0786870603106217f, 0.876879780935314f }, + { 0.0289286321285029f, 0.0531432708659453f, 0.917928097443955f }, + { 0.0223166534847512f, 0.0422881460313421f, 0.935395200669632f }, + { 0.0169113072926318f, 0.0333183455029171f, 0.949770347115183f }, + { 0.0141811071179667f, 0.0297559481859724f, 0.95606294480524f }, + { 0.0130531426774873f, 0.0303312505369047f, 0.956615606890316f }, + { 0.011986163627845f, 0.0309885718973007f, 0.957025264931328f }, + { 0.0112887147124048f, 0.0316863551888381f, 0.957024930534713f }, + { 0.0109060664656517f, 0.0346699615029974f, 0.954423972737066f }, + { 0.0104007134810042f, 0.034551957443675f, 0.955047329020204f }, + { 0.0106373602541465f, 0.0406848061948297f, 0.948677833093334f }, + { 0.0109076625337741f, 0.0544600373694056f, 0.934632299842328f }, + { 0.0110327124480988f, 0.0809052874204737f, 0.908061999852269f }, + { 0.0113106565912268f, 0.146348302857044f, 0.842341039463727f }, + { 0.0111546420569403f, 0.379679643296617f, 0.609165715365647f }, + { 0.0101487704062122f, 0.766744268654033f, 0.223106960959533f }, + { 0.00891858211883843f, 0.87621474761337f, 0.114866670291336f }, + { 0.00768557633847106f, 0.918491655613843f, 0.0738227678957437f }, + { 0.00670570828469526f, 0.940655562534437f, 0.0526387287910555f }, + { 0.00599580598764424f, 0.95373188453302f, 0.0402723090168887f }, + { 0.00553725664234189f, 0.961643279840238f, 0.0328194626509591f }, + { 0.00519378424120663f, 0.967200019685078f, 0.0276061959270456f }, + { 0.00502536226522334f, 0.970989746390046f, 0.0239848911270394f }, + { 0.00513636276967508f, 0.972852303563554f, 0.0220113333527922f }, + { 0.00543320026053983f, 0.973116594076444f, 0.0214502052559966f }, + { 0.00581998590243535f, 0.973351069154143f, 0.0208289445095685f }, + { 0.00640057277462412f, 0.973351115544369f, 0.0202483113888087f }, + { 0.00744952868340878f, 0.972261079731725f, 0.0202893914512066f }, + { 0.00858363581937657f, 0.973351021746917f, 0.018065342335913f }, + { 0.0103957624651674f, 0.973148495185693f, 0.0164557422344685f }, + { 0.0135654335386492f, 0.971061306300914f, 0.0153732601340955f }, + { 0.0193845158399742f, 0.966371305955183f, 0.0142441784845517f }, + { 0.0320840712020024f, 0.954941967502548f, 0.0129739615543347f }, + { 0.0743560378459411f, 0.913578989551261f, 0.0120649741345218f }, + { 0.624393724178075f, 0.364348803907687f, 0.0112574781603901f }, + { 0.91831003276872f, 0.0715072425408851f, 0.0101827246716942f }, + { 0.949253030175051f, 0.0412304344713751f, 0.00951653538723741f }, + { 0.958187833329246f, 0.0324238741836685f, 0.00938829272866817f }, + { 0.958187751332698f, 0.0319246297982003f, 0.00988761909067028f }, + { 0.958187625087782f, 0.0312760331730969f, 0.0105363420064589f }, + { 0.955679060771746f, 0.0326303704290574f, 0.0116905688374448f }, + { 0.958006154893429f, 0.0295308721490739f, 0.0124629728871037f }, + { 0.954101573456564f, 0.0315617611702464f, 0.0143366651774203f }, + { 0.947607606237237f, 0.0356742182708204f, 0.0167181753275443f }, + { 0.938681328447549f, 0.0414030053955673f, 0.0199156660750025f }, + { 0.924466682751434f, 0.0506042604489561f, 0.024929056163281f }, + { 0.904606025333056f, 0.0634343003817003f, 0.0319596735860402f }, + { 0.880412198927933f, 0.0789182452939229f, 0.0406695540952484f }, + { 0.8477878731517f, 0.0995427426653747f, 0.0526693824219396f }, + { 0.805779126623019f, 0.125595760093287f, 0.0686251105141947f }, + { 0.752531853871421f, 0.15759091044168f, 0.0898772323000136f }, + { 0.686439396844578f, 0.19539823904421f, 0.118162358926434f }, + { 0.61869457086061f, 0.231474474772178f, 0.149830947442133f }, + { 0.540264443959111f, 0.268852136095262f, 0.190883409341834f }, + { 0.472964416293838f, 0.296029164217928f, 0.231006403025217f }, + { 0.432701596704049f, 0.309754994441945f, 0.257543385422202f }, + { 0.405358045528392f, 0.317815883383822f, 0.276826038721536f }, + { 0.385491834974902f, 0.322990347389898f, 0.291517772810795f }, + { 0.370983584551061f, 0.326353847938009f, 0.302662506083233f }, + { 0.357608701523081f, 0.32914390227898f, 0.313247301302886f }, + { 0.348712800108393f, 0.330808726803682f, 0.320478325124633f }, + { 0.344880119344691f, 0.331482689922243f, 0.323636994707961f }, + { 0.341917877323291f, 0.331984550352389f, 0.3260973088469f }, + { 0.339531092987129f, 0.332341172522545f, 0.328127369340184f }, + { 0.337169503774367f, 0.332912009415539f, 0.329917975958888f }, + { 0.336172018527717f, 0.332919279695214f, 0.330907901216649f }, + { 0.335167443433363f, 0.333027672578856f, 0.331803633095995f }, + { 0.334421625306463f, 0.33317970467326f, 0.332396627255361f }, + { 0.334008760376402f, 0.333247030974549f, 0.332740780726824f }, + { 0.333915792790082f, 0.333259349210601f, 0.332820857081489f }, + { 0.333818454946367f, 0.333275050279383f, 0.332901731283444f }, + { 0.333672774928456f, 0.333294328448732f, 0.333025967488632f }, + { 0.333569513405591f, 0.333309424957775f, 0.333111083081497f } } + }; + return data; +} + +BSDL_INLINE_METHOD const Spectrum::Data& +Spectrum::get_luts() +{ + static Data data = get_luts_ctxr(); + return data; +} + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/static_virtual.h b/src/libbsdl/include/BSDL/static_virtual.h new file mode 100644 index 000000000..125a8e537 --- /dev/null +++ b/src/libbsdl/include/BSDL/static_virtual.h @@ -0,0 +1,484 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include + +// This header defines the class StaticVirtual which is used for dynamic dispatch +// of subclass methods using a switch/case statement instead of virtual methods. +// It is inspired on PBRT4 and the tagged pointers, except we don't use tagged +// pointers, instead the subclass ID is stored by StaticVirtual as a member. When +// defineing a base class the list of possible subclasses is explicitly stated like +// +// struct Base : public StaticVirtual { ... } +// +// Then methods can be dispatched from Base using the dispatch() method. See an +// example at the end of this file. StaticVirtual has a templated constructor, this +// way at construction time we feed it the specific subclass just passing the this +// pointer (see example). + +BSDL_ENTER_NAMESPACE + +// First tool is to get the index of a type within a type list. We use std::tuple +// as a type list, and with this recursive template we can say +// +// Index::value == 1 +// +// to map a type to an index +// +template struct Index; + +template +struct Index> { + static constexpr std::size_t value = 0; +}; + +template +struct Index> { + static constexpr std::size_t value + = 1 + Index>::value; +}; + +// Then we define the Dispatcher struct, which takes an object of type F with a +// templated call() method and calls this method with the class corresponding to +// the requested index. + +template struct Dispatcher; + +// Trivial case, just one subclass A0, disptch is a no brainer +template struct Dispatcher> { + template BSDL_INLINE_METHOD static auto call(size_t index, F f) + { + return f.template call(); + } +}; + +// These macros help us emitting the switch/case statement to save some typing + +#define CASE1(offset) \ + case offset: \ + return f \ + .template call::type>(); +#define CASE2(offset) CASE1(offset) CASE1(offset + 1) +#define CASE4(offset) CASE2(offset) CASE2(offset + 2) +#define CASE8(offset) CASE4(offset) CASE4(offset + 4) +#define CASE16(offset) CASE8(offset) CASE8(offset + 8) +#define CASEDEF(offset) \ + default: \ + return f \ + .template call::type>(); + +// Now we define ARGSXX(D) macros to emit argument lists with a decorator D to +// optionally prefix the arg name. Arguments are called A0, A1, ... AN. This would be +// shorter if we had recursive macros. + +#define ARGS2(D) D(A0), D(A1) +#define ARGS3(D) ARGS2(D), D(A2) +#define ARGS4(D) ARGS3(D), D(A3) +#define ARGS5(D) ARGS4(D), D(A4) +#define ARGS6(D) ARGS5(D), D(A5) +#define ARGS7(D) ARGS6(D), D(A6) +#define ARGS8(D) ARGS7(D), D(A7) +#define ARGS9(D) ARGS8(D), D(A8) +#define ARGS10(D) ARGS9(D), D(A9) +#define ARGS11(D) ARGS10(D), D(A10) +#define ARGS12(D) ARGS11(D), D(A11) +#define ARGS13(D) ARGS12(D), D(A12) +#define ARGS14(D) ARGS13(D), D(A13) +#define ARGS15(D) ARGS14(D), D(A14) +#define ARGS16(D) ARGS15(D), D(A15) +#define ARGS17(D) ARGS16(D), D(A16) +#define ARGS18(D) ARGS17(D), D(A17) +#define ARGS19(D) ARGS18(D), D(A18) +#define ARGS20(D) ARGS19(D), D(A19) +#define ARGS21(D) ARGS20(D), D(A20) +#define ARGS22(D) ARGS21(D), D(A21) +#define ARGS23(D) ARGS22(D), D(A22) +#define ARGS24(D) ARGS23(D), D(A23) +#define ARGS25(D) ARGS24(D), D(A24) +#define ARGS26(D) ARGS25(D), D(A25) +#define ARGS27(D) ARGS26(D), D(A26) +#define ARGS28(D) ARGS27(D), D(A27) +#define ARGS29(D) ARGS28(D), D(A28) +#define ARGS30(D) ARGS29(D), D(A29) +#define ARGS31(D) ARGS30(D), D(A30) +#define ARGS32(D) ARGS31(D), D(A31) + +// We also need helpers to emit the case statements. + +#define CASES2 CASE1(0) CASEDEF(1) +#define CASES3 CASE2(0) CASEDEF(2) +#define CASES4 CASE2(0) CASE1(2) CASEDEF(3) +#define CASES5 CASE4(0) CASEDEF(4) +#define CASES6 CASE4(0) CASE1(4) CASEDEF(5) +#define CASES7 CASE4(0) CASE2(4) CASEDEF(6) +#define CASES8 CASE4(0) CASE2(4) CASE1(6) CASEDEF(7) +#define CASES9 CASE8(0) CASEDEF(8) +#define CASES10 CASE8(0) CASE1(8) CASEDEF(9) +#define CASES11 CASE8(0) CASE2(8) CASEDEF(10) +#define CASES12 CASE8(0) CASE2(8) CASE1(10) CASEDEF(11) +#define CASES13 CASE8(0) CASE4(8) CASEDEF(12) +#define CASES14 CASE8(0) CASE4(8) CASE1(12) CASEDEF(13) +#define CASES15 CASE8(0) CASE4(8) CASE2(12) CASEDEF(14) +#define CASES16 CASE8(0) CASE4(8) CASE2(12) CASE1(14) CASEDEF(15) +#define CASES17 CASE16(0) CASEDEF(16) +#define CASES18 CASE16(0) CASE1(16) CASEDEF(17) +#define CASES19 CASE16(0) CASE2(16) CASEDEF(18) +#define CASES20 CASE16(0) CASE2(16) CASE1(18) CASEDEF(19) +#define CASES21 CASE16(0) CASE4(16) CASEDEF(20) +#define CASES22 CASE16(0) CASE4(16) CASE1(20) CASEDEF(21) +#define CASES23 CASE16(0) CASE4(16) CASE2(20) CASEDEF(22) +#define CASES24 CASE16(0) CASE4(16) CASE2(20) CASE1(22) CASEDEF(23) +#define CASES25 CASE16(0) CASE8(16) CASEDEF(24) +#define CASES26 CASE16(0) CASE8(16) CASE1(24) CASEDEF(25) +#define CASES27 CASE16(0) CASE8(16) CASE2(24) CASEDEF(26) +#define CASES28 CASE16(0) CASE8(16) CASE2(24) CASE1(26) CASEDEF(27) +#define CASES29 CASE16(0) CASE8(16) CASE4(24) CASEDEF(28) +#define CASES30 CASE16(0) CASE8(16) CASE4(24) CASE1(28) CASEDEF(29) +#define CASES31 CASE16(0) CASE8(16) CASE4(24) CASE2(28) CASEDEF(30) +#define CASES32 CASE16(0) CASE8(16) CASE4(24) CASE2(28) CASE1(30) CASEDEF(31) + +// And we are ready to create dispatchers up to 32 entries. We use two decorators: +// one that adds the typename keyword and another one that does nothing. + +#define TYPENAME(X) typename X +#define ARG(X) X + +#define DECL_DISPATCH(_ARGS, _CASES) \ + template \ + struct Dispatcher> { \ + using pack = std::tuple<_ARGS(ARG)>; \ + template \ + static BSDL_INLINE_METHOD auto call(size_t index, F f) \ + { \ + switch (index - BASE) { \ + _CASES \ + } \ + } \ + } + +DECL_DISPATCH(ARGS2, CASES2); +DECL_DISPATCH(ARGS3, CASES3); +DECL_DISPATCH(ARGS4, CASES4); +DECL_DISPATCH(ARGS5, CASES5); +DECL_DISPATCH(ARGS6, CASES6); +DECL_DISPATCH(ARGS7, CASES7); +DECL_DISPATCH(ARGS8, CASES8); +DECL_DISPATCH(ARGS9, CASES9); +DECL_DISPATCH(ARGS10, CASES10); +DECL_DISPATCH(ARGS11, CASES11); +DECL_DISPATCH(ARGS12, CASES12); +DECL_DISPATCH(ARGS13, CASES13); +DECL_DISPATCH(ARGS14, CASES14); +DECL_DISPATCH(ARGS15, CASES15); +DECL_DISPATCH(ARGS16, CASES16); +DECL_DISPATCH(ARGS17, CASES17); +DECL_DISPATCH(ARGS18, CASES18); +DECL_DISPATCH(ARGS19, CASES19); +DECL_DISPATCH(ARGS20, CASES20); +DECL_DISPATCH(ARGS21, CASES21); +DECL_DISPATCH(ARGS22, CASES22); +DECL_DISPATCH(ARGS23, CASES23); +DECL_DISPATCH(ARGS24, CASES24); +DECL_DISPATCH(ARGS25, CASES25); +DECL_DISPATCH(ARGS26, CASES26); +DECL_DISPATCH(ARGS27, CASES27); +DECL_DISPATCH(ARGS28, CASES28); +DECL_DISPATCH(ARGS29, CASES29); +DECL_DISPATCH(ARGS30, CASES30); +DECL_DISPATCH(ARGS31, CASES31); +DECL_DISPATCH(ARGS32, CASES32); + +#define MAX_SWITCH 32 + +// And then if we have more than 32 types we divide them in smaller switches with +// if statements +template +struct Dispatcher> { + template static BSDL_INLINE_METHOD auto call(size_t index, F f) + { + if (index < BASE + MAX_SWITCH) + return Dispatcher>::template call( + index, f); + else + return Dispatcher>::template call< + BASE + MAX_SWITCH>(index, f); + } +}; + +template struct Includes; +template struct Includes { + static constexpr bool value = false; +}; +template +struct Includes { + static constexpr bool value = std::is_same::value + || Includes::value; +}; + +// Finally, yes, we are ready to define our StaticVirtual class +template struct StaticVirtual { + // The subclasses type list + using types = std::tuple; + // A templated constructor to store the type index in idx, we pass a pointer + // to infer the type because the compiler has problems with templated constructors. + template + BSDL_INLINE_METHOD StaticVirtual(const T*) : idx(GET_ID()) + { + } + + // Simple handler for the Dispatch helper that takes a callable F, possible + // lambda function that receives obj cast to the particular type. + template struct Handler { + template BSDL_INLINE_METHOD auto call() + { + return f(*static_cast(obj)); + } + F f; + StaticVirtual* obj; + }; + // const version + template struct ConstHandler { + template BSDL_INLINE_METHOD auto call() + { + return f(*static_cast(obj)); + } + F f; + const StaticVirtual* obj; + }; + + // Actual dispatch function that you can feed a lambda + template BSDL_INLINE_METHOD auto dispatch(F f) + { + Handler h = { f, this }; + return Dispatcher, types>::template call<0>(idx, h); + } + // Const version + template BSDL_INLINE_METHOD auto dispatch(F f) const + { + ConstHandler h = { f, this }; + return Dispatcher, types>::template call<0>(idx, h); + } + // For inspecting a type alone (no object), f should only use its arg type + template + static BSDL_INLINE_METHOD auto dispatch(unsigned idx, F f) + { + StaticVirtual tmp(idx); + return tmp.dispatch(f); + } + + // Static mapping from type to index + template static BSDL_INLINE_METHOD constexpr unsigned GET_ID() + { + constexpr bool included = Includes::value; + static_assert(included, + "Constructed type is not in StaticVirtual declaration"); + if constexpr (included) + return Index::value; + else + return 0; + } + // Dynamic version + unsigned BSDL_INLINE_METHOD get_id() const { return idx; } + +private: + // For static dispatch + BSDL_INLINE_METHOD StaticVirtual(unsigned idx) : idx(idx) {} + + uint16_t idx; +}; + +// Tuples as type packs tools, this one gives you the maximum size +// and align for a collection of types should you put them in a union +template struct SizeAlign; + +template<> struct SizeAlign> { + static constexpr std::size_t size = 0; + static constexpr std::size_t align = 0; +}; + +template +struct SizeAlign> { + using Other = SizeAlign>; + static constexpr std::size_t size = std::max(sizeof(T), Other::size); + static constexpr std::size_t align = std::max(alignof(T), Other::align); +}; + +// And this is a very useful tool to map an std::tuple of types to another with a +// template Filter that maps Filter::ToSomeOtherType. The Filter +// template also needs to define a constexpr bool "keep", which is used to remove +// types from the tuple if set to false. + +template typename Filter, + typename Orig, // Original tuple + typename Dest = std::tuple<>> // Placeholder for the result +struct MapTuple; +// Trivial case, empty tuple +template typename Filter, typename... Ds> +struct MapTuple, std::tuple> { + using type = std::tuple; // We are done, result is whatever is in Ds +}; +// Recursion: there is a list of original types and a list of already +// convrted types +template typename Filter, typename O, typename... Os, + typename... Ds> +struct MapTuple, std::tuple> { + using D = typename Filter::type; // Convert the head of the list + using Grown = + typename std:: // If "keep" append D to Ds, otherwise leave Ds alone + conditional::keep, std::tuple, + std::tuple>::type; + // Recursive "call" to itself with a shorter list of Os... (without the head O) + using type = typename MapTuple, Grown>::type; +}; + +// Just another tool to represent type lists that provides a static method +// ::apply(someobject) to run all the types T through someobject.visit() +template struct TypeList; +template<> struct TypeList<> { + template static void apply(F f) {} +}; +template struct TypeList { + template static void apply(F f) + { + f.template visit(); + TypeList::apply(f); + } +}; + + +BSDL_LEAVE_NAMESPACE + +#if 0 +// Example usage +# include "static_virtual.h" +# include + +struct One; +struct Two; +struct Three; + +struct Base : public StaticVirtual { + template Base(const T* o): StaticVirtual(o) {} + int mymethod(float v)const; +}; +struct One : public Base { + One(): Base(this) {} + int mymethod(float v) const + { + printf("I'm one\n"); + return 1; + } +}; +struct Two : public Base { + Two(): Base(this) {} + int mymethod(float v)const + { + printf("I'm two\n"); + return 2; + } +}; +struct Three : public Base { + Three(): Base(this) {} + int mymethod(float v)const + { + printf("I'm three\n"); + return 3; + } +}; +// Dispatch definition has to go after subclasses declarations +int Base::mymethod(float v)const +{ + return dispatch([&](auto& obj) { return obj.mymethod(v); }); +} + +int main() +{ + + One one; + Two two; + Base* obj = &one; + printf("got %d\n", obj->mymethod(1.0f)); + obj = &two; + printf("got %d\n", obj->mymethod(1.0f)); + return 0; +} +#endif + +// Cleanup macro mess + +#undef CASE1 +#undef CASE2 +#undef CASE4 +#undef CASE8 +#undef CASE16 +#undef CASEDEF +#undef ARGS2 +#undef ARGS3 +#undef ARGS4 +#undef ARGS5 +#undef ARGS6 +#undef ARGS7 +#undef ARGS8 +#undef ARGS9 +#undef ARGS10 +#undef ARGS11 +#undef ARGS12 +#undef ARGS13 +#undef ARGS14 +#undef ARGS15 +#undef ARGS16 +#undef ARGS17 +#undef ARGS18 +#undef ARGS19 +#undef ARGS20 +#undef ARGS21 +#undef ARGS22 +#undef ARGS23 +#undef ARGS24 +#undef ARGS25 +#undef ARGS26 +#undef ARGS27 +#undef ARGS28 +#undef ARGS29 +#undef ARGS30 +#undef ARGS31 +#undef ARGS32 +#undef CASES2 +#undef CASES3 +#undef CASES4 +#undef CASES5 +#undef CASES6 +#undef CASES7 +#undef CASES8 +#undef CASES9 +#undef CASES10 +#undef CASES11 +#undef CASES12 +#undef CASES13 +#undef CASES14 +#undef CASES15 +#undef CASES16 +#undef CASES17 +#undef CASES18 +#undef CASES19 +#undef CASES20 +#undef CASES21 +#undef CASES22 +#undef CASES23 +#undef CASES24 +#undef CASES25 +#undef CASES26 +#undef CASES27 +#undef CASES28 +#undef CASES29 +#undef CASES30 +#undef CASES31 +#undef CASES32 +#undef TYPENAME +#undef ARG +#undef MAX_SWITCH diff --git a/src/libbsdl/include/BSDL/thinfilm_decl.h b/src/libbsdl/include/BSDL/thinfilm_decl.h new file mode 100644 index 000000000..e1bdb33aa --- /dev/null +++ b/src/libbsdl/include/BSDL/thinfilm_decl.h @@ -0,0 +1,27 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +BSDL_ENTER_NAMESPACE + +struct ThinFilm { + float saturation = 0; + float min_thickness; + float max_thickness; + float view_dependence; + float thickness; + int enhanced; + + static BSDL_INLINE_METHOD float interferenceEnergy(float r, float s); + static BSDL_INLINE_METHOD float phase(float n, float d, float sinTheta); + static BSDL_INLINE_METHOD float schlick(float cosTheta, float r); + BSDL_INLINE_METHOD Imath::C3f thinFilmSpectrum(float cosTheta) const; + + BSDL_INLINE_METHOD Imath::C3f + get(const Imath::V3f& wo, const Imath::V3f& wi, float roughness) const; +}; + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/thinfilm_impl.h b/src/libbsdl/include/BSDL/thinfilm_impl.h new file mode 100644 index 000000000..55896bd53 --- /dev/null +++ b/src/libbsdl/include/BSDL/thinfilm_impl.h @@ -0,0 +1,167 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include + +BSDL_ENTER_NAMESPACE + +// We're computing the energy of interference pattern between the directly reflected wave +// and the phase-shifted refracted-reflected wave: +// +// w/pi\int_0^{2*pi/w}((r*sin(wi)+(1-r)*sin(w+s))^2) +// +// where +// w = frequency +// s = phase-shift +// r = amount of light reflected from the upper interface +// +// We're currently making a some assumptions - all light refracted through the upper interface +// of the thin film is reflected by the lower interface. This is wrong, but visually it +// just amounts to a saturation control. +BSDL_INLINE_METHOD float +ThinFilm::interferenceEnergy(float r, float s) +{ + float c = BSDLConfig::Fast::cosf(s); + return 1 + 2 * r * (r + c - r * c - 1); +} + +// compute the phase shift between reflected and refracted-reflected wave +// for an arbitrary viewing angle. +// +BSDL_INLINE_METHOD float +ThinFilm::phase(float n, float d, float sinTheta) +{ + return d * std::sqrt((n - sinTheta) * (n + sinTheta)); +} + +// We need this to compute the reflectance off the outer thin-film interface +BSDL_INLINE_METHOD float +ThinFilm::schlick(float cosTheta, float r) +{ + float c = 1 - cosTheta; + return r + (1 - r) * SQR(SQR(c)) * c; +} + +BSDL_INLINE_METHOD Imath::C3f +ThinFilm::thinFilmSpectrum(float cosTheta) const +{ + cosTheta = CLAMP(cosTheta, 0.0f, 1.0f); + // Compute the amount of light reflected from the top interface, depending + // on viewing angle. We're setting the normal incidence reflectivity to + // 0.5 - it is arbitrary, but suitable, as we need something between + // 0 and 1 to get an interference pattern. + float refl = schlick(cosTheta, 0.5); + + // Make sure max thickness is at least as large as min thickness + const float max_thickness = std::max(this->max_thickness, min_thickness); + // We take the absolute value here since we support negative view_dependence (for + // flipping the rainbow ramp) + const float view_dependence_mix = std::fabs(view_dependence); + + // The thin film range is specified by min and max thickness, assuming an ior = 1 (for convenience). + // If view dependent, we compute the corresponding ior and thickness needed for a + // similar spectrum range for a varying view dependence + const float ratio = CLAMP(min_thickness / max_thickness, 0.f, 1 - EPSILON); + const float n = 1 / std::sqrt(1 - ratio * ratio); // 1 here is the ior + + const float d_view = max_thickness / n; + const float d_normal = LERP(thickness, min_thickness, max_thickness); + + // We support flipping the direction of the ramp when view-dependent, if view + // dependence is negative. + if (view_dependence < 0) + cosTheta = std::sqrt(1 - cosTheta * cosTheta); + + // we're feeding in cosTheta, and not sinTheta as the phase function suggests - it is just + // to make the direction of the view dependent ramp go from normal to grazing angle from left to right + const float sigma_view = phase(n, d_view, cosTheta); + const float sigma_norm = d_normal; + const float sigma = 2 * LERP(view_dependence_mix, sigma_norm, sigma_view); + + Imath::C3f RGBout = { 0, 0, 0 }; + Imath::C3f XYZout = { 0, 0, 0 }; + + auto XYZ_to_RGB = [](const Imath::C3f& xyzCol) -> Imath::C3f { + Imath::V3f xyzVec = { xyzCol.x, xyzCol.y, xyzCol.z }; + + // E - equal energy illuminant + constexpr Imath::V3f redVec = { 2.3706743f, -0.9000405f, -0.4706338f }; + constexpr Imath::V3f grnVec = { -0.5138850f, 1.4253036f, 0.0885814f }; + constexpr Imath::V3f bluVec = { 0.0052982f, -0.0146949f, 1.0093968f }; + + return { std::max(0.f, redVec.dot(xyzVec)), + std::max(0.f, grnVec.dot(xyzVec)), + std::max(0.f, bluVec.dot(xyzVec)) }; + }; + + if (enhanced == 1) { + // Normalization by Sandip Shukla. + auto XYZ_normalize = [](const Imath::C3f& xyzCol) { + return xyzCol + * (1 + / std::max(1e-4f, + sqrtf(xyzCol.x * xyzCol.x + xyzCol.y * xyzCol.y + + xyzCol.z * xyzCol.z))); + }; + // This is incorrect, but usually produces brighter colors and looks better. + // First suggested by Sandip Shukla. + for (int i = 0; i < Spectrum::LAMBDA_RES; ++i) { + float wavelength = Spectrum::LAMBDA_MIN + i * Spectrum::LAMBDA_STEP; + float spectrum = interferenceEnergy(refl, sigma / wavelength); + XYZout += spectrum * Spectrum::get_luts().xyz_response[i]; + } + RGBout = XYZ_to_RGB(XYZ_normalize(XYZout)); + } else if (enhanced == 2) { + // Normalization by Sandip Shukla. + Imath::C3f XYZtotal = { EPSILON, EPSILON, EPSILON }; + for (int i = 0; i < Spectrum::LAMBDA_RES; ++i) { + float wavelength = Spectrum::LAMBDA_MIN + i * Spectrum::LAMBDA_STEP; + float spectrum = interferenceEnergy(refl, sigma / wavelength); + Imath::C3f CIEcolor = Spectrum::get_luts().xyz_response[i]; + XYZout += spectrum * CIEcolor; + XYZtotal += CIEcolor; + } + // normalize spectrum colors + XYZout.x /= XYZtotal.z; + XYZout.y /= XYZtotal.y; + XYZout.z /= XYZtotal.z; + RGBout = XYZ_to_RGB(XYZout); + } else { + auto old_XYZ_normalize = [](const Imath::C3f& xyzCol) { + return xyzCol + * (1 / std::max(1e-4f, xyzCol.x + xyzCol.y + xyzCol.z)); + }; + for (int i = 0; i < Spectrum::LAMBDA_RES; ++i) { + float wavelength = Spectrum::LAMBDA_MIN + i * Spectrum::LAMBDA_STEP; + float spectrum = interferenceEnergy(refl, sigma / wavelength); + XYZout += spectrum * Spectrum::get_luts().xyz_response[i]; + } + RGBout = XYZ_to_RGB(old_XYZ_normalize(XYZout)); + } + + return RGBout; +} + +BSDL_INLINE_METHOD Imath::C3f +ThinFilm::get(const Imath::V3f& wo, const Imath::V3f& wi, float roughness) const +{ + if (saturation == 0.0f) + return { 1, 1, 1 }; + const float GLOSSY_HI = 0.3f; + const float DIFFUSE_LO = 0.6f; + // Apply only to glossy exclusively. + const float saturation = CLAMP(this->saturation, 0.0f, 1.0f); + const float actual_saturation + = saturation * (1 - SMOOTHSTEP(GLOSSY_HI, DIFFUSE_LO, roughness)); + if (actual_saturation == 0.0f) + return { 1, 1, 1 }; + Imath::V3f m = (wi + wo).normalized(); + Imath::C3f c = thinFilmSpectrum(wo.dot(m)); + return LERP(saturation, Imath::C3f(1, 1, 1), c); +} + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/tools.h b/src/libbsdl/include/BSDL/tools.h new file mode 100644 index 000000000..bbb049f0f --- /dev/null +++ b/src/libbsdl/include/BSDL/tools.h @@ -0,0 +1,528 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +#pragma once + +#include +#include + +#include + +BSDL_ENTER_NAMESPACE + +BSDL_INLINE float +MAX_ABS_XYZ(const Imath::V3f& v) +{ + return std::max(fabsf(v.x), std::max(fabsf(v.y), fabsf(v.z))); +} + +BSDL_INLINE float +MAX_RGB(const Imath::C3f& c) +{ + return std::max(c.x, std::max(c.y, c.z)); +} + +BSDL_INLINE float +MAX_ABS_RGB(const Imath::C3f& c) +{ + return std::max(fabsf(c.x), std::max(fabsf(c.y), fabsf(c.z))); +} + +BSDL_INLINE float +MIN_RGB(const Imath::C3f& c) +{ + return std::min(c.x, std::min(c.y, c.z)); +} + +BSDL_INLINE float +AVG_RGB(const Imath::C3f& c) +{ + return (c.x + c.y + c.z) * (1.0f / 3); +} + +template +BSDL_INLINE constexpr T +SQR(T x) +{ + return x * x; +} + +BSDL_INLINE float +CLAMP(float x, float a, float b) +{ + return std::min(std::max(x, a), b); +} + +BSDL_INLINE Imath::C3f +CLAMP(const Imath::C3f& c, float a, float b) +{ + return { CLAMP(c.x, a, b), CLAMP(c.y, a, b), CLAMP(c.z, a, b) }; +} + +template +BSDL_INLINE T +LERP(float f, T a, T b) +{ + f = CLAMP(f, 0, 1); + return (1 - f) * a + f * b; +} + +// Hermite interpolation between 0 and 1 using 't' (0<=t<=1) +template +BSDL_INLINE constexpr T +HERP01(T t) +{ + return t * t * (3 - 2 * t); +} + +template +constexpr T +LINEARSTEP(T lo, T hi, T t) +{ + return CLAMP((t - lo) / (hi - lo), T(0), T(1)); +} + +// RenderMan's smoothstep() function +// return 0 if (t < e0) or 1 if (t > e1) or +// a hermitian interpolation for (e0 < t < e1) +template +BSDL_INLINE constexpr T +SMOOTHSTEP(T e0, T e1, T t) +{ + return (t <= e0) + ? T(0) + : ((t >= e1) ? T(1) + : CLAMP(HERP01((t - e0) / (e1 - e0)), T(0), T(1))); +} + +// Return the sum of a and b but without exceeding smax, given that +// the two operands are in range. This is a smooth alternative to clamp +// and has the following properties: +// +// sum_max(a, b, smax) == sum_max(b, a, smax) +// sum_max(a, b, smax) >= a +// sum_max(a, b, smax) >= b +// sum_max(a, 0, smax) == a +// sum_max(a, b, smax) <= smax +// +// When called like sum_max(x, x, 1.0) is equivalent to 2x - x^2 +BSDL_INLINE float +sum_max(float a, float b, float smax) +{ + const float maxab = std::max(a, b), minab = std::min(a, b); + return maxab + (smax - maxab) * (minab / smax); +} + +// This parametric curve (k) is a tool for adjusting responses from a 0-1 input +// where the parameter k adjusts the shape. +// +// Flat-zero exp()-ish Identity Reverse exp() Flat one +// _______ +// | | / _,--- | +// | | / / | +// _______| ____/ / | | +// +// k = 0 k = 0.1 k = 0.5 k = 0.9 k = 1 +// +// And it transitions smoothly from one extreme to the other with k in [0, 1] +// where k = 0.5 gives you the identity. Also gamma^-1(x, k) == gamma(x, 1 -k) +// +BSDL_INLINE float +bias_curve01(float x, float k) +{ + // From Christophe Schlick. “Fast Alternatives to Perlin’s Bias and Gain Functions”. + // In Graphics Gems IV, Morgan Kaufmann, 1994, pages 401–403. + return x / std::max((1 - x) * (1 / k - 2) + 1, FLOAT_MIN); +} + +// Using the above bias curve, this is a simple sigmoid in [0, 1] +BSDL_INLINE float +gain_curve01(float x, float k) +{ + return x < 0.5f ? 0.5f * bias_curve01(2 * x, k) + : 0.5f * bias_curve01(2 * x - 1, 1 - k) + 0.5f; +} + +// Function approximations below are from +// "Fast Equal-Area Mapping of the (Hemi)Sphere using SIMD" by Petrik Clarberg. +// +// http://fileadmin.cs.lth.se/graphics/research/papers/2008/simdmapping/clarberg_simdmapping08_preprint.pdf +// +// They are handy for the low distortion mapping to and from the unit disc/hemisphere/sphere +BSDL_INLINE float +fast_cos_quadrant(float x) +{ + assert(x >= -2); + assert(x <= 2); + // Coefficients for minimax approximation of cos(x*pi/4), x=[-2,2]. + constexpr float c1 = 0.99998736f; + constexpr float c2 = -0.30837047f; + constexpr float c3 = 0.01578646f; + constexpr float c4 = -0.00029826362f; + + float x2 = x * x; + float cp = c3 + c4 * x2; + cp = c2 + cp * x2; + cp = c1 + cp * x2; + return cp; +} + +BSDL_INLINE float +fast_sin_quadrant(float x) +{ + assert(x >= -2); + assert(x <= 2); + // Coefficients for minimax approximation of sin(x*pi/4), x=[0,2]. + const float s1 = 0.7853975892066955566406250000000000f; + const float s2 = -0.0807407423853874206542968750000000f; + const float s3 = 0.0024843954015523195266723632812500f; + const float s4 = -0.0000341485538228880614042282104492f; + + float x2 = x * x; + float sp = s3 + s4 * x2; + sp = s2 + sp * x2; + sp = s1 + sp * x2; + return sp * x; +} + +BSDL_INLINE float +fast_atan_quadrant(float x) +{ + assert(x >= -1); + assert(x <= 1); + // Coefficients for 6th degree minimax approximation of atan(x)*2/pi, x=[0,1]. + const float t1 = 0.406758566246788489601959989e-5f; + const float t2 = 0.636226545274016134946890922156f; + const float t3 = 0.61572017898280213493197203466e-2f; + const float t4 = -0.247333733281268944196501420480f; + const float t5 = 0.881770664775316294736387951347e-1f; + const float t6 = 0.419038818029165735901852432784e-1f; + const float t7 = -0.251390972343483509333252996350e-1f; + + // Polynomial approximation of atan(x)*2/pi + float phi = t6 + t7 * x; + phi = t5 + phi * x; + phi = t4 + phi * x; + phi = t3 + phi * x; + phi = t2 + phi * x; + phi = t1 + phi * x; + return phi; +} + +BSDL_INLINE Imath::V3f +sample_cos_hemisphere(float randu, float randv) +{ + // stretch unit square + get quadrant + const float a = 2 * randu - 1, qa = fabsf(a); // (a,b) is now on [-1,1]^2 + const float b = 2 * randv - 1, qb = fabsf(b); + // map to radius/angle + const float rad = qa > qb ? qa : qb; + const float phi = qa > qb ? qb / qa : ((qa == qb) ? 1.0f : 2 - qa / qb); + // map to disk + flip back into right quadrant + const float x = copysignf(rad * fast_cos_quadrant(phi), a); + const float y = copysignf(rad * fast_sin_quadrant(phi), b); + assert(rad <= 1); + assert(fabsf(x) <= 1); + assert(fabsf(y) <= 1); + // map to cosine weighted hemisphere + return { x, y, sqrtf(1 - rad * rad) }; +} + +BSDL_INLINE Imath::V3f +sample_uniform_hemisphere(float randu, float randv) +{ + // stretch unit square + get quadrant + const float a = 2 * randu - 1, qa = fabsf(a); // (a,b) is now on [-1,1]^2 + const float b = 2 * randv - 1, qb = fabsf(b); + // map to radius/angle + const float rad = qa > qb ? qa : qb; + const float phi = qa > qb ? qb / qa : ((qa == qb) ? 1.0f : 2 - qa / qb); + // map to disk + flip back into right quadrant + const float x = copysignf(rad * fast_cos_quadrant(phi), a); + const float y = copysignf(rad * fast_sin_quadrant(phi), b); + assert(rad <= 1); + assert(fabsf(x) <= 1); + assert(fabsf(y) <= 1); + // map to uniform hemisphere + const float cos_theta = 1 - rad * rad; + const float sin_theta = sqrtf(2 - rad * rad); + return { sin_theta * x, sin_theta * y, cos_theta }; +} + +BSDL_INLINE Imath::V3f +reflect(const Imath::V3f& E, const Imath::V3f& N) +{ + return N * (2 * N.dot(E)) - E; +} + +BSDL_INLINE Imath::V3f +refract(const Imath::V3f& E, const Imath::V3f& N, float eta) +{ + Imath::V3f R(0.0f); + if (eta == 0) + return R; + + Imath::V3f Nn; + float cosi = E.dot(N), neta; + // check which side of the surface we are on + if (cosi > 0) { + // we are on the outside of the surface, going in + neta = 1 / eta; + Nn = N; + } else { + // we are inside the surface, + cosi = -cosi; + neta = eta; + Nn = -N; + } + + float arg = 1 - (neta * neta * (1 - (cosi * cosi))); + if (arg >= 0) { + float dnp = sqrtf(arg); + float nK = (neta * cosi) - dnp; + R = (E * (-neta) + Nn * nK).normalized(); + } + return R; +} + +BSDL_INLINE Imath::V3f +rotate(const Imath::V3f& v, const Imath::V3f& axis, float angle) +{ + float s = BSDLConfig::Fast::sinf(angle), c = BSDLConfig::Fast::cosf(angle); + return v * c + axis * v.dot(axis) * (1.f - c) + s * axis.cross(v); +} + +BSDL_INLINE float +fresnel_dielectric(float cosi, float eta) +{ + if (eta == 0.0f) + // Early exit for some reflectors that leave eta = 0 + // meaning no fresnel decay + return 1.0f; + // compute fresnel reflectance without explicitly computing + // the refracted direction + if (cosi < 0.0f) + eta = 1.0f / eta; + float c = fabsf(cosi); + float g = eta * eta - 1 + c * c; + if (g > 0) { + g = sqrtf(g); + float A = (g - c) / (g + c); + float B = (c * (g + c) - 1) / (c * (g - c) + 1); + return 0.5f * A * A * (1 + B * B); + } + return 1.0f; // TIR (no refracted component) +} + +BSDL_INLINE float +avg_fresnel_dielectric(float eta) +{ +#if 0 + // A quadrature is appliead to the interval [a, b] where a is the critical + // angle of fresnel (its cosine). And b is always 1.0. + const float a = eta < 1 ? sqrtf((1 - eta) * (1 + eta)) : 0; + // now compute a quadrature for the [a, 1] interval + // average fresnel (max error ~= 0.0009) for eta > 1, but about 0.04 under 1 + // This evaluates the integral F(eta)=2*integrate(fresnel(C, eta)*C,C,0,1) + // using a 4 point gauss-legendre quadrature + // http://www.efunda.com/math/num_integration/findgausslegendre.cfm + const float h = (1 + a) / 2; // center of quadrature + const float r = (1 - a) / 2; // radius + const float xi[2] = { 0.861136311594f, 0.339981043585f }; + const float wi[2] = { 0.347854845137f, 0.652145154863f }; + float q = fresnel_dielectric(r * -xi[0] + h, eta) * ((r * -xi[0] + h) * wi[0]) + + fresnel_dielectric(r * -xi[1] + h, eta) * ((r * -xi[1] + h) * wi[1]) + + fresnel_dielectric(r * xi[1] + h, eta) * ((r * xi[1] + h) * wi[1]) + + fresnel_dielectric(r * xi[0] + h, eta) * ((r * xi[0] + h) * wi[0]); + // The average is cosine weighted. The average of F*x in [0 a) is 1, and q + // is the avg in [a 1), now we need to compute the weighted average of the + // two. Since we weight with cosine, the first interval weights the integral + // of x from 0 to a (a^2/2) divided by the integral of x from 0 to 1 (1/2), + // and the second interval weights the complement. Therefore: + float avg = q * (1.0f - a * a) + a * a; + assert(0 <= avg && avg <= 1.0f); + return avg; +#elif 0 + // much simpler fit computed in mathematica: + // max error for 0<=eta<= 1 is 0.003 + // max error for 1<=eta<=400 is 0.010 + if (eta < 1) + return CLAMP((9.13734f - 0.00419542f * eta - 9.11295f * SQR(eta)) + / (9.16567f - 0.974132f * eta), + 0.0f, 1.0f); + else + return CLAMP((-2.78491f + 2.72524f * eta + 6.55885e-6f * SQR(eta)) + / (10.9957f + 2.7292f * eta), + 0.0f, 1.0f); +#else + // even simpler fit with lower error (computed in mathematica) + // max error for 0<=eta<= 1 is ~0.29% + // max error for 1<=eta<=400 is ~0.65% + if (eta < 1) + return 0.997118f + + eta * (0.1014f + eta * (-0.965241f - eta * 0.130607f)); + else + return (eta - 1) / (4.08567f + 1.00071f * eta); +#endif +} + +// Fast fresnel function for metals +// +// c is the angle cosine +// r is the reflectance +// g is the edge tint +BSDL_INLINE float +fresnel_metal(float c, float r, float g) +{ + // from: "Artist Friendly Metallic Fresnel", Ole Gulbrandsen + // http://jcgt.org/published/0003/04/03/ + const float n = LERP(g, (1 + sqrtf(r)) / (1 - sqrtf(r)), (1 - r) / (1 + r)); + const float k2 = (SQR(n + 1) * r - SQR(n - 1)) / (1 - r); + const float n2 = n * n; + const float c2 = c * c; + const float tnc = 2 * n * c; + + const float rs_num = (n2 + k2) - tnc + c2; + const float rs_den = (n2 + k2) + tnc + c2; + const float rp_num = (n2 + k2) * c2 - tnc + 1; + const float rp_den = (n2 + k2) * c2 + tnc + 1; + + return 0.5f * (rs_num / rs_den + rp_num / rp_den); +} + +BSDL_INLINE Power +fresnel_metal(float c, const Power r, const Power g, float lambda_0) +{ + return Power([&](int i) { return fresnel_metal(c, r[i], g[i]); }, lambda_0); +} + +BSDL_INLINE Power +fresnel_schlick(float c, const Power r, const Power g, float p) +{ + constexpr auto fast_exp2 = BSDLConfig::Fast::exp2f; + constexpr auto fast_log2 = BSDLConfig::Fast::log2f; + + c = std::min(c, 1 - 1e-4f); + p = std::max(p, 1e-4f); + return LERP(fast_exp2(fast_log2(1 - c) / p), r, g); +} + +struct Frame { + // Given a unit vector N, build two arbitrary orthogonal vectors U and V + // The output is guarenteed to form a right handed orthonormal basis. (U x V = N) + static BSDL_INLINE_METHOD std::tuple + ortho_build(const Imath::V3f& Z) + { + // http://jcgt.org/published/0006/01/01/ + // + // Building an Orthonormal Basis, Revisited + // Tom Duff, James Burgess, Per Christensen, Christophe Hery, Andrew Kensler, Max Liani, Ryusuke Villemin + const float s = copysignf(1.0f, Z.z); + const float a = -1.0f / (s + Z.z); + const float b = Z.x * Z.y * a; + Imath::V3f X = { 1.0f + s * Z.x * Z.x * a, s * b, -s * Z.x }; + Imath::V3f Y = { b, s + Z.y * Z.y * a, -Z.y }; + return { X, Y }; + } + BSDL_INLINE_METHOD Frame(const Imath::V3f& Z) : Z(Z) + { + auto XY = ortho_build(Z); + X = std::get<0>(XY); + Y = std::get<1>(XY); + } + // frame with z axis pointing along n and x axis pointing in the same direction as u (but orthogonal) + BSDL_INLINE_METHOD Frame(const Imath::V3f& Z, const Imath::V3f& _X) + : X(_X), Z(Z) + { + if (MAX_ABS_XYZ(X) < 1e-4f) { + // X not provided, pick arbitrary + auto XY = ortho_build(Z); + X = std::get<0>(XY); + Y = std::get<1>(XY); + } else { + Y = Z.cross(X).normalized(); + X = Y.cross(Z); + } + } + // take a world space vector and spin it around to be expressed in the + // coordinate system of this frame + BSDL_INLINE_METHOD Imath::V3f local(const Imath::V3f& a) const + { + return { a.dot(X), a.dot(Y), a.dot(Z) }; + } + BSDL_INLINE_METHOD Imath::V3f world(const Imath::V3f& a) const + { + return { X.x * a.x + Y.x * a.y + Z.x * a.z, + X.y * a.x + Y.y * a.y + Z.y * a.z, + X.z * a.x + Y.z * a.y + Z.z * a.z }; + } + + Imath::V3f X, Y, Z; +}; + +// This transforms points on [0,1]^2 to points on unit disc centered at +// origin. Each "pie-slice" quadrant of square is handled as a separate +// case. The bad floating point cases are all handled appropriately. +// The regions for (a,b) are: +// +// phi = pi/2 +// -----*----- +// |\ /| +// | \ 2 / | +// | \ / | +// phi=pi * 3 * 1 * phi = 0 +// | / \ | +// | / 4 \ | +// |/ \| +// -----*----- +// phi = 3pi/2 +// +// (rnd.x,rnd.y) is a point on [0,1]^2. (x,y) is point on radius 1 disc +// +BSDL_INLINE Imath::V2f +square_to_unit_disc(const Imath::V2f rnd) +{ + // assert(rnd.x >= 0); + // assert(rnd.x <= 1); + // assert(rnd.y >= 0); + // assert(rnd.y <= 1); + // stretch unit square + get quadrant + const float a = 2 * rnd.x - 1, qa = fabsf(a); // (a,b) is now on [-1,1]^2 + const float b = 2 * rnd.y - 1, qb = fabsf(b); + // map to radius/angle + const float rad = qa > qb ? qa : qb; + const float phi = qa > qb ? qb / qa : ((qa == qb) ? 1.0f : 2 - qa / qb); + // map to disk + flip back into right quadrant + const float x = copysignf(rad * fast_cos_quadrant(phi), a); + const float y = copysignf(rad * fast_sin_quadrant(phi), b); + // assert(x >= -1); + // assert(x <= 1); + // assert(y >= -1); + // assert(y <= 1); + return { x, y }; +} + +// Inverse function of the above disk mapping +BSDL_INLINE Imath::V2f +disc_to_unit_square(const Imath::V2f& disc) +{ + const float r = sqrtf(std::min(SQR(disc.x) + SQR(disc.y), 1.0f)); + // compute on quadrant + const float qa = fabsf(disc.x); + const float qb = fabsf(disc.y); + // figure out angle in [0,1] + const float t = qa > qb ? qb / qa : ((qa == qb) ? 1.0f : qa / qb); + const float phi = fast_atan_quadrant(t) * 2; + // Map back to unit square + const float x = copysignf(qa > qb ? r : r * phi, disc.x) * 0.5f + 0.5f; + const float y = copysignf(qa > qb ? r * phi : r, disc.y) * 0.5f + 0.5f; + assert(x >= 0); + assert(x <= 1); + assert(y >= 0); + assert(y <= 1); + return { x, y }; +} + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/src/genluts.cpp b/src/libbsdl/src/genluts.cpp new file mode 100644 index 000000000..b5f0a6ac1 --- /dev/null +++ b/src/libbsdl/src/genluts.cpp @@ -0,0 +1,218 @@ +#define BAKE_BSDL_TABLES 1 + +#define BSDL_UNROLL() // Do nothing + +#include +using BSDLConfig = bsdl::BSDLDefaultConfig; + +#include +#include +#include +#include +#include +#include +#include + +#include "parallel.h" + +#include +#include +#include +#include + +#define BAKE_BSDF_LIST(E) \ + E(spi::MiniMicrofacetGGX) \ + E(spi::PlasticGGX) \ + E(spi::DielectricFront) \ + E(spi::DielectricBack) \ + E(spi::CharlieSheen) \ + E(spi::SheenLTC) \ + E(spi::Thinlayer) + +#define LUT_PLACEHOLDER(type) \ + BSDL_ENTER_NAMESPACE \ + BSDL_INLINE_METHOD type::Energy& type::get_energy() \ + { \ + static Energy energy = {}; \ + return energy; \ + } \ + BSDL_LEAVE_NAMESPACE + +BAKE_BSDF_LIST(LUT_PLACEHOLDER) + +using namespace bsdl; + +BSDL_INLINE uint32_t +ri_LP(uint32_t i) +{ + uint32_t r = 0; + for (uint32_t v = 1U << 31; i; i >>= 1, v |= v >> 1) + if (i & 1) + r ^= v; + return r; +} + +BSDL_INLINE uint32_t +ri_LP_inv(uint32_t i) +{ + uint32_t r = 0; + for (uint32_t v = 3U << 30; i; i >>= 1, v >>= 1) + if (i & 1) + r ^= v; + return r; +} + +BSDL_INLINE Imath::V3f +get_sample(int si, int AA, uint32_t scramble_x, uint32_t scramble_y, + uint32_t scramble_z) +{ + const uint32_t ex = si % AA; + const uint32_t ey = si / AA; + + const uint32_t upper = (ex ^ (scramble_x >> 16)) << 16; + const uint32_t lpUpper = ri_LP(upper) ^ scramble_y; + const uint32_t delta = (ey << 16) ^ (lpUpper & 0xFFFF0000u); + const uint32_t lower = ri_LP_inv(delta); + const uint32_t index = upper | lower; + const uint32_t x = index ^ scramble_x; + const uint32_t y = lpUpper ^ delta; + const float jx = (x & 65535) * (1 / 65536.0f); + const float jy = (y & 65535) * (1 / 65536.0f); + uint32_t rz = scramble_z, ii = index; + for (uint64_t v2 = uint64_t(3) << 62; ii; ii >>= 1, v2 ^= v2 >> 1) + if (ii & 1) + rz ^= uint32_t(v2 >> 31); + + Imath::V3f v = { (ex + jx) / AA, (ey + jy) / AA, rz * 2.3283063e-10f }; + assert(v.x >= 0); + assert(v.x < 1); + assert(v.y >= 0); + assert(v.y < 1); + assert(v.z >= 0); + assert(v.z < 1); + return v; +} + +template +BSDL_INLINE float +compute_E(float cos_theta, const BSDF& bsdf, uint32_t fresnel_index, + uint32_t roughness_index) +{ + auto fasthash64_mix = [](uint64_t h) -> uint64_t { + h ^= h >> 23; + h *= 0x2127599bf4325c37ULL; + h ^= h >> 47; + return h; + }; + auto fasthash64 = + [&](const std::initializer_list buf) -> uint64_t { + const uint64_t m = 0x880355f21e6d1965ULL; + uint64_t h = (buf.size() * sizeof(uint64_t)) * m; + for (const uint64_t v : buf) { + h ^= fasthash64_mix(v); + h *= m; + } + return fasthash64_mix(h); + }; + auto randhash3 = [&](uint32_t x, uint32_t y, uint32_t z) -> uint32_t { + return fasthash64({ (uint64_t(x) << 32) + y, uint64_t(z) }); + }; + constexpr int AA = 128; + constexpr int NUM_SAMPLES = AA * AA; + assert(cos_theta > 0); + const Imath::V3f wo = { sqrtf(1 - SQR(cos_theta)), 0, cos_theta }; + float E = 0; + uint32_t seedx = randhash3(fresnel_index, roughness_index, 0); + uint32_t seedy = randhash3(fresnel_index, roughness_index, 1); + uint32_t seedz = randhash3(fresnel_index, roughness_index, 2); + for (int i = 0; i < NUM_SAMPLES; i++) { + Imath::V3f rnd = get_sample(i, AA, seedx, seedy, seedz); + float out = bsdf.sample(wo, rnd.x, rnd.y, rnd.z).weight.max(); + + // accumulate result progressively to minimize error with large sample counts + E = LERP(1.0f / (1.0f + i), E, out); + } + assert(E >= 0); + assert(E <= 1); + return E; +} + +template +BSDL_INLINE void +bake_emiss_tables(const std::string& output_dir) +{ + float* storedE = BSDF::get_energy().data; + + printf("Generating LUTs for %s ...\n", BSDF::struct_name()); + + parallel_for(0, BSDF::Nf, [&](unsigned f) { + const float fresnel_index = float(f) + * (1.0f / std::max(1, BSDF::Nf - 1)); + for (int r = 0; r < BSDF::Nr; r++) { + const float roughness_index + = BSDF::Nr > 1 ? float(r) * (1.0f / (BSDF::Nr - 1)) : 0.0f; + for (int c = 0; c < BSDF::Nc; c++) { + int idx = f * BSDF::Nr * BSDF::Nc + r * BSDF::Nc + c; + const BSDF bsdf(BSDF::get_cosine(c), roughness_index, + fresnel_index); + storedE[idx] = 1 - compute_E(BSDF::get_cosine(c), bsdf, f, r); + } + } + }); + + std::string out_file = output_dir + "/" + BSDF::lut_header(); + FILE* outf = fopen(out_file.c_str(), "wb"); + if (!outf) { + printf("Failed to open %s for writing\n", out_file.c_str()); + exit(-1); + } + + fprintf(outf, "#pragma once\n\n"); + fprintf(outf, "BSDL_ENTER_NAMESPACE\n\n"); + fprintf(outf, "namespace spi {\n\n"); + fprintf(outf, "BSDL_INLINE_METHOD %s::Energy& %s::get_energy()\n", + BSDF::struct_name(), BSDF::struct_name()); + fprintf(outf, "{\n"); + fprintf(outf, " static Energy energy = {{\n"); + for (int f = 0, idx = 0; f < BSDF::Nf; f++) { + for (int r = 0; r < BSDF::Nr; r++) { + fprintf(outf, " "); + for (int c = 0; c < BSDF::Nc; c++, idx++) + if (storedE[idx] == int(storedE[idx])) + fprintf(outf, " %12d.0f,", int(storedE[idx])); + else + fprintf(outf, " %14.9gf,", storedE[idx]); + fprintf(outf, "\n"); + } + } + fprintf(outf, " }};\n"); + fprintf(outf, " return energy;\n"); + fprintf(outf, "}\n\n"); + fprintf(outf, "} // namespace spi \n\n"); + fprintf(outf, "BSDL_LEAVE_NAMESPACE\n"); + fclose(outf); + printf("Wrote LUTs to %s\n", out_file.c_str()); +} + +int +main(int argc, const char** argv) +{ + if (argc < 2) { + printf("Must provide output dir for headers\n"); + return -1; + } + +#define DECLARE_DUMMY(type) type(0, 0, 0), + + std::tuple bsdf_list { BAKE_BSDF_LIST(DECLARE_DUMMY) }; + + std::apply( + [&](auto... args) { + (bake_emiss_tables< + typename std::remove_reference::type>(argv[1]), + ...); + }, + bsdf_list); + + return 0; +} diff --git a/src/libbsdl/src/jakobhanika_luts.cpp b/src/libbsdl/src/jakobhanika_luts.cpp new file mode 100644 index 000000000..b70b4f5de --- /dev/null +++ b/src/libbsdl/src/jakobhanika_luts.cpp @@ -0,0 +1,799 @@ +// This code was contributed to mitsuba by the authors of "A Low-Dimensional +// Function Space for Efficient Spectral Upsampling" using TBB for parallel +// execution, then adapted to PBRT using its own thread pool. This version uses +// parallel std::for_each. +// +// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys. +// The pbrt source code is licensed under the Apache License, Version 2.0. +// SPDX: Apache-2.0 + +#include + +#include +#include +#include +#include + +#include "parallel.h" + +/** + * This file contains: + * + * 1. CIE 1931 curves at sampled at 5nm intervals + * + * 2. CIE D65 and D50 spectra sampled at 5nm intervals. + * Both are normalized to have unit luminance. + * + * 3. XYZ <-> sRGB conversion matrices + * XYZ <-> ProPhoto RGB conversion matrices + * + * 4. A convenience function "cie_interp" to access the discretized + * data at arbitrary wavelengths (with linear interpolation) +} + */ +#define CIE_LAMBDA_MIN 360.0 +#define CIE_LAMBDA_MAX 830.0 +#define CIE_SAMPLES 95 + +const double cie_x[CIE_SAMPLES] + = { 0.000129900000, 0.000232100000, 0.000414900000, 0.000741600000, + 0.001368000000, 0.002236000000, 0.004243000000, 0.007650000000, + 0.014310000000, 0.023190000000, 0.043510000000, 0.077630000000, + 0.134380000000, 0.214770000000, 0.283900000000, 0.328500000000, + 0.348280000000, 0.348060000000, 0.336200000000, 0.318700000000, + 0.290800000000, 0.251100000000, 0.195360000000, 0.142100000000, + 0.095640000000, 0.057950010000, 0.032010000000, 0.014700000000, + 0.004900000000, 0.002400000000, 0.009300000000, 0.029100000000, + 0.063270000000, 0.109600000000, 0.165500000000, 0.225749900000, + 0.290400000000, 0.359700000000, 0.433449900000, 0.512050100000, + 0.594500000000, 0.678400000000, 0.762100000000, 0.842500000000, + 0.916300000000, 0.978600000000, 1.026300000000, 1.056700000000, + 1.062200000000, 1.045600000000, 1.002600000000, 0.938400000000, + 0.854449900000, 0.751400000000, 0.642400000000, 0.541900000000, + 0.447900000000, 0.360800000000, 0.283500000000, 0.218700000000, + 0.164900000000, 0.121200000000, 0.087400000000, 0.063600000000, + 0.046770000000, 0.032900000000, 0.022700000000, 0.015840000000, + 0.011359160000, 0.008110916000, 0.005790346000, 0.004109457000, + 0.002899327000, 0.002049190000, 0.001439971000, 0.000999949300, + 0.000690078600, 0.000476021300, 0.000332301100, 0.000234826100, + 0.000166150500, 0.000117413000, 0.000083075270, 0.000058706520, + 0.000041509940, 0.000029353260, 0.000020673830, 0.000014559770, + 0.000010253980, 0.000007221456, 0.000005085868, 0.000003581652, + 0.000002522525, 0.000001776509, 0.000001251141 }; + +const double cie_y[CIE_SAMPLES] + = { 0.000003917000, 0.000006965000, 0.000012390000, 0.000022020000, + 0.000039000000, 0.000064000000, 0.000120000000, 0.000217000000, + 0.000396000000, 0.000640000000, 0.001210000000, 0.002180000000, + 0.004000000000, 0.007300000000, 0.011600000000, 0.016840000000, + 0.023000000000, 0.029800000000, 0.038000000000, 0.048000000000, + 0.060000000000, 0.073900000000, 0.090980000000, 0.112600000000, + 0.139020000000, 0.169300000000, 0.208020000000, 0.258600000000, + 0.323000000000, 0.407300000000, 0.503000000000, 0.608200000000, + 0.710000000000, 0.793200000000, 0.862000000000, 0.914850100000, + 0.954000000000, 0.980300000000, 0.994950100000, 1.000000000000, + 0.995000000000, 0.978600000000, 0.952000000000, 0.915400000000, + 0.870000000000, 0.816300000000, 0.757000000000, 0.694900000000, + 0.631000000000, 0.566800000000, 0.503000000000, 0.441200000000, + 0.381000000000, 0.321000000000, 0.265000000000, 0.217000000000, + 0.175000000000, 0.138200000000, 0.107000000000, 0.081600000000, + 0.061000000000, 0.044580000000, 0.032000000000, 0.023200000000, + 0.017000000000, 0.011920000000, 0.008210000000, 0.005723000000, + 0.004102000000, 0.002929000000, 0.002091000000, 0.001484000000, + 0.001047000000, 0.000740000000, 0.000520000000, 0.000361100000, + 0.000249200000, 0.000171900000, 0.000120000000, 0.000084800000, + 0.000060000000, 0.000042400000, 0.000030000000, 0.000021200000, + 0.000014990000, 0.000010600000, 0.000007465700, 0.000005257800, + 0.000003702900, 0.000002607800, 0.000001836600, 0.000001293400, + 0.000000910930, 0.000000641530, 0.000000451810 }; + +const double cie_z[CIE_SAMPLES] + = { 0.000606100000, 0.001086000000, 0.001946000000, 0.003486000000, + 0.006450001000, 0.010549990000, 0.020050010000, 0.036210000000, + 0.067850010000, 0.110200000000, 0.207400000000, 0.371300000000, + 0.645600000000, 1.039050100000, 1.385600000000, 1.622960000000, + 1.747060000000, 1.782600000000, 1.772110000000, 1.744100000000, + 1.669200000000, 1.528100000000, 1.287640000000, 1.041900000000, + 0.812950100000, 0.616200000000, 0.465180000000, 0.353300000000, + 0.272000000000, 0.212300000000, 0.158200000000, 0.111700000000, + 0.078249990000, 0.057250010000, 0.042160000000, 0.029840000000, + 0.020300000000, 0.013400000000, 0.008749999000, 0.005749999000, + 0.003900000000, 0.002749999000, 0.002100000000, 0.001800000000, + 0.001650001000, 0.001400000000, 0.001100000000, 0.001000000000, + 0.000800000000, 0.000600000000, 0.000340000000, 0.000240000000, + 0.000190000000, 0.000100000000, 0.000049999990, 0.000030000000, + 0.000020000000, 0.000010000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, + 0.000000000000, 0.000000000000, 0.000000000000 }; + +#define N(x) (x / 10566.864005283874576) + +const double cie_d65[CIE_SAMPLES] + = { N(46.6383), N(49.3637), N(52.0891), N(51.0323), N(49.9755), N(52.3118), + N(54.6482), N(68.7015), N(82.7549), N(87.1204), N(91.486), N(92.4589), + N(93.4318), N(90.057), N(86.6823), N(95.7736), N(104.865), N(110.936), + N(117.008), N(117.41), N(117.812), N(116.336), N(114.861), N(115.392), + N(115.923), N(112.367), N(108.811), N(109.082), N(109.354), N(108.578), + N(107.802), N(106.296), N(104.79), N(106.239), N(107.689), N(106.047), + N(104.405), N(104.225), N(104.046), N(102.023), N(100.0), N(98.1671), + N(96.3342), N(96.0611), N(95.788), N(92.2368), N(88.6856), N(89.3459), + N(90.0062), N(89.8026), N(89.5991), N(88.6489), N(87.6987), N(85.4936), + N(83.2886), N(83.4939), N(83.6992), N(81.863), N(80.0268), N(80.1207), + N(80.2146), N(81.2462), N(82.2778), N(80.281), N(78.2842), N(74.0027), + N(69.7213), N(70.6652), N(71.6091), N(72.979), N(74.349), N(67.9765), + N(61.604), N(65.7448), N(69.8856), N(72.4863), N(75.087), N(69.3398), + N(63.5927), N(55.0054), N(46.4182), N(56.6118), N(66.8054), N(65.0941), + N(63.3828), N(63.8434), N(64.304), N(61.8779), N(59.4519), N(55.7054), + N(51.959), N(54.6998), N(57.4406), N(58.8765), N(60.3125) }; + +#undef N + +#define N(x) (x / 106.8) +const double cie_e[CIE_SAMPLES] + = { N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), + N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), + N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), + N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), + N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), + N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), + N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), + N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), + N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), + N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), N(1.0), + N(1.0), N(1.0), N(1.0), N(1.0), N(1.0) }; +#undef N + +#define N(x) (x / 10503.2) + +const double cie_d50[CIE_SAMPLES] = { + N(23.942000), N(25.451000), N(26.961000), N(25.724000), N(24.488000), + N(27.179000), N(29.871000), N(39.589000), N(49.308000), N(52.910000), + N(56.513000), N(58.273000), N(60.034000), N(58.926000), N(57.818000), + N(66.321000), N(74.825000), N(81.036000), N(87.247000), N(88.930000), + N(90.612000), N(90.990000), N(91.368000), N(93.238000), N(95.109000), + N(93.536000), N(91.963000), N(93.843000), N(95.724000), N(96.169000), + N(96.613000), N(96.871000), N(97.129000), N(99.614000), N(102.099000), + N(101.427000), N(100.755000), N(101.536000), N(102.317000), N(101.159000), + N(100.000000), N(98.868000), N(97.735000), N(98.327000), N(98.918000), + N(96.208000), N(93.499000), N(95.593000), N(97.688000), N(98.478000), + N(99.269000), N(99.155000), N(99.042000), N(97.382000), N(95.722000), + N(97.290000), N(98.857000), N(97.262000), N(95.667000), N(96.929000), + N(98.190000), N(100.597000), N(103.003000), N(101.068000), N(99.133000), + N(93.257000), N(87.381000), N(89.492000), N(91.604000), N(92.246000), + N(92.889000), N(84.872000), N(76.854000), N(81.683000), N(86.511000), + N(89.546000), N(92.580000), N(85.405000), N(78.230000), N(67.961000), + N(57.692000), N(70.307000), N(82.923000), N(80.599000), N(78.274000), + N(0), N(0), N(0), N(0), N(0), + N(0), N(0), N(0), N(0) +}; + +#undef N + +#define N(x) (x / 10536.3) + +const double cie_d60[CIE_SAMPLES] = { + N(38.683115), N(41.014457), N(42.717548), N(42.264182), N(41.454941), + N(41.763698), N(46.605319), N(59.226938), N(72.278594), N(78.231500), + N(80.440600), N(82.739580), N(82.915027), N(79.009168), N(77.676264), + N(85.163609), N(95.681274), N(103.267764), N(107.954821), N(109.777964), + N(109.559187), N(108.418402), N(107.758141), N(109.071548), N(109.671404), + N(106.734741), N(103.707873), N(103.981942), N(105.232199), N(105.235867), + N(104.427667), N(103.052881), N(102.522934), N(104.371416), N(106.052671), + N(104.948900), N(103.315154), N(103.416286), N(103.538599), N(102.099304), + N(100.000000), N(97.992725), N(96.751421), N(97.102402), N(96.712823), + N(93.174457), N(89.921479), N(90.351933), N(91.999793), N(92.384009), + N(92.098710), N(91.722859), N(90.646003), N(88.327552), N(86.526483), + N(87.034239), N(87.579186), N(85.884584), N(83.976140), N(83.743140), + N(84.724074), N(86.450818), N(87.493491), N(86.546330), N(83.483070), + N(78.268785), N(74.172451), N(74.275184), N(76.620385), N(79.423856), + N(79.051849), N(71.763360), N(65.471371), N(67.984085), N(74.106079), + N(78.556612), N(79.527120), N(75.584935), N(67.307163), N(55.275106), + N(49.273538), N(59.008629), N(70.892412), N(70.950115), N(67.163996), + N(67.445480), N(68.171371), N(66.466636), N(62.989809), N(58.067786), + N(54.990892), N(56.915942), N(60.825601), N(62.987850) +}; + +#undef N + +const double xyz_to_srgb[3][3] = { { 3.240479, -1.537150, -0.498535 }, + { -0.969256, 1.875991, 0.041556 }, + { 0.055648, -0.204043, 1.057311 } }; + +const double srgb_to_xyz[3][3] = { { 0.412453, 0.357580, 0.180423 }, + { 0.212671, 0.715160, 0.072169 }, + { 0.019334, 0.119193, 0.950227 } }; + +const double xyz_to_xyz[3][3] = { + { 1.0, 0.0, 0.0 }, + { 0.0, 1.0, 0.0 }, + { 0.0, 0.0, 1.0 }, +}; + +const double xyz_to_ergb[3][3] = { + { 2.689989, -1.276020, -0.413844 }, + { -1.022095, 1.978261, 0.043821 }, + { 0.061203, -0.224411, 1.162859 }, +}; + +const double ergb_to_xyz[3][3] = { + { 0.496859, 0.339094, 0.164047 }, + { 0.256193, 0.678188, 0.065619 }, + { 0.023290, 0.113031, 0.863978 }, +}; + +const double xyz_to_prophoto_rgb[3][3] + = { { 1.3459433, -0.2556075, -0.0511118 }, + { -0.5445989, 1.5081673, 0.0205351 }, + { 0.0000000, 0.0000000, 1.2118128 } }; + +const double prophoto_rgb_to_xyz[3][3] = { { 0.7976749, 0.1351917, 0.0313534 }, + { 0.2880402, 0.7118741, 0.0000857 }, + { 0.0000000, 0.0000000, 0.8252100 } }; + +const double xyz_to_aces2065_1[3][3] + = { { 1.0498110175, 0.0000000000, -0.0000974845 }, + { -0.4959030231, 1.3733130458, 0.0982400361 }, + { 0.0000000000, 0.0000000000, 0.9912520182 } }; + +const double aces2065_1_to_xyz[3][3] + = { { 0.9525523959, 0.0000000000, 0.0000936786 }, + { 0.3439664498, 0.7281660966, -0.0721325464 }, + { 0.0000000000, 0.0000000000, 1.0088251844 } }; + +const double xyz_to_acescg[3][3] = { { 1.641023, -0.324803, -0.236425 }, + { -0.663663, 1.615332, 0.016756 }, + { 0.011722, -0.008284, 0.988395 } }; + +const double acescg_to_xyz[3][3] = { { 0.66245418, 0.13400421, 0.15618769 }, + { 0.27222872, 0.67408177, 0.05368952 }, + { -0.00557465, 0.00406073, 1.0103391 } }; + +const double xyz_to_rec2020[3][3] + = { { 1.7166511880, -0.3556707838, -0.2533662814 }, + { -0.6666843518, 1.6164812366, 0.0157685458 }, + { 0.0176398574, -0.0427706133, 0.9421031212 } }; + +const double rec2020_to_xyz[3][3] + = { { 0.6369580483, 0.1446169036, 0.1688809752 }, + { 0.2627002120, 0.6779980715, 0.0593017165 }, + { 0.0000000000, 0.0280726930, 1.0609850577 } }; + +const double xyz_to_dcip3[3][3] = { { 2.4931748, -0.93126315, -0.40265882 }, + { -0.82950425, 1.7626965, 0.023625137 }, + { 0.035853732, -0.07618918, 0.9570952 } }; +const double dcip3_to_xyz[3][3] = { { 0.48663378, 0.26566276, 0.19817366 }, + { 0.22900413, 0.69172573, 0.079269454 }, + { 0., 0.04511256, 1.0437145 } }; + +double +cie_interp(const double* data, double x) +{ + x -= CIE_LAMBDA_MIN; + x *= (CIE_SAMPLES - 1) / (CIE_LAMBDA_MAX - CIE_LAMBDA_MIN); + int offset = (int)x; + if (offset < 0) + offset = 0; + if (offset > CIE_SAMPLES - 2) + offset = CIE_SAMPLES - 2; + double weight = x - offset; + return (1.0 - weight) * data[offset] + weight * data[offset + 1]; +} + +// LU decomposition & triangular solving code lifted from Wikipedia + +/* INPUT: A - array of pointers to rows of a square matrix having dimension N + * Tol - small tolerance number to detect failure when the matrix is near + * degenerate OUTPUT: Matrix A is changed, it contains both matrices L-E and U + * as A=(L-E)+U such that P*A=L*U. The permutation matrix is not stored as a + * matrix, but in an integer vector P of size N+1 containing column indexes + * where the permutation matrix has "1". The last element P[N]=S+N, where S is + * the number of row exchanges needed for determinant computation, det(P)=(-1)^S + */ +int +LUPDecompose(double** A, int N, double Tol, int* P) +{ + int i, j, k, imax; + double maxA, *ptr, absA; + + for (i = 0; i <= N; i++) + P[i] = i; // Unit permutation matrix, P[N] initialized with N + + for (i = 0; i < N; i++) { + maxA = 0.0; + imax = i; + + for (k = i; k < N; k++) + if ((absA = fabs(A[k][i])) > maxA) { + maxA = absA; + imax = k; + } + + if (maxA < Tol) + return 0; // failure, matrix is degenerate + + if (imax != i) { + // pivoting P + j = P[i]; + P[i] = P[imax]; + P[imax] = j; + + // pivoting rows of A + ptr = A[i]; + A[i] = A[imax]; + A[imax] = ptr; + + // counting pivots starting from N (for determinant) + P[N]++; + } + + for (j = i + 1; j < N; j++) { + A[j][i] /= A[i][i]; + + for (k = i + 1; k < N; k++) + A[j][k] -= A[j][i] * A[i][k]; + } + } + + return 1; // decomposition done +} + +/* INPUT: A,P filled in LUPDecompose; b - rhs vector; N - dimension + * OUTPUT: x - solution vector of A*x=b + */ +void +LUPSolve(double** const A, const int* P, const double* b, int N, double* x) +{ + for (int i = 0; i < N; i++) { + x[i] = b[P[i]]; + + for (int k = 0; k < i; k++) + x[i] -= A[i][k] * x[k]; + } + + for (int i = N - 1; i >= 0; i--) { + for (int k = i + 1; k < N; k++) + x[i] -= A[i][k] * x[k]; + + x[i] = x[i] / A[i][i]; + } +} + +/// Discretization of quadrature scheme +#define CIE_FINE_SAMPLES ((CIE_SAMPLES - 1) * 3 + 1) +#define RGB2SPEC_EPSILON 1e-4 + +/// Precomputed tables for fast spectral -> RGB conversion +double lambda_tbl[CIE_FINE_SAMPLES], rgb_tbl[3][CIE_FINE_SAMPLES], + rgb_to_xyz[3][3], xyz_to_rgb[3][3], xyz_whitepoint[3]; + +/// Currently supported gamuts +enum Gamut { + SRGB, + ProPhotoRGB, + ACES2065_1, + ACEScg, + REC2020, + ERGB, + XYZ, + DCI_P3, + NO_GAMUT, +}; + +double +sigmoid(double x) +{ + return 0.5 * x / std::sqrt(1.0 + x * x) + 0.5; +} + +double +smoothstep(double x) +{ + return x * x * (3.0 - 2.0 * x); +} + +double +sqr(double x) +{ + return x * x; +} + +void +cie_lab(double* p) +{ + double X = 0.0, Y = 0.0, Z = 0.0, Xw = xyz_whitepoint[0], + Yw = xyz_whitepoint[1], Zw = xyz_whitepoint[2]; + + for (int j = 0; j < 3; ++j) { + X += p[j] * rgb_to_xyz[0][j]; + Y += p[j] * rgb_to_xyz[1][j]; + Z += p[j] * rgb_to_xyz[2][j]; + } + + auto f = [](double t) -> double { + double delta = 6.0 / 29.0; + if (t > delta * delta * delta) + return cbrt(t); + else + return t / (delta * delta * 3.0) + (4.0 / 29.0); + }; + + p[0] = 116.0 * f(Y / Yw) - 16.0; + p[1] = 500.0 * (f(X / Xw) - f(Y / Yw)); + p[2] = 200.0 * (f(Y / Yw) - f(Z / Zw)); +} + +/** + * This function precomputes tables used to convert arbitrary spectra + * to RGB (either sRGB or ProPhoto RGB) + * + * A composite quadrature rule integrates the CIE curves, reflectance, and + * illuminant spectrum over each 5nm segment in the 360..830nm range using + * Simpson's 3/8 rule (4th-order accurate), which evaluates the integrand at + * four positions per segment. While the CIE curves and illuminant spectrum are + * linear over the segment, the reflectance could have arbitrary behavior, + * hence the extra precations. + */ +void +init_tables(Gamut gamut) +{ + memset(rgb_tbl, 0, sizeof(rgb_tbl)); + memset(xyz_whitepoint, 0, sizeof(xyz_whitepoint)); + + double h = (CIE_LAMBDA_MAX - CIE_LAMBDA_MIN) / (CIE_FINE_SAMPLES - 1); + + const double* illuminant = nullptr; + + switch (gamut) { + case SRGB: + illuminant = cie_d65; + memcpy(xyz_to_rgb, xyz_to_srgb, sizeof(double) * 9); + memcpy(rgb_to_xyz, srgb_to_xyz, sizeof(double) * 9); + break; + + case ERGB: + illuminant = cie_e; + memcpy(xyz_to_rgb, xyz_to_ergb, sizeof(double) * 9); + memcpy(rgb_to_xyz, ergb_to_xyz, sizeof(double) * 9); + break; + + case XYZ: + illuminant = cie_e; + memcpy(xyz_to_rgb, xyz_to_xyz, sizeof(double) * 9); + memcpy(rgb_to_xyz, xyz_to_xyz, sizeof(double) * 9); + break; + + case ProPhotoRGB: + illuminant = cie_d50; + memcpy(xyz_to_rgb, xyz_to_prophoto_rgb, sizeof(double) * 9); + memcpy(rgb_to_xyz, prophoto_rgb_to_xyz, sizeof(double) * 9); + break; + + case ACES2065_1: + illuminant = cie_d60; + memcpy(xyz_to_rgb, xyz_to_aces2065_1, sizeof(double) * 9); + memcpy(rgb_to_xyz, aces2065_1_to_xyz, sizeof(double) * 9); + break; + + case ACEScg: + illuminant = cie_d60; + memcpy(xyz_to_rgb, xyz_to_acescg, sizeof(double) * 9); + memcpy(rgb_to_xyz, acescg_to_xyz, sizeof(double) * 9); + break; + + case REC2020: + illuminant = cie_d65; + memcpy(xyz_to_rgb, xyz_to_rec2020, sizeof(double) * 9); + memcpy(rgb_to_xyz, rec2020_to_xyz, sizeof(double) * 9); + break; + + case DCI_P3: + illuminant = cie_d65; + memcpy(xyz_to_rgb, xyz_to_dcip3, sizeof(double) * 9); + memcpy(rgb_to_xyz, dcip3_to_xyz, sizeof(double) * 9); + break; + + default: printf("init_gamut(): invalid/unsupported gamut.\n"); exit(1); + } + + for (int i = 0; i < CIE_FINE_SAMPLES; ++i) { + double lambda = CIE_LAMBDA_MIN + i * h; + + double xyz[3] = { cie_interp(cie_x, lambda), cie_interp(cie_y, lambda), + cie_interp(cie_z, lambda) }, + I = cie_interp(illuminant, lambda); + + double weight = 3.0 / 8.0 * h; + if (i == 0 || i == CIE_FINE_SAMPLES - 1) + ; + else if ((i - 1) % 3 == 2) + weight *= 2.f; + else + weight *= 3.f; + + lambda_tbl[i] = lambda; + for (int k = 0; k < 3; ++k) + for (int j = 0; j < 3; ++j) + rgb_tbl[k][i] += xyz_to_rgb[k][j] * xyz[j] * I * weight; + + for (int i = 0; i < 3; ++i) + xyz_whitepoint[i] += xyz[i] * I * weight; + } +} + +void +eval_residual(const double* coeffs, const double* rgb, double* residual) +{ + double out[3] = { 0.0, 0.0, 0.0 }; + + for (int i = 0; i < CIE_FINE_SAMPLES; ++i) { + /* Scale lambda to 0..1 range */ + double lambda = (lambda_tbl[i] - CIE_LAMBDA_MIN) + / (CIE_LAMBDA_MAX - CIE_LAMBDA_MIN); + + /* Polynomial */ + double x = 0.0; + for (int i = 0; i < 3; ++i) + x = x * lambda + coeffs[i]; + + /* Sigmoid */ + double s = sigmoid(x); + + /* Integrate against precomputed curves */ + for (int j = 0; j < 3; ++j) + out[j] += rgb_tbl[j][i] * s; + } + cie_lab(out); + memcpy(residual, rgb, sizeof(double) * 3); + cie_lab(residual); + + for (int j = 0; j < 3; ++j) + residual[j] -= out[j]; +} + +void +eval_jacobian(const double* coeffs, const double* rgb, double** jac) +{ + double r0[3], r1[3], tmp[3]; + + for (int i = 0; i < 3; ++i) { + memcpy(tmp, coeffs, sizeof(double) * 3); + tmp[i] -= RGB2SPEC_EPSILON; + eval_residual(tmp, rgb, r0); + + memcpy(tmp, coeffs, sizeof(double) * 3); + tmp[i] += RGB2SPEC_EPSILON; + eval_residual(tmp, rgb, r1); + + for (int j = 0; j < 3; ++j) + jac[j][i] = (r1[j] - r0[j]) * 1.0 / (2 * RGB2SPEC_EPSILON); + } +} + +double +gauss_newton(const double rgb[3], double coeffs[3], int it = 200) +{ + const double maxc = 2000; + double rate = 1.0; + double r = 0; + double best_r = std::numeric_limits::infinity(); + double best_c[3] = { coeffs[0], coeffs[1], coeffs[2] }; + for (int i = 0; i < it; ++i) { + double J0[3], J1[3], J2[3], *J[3] = { J0, J1, J2 }; + + double residual[3]; + + eval_residual(coeffs, rgb, residual); + + r = 0.0; + for (int j = 0; j < 3; ++j) + r += residual[j] * residual[j]; + if (r < best_r) { + best_r = r; + memcpy(best_c, coeffs, sizeof(double) * 3); + } else { + // go back, try smaller step + memcpy(coeffs, best_c, sizeof(double) * 3); + rate *= 0.5; + if (rate < 1e-2) + break; + else + continue; + } + + eval_jacobian(coeffs, rgb, J); + + int P[4]; + int rv = LUPDecompose(J, 3, 1e-15, P); + if (rv != 1) { + printf("RGB %f %f %f\n", rgb[0], rgb[1], rgb[2]); + printf("-> %f %f %f\n", coeffs[0], coeffs[1], coeffs[2]); + exit(-1); + } + + double x[3]; + LUPSolve(J, P, residual, 3, x); + + for (int j = 0; j < 3; ++j) + coeffs[j] -= x[j] * rate; + double max = std::max(std::max(fabs(coeffs[0]), fabs(coeffs[1])), + fabs(coeffs[2])); + + if (max > maxc) { + for (int j = 0; j < 3; ++j) + coeffs[j] *= maxc / max; + } + + if (r < 1e-6) + break; + } + memcpy(coeffs, best_c, sizeof(double) * 3); + return best_r * 0.0001; +} + +static Gamut +parse_gamut(const char* str) +{ + if (!strcasecmp(str, "sRGB")) + return SRGB; + if (!strcasecmp(str, "eRGB")) + return ERGB; + if (!strcasecmp(str, "XYZ")) + return XYZ; + if (!strcasecmp(str, "ProPhotoRGB")) + return ProPhotoRGB; + if (!strcasecmp(str, "ACES2065_1")) + return ACES2065_1; + if (!strcasecmp(str, "ACEScg")) + return ACEScg; + if (!strcasecmp(str, "REC2020")) + return REC2020; + if (!strcasecmp(str, "DCI_P3")) + return DCI_P3; + return NO_GAMUT; +} + +int +main(int argc, char** argv) +{ + if (argc < 3) { + printf("Syntax: rgb2spec_opt []\n" + "where is one of " + "sRGB,eRGB,XYZ,ProPhotoRGB,ACES2065_1,ACEScg,REC2020\n"); + exit(-1); + } + Gamut gamut = SRGB; + if (argc > 3) + gamut = parse_gamut(argv[3]); + if (gamut == NO_GAMUT) { + fprintf(stderr, "Could not parse gamut `%s'!\n", argv[3]); + exit(-1); + } + init_tables(gamut); + + const int res = atoi(argv[1]); + if (res == 0) { + printf("Invalid resolution!\n"); + exit(-1); + } + + printf("Optimizing %s spectra... ", argv[3]); + fflush(stdout); + + float* scale = new float[res]; + for (int k = 0; k < res; ++k) + scale[k] = (float)smoothstep(smoothstep(k / double(res - 1))); + + size_t bufsize = 3 * 3 * res * res * res; + float* out = new float[bufsize]; + + float MSE = 0; + for (int l = 0; l < 3; ++l) { + parallel_for(0, res, [&](size_t j) { + const double y = j / double(res - 1); + fflush(stdout); + for (int i = 0; i < res; ++i) { + const double x = i / double(res - 1); + double coeffs[3], rgb[3]; + memset(coeffs, 0, sizeof(double) * 3); + + // The convergence of this optimization is not always stable, so the + // technique from the paper is to start with an intensity (0.2) where + // it is stable and then go up and down using the previous result as + // the starting guess. + int start = res / 5; + // First go up from stable ... + for (int k = start; k < res; ++k) { + double b = (double)scale[k]; + + rgb[l] = b; + rgb[(l + 1) % 3] = x * b; + rgb[(l + 2) % 3] = y * b; + + MSE += gauss_newton(rgb, coeffs); + + double c0 = 360.0, c1 = 1.0 / (830.0 - 360.0); + double A = coeffs[0], B = coeffs[1], C = coeffs[2]; + + int idx = ((l * res + k) * res + j) * res + i; + + out[3 * idx + 0] = float(A * (sqr(c1))); + out[3 * idx + 1] = float(B * c1 - 2 * A * c0 * (sqr(c1))); + out[3 * idx + 2] = float(C - B * c0 * c1 + + A * (sqr(c0 * c1))); + // out[3*idx + 2] = resid; + } + // Then down ... + memset(coeffs, 0, sizeof(double) * 3); + for (int k = start; k >= 0; --k) { + double b = (double)scale[k]; + + rgb[l] = b; + rgb[(l + 1) % 3] = x * b; + rgb[(l + 2) % 3] = y * b; + + MSE += gauss_newton(rgb, coeffs); + + double c0 = 360.0, c1 = 1.0 / (830.0 - 360.0); + double A = coeffs[0], B = coeffs[1], C = coeffs[2]; + + int idx = ((l * res + k) * res + j) * res + i; + + out[3 * idx + 0] = float(A * (sqr(c1))); + out[3 * idx + 1] = float(B * c1 - 2 * A * c0 * (sqr(c1))); + out[3 * idx + 2] = float(C - B * c0 * c1 + + A * (sqr(c0 * c1))); + // out[3*idx + 2] = resid; + } + } + }); + } + + MSE /= 3 * 64 * 64 * 64 * 3; + printf("Done! (RMSE = %g)\n", sqrt(MSE)); + + FILE* f = fopen(argv[2], "w"); + if (f == nullptr) { + printf("Could not create file!\n"); + exit(1); + } + fprintf(f, "#include \n\n"); + fprintf(f, "namespace bsdl {\n"); + fprintf( + f, + " const BSDLDefaultConfig::JakobHanikaLut BSDLDefaultConfig::JH_%s_lut = {\n", + argv[3]); + fprintf(f, " {\n"); // Scale + for (int i = 0; i < res; ++i) + fprintf(f, "%.9g, ", scale[i]); + fprintf(f, " },\n {\n"); // actual table + const float* ptr = out; + for (int maxc = 0; maxc < 3; ++maxc) { + fprintf(f, "{ "); + for (int z = 0; z < res; ++z) { + fprintf(f, "{ "); + for (int y = 0; y < res; ++y) { + fprintf(f, "{ "); + for (int x = 0; x < res; ++x) { + fprintf(f, "{ "); + for (int c = 0; c < 3; ++c) + fprintf(f, "%.9g, ", *ptr++); + fprintf(f, "}, "); + } + fprintf(f, "},\n "); + } + fprintf(f, "}, "); + } + fprintf(f, "}, "); + } + fprintf(f, " }\n"); + fprintf(f, " };\n"); + fprintf(f, "} // namespace bsdl\n"); + fclose(f); +} diff --git a/src/libbsdl/src/parallel.h b/src/libbsdl/src/parallel.h new file mode 100644 index 000000000..b3f01cf8c --- /dev/null +++ b/src/libbsdl/src/parallel.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +// Silly, simple and good enough parallel_for for the LUT +// generation tools +template +void +parallel_for(unsigned begin, unsigned end, F c) +{ + struct Job { + F callback; + std::atomic begin; + unsigned end; + + bool next() + { + unsigned i = begin++; + if (i < end) + callback(i); + return i < end; + } + } j = { c, begin, end }; + std::vector threads; + const int nt = std::max(1u, std::min(end - begin, + std::thread::hardware_concurrency())); + + for (int i = 0; i < nt; ++i) { + threads.emplace_back([&]() { + do { + } while (j.next()); + }); + } + for (int i = 0; i < nt; ++i) + threads[i].join(); +} \ No newline at end of file diff --git a/src/testrender/CMakeLists.txt b/src/testrender/CMakeLists.txt index 7643eba57..657572b01 100644 --- a/src/testrender/CMakeLists.txt +++ b/src/testrender/CMakeLists.txt @@ -39,7 +39,7 @@ if (OSL_USE_OPTIX) # Generate PTX for all of the CUDA files foreach (cudasrc ${testrender_cuda_srcs}) - NVCC_COMPILE ( ${cudasrc} "${testrender_cuda_headers}" ptx_generated "" ) + NVCC_COMPILE ( ${cudasrc} "${testrender_cuda_headers}" ptx_generated "" BSDL) list (APPEND ptx_list ${ptx_generated}) endforeach () @@ -79,7 +79,7 @@ add_executable (testrender ${testrender_srcs}) target_link_libraries (testrender PRIVATE - oslexec oslquery oslcomp + oslexec oslquery oslcomp BSDL pugixml::pugixml Threads::Threads) diff --git a/src/testrender/background.h b/src/testrender/background.h index 53e0a99d8..2bfee44c5 100644 --- a/src/testrender/background.h +++ b/src/testrender/background.h @@ -120,7 +120,8 @@ struct Background { int i = y * res + x; float row_pdf = rows[y] - (y > 0 ? rows[y - 1] : 0.0f); float col_pdf = cols[i] - (x > 0 ? cols[i - 1] : 0.0f); - pdf = row_pdf * col_pdf * invjacobian; + // FIXME: This sometimes comes out negative in Optix + pdf = std::max(0.0f, row_pdf * col_pdf * invjacobian); return values[i]; } @@ -132,7 +133,8 @@ struct Background { ry = sample_cdf(rows, res, ry, &y, &row_pdf); rx = sample_cdf(cols + y * res, res, rx, &x, &col_pdf); dir = map(x + rx, y + ry); - pdf = row_pdf * col_pdf * invjacobian; + // FIXME: This sometimes comes out negative in Optix + pdf = std::max(0.0f, row_pdf * col_pdf * invjacobian); return values[y * res + x]; } diff --git a/src/testrender/bsdl_config.h b/src/testrender/bsdl_config.h new file mode 100644 index 000000000..5e9f59d3b --- /dev/null +++ b/src/testrender/bsdl_config.h @@ -0,0 +1,84 @@ +#pragma once + +#define BSDL_INLINE static inline OSL_HOSTDEVICE +#define BSDL_INLINE_METHOD inline OSL_HOSTDEVICE +#define BSDL_DECL OSL_DEVICE +#define BSDL_UNROLL() // Do nothing + +#include + +#include + +struct BSDLConfig : public bsdl::BSDLDefaultConfig { + // testrender won't do spectral render, just 3 channels covers RGB + static constexpr int HERO_WAVELENGTH_CHANNELS = 3; + + struct Fast { + static BSDL_INLINE_METHOD float cosf(float x) + { + return OIIO::fast_cos(x); + } + static BSDL_INLINE_METHOD float sinf(float x) + { + return OIIO::fast_sin(x); + } + static BSDL_INLINE_METHOD float asinf(float x) + { + return OIIO::fast_asin(x); + } + static BSDL_INLINE_METHOD float acosf(float x) + { + return OIIO::fast_acos(x); + } + static BSDL_INLINE_METHOD float atan2f(float y, float x) + { + return OIIO::fast_atan2(y, x); + } + static BSDL_INLINE_METHOD void sincosf(float x, float* s, float* c) + { + return OIIO::fast_sincos(x, s, c); + } + static BSDL_INLINE_METHOD float sinpif(float x) + { + return OIIO::fast_sinpi(x); + } + static BSDL_INLINE_METHOD float cospif(float x) + { + return OIIO::fast_cospi(x); + } + static BSDL_INLINE_METHOD float expf(float x) + { + return OIIO::fast_exp(x); + } + static BSDL_INLINE_METHOD float exp2f(float x) + { + return OIIO::fast_exp2(x); + } + static BSDL_INLINE_METHOD float logf(float x) + { + return OIIO::fast_log(x); + } + static BSDL_INLINE_METHOD float log2f(float x) + { + return OIIO::fast_log2(x); + } + static BSDL_INLINE_METHOD float log1pf(float x) + { + return OIIO::fast_log1p(x); + } + static BSDL_INLINE_METHOD float powf(float x, float y) + { + return OIIO::fast_safe_pow(x, y); + } + }; + + static BSDL_INLINE_METHOD ColorSpaceTag current_color_space() + { + return ColorSpaceTag::sRGB; + } + static BSDL_INLINE_METHOD const JakobHanikaLut* + get_jakobhanika_lut(ColorSpaceTag cs) + { + return nullptr; + } +}; diff --git a/src/testrender/raytracer.h b/src/testrender/raytracer.h index e700dbcf3..4be865678 100644 --- a/src/testrender/raytracer.h +++ b/src/testrender/raytracer.h @@ -61,11 +61,12 @@ struct Ray { OSL_HOSTDEVICE Ray(const Vec3& o, const Vec3& d, float radius, float spread, - RayType raytype) + float roughness, RayType raytype) : origin(o) , direction(d) , radius(radius) , spread(spread) + , roughness(roughness) , raytype(static_cast(raytype)) { } @@ -97,7 +98,7 @@ struct Ray { } Vec3 origin, direction; - float radius, spread; + float radius, spread, roughness; int raytype; }; @@ -157,7 +158,7 @@ struct Camera { const float cos_a = dir.dot(v); const float spread = sqrtf(invw * invh * cx.length() * cy.length() * cos_a) * cos_a; - return Ray(eye, v, 0, spread, Ray::CAMERA); + return Ray(eye, v, 0, spread, 0.0f, Ray::CAMERA); } // Specified by user: diff --git a/src/testrender/shading.cpp b/src/testrender/shading.cpp index d54c4ee84..83d5b2c56 100644 --- a/src/testrender/shading.cpp +++ b/src/testrender/shading.cpp @@ -8,6 +8,9 @@ #include "optics.h" #include "sampling.h" +#include +#include + using namespace OSL; @@ -38,11 +41,89 @@ is_black(const Color3& c) } // anonymous namespace - OSL_NAMESPACE_BEGIN +// BSDL expects this minimum functionality. We could put it in BSDF but +// the roughness() method interferes with other BSDFs +struct BSDLLobe : public BSDF { + template + OSL_HOSTDEVICE BSDLLobe(LOBE* lobe, float rough, float l0, bool tr) + : BSDF(lobe), m_roughness(rough) + { + } + + OSL_HOSTDEVICE float roughness() const { return m_roughness; } + + float m_roughness; +}; + +// This is the thin wrapper to insert spi::ThinLayerLobe into testrender +struct SpiThinLayer : public bsdl::spi::ThinLayerLobe { + using Base = bsdl::spi::ThinLayerLobe; + + static constexpr int closureid() { return SPI_THINLAYER; } + + OSL_HOSTDEVICE SpiThinLayer(const Data& data, const Vec3& wo, + float path_roughness) + : Base(this, + bsdl::BsdfGlobals(wo, + data.N, // Nf + data.N, // Ngf + false, // backfacing + path_roughness, + 1.0f, // outer_ior + 0), // hero wavelength off + data) + { + } + + OSL_HOSTDEVICE BSDF::Sample eval(const Vec3& wo, const Vec3& wi) const + { + bsdl::Sample s = Base::eval_impl(Base::frame.local(wo), + Base::frame.local(wi), true, true); + return { wi, s.weight.toRGB(0), s.pdf, s.roughness }; + } + OSL_HOSTDEVICE BSDF::Sample sample(const Vec3& wo, float rx, float ry, + float rz) const + { + bsdl::Sample s = Base::sample_impl(Base::frame.local(wo), + { rx, ry, rz }, true, true); + return { Base::frame.world(s.wi), s.weight.toRGB(0), s.pdf, + s.roughness }; + } +}; + #ifndef __CUDACC__ +// Helper to register BSDL closures +struct BSDLtoOSL { + template void visit() + { + const auto e = BSDF::template entry(); + std::vector params; + for (int i = 0; true; ++i) { + const bsdl::LobeParam& in = e.params[i]; + TypeDesc osltype; + switch (in.type) { + case bsdl::ParamType::NONE: osltype = TypeDesc(); break; + case bsdl::ParamType::VECTOR: osltype = OSL::TypeVector; break; + case bsdl::ParamType::INT: osltype = OSL::TypeInt; break; + case bsdl::ParamType::FLOAT: osltype = OSL::TypeFloat; break; + case bsdl::ParamType::COLOR: osltype = OSL::TypeColor; break; + case bsdl::ParamType::STRING: osltype = OSL::TypeString; break; + case bsdl::ParamType::CLOSURE: osltype = TypeDesc::PTR; break; + } + params.push_back({ osltype, in.offset, in.key, in.type_size }); + if (in.type == bsdl::ParamType::NONE) + break; + } + shadingsys->register_closure(e.name, BSDF::closureid(), params.data(), + nullptr, nullptr); + } + + OSL::ShadingSystem* shadingsys; +}; + void register_closures(OSL::ShadingSystem* shadingsys) { @@ -228,16 +309,17 @@ register_closures(OSL::ShadingSystem* shadingsys) for (const BuiltinClosures& b : builtins) shadingsys->register_closure(b.name, b.id, b.params, nullptr, nullptr); + + // BSDFs coming from BSDL + using bsdl_set = bsdl::TypeList; + // Register them + bsdl_set::apply(BSDLtoOSL { shadingsys }); } #endif // ifndef __CUDACC__ -OSL_NAMESPACE_END - -namespace { // anonymous namespace - template struct Diffuse final : public BSDF, DiffuseParams { OSL_HOSTDEVICE Diffuse(const DiffuseParams& params) - : BSDF(DIFFUSE_ID), DiffuseParams(params) + : BSDF(this), DiffuseParams(params) { if (trans) N = -N; @@ -259,7 +341,7 @@ template struct Diffuse final : public BSDF, DiffuseParams { struct OrenNayar final : public BSDF, OrenNayarParams { OSL_HOSTDEVICE OrenNayar(const OrenNayarParams& params) - : BSDF(OREN_NAYAR_ID), OrenNayarParams(params) + : BSDF(this), OrenNayarParams(params) { } OSL_HOSTDEVICE Sample eval(const Vec3& wo, const OSL::Vec3& wi) const @@ -297,7 +379,7 @@ struct OrenNayar final : public BSDF, OrenNayarParams { struct EnergyCompensatedOrenNayar : public BSDF, MxOrenNayarDiffuseParams { OSL_HOSTDEVICE EnergyCompensatedOrenNayar(const MxOrenNayarDiffuseParams& params) - : BSDF(MX_OREN_NAYAR_DIFFUSE_ID), MxOrenNayarDiffuseParams(params) + : BSDF(this), MxOrenNayarDiffuseParams(params) { } OSL_HOSTDEVICE Sample eval(const Vec3& wo, const OSL::Vec3& wi) const @@ -368,7 +450,7 @@ struct EnergyCompensatedOrenNayar : public BSDF, MxOrenNayarDiffuseParams { struct Phong final : public BSDF, PhongParams { OSL_HOSTDEVICE Phong(const PhongParams& params) - : BSDF(PHONG_ID), PhongParams(params) + : BSDF(this), PhongParams(params) { } OSL_HOSTDEVICE Sample eval(const Vec3& wo, const Vec3& wi) const @@ -411,7 +493,7 @@ struct Phong final : public BSDF, PhongParams { struct Ward final : public BSDF, WardParams { OSL_HOSTDEVICE Ward(const WardParams& params) - : BSDF(WARD_ID), WardParams(params) + : BSDF(this), WardParams(params) { } OSL_HOSTDEVICE Sample eval(const Vec3& wo, const OSL::Vec3& wi) const @@ -617,7 +699,7 @@ struct BeckmannDist { template struct Microfacet final : public BSDF, MicrofacetParams { OSL_HOSTDEVICE Microfacet(const MicrofacetParams& params) - : BSDF(MICROFACET_ID) + : BSDF(this) , MicrofacetParams(params) , tf(TangentFrame::from_normal_and_tangent(N, U)) { @@ -844,21 +926,14 @@ struct Microfacet final : public BSDF, MicrofacetParams { TangentFrame tf; }; -typedef Microfacet MicrofacetGGXRefl; -typedef Microfacet MicrofacetGGXRefr; -typedef Microfacet MicrofacetGGXBoth; -typedef Microfacet MicrofacetBeckmannRefl; -typedef Microfacet MicrofacetBeckmannRefr; -typedef Microfacet MicrofacetBeckmannBoth; - // We use the CRTP to inherit the parameters because each MaterialX closure uses a different set of parameters -template struct MxMicrofacet final : public BSDF, MxMicrofacetParams { OSL_HOSTDEVICE MxMicrofacet(const MxMicrofacetParams& params, float refraction_ior) - : BSDF(ID) + : BSDF(this) , MxMicrofacetParams(params) , tf(TangentFrame::from_normal_and_tangent(MxMicrofacetParams::N, MxMicrofacetParams::U)) @@ -1133,7 +1208,7 @@ struct MxMicrofacet final : public BSDF, MxMicrofacetParams { struct Reflection final : public BSDF, ReflectionParams { OSL_HOSTDEVICE Reflection(const ReflectionParams& params) - : BSDF(REFLECTION_ID), ReflectionParams(params) + : BSDF(this), ReflectionParams(params) { } OSL_HOSTDEVICE Color3 get_albedo(const Vec3& wo) const @@ -1164,7 +1239,7 @@ struct Reflection final : public BSDF, ReflectionParams { struct Refraction final : public BSDF, RefractionParams { OSL_HOSTDEVICE Refraction(const RefractionParams& params) - : BSDF(REFRACTION_ID), RefractionParams(params) + : BSDF(this), RefractionParams(params) { } OSL_HOSTDEVICE Color3 get_albedo(const Vec3& wo) const @@ -1188,7 +1263,7 @@ struct Refraction final : public BSDF, RefractionParams { }; struct Transparent final : public BSDF { - OSL_HOSTDEVICE Transparent() : BSDF(TRANSPARENT_ID) {} + OSL_HOSTDEVICE Transparent() : BSDF(this) {} OSL_HOSTDEVICE Sample eval(const Vec3& /*wo*/, const Vec3& /*wi*/) const { return {}; @@ -1204,7 +1279,7 @@ struct Transparent final : public BSDF { struct MxBurleyDiffuse final : public BSDF, MxBurleyDiffuseParams { OSL_HOSTDEVICE MxBurleyDiffuse(const MxBurleyDiffuseParams& params) - : BSDF(MX_BURLEY_DIFFUSE_ID), MxBurleyDiffuseParams(params) + : BSDF(this), MxBurleyDiffuseParams(params) { } @@ -1240,7 +1315,7 @@ struct MxBurleyDiffuse final : public BSDF, MxBurleyDiffuseParams { // https://dassaultsystemes-technology.github.io/EnterprisePBRShadingModel/spec-2022x.md.html#components/sheen struct CharlieSheen final : public BSDF, MxSheenParams { OSL_HOSTDEVICE CharlieSheen(const MxSheenParams& params) - : BSDF(MX_SHEEN_ID), MxSheenParams(params) + : BSDF(this), MxSheenParams(params) { } @@ -1294,7 +1369,7 @@ struct CharlieSheen final : public BSDF, MxSheenParams { // https://tizianzeltner.com/projects/Zeltner2022Practical/ struct ZeltnerBurleySheen final : public BSDF, MxSheenParams { OSL_HOSTDEVICE ZeltnerBurleySheen(const MxSheenParams& params) - : BSDF(MX_SHEEN_ID), MxSheenParams(params) + : BSDF(this), MxSheenParams(params) { } @@ -1396,7 +1471,6 @@ struct ZeltnerBurleySheen final : public BSDF, MxSheenParams { } }; - OSL_HOSTDEVICE Color3 evaluate_layer_opacity(const ShaderGlobalsType& sg, const ClosureColor* closure) { @@ -1448,8 +1522,8 @@ evaluate_layer_opacity(const ShaderGlobalsType& sg, const ClosureColor* closure) closure = nullptr; break; } - MxMicrofacet - mf(params, 1.0f); + MxMicrofacet mf(params, + 1.0f); weight *= w * mf.get_albedo(-sg.I); closure = nullptr; break; @@ -1462,9 +1536,8 @@ evaluate_layer_opacity(const ShaderGlobalsType& sg, const ClosureColor* closure) closure = nullptr; break; } - MxMicrofacet - mf(params, 1.0f); + MxMicrofacet mf( + params, 1.0f); weight *= w * mf.get_albedo(-sg.I); closure = nullptr; break; @@ -1598,9 +1671,9 @@ process_medium_closure(const ShaderGlobalsType& sg, ShadingResult& result, // recursively walk through the closure tree, creating bsdfs as we go OSL_HOSTDEVICE void -process_bsdf_closure(const ShaderGlobalsType& sg, ShadingResult& result, - const ClosureColor* closure, const Color3& w, - bool light_only) +process_bsdf_closure(const ShaderGlobalsType& sg, float path_roughness, + ShadingResult& result, const ClosureColor* closure, + const Color3& w, bool light_only) { static const ustringhash uh_ggx("ggx"); static const ustringhash uh_beckmann("beckmann"); @@ -1735,35 +1808,34 @@ process_bsdf_closure(const ShaderGlobalsType& sg, ShadingResult& result, = *comp->as(); if (is_black(params.transmission_tint)) ok = result.bsdf.add_bsdf< - MxMicrofacet>(cw, params, - 1.0f); + MxMicrofacet>( + cw, params, 1.0f); else - ok = result.bsdf.add_bsdf>( + ok = result.bsdf.add_bsdf< + MxMicrofacet>( cw, params, result.refraction_ior); break; } case MX_CONDUCTOR_ID: { const MxConductorParams& params = *comp->as(); - ok = result.bsdf.add_bsdf>( - cw, params, 1.0f); + ok = result.bsdf.add_bsdf< + MxMicrofacet>(cw, + params, + 1.0f); break; }; case MX_GENERALIZED_SCHLICK_ID: { const MxGeneralizedSchlickParams& params = *comp->as(); if (is_black(params.transmission_tint)) - ok = result.bsdf.add_bsdf< - MxMicrofacet>( - cw, params, 1.0f); + ok = result.bsdf.add_bsdf>(cw, + params, + 1.0f); else - ok = result.bsdf.add_bsdf< - MxMicrofacet>( + ok = result.bsdf.add_bsdf>( cw, params, result.refraction_ior); break; }; @@ -1822,6 +1894,13 @@ process_bsdf_closure(const ShaderGlobalsType& sg, ShadingResult& result, ok = true; break; } + case SpiThinLayer::closureid(): { + const SpiThinLayer::Data& params + = *comp->as(); + ok = result.bsdf.add_bsdf(cw, params, -sg.I, + path_roughness); + break; + } } #ifndef __CUDACC__ OSL_ASSERT(ok && "Invalid closure invoked in surface shader"); @@ -1842,17 +1921,14 @@ process_bsdf_closure(const ShaderGlobalsType& sg, ShadingResult& result, } } -} // anonymous namespace - -OSL_NAMESPACE_BEGIN OSL_HOSTDEVICE void -process_closure(const ShaderGlobalsType& sg, ShadingResult& result, - const ClosureColor* Ci, bool light_only) +process_closure(const ShaderGlobalsType& sg, float path_roughness, + ShadingResult& result, const ClosureColor* Ci, bool light_only) { if (!light_only) process_medium_closure(sg, result, Ci, Color3(1)); - process_bsdf_closure(sg, result, Ci, Color3(1), light_only); + process_bsdf_closure(sg, path_roughness, result, Ci, Color3(1), light_only); } OSL_HOSTDEVICE Vec3 @@ -1895,317 +1971,22 @@ process_background_closure(const ClosureColor* closure) return weight; } - -typedef MxMicrofacet - MxConductor; -typedef MxMicrofacet - MxDielectric; -typedef MxMicrofacet - MxDielectricOpaque; -typedef MxMicrofacet - MxGeneralizedSchlick; -typedef MxMicrofacet - MxGeneralizedSchlickOpaque; - OSL_HOSTDEVICE Color3 -CompositeBSDF::get_albedo(const BSDF* bsdf, const Vec3& wo) const +BSDF::get_albedo_vrtl(const Vec3& wo) const { - static const ustringhash uh_ggx("ggx"); - static const ustringhash uh_beckmann("beckmann"); - static const ustringhash uh_default("default"); - - Color3 albedo(0); - switch (bsdf->id) { - case DIFFUSE_ID: - albedo = BSDF_CAST(Diffuse<0>, bsdf)->get_albedo(wo); - break; - case TRANSPARENT_ID: - case MX_TRANSPARENT_ID: - albedo = BSDF_CAST(Transparent, bsdf)->get_albedo(wo); - break; - case OREN_NAYAR_ID: - albedo = BSDF_CAST(OrenNayar, bsdf)->get_albedo(wo); - break; - case TRANSLUCENT_ID: - albedo = BSDF_CAST(Diffuse<1>, bsdf)->get_albedo(wo); - break; - case PHONG_ID: albedo = BSDF_CAST(Phong, bsdf)->get_albedo(wo); break; - case WARD_ID: albedo = BSDF_CAST(Ward, bsdf)->get_albedo(wo); break; - case REFLECTION_ID: - case FRESNEL_REFLECTION_ID: - albedo = BSDF_CAST(Reflection, bsdf)->get_albedo(wo); - break; - case REFRACTION_ID: - albedo = BSDF_CAST(Refraction, bsdf)->get_albedo(wo); - break; - case MICROFACET_ID: { - const int refract = ((MicrofacetBeckmannRefl*)bsdf)->refract; - const ustringhash dist = ((MicrofacetBeckmannRefl*)bsdf)->dist; - if (dist == uh_default || dist == uh_beckmann) { - switch (refract) { - case 0: - albedo = BSDF_CAST(MicrofacetBeckmannRefl, bsdf)->get_albedo(wo); - break; - case 1: - albedo = BSDF_CAST(MicrofacetBeckmannRefr, bsdf)->get_albedo(wo); - break; - case 2: - albedo = BSDF_CAST(MicrofacetBeckmannBoth, bsdf)->get_albedo(wo); - break; - } - } else if (dist == uh_ggx) { - switch (refract) { - case 0: - albedo = BSDF_CAST(MicrofacetGGXRefl, bsdf)->get_albedo(wo); - break; - case 1: - albedo = BSDF_CAST(MicrofacetGGXRefr, bsdf)->get_albedo(wo); - break; - case 2: - albedo = BSDF_CAST(MicrofacetGGXBoth, bsdf)->get_albedo(wo); - break; - } - } - break; - } - case MX_CONDUCTOR_ID: - albedo = BSDF_CAST(MxConductor, bsdf)->get_albedo(wo); - break; - case MX_DIELECTRIC_ID: - if (is_black(((MxDielectricOpaque*)bsdf)->transmission_tint)) - albedo = BSDF_CAST(MxDielectricOpaque, bsdf)->get_albedo(wo); - else - albedo = BSDF_CAST(MxDielectric, bsdf)->get_albedo(wo); - break; - case MX_OREN_NAYAR_DIFFUSE_ID: - albedo = BSDF_CAST(EnergyCompensatedOrenNayar, bsdf)->get_albedo(wo); - break; - case MX_BURLEY_DIFFUSE_ID: - albedo = BSDF_CAST(MxBurleyDiffuse, bsdf)->get_albedo(wo); - break; - case MX_SHEEN_ID: - if (BSDF_CAST(CharlieSheen, bsdf)->mode == 1) - albedo = BSDF_CAST(ZeltnerBurleySheen, bsdf)->get_albedo(wo); - else - albedo = BSDF_CAST(CharlieSheen, bsdf)->get_albedo(wo); - break; - case MX_GENERALIZED_SCHLICK_ID: { - const Color3& tint = ((MxGeneralizedSchlick*)bsdf)->transmission_tint; - if (is_black(tint)) - albedo = BSDF_CAST(MxGeneralizedSchlickOpaque, bsdf)->get_albedo(wo); - else - albedo = BSDF_CAST(MxGeneralizedSchlick, bsdf)->get_albedo(wo); - break; - } - default: break; - } - return albedo; + return dispatch([&](auto bsdf) { return bsdf.get_albedo(wo); }); } - OSL_HOSTDEVICE BSDF::Sample -CompositeBSDF::sample(const BSDF* bsdf, const Vec3& wo, float rx, float ry, - float rz) const +BSDF::eval_vrtl(const Vec3& wo, const Vec3& wi) const { - static const ustringhash uh_ggx("ggx"); - static const ustringhash uh_beckmann("beckmann"); - static const ustringhash uh_default("default"); - - BSDF::Sample sample = {}; - switch (bsdf->id) { - case DIFFUSE_ID: - sample = BSDF_CAST(Diffuse<0>, bsdf)->sample(wo, rx, ry, rz); - break; - case TRANSPARENT_ID: - case MX_TRANSPARENT_ID: - sample = BSDF_CAST(Transparent, bsdf)->sample(wo, rx, ry, rz); - break; - case OREN_NAYAR_ID: - sample = BSDF_CAST(OrenNayar, bsdf)->sample(wo, rx, ry, rz); - break; - case TRANSLUCENT_ID: - sample = BSDF_CAST(Diffuse<1>, bsdf)->sample(wo, rx, ry, rz); - break; - case PHONG_ID: - sample = BSDF_CAST(Phong, bsdf)->sample(wo, rx, ry, rz); - break; - case WARD_ID: sample = BSDF_CAST(Ward, bsdf)->sample(wo, rx, ry, rz); break; - case REFLECTION_ID: - case FRESNEL_REFLECTION_ID: - sample = BSDF_CAST(Reflection, bsdf)->sample(wo, rx, ry, rz); - break; - case REFRACTION_ID: - sample = BSDF_CAST(Refraction, bsdf)->sample(wo, rx, ry, rz); - break; - case MICROFACET_ID: { - const int refract = ((MicrofacetBeckmannRefl*)bsdf)->refract; - const ustringhash dist = ((MicrofacetBeckmannRefl*)bsdf)->dist; - if (dist == uh_default || dist == uh_beckmann) { - switch (refract) { - case 0: - sample = BSDF_CAST(MicrofacetBeckmannRefl, bsdf) - ->sample(wo, rx, ry, rz); - break; - case 1: - sample = BSDF_CAST(MicrofacetBeckmannRefr, bsdf) - ->sample(wo, rx, ry, rz); - break; - case 2: - sample = BSDF_CAST(MicrofacetBeckmannBoth, bsdf) - ->sample(wo, rx, ry, rz); - break; - } - } else if (dist == uh_ggx) { - switch (refract) { - case 0: - sample - = BSDF_CAST(MicrofacetGGXRefl, bsdf)->sample(wo, rx, ry, rz); - break; - case 1: - sample - = BSDF_CAST(MicrofacetGGXRefr, bsdf)->sample(wo, rx, ry, rz); - break; - case 2: - sample - = BSDF_CAST(MicrofacetGGXBoth, bsdf)->sample(wo, rx, ry, rz); - break; - } - } - break; - } - case MX_CONDUCTOR_ID: - sample = BSDF_CAST(MxConductor, bsdf)->sample(wo, rx, ry, rz); - break; - case MX_DIELECTRIC_ID: - if (is_black(((MxDielectricOpaque*)bsdf)->transmission_tint)) - sample = BSDF_CAST(MxDielectricOpaque, bsdf)->sample(wo, rx, ry, rz); - else - sample = BSDF_CAST(MxDielectric, bsdf)->sample(wo, rx, ry, rz); - break; - case MX_BURLEY_DIFFUSE_ID: - sample = BSDF_CAST(MxBurleyDiffuse, bsdf)->sample(wo, rx, ry, rz); - break; - case MX_OREN_NAYAR_DIFFUSE_ID: - sample = BSDF_CAST(EnergyCompensatedOrenNayar, bsdf) - ->sample(wo, rx, ry, rz); - break; - case MX_SHEEN_ID: - if (BSDF_CAST(CharlieSheen, bsdf)->mode == 1) - sample = BSDF_CAST(ZeltnerBurleySheen, bsdf)->sample(wo, rx, ry, rz); - else - sample = BSDF_CAST(CharlieSheen, bsdf)->sample(wo, rx, ry, rz); - break; - case MX_GENERALIZED_SCHLICK_ID: { - const Color3& tint = ((MxGeneralizedSchlick*)bsdf)->transmission_tint; - if (is_black(tint)) { - sample = BSDF_CAST(MxGeneralizedSchlickOpaque, bsdf) - ->sample(wo, rx, ry, rz); - } else { - sample - = BSDF_CAST(MxGeneralizedSchlick, bsdf)->sample(wo, rx, ry, rz); - } - break; - } - default: break; - } - return sample; + return dispatch([&](auto bsdf) { return bsdf.eval(wo, wi); }); } - OSL_HOSTDEVICE BSDF::Sample -CompositeBSDF::eval(const BSDF* bsdf, const Vec3& wo, const Vec3& wi) const +BSDF::sample_vrtl(const Vec3& wo, float rx, float ry, float rz) const { - static const ustringhash uh_ggx("ggx"); - static const ustringhash uh_beckmann("beckmann"); - static const ustringhash uh_default("default"); - - BSDF::Sample sample = {}; - switch (bsdf->id) { - case DIFFUSE_ID: sample = BSDF_CAST(Diffuse<0>, bsdf)->eval(wo, wi); break; - case TRANSPARENT_ID: - case MX_TRANSPARENT_ID: - sample = BSDF_CAST(Transparent, bsdf)->eval(wo, wi); - break; - case OREN_NAYAR_ID: - sample = BSDF_CAST(OrenNayar, bsdf)->eval(wo, wi); - break; - case TRANSLUCENT_ID: - sample = BSDF_CAST(Diffuse<1>, bsdf)->eval(wo, wi); - break; - case PHONG_ID: sample = BSDF_CAST(Phong, bsdf)->eval(wo, wi); break; - case WARD_ID: sample = BSDF_CAST(Ward, bsdf)->eval(wo, wi); break; - case REFLECTION_ID: - case FRESNEL_REFLECTION_ID: - sample = BSDF_CAST(Reflection, bsdf)->eval(wo, wi); - break; - case REFRACTION_ID: - sample = BSDF_CAST(Refraction, bsdf)->eval(wo, wi); - break; - case MICROFACET_ID: { - const int refract = ((MicrofacetBeckmannRefl*)bsdf)->refract; - const ustringhash dist = ((MicrofacetBeckmannRefl*)bsdf)->dist; - if (dist == uh_default || dist == uh_beckmann) { - switch (refract) { - case 0: - sample = BSDF_CAST(MicrofacetBeckmannRefl, bsdf)->eval(wo, wi); - break; - case 1: - sample = BSDF_CAST(MicrofacetBeckmannRefr, bsdf)->eval(wo, wi); - break; - case 2: - sample = BSDF_CAST(MicrofacetBeckmannBoth, bsdf)->eval(wo, wi); - break; - } - } else if (dist == uh_ggx) { - switch (refract) { - case 0: - sample = BSDF_CAST(MicrofacetGGXRefl, bsdf)->eval(wo, wi); - break; - case 1: - sample = BSDF_CAST(MicrofacetGGXRefr, bsdf)->eval(wo, wi); - break; - case 2: - sample = BSDF_CAST(MicrofacetGGXBoth, bsdf)->eval(wo, wi); - break; - } - } - break; - } - case MX_CONDUCTOR_ID: - sample = BSDF_CAST(MxConductor, bsdf)->eval(wo, wi); - break; - case MX_DIELECTRIC_ID: - if (is_black(((MxDielectricOpaque*)bsdf)->transmission_tint)) - sample = BSDF_CAST(MxDielectricOpaque, bsdf)->eval(wo, wi); - else - sample = BSDF_CAST(MxDielectric, bsdf)->eval(wo, wi); - break; - case MX_BURLEY_DIFFUSE_ID: - sample = BSDF_CAST(MxBurleyDiffuse, bsdf)->eval(wo, wi); - break; - case MX_OREN_NAYAR_DIFFUSE_ID: - sample = BSDF_CAST(EnergyCompensatedOrenNayar, bsdf)->eval(wo, wi); - break; - case MX_SHEEN_ID: - if (BSDF_CAST(CharlieSheen, bsdf)->mode == 1) - sample = BSDF_CAST(ZeltnerBurleySheen, bsdf)->eval(wo, wi); - else - sample = BSDF_CAST(CharlieSheen, bsdf)->eval(wo, wi); - break; - case MX_GENERALIZED_SCHLICK_ID: { - const Color3& tint = ((MxGeneralizedSchlick*)bsdf)->transmission_tint; - if (is_black(tint)) { - sample = BSDF_CAST(MxGeneralizedSchlickOpaque, bsdf)->eval(wo, wi); - } else { - sample = BSDF_CAST(MxGeneralizedSchlick, bsdf)->eval(wo, wi); - } - break; - } - default: break; - } - return sample; + return dispatch([&](auto bsdf) { return bsdf.sample(wo, rx, ry, rz); }); } diff --git a/src/testrender/shading.h b/src/testrender/shading.h index 741fabaed..732d3f002 100644 --- a/src/testrender/shading.h +++ b/src/testrender/shading.h @@ -10,6 +10,10 @@ #include #include #include + +#include "bsdl_config.h" +#include + #include "optics.h" #include "sampling.h" @@ -50,12 +54,12 @@ enum ClosureIDs { MX_MEDIUM_VDF_ID, MX_LAYER_ID, // TODO: adding vdfs would require extending testrender with volume support ... + // BSDL SPI closures + SPI_THINLAYER, EMPTY_ID }; -namespace { // anonymous namespace - // these structures hold the parameters of each closure type // they will be contained inside ClosureComponent struct EmptyParams {}; @@ -298,15 +302,58 @@ struct MxMediumVdfParams { ustringhash label; }; -} // anonymous namespace - - -// Cast a BSDF* to the specified sub-type -#define BSDF_CAST(BSDF_TYPE, bsdf) reinterpret_cast(bsdf) +struct GGXDist; +struct BeckmannDist; + +template struct Diffuse; + +template struct Microfacet; +using MicrofacetGGXRefl = Microfacet; +using MicrofacetGGXRefr = Microfacet; +using MicrofacetGGXBoth = Microfacet; +using MicrofacetBeckmannRefl = Microfacet; +using MicrofacetBeckmannRefr = Microfacet; +using MicrofacetBeckmannBoth = Microfacet; + +template +struct MxMicrofacet; + +using MxGeneralizedSchlick + = MxMicrofacet; +using MxGeneralizedSchlickOpaque + = MxMicrofacet; +using MxDielectric = MxMicrofacet; +using MxDielectricOpaque = MxMicrofacet; +using MxConductor = MxMicrofacet; + +struct Transparent; +struct OrenNayar; +struct Phong; +struct Ward; +struct Reflection; +struct Refraction; +struct MxBurleyDiffuse; +struct EnergyCompensatedOrenNayar; +struct ZeltnerBurleySheen; +struct CharlieSheen; +struct SpiThinLayer; + +// StaticVirtual generates a switch/case dispatch method for us given +// a list of possible subtypes. We just need to forward declare them. +using AbstractBSDF = bsdl::StaticVirtual< + Diffuse<0>, Transparent, OrenNayar, Diffuse<1>, Phong, Ward, Reflection, + Refraction, MicrofacetBeckmannRefl, MicrofacetBeckmannRefr, + MicrofacetBeckmannBoth, MicrofacetGGXRefl, MicrofacetGGXRefr, + MicrofacetGGXBoth, MxConductor, MxDielectricOpaque, MxDielectric, + MxBurleyDiffuse, EnergyCompensatedOrenNayar, ZeltnerBurleySheen, + CharlieSheen, MxGeneralizedSchlickOpaque, MxGeneralizedSchlick, SpiThinLayer>; + +// Then we just need to inherit from AbstractBSDF /// Individual BSDF (diffuse, phong, refraction, etc ...) /// Actual implementations of this class are private -struct BSDF { +struct BSDF : public AbstractBSDF { struct Sample { OSL_HOSTDEVICE Sample() : wi(0.0f), weight(0.0f), pdf(0.0f), roughness(0.0f) @@ -325,7 +372,13 @@ struct BSDF { float pdf; float roughness; }; - OSL_HOSTDEVICE BSDF(ClosureIDs id = EMPTY_ID) : id(id) {} + // We get the specific BSDF type as a template parameter LOBE in + // the constructor. We pass it to AbstractBSDF and it computes an + // id internally to remember. + template OSL_HOSTDEVICE BSDF(LOBE* lobe) : AbstractBSDF(lobe) + { + } + // Default implementations, to be overriden by subclasses OSL_HOSTDEVICE Color3 get_albedo(const Vec3& /*wo*/) const { return Color3(1); @@ -339,7 +392,13 @@ struct BSDF { { return {}; } - ClosureIDs id; + // And the "virtual" versions of the above. They are implemented via + // dispatch with a lambda, but it has to be written after subclasses + // with their inline methods have been defined. See shading.cpp + OSL_HOSTDEVICE Color3 get_albedo_vrtl(const Vec3& wo) const; + OSL_HOSTDEVICE Sample eval_vrtl(const Vec3& wo, const Vec3& wi) const; + OSL_HOSTDEVICE Sample sample_vrtl(const Vec3& wo, float rx, float ry, + float rz) const; #ifdef __CUDACC__ // TODO: This is a total hack to avoid a misaligned address error // that sometimes occurs with the EnergyCompensatedOrenNayar BSDF. @@ -360,7 +419,8 @@ struct CompositeBSDF { { float total = 0; for (int i = 0; i < num_bsdfs; i++) { - pdfs[i] = weights[i].dot(path_weight * get_albedo(bsdfs[i], wo)) + pdfs[i] = weights[i].dot(path_weight + * bsdfs[i]->get_albedo_vrtl(wo)) / (path_weight.x + path_weight.y + path_weight.z); #ifndef __CUDACC__ // TODO: Figure out what to do with weights/albedos with negative @@ -392,7 +452,7 @@ struct CompositeBSDF { { Color3 result(0, 0, 0); for (int i = 0; i < num_bsdfs; i++) - result += weights[i] * get_albedo(bsdfs[i], wo); + result += weights[i] * bsdfs[i]->get_albedo_vrtl(wo); return result; } @@ -400,7 +460,7 @@ struct CompositeBSDF { { BSDF::Sample s = {}; for (int i = 0; i < num_bsdfs; i++) { - BSDF::Sample b = eval(bsdfs[i], wo, wi); + BSDF::Sample b = bsdfs[i]->eval_vrtl(wo, wi); b.weight *= weights[i]; MIS::update_eval(&s.weight, &s.pdf, b.weight, b.pdf, pdfs[i]); s.roughness += b.roughness * pdfs[i]; @@ -416,7 +476,7 @@ struct CompositeBSDF { if (rx < (pdfs[i] + accum)) { rx = (rx - accum) / pdfs[i]; rx = std::min(rx, 0.99999994f); // keep result in [0,1) - BSDF::Sample s = sample(bsdfs[i], wo, rx, ry, rz); + BSDF::Sample s = bsdfs[i]->sample_vrtl(wo, rx, ry, rz); s.weight *= weights[i] * (1 / pdfs[i]); s.pdf *= pdfs[i]; if (s.pdf == 0.0f) @@ -424,7 +484,7 @@ struct CompositeBSDF { // we sampled PDF i, now figure out how much the other bsdfs contribute to the chosen direction for (int j = 0; j < num_bsdfs; j++) { if (i != j) { - BSDF::Sample b = eval(bsdfs[j], wo, s.wi); + BSDF::Sample b = bsdfs[j]->eval_vrtl(wo, s.wi); b.weight *= weights[j]; MIS::update_eval(&s.weight, &s.pdf, b.weight, b.pdf, pdfs[j]); @@ -458,11 +518,8 @@ struct CompositeBSDF { OSL_HOSTDEVICE CompositeBSDF(const CompositeBSDF& c); OSL_HOSTDEVICE CompositeBSDF& operator=(const CompositeBSDF& c); - OSL_HOSTDEVICE Color3 get_albedo(const BSDF* bsdf, const Vec3& wo) const; OSL_HOSTDEVICE BSDF::Sample eval(const BSDF* bsdf, const Vec3& wo, const Vec3& wi) const; - OSL_HOSTDEVICE BSDF::Sample sample(const BSDF* bsdf, const Vec3& wo, - float rx, float ry, float rz) const; enum { MaxEntries = 8 }; enum { MaxSize = 256 * sizeof(float) }; @@ -488,8 +545,8 @@ struct ShadingResult { void register_closures(ShadingSystem* shadingsys); OSL_HOSTDEVICE void -process_closure(const OSL::ShaderGlobals& sg, ShadingResult& result, - const ClosureColor* Ci, bool light_only); +process_closure(const OSL::ShaderGlobals& sg, float path_roughness, + ShadingResult& result, const ClosureColor* Ci, bool light_only); OSL_HOSTDEVICE Vec3 process_background_closure(const ClosureColor* Ci); diff --git a/src/testrender/simpleraytracer.cpp b/src/testrender/simpleraytracer.cpp index 2318021fa..e86d968be 100644 --- a/src/testrender/simpleraytracer.cpp +++ b/src/testrender/simpleraytracer.cpp @@ -1030,7 +1030,8 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler, #endif ShadingResult result; bool last_bounce = b == max_bounces; - process_closure(sg, result, (const ClosureColor*)sg.Ci, last_bounce); + process_closure(sg, r.roughness, result, (const ClosureColor*)sg.Ci, + last_bounce); #ifndef __CUDACC__ const size_t lightprims_size = m_lightprims.size(); @@ -1080,7 +1081,7 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler, b.pdf); if ((contrib.x + contrib.y + contrib.z) > 0) { ShaderGlobalsType shadow_sg; - Ray shadow_ray = Ray(sg.P, bg_dir.val(), radius, 0, + Ray shadow_ray = Ray(sg.P, bg_dir.val(), radius, 0, 0, Ray::SHADOW); Intersection shadow_hit = scene.intersect(shadow_ray, inf, hit.id); @@ -1110,7 +1111,7 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler, light_pick_pdf * sample.pdf, b.pdf); if ((contrib.x + contrib.y + contrib.z) > 0) { ShaderGlobalsType light_sg; - Ray shadow_ray = Ray(sg.P, sample.dir, radius, 0, + Ray shadow_ray = Ray(sg.P, sample.dir, radius, 0, 0, Ray::SHADOW); // trace a shadow ray and see if we actually hit the target // in this tiny renderer, tracing a ray is probably cheaper than evaluating the light shader @@ -1140,7 +1141,7 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler, execute_shader(light_sg, shaderID, light_closure_pool); #endif ShadingResult light_result; - process_closure(light_sg, light_result, + process_closure(light_sg, r.roughness, light_result, (const ClosureColor*)light_sg.Ci, true); // accumulate contribution path_radiance += contrib * light_result.Le; @@ -1157,7 +1158,8 @@ SimpleRaytracer::subpixel_radiance(float x, float y, Sampler& sampler, r.direction = p.wi; r.radius = radius; // Just simply use roughness as spread slope - r.spread = std::max(r.spread, p.roughness); + r.spread = std::max(r.spread, p.roughness); + r.roughness = p.roughness; if (!(path_weight.x > 0) && !(path_weight.y > 0) && !(path_weight.z > 0)) break; // filter out all 0's or NaNs diff --git a/src/testshade/CMakeLists.txt b/src/testshade/CMakeLists.txt index e8d72cfc0..29cab5a18 100644 --- a/src/testshade/CMakeLists.txt +++ b/src/testshade/CMakeLists.txt @@ -35,7 +35,7 @@ if (OSL_USE_OPTIX) # Generate PTX for all of the CUDA files foreach (cudasrc ${testshade_cuda_srcs}) - NVCC_COMPILE ( ${cudasrc} ${extra_cuda_headers} ptx_generated "-I../testrender/cuda" ) + NVCC_COMPILE ( ${cudasrc} ${extra_cuda_headers} ptx_generated "-I../testrender/cuda" "") list (APPEND ptx_list ${ptx_generated}) endforeach () diff --git a/testsuite/render-spi-thinlayer/OPTIX b/testsuite/render-spi-thinlayer/OPTIX new file mode 100644 index 000000000..e69de29bb diff --git a/testsuite/render-spi-thinlayer/checkerboard.osl b/testsuite/render-spi-thinlayer/checkerboard.osl new file mode 100644 index 000000000..121265609 --- /dev/null +++ b/testsuite/render-spi-thinlayer/checkerboard.osl @@ -0,0 +1,36 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +shader +checkerboard + [[ string description = "Procedural checkerboard" ]] +( + float s = u + [[ string description = "s coordinate for the lookup", + float UImin = 0, float UIsoftmax = 1 ]], + float t = v + [[ string description = "t coordinate for the lookup", + float UImin = 0, float UIsoftmax = 1 ]], + float scale_s = 4 + [[ string description = "scale factor for s coordinate" ]], + float scale_t = 4 + [[ string description = "scale factor for t coordinate" ]], + color Ca = color(1, 1, 1) + [[ string description = "color of even squares" ]], + color Cb = color(0, 0, 0) + [[ string description = "color of odd squares" ]], + output color Cout = 0 + [[ string description = "Output color", + float UImin = 0, float UImax = 1 ]] + ) +{ +// TODO: anti-alias + float cs = fmod(s * scale_s, 2); + float ct = fmod(t * scale_t, 2); + if ((int(cs) ^ int(ct)) == 0) + Cout = Ca; + else + Cout = Cb; +} diff --git a/testsuite/render-spi-thinlayer/envmap.osl b/testsuite/render-spi-thinlayer/envmap.osl new file mode 100644 index 000000000..7e5906f05 --- /dev/null +++ b/testsuite/render-spi-thinlayer/envmap.osl @@ -0,0 +1,15 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + +shader envmap(float Kb = 1, string filename = "") +{ + vector dir = normalize(I); + float radial = atan2(-dir[2], dir[0]); + float nradial = acos(dir[1]); + float r = 0.5 * sin(nradial * 0.5); + float tu = 0.5 + r * cos(radial); + float tv = 0.5 - r * sin(radial); + color c = texture(filename, tu, tv); + Ci = Kb * c * background(); +} diff --git a/testsuite/render-spi-thinlayer/glossy_glass.osl b/testsuite/render-spi-thinlayer/glossy_glass.osl new file mode 100644 index 000000000..11cb0d4e9 --- /dev/null +++ b/testsuite/render-spi-thinlayer/glossy_glass.osl @@ -0,0 +1,30 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + +closure color thinlayer( + normal N, // normal + vector U, // side vector + float IOR, // Dielectric IOR + float roughness, // Roughness + float anisotropy, // Anisotropy (0-1) + float thickness, // World space thickness to fake + color refl_tint, // Reflection tint + color refr_tint, // Refraction tint + color sigma_t) [[ int builtin = 1 ]]; // Internal extintion (sgima_a + sigma_s) + +shader glossy_glass (float Kr = 1, color Cs = 1, color absorption = 0, + float xalpha = 0.01, float yalpha = 0.01) +{ + vector U; + if (abs(N[0]) > 0.01) + U = vector(N[2], 0, -N[0]); + else + U = vector(0, -N[2], N[1]); + U = normalize(U); + + float eta = 1.5; + float roughness = max(xalpha, yalpha); + float anisotropy = 1 - min(xalpha, yalpha) / roughness; + Ci = (Kr * Cs) * thinlayer(N, U, eta, roughness, anisotropy, 1.0, color(1), color(1), absorption); +} diff --git a/testsuite/render-spi-thinlayer/matte.osl b/testsuite/render-spi-thinlayer/matte.osl new file mode 100644 index 000000000..a8c6f187e --- /dev/null +++ b/testsuite/render-spi-thinlayer/matte.osl @@ -0,0 +1,19 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + + +surface +matte + [[ string description = "Lambertian diffuse material" ]] +( + float Kd = 1 + [[ string description = "Diffuse scaling", + float UImin = 0, float UIsoftmax = 1 ]], + color Cs = 1 + [[ string description = "Base color", + float UImin = 0, float UImax = 1 ]] + ) +{ + Ci = Kd * Cs * diffuse (N); +} diff --git a/testsuite/render-spi-thinlayer/ref/out-icx-alt.exr b/testsuite/render-spi-thinlayer/ref/out-icx-alt.exr new file mode 100644 index 000000000..74052b207 Binary files /dev/null and b/testsuite/render-spi-thinlayer/ref/out-icx-alt.exr differ diff --git a/testsuite/render-spi-thinlayer/ref/out-macos-alt.exr b/testsuite/render-spi-thinlayer/ref/out-macos-alt.exr new file mode 100644 index 000000000..21121f391 Binary files /dev/null and b/testsuite/render-spi-thinlayer/ref/out-macos-alt.exr differ diff --git a/testsuite/render-spi-thinlayer/ref/out-optix-alt.exr b/testsuite/render-spi-thinlayer/ref/out-optix-alt.exr new file mode 100644 index 000000000..925d8a4e5 Binary files /dev/null and b/testsuite/render-spi-thinlayer/ref/out-optix-alt.exr differ diff --git a/testsuite/render-spi-thinlayer/ref/out.exr b/testsuite/render-spi-thinlayer/ref/out.exr new file mode 100644 index 000000000..556fee7ab Binary files /dev/null and b/testsuite/render-spi-thinlayer/ref/out.exr differ diff --git a/testsuite/render-spi-thinlayer/run.py b/testsuite/render-spi-thinlayer/run.py new file mode 100755 index 000000000..f46b389e3 --- /dev/null +++ b/testsuite/render-spi-thinlayer/run.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +# Copyright Contributors to the Open Shading Language project. +# SPDX-License-Identifier: BSD-3-Clause +# https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + +failthresh = 0.02 +failrelative = 0.01 +failpercent = 1 +allowfailures = 5 +idiff_program = "idiff" + +outputs = [ "out.exr" ] +command = testrender("-r 320 240 -aa 16 scene.xml out.exr") diff --git a/testsuite/render-spi-thinlayer/scene.xml b/testsuite/render-spi-thinlayer/scene.xml new file mode 100644 index 000000000..dc31c9a4a --- /dev/null +++ b/testsuite/render-spi-thinlayer/scene.xml @@ -0,0 +1,30 @@ + + + + + string filename "../common/textures/kitchen_probe.hdr"; + shader envmap layer1; + + + + + param float scale_s 20; + param float scale_t 20; + param color Ca 0.1 0.1 0.1; + param color Cb 0.5 0.5 0.5; + shader checkerboard tex; + shader matte layer1; + connect tex.Cout layer1.Cs; + + + + + + float xalpha 0.1; float yalpha 0.02; color absorption 0.2 0 0.1; shader glossy_glass layer1; + + float xalpha 0.05; float yalpha 0.05; color absorption 0.2 0.1 0.0; shader glossy_glass layer1; + + float xalpha 0.001; float xalpha 0.001; color absorption 0 0.1 0.2; shader glossy_glass layer1; + + +