Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
v1.0.10 November 19th, 2024
fbx:
- small fbx spot light fixes
- missing file warning
- file names added to metadata now avoid dupes
gltf:
- ensure consistent light with usd
stl:
- fix up axis rotation, default is assumed to be z-up
utility:
- more robust handling of the no texture coordinate warning
- add mesh name in generated sub mesh

v1.0.9 October 29th, 2024
fbx:
- import dependent filenames now added to metadata
Expand Down
3 changes: 3 additions & 0 deletions fbx/src/fbx.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ namespace adobe::usd {
// lighting doesn't match
constexpr float FBX_TO_USD_INTENSITY_SCALE_FACTOR = 1.0;

constexpr float DEFAULT_POINT_LIGHT_RADIUS = 0.01; // 1 cm
constexpr float DEFAULT_SPOT_LIGHT_RADIUS = 0.1; // 10 cm

// Camera rotation to apply to revert to FBX coordinates, on export. Inspired by the Blender code
// base, which converts from -Z to +X with a 90º rotation around the Y axis:
// https://github.com/blender/blender/blob/e1a44ad129d53fbd47215845be2c42fb0850135d/scripts/addons_core/io_scene_fbx/fbx_utils.py#L74C64-L74C88
Expand Down
13 changes: 11 additions & 2 deletions fbx/src/fbxExport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -765,8 +765,17 @@ exportFbxLights(ExportFbxContext& ctx)
type = "spot (from USD disk light)";
lightType = FbxLight::EType::eSpot;

innerAngle = light.coneAngle;
outerAngle = light.coneFalloff;
// FBX inner cone angle is from the center to where falloff begins, and outer cone
// angle is from the center to where falloff ends. Meanwhile, in USD, angle is from
// the center to the edge of the cone, and softness is a number from 0 to 1
// indicating how close to the center the falloff begins.

// USD's cone angle is the entire shape of the spot light, corresponding to FBX's
// outer angle
outerAngle = light.coneAngle;

// Use the fraction of the cone containing the falloff to calculate the inner cone
innerAngle = (1 - light.coneFalloff) * outerAngle;

break;
case LightType::Rectangle:
Expand Down
47 changes: 30 additions & 17 deletions fbx/src/fbxImport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,6 @@ struct ImportFbxContext

// Each ImportedFbxStack has a cache of all anim layers present in that animation stack
std::vector<ImportedFbxStack> animationStacks;

// paths to files loaded on import
PXR_NS::VtArray<std::string> filenames;
};

// Metadata on USD will be stored uniformily in the CustomLayerData dictionary.
Expand Down Expand Up @@ -1133,7 +1130,7 @@ importFbxMaterials(ImportFbxContext& ctx)
normalizePathFromAnyOS(fileTexture->GetRelativeFileName());

// Add the path to the metadata even if the file is not present on disk.
ctx.filenames.push_back(filePathNormalized.u8string());
ctx.usd->importedFileNames.insert(filePathNormalized.u8string());

std::filesystem::path absFilePath;
if (isAbsolutePathFromAnyOS(filePathNormalized)) {
Expand Down Expand Up @@ -1183,7 +1180,7 @@ importFbxMaterials(ImportFbxContext& ctx)
} else {
std::ifstream file(absFileName, std::ios::binary);
if (!file.is_open()) {
TF_RUNTIME_ERROR("Failed to open file \"%s\"", absFileName.c_str());
TF_WARN("Failed to open file \"%s\"", absFileName.c_str());
continue;
}
file.seekg(0, file.end);
Expand Down Expand Up @@ -1401,11 +1398,14 @@ importFbxLight(ImportFbxContext& ctx, FbxNodeAttribute* attribute, int parent)
LightType usdType;
float coneAngle = 0;
float coneFalloff = 0;
float radius = 0.5;
switch (fbxLight->LightType.Get()) {
case FbxLight::ePoint:
type = "sphere (from FBX point light)";
usdType = LightType::Sphere;

radius = DEFAULT_POINT_LIGHT_RADIUS;

break;
case FbxLight::eDirectional:
type = "sun (from FBX directional light)";
Expand All @@ -1416,12 +1416,21 @@ importFbxLight(ImportFbxContext& ctx, FbxNodeAttribute* attribute, int parent)
type = "disk (from FBX spot light)";
usdType = LightType::Disk;

// According to FBX specs, inner angle is "HotSpot". In USD, this translates to the
// USDLuxShapingAPI ConeAngleAttribute
coneAngle = fbxLight->InnerAngle.Get();
// According to FBX specs, outer angle is falloff. In USD, this translates to the
// USDLuxShapingAPI ConeSoftnessAttribute
coneFalloff = fbxLight->OuterAngle.Get();
radius = DEFAULT_SPOT_LIGHT_RADIUS;

// FBX inner cone angle is from the center to where falloff begins, and outer cone
// angle is from the center to where falloff ends. Meanwhile, in USD, angle is from
// the center to the edge of the cone, and softness is a number from 0 to 1 indicating
// how close to the center the falloff begins.

// USD's cone angle is the entire shape of the spot light, corresponding to FBX's
// outer angle
coneAngle = fbxLight->OuterAngle.Get();

// Get the fraction of the cone containing the falloff
if (fbxLight->OuterAngle.Get()) {
coneFalloff = 1 - (fbxLight->InnerAngle.Get() / fbxLight->OuterAngle.Get());
}

break;
case FbxLight::eArea:
Expand Down Expand Up @@ -1478,8 +1487,10 @@ importFbxLight(ImportFbxContext& ctx, FbxNodeAttribute* attribute, int parent)

// TODO: Extract FBX light radius and replace this temporary dummy value with it. When this is
// updated, please update corresponding unit tests as well
light.radius = 0.5;
TF_WARN("importFbxLight: ignoring FBX light radius, setting radius=0.5\n");
light.radius = radius;
TF_WARN("importFbxLight: ignoring FBX light radius for light of type %s, setting radius=%f\n",
type.c_str(),
radius);

return true;
}
Expand Down Expand Up @@ -2133,6 +2144,12 @@ importFbx(const ImportFbxOptions& options, Fbx& fbx, UsdData& usd)
ctx.scene = fbx.scene;
ctx.originalColorSpace = options.originalColorSpace;

// Include the FBX file name itself in the filenames we add to the metadata
{
std::string baseName = TfGetBaseName(fbx.filename);
usd.importedFileNames.emplace(std::move(baseName));
}

importMetadata(ctx);
importFbxSettings(ctx);

Expand All @@ -2149,10 +2166,6 @@ importFbx(const ImportFbxOptions& options, Fbx& fbx, UsdData& usd)
setSkeletonParents(ctx);
}

if (!ctx.filenames.empty()) {
usd.metadata.SetValueAtPath("filenames", VtValue(ctx.filenames));
}

return true;
}
}
13 changes: 8 additions & 5 deletions gltf/src/gltf.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ governing permissions and limitations under the License.

namespace adobe::usd {

// Scale between intensity of USD lights and GLTF lights
const float GLTF_TO_USD_INTENSITY_SCALE_FACTOR = 100.0;
// Experimentally found to result in similar brightnesses between glTF and USD
constexpr float GLTF_POINT_LIGHT_INTENSITY_MULT = 0.225;
constexpr float GLTF_DIRECTIONAL_LIGHT_INTENSITY_MULT = 0.0000625;
constexpr float GLTF_SPOT_LIGHT_INTENSITY_MULT = 1.0;

// lights are by default given a diameter of 1, since there is no concept of light radius in glTF
const float DEFAULT_LIGHT_RADIUS = 0.5;
// Note there is no concept of a light radius in glTF
constexpr float DEFAULT_POINT_LIGHT_RADIUS = 0.01; // 1 cm
constexpr float DEFAULT_SPOT_LIGHT_RADIUS = 0.1; // 10 cm

// max color value of a pixel
const float MAX_COLOR_VALUE = 255.0f;
constexpr float MAX_COLOR_VALUE = 255.0f;

struct WriteGltfOptions
{
Expand Down
70 changes: 61 additions & 9 deletions gltf/src/gltfExport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,43 +224,95 @@ exportLightExtension(ExportGltfContext& ctx, int lightIndex, ExtMap& extensions)
bool
exportLights(ExportGltfContext& ctx)
{

ctx.gltf->lights.resize(ctx.usd->lights.size());
for (size_t i = 0; i < ctx.usd->lights.size(); ++i) {
const Light& light = ctx.usd->lights[i];
tinygltf::Light& gltfLight = ctx.gltf->lights[i];

float radius = light.radius;
GfVec2f length = light.length;

// Modify light values if the incoming USD values are in different units
if (ctx.usd->metersPerUnit > 0) {
if (radius > 0) {
radius *= ctx.usd->metersPerUnit;
}
if (length[0] > 0) {
length[0] *= ctx.usd->metersPerUnit;
}
if (length[1] > 0) {
length[1] *= ctx.usd->metersPerUnit;
}
}

// glTF doesn't use lights that emit based on their surface area, so will multiply the
// intensity below based on the light type
float intensity = light.intensity;

switch (light.type) {
case LightType::Disk: {
gltfLight.type = "spot";

// Only spot lights have innerConeAngle and outerConeAngle. We must make a separate
// "spot" attribute with this information
gltfLight.spot.innerConeAngle = GfDegreesToRadians(light.coneAngle);
gltfLight.spot.outerConeAngle = GfDegreesToRadians(light.coneFalloff);
// glTF inner cone angle is from the center to where falloff begins, and outer cone
// angle is from the center to where falloff ends. Meanwhile, in USD, angle is from
// the center to the edge of the cone, and softness is a number from 0 to 1
// indicating how close to the center the falloff begins.

// glTF outer cone angle is equivalent to USD cone angle
gltfLight.spot.outerConeAngle = GfDegreesToRadians(light.coneAngle);

// Use the fraction of the cone containing the falloff to calculate the inner cone
gltfLight.spot.innerConeAngle =
(1 - ctx.usd->lights[i].coneFalloff) * gltfLight.spot.outerConeAngle;

// inner cone angle must always be less than outer cone angle, according to the
// glTF spec. If it isn't, set it to be just less than the outer cone angle
const float epsilon = 1e-6;
if (gltfLight.spot.innerConeAngle >= gltfLight.spot.outerConeAngle &&
gltfLight.spot.outerConeAngle >= epsilon) {
gltfLight.spot.innerConeAngle = gltfLight.spot.outerConeAngle - epsilon;
}

if (radius > 0) { // Disk light, area = pi r^2
intensity *= (M_PI * radius * radius);
}

intensity *= GLTF_SPOT_LIGHT_INTENSITY_MULT;

} break;
break;
}
case LightType::Sun:
gltfLight.type = "directional";

intensity *= GLTF_DIRECTIONAL_LIGHT_INTENSITY_MULT;

break;
default:
// All other light types are encoded as point lights, since gltf supports fewer
// light types
gltfLight.type = "point";

if (radius > 0) { // Sphere light, area = 4 pi r^2
intensity *= (4.0 * M_PI * radius * radius);
} else if (length[0] > 0 && length[1] > 0) { // Rectangle light, area = l * w
intensity *= (length[0] * length[1]);
}

intensity *= GLTF_POINT_LIGHT_INTENSITY_MULT;

// TODO: Address environment lights separately

break;
}

gltfLight.name = light.name;

gltfLight.intensity = intensity;

gltfLight.color.resize(3);
gltfLight.color[0] = light.color[0];
gltfLight.color[1] = light.color[1];
gltfLight.color[2] = light.color[2];

// Divide by the scale factor to convert from USD to GLTF
gltfLight.intensity = light.intensity / GLTF_TO_USD_INTENSITY_SCALE_FACTOR;
}
return true;
}
Expand Down
41 changes: 36 additions & 5 deletions gltf/src/gltfImport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1913,25 +1913,56 @@ importLights(ImportGltfContext& ctx)
light.color[1] = gltfLight.color[1];
light.color[2] = gltfLight.color[2];
}
light.intensity = gltfLight.intensity * GLTF_TO_USD_INTENSITY_SCALE_FACTOR;

// GLTF lights have no radius, so we use a default value
light.radius = DEFAULT_LIGHT_RADIUS;
// USD uses lights that emit based on their surface area, so will multiply the intensity
// below based on the light type
float intensity = gltfLight.intensity;

// Add type-specific light info

if (gltfLight.type == "directional") {
light.type = LightType::Sun;

intensity /= GLTF_DIRECTIONAL_LIGHT_INTENSITY_MULT;

} else if (gltfLight.type == "point") {
light.type = LightType::Sphere;

// Divide by the surface area of a sphere, 4 pi r^2
intensity /= (4.0 * M_PI * DEFAULT_POINT_LIGHT_RADIUS * DEFAULT_POINT_LIGHT_RADIUS);
intensity /= GLTF_POINT_LIGHT_INTENSITY_MULT;

// glTF lights have no radius, so we use a default value
light.radius = DEFAULT_POINT_LIGHT_RADIUS;

} else if (gltfLight.type == "spot") {
light.type = LightType::Disk;

ctx.usd->lights[i].coneAngle = GfRadiansToDegrees(gltfLight.spot.innerConeAngle);
ctx.usd->lights[i].coneFalloff = GfRadiansToDegrees(gltfLight.spot.outerConeAngle);
// Divide by the area of a disk, pi r^2
intensity /= (M_PI * DEFAULT_SPOT_LIGHT_RADIUS * DEFAULT_SPOT_LIGHT_RADIUS);
intensity /= GLTF_SPOT_LIGHT_INTENSITY_MULT;

// glTF lights have no radius, so we use a default value
light.radius = DEFAULT_SPOT_LIGHT_RADIUS;

// glTF inner cone angle is from the center to where falloff begins, and outer cone
// angle is from the center to where falloff ends. Meanwhile, in USD, angle is from
// the center to the edge of the cone, and softness is a number from 0 to 1 indicating
// how close to the center the falloff begins.

// glTF outer cone angle is equivalent to USD cone angle
ctx.usd->lights[i].coneAngle = GfRadiansToDegrees(gltfLight.spot.outerConeAngle);

if (gltfLight.spot.outerConeAngle > 0) {
// Get the fraction of the cone containing the falloff
ctx.usd->lights[i].coneFalloff =
1 - (gltfLight.spot.innerConeAngle / gltfLight.spot.outerConeAngle);
} else {
ctx.usd->lights[i].coneFalloff = 0;
}
}

ctx.usd->lights[i].intensity = intensity;
}
}

Expand Down
9 changes: 4 additions & 5 deletions obj/src/obj.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1065,7 +1065,7 @@ addImage(Obj& obj,
image.uri = basename;
image.name = TfStringGetBeforeSuffix(basename);
image.format = getFormat(extension);
obj.filenames.push_back(filename);
obj.importedFilenames.insert(filename);
if (readImages) {
std::string fullFilename = parentPath + filename;
if (!readFileContents(fullFilename,
Expand Down Expand Up @@ -1516,7 +1516,7 @@ readObj(Obj& obj, const std::string& filename, bool readImages)
TfStopwatch watch;
watch.Start();
std::string baseName = TfGetBaseName(filename);
obj.filenames.push_back(baseName);
obj.importedFilenames.insert(baseName);
std::vector<char> objBuffer;
GUARD(readFileContents(filename, objBuffer), "Failed reading obj file");
watch.Stop();
Expand All @@ -1530,7 +1530,7 @@ readObj(Obj& obj, const std::string& filename, bool readImages)
const std::string parentPath = TfGetPathName(filename);
for (size_t i = 0; i < obj.libraries.size(); i++) {
ObjMaterialLibrary& library = obj.libraries[i];
obj.filenames.push_back(library.filename);
obj.importedFilenames.insert(library.filename);
std::string materialFilename = parentPath + library.filename;
std::vector<char> materialBuffer;
if (!readFileContents(materialFilename, materialBuffer)) {
Expand Down Expand Up @@ -1631,12 +1631,11 @@ writeObjHeader(const Obj& obj, std::fstream& file)

buffer.directWrite("# Obj model");
buffer.directWrite("\n# This model was generated by the USD fileformat plugin");
for (const auto& comment: obj.comments)
for (const auto& comment : obj.comments)
buffer.directWrite(std::string("\n") + comment);
buffer.flush();
}


// Writes obj geometry to the stream `file` in a buffered way.
/// See `BufferControl`.
void
Expand Down
2 changes: 1 addition & 1 deletion obj/src/obj.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ struct ObjObject
struct Obj
{
bool hasAdobeProperties = false;
PXR_NS::VtArray<std::string> filenames;
std::set<std::string> importedFilenames;
std::vector<ObjObject> objects;
std::vector<ObjMaterial> materials;
std::vector<ImageAsset> images;
Expand Down
Loading