mirror of
https://github.com/lighttransport/tinyusdz.git
synced 2026-01-18 01:11:17 +01:00
usdlux
This commit is contained in:
178
src/usdLux.cc
178
src/usdLux.cc
@@ -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
|
||||
|
||||
|
||||
|
||||
158
src/usdLux.hh
158
src/usdLux.hh
@@ -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"
|
||||
|
||||
|
||||
313
src/usdMtlx.cc
313
src/usdMtlx.cc
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
75
tests/feat/lux/01_basic_uniform_light.usda
Normal file
75
tests/feat/lux/01_basic_uniform_light.usda
Normal 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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
89
tests/feat/lux/02_conical_spotlight.usda
Normal file
89
tests/feat/lux/02_conical_spotlight.usda
Normal 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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
112
tests/feat/lux/03_measured_ies_light.usda
Normal file
112
tests/feat/lux/03_measured_ies_light.usda
Normal 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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
274
tests/feat/lux/04_complete_scene.usda
Normal file
274
tests/feat/lux/04_complete_scene.usda
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
143
tests/feat/lux/05_mtlx_reference.usda
Normal file
143
tests/feat/lux/05_mtlx_reference.usda
Normal 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
232
tests/feat/lux/README.md
Normal 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)
|
||||
56
tests/feat/lux/example_light.mtlx
Normal file
56
tests/feat/lux/example_light.mtlx
Normal 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>
|
||||
Reference in New Issue
Block a user