This commit is contained in:
Syoyo Fujita
2025-11-10 02:54:07 +09:00
parent a04ee0bcbd
commit 4ca91e3012
12 changed files with 1697 additions and 10 deletions

View File

@@ -1,12 +1,190 @@
// SPDX-License-Identifier: Apache 2.0
// Copyright 2022 - Present, Syoyo Fujita.
// Copyright 2023 - Present, Light Transport Entertainment Inc.
//
// UsdLux implementations
#include "usdLux.hh"
#include <sstream>
#include "str-util.hh"
#include "value-types.hh"
#include "common-macros.inc"
namespace tinyusdz {
//
// Utility functions for DomeLight::TextureFormat
//
std::string to_string(const DomeLight::TextureFormat &format) {
switch (format) {
case DomeLight::TextureFormat::Automatic:
return "automatic";
case DomeLight::TextureFormat::Latlong:
return "latlong";
case DomeLight::TextureFormat::MirroredBall:
return "mirroredBall";
case DomeLight::TextureFormat::Angular:
return "angular";
}
return "[[InvalidTextureFormat]]";
}
bool DomeLight_TextureFormat_from_string(const std::string &str, DomeLight::TextureFormat *format) {
if (!format) {
return false;
}
if (str == "automatic") {
(*format) = DomeLight::TextureFormat::Automatic;
return true;
} else if (str == "latlong") {
(*format) = DomeLight::TextureFormat::Latlong;
return true;
} else if (str == "mirroredBall") {
(*format) = DomeLight::TextureFormat::MirroredBall;
return true;
} else if (str == "angular") {
(*format) = DomeLight::TextureFormat::Angular;
return true;
}
return false;
}
//
// Helper functions for light primitives
//
bool IsLightFilterPrim(const Prim &prim) {
// Note: LightFilter and PluginLightFilter would need TYPE_IDs added
// to value-types.hh to be properly identified
// For now, we can check by name
return (prim.prim_type_name() == kLightFilter) ||
(prim.prim_type_name() == kPluginLightFilter);
}
bool IsBoundableLight(const Prim &prim) {
uint32_t tid = prim.type_id();
return (tid == value::TYPE_ID_LUX_SPHERE) ||
(tid == value::TYPE_ID_LUX_CYLINDER) ||
(tid == value::TYPE_ID_LUX_DISK) ||
(tid == value::TYPE_ID_LUX_RECT) ||
(tid == value::TYPE_ID_LUX_PORTAL);
}
bool IsNonboundableLight(const Prim &prim) {
uint32_t tid = prim.type_id();
return (tid == value::TYPE_ID_LUX_DISTANT) ||
(tid == value::TYPE_ID_LUX_DOME) ||
(tid == value::TYPE_ID_LUX_GEOMETRY);
}
//
// Light API helper functions
//
// Compute effective light color including color temperature
value::color3f ComputeEffectiveLightColor(
const value::color3f &baseColor,
bool enableColorTemperature,
float colorTemperature) {
if (!enableColorTemperature) {
return baseColor;
}
// Convert color temperature to RGB using approximate blackbody radiation
// This is a simplified implementation based on common CG practices
float temp = colorTemperature;
temp = std::max(1000.0f, std::min(40000.0f, temp)); // clamp to reasonable range
temp /= 100.0f;
value::color3f tempColor;
// Red channel
if (temp <= 66.0f) {
tempColor[0] = 1.0f;
} else {
float r = temp - 60.0f;
r = 329.698727446f * std::pow(r, -0.1332047592f);
tempColor[0] = std::max(0.0f, std::min(1.0f, r / 255.0f));
}
// Green channel
if (temp <= 66.0f) {
float g = temp;
g = 99.4708025861f * std::log(g) - 161.1195681661f;
tempColor[1] = std::max(0.0f, std::min(1.0f, g / 255.0f));
} else {
float g = temp - 60.0f;
g = 288.1221695283f * std::pow(g, -0.0755148492f);
tempColor[1] = std::max(0.0f, std::min(1.0f, g / 255.0f));
}
// Blue channel
if (temp >= 66.0f) {
tempColor[2] = 1.0f;
} else if (temp <= 19.0f) {
tempColor[2] = 0.0f;
} else {
float b = temp - 10.0f;
b = 138.5177312231f * std::log(b) - 305.0447927307f;
tempColor[2] = std::max(0.0f, std::min(1.0f, b / 255.0f));
}
// Multiply base color by temperature color
return value::color3f({
baseColor[0] * tempColor[0],
baseColor[1] * tempColor[1],
baseColor[2] * tempColor[2]
});
}
// Compute light intensity from exposure (EV)
float ComputeLightIntensityFromExposure(float baseIntensity, float exposure) {
// exposure is in EV (exposure value)
// intensity_multiplier = 2^exposure
return baseIntensity * std::pow(2.0f, exposure);
}
// Compute final light intensity combining base intensity and exposure
float ComputeFinalLightIntensity(float baseIntensity, float exposure) {
return ComputeLightIntensityFromExposure(baseIntensity, exposure);
}
//
// Shaping API helper functions
//
// Check if a light has shaping applied (cone angle < 90 degrees or IES profile)
bool HasLightShaping(const ShapingAPI &shaping) {
float coneAngle = 90.0f;
shaping.shapingConeAngle.get_value().get_scalar(&coneAngle);
bool hasConeShaping = coneAngle < 90.0f;
bool hasIES = shaping.shapingIesFile.authored();
return hasConeShaping || hasIES;
}
//
// Shadow API helper functions
//
// Check if shadows are enabled
bool AreShadowsEnabled(const ShadowAPI &shadow) {
bool enabled = true;
shadow.shadowEnable.get_value().get_scalar(&enabled);
return enabled;
}
// Get effective shadow color
value::color3f GetEffectiveShadowColor(const ShadowAPI &shadow) {
value::color3f color{0.0f, 0.0f, 0.0f};
shadow.shadowColor.get_value().get_scalar(&color);
return color;
}
} // namespace tinyusdz

View File

@@ -19,6 +19,50 @@ constexpr auto kDistantLight = "DistantLight";
constexpr auto kGeometryLight = "GeometryLight";
constexpr auto kPortalLight = "PortalLight";
constexpr auto kPluginLight = "PluginLight";
constexpr auto kLightFilter = "LightFilter";
constexpr auto kPluginLightFilter = "PluginLightFilter";
//
// API Schemas - Declared before light classes since they're used as optional members
//
// ShapingAPI: Light emission shaping (cone, focus, IES)
struct ShapingAPI {
TypedAttributeWithFallback<Animatable<float>> shapingFocus{0.0f}; // inputs:shaping:focus
TypedAttributeWithFallback<Animatable<value::color3f>> shapingFocusTint{value::color3f({0.0f, 0.0f, 0.0f})}; // inputs:shaping:focusTint
TypedAttributeWithFallback<Animatable<float>> shapingConeAngle{90.0f}; // inputs:shaping:cone:angle (degrees)
TypedAttributeWithFallback<Animatable<float>> shapingConeSoftness{0.0f}; // inputs:shaping:cone:softness
TypedAttribute<Animatable<value::AssetPath>> shapingIesFile; // inputs:shaping:ies:file
TypedAttributeWithFallback<Animatable<float>> shapingIesAngleScale{0.0f}; // inputs:shaping:ies:angleScale
TypedAttributeWithFallback<Animatable<bool>> shapingIesNormalize{false}; // inputs:shaping:ies:normalize
};
// ShadowAPI: Shadow controls
struct ShadowAPI {
TypedAttributeWithFallback<Animatable<bool>> shadowEnable{true}; // inputs:shadow:enable
TypedAttributeWithFallback<Animatable<value::color3f>> shadowColor{value::color3f({0.0f, 0.0f, 0.0f})}; // inputs:shadow:color
TypedAttributeWithFallback<Animatable<float>> shadowDistance{-1.0f}; // inputs:shadow:distance (-1 = infinite)
TypedAttributeWithFallback<Animatable<float>> shadowFalloff{-1.0f}; // inputs:shadow:falloff (-1 = no falloff)
TypedAttributeWithFallback<Animatable<float>> shadowFalloffGamma{1.0f}; // inputs:shadow:falloffGamma
};
// MeshLightAPI: Applied to mesh geometry to make it emit light
struct MeshLightAPI {
// Inherits LightAPI properties
// materialSyncMode defaults to "materialGlowTintsLight"
TypedAttributeWithFallback<Animatable<value::token>> materialSyncMode{value::token("materialGlowTintsLight")}; // light:materialSyncMode
};
// VolumeLightAPI: Applied to volume geometry for volumetric lighting
struct VolumeLightAPI {
// Inherits LightAPI properties
// materialSyncMode defaults to "materialGlowTintsLight"
TypedAttributeWithFallback<Animatable<value::token>> materialSyncMode{value::token("materialGlowTintsLight")}; // light:materialSyncMode
};
//
// Light Base Classes
//
class BoundableLight : public Xformable, public Collection {
@@ -40,8 +84,13 @@ class BoundableLight : public Xformable, public Collection {
TypedAttributeWithFallback<Animatable<float>> intensity{1.0f}; // inputs:intensity
TypedAttributeWithFallback<Animatable<bool>> normalize{false}; // inputs:normalize normalize power by the surface area of the light.
TypedAttributeWithFallback<Animatable<float>> specular{1.0f}; // inputs:specular specular multiplier
// rel light:filters
// Relationships
RelationshipProperty filters; // rel light:filters - light filters affecting this light
// Optional API schemas (can be applied)
nonstd::optional<ShapingAPI> shaping;
nonstd::optional<ShadowAPI> shadow;
std::pair<ListEditQual, std::vector<Reference>> references;
std::pair<ListEditQual, std::vector<Payload>> payload;
@@ -84,8 +133,13 @@ class NonboundableLight : public Xformable, public Collection {
TypedAttributeWithFallback<Animatable<float>> intensity{1.0f}; // inputs:intensity
TypedAttributeWithFallback<Animatable<bool>> normalize{false}; // inputs:normalize normalize power by the surface area of the light.
TypedAttributeWithFallback<Animatable<float>> specular{1.0f}; // inputs:specular specular multiplier
// rel light:filters
// Relationships
RelationshipProperty filters; // rel light:filters - light filters affecting this light
// Optional API schemas (can be applied)
nonstd::optional<ShapingAPI> shaping;
nonstd::optional<ShadowAPI> shadow;
std::pair<ListEditQual, std::vector<Reference>> references;
std::pair<ListEditQual, std::vector<Payload>> payload;
@@ -155,8 +209,10 @@ struct DomeLight : public NonboundableLight {
TypedAttributeWithFallback<Animatable<float>> guideRadius{1.0e5f};
TypedAttribute<Animatable<value::AssetPath>> file; // asset inputs:texture:file
TypedAttributeWithFallback<Animatable<TextureFormat>> textureFormat{TextureFormat::Automatic}; // token inputs:texture:format
// rel portals
// rel proxyPrim
// Relationships
RelationshipProperty portals; // rel portals - portal lights for dome light
RelationshipProperty proxyPrim; // rel proxyPrim - proxy geometry for light shape
};
@@ -168,23 +224,107 @@ struct GeometryLight : public NonboundableLight {
};
// TODO
struct PortalLight : public NonboundableLight {
struct PortalLight : public BoundableLight {
};
// TODO
struct PluginLight : public Xformable, public Collection {
// Plugin-based lights defined via shader registry
TypedAttribute<Animatable<value::token>> shaderId; // light:shaderId
};
#if 0 // TODO
struct PluginLightFilter : public Light {
//
// Light Filters
//
// Base class for light filters
struct LightFilter : public Xformable {
std::string name;
Specifier spec{Specifier::Def};
int64_t parent_id{-1};
TypedAttributeWithFallback<Animatable<Visibility>> visibility{Visibility::Inherited};
TypedAttributeWithFallback<Purpose> purpose{Purpose::Default};
std::pair<ListEditQual, std::vector<Reference>> references;
std::pair<ListEditQual, std::vector<Payload>> payload;
std::map<std::string, VariantSet> variantSet;
std::map<std::string, Property> props;
PrimMeta meta;
const PrimMeta &metas() const { return meta; }
PrimMeta &metas() { return meta; }
const std::vector<value::token> &primChildrenNames() const { return _primChildren; }
const std::vector<value::token> &propertyNames() const { return _properties; }
std::vector<value::token> &primChildrenNames() { return _primChildren; }
std::vector<value::token> &propertyNames() { return _properties; }
private:
std::vector<value::token> _primChildren;
std::vector<value::token> _properties;
};
struct PluginLightFilter : public LightFilter {
TypedAttribute<Animatable<value::token>> shaderId; // light:shaderId
};
#endif
inline bool IsLightPrim(const Prim &prim) {
return (prim.type_id() > value::TYPE_ID_LUX_BEGIN) && (prim.type_id() < value::TYPE_ID_LUX_END);
}
//
// Utility functions
//
// Convert DomeLight::TextureFormat to string
std::string to_string(const DomeLight::TextureFormat &format);
// Parse string to DomeLight::TextureFormat
bool DomeLight_TextureFormat_from_string(const std::string &str, DomeLight::TextureFormat *format);
// Check if a prim is a light filter
bool IsLightFilterPrim(const Prim &prim);
// Check if a light is boundable
bool IsBoundableLight(const Prim &prim);
// Check if a light is non-boundable
bool IsNonboundableLight(const Prim &prim);
//
// Light API helper functions
//
// Compute effective light color including color temperature
value::color3f ComputeEffectiveLightColor(
const value::color3f &baseColor,
bool enableColorTemperature,
float colorTemperature);
// Compute light intensity from exposure (EV)
float ComputeLightIntensityFromExposure(float baseIntensity, float exposure);
// Compute final light intensity combining base intensity and exposure
float ComputeFinalLightIntensity(float baseIntensity, float exposure);
//
// Shaping API helper functions
//
// Check if a light has shaping applied (cone angle < 90 degrees or IES profile)
bool HasLightShaping(const ShapingAPI &shaping);
//
// Shadow API helper functions
//
// Check if shadows are enabled
bool AreShadowsEnabled(const ShadowAPI &shadow);
// Get effective shadow color
value::color3f GetEffectiveShadowColor(const ShadowAPI &shadow);
// import DEFINE_TYPE_TRAIT and DEFINE_ROLE_TYPE_TRAIT
#include "define-type-trait.inc"

View File

@@ -19,6 +19,7 @@
#include <sstream>
#include "usdMtlx.hh"
#include "usdLux.hh"
#if defined(TINYUSDZ_USE_USDMTLX)
@@ -773,11 +774,178 @@ bool ReadMaterialXFromString(const std::string &str,
// standard_surface
for (auto sd_surface : root.children("standard_surface")) {
PUSH_WARN("TODO: `look`");
PUSH_WARN("TODO: `standard_surface`");
// TODO
(void)sd_surface;
}
// uniform_edf
for (auto uniform_edf : root.children("uniform_edf")) {
std::string node_name;
{
std::string typeName;
GET_ATTR_VALUE(uniform_edf, "name", std::string, node_name);
GET_ATTR_VALUE(uniform_edf, "type", std::string, typeName);
if (typeName != "EDF") {
PUSH_ERROR_AND_RETURN(
fmt::format("`EDF` expected for type of uniform_edf, but got `{}`",
typeName));
}
}
MtlxUniformEdf edf;
for (auto inp : uniform_edf.children("input")) {
std::string name;
std::string typeName;
std::string valueStr;
GET_ATTR_VALUE(inp, "name", std::string, name);
GET_ATTR_VALUE(inp, "type", std::string, typeName);
GET_ATTR_VALUE(inp, "value", std::string, valueStr);
GET_SHADER_PARAM(name, typeName, "color", "color3", value::color3f,
valueStr, edf.color) {
PUSH_WARN("Unknown/unsupported input " << name);
}
}
mtlx->light_shaders[node_name] = edf;
}
// conical_edf
for (auto conical_edf : root.children("conical_edf")) {
std::string node_name;
{
std::string typeName;
GET_ATTR_VALUE(conical_edf, "name", std::string, node_name);
GET_ATTR_VALUE(conical_edf, "type", std::string, typeName);
if (typeName != "EDF") {
PUSH_ERROR_AND_RETURN(
fmt::format("`EDF` expected for type of conical_edf, but got `{}`",
typeName));
}
}
MtlxConicalEdf edf;
for (auto inp : conical_edf.children("input")) {
std::string name;
std::string typeName;
std::string valueStr;
GET_ATTR_VALUE(inp, "name", std::string, name);
GET_ATTR_VALUE(inp, "type", std::string, typeName);
GET_ATTR_VALUE(inp, "value", std::string, valueStr);
GET_SHADER_PARAM(name, typeName, "color", "color3", value::color3f,
valueStr, edf.color)
GET_SHADER_PARAM(name, typeName, "normal", "vector3", value::normal3f,
valueStr, edf.normal)
GET_SHADER_PARAM(name, typeName, "inner_angle", "float", float, valueStr,
edf.inner_angle)
GET_SHADER_PARAM(name, typeName, "outer_angle", "float", float, valueStr,
edf.outer_angle) {
PUSH_WARN("Unknown/unsupported input " << name);
}
}
mtlx->light_shaders[node_name] = edf;
}
// measured_edf
for (auto measured_edf : root.children("measured_edf")) {
std::string node_name;
{
std::string typeName;
GET_ATTR_VALUE(measured_edf, "name", std::string, node_name);
GET_ATTR_VALUE(measured_edf, "type", std::string, typeName);
if (typeName != "EDF") {
PUSH_ERROR_AND_RETURN(
fmt::format("`EDF` expected for type of measured_edf, but got `{}`",
typeName));
}
}
MtlxMeasuredEdf edf;
for (auto inp : measured_edf.children("input")) {
std::string name;
std::string typeName;
std::string valueStr;
GET_ATTR_VALUE(inp, "name", std::string, name);
GET_ATTR_VALUE(inp, "type", std::string, typeName);
GET_ATTR_VALUE(inp, "value", std::string, valueStr);
GET_SHADER_PARAM(name, typeName, "color", "color3", value::color3f,
valueStr, edf.color)
// file is a filename type
if (name == "file") {
if (typeName != "filename") {
PUSH_ERROR_AND_RETURN(
fmt::format("type `{}` expected for input `{}`, but got `{}`",
"filename", "file", typeName));
}
std::string filepath;
if (!detail::ParseMaterialXValue(valueStr, &filepath, err)) {
return false;
}
edf.file.set_value(value::AssetPath(filepath));
} else {
PUSH_WARN("Unknown/unsupported input " << name);
}
}
mtlx->light_shaders[node_name] = edf;
}
// light
for (auto light : root.children("light")) {
std::string node_name;
{
std::string typeName;
GET_ATTR_VALUE(light, "name", std::string, node_name);
GET_ATTR_VALUE(light, "type", std::string, typeName);
if (typeName != "lightshader") {
PUSH_ERROR_AND_RETURN(
fmt::format("`lightshader` expected for type of light, but got `{}`",
typeName));
}
}
MtlxLight light_shader;
for (auto inp : light.children("input")) {
std::string name;
std::string typeName;
std::string valueStr;
pugi::xml_attribute nodename_attr = inp.attribute("nodename");
GET_ATTR_VALUE(inp, "name", std::string, name);
GET_ATTR_VALUE(inp, "type", std::string, typeName);
// Handle connections via nodename
if (nodename_attr) {
std::string nodename = nodename_attr.as_string();
if (name == "edf") {
light_shader.edf.set_value(value::token(nodename));
} else {
PUSH_WARN("Unknown/unsupported connection input " << name);
}
} else {
// Handle direct values
GET_ATTR_VALUE(inp, "value", std::string, valueStr);
GET_SHADER_PARAM(name, typeName, "intensity", "color3", value::color3f,
valueStr, light_shader.intensity)
GET_SHADER_PARAM(name, typeName, "exposure", "float", float, valueStr,
light_shader.exposure) {
PUSH_WARN("Unknown/unsupported input " << name);
}
}
}
mtlx->light_shaders[node_name] = light_shader;
}
// standard_surface
for (auto usd_surface : root.children("UsdPreviewSurface")) {
std::string surface_name;
@@ -1011,6 +1179,149 @@ bool LoadMaterialXFromAsset(const Asset &asset, const std::string &asset_path,
return true;
}
///
/// Convert MaterialX Light shader to UsdLux light
///
bool ConvertMtlxLightToUsdLux(const MtlxLight &mtlx_light,
const std::map<std::string, value::Value> &light_shaders,
value::Value *usd_light,
std::string *warn, std::string *err) {
(void)warn;
if (!usd_light) {
PUSH_ERROR_AND_RETURN("usd_light is nullptr");
}
// Get the EDF node name from the light shader
value::token edf_name;
if (!mtlx_light.edf.get_value(&edf_name)) {
PUSH_ERROR_AND_RETURN("Light shader has no EDF connection");
}
// Find the EDF node in light_shaders
auto edf_it = light_shaders.find(edf_name.str());
if (edf_it == light_shaders.end()) {
PUSH_ERROR_AND_RETURN(fmt::format("EDF node '{}' not found", edf_name.str()));
}
const value::Value &edf_value = edf_it->second;
// Get intensity and exposure from the light shader
value::color3f intensity{1.0f, 1.0f, 1.0f};
mtlx_light.intensity.get_value().get_scalar(&intensity);
float exposure = 0.0f;
if (mtlx_light.exposure.authored()) {
if (auto exp_val = mtlx_light.exposure.get_value()) {
exp_val.value().get_scalar(&exposure);
}
}
// Convert based on EDF type
if (auto uniform_edf = edf_value.as<MtlxUniformEdf>()) {
// uniform_edf -> SphereLight (omnidirectional point light)
SphereLight light;
value::color3f edf_color{1.0f, 1.0f, 1.0f};
uniform_edf->color.get_value().get_scalar(&edf_color);
// Combine EDF color with light intensity
value::color3f final_color{
edf_color[0] * intensity[0],
edf_color[1] * intensity[1],
edf_color[2] * intensity[2]
};
light.color.set_value(final_color);
light.exposure.set_value(exposure);
light.intensity.set_value(1.0f); // Already baked into color
(*usd_light) = light;
return true;
} else if (auto conical_edf = edf_value.as<MtlxConicalEdf>()) {
// conical_edf -> RectLight with ShapingAPI (spot light effect)
RectLight light;
value::color3f edf_color{1.0f, 1.0f, 1.0f};
conical_edf->color.get_value().get_scalar(&edf_color);
// Combine EDF color with light intensity
value::color3f final_color{
edf_color[0] * intensity[0],
edf_color[1] * intensity[1],
edf_color[2] * intensity[2]
};
light.color.set_value(final_color);
light.exposure.set_value(exposure);
light.intensity.set_value(1.0f);
// Add ShapingAPI for cone control
ShapingAPI shaping;
float inner_angle = 60.0f;
conical_edf->inner_angle.get_value().get_scalar(&inner_angle);
shaping.shapingConeAngle.set_value(inner_angle);
if (conical_edf->outer_angle.authored()) {
float outer_angle = 60.0f;
if (auto oa_val = conical_edf->outer_angle.get_value()) {
oa_val.value().get_scalar(&outer_angle);
// Use softness to represent the difference between inner and outer angles
float softness = (outer_angle - inner_angle) / inner_angle;
shaping.shapingConeSoftness.set_value(std::max(0.0f, softness));
}
}
light.shaping = shaping;
(*usd_light) = light;
return true;
} else if (auto measured_edf = edf_value.as<MtlxMeasuredEdf>()) {
// measured_edf -> RectLight or SphereLight with IES profile via ShapingAPI
SphereLight light;
value::color3f edf_color{1.0f, 1.0f, 1.0f};
measured_edf->color.get_value().get_scalar(&edf_color);
// Combine EDF color with light intensity
value::color3f final_color{
edf_color[0] * intensity[0],
edf_color[1] * intensity[1],
edf_color[2] * intensity[2]
};
light.color.set_value(final_color);
light.exposure.set_value(exposure);
light.intensity.set_value(1.0f);
// Add ShapingAPI with IES profile
if (measured_edf->file.authored()) {
ShapingAPI shaping;
if (auto file_val = measured_edf->file.get_value()) {
value::AssetPath ies_file;
if (file_val.value().get_scalar(&ies_file)) {
shaping.shapingIesFile.set_value(ies_file);
shaping.shapingIesNormalize.set_value(true);
}
}
light.shaping = shaping;
}
(*usd_light) = light;
return true;
} else {
PUSH_ERROR_AND_RETURN(fmt::format("Unknown EDF type for node '{}'", edf_name.str()));
}
return false;
}
//} // namespace usdMtlx
} // namespace tinyusdz

View File

@@ -28,6 +28,11 @@ namespace tinyusdz {
constexpr auto kMtlxUsdPreviewSurface = "MtlxUsdPreviewSurface";
constexpr auto kMtlxAutodeskStandardSurface = "MtlxAutodeskStandaradSurface";
// MaterialX Light Shader Nodes
constexpr auto kMtlxUniformEdf = "uniform_edf";
constexpr auto kMtlxConicalEdf = "conical_edf";
constexpr auto kMtlxMeasuredEdf = "measured_edf";
constexpr auto kMtlxLight = "light";
namespace mtlx {
@@ -65,6 +70,7 @@ struct MtlxModel {
std::map<std::string, MtlxMaterial> surface_materials;
std::map<std::string, value::Value> shaders; // MtlxUsdPreviewSurface or MtlxAutodeskStandaradSurface
std::map<std::string, value::Value> light_shaders; // MtlxLight, MtlxUniformEdf, MtlxConicalEdf, MtlxMeasuredEdf
};
struct MtlxUsdPreviewSurface : UsdPreviewSurface {
@@ -100,6 +106,54 @@ struct MtlxAutodeskStandardSurface : ShaderNode {
TypedTerminalAttribute<value::token> out; // 'out'
};
//
// MaterialX Light Shader Nodes (EDF - Emission Distribution Functions)
//
// uniform_edf: Constructs an EDF emitting light uniformly in all directions
struct MtlxUniformEdf : ShaderNode {
TypedAttributeWithFallback<Animatable<value::color3f>> color{
value::color3f{1.0f, 1.0f, 1.0f}}; // color3 - Radiant emittance
// Output
TypedTerminalAttribute<value::token> out; // 'out' (EDF type)
};
// conical_edf: Constructs an EDF emitting light inside a cone around the normal direction
struct MtlxConicalEdf : ShaderNode {
TypedAttributeWithFallback<Animatable<value::color3f>> color{
value::color3f{1.0f, 1.0f, 1.0f}}; // color3 - Radiant emittance
TypedAttribute<Animatable<value::normal3f>> normal; // vector3 - Surface normal (default: world space normal)
TypedAttributeWithFallback<Animatable<float>> inner_angle{60.0f}; // float - Inner cone angle in degrees
TypedAttribute<Animatable<float>> outer_angle; // float - Outer cone angle for intensity falloff
// Output
TypedTerminalAttribute<value::token> out; // 'out' (EDF type)
};
// measured_edf: Constructs an EDF emitting light according to a measured IES light profile
struct MtlxMeasuredEdf : ShaderNode {
TypedAttributeWithFallback<Animatable<value::color3f>> color{
value::color3f{1.0f, 1.0f, 1.0f}}; // color3 - Radiant emittance
TypedAttribute<Animatable<value::AssetPath>> file; // filename - Path to IES light profile data
// Output
TypedTerminalAttribute<value::token> out; // 'out' (EDF type)
};
// light: Constructs a light shader from an emission distribution function (EDF)
struct MtlxLight : ShaderNode {
TypedAttribute<value::token> edf; // EDF - Emission distribution function (connection to EDF node)
TypedAttributeWithFallback<Animatable<value::color3f>> intensity{
value::color3f{1.0f, 1.0f, 1.0f}}; // color3 - Intensity multiplier for EDF emittance
// Optional: exposure (EV) - some renderers support this
TypedAttribute<Animatable<float>> exposure; // float - Exposure value
// Output
TypedTerminalAttribute<value::token> out; // 'out' (lightshader type)
};
//
// IO
//
@@ -145,6 +199,15 @@ bool LoadMaterialXFromAsset(const Asset &asset,
const std::string &asset_path, PrimSpec &ps /* inout */,
std::string *warn, std::string *err);
///
/// Convert MaterialX Light shader to UsdLux light
/// This helps map MaterialX light shaders to corresponding USD light types
///
bool ConvertMtlxLightToUsdLux(const MtlxLight &mtlx_light,
const std::map<std::string, value::Value> &light_shaders,
value::Value *usd_light,
std::string *warn, std::string *err);
// import DEFINE_TYPE_TRAIT and DEFINE_ROLE_TYPE_TRAIT
#include "define-type-trait.inc"
@@ -156,6 +219,16 @@ DEFINE_TYPE_TRAIT(MtlxUsdPreviewSurface, kMtlxUsdPreviewSurface,
DEFINE_TYPE_TRAIT(MtlxAutodeskStandardSurface, kMtlxAutodeskStandardSurface,
TYPE_ID_IMAGING_MTLX_STANDARDSURFACE, 1);
// Light ShaderNodes (EDF and Light)
DEFINE_TYPE_TRAIT(MtlxUniformEdf, kMtlxUniformEdf,
TYPE_ID_IMAGING_MTLX_UNIFORMEDF, 1);
DEFINE_TYPE_TRAIT(MtlxConicalEdf, kMtlxConicalEdf,
TYPE_ID_IMAGING_MTLX_CONICALEDF, 1);
DEFINE_TYPE_TRAIT(MtlxMeasuredEdf, kMtlxMeasuredEdf,
TYPE_ID_IMAGING_MTLX_MEASUREDEDF, 1);
DEFINE_TYPE_TRAIT(MtlxLight, kMtlxLight,
TYPE_ID_IMAGING_MTLX_LIGHT, 1);
#undef DEFINE_TYPE_TRAIT
#undef DEFINE_ROLE_TYPE_TRAIT

View File

@@ -494,6 +494,10 @@ enum TypeId {
TYPE_ID_IMAGING_MTLX_PREVIEWSURFACE,
TYPE_ID_IMAGING_MTLX_STANDARDSURFACE,
TYPE_ID_IMAGING_MTLX_UNIFORMEDF,
TYPE_ID_IMAGING_MTLX_CONICALEDF,
TYPE_ID_IMAGING_MTLX_MEASUREDEDF,
TYPE_ID_IMAGING_MTLX_LIGHT,
TYPE_ID_IMAGING_END,

View File

@@ -0,0 +1,75 @@
#usda 1.0
(
doc = """MaterialX Basic Uniform Light Example
This example demonstrates a basic omnidirectional point light using MaterialX's
uniform_edf (Emission Distribution Function) node connected to a light shader.
The uniform_edf emits light uniformly in all directions, making it ideal for
representing point lights or area lights with omnidirectional emission.
"""
metersPerUnit = 1
upAxis = "Y"
)
def Xform "World"
{
def Sphere "Sphere" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </World/Materials/SimpleMaterial>
double radius = 1
}
def Scope "Materials"
{
def Material "SimpleMaterial"
{
token outputs:surface.connect = </World/Materials/SimpleMaterial/PreviewSurface.outputs:surface>
def Shader "PreviewSurface"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (0.8, 0.8, 0.8)
float inputs:roughness = 0.4
token outputs:surface
}
}
}
def Scope "Lights"
{
# MaterialX-style light definition
# uniform_edf creates an omnidirectional emission profile
def Shader "UniformEDF" (
doc = "Omnidirectional emission distribution function"
)
{
uniform token info:id = "uniform_edf"
color3f inputs:color = (1.0, 0.95, 0.8) # Warm white light
token outputs:out
}
# Light shader combines EDF with intensity control
def Shader "MainLightShader"
{
uniform token info:id = "light"
token inputs:edf.connect = </World/Lights/UniformEDF.outputs:out>
color3f inputs:intensity = (5.0, 5.0, 5.0) # Bright intensity
float inputs:exposure = 0.0 # EV exposure adjustment
token outputs:out
}
# USD SphereLight representation (equivalent to uniform_edf)
def SphereLight "KeyLight"
{
color3f inputs:color = (1.0, 0.95, 0.8)
float inputs:intensity = 5.0
float inputs:exposure = 0.0
float inputs:radius = 0.5
double3 xformOp:translate = (3, 5, 4)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
}
}

View File

@@ -0,0 +1,89 @@
#usda 1.0
(
doc = """MaterialX Conical Spotlight Example
This example demonstrates a spotlight using MaterialX's conical_edf node.
The conical_edf emits light within a cone, with controls for inner/outer
angles to create soft-edged spotlights.
This maps to USD's RectLight with ShapingAPI applied for cone control.
"""
metersPerUnit = 1
upAxis = "Y"
)
def Xform "World"
{
def Sphere "Sphere" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </World/Materials/GlossyMaterial>
double radius = 1
double3 xformOp:translate = (0, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
def Scope "Materials"
{
def Material "GlossyMaterial"
{
token outputs:surface.connect = </World/Materials/GlossyMaterial/PreviewSurface.outputs:surface>
def Shader "PreviewSurface"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (0.2, 0.4, 0.8) # Blue
float inputs:metallic = 0.8
float inputs:roughness = 0.2
token outputs:surface
}
}
}
def Scope "Lights"
{
# MaterialX conical_edf for spotlight effect
def Shader "ConicalEDF" (
doc = "Conical emission distribution - spotlight"
)
{
uniform token info:id = "conical_edf"
color3f inputs:color = (1.0, 1.0, 1.0) # White light
vector3f inputs:normal = (0, -1, 0) # Pointing down
float inputs:inner_angle = 30.0 # Inner cone: 30 degrees
float inputs:outer_angle = 45.0 # Outer cone: 45 degrees (soft edge)
token outputs:out
}
def Shader "SpotLightShader"
{
uniform token info:id = "light"
token inputs:edf.connect = </World/Lights/ConicalEDF.outputs:out>
color3f inputs:intensity = (10.0, 10.0, 10.0)
float inputs:exposure = 1.0 # +1 EV brighter
token outputs:out
}
# USD RectLight with ShapingAPI (equivalent to conical_edf)
def RectLight "SpotLight" (
prepend apiSchemas = ["ShapingAPI"]
)
{
color3f inputs:color = (1.0, 1.0, 1.0)
float inputs:intensity = 10.0
float inputs:exposure = 1.0
float inputs:width = 0.1
float inputs:height = 0.1
# ShapingAPI attributes for cone control
float inputs:shaping:cone:angle = 30.0 # Inner angle
float inputs:shaping:cone:softness = 0.5 # Soft edge (outer_angle relationship)
float inputs:shaping:focus = 0.0
float3 xformOp:rotateXYZ = (90, 0, 0) # Point down
double3 xformOp:translate = (0, 5, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"]
}
}
}

View File

@@ -0,0 +1,112 @@
#usda 1.0
(
doc = """MaterialX IES Profile Light Example
This example demonstrates a light using MaterialX's measured_edf node with
an IES light profile. IES profiles provide realistic light distribution
patterns from real-world light fixtures.
The measured_edf maps to USD's SphereLight with ShapingAPI's IES support.
"""
metersPerUnit = 1
upAxis = "Y"
)
def Xform "World"
{
def Mesh "Floor" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </World/Materials/FloorMaterial>
int[] faceVertexCounts = [4]
int[] faceVertexIndices = [0, 1, 2, 3]
point3f[] points = [(-5, 0, -5), (5, 0, -5), (5, 0, 5), (-5, 0, 5)]
texCoord2f[] primvars:st = [(0, 0), (1, 0), (1, 1), (0, 1)] (
interpolation = "vertex"
)
}
def Sphere "Sphere" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </World/Materials/MetallicMaterial>
double radius = 0.8
double3 xformOp:translate = (0, 0.8, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
def Scope "Materials"
{
def Material "FloorMaterial"
{
token outputs:surface.connect = </World/Materials/FloorMaterial/PreviewSurface.outputs:surface>
def Shader "PreviewSurface"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (0.6, 0.6, 0.6)
float inputs:roughness = 0.8
token outputs:surface
}
}
def Material "MetallicMaterial"
{
token outputs:surface.connect = </World/Materials/MetallicMaterial/PreviewSurface.outputs:surface>
def Shader "PreviewSurface"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (0.95, 0.8, 0.3) # Gold color
float inputs:metallic = 1.0
float inputs:roughness = 0.15
token outputs:surface
}
}
}
def Scope "Lights"
{
# MaterialX measured_edf with IES profile
def Shader "MeasuredEDF" (
doc = "IES profile-based emission distribution"
)
{
uniform token info:id = "measured_edf"
color3f inputs:color = (1.0, 0.95, 0.85) # Warm light
asset inputs:file = @./ies_profiles/fixture_001.ies@ # Path to IES file
token outputs:out
}
def Shader "IESLightShader"
{
uniform token info:id = "light"
token inputs:edf.connect = </World/Lights/MeasuredEDF.outputs:out>
color3f inputs:intensity = (8.0, 8.0, 8.0)
float inputs:exposure = 0.5 # +0.5 EV
token outputs:out
}
# USD SphereLight with ShapingAPI IES (equivalent to measured_edf)
def SphereLight "IESLight" (
prepend apiSchemas = ["ShapingAPI"]
)
{
color3f inputs:color = (1.0, 0.95, 0.85)
float inputs:intensity = 8.0
float inputs:exposure = 0.5
float inputs:radius = 0.3
# ShapingAPI with IES profile
asset inputs:shaping:ies:file = @./ies_profiles/fixture_001.ies@
bool inputs:shaping:ies:normalize = true
float inputs:shaping:ies:angleScale = 0.0
double3 xformOp:translate = (0, 4, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
}
}

View File

@@ -0,0 +1,274 @@
#usda 1.0
(
doc = """Complete MaterialX Lighting and Material Scene
This comprehensive example demonstrates:
- Multiple MaterialX light types (uniform_edf, conical_edf, measured_edf)
- Advanced materials using UsdPreviewSurface
- Proper light-material interaction setup
- Color temperature and exposure controls
- Three-point lighting setup
"""
defaultPrim = "Scene"
metersPerUnit = 1
upAxis = "Y"
)
def Xform "Scene"
{
def Xform "Geometry"
{
def Mesh "Floor" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </Scene/Materials/ConcreteMaterial>
int[] faceVertexCounts = [4]
int[] faceVertexIndices = [0, 1, 2, 3]
point3f[] points = [(-10, 0, -10), (10, 0, -10), (10, 0, 10), (-10, 0, 10)]
normal3f[] normals = [(0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0)] (
interpolation = "vertex"
)
texCoord2f[] primvars:st = [(0, 0), (5, 0), (5, 5), (0, 5)] (
interpolation = "vertex"
)
}
def Sphere "CenterSphere" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </Scene/Materials/MetallicMaterial>
double radius = 1.0
double3 xformOp:translate = (0, 1, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
def Cube "LeftCube" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </Scene/Materials/DielectricMaterial>
double size = 1.5
double3 xformOp:translate = (-3, 0.75, 2)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
def Cylinder "RightCylinder" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </Scene/Materials/DiffuseMaterial>
double height = 2.5
double radius = 0.6
double3 xformOp:translate = (3, 1.25, -1)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
}
def Scope "Materials"
{
def Material "ConcreteMaterial"
{
token outputs:surface.connect = </Scene/Materials/ConcreteMaterial/Surface.outputs:surface>
def Shader "Surface"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (0.5, 0.5, 0.52)
float inputs:roughness = 0.9
float inputs:metallic = 0.0
token outputs:surface
}
}
def Material "MetallicMaterial"
{
token outputs:surface.connect = </Scene/Materials/MetallicMaterial/Surface.outputs:surface>
def Shader "Surface"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (0.95, 0.7, 0.2) # Gold
float inputs:roughness = 0.2
float inputs:metallic = 1.0
token outputs:surface
}
}
def Material "DielectricMaterial"
{
token outputs:surface.connect = </Scene/Materials/DielectricMaterial/Surface.outputs:surface>
def Shader "Surface"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (0.1, 0.3, 0.8) # Blue glass
float inputs:roughness = 0.05
float inputs:metallic = 0.0
float inputs:opacity = 0.3
float inputs:ior = 1.5
token outputs:surface
}
}
def Material "DiffuseMaterial"
{
token outputs:surface.connect = </Scene/Materials/DiffuseMaterial/Surface.outputs:surface>
def Shader "Surface"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (0.8, 0.2, 0.2) # Red
float inputs:roughness = 0.6
float inputs:metallic = 0.0
token outputs:surface
}
}
}
def Scope "Lights"
{
# KEY LIGHT: Main directional light using uniform_edf
def Scope "KeyLightSetup" (
doc = "Primary light source - warm white uniform emission"
)
{
def Shader "KeyEDF"
{
uniform token info:id = "uniform_edf"
color3f inputs:color = (1.0, 0.95, 0.85) # Warm white
token outputs:out
}
def Shader "KeyShader"
{
uniform token info:id = "light"
token inputs:edf.connect = </Scene/Lights/KeyLightSetup/KeyEDF.outputs:out>
color3f inputs:intensity = (8.0, 8.0, 8.0)
float inputs:exposure = 1.0
token outputs:out
}
# USD equivalent
def SphereLight "KeyLight"
{
color3f inputs:color = (1.0, 0.95, 0.85)
float inputs:intensity = 8.0
float inputs:exposure = 1.0
bool inputs:enableColorTemperature = true
float inputs:colorTemperature = 5500 # Warm daylight
float inputs:radius = 0.5
float3 xformOp:rotateXYZ = (0, 45, 0)
double3 xformOp:translate = (5, 7, 5)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"]
}
}
# FILL LIGHT: Softer light using conical_edf with wide cone
def Scope "FillLightSetup" (
doc = "Fill light - cool tone, wide spread"
)
{
def Shader "FillEDF"
{
uniform token info:id = "conical_edf"
color3f inputs:color = (0.8, 0.9, 1.0) # Cool white
vector3f inputs:normal = (0.7, -0.5, 0.5)
float inputs:inner_angle = 60.0 # Wide coverage
float inputs:outer_angle = 75.0
token outputs:out
}
def Shader "FillShader"
{
uniform token info:id = "light"
token inputs:edf.connect = </Scene/Lights/FillLightSetup/FillEDF.outputs:out>
color3f inputs:intensity = (3.0, 3.0, 3.0)
float inputs:exposure = 0.0
token outputs:out
}
# USD equivalent
def RectLight "FillLight" (
prepend apiSchemas = ["ShapingAPI"]
)
{
color3f inputs:color = (0.8, 0.9, 1.0)
float inputs:intensity = 3.0
float inputs:exposure = 0.0
bool inputs:enableColorTemperature = true
float inputs:colorTemperature = 7000 # Cool daylight
float inputs:width = 2.0
float inputs:height = 2.0
# ShapingAPI
float inputs:shaping:cone:angle = 60.0
float inputs:shaping:cone:softness = 0.25
float3 xformOp:rotateXYZ = (30, -135, 0)
double3 xformOp:translate = (-4, 4, 3)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"]
}
}
# BACK LIGHT: Rim light using conical_edf spotlight
def Scope "BackLightSetup" (
doc = "Back/rim light - tight cone for edge highlighting"
)
{
def Shader "BackEDF"
{
uniform token info:id = "conical_edf"
color3f inputs:color = (1.0, 1.0, 1.0)
vector3f inputs:normal = (0, -0.3, 1)
float inputs:inner_angle = 25.0 # Tight spotlight
float inputs:outer_angle = 35.0
token outputs:out
}
def Shader "BackShader"
{
uniform token info:id = "light"
token inputs:edf.connect = </Scene/Lights/BackLightSetup/BackEDF.outputs:out>
color3f inputs:intensity = (6.0, 6.0, 6.0)
float inputs:exposure = 0.5
token outputs:out
}
# USD equivalent
def RectLight "BackLight" (
prepend apiSchemas = ["ShapingAPI", "ShadowAPI"]
)
{
color3f inputs:color = (1.0, 1.0, 1.0)
float inputs:intensity = 6.0
float inputs:exposure = 0.5
float inputs:width = 0.3
float inputs:height = 0.3
# ShapingAPI for spotlight
float inputs:shaping:cone:angle = 25.0
float inputs:shaping:cone:softness = 0.4
float inputs:shaping:focus = 0.2
# ShadowAPI
bool inputs:shadow:enable = true
color3f inputs:shadow:color = (0.1, 0.1, 0.15)
float3 xformOp:rotateXYZ = (20, 0, 0)
double3 xformOp:translate = (0, 3, -6)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"]
}
}
# AMBIENT/DOME: Environment light (simulated with DomeLight)
def DomeLight "EnvironmentLight"
{
color3f inputs:color = (0.4, 0.45, 0.5) # Sky blue tint
float inputs:intensity = 0.3
float inputs:exposure = -1.0 # Dimmer ambient
}
}
}

View File

@@ -0,0 +1,143 @@
#usda 1.0
(
doc = """MaterialX File Reference Example
This example demonstrates how to reference MaterialX light definitions
from an external .mtlx file using USD's reference composition.
The MaterialX file contains light shader definitions that are loaded
and applied to USD light prims.
"""
defaultPrim = "Scene"
metersPerUnit = 1
upAxis = "Y"
)
def Xform "Scene"
{
def Sphere "TestGeometry" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </Scene/Materials/TestMaterial>
double radius = 1.0
double3 xformOp:translate = (0, 1, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
def Scope "Materials"
{
def Material "TestMaterial"
{
token outputs:surface.connect = </Scene/Materials/TestMaterial/Surface.outputs:surface>
def Shader "Surface"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (0.7, 0.7, 0.7)
float inputs:roughness = 0.5
float inputs:metallic = 0.3
token outputs:surface
}
}
}
def Scope "Lights"
{
# Reference MaterialX light shader from external file
# The .mtlx file contains the complete light shader definition
def Shader "MainLightFromMtlx" (
doc = "Light shader loaded from MaterialX file"
prepend references = @./example_light.mtlx@</main_light>
)
{
# The referenced MaterialX defines:
# - uniform_edf with color (1.0, 0.95, 0.8)
# - light shader with intensity (5.0, 5.0, 5.0)
# - exposure value 0.0
# We can override parameters here if needed
# color3f inputs:intensity = (6.0, 6.0, 6.0) # Override intensity
}
# Corresponding USD SphereLight for rendering
def SphereLight "MainLight"
{
color3f inputs:color = (1.0, 0.95, 0.8)
float inputs:intensity = 5.0
float inputs:exposure = 0.0
float inputs:radius = 0.5
double3 xformOp:translate = (4, 5, 3)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
# Reference spotlight from MaterialX
def Shader "SpotLightFromMtlx" (
doc = "Spotlight loaded from MaterialX file"
prepend references = @./example_light.mtlx@</spot_light>
)
{
# References conical_edf with 30-45 degree cone
}
# Corresponding USD RectLight with ShapingAPI
def RectLight "SpotLight" (
prepend apiSchemas = ["ShapingAPI"]
)
{
color3f inputs:color = (1.0, 1.0, 1.0)
float inputs:intensity = 10.0
float inputs:exposure = 1.0
float inputs:width = 0.2
float inputs:height = 0.2
# ShapingAPI for spotlight cone
float inputs:shaping:cone:angle = 30.0
float inputs:shaping:cone:softness = 0.5
float3 xformOp:rotateXYZ = (90, 0, 0)
double3 xformOp:translate = (-2, 4, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"]
}
# Reference IES light from MaterialX
def Shader "FixtureLightFromMtlx" (
doc = "IES profile light loaded from MaterialX file"
prepend references = @./example_light.mtlx@</fixture_light>
)
{
# References measured_edf with IES profile
}
# Corresponding USD SphereLight with IES
def SphereLight "FixtureLight" (
prepend apiSchemas = ["ShapingAPI"]
)
{
color3f inputs:color = (1.0, 0.95, 0.85)
float inputs:intensity = 8.0
float inputs:exposure = 0.5
float inputs:radius = 0.3
# ShapingAPI with IES profile
asset inputs:shaping:ies:file = @fixtures/led_spotlight.ies@
bool inputs:shaping:ies:normalize = true
double3 xformOp:translate = (2, 3.5, -2)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
}
}
over "Scene"
{
over "Lights"
{
# You can also override MaterialX light properties here
over "MainLightFromMtlx"
{
# Override the intensity from the MaterialX definition
color3f inputs:intensity = (7.0, 7.0, 7.0)
}
}
}

232
tests/feat/lux/README.md Normal file
View File

@@ -0,0 +1,232 @@
# MaterialX Light Shader Test Examples
This directory contains USDA test files demonstrating MaterialX light shader integration with TinyUSDZ.
## Files
### 01_basic_uniform_light.usda
**Basic Omnidirectional Point Light**
Demonstrates:
- `uniform_edf` - Emits light uniformly in all directions
- MaterialX `light` shader node combining EDF with intensity
- USD SphereLight equivalent representation
- Simple material binding
**Key Features:**
- Warm white light (color temperature simulation)
- Intensity and exposure controls
- Maps to USD SphereLight
---
### 02_conical_spotlight.usda
**Conical Spotlight with Soft Edges**
Demonstrates:
- `conical_edf` - Cone-shaped emission for spotlights
- Inner/outer angle controls for soft-edge spotlights
- MaterialX spotlight → USD RectLight + ShapingAPI mapping
**Key Features:**
- Inner cone angle: 30°
- Outer cone angle: 45° (creates soft falloff)
- ShapingAPI cone control
- Glossy metallic material for specular highlights
**Technical Notes:**
- `conical_edf` inner_angle maps to ShapingAPI cone:angle
- Outer angle creates softness via (outer - inner) / inner calculation
- Normal vector controls emission direction
---
### 03_measured_ies_light.usda
**IES Profile-Based Realistic Lighting**
Demonstrates:
- `measured_edf` - Real-world light distribution from IES profiles
- IES file integration via ShapingAPI
- Proper warm light color with IES pattern
**Key Features:**
- IES profile file reference
- IES normalization for consistent brightness
- Gold metallic material showcasing realistic light interaction
- Floor geometry for light pattern visualization
**Technical Notes:**
- IES files define real photometric light distributions
- MaterialX `file` input maps to ShapingAPI ies:file
- Normalization ensures consistent intensity across profiles
---
### 04_complete_scene.usda
**Three-Point Lighting Setup with Multiple Materials**
Demonstrates:
- Complete production lighting setup
- Multiple MaterialX EDF types in one scene
- Advanced material library (metallic, dielectric, diffuse, concrete)
- Professional lighting techniques
**Lighting Setup:**
1. **Key Light** (uniform_edf)
- Primary illumination source
- Warm white (5500K color temperature)
- High intensity (8.0) with +1 EV exposure
- Position: Front-right, elevated
2. **Fill Light** (conical_edf)
- Reduces harsh shadows from key light
- Cool white (7000K) for color variation
- Wide cone (60°) for broad coverage
- Lower intensity (3.0)
- Position: Front-left, elevated
3. **Back Light** (conical_edf)
- Rim lighting for edge definition
- Tight spotlight (25° cone)
- Higher intensity (6.0) for prominence
- ShadowAPI enabled for control
- Position: Behind and above
4. **Environment Light** (DomeLight)
- Ambient fill from sky
- Low intensity (0.3) with -1 EV
- Sky blue tint
**Materials:**
- **Concrete** - Rough, non-metallic floor (roughness: 0.9)
- **Metallic** - Gold sphere (metallic: 1.0, roughness: 0.2)
- **Dielectric** - Blue glass cube (transparent, IOR: 1.5)
- **Diffuse** - Red matte cylinder (roughness: 0.6)
---
### 05_mtlx_reference.usda
**MaterialX File Reference Example**
Demonstrates:
- Referencing external .mtlx files from USD
- USD reference composition with MaterialX
- Overriding MaterialX light parameters
- Multiple light shader references
**Key Features:**
- External MaterialX file (`example_light.mtlx`)
- Reference syntax: `prepend references = @./example_light.mtlx@</main_light>`
- Parameter overrides using USD's opinion strength
- Shows all three EDF types via references
**Technical Notes:**
- MaterialX files can be referenced like any USD layer
- USD path syntax `</main_light>` targets specific light in .mtlx
- Overrides in USD take precedence over MaterialX defaults
- Enables sharing MaterialX definitions across multiple USD files
---
### example_light.mtlx
**Pure MaterialX XML Light Definitions**
A standalone MaterialX 1.38 file containing:
- Three EDF definitions (uniform, conical, measured)
- Three light shader definitions
- Proper MaterialX XML structure
- Can be referenced from USD files
This file demonstrates the MaterialX XML format that TinyUSDZ can parse.
---
## MaterialX Light Shader Concepts
### Emission Distribution Functions (EDFs)
MaterialX separates light definition into:
1. **EDF Node** - Defines emission pattern (uniform, conical, measured)
2. **Light Shader** - Combines EDF with intensity/exposure controls
This modular approach allows reusing EDFs across multiple lights.
### USD Mapping
| MaterialX EDF | USD Light Type | Additional APIs |
|---------------|----------------|-----------------|
| uniform_edf | SphereLight | - |
| conical_edf | RectLight | ShapingAPI (cone) |
| measured_edf | SphereLight | ShapingAPI (IES) |
### Color Temperature
While MaterialX uses direct color3f values, USD supports color temperature:
- `enableColorTemperature` - Enables blackbody radiation calculation
- `colorTemperature` - Temperature in Kelvin (2700K-10000K typical range)
Common values:
- 2700K - Warm incandescent
- 3200K - Tungsten
- 5500K - Daylight
- 6500K - Cool daylight
- 7000K - Overcast sky
### Exposure Value (EV)
Exposure control using photographic EV stops:
- EV 0.0 = baseline intensity
- EV +1.0 = 2× brighter
- EV -1.0 = 0.5× dimmer
- Formula: final_intensity = base_intensity × 2^exposure
---
## Testing These Files
### With TinyUSDZ
```bash
# Parse and validate
tinyusdz_viewer 01_basic_uniform_light.usda
# Test MaterialX parsing
tinyusdz_test --mtlx 04_complete_scene.usda
```
### Expected Behavior
1. **Light Parsing**
- MaterialX light shaders should be recognized
- EDF nodes correctly parsed with parameters
- Light shader connections validated
2. **Conversion**
- MaterialX lights convert to appropriate USD light types
- Intensity and color properly transferred
- ShapingAPI applied for conical/measured EDFs
3. **Rendering**
- Three-point lighting creates proper illumination
- Materials respond correctly to light properties
- Shadows and specular highlights visible
---
## Notes
- IES file paths in `03_measured_ies_light.usda` are placeholder references
- For actual rendering, replace with valid IES profile files
- Color temperature is simulated via RGB values in pure MaterialX
- USD's native color temperature is used in USD light representations
---
## References
- [MaterialX Specification v1.38](https://materialx.org/assets/MaterialX.v1.38D1.Spec.pdf)
- [MaterialX PBR Spec](https://materialx.org/assets/MaterialX.v1.38.PBRSpec.pdf)
- [USD Lux Schema](https://openusd.org/release/api/usd_lux_page_front.html)
- [TinyUSDZ Documentation](../../../README.md)

View File

@@ -0,0 +1,56 @@
<?xml version="1.0"?>
<!--
MaterialX Light Shader Example
This is a pure MaterialX XML file demonstrating light shader definition.
It can be referenced from USD files using:
def Shader "MyLight" (
prepend references = @example_light.mtlx@
)
This follows MaterialX 1.38 specification.
-->
<materialx version="1.38" colorspace="lin_rec709">
<!-- Uniform EDF: Omnidirectional emission -->
<uniform_edf name="omnidirectional_edf" type="EDF">
<input name="color" type="color3" value="1.0, 0.95, 0.8" />
</uniform_edf>
<!-- Conical EDF: Spotlight emission -->
<conical_edf name="spotlight_edf" type="EDF">
<input name="color" type="color3" value="1.0, 1.0, 1.0" />
<input name="normal" type="vector3" value="0.0, -1.0, 0.0" />
<input name="inner_angle" type="float" value="30.0" />
<input name="outer_angle" type="float" value="45.0" />
</conical_edf>
<!-- Measured EDF: IES profile emission -->
<measured_edf name="ies_edf" type="EDF">
<input name="color" type="color3" value="1.0, 0.95, 0.85" />
<input name="file" type="filename" value="fixtures/led_spotlight.ies" />
</measured_edf>
<!-- Light shader: Main omnidirectional light -->
<light name="main_light" type="lightshader">
<input name="edf" type="EDF" nodename="omnidirectional_edf" />
<input name="intensity" type="color3" value="5.0, 5.0, 5.0" />
<input name="exposure" type="float" value="0.0" />
</light>
<!-- Light shader: Spotlight -->
<light name="spot_light" type="lightshader">
<input name="edf" type="EDF" nodename="spotlight_edf" />
<input name="intensity" type="color3" value="10.0, 10.0, 10.0" />
<input name="exposure" type="float" value="1.0" />
</light>
<!-- Light shader: IES-based light -->
<light name="fixture_light" type="lightshader">
<input name="edf" type="EDF" nodename="ies_edf" />
<input name="intensity" type="color3" value="8.0, 8.0, 8.0" />
<input name="exposure" type="float" value="0.5" />
</light>
</materialx>