Skip to content

Commit 8aff0f7

Browse files
Beer node needs a thickness unfortunately.
1 parent 0dc31aa commit 8aff0f7

4 files changed

Lines changed: 119 additions & 216 deletions

File tree

include/nbl/asset/material_compiler3/CFrontendIR.h

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -584,24 +584,23 @@ class CFrontendIR final : public CNodePool
584584
{
585585
public:
586586
inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CBeer);}
587-
inline uint8_t getChildCount() const override {return 1;}
587+
inline uint8_t getChildCount() const override {return 2;}
588588

589589
// you can set the members later
590590
inline CBeer() = default;
591591

592-
// Effective transparency = exp2(log2(perpTransmittance)/dot(refract(V,X,eta),X)) = exp2(log2(perpTransmittance)*inversesqrt(1.f+(LdotX-1)*rcpEta))
593-
// Absorption and thickness of the interface combined into a single variable, eta and `LdotX` is taken from the leaf BTDF node.
594-
// With refractions from Dielectrics, we get just `1/LdotX`, for Delta Transmission we get `1/VdotN` since its the same
592+
// Effective transparency = exp2(log2(perpTransmittance)*thickness/dot(refract(V,X,eta),X)) = exp2(log2(perpTransmittance)*thickness*inversesqrt(1.f+(LdotX-1)*rcpEta))
593+
// 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
595594
typed_pointer_type<CSpectralVariable> perpTransmittance = {};
596-
595+
typed_pointer_type<CSpectralVariable> thickness = {};
597596

598597
protected:
599598
COPY_DEFAULT_IMPL
600599

601-
inline typed_pointer_type<IExprNode> getChildHandle_impl(const uint8_t ix) const override {return perpTransmittance;}
600+
inline typed_pointer_type<IExprNode> getChildHandle_impl(const uint8_t ix) const override {return ix ? perpTransmittance:thickness;}
602601
inline void setChild_impl(const uint8_t ix, _typed_pointer_type<IExprNode> newChild) override
603602
{
604-
perpTransmittance = block_allocator_type::_static_cast<CSpectralVariable>(newChild);
603+
*(ix ? &perpTransmittance:&thickness) = block_allocator_type::_static_cast<CSpectralVariable>(newChild);
605604
}
606605

607606
inline std::string_view getChildName_impl(const uint8_t ix) const override {return "Perpendicular\\nTransmittance";}
@@ -645,20 +644,40 @@ class CFrontendIR final : public CNodePool
645644
NBL_API2 void printDot(std::ostringstream& sstr, const core::string& selfID) const override;
646645
};
647646
// Compute Inifinite Scatter and extinction between two parallel infinite planes.
647+
//
648648
// It's a specialization of what would be a layer of two identical smooth BRDF and BTDF with arbitrary Fresnel function and beer's
649-
// extinction on the BRDFs (not BTDFs), all applied on a per micro-facet basis (layering per microfacet, not whole surface).
649+
// extinction, all applied on a per micro-facet basis (layering per microfacet, not whole surface) so the NDFs of two layers would be correlated.
650650
//
651-
// 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.
651+
// 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.
652652
// 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
653653
// meaning that in such case you can no longer optimize the BTDF contributor into a DeltaTransmission but need a CookTorrance with
654654
// 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).
655-
//
655+
// 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.
656+
//
656657
// Because we split BRDF and BTDF into separate expressions, what this node computes differs depending on where it gets used:
658+
// 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`
659+
//
657660
// 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)
661+
// --------------------
662+
// Top BRDF as multiplied with CThinInfiniteScatterCorrection node with `reflectanceTop`
663+
// BTDF matching the BRDF above
664+
// Bottom BRDF matching Top (but corellated so you always hit the same microfacet going back)
665+
// Null BRDF
666+
// Delta Transmission Beer extinction
667+
// Null BRDF
668+
// Top Smooth BRDF with `reflectanceBottom` applied to a Delta Reflection
669+
// ------------------
670+
//
658671
// 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)
659-
// 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`
672+
// --------------------
673+
// Bottom BRDF as multiplied with CThinInfiniteScatterCorrection node with `reflectanceTop`
674+
// Null BRDF
675+
// Delta Transmission Beer extinction
676+
// Null BRDF
677+
// Top BRDF as multiplied with CThinInfiniteScatterCorrection node but with `reflectanceBottom` (but corellated so you always hit the same microfacet leading to no refraction)
678+
// ------------------
660679
//
661-
// 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.
680+
// The obvious downside of using this node for transmission is that its impossible to get "milky" glass because a spread of refractions is needed
662681
class CThinInfiniteScatterCorrection final : public obj_pool_type::INonTrivial, public IExprNode
663682
{
664683
protected:
@@ -736,7 +755,7 @@ class CFrontendIR final : public CNodePool
736755
// - Delta Reflection -> Any Cook Torrance BxDF with roughness=0 attached as BRDF
737756
// - Smooth Conductor -> above multiplied with Conductor-Fresnel
738757
// - 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)
739-
// - Thindielectric -> Any Cook Torrance BxDF multiplied with Dielectric-Fresnel .... TODO description ....
758+
// - Thindielectric Correlated -> Any Cook Torrance BxDF multiplied with Dielectric-Fresnel, then Delta Transmission as BTDF with fresnels
740759
// - Plastic -> Similar to layering the above over Diffuse BRDF, its of uttmost importance that the BTDF is Delta Transmission.
741760
class CDeltaTransmission final : public IBxDF
742761
{

src/nbl/asset/material_compiler3/CFrontendIR.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ bool CFrontendIR::CBeer::invalid(const SInvalidCheckArgs& args) const
3232
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());
3333
return true;
3434
}
35+
if (const auto* const thick=args.pool->getObjectPool().deref(thickness); !thick || thick->getKnotCount()!=0)
36+
{
37+
args.logger.log("Monochromatic Thickness node must be attached, but is %u of type %s",ELL_ERROR,thickness,args.pool->getTypeName(thickness).data());
38+
return true;
39+
}
3540
return false;
3641
}
3742

src/nbl/ext/MitsubaLoader/CMitsubaLoader.cpp

Lines changed: 81 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -632,32 +632,6 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW
632632
frontPool.deref(factorH)->debugInfo = commonDebugNames[uint16_t(debug)]._const_cast();
633633
return factorH;
634634
};
635-
// TODO: Move this lambda to CFrontendIR for everyone to use
636-
using expr_t = frontend_ir_t::typed_pointer_type<frontend_ir_t::IExprNode>;
637-
auto mul = [&](const expr_t original, const expr_t factor)->frontend_ir_t::typed_pointer_type<frontend_ir_t::CMul>
638-
{
639-
// if there was no original, attenuating a black colour is useless
640-
if (!original)
641-
return {};
642-
const auto mulH = frontPool.emplace<frontend_ir_t::CMul>();
643-
auto* const mul = frontPool.deref(mulH);
644-
mul->lhs = original;
645-
mul->rhs = factor;
646-
return mulH;
647-
};
648-
auto add = [&](const expr_t lhs, const expr_t rhs)->expr_t
649-
{
650-
if (!lhs)
651-
return rhs;
652-
if (!rhs)
653-
return lhs;
654-
const auto addH = frontPool.emplace<frontend_ir_t::CAdd>();
655-
auto* const add = frontPool.deref(addH);
656-
add->lhs = lhs;
657-
add->rhs = rhs;
658-
return addH;
659-
};
660-
// end TODO
661635
auto createCookTorrance = [&](const CElementBSDF::RoughSpecularBase* base, const frontend_ir_t::typed_pointer_type<frontend_ir_t::CFresnel> fresnelH, const CElementTexture::SpectrumOrTexture& specularReflectance)->auto
662636
{
663637
const auto mulH = frontPool.emplace<frontend_ir_t::CMul>();
@@ -697,7 +671,7 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW
697671
mul->lhs = ctH;
698672
mul->rhs = fresnelH;
699673
}
700-
return mul(mulH,factorH);
674+
return frontIR->createMul(mulH,factorH);
701675
};
702676
auto createOrenNayar = [&](const CElementTexture::SpectrumOrTexture& albedo, const CElementTexture::FloatOrTexture& alphaU, const CElementTexture::FloatOrTexture& alphaV)->auto
703677
{
@@ -714,7 +688,24 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW
714688
getParameters({roughness.data()+1,1},alphaV);
715689
}
716690
}
717-
return mul(orenNayarH,factorH);
691+
return frontIR->createMul(orenNayarH,factorH);
692+
};
693+
694+
auto fillCoatingLayer = [&]<typename T>(frontend_ir_t::CLayer* layer, const T& element, const bool rough, const frontend_ir_t::typed_pointer_type<frontend_ir_t::CBeer> extinctionH={})->void
695+
{
696+
const auto fresnelH = frontIR->createConstantMonochromeRealFresnel(element.intIOR/element.extIOR);
697+
const auto dielectricH = createCookTorrance(rough ? &element:nullptr,fresnelH,element.specularReflectance);
698+
layer->brdfTop = dielectricH;
699+
const auto transH = frontPool.emplace<frontend_ir_t::CMul>();
700+
{
701+
auto* const trans = frontPool.deref(transH);
702+
trans->lhs = frontIR->createMul(deltaTransmission._const_cast(),extinctionH);
703+
const auto factorH = createFactorNode(element.specularTransmittance,ECommonDebug::MitsubaExtraFactor);
704+
trans->rhs = frontIR->createMul(fresnelH,factorH);
705+
}
706+
layer->btdf = transH;
707+
// identical BRDF on the bottom, to have correct multiscatter
708+
layer->brdfBottom = dielectricH;
718709
};
719710

720711
using bxdf_t = frontend_ir_t::typed_pointer_type<frontend_ir_t::IExprNode>;
@@ -825,35 +816,29 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW
825816
mul->lhs = deltaTransmission._const_cast();
826817
mul->rhs = thinInfiniteScatterH;
827818
}
828-
// we're rethreading the fresnel here
819+
// We need to attach the other glass interface as another layer because the Etas need to be reciprocated because the interactions are flipped
820+
leaf->coated = frontIR->reverse(retval);
821+
// we're rethreading the fresnel here, but needed to be careful to not apply thindielectric scatter correction and volumetric absorption / Mitsuba extra factor twice
829822
btdfH = correctedTransmissionH;
830823
}
831-
else // beautiful thing we can reuse the same BxDF nodes for a transmission function without touching Etas or anything!
824+
else
825+
{
826+
// beautiful thing we can reuse the same BxDF nodes for a transmission function without touching Etas or anything!
832827
btdfH = brdf->lhs;
828+
leaf->brdfBottom = brdfH;
829+
}
833830
}
834-
leaf->btdf = mul(btdfH,factorH);
835-
// By default, all non-transmissive scattering models in Mitsuba are one-sided = all transmissive are two sided
836-
leaf->brdfBottom = brdfH;
831+
leaf->btdf = frontIR->createMul(btdfH,factorH);
837832
}
838833
break;
839834
}
840835
case CElementBSDF::PLASTIC: [[fallthrough]];
841836
case CElementBSDF::ROUGHPLASTIC:
842837
{
843-
const auto fresnelH = frontIR->createConstantMonochromeRealFresnel(_bsdf->plastic.intIOR/_bsdf->plastic.extIOR);
844-
const auto dielectricH = createCookTorrance(_bsdf->type==CElementBSDF::ROUGHPLASTIC ? &_bsdf->plastic:nullptr,fresnelH,_bsdf->plastic.specularReflectance);
845-
leaf->brdfTop = dielectricH;
846-
const auto transH = frontPool.emplace<frontend_ir_t::CMul>();
847-
{
848-
auto* const trans = frontPool.deref(transH);
849-
trans->lhs = deltaTransmission._const_cast();
850-
const auto factorH = createFactorNode(_bsdf->plastic.specularTransmittance,ECommonDebug::MitsubaExtraFactor);
851-
trans->rhs = mul(fresnelH,factorH);
852-
}
853-
leaf->btdf = transH;
838+
fillCoatingLayer(leaf,_bsdf->plastic,_bsdf->type==CElementBSDF::ROUGHPLASTIC); // shall we plug albedo back inside as an extinction factor !?
854839
const auto substrateH = frontPool.emplace<frontend_ir_t::CLayer>();
855840
{
856-
// TODO: `_bsdf->plastic.nonlinear` , do we use beer?
841+
// 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
857842
auto* const substrate = frontPool.deref(substrateH);
858843
substrate->brdfTop = createOrenNayar(_bsdf->plastic.diffuseReflectance,_bsdf->plastic.alphaU,_bsdf->plastic.alphaV);
859844
}
@@ -884,7 +869,7 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW
884869
}
885870
leaf->brdfTop = diffTransH;
886871
leaf->btdf = diffTransH;
887-
// By default, all non-transmissive scattering models in Mitsuba are one-sided = all transmissive are two sided
872+
// By default, all non-transmissive scattering models in Mitsuba are one-sided => all transmissive are two sided
888873
leaf->brdfBottom = diffTransH;
889874
break;
890875
}
@@ -953,23 +938,32 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW
953938
}
954939
else
955940
{
956-
material_t newNodeH = {};
941+
material_t newMaterialH = {};
957942
if (_bsdf->isMeta())
958943
{
944+
const auto childCount = _bsdf->meta_common.childCount;
959945
switch(_bsdf->type)
960946
{
961947
case CElementBSDF::COATING: [[fallthrough]];
962948
case CElementBSDF::ROUGHCOATING:
963949
{
964950
const auto coatingH = frontPool.emplace<frontend_ir_t::CLayer>();
965951
auto* const coating = frontPool.deref(coatingH);
966-
// create BRDF thats cook torrance
967-
// TODO
968-
// BTDF thats transmission with absorption
969-
// TODO
970-
// attach leaf as layer
952+
// the top layer
953+
const auto& sigmaA = _bsdf->coating.sigmaA;
954+
const bool hasExtinction = sigmaA.texture||sigmaA.value.type==SPropertyElementData::FLOAT&&sigmaA.value.fvalue!=0.f;
955+
const auto beerH = hasExtinction ? frontPool.emplace<frontend_ir_t::CBeer>():frontend_ir_t::typed_pointer_type<frontend_ir_t::CBeer>{};
956+
if (auto* const beer=frontPool.deref(beerH); beer)
957+
{
958+
spectral_var_t::SCreationParams<3> params = {};
959+
params.knots.uvTransform = getParameters(params.knots.params,sigmaA);
960+
params.getSemantics() = spectral_var_t::Semantics::Fixed3_SRGB;
961+
beer->perpTransmittance = frontPool.emplace<spectral_var_t>(std::move(params));
962+
}
963+
fillCoatingLayer(coating,_bsdf->coating,_bsdf->type==CElementBSDF::ROUGHCOATING,beerH);
964+
// attach the nested as layer
971965
coating->coated = localCache[_bsdf->mask.bsdf[0]]._const_cast();
972-
newNodeH = coatingH;
966+
newMaterialH = coatingH;
973967
break;
974968
}
975969
case CElementBSDF::MASK:
@@ -981,14 +975,15 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW
981975
const auto* const nested = frontPool.deref(nestedH);
982976
assert(nested && nested->brdfTop);
983977
const auto opacityH = createFactorNode(_bsdf->mask.opacity,ECommonDebug::Opacity);
984-
mask->brdfTop = mul(nested->brdfTop,opacityH);
978+
mask->brdfTop = frontIR->createMul(nested->brdfTop,opacityH);
985979
{
986-
const auto transparencyH = frontPool.emplace<frontend_ir_t::CComplement>();
987-
frontPool.deref(transparencyH)->child = opacityH;
988-
mask->btdf = add(mul(deltaTransmission._const_cast(),transparencyH),mul(nested->btdf,opacityH));
980+
mask->btdf = frontIR->createAdd(
981+
frontIR->createMul(deltaTransmission._const_cast(),frontIR->createComplement(opacityH)),
982+
frontIR->createMul(nested->btdf,opacityH)
983+
);
989984
}
990-
mask->brdfBottom = mul(nested->brdfBottom,opacityH);
991-
newNodeH = maskH;
985+
mask->brdfBottom = frontIR->createMul(nested->brdfBottom,opacityH);
986+
newMaterialH = maskH;
992987
break;
993988
}
994989
case CElementBSDF::BUMPMAP:
@@ -1013,40 +1008,33 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW
10131008
}
10141009
case CElementBSDF::TWO_SIDED:
10151010
{
1016-
const auto origFrontH = localCache[_bsdf->mask.bsdf[0]];
1017-
const auto* const origFront = frontPool.deref(origFrontH);
1018-
#if 0
1019-
// attach the original on both sides
1020-
if (_bsdf->twosided.childCount==1)
1011+
const auto origFrontH = localCache[_bsdf->twosided.bsdf[0]];
1012+
const auto chosenBackH = childCount!=1 ? localCache[_bsdf->twosided.bsdf[1]]:origFrontH;
1013+
// 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
1014+
// but then it saves the day by disallowing transmission on the combination two BxDFs it layers together. Lets do the same.
1015+
if (const bool firstIsTransmissive=frontIR->transmissive(origFrontH); firstIsTransmissive && (childCount==1 || frontIR->transmissive(chosenBackH)))
10211016
{
1022-
// get to the bottom layer
1023-
auto lastLayerH = origTopH;
1024-
while (lastLayerH)
1025-
{
1026-
const auto* const layer = frontPool.deref(lastLayerH);
1027-
lastLayerH = layer->coated;
1028-
}
1029-
// do we even have a non-twosided material?
1030-
//
1031-
1032-
// create a copy of the layer, with different settings (don't want to disturb the original)
1033-
const auto twoSidedH = frontPool.emplace<frontend_ir_t::CLayer>();
1034-
auto* const twoSided = frontPool.deref(twoSidedH);
1035-
if (origTop->coated)
1036-
{
1037-
//
1038-
}
1039-
else
1040-
//
1041-
twoSided->brdfBottom = origTop->brdfTop;
1017+
logger.log("Mitsuba <twosided> cannot be used to glue two transmissive BxDF Layer Stacks together!",LoggerError,debugName.c_str());
1018+
break;
10421019
}
1043-
else
1020+
// this is the stack we attach to the back, but we need to reverse it
1021+
const auto backH = frontIR->reverse(chosenBackH);
1022+
if (!backH)
10441023
{
1045-
// glue a copy of the second child in reverse from its last layer to the front while reciprocating
1046-
const auto origBottomH = localCache[_bsdf->mask.bsdf[1]];
1024+
logger.log("Failed to reverse and reciprocate the BxDF Layer Stack!",LoggerError,debugName.c_str());
1025+
break;
10471026
}
1048-
#endif
1049-
assert(false); // unimplemented
1027+
// we need to make a copy because we'll be changing the last layer
1028+
const auto combinedH = frontIR->copyLayers(origFrontH);
1029+
{
1030+
auto* lastInFront = frontPool.deref(combinedH);
1031+
// scroll to the end
1032+
while (lastInFront->coated)
1033+
lastInFront = frontPool.deref(lastInFront->coated);
1034+
// attach the back stack
1035+
lastInFront->coated = backH;
1036+
}
1037+
newMaterialH = combinedH;
10501038
break;
10511039
}
10521040
default:
@@ -1055,10 +1043,10 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW
10551043
}
10561044
}
10571045
else
1058-
newNodeH = createMistubaLeaf(_bsdf);
1059-
if (!newNodeH)
1060-
newNodeH = errorMaterial;
1061-
localCache[_bsdf] = newNodeH;
1046+
newMaterialH = createMistubaLeaf(_bsdf);
1047+
if (!newMaterialH)
1048+
newMaterialH = errorMaterial;
1049+
localCache[_bsdf] = newMaterialH;
10621050
stack.pop_back();
10631051
}
10641052
}

0 commit comments

Comments
 (0)