diff --git a/examples_tests b/examples_tests index aa2d4bda3e..8f045a1c27 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit aa2d4bda3ee0a0e0590aaaba32942fd45c6ddf01 +Subproject commit 8f045a1c27a198f8542456378f865032765378b8 diff --git a/include/nbl/asset/ICPUScene.h b/include/nbl/asset/ICPUScene.h index 62d8d2e9a3..3a3ab469a3 100644 --- a/include/nbl/asset/ICPUScene.h +++ b/include/nbl/asset/ICPUScene.h @@ -276,7 +276,7 @@ class ICPUScene final : public IAsset, public IScene instances.emplace_back().instance = std::move(inst); } // TODO: adjust BLAS geometry flags according to materials set opaqueness and NO_DUPLICATE_ANY_HIT_INVOCATION_BIT - SResult retval = {.instances=core::make_refctd_dynamic_array(instanceCount),.allInstancesValid=allInstancesValid}; + SResult retval = {.instances=core::make_refctd_dynamic_array(instances.size()),.allInstancesValid=allInstancesValid}; std::move(instances.begin(),instances.end(),retval.instances->begin()); return retval; } diff --git a/include/nbl/asset/material_compiler3/CFrontendIR.h b/include/nbl/asset/material_compiler3/CFrontendIR.h index d43b0be0ae..4269af5d6f 100644 --- a/include/nbl/asset/material_compiler3/CFrontendIR.h +++ b/include/nbl/asset/material_compiler3/CFrontendIR.h @@ -131,7 +131,7 @@ class CFrontendIR final : public CNodePool float scale = std::numeric_limits::infinity(); // rest are ignored if the view is null uint8_t viewChannel : 2 = 0; - uint8_t padding[3] = {0,0,0}; + uint8_t padding[3] = {0,0,0}; // TODO: padding stores metadata, shall we exclude from assignment and copy operators? core::smart_refctd_ptr view = {}; // shadow comparison functions are ignored // NOTE: could take only things that matter from the sampler and pack the viewChannel and reduce padding @@ -189,13 +189,13 @@ class CFrontendIR final : public CNodePool printDot(Count,sstr,selfID,std::forward(paramNameBegin),uvRequired); } - SParameter params[Count] = {}; // identity transform by default, ignored if no UVs // NOTE: a transform could be applied per-param, whats important that the UV slot remains the smae across all of them. hlsl::float32_t2x3 uvTransform = hlsl::float32_t2x3( 1,0,0, 0,1,0 ); + SParameter params[Count] = {}; // to make sure there will be no padding inbetween static_assert(alignof(SParameter)>=alignof(hlsl::float32_t2x3)); @@ -271,6 +271,26 @@ class CFrontendIR final : public CNodePool protected: friend class CFrontendIR; + // copy + virtual _typed_pointer_type copy(CFrontendIR* ir) const = 0; +#define COPY_DEFAULT_IMPL inline _typed_pointer_type copy(CFrontendIR* ir) const override final \ + { \ + auto& pool = ir->getObjectPool(); \ + const auto copyH = pool.emplace > >(); \ + if (auto* const copy = pool.deref(copyH); copyH) \ + *copy = *this; \ + return copyH; \ + } + + // child managment + virtual inline _typed_pointer_type getChildHandle_impl(const uint8_t ix) const {assert(false); return {};} + inline void setChild(const uint8_t ix, _typed_pointer_type newChild) + { + assert(ix newChild) {assert(false);} + // default is no special checks beyond the above struct SInvalidCheckArgs { @@ -291,7 +311,10 @@ class CFrontendIR final : public CNodePool } return false; } - virtual _typed_pointer_type getChildHandle_impl(const uint8_t ix) const = 0; + + virtual bool inline reciprocatable() const {return false;} + // unless you override it, you're not supposed to call it + virtual void reciprocate(IExprNode* dst) const {assert(reciprocatable() && dst);} virtual inline core::string getLabelSuffix() const {return "";} virtual inline std::string_view getChildName_impl(const uint8_t ix) const {return "";} @@ -356,6 +379,10 @@ class CFrontendIR final : public CNodePool } std::construct_at(reinterpret_cast*>(this+1),std::move(params)); } + inline CSpectralVariable(const CSpectralVariable& other) + { + std::uninitialized_copy_n(other.pWonky(),other.getKnotCount(),pWonky()); + } // encapsulation due to padding abuse inline uint8_t& uvSlot() {return pWonky()->knots.uvSlot();} @@ -390,11 +417,11 @@ class CFrontendIR final : public CNodePool { return sizeof(CSpectralVariable)+sizeof(SCreationParams); } - /* - inline uint32_t getSize() const override + // for copying + static inline uint32_t calc_size(const CSpectralVariable& other) { - return sizeof(CSpectralVariable)+sizeof(SCreationParams<1>)+(getKnotCount()-1)*sizeof(SParameter); - }*/ + return sizeof(CSpectralVariable)+sizeof(SCreationParams<1>)+(other.getKnotCount()-1)*sizeof(SParameter); + } inline operator bool() const {return !invalid(SInvalidCheckArgs{.pool=nullptr,.logger=nullptr});} @@ -403,8 +430,13 @@ class CFrontendIR final : public CNodePool { std::destroy_n(pWonky()->knots.params,getKnotCount()); } + inline _typed_pointer_type copy(CFrontendIR* ir) const override final + { + auto& pool = ir->getObjectPool(); + const uint8_t count = getKnotCount(); + return pool.emplace(*this); + } - inline _typed_pointer_type getChildHandle_impl(const uint8_t ix) const override {return {};} inline bool invalid(const SInvalidCheckArgs& args) const override { const auto knotCount = getKnotCount(); @@ -442,13 +474,14 @@ class CFrontendIR final : public CNodePool private: SCreationParams<1>* pWonky() {return reinterpret_cast*>(this+1);} const SCreationParams<1>* pWonky() const {return reinterpret_cast*>(this+1);} - const uint8_t* paramsBeginPadding() const {return pWonky()->knots.params[0].padding; } + const uint8_t* paramsBeginPadding() const {return pWonky()->knots.params[0].padding;} }; // class IUnaryOp : public obj_pool_type::INonTrivial, public IExprNode { protected: inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override final {return child;} + inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override final {child = newChild;} public: inline uint8_t getChildCount() const override final {return 1;} @@ -459,6 +492,8 @@ class CFrontendIR final : public CNodePool { protected: inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override final {return ix ? rhs:lhs;} + inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override final {*(ix ? &rhs:&lhs) = newChild;} + inline std::string_view getChildName_impl(const uint8_t ix) const override final {return ix ? "rhs":"lhs";} public: @@ -476,6 +511,9 @@ class CFrontendIR final : public CNodePool // you can set the children later inline CMul() = default; + + protected: + COPY_DEFAULT_IMPL }; class CAdd final : public IBinOp { @@ -485,6 +523,9 @@ class CFrontendIR final : public CNodePool // you can set the children later inline CAdd() = default; + + protected: + COPY_DEFAULT_IMPL }; // does `1-expression` class CComplement final : public IUnaryOp @@ -494,6 +535,9 @@ class CFrontendIR final : public CNodePool // you can set the children later inline CComplement() = default; + + protected: + COPY_DEFAULT_IMPL }; // Emission nodes are only allowed in BRDF expressions, not BTDF. To allow different emission on both sides, expressed unambigously. // Basic Emitter - note that it is of unit radiance so its easier to importance sample @@ -517,8 +561,10 @@ class CFrontendIR final : public CNodePool // TODO: semantic flags/metadata (symmetries of the profile) protected: - inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override {return {};} + COPY_DEFAULT_IMPL + NBL_API2 bool invalid(const SInvalidCheckArgs& args) const override; + NBL_API2 void printDot(std::ostringstream& sstr, const core::string& selfID) const override; }; //! Special nodes meant to be used as `CMul::rhs`, their behaviour depends on the IContributor in its MUL node relative subgraph. @@ -538,18 +584,24 @@ class CFrontendIR final : public CNodePool { public: inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CBeer);} - inline uint8_t getChildCount() const override {return 1;} + inline uint8_t getChildCount() const override {return 2;} // you can set the members later inline CBeer() = default; - // Effective transparency = exp2(log2(perpTransmittance)/dot(refract(V,X,eta),X)) = exp2(log2(perpTransmittance)*inversesqrt(1.f+(LdotX-1)*rcpEta)) - // Absorption and thickness of the interface combined into a single variable, eta and `LdotX` is taken from the leaf BTDF node. - // With refractions from Dielectrics, we get just `1/LdotX`, for Delta Transmission we get `1/VdotN` since its the same + // Effective transparency = exp2(log2(perpTransmittance)*thickness/dot(refract(V,X,eta),X)) = exp2(log2(perpTransmittance)*thickness*inversesqrt(1.f+(LdotX-1)*rcpEta)) + // Eta and `LdotX` is taken from the leaf BTDF node. With refractions from Dielectrics, we get just `1/LdotX`, for Delta Transmission we get `1/VdotN` since its the same typed_pointer_type perpTransmittance = {}; + typed_pointer_type thickness = {}; protected: - inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override {return perpTransmittance;} + COPY_DEFAULT_IMPL + + inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override {return ix ? perpTransmittance:thickness;} + inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override + { + *(ix ? &perpTransmittance:&thickness) = block_allocator_type::_static_cast(newChild); + } inline std::string_view getChildName_impl(const uint8_t ix) const override {return "Perpendicular\\nTransmittance";} NBL_API2 bool invalid(const SInvalidCheckArgs& args) const override; @@ -572,30 +624,71 @@ class CFrontendIR final : public CNodePool uint8_t reciprocateEtas : 1 = false; protected: + COPY_DEFAULT_IMPL + inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override {return ix ? orientedImagEta:orientedRealEta;} + inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override + { + *(ix ? &orientedImagEta:&orientedRealEta) = block_allocator_type::_static_cast(newChild); + } + NBL_API2 bool invalid(const SInvalidCheckArgs& args) const override; + + inline bool reciprocatable() const override {return true;} + inline void reciprocate(IExprNode* dst) const override + { + (*static_cast(dst) = *this).reciprocateEtas = ~reciprocateEtas; + } + inline std::string_view getChildName_impl(const uint8_t ix) const override {return ix ? "Imaginary":"Real";} NBL_API2 void printDot(std::ostringstream& sstr, const core::string& selfID) const override; }; // Compute Inifinite Scatter and extinction between two parallel infinite planes. + // // It's a specialization of what would be a layer of two identical smooth BRDF and BTDF with arbitrary Fresnel function and beer's - // extinction on the BRDFs (not BTDFs), all applied on a per micro-facet basis (layering per microfacet, not whole surface). + // extinction, all applied on a per micro-facet basis (layering per microfacet, not whole surface) so the NDFs of two layers would be correlated. // - // We actually allow you to use different reflectance nodes R_u and R_b, the NDFs of both layers remain the same but Reflectance Functions to differ. + // We actually allow you to use different reflectance nodes R_u and R_b, the NDFs of both layers remain the same but Reflectance Functions differ. // Note that e.g. using different Etas for the Fresnel used for the top and bottom reflectance will result in a compound Fresnel!=1.0 - // meaning that in such case you can no longer optimize the BTDF contributor into a DeltaTransmission but need a CookTorrance with + // meaning that in such case you can no longer optimize the BTDF contributor into a DeltaTransmission but need a zero-roughness CookTorrance with // an Eta equal to the ratio of the first Eta over the second Eta (note that when they're equal the ratio is 1 which turns into Delta Trans). - // + // This will require you to make an AST that "seems wrong" that is where neither of the Etas of the CFresnel nodes match the Cook Torrance one. + // // Because we split BRDF and BTDF into separate expressions, what this node computes differs depending on where it gets used: + // Note the transformation in the equations at the end just makes the prevention of 0/0 or 0*INF same as for a non-extinctive equation, just check `R_u*R_b < Threshold` + // // BRDF: R_u + (1-R_u)^2 E^2 R_b Sum_{i=0}^{\Inf}{(R_b R_u E^2)^i} = R_u + (1-R_u)^2 E^2 R_b / (1 - R_u R_b E^2) = R_u + (1-R_u)^2 R_b / (E^-2 - R_u R_b) + // -------------------- + // Top BRDF as multiplied with CThinInfiniteScatterCorrection node with `reflectanceTop` + // BTDF matching the BRDF above + // Bottom BRDF matching Top (but corellated so you always hit the same microfacet going back) + // Null BRDF + // Delta Transmission Beer extinction + // Null BRDF + // Top Smooth BRDF with `reflectanceBottom` applied to a Delta Reflection + // ------------------ + // // BTDF: (1-R_u) E (1-R_b) Sum_{i=0}^{\Inf}{(R_b R_u E^2)^i} = (1-R_u) E^2 (1-R_b) / (1 - R_u R_b E^2) = (1-R_u) (1-R_b) / (E^-2 - R_u R_b) - // Note the transformation at the end just makes the prevention of 0/0 or 0*INF same as for a non-extinctive equation, just check `R_u*R_b < Threshold` + // -------------------- + // Bottom BRDF as multiplied with CThinInfiniteScatterCorrection node with `reflectanceTop` + // Null BRDF + // Delta Transmission Beer extinction + // Null BRDF + // Top BRDF as multiplied with CThinInfiniteScatterCorrection node but with `reflectanceBottom` (but corellated so you always hit the same microfacet leading to no refraction) + // ------------------ // - // Note: This node can be also used to model non-linear color shifts of Diffuse BRDF multiple scattering if one plugs in the albedo as the extinction. + // The obvious downside of using this node for transmission is that its impossible to get "milky" glass because a spread of refractions is needed class CThinInfiniteScatterCorrection final : public obj_pool_type::INonTrivial, public IExprNode { protected: - inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override final {return ix ? (ix>1 ? reflectanceBottom:extinction):reflectanceTop;} + COPY_DEFAULT_IMPL + + inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override {return ix ? (ix>1 ? reflectanceBottom:extinction):reflectanceTop;} + inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override + { + *(ix ? (ix>1 ? &reflectanceBottom:&extinction):&reflectanceTop) = newChild; + } + NBL_API2 bool invalid(const SInvalidCheckArgs& args) const override; inline std::string_view getChildName_impl(const uint8_t ix) const override {return ix ? (ix>1 ? "reflectanceBottom":"extinction"):"reflectanceTop";} @@ -644,7 +737,7 @@ class CFrontendIR final : public CNodePool // if roughness inputs are not equal (same scale, same texture) then NDF can be anisotropic in places if (getRougness()[0]!=getRougness()[1]) return false; - // if a reference stretch is used, stretched triangles can turn the distribution isotropic + // if a reference stretch is used, stretched triangles can turn the distribution anisotropic return stretchInvariant(); } // whether the derivative map and roughness is constant regardless of UV-space texture stretching @@ -662,9 +755,17 @@ class CFrontendIR final : public CNodePool // - Delta Reflection -> Any Cook Torrance BxDF with roughness=0 attached as BRDF // - Smooth Conductor -> above multiplied with Conductor-Fresnel // - Smooth Dielectric -> Any Cook Torrance BxDF with roughness=0 attached as BRDF on both sides of a Layer and BTDF multiplied with Dielectric-Fresnel (no imaginary component) - // - Thindielectric -> Any Cook Torrance BxDF multiplied with Dielectric-Fresnel as BRDF in both sides and a Delta Transmission BTDF with `CThinInfiniteScatterCorrection` on the fresnel + // - Thindielectric Correlated -> Cook Torrance BxDF multiplied with Dielectric-Fresnel as top BRDF and its reciprocal as the bottom, then Delta Transmission as BTDF with fresnels of similar Eta + // - Thindielectric Uncorrelated -> BRDF and BTDF same as above, no bottom BRDF, then another layer with delta transmission BTDF + // For Smooth dielectrics it makes sense because fresnel of the interface is the same (microfacet equals macro surface normal, no confusion) + // For Rough its a little more complicated, but using the same BTDF still makes sense. + // Why? Because you enter all microfacets at once with a ray packet, and because their backfaces are correlated you don't refract. + // If we then assume that they're quite big in relation to the thickness, most of the Total Internal Reflection stays within the same microfacet slab. + // So for a single microfacet we have the thindielectric infinite TIR equation with `R_u = (1-Fresnel(VdotH))` and `R_b = (1-Fresnel(-LdotH))`, + // which when convolved with the VNDF (integral of complete TIR equation over all H) can be approximated by substitution of `...dotH` with `...dotN`. + // It also wouldn't matter if we dictate each slab have uniform perpendicular or geometric normal thickness, as the VNDF keeps projected surface area proportional to microfacet angle. + // So the average VdotH or LdotH are equal to NdotV and NdotL respectively, which doesn't guarantee average `inversesqrt(1-VdotH*VdotH)` equals `inversesqrt(1-NdotV*NdotV)` but difference is small. // - Plastic -> Similar to layering the above over Diffuse BRDF, its of uttmost importance that the BTDF is Delta Transmission. - // If one wants to emulate non-linear diffuse TIR color shifts, abuse `CThinInfiniteScatterCorrection`. class CDeltaTransmission final : public IBxDF { public: @@ -673,7 +774,7 @@ class CFrontendIR final : public CNodePool inline CDeltaTransmission() = default; protected: - inline _typed_pointer_type getChildHandle_impl(const uint8_t ix) const override {return {};} + COPY_DEFAULT_IMPL }; //! Because of Schussler et. al 2017 every one of these nodes splits into 2 (if no L dependence) or 3 during canonicalization // Base diffuse node @@ -687,8 +788,10 @@ class CFrontendIR final : public CNodePool SBasicNDFParams ndParams = {}; protected: - inline _typed_pointer_type getChildHandle_impl(const uint8_t ix) const override {return {};} + COPY_DEFAULT_IMPL + NBL_API2 bool invalid(const SInvalidCheckArgs& args) const override; + NBL_API2 void printDot(std::ostringstream& sstr, const core::string& selfID) const override; }; // Supports anisotropy for all models @@ -714,49 +817,123 @@ class CFrontendIR final : public CNodePool // BRDFs and BTDFs components into separate expressions, and also importance sample much better, for details see comments in CTrueIR. typed_pointer_type orientedRealEta = {}; // - NDF ndf = NDF::GGX; + NDF ndf : 7 = NDF::GGX; + uint8_t reciprocateEta : 1 = false; protected: + COPY_DEFAULT_IMPL + inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override {return orientedRealEta;} + inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override {orientedRealEta = block_allocator_type::_static_cast(newChild);} + NBL_API2 bool invalid(const SInvalidCheckArgs& args) const override; + + inline bool reciprocatable() const override {return true;} + inline void reciprocate(IExprNode* dst) const override + { + (*static_cast(dst) = *this).reciprocateEta = ~reciprocateEta; + } inline core::string getLabelSuffix() const override {return ndf!=NDF::GGX ? "\\nNDF = Beckmann":"\\nNDF = GGX";} inline std::string_view getChildName_impl(const uint8_t ix) const override {return "Oriented η";} NBL_API2 void printDot(std::ostringstream& sstr, const core::string& selfID) const override; }; +#undef COPY_DEFAULT_IMPL #undef TYPE_NAME_STR - // Each material comes down to this, YOU MUST NOT MODIFY THE NODES AFTER ADDING THEIR PARENT TO THE ROOT NODES! - // TODO: shall we copy and hand out a new handle? - inline std::span> getMaterials() {return m_rootNodes;} - inline bool addMaterial(const typed_pointer_type rootNode, system::logger_opt_ptr logger) + // basic utilities + inline typed_pointer_type createMul(const typed_pointer_type lhs, const typed_pointer_type rhs) { - if (valid(rootNode,logger)) - { - m_rootNodes.push_back(rootNode); - return true; - } - return false; + if (!lhs || !rhs) // acceptable premaute optimization + return {}; + const auto mulH = getObjectPool().emplace(); + auto* const mul = getObjectPool().deref(mulH); + mul->lhs = lhs; + mul->rhs = rhs; + return mulH; + } + inline typed_pointer_type createAdd(const typed_pointer_type lhs, const typed_pointer_type rhs) + { + if (!lhs) + return rhs; + if (!rhs) + return lhs; + const auto addH = getObjectPool().emplace(); + auto* const add = getObjectPool().deref(addH); + add->lhs = lhs; + add->rhs = rhs; + return addH; + } + inline typed_pointer_type createFMA(const typed_pointer_type a, const typed_pointer_type b, const typed_pointer_type c) + { + return createAdd(createMul(a,b),c); + } + inline typed_pointer_type createWeightedSum(const typed_pointer_type x0, const typed_pointer_type w0, const typed_pointer_type x1, const typed_pointer_type w1) + { + return createAdd(createMul(x0,w0),createMul(x1,w1)); + } + inline typed_pointer_type createComplement(const typed_pointer_type child) + { + if (!child) + return {}; + const auto complH = getObjectPool().emplace(); + getObjectPool().deref(complH)->child = child; + return complH; } - // To quickly make a matching backface material from a frontface or vice versa - NBL_API2 typed_pointer_type reciprocate(const typed_pointer_type other); + // To quickly make a fresnel NBL_API2 typed_pointer_type createNamedFresnel(const std::string_view name); inline typed_pointer_type createConstantMonochromeRealFresnel(const hlsl::float32_t orientedRealEta) { - const auto fresnelH = getObjectPool().emplace(); + auto& pool = getObjectPool(); + const auto fresnelH = pool.emplace(); CSpectralVariable::SCreationParams<1> params = {}; params.knots.params[0].scale = orientedRealEta; - if (auto* const fresnel=getObjectPool().deref(fresnelH); fresnel) - fresnel->orientedRealEta = getObjectPool().emplace(std::move(params)); + if (auto* const fresnel=pool.deref(fresnelH); fresnel) + fresnel->orientedRealEta = pool.emplace(std::move(params)); return fresnelH; } + // To quickly make a matching backface BxDF from a frontface or vice versa + NBL_API2 typed_pointer_type reciprocate(const typed_pointer_type orig); + + // a deep copy of the layer stack, wont copy the BxDFs + NBL_API2 typed_pointer_type copyLayers(const typed_pointer_type orig); + // Reverse the linked list of layers and reciprocate their Etas + NBL_API2 typed_pointer_type reverse(const typed_pointer_type orig); + + // first query, we check presence of btdf layers all the way through the layer stack + inline bool transmissive(const typed_pointer_type rootHandle) const + { + auto& pool = getObjectPool(); + for (auto layer=pool.deref(rootHandle); layer; layer=pool.deref(layer->coated)) + { + // it takes only one layer without transmission to break the chain + if (!layer->btdf) + return false; + } + return true; + } + // IMPORTANT: Two BxDFs are not allowed to be multiplied together. // NOTE: Right now all Spectral Variables are required to be Monochrome or 3 bucket fixed semantics, all the same wavelength. // Some things we can't check such as the compatibility of the BTDF with the BRDF (matching indices of refraction, etc.) - bool valid(const typed_pointer_type rootHandle, system::logger_opt_ptr logger) const; + NBL_API2 bool valid(const typed_pointer_type rootHandle, system::logger_opt_ptr logger) const; + + inline std::span> getMaterials() {return m_rootNodes;} + + // Each material comes down to this, YOU MUST NOT MODIFY THE NODES AFTER ADDING THEIR PARENT TO THE ROOT NODES! + // TODO: shall we copy and hand out a new handle? Allow RootNode from a foreign const pool + inline bool addMaterial(const typed_pointer_type rootNode, system::logger_opt_ptr logger) + { + if (valid(rootNode,logger)) + { + m_rootNodes.push_back(rootNode); + return true; + } + return false; + } // For Debug Visualization struct SDotPrinter final diff --git a/include/nbl/ext/MitsubaLoader/SContext.h b/include/nbl/ext/MitsubaLoader/SContext.h index d91fd98a9b..17b8f48bb3 100644 --- a/include/nbl/ext/MitsubaLoader/SContext.h +++ b/include/nbl/ext/MitsubaLoader/SContext.h @@ -87,6 +87,7 @@ struct SContext final #endif core::smart_refctd_ptr frontIR; // common frontend nodes + frontend_ir_t::typed_pointer_type unityFactor; frontend_ir_t::typed_pointer_type errorBRDF; frontend_ir_t::typed_pointer_type errorMaterial, unsupportedPhong, unsupportedWard; frontend_ir_t::typed_pointer_type deltaTransmission; @@ -95,6 +96,7 @@ struct SContext final { Albedo, Opacity, + Weight, MitsubaExtraFactor, Count }; diff --git a/src/nbl/asset/interchange/CImageLoaderJPG.cpp b/src/nbl/asset/interchange/CImageLoaderJPG.cpp index 1db5e16ac2..4c8445190b 100644 --- a/src/nbl/asset/interchange/CImageLoaderJPG.cpp +++ b/src/nbl/asset/interchange/CImageLoaderJPG.cpp @@ -157,10 +157,11 @@ bool CImageLoaderJPG::isALoadableFileFormat(system::IFile* _file, const system:: if (!_file) return false; - uint32_t header = 0; + uint16_t soiMarker = 0; system::IFile::success_t success; - _file->read(success, &header, 6, sizeof(uint32_t)); - return success && ((header&0x00FFD8FFu)==0x00FFD8FFu || header == 0x4a464946 || header == 0x4649464a || header == 0x66697845u || header == 0x70747468u); // maybe 0x4a464946 can go + _file->read(success, &soiMarker, 0, sizeof(uint16_t)); + constexpr auto JPEG_VALID_SOI_MARKER = 0xD8FF; + return success && (soiMarker == JPEG_VALID_SOI_MARKER); #endif } diff --git a/src/nbl/asset/material_compiler3/CFrontendIR.cpp b/src/nbl/asset/material_compiler3/CFrontendIR.cpp index c4b7fd49a8..d77ac287da 100644 --- a/src/nbl/asset/material_compiler3/CFrontendIR.cpp +++ b/src/nbl/asset/material_compiler3/CFrontendIR.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2025 - DevSH Graphics Programming Sp. z O.O. +// Copyright (C) 2022-2025 - DevSH Graphics Programming Sp. z O.O. // This file is part of the "Nabla Engine". // For conditions of distribution and use, see copyright notice in nabla.h #include "nbl/asset/material_compiler3/CFrontendIR.h" @@ -32,6 +32,11 @@ bool CFrontendIR::CBeer::invalid(const SInvalidCheckArgs& args) const args.logger.log("Perpendicular Transparency node of correct type must be attached, but is %u of type %s",ELL_ERROR,perpTransmittance,args.pool->getTypeName(perpTransmittance).data()); return true; } + if (const auto* const thick=args.pool->getObjectPool().deref(thickness); !thick || thick->getKnotCount()!=1) + { + args.logger.log("Monochromatic Thickness node must be attached, but is %u of type %s",ELL_ERROR,thickness,args.pool->getTypeName(thickness).data()); + return true; + } return false; } @@ -111,18 +116,127 @@ bool CFrontendIR::CCookTorrance::invalid(const SInvalidCheckArgs& args) const } -auto CFrontendIR::reciprocate(const typed_pointer_type other) -> typed_pointer_type +auto CFrontendIR::reciprocate(const typed_pointer_type orig) -> typed_pointer_type { - if (const auto* in=getObjectPool().deref(block_allocator_type::_static_cast(other)); in) + auto& pool = getObjectPool(); + struct SEntry + { + typed_pointer_type handle; + bool visited = false; + }; + core::vector stack; + stack.reserve(32); + stack.push_back({.handle=orig}); + // use a hashmap because of holes in child arrays + core::unordered_map,typed_pointer_type> substitutions; + while (!stack.empty()) { - auto fresnelH = getObjectPool().emplace(); - auto* fresnel = getObjectPool().deref(fresnelH); - *fresnel = *in; - fresnel->reciprocateEtas = ~in->reciprocateEtas; - return fresnelH; + auto& entry = stack.back(); + const auto* const node = pool.deref(entry.handle); + if (!node) // this is an error + return {}; + const auto childCount = node->getChildCount(); + if (entry.visited) + { + entry.visited = true; + for (uint8_t c=0; cgetChildHandle(c); + if (auto child=pool.deref(childH); !child) + continue; // this is not an error + stack.push_back({.handle=childH}); + } + } + else + { + const bool needToReciprocate = node->reciprocatable(); + bool needToCopy = needToReciprocate; + // if one descendant has changed then we need to copy node + if (!needToCopy) + { + uint8_t c = 0; + for (; cgetChildHandle(c)); found!=substitutions.end()) + break; + } + needToCopy = c!=childCount; + } + if (needToCopy) + { + const auto copyH = node->copy(this); + // copy copies everything including children + auto* const copy = pool.deref(copyH); + if (!copy) + return {}; + if (needToReciprocate) + node->reciprocate(copy); + // only changed children need to be set + for (uint8_t c=0; cgetChildHandle(c); + if (!childH) + continue; + if (auto found=substitutions.find(childH); found!=substitutions.end()) + copy->setChild(c,found->second); + } + substitutions.insert({entry.handle,copyH}); + } + stack.pop_back(); + } + } + // there was nothing to reciprocate in the expression stack + if (substitutions.empty()) + return orig; + return substitutions[orig]; +} + +auto CFrontendIR::copyLayers(const typed_pointer_type orig) -> typed_pointer_type +{ + auto& pool = getObjectPool(); + auto copyH = pool.emplace(); + { + auto* outLayer = pool.deref(copyH); + for (const auto* layer=pool.deref(orig); true; layer=pool.deref(layer->coated)) + { + *outLayer = *layer; + if (!layer->coated) + { + // terminate the new stack + outLayer->coated = {}; + break; + } + // continue the new stack + outLayer->coated = pool.emplace(); + outLayer = pool.deref(outLayer->coated); + } + } + return copyH; +} + +auto CFrontendIR::reverse(const typed_pointer_type orig) -> typed_pointer_type +{ + auto& pool = getObjectPool(); + // we build the new linked list from the tail + auto copyH = pool.emplace(); + { + auto* outLayer = pool.deref(copyH); + typed_pointer_type underLayerH={}; + for (const auto* layer=pool.deref(orig); true; layer=pool.deref(layer->coated)) + { + outLayer->coated = underLayerH; + // we reciprocate everything because numerator and denominator switch (top and bottom of layer stack) + outLayer->brdfBottom = reciprocate(layer->brdfTop)._const_cast(); + outLayer->btdf = reciprocate(layer->btdf)._const_cast(); + outLayer->brdfTop = reciprocate(layer->brdfBottom)._const_cast(); + if (!layer->coated) + break; + underLayerH = copyH; + copyH = pool.emplace(); + outLayer = pool.deref(copyH); + } } - assert(false); // unimplemented - return {}; + return copyH; } auto CFrontendIR::createNamedFresnel(const std::string_view name) -> typed_pointer_type diff --git a/src/nbl/ext/MitsubaLoader/CMitsubaLoader.cpp b/src/nbl/ext/MitsubaLoader/CMitsubaLoader.cpp index 6fc11258d4..bd90b15887 100644 --- a/src/nbl/ext/MitsubaLoader/CMitsubaLoader.cpp +++ b/src/nbl/ext/MitsubaLoader/CMitsubaLoader.cpp @@ -164,7 +164,7 @@ bool CMitsubaLoader::isALoadableFileFormat(system::IFile* _file, const system::l // TODO: make configurable -constexpr bool PrintMaterialDot3 = true; +constexpr bool PrintMaterialDot3 = false; system::path DebugDir("D:\\work\\Nabla-master\\examples_tests\\15_MitsubaLoader\\bin"); // void SContext::writeDot3File(system::ISystem* system, const system::path& filepath, frontend_ir_t::SDotPrinter& printer) @@ -337,7 +337,7 @@ SAssetBundle CMitsubaLoader::loadAsset(system::IFile* _file, const IAssetLoader: } ctx.transferMetadata(); - return asset::SAssetBundle(std::move(result.metadata),{std::move(ctx.scene)}); + return SAssetBundle(std::move(result.metadata),{std::move(ctx.scene)}); } } @@ -371,6 +371,7 @@ auto SContext::getMaterial( { // only front-face on top layer get changed, Mistuba XML only allows for unobscured/uncoated emission { + // TODO replace with utility auto combinerH = frontPool.emplace(); auto* const combiner = frontPool.deref(combinerH); combiner->lhs = foundEmitter->second._const_cast(); @@ -418,62 +419,72 @@ parameter_t SContext::getTexture(const CElementTexture* const rootTex, hlsl::flo SAssetBundle viewBundle = interm_getImageViewInHierarchy(tex->bitmap.filename,/*ICPUScene::MATERIAL_IMAGES_HIERARCHY_LEVELS_BELOW*/1); if (auto contents=viewBundle.getContents(); !contents.empty()) { - retval.view = IAsset::castDown(*contents.begin()); + auto view = IAsset::castDown(*contents.begin()); + const auto format = view->getCreationParameters().format; if (bitmap.channel!=CElementTexture::Bitmap::CHANNEL::INVALID) retval.viewChannel = bitmap.channel-CElementTexture::Bitmap::CHANNEL::R; - // get sampler parameters - using tex_clamp_e = asset::ISampler::E_TEXTURE_CLAMP; - auto getWrapMode = [](CElementTexture::Bitmap::WRAP_MODE mode) + if (retval.viewChannel(bitmap.maxAnisotropy),1u); + // TODO: embed the gamma in the material compiler Frontend + // or adjust gamma on pixels (painful and long process) + //assert(std::isnan(bitmap.gamma)); + auto& transform = *outUvTransform; + transform[0][0] = bitmap.uscale; + transform[0][2] = bitmap.uoffset; + transform[1][1] = bitmap.vscale; + transform[1][2] = bitmap.voffset; } - params.AnisotropicFilter = core::max(hlsl::findMSB(bitmap.maxAnisotropy),1u); - // TODO: embed the gamma in the material compiler Frontend - // or adjust gamma on pixels (painful and long process) - assert(std::isnan(bitmap.gamma)); - auto& transform = *outUvTransform; - transform[0][0] = bitmap.uscale; - transform[0][2] = bitmap.uoffset; - transform[1][1] = bitmap.vscale; - transform[1][2] = bitmap.voffset; + else + inner.params.logger.log( + "Failed to parse CElementTexture bitmap for %p with id %s from path \"%s\", channel swizzle %s requested out of range of format %s", + LoggerError,tex,tex ? tex->id.c_str():"",tex->bitmap.filename,system::to_string(bitmap.channel),system::to_string(format) + ); } else - inner.params.logger.log("Failed to load bitmap texture for %p with id %s",LoggerError,tex,tex ? tex->id.c_str():""); + inner.params.logger.log("Failed to load bitmap texture for %p with id %s from path \"%s\"",LoggerError,tex,tex ? tex->id.c_str():"",tex->bitmap.filename); } else inner.params.logger.log("Failed to unroll texture scale for %p with id %s",LoggerError,rootTex,rootTex ? rootTex->id.c_str():""); @@ -500,10 +511,21 @@ hlsl::float32_t2x3 SContext::getParameters(const std::span out, c if (src.texture) { const auto param = getTexture(src.texture,&retval); + const auto formatChannelCount = getFormatChannelCount(param.view ? param.view->getCreationParameters().format:E_FORMAT::EF_UNKNOWN); + if (src.texture->bitmap.channel==CElementTexture::Bitmap::CHANNEL::INVALID && formatChannelCount>1 && out.size()>formatChannelCount) + { + for (auto c=0; c::signaling_NaN(); + return hlsl::math::linalg::diagonal(0.f);; + } for (auto c=0; c1 && src.texture->bitmap.channel==CElementTexture::Bitmap::CHANNEL::INVALID) + out[c].viewChannel += c; + out[c].view = param.view; + out[c].sampler = param.sampler; } } else @@ -527,7 +549,17 @@ hlsl::float32_t2x3 SContext::getParameters(const std::span out, con { auto retval = hlsl::math::linalg::diagonal(0.f); if (src.texture) - std::fill(out.begin(),out.end(),getTexture(src.texture,&retval)); + { + const auto param = getTexture(src.texture,&retval); + for (auto c=0; c frontend_ir return retval; } +// TODO: include source debug information / location, e.g. XML path, line and column in the nodes auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileWriter) -> frontend_material_t { auto& frontPool = frontIR->getObjectPool(); @@ -624,22 +657,23 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW auto createFactorNode = [&](const CElementTexture::SpectrumOrTexture& factor, const ECommonDebug debug)->auto { - const auto mulH = frontPool.emplace(); - auto* mul = frontPool.deref(mulH); spectral_var_t::SCreationParams<3> params = {}; params.knots.uvTransform = getParameters(params.knots.params,factor); params.getSemantics() = spectral_var_t::Semantics::Fixed3_SRGB; const auto factorH = frontPool.emplace(std::move(params)); frontPool.deref(factorH)->debugInfo = commonDebugNames[uint16_t(debug)]._const_cast(); - mul->rhs = factorH; - return mulH; + return factorH; + }; + + struct SDerivativeMap + { + // TODO: derivative map SParameter[2] }; + // TODO: take `SParameter[2]` for the derivative maps auto createCookTorrance = [&](const CElementBSDF::RoughSpecularBase* base, const frontend_ir_t::typed_pointer_type fresnelH, const CElementTexture::SpectrumOrTexture& specularReflectance)->auto { - const auto handle = createFactorNode(specularReflectance,ECommonDebug::MitsubaExtraFactor); - // rhs already filled, waiting for contributor in lhs subtree const auto mulH = frontPool.emplace(); - frontPool.deref(handle)->lhs = mulH; + const auto factorH = createFactorNode(specularReflectance,ECommonDebug::MitsubaExtraFactor); { auto* mul = frontPool.deref(mulH); const auto ctH = frontPool.emplace(); @@ -664,6 +698,7 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW // TODO: check if UV transform is the same and warn if not getParameters({roughness.data()+1,1},base->alphaV); } + // TODO: derivative maps } else roughness[0].scale = roughness[1].scale = 0.f; @@ -675,13 +710,13 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW mul->lhs = ctH; mul->rhs = fresnelH; } - return handle; + return frontIR->createMul(mulH,factorH); }; auto createOrenNayar = [&](const CElementTexture::SpectrumOrTexture& albedo, const CElementTexture::FloatOrTexture& alphaU, const CElementTexture::FloatOrTexture& alphaV)->auto { - const auto mulH = createFactorNode(albedo,ECommonDebug::Albedo); + const auto orenNayarH = frontPool.emplace(); + const auto factorH = createFactorNode(albedo,ECommonDebug::Albedo); { - const auto orenNayarH = frontPool.emplace(); auto* orenNayar = frontPool.deref(orenNayarH); // TODO: factor this out between Oren-Nayar and Cook Torrance auto roughness = orenNayar->ndParams.getRougness(); @@ -691,16 +726,75 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW // TODO: check if UV transform is the same and warn if not getParameters({roughness.data()+1,1},alphaV); } - frontPool.deref(mulH)->lhs = orenNayarH; + // TODO: derivative maps + } + return frontIR->createMul(orenNayarH,factorH); + }; + + auto fillCoatingLayer = [&](frontend_ir_t::CLayer* layer, const T& element, const bool rough, const frontend_ir_t::typed_pointer_type extinctionH={})->void + { + const auto fresnelH = frontIR->createConstantMonochromeRealFresnel(element.intIOR/element.extIOR); + const auto dielectricH = createCookTorrance(rough ? &element:nullptr,fresnelH,element.specularReflectance); + layer->brdfTop = dielectricH; + const auto transH = frontPool.emplace(); + { + auto* const trans = frontPool.deref(transH); + if (extinctionH) + trans->lhs = frontIR->createMul(deltaTransmission._const_cast(),extinctionH); + else + trans->lhs = deltaTransmission._const_cast(); + const auto factorH = createFactorNode(element.specularTransmittance,ECommonDebug::MitsubaExtraFactor); + trans->rhs = frontIR->createMul(fresnelH,factorH); + } + layer->btdf = transH; + // identical BRDF on the bottom, to have correct multiscatter + layer->brdfBottom = dielectricH; + }; + + using expr_handle_t = frontend_ir_t::typed_pointer_type; + auto createWeightedSum = [&](frontend_ir_t::CLayer* out, material_t A, const expr_handle_t w0, material_t B, const expr_handle_t w1)->void + { + while (true) + { + auto* const a = frontPool.deref(A); + auto* const b = frontPool.deref(B); + // I don't actually need to check if the child Expressions are non-empty, the CFrontendIR utilities nicely carry through NOOPs + out->brdfTop = frontIR->createWeightedSum(a ? a->brdfTop:expr_handle_t{},w0,b ? b->brdfTop:expr_handle_t{},w1); + out->btdf = frontIR->createWeightedSum(a ? a->btdf :expr_handle_t{},w0,b ? b->btdf:expr_handle_t{},w1); + out->brdfBottom = frontIR->createWeightedSum(a ? a->brdfBottom:expr_handle_t{},w0,b ? b->brdfBottom:expr_handle_t{},w1); + if (a && a->coated || b && b->coated) + { + out = frontPool.deref(out->coated = frontPool.emplace()); + A = a ? a->coated:material_t{}; + B = b ? b->coated:material_t{}; + } + else + { + out->coated = {}; + break; + } } - return mulH; }; + struct SEntry + { + inline bool operator==(const SEntry& other) const {return bsdf==other.bsdf;} + + const CElementBSDF* bsdf; + // SDerivativeMap derivMap; + }; + struct HashEntry + { + inline size_t operator()(const SEntry& entry) const {return std::hash()(entry.bsdf);} + }; + core::unordered_map localCache; + localCache.reserve(16); // the layer returned will never have a bottom BRDF - auto createMistubaLeaf = [&](const CElementBSDF* _bsdf/*TODO: debug source information*/)->frontend_ir_t::typed_pointer_type + auto createMistubaLeaf = [&](const SEntry& entry)->frontend_ir_t::typed_pointer_type { + const CElementBSDF* _bsdf = entry.bsdf; auto retval = frontPool.emplace(); - auto* const leaf = frontPool.deref(retval); + auto* leaf = frontPool.deref(retval); switch (_bsdf->type) { case CElementBSDF::DIFFUSE: [[fallthrough]]; @@ -782,10 +876,10 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW if (!isConductor) { // want a specularTransmittance instead of SpecularReflectance factor - const auto btdfH = createFactorNode(_bsdf->dielectric.specularTransmittance,ECommonDebug::MitsubaExtraFactor); + const auto factorH = createFactorNode(_bsdf->dielectric.specularTransmittance,ECommonDebug::MitsubaExtraFactor); + expr_handle_t btdfH; { const auto* const brdf = frontPool.deref(brdfH); - auto* const btdf = frontPool.deref(btdfH); // make the trans node refraction-less for thin dielectric and apply thin scattering correction to the transmissive fresnel if (_bsdf->type==CElementBSDF::THINDIELECTRIC) { @@ -803,34 +897,34 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW mul->lhs = deltaTransmission._const_cast(); mul->rhs = thinInfiniteScatterH; } - // we're rethreading the fresnel here - btdf->lhs = correctedTransmissionH; +#ifdef UNCORRELLATED + // We need to attach the other glass interface as another layer because the Etas need to be reciprocated because the interactions are flipped + leaf->coated = frontIR->reverse(retval); + frontPool.deref(leaf->coated)->btdf = deltaTransmission._const_cast(); + // we're rethreading the fresnel here, but needed to be careful to not apply thindielectric scatter correction and volumetric absorption / Mitsuba extra factor twice +#else + leaf->brdfBottom = frontIR->reciprocate(brdfH)._const_cast(); +#endif + btdfH = correctedTransmissionH; + } + else + { + // beautiful thing we can reuse the same BxDF nodes for a transmission function without touching Etas or anything! + btdfH = brdf->lhs; + leaf->brdfBottom = brdfH; } - else // beautiful thing we can reuse the same nodes for a transmission function without touching Etas or anything! - btdf->lhs = brdf->lhs; } - leaf->btdf = btdfH; + leaf->btdf = frontIR->createMul(btdfH,factorH); } break; } case CElementBSDF::PLASTIC: [[fallthrough]]; case CElementBSDF::ROUGHPLASTIC: { - const auto fresnelH = frontIR->createConstantMonochromeRealFresnel(_bsdf->plastic.intIOR/_bsdf->plastic.extIOR); - const auto dielectricH = createCookTorrance(_bsdf->type==CElementBSDF::ROUGHPLASTIC ? &_bsdf->plastic:nullptr,fresnelH,_bsdf->plastic.specularReflectance); - leaf->brdfTop = dielectricH; - const auto transH = frontPool.emplace(); - { - auto* const mul = frontPool.deref(transH); - mul->lhs = deltaTransmission._const_cast(); - const auto transmittanceH = createFactorNode(_bsdf->plastic.specularTransmittance,ECommonDebug::MitsubaExtraFactor); - frontPool.deref(transmittanceH)->lhs = fresnelH; - mul->rhs = transmittanceH; - } - leaf->btdf = transH; + fillCoatingLayer(leaf,_bsdf->plastic,_bsdf->type==CElementBSDF::ROUGHPLASTIC); // shall we plug albedo back inside as an extinction factor !? const auto substrateH = frontPool.emplace(); { - // TODO: `_bsdf->plastic.nonlinear` , do we use beer? + // TODO: `_bsdf->plastic.nonlinear` to let the backend know the multiscatter should match provided albedo? Basically provided albedo is not the color at every bounce but after all bounces auto* const substrate = frontPool.deref(substrateH); substrate->brdfTop = createOrenNayar(_bsdf->plastic.diffuseReflectance,_bsdf->plastic.alphaU,_bsdf->plastic.alphaV); } @@ -861,81 +955,220 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW } leaf->brdfTop = diffTransH; leaf->btdf = diffTransH; + // By default, all non-transmissive scattering models in Mitsuba are one-sided => all transmissive are two sided + leaf->brdfBottom = diffTransH; break; } default: assert(false); // we shouldn't get this case here - return errorMaterial._const_cast(); + retval = errorMaterial._const_cast(); + break; } - assert(!leaf->brdfBottom); + leaf = frontPool.deref(retval); + assert(leaf->brdfTop); return retval; }; // Post-order Depth First Traversal (create children first, then create parent) struct SStackEntry { - const CElementBSDF* bsdf; - uint64_t visited : 1 = false; - uint64_t expectedNodeType : 2 = false; - uint64_t unused : 61 = false; + SEntry immutable; + bool visited = false; }; core::vector stack; stack.reserve(128); - stack.emplace_back() = { - .bsdf = bsdf - }; - // for the static casts of handles - using block_allocator_t = frontend_ir_t::obj_pool_type::mem_pool_type::block_allocator_type; - using node_t = frontend_ir_t::typed_pointer_type; - core::unordered_map localCache; - while (!stack.front().visited) + stack.emplace_back() = {.immutable={.bsdf=bsdf}}; + // + frontend_ir_t::typed_pointer_type rootH = {}; + while (!stack.empty()) { - const auto entry = stack.back(); - assert(entry.bsdf); - if (!entry.visited) + auto& entry = stack.back(); + const auto* const _bsdf = entry.immutable.bsdf; + assert(_bsdf); + // we only do post-dfs for non-leafs + if (_bsdf->isMeta() && !entry.visited) { - node_t newNodeH = {}; // TODO: {errorFactor,errorBRDF,errorLayer}[entry.expectedNodeType]; - if (entry.bsdf->isMeta()) + if (_bsdf->isMeta()) { - return errorMaterial;// temporary - switch(entry.bsdf->type) + const auto& meta_common = _bsdf->meta_common; + assert(meta_common.childCount); + switch(_bsdf->type) { + case CElementBSDF::COATING: [[fallthrough]]; + case CElementBSDF::ROUGHCOATING: + assert(meta_common.childCount==1); + break; + case CElementBSDF::BUMPMAP: [[fallthrough]]; + case CElementBSDF::NORMALMAP: + assert(meta_common.childCount==1); + // TODO : create the derivative map and cache it + break; + case CElementBSDF::MASK: + assert(meta_common.childCount==1); + break; + case CElementBSDF::MIXTURE_BSDF: + break; + case CElementBSDF::BLEND_BSDF: + assert(meta_common.childCount==2); + break; + case CElementBSDF::TWO_SIDED: + assert(meta_common.childCount<=2); + break; + default: + assert(false); // we shouldn't get this case here + break; + } + // TODO : make sure child gets pushed with derivative map info + for (decltype(meta_common.childCount) i=0; iisMeta()) + { + const auto childCount = _bsdf->meta_common.childCount; + auto getChildFromCache = [&](const CElementBSDF* child)->frontend_ir_t::typed_pointer_type + { + return localCache[{.bsdf=child/*, TODO: copy the current normalmap stuff from entry or self if self is bump map*/}]._const_cast(); + }; + switch(_bsdf->type) + { + case CElementBSDF::COATING: [[fallthrough]]; + case CElementBSDF::ROUGHCOATING: + { + const auto coatingH = frontPool.emplace(); + auto* const coating = frontPool.deref(coatingH); + // the top layer + const auto& sigmaA = _bsdf->coating.sigmaA; + const bool hasExtinction = sigmaA.texture||sigmaA.value.type==SPropertyElementData::FLOAT&&sigmaA.value.fvalue!=0.f; + const auto beerH = hasExtinction ? frontPool.emplace():frontend_ir_t::typed_pointer_type{}; + if (auto* const beer=frontPool.deref(beerH); beer) + { + spectral_var_t::SCreationParams<3> params = {}; + params.knots.uvTransform = getParameters(params.knots.params,sigmaA); + params.getSemantics() = spectral_var_t::Semantics::Fixed3_SRGB; + beer->perpTransmittance = frontPool.emplace(std::move(params)); + } + fillCoatingLayer(coating,_bsdf->coating,_bsdf->type==CElementBSDF::ROUGHCOATING,beerH); + // attach the nested as layer + coating->coated = getChildFromCache(_bsdf->mask.bsdf[0]); + newMaterialH = coatingH; + break; + } case CElementBSDF::MASK: { - const auto maskH = createFactorNode(entry.bsdf->mask.opacity,ECommonDebug::Opacity); + const auto maskH = frontPool.emplace(); auto* const mask = frontPool.deref(maskH); -// mask->lhs = ; - newNodeH = maskH; + // + const auto nestedH = getChildFromCache(_bsdf->mask.bsdf[0]); + const auto* const nested = frontPool.deref(nestedH); + assert(nested && nested->brdfTop); + const auto opacityH = createFactorNode(_bsdf->mask.opacity,ECommonDebug::Opacity); + mask->brdfTop = frontIR->createMul(nested->brdfTop,opacityH); + { + mask->btdf = frontIR->createAdd( + frontIR->createMul(deltaTransmission._const_cast(),frontIR->createComplement(opacityH)), + frontIR->createMul(nested->btdf,opacityH) + ); + } + mask->brdfBottom = frontIR->createMul(nested->brdfBottom,opacityH); + newMaterialH = maskH; + break; + } + case CElementBSDF::BUMPMAP: [[fallthrough]]; + case CElementBSDF::NORMALMAP: + { + // we basically ignore and skip because derivative map already applied + newMaterialH = getChildFromCache(_bsdf->mask.bsdf[0]); + break; + } + case CElementBSDF::MIXTURE_BSDF: + { + const auto mixH = frontPool.emplace(); + auto* const mix = frontPool.deref(mixH); + const uint8_t realChildCount = hlsl::min(childCount,_bsdf->mixturebsdf.weightCount); + const auto firstH = realChildCount>=1 ? getChildFromCache(_bsdf->mixturebsdf.bsdf[0]):frontend_ir_t::typed_pointer_type{}; + spectral_var_t::SCreationParams<1> firstParams = {}; + firstParams.knots.params[0].scale = _bsdf->mixturebsdf.weights[0]; + const auto firstWeightH = frontPool.emplace(std::move(firstParams)); + if (realChildCount<2) + createWeightedSum(mix,firstH,firstWeightH,{},{}); + else + for (uint8_t c=1; cmixturebsdf.bsdf[c]); + spectral_var_t::SCreationParams<1> params = {}; + params.knots.params[0].scale = _bsdf->mixturebsdf.weights[c]; + // don't feel like spending time on this, since its never used, trust CTrueIR optimizer to remove this during canonicalization + createWeightedSum(mix,mixH,unityFactor._const_cast(),otherH,frontPool.emplace(std::move(params))); + } + newMaterialH = mixH; + break; + } + case CElementBSDF::BLEND_BSDF: + { + const auto blendH = frontPool.emplace(); + const auto tH = createFactorNode(_bsdf->blendbsdf.weight,ECommonDebug::Weight); + const auto tComplementH = frontIR->createComplement(tH); + const auto loH = getChildFromCache(_bsdf->blendbsdf.bsdf[0]); + const auto hiH = getChildFromCache(_bsdf->blendbsdf.bsdf[1]); + createWeightedSum(frontPool.deref(blendH),loH,tComplementH,hiH,tH); + newMaterialH = blendH; + break; + } + case CElementBSDF::TWO_SIDED: + { + const auto origFrontH = getChildFromCache(_bsdf->twosided.bsdf[0]); + const auto chosenBackH = childCount!=1 ? getChildFromCache(_bsdf->twosided.bsdf[1]):origFrontH; + // Mitsuba does a mad thing where it will pick the BSDF to use based on NdotV which would normally break the required reciprocity of a BxDF + // but then it saves the day by disallowing transmission on the combination two BxDFs it layers together. Lets do the same. + if (const bool firstIsTransmissive=frontIR->transmissive(origFrontH); firstIsTransmissive && (childCount==1 || frontIR->transmissive(chosenBackH))) + { + logger.log("Mitsuba cannot be used to glue two transmissive BxDF Layer Stacks together!",LoggerError,debugName.c_str()); + break; + } + // this is the stack we attach to the back, but we need to reverse it + const auto backH = frontIR->reverse(chosenBackH); + if (!backH) + { + logger.log("Failed to reverse and reciprocate the BxDF Layer Stack!",LoggerError,debugName.c_str()); + break; + } + // we need to make a copy because we'll be changing the last layer + const auto combinedH = frontIR->copyLayers(origFrontH); + { + auto* lastInFront = frontPool.deref(combinedH); + // scroll to the end + while (lastInFront->coated) + lastInFront = frontPool.deref(lastInFront->coated); + // attach the back stack + lastInFront->coated = backH; + } + newMaterialH = combinedH; break; } - //case CElementBSDF::TWO_SIDED: - // material compiler is designed so that we don't need to reciprocate the BRDFs as we go through layers as long as observer is still on the same side - //break; default: assert(false); // we shouldn't get this case here break; } } else - { - newNodeH = createMistubaLeaf(entry.bsdf); - // have to make it available somehow for parent - } - localCache[entry.bsdf] = newNodeH; - stack.back().visited = true; - } - else - { + newMaterialH = createMistubaLeaf(entry.immutable); + if (!newMaterialH) + newMaterialH = errorMaterial; + localCache[entry.immutable] = newMaterialH; stack.pop_back(); + if (stack.empty()) + rootH = newMaterialH._const_cast(); } } - - const auto found = localCache.find(bsdf); - if (found!=localCache.end()) - return errorMaterial; - const auto rootH = block_allocator_t::_static_cast(found->second); if (!rootH) return errorMaterial; + + // add debug info auto* const root = frontPool.deref(rootH); root->debugInfo = frontPool.emplace(debugName); @@ -1068,6 +1301,11 @@ SContext::SContext( } // { + { + spectral_var_t::SCreationParams<1> params = {}; + params.knots.params[0].scale = 1.f; + unityFactor = frontPool.emplace(std::move(params)); + } deltaTransmission = frontPool.emplace(); const auto mulH = frontPool.emplace(); { @@ -1096,6 +1334,7 @@ SContext::SContext( { #define ADD_DEBUG_NODE(NAME) commonDebugNames[uint16_t(ECommonDebug::NAME)] = frontPool.emplace(#NAME) ADD_DEBUG_NODE(Albedo); + ADD_DEBUG_NODE(Weight); ADD_DEBUG_NODE(Opacity); ADD_DEBUG_NODE(MitsubaExtraFactor); #undef ADD_DEBUG_NODE diff --git a/src/nbl/ext/MitsubaLoader/CMitsubaMaterialCompilerFrontend.cpp b/src/nbl/ext/MitsubaLoader/CMitsubaMaterialCompilerFrontend.cpp index 2760c21041..14b9f5d434 100644 --- a/src/nbl/ext/MitsubaLoader/CMitsubaMaterialCompilerFrontend.cpp +++ b/src/nbl/ext/MitsubaLoader/CMitsubaMaterialCompilerFrontend.cpp @@ -149,31 +149,6 @@ CMitsubaMaterialCompilerFrontend::tex_ass_type CMitsubaMaterialCompilerFrontend: } #endif - auto CMitsubaMaterialCompilerFrontend::createIRNode(asset::material_compiler::IR* ir, const CElementBSDF* _bsdf, const system::logger_opt_ptr& logger) -> IRNode* - { - using namespace asset; - using namespace material_compiler; - - - - const auto type = _bsdf->type; - IRNode* ir_node = nullptr; - switch (type) - { - case CElementBSDF::TWO_SIDED: - //TWO_SIDED is not translated into IR node directly - break; - case CElementBSDF::MASK: - ir_node = ir->allocNode(); - ir_node->children.count = 1u; - getSpectrumOrTexture(_bsdf->mask.opacity,static_cast(ir_node)->opacity,EIVS_BLEND_WEIGHT); - break; - - - - - - case CElementBSDF::BUMPMAP: @@ -188,203 +163,4 @@ CMitsubaMaterialCompilerFrontend::tex_ass_type CMitsubaMaterialCompilerFrontend: std::tie(node->texture.image,node->texture.sampler,node->texture.scale) = getTexture(_bsdf->bumpmap.texture,_bsdf->bumpmap.wasNormal ? EIVS_NORMAL_MAP:EIVS_BUMP_MAP); } - break; - case CElementBSDF::COATING: - case CElementBSDF::ROUGHCOATING: - { - ir_node = ir->allocNode(); - ir_node->children.count = 1u; - - const float eta = _bsdf->dielectric.intIOR/_bsdf->dielectric.extIOR; - - auto* node = static_cast(ir_node); - - const float thickness = _bsdf->coating.thickness; - getSpectrumOrTexture(_bsdf->coating.sigmaA, node->thicknessSigmaA); - if (node->thicknessSigmaA.isConstant()) - node->thicknessSigmaA.constant *= thickness; - else - node->thicknessSigmaA.texture.scale *= thickness; - - node->eta = IR::INode::color_t(eta); - node->shadowing = IR::CMicrofacetCoatingBSDFNode::EST_SMITH; - if (type == CElementBSDF::ROUGHCOATING) - { - node->ndf = ndfMap[_bsdf->coating.distribution]; - getFloatOrTexture(_bsdf->coating.alphaU, node->alpha_u); - if (node->ndf == IR::CMicrofacetSpecularBSDFNode::ENDF_ASHIKHMIN_SHIRLEY) - getFloatOrTexture(_bsdf->coating.alphaV, node->alpha_v); - else - node->alpha_v = node->alpha_u; - } - else - { - node->setSmooth(); - } - } - break; - case CElementBSDF::BLEND_BSDF: - { - ir_node = ir->allocNode(); - ir_node->children.count = 2u; - getSpectrumOrTexture(_bsdf->blendbsdf.weight,static_cast(ir_node)->weight,EIVS_BLEND_WEIGHT); - } - break; - case CElementBSDF::MIXTURE_BSDF: - { - ir_node = ir->allocNode(); - auto* node = static_cast(ir_node); - const size_t cnt = _bsdf->mixturebsdf.childCount; - ir_node->children.count = cnt; - const auto* weightIt = _bsdf->mixturebsdf.weights; - for (size_t i=0u; iweights[i] = *(weightIt++); - } - break; - } - - return ir_node; - } - -auto CMitsubaMaterialCompilerFrontend::compileToIRTree(asset::material_compiler::IR* ir, const CElementBSDF* _bsdf, const system::logger_opt_ptr& logger) -> front_and_back_t -{ - using namespace asset; - using namespace material_compiler; - - struct SNode - { - const CElementBSDF* bsdf; - IRNode* ir_node = nullptr; - uint32_t parent_ix = static_cast(-1); - uint32_t child_num = 0u; - bool twosided = false; - bool front = true; - - CElementBSDF::Type type() const { return bsdf->type; } - }; - auto node_parent = [](const SNode& node, core::vector& traversal) { return &traversal[node.parent_ix]; }; - - core::vector bfs; - { - core::queue q; - { - SNode root{ _bsdf }; - root.twosided = (root.type() == CElementBSDF::TWO_SIDED); - q.push(root); - } - - while (q.size()) - { - SNode parent = q.front(); - q.pop(); - //node.ir_node = createIRNode(node.bsdf); - - if (parent.bsdf->isMeta()) - { - const uint32_t child_count = (parent.bsdf->type == CElementBSDF::COATING) ? parent.bsdf->coating.childCount : parent.bsdf->meta_common.childCount; - for (uint32_t i = 0u; i < child_count; ++i) - { - SNode child_node; - child_node.bsdf = (parent.bsdf->type == CElementBSDF::COATING) ? parent.bsdf->coating.bsdf[i] : parent.bsdf->meta_common.bsdf[i]; - child_node.parent_ix = parent.type() == CElementBSDF::TWO_SIDED ? parent.parent_ix : bfs.size(); - child_node.twosided = (child_node.type() == CElementBSDF::TWO_SIDED) || parent.twosided; - child_node.child_num = (parent.type() == CElementBSDF::TWO_SIDED) ? parent.child_num : i; - child_node.front = parent.front; - if (parent.type() == CElementBSDF::TWO_SIDED && i == 1u) - child_node.front = false; - q.push(child_node); - } - } - if (parent.type() != CElementBSDF::TWO_SIDED) - bfs.push_back(parent); - } - } - - auto createBackfaceNodeFromFrontface = [&ir](const IRNode* front) -> IRNode* - { - switch (front->symbol) - { - case IRNode::ES_BSDF_COMBINER: [[fallthrough]]; - case IRNode::ES_OPACITY: [[fallthrough]]; - case IRNode::ES_GEOM_MODIFIER: [[fallthrough]]; - case IRNode::ES_EMITTER: - case IRNode::ES_ROOT: - return ir->copyNode(front); - case IRNode::ES_BSDF: - { - auto* bsdf = static_cast(front); - if (bsdf->type == IR::CBSDFNode::ET_MICROFACET_DIELECTRIC) - { - auto* dielectric = static_cast(bsdf); - auto* copy = static_cast(ir->copyNode(front)); - if (!copy->thin) //we're always outside in case of thin dielectric - copy->eta = IRNode::color_t(1.f) / copy->eta; - - return copy; - } - else if (bsdf->type == IR::CBSDFNode::ET_MICROFACET_DIFFTRANS) - return ir->copyNode(front); - } - [[fallthrough]]; // intentional - default: - { - // black diffuse otherwise - auto* invalid = ir->allocNode(); - invalid->setSmooth(); - invalid->reflectance = IR::INode::color_t(0.f); - - return invalid; - } - } - }; - - //create frontface IR - IRNode* frontroot = nullptr; - for (auto& node : bfs) - { - if (!node.front) - continue; - - IRNode** dst = nullptr; - if (node.parent_ix >= bfs.size()) - dst = &frontroot; - else - dst = const_cast(&node_parent(node, bfs)->ir_node->children[node.child_num]); - - node.ir_node = *dst = createIRNode(ir, node.bsdf, logger); - } - IRNode* backroot = nullptr; - for (uint32_t i = 0u; i < bfs.size(); ++i) - { - SNode& node = bfs[i]; - - IRNode* ir_node = nullptr; - if (!node.twosided) - ir_node = createBackfaceNodeFromFrontface(node.ir_node); - else - { - if (node.front) - { - if ((i+1u) < bfs.size() && bfs[i+1u].twosided && !bfs[i+1u].front) - continue; // will take backface node in next iteration - //otherwise copy the one from front (same bsdf on both sides_ - ir_node = ir->copyNode(node.ir_node); - } - else - ir_node = createIRNode(ir, node.bsdf, logger); - } - node.ir_node = ir_node; - - IRNode** dst = nullptr; - if (node.parent_ix >= bfs.size()) - dst = &backroot; - else - dst = const_cast(&node_parent(node, bfs)->ir_node->children[node.child_num]); - - *dst = ir_node; - } - - return { frontroot, backroot }; -} - -} + break; \ No newline at end of file