Skip to content

Commit 1315b7a

Browse files
author
devsh
committed
Realize that UV slot (tangents) are needed whenever NDF is anisotropic even if no textures are used
Print Dot Graph edges with custom child-link names rename `perpTransparency` to `perpTransmittance`
1 parent 549c7b7 commit 1315b7a

File tree

4 files changed

+68
-20
lines changed

4 files changed

+68
-20
lines changed

examples_tests

include/nbl/asset/material_compiler3/CFrontendIR.h

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2022-2025 - DevSH Graphics Programming Sp. z O.O.
1+
// Copyright (C) 2022-2025 - DevSH Graphics Programming Sp. z O.O.
22
// This file is part of the "Nabla Engine".
33
// For conditions of distribution and use, see copyright notice in nabla.h
44
#ifndef _NBL_ASSET_MATERIAL_COMPILER_V3_C_FRONTEND_IR_H_INCLUDED_
@@ -113,6 +113,18 @@ class CFrontendIR : public CNodePool
113113
{
114114
return abs(scale)<std::numeric_limits<float>::infinity() && (!view || viewChannel<getFormatChannelCount(view->getCreationParameters().format));
115115
}
116+
inline bool operator!=(const SParameter& other) const
117+
{
118+
if (scale!=other.scale)
119+
return true;
120+
if (viewChannel!=other.viewChannel)
121+
return true;
122+
// don't compare paddings!
123+
if (view!=other.view)
124+
return true;
125+
return sampler!=other.sampler;
126+
}
127+
inline bool operator==(const SParameter& other) const {return !operator!=(other);}
116128

117129
NBL_API void printDot(std::ostringstream& sstr, const core::string& selfID) const;
118130

@@ -123,6 +135,7 @@ class CFrontendIR : public CNodePool
123135
uint8_t padding[3] = {0,0,0};
124136
core::smart_refctd_ptr<const ICPUImageView> view = {};
125137
// shadow comparison functions are ignored
138+
// NOTE: could take only things that matter from the sampler and pack the viewChannel and reduce padding
126139
ICPUSampler::SParams sampler = {};
127140
};
128141
// In the forest, this is not a node, we'll deduplicate later
@@ -132,7 +145,7 @@ class CFrontendIR : public CNodePool
132145
private:
133146
friend class CSpectralVariable;
134147
template<typename StringConstIterator=const core::string*>
135-
inline void printDot(const uint8_t _count, std::ostringstream& sstr, const core::string& selfID, StringConstIterator paramNameBegin={}) const
148+
inline void printDot(const uint8_t _count, std::ostringstream& sstr, const core::string& selfID, StringConstIterator paramNameBegin={}, const bool uvRequired=false) const
136149
{
137150
bool imageUsed = false;
138151
for (uint8_t i=0; i<_count; i++)
@@ -147,10 +160,10 @@ class CFrontendIR : public CNodePool
147160
else
148161
sstr <<" [label=\"Param " << std::to_string(i) <<"\"]";
149162
}
150-
if (imageUsed)
163+
if (uvRequired || imageUsed)
151164
{
152165
const auto uvTransformID = selfID+"_uvTransform";
153-
sstr << "\n\t" << uvTransformID << " [label=\"";
166+
sstr << "\n\t" << uvTransformID << " [label=\"uvSlot = " << std::to_string(uvSlot()) << "\\n";
154167
printMatrix(sstr,*reinterpret_cast<const decltype(uvTransform)*>(params+_count));
155168
sstr << "\"]";
156169
sstr << "\n\t" << selfID << " -> " << uvTransformID << "[label=\"UV Transform\"]";
@@ -165,20 +178,21 @@ class CFrontendIR : public CNodePool
165178
return false;
166179
return true;
167180
}
168-
// Ignored if no modulator textures
181+
// Ignored if no modulator textures and isotropic BxDF
169182
uint8_t& uvSlot() {return params[0].padding[0];}
170183
const uint8_t& uvSlot() const {return params[0].padding[0];}
171184
// Note: the padding abuse
172185
static_assert(sizeof(SParameter::padding)>0);
173186

174187
template<typename StringConstIterator=const core::string*>
175-
inline void printDot(std::ostringstream& sstr, const core::string& selfID, StringConstIterator paramNameBegin={}) const
188+
inline void printDot(std::ostringstream& sstr, const core::string& selfID, StringConstIterator paramNameBegin={}, const bool uvRequired=false) const
176189
{
177-
printDot<StringConstIterator>(Count,sstr,selfID,std::forward<StringConstIterator>(paramNameBegin));
190+
printDot<StringConstIterator>(Count,sstr,selfID,std::forward<StringConstIterator>(paramNameBegin),uvRequired);
178191
}
179192

180193
SParameter params[Count] = {};
181194
// identity transform by default, ignored if no UVs
195+
// NOTE: a transform could be applied per-param, whats important that the UV slot remains the smae across all of them.
182196
hlsl::float32_t2x3 uvTransform = hlsl::float32_t2x3(
183197
1,0,0,
184198
0,1,0
@@ -280,6 +294,7 @@ class CFrontendIR : public CNodePool
280294
virtual _TypedHandle<IExprNode> getChildHandle_impl(const uint8_t ix) const = 0;
281295

282296
virtual inline core::string getLabelSuffix() const {return "";}
297+
virtual inline std::string_view getChildName_impl(const uint8_t ix) const {return "";}
283298
virtual inline void printDot(std::ostringstream& sstr, const core::string& selfID) const {}
284299
};
285300

@@ -443,6 +458,7 @@ class CFrontendIR : public CNodePool
443458
{
444459
protected:
445460
inline TypedHandle<IExprNode> getChildHandle_impl(const uint8_t ix) const override final {return ix ? rhs:lhs;}
461+
inline std::string_view getChildName_impl(const uint8_t ix) const override final {return ix ? "rhs":"lhs";}
446462

447463
public:
448464
inline uint8_t getChildCount() const override final {return 2;}
@@ -493,6 +509,8 @@ class CFrontendIR : public CNodePool
493509
protected:
494510
inline TypedHandle<IExprNode> getChildHandle_impl(const uint8_t ix) const override final {return ix ? (ix!=1 ? extinction:transmittance):reflectance;}
495511
NBL_API bool invalid(const SInvalidCheckArgs& args) const override;
512+
513+
inline std::string_view getChildName_impl(const uint8_t ix) const override {return ix ? (ix>1 ? "extinction":"reflectance"):"transmittance";}
496514
inline void printDot(std::ostringstream& sstr, const core::string& selfID) const override
497515
{
498516
sstr << "\n\t" << selfID << " -> " << selfID << "_computeTransmittance [label=\"computeTransmittance = " << (computeTransmittance ? "true":"false") << "\"]";
@@ -563,13 +581,15 @@ class CFrontendIR : public CNodePool
563581
inline uint32_t getSize() const override {return calc_size();}
564582
inline CBeer() = default;
565583

566-
// Effective transparency = exp2(log2(perpTransparency)/dot(refract(V,X,eta),X)) = exp2(log2(perpTransparency)*inversesqrt(1.f+(LdotX-1)*rcpEta))
584+
// Effective transparency = exp2(log2(perpTransmittance)/dot(refract(V,X,eta),X)) = exp2(log2(perpTransmittance)*inversesqrt(1.f+(LdotX-1)*rcpEta))
567585
// Absorption and thickness of the interface combined into a single variable, eta and `LdotX` is taken from the leaf BTDF node.
568586
// With refractions from Dielectrics, we get just `1/LdotX`, for Delta Transmission we get `1/VdotN` since its the same
569-
TypedHandle<CSpectralVariable> perpTransparency = {};
587+
TypedHandle<CSpectralVariable> perpTransmittance = {};
570588

571589
protected:
572-
inline TypedHandle<IExprNode> getChildHandle_impl(const uint8_t ix) const override {return perpTransparency;}
590+
inline TypedHandle<IExprNode> getChildHandle_impl(const uint8_t ix) const override {return perpTransmittance;}
591+
592+
inline std::string_view getChildName_impl(const uint8_t ix) const override {return "Perpendicular\\nTransmittance";}
573593
NBL_API bool invalid(const SInvalidCheckArgs& args) const override;
574594
};
575595
// The "oriented" in the Etas means from frontface to backface, so there's no need to reciprocate them when creating matching BTDF for BRDF
@@ -593,6 +613,7 @@ class CFrontendIR : public CNodePool
593613
protected:
594614
inline TypedHandle<IExprNode> getChildHandle_impl(const uint8_t ix) const override {return ix ? orientedImagEta:orientedRealEta;}
595615
NBL_API bool invalid(const SInvalidCheckArgs& args) const override;
616+
inline std::string_view getChildName_impl(const uint8_t ix) const override {return ix ? "Real":"Imaginary";}
596617
NBL_API void printDot(std::ostringstream& sstr, const core::string& selfID) const override;
597618
};
598619
// @kept_secret TODO: Thin Film Interference Fresnel
@@ -618,6 +639,19 @@ class CFrontendIR : public CNodePool
618639
param.scale = 0.f;
619640
}
620641

642+
// conservative check, checks if we can optimize certain things this way
643+
inline bool definitelyIsotropic() const
644+
{
645+
// a derivative map from a texture allows for anisotropic NDFs at higher mip levels when pre-filtered properly
646+
for (auto i=0; i<2; i++)
647+
if (getDerivMap()[i].scale!=0.f && getDerivMap()[i].view)
648+
return false;
649+
// if roughness inputs are not equal (same scale, same texture) then NDF can be anisotropic in places
650+
if (getRougness()[0]!=getRougness()[1])
651+
return false;
652+
// if a reference stretch is used, stretched triangles can turn the distribution isotropic
653+
return stretchInvariant();
654+
}
621655
// whether the derivative map and roughness is constant regardless of UV-space texture stretching
622656
inline bool stretchInvariant() const {return !(abs(hlsl::determinant(reference))>std::numeric_limits<float>::min());}
623657

@@ -704,6 +738,7 @@ class CFrontendIR : public CNodePool
704738
NBL_API bool invalid(const SInvalidCheckArgs& args) const override;
705739

706740
inline core::string getLabelSuffix() const override {return ndf!=NDF::GGX ? "\\nNDF = Beckmann":"\\nNDF = GGX";}
741+
inline std::string_view getChildName_impl(const uint8_t ix) const override {return "Oriented η";}
707742
NBL_API void printDot(std::ostringstream& sstr, const core::string& selfID) const override;
708743
};
709744

include/nbl/asset/material_compiler3/CTrueIR.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,26 @@ class CTrueIR : public CNodePool
4646
{
4747
// TypedHandle<CRootNode> root;
4848
CNodePool::TypedHandle<CNodePool::CDebugInfo> debugInfo;
49+
//
50+
constexpr static inline uint8_t MaxUVSlots = 32;
51+
std::bitset<MaxUVSlots> usedUVSlots;
52+
// the tangent frames are a subset of used UV slots, unless there's an anisotropic BRDF involved
53+
std::bitset<MaxUVSlots> usedTangentFrames;
4954
};
5055
inline std::span<const Material> getMaterials() const {return m_materials;}
5156

5257
// We take the trees from the forest, and canonicalize them into our weird Domain Specific IR with Upside down expression trees.
58+
// Process:
59+
// 1. Schusslerization (for derivative map usage) and Decompression (duplicating nodes, etc.)
60+
// 2. Canonicalize Expressions (Transform into Sum-Product form, DCE, etc.)
61+
// 3. Split BTDFs (front vs. back part), reciprocate Etas
62+
// 4. Simplify and Hoist Layer terms (delta sampling property)
63+
// 5. Subexpression elimination
64+
// It is the backend's job to handle:
65+
// - constant encoding precision (scale factors, UV matrices, IoRs)
66+
// - multiscatter compensation
67+
// - compilation failure to unsupported complex layering
68+
// - compilation failure to unsupported complex layering
5369
bool addMaterials(const CFrontendIR* forest);
5470

5571
protected:

src/nbl/asset/material_compiler3/CFrontendIR.cpp

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ bool CFrontendIR::CEmitter::invalid(const SInvalidCheckArgs& args) const
2727

2828
bool CFrontendIR::CBeer::invalid(const SInvalidCheckArgs& args) const
2929
{
30-
if (!args.pool->deref(perpTransparency))
30+
if (!args.pool->deref(perpTransmittance))
3131
{
32-
args.logger.log("Perpendicular Transparency node of correct type must be attached, but is %u of type %s",ELL_ERROR,perpTransparency,args.pool->getTypeName(perpTransparency).data());
32+
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
}
3535
return false;
@@ -272,22 +272,19 @@ void CFrontendIR::printDotGraph(std::ostringstream& str) const
272272
const auto childCount = node->getChildCount();
273273
if (childCount)
274274
{
275-
// TODO: print with child link names
276-
str << "\n\t" << nodeID << " -> {";
277275
for (auto childIx=0; childIx<childCount; childIx++)
278276
{
279277
const auto childHandle = node->getChildHandle(childIx);
280278
if (const auto child=deref(childHandle); child)
281279
{
282-
str << getNodeID(childHandle) << " ";
280+
str << "\n\t" << nodeID << " -> " << getNodeID(childHandle) << "[label=\"" << node->getChildName_impl(childIx) << "\"]";
283281
const auto visited = visitedNodes.find(childHandle);
284282
if (visited!=visitedNodes.end())
285283
continue;
286284
exprStack.push(childHandle);
287285
visitedNodes.insert(childHandle);
288286
}
289287
}
290-
str << "}\n";
291288
}
292289
// special printing
293290
node->printDot(str,nodeID);
@@ -347,7 +344,7 @@ core::string CFrontendIR::CSpectralVariable::getLabelSuffix() const
347344
void CFrontendIR::CSpectralVariable::printDot(std::ostringstream& sstr, const core::string& selfID) const
348345
{
349346
auto pWonky = reinterpret_cast<const SCreationParams<1>*>(this+1);
350-
pWonky->knots.printDot(getKnotCount(),sstr,selfID);
347+
pWonky->knots.printDot(getKnotCount(),sstr,selfID,{});
351348
}
352349

353350
void CFrontendIR::CEmitter::printDot(std::ostringstream& sstr, const core::string& selfID) const
@@ -377,8 +374,8 @@ void CFrontendIR::IBxDF::SBasicNDFParams::printDot(std::ostringstream& sstr, con
377374
"alpha_u",
378375
"alpha_v"
379376
};
380-
SParameterSet<4>::printDot(sstr,selfID,paramSemantics);
381-
if (hlsl::determinant(reference)>0.f)
377+
SParameterSet<4>::printDot(sstr,selfID,paramSemantics,!definitelyIsotropic());
378+
if (!stretchInvariant())
382379
{
383380
const auto referenceID = selfID+"_reference";
384381
sstr << "\n\t" << referenceID << " [label=\"";

0 commit comments

Comments
 (0)