Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

usdShade: Adding NormalMapTexture validator #3359

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
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
5 changes: 4 additions & 1 deletion pxr/usd/usdShade/plugInfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@
},
"MaterialBindingRelationships": {
"doc": "All properties named 'material:binding' or in that namespace should be relationships."
},
},
"NormalMapTextureValidator" : {
"doc": "UsdUVTexture nodes that feed the _inputs:normals_ of a UsdPreviewSurface must ensure that the data is encoded and scaled properly. Specifically, since normals are expected to be in the range [(-1,-1,-1), (1,1,1)], the Texture node must transform 8-bit textures from their [0..1] range by setting its _inputs:scale_ to (2, 2, 2, 1) and _inputs:bias_ to (-1, -1, -1, 0). Normal map data is commonly expected to be linearly encoded. However, many image-writing tools automatically set the profile of three-channel, 8-bit images to SRGB. To prevent an unwanted transformation, the UsdUVTexture's _inputs:sourceColorSpace_ must be set to raw."
},
"ShaderSdrCompliance": {
"doc": "Shader prim's input types must be conforming to their appropriate sdf types in the respective sdr shader.",
"schemaTypes": [
Expand Down
218 changes: 214 additions & 4 deletions pxr/usd/usdShade/testenv/testUsdShadeValidators.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "pxr/usd/usd/validator.h"
#include "pxr/usd/usdGeom/validatorTokens.h"
#include "pxr/usd/usdGeom/scope.h"
#include "pxr/usd/usdGeom/xform.h"
#include "pxr/usd/usdShade/shader.h"
#include "pxr/usd/usdShade/shaderDefUtils.h"
#include "pxr/usd/usdShade/tokens.h"
Expand All @@ -41,17 +42,18 @@ TestUsdShadeValidators()
// This should be updated with every new validator added with the
// UsdShadeValidators keyword.
const std::set<TfToken> expectedUsdShadeValidatorNames = {
UsdShadeValidatorNameTokens->encapsulationValidator,
UsdShadeValidatorNameTokens->materialBindingApiAppliedValidator,
UsdShadeValidatorNameTokens->materialBindingRelationships,
UsdShadeValidatorNameTokens->materialBindingCollectionValidator,
UsdShadeValidatorNameTokens->normalMapTextureValidator,
UsdShadeValidatorNameTokens->shaderSdrCompliance,
UsdShadeValidatorNameTokens->subsetMaterialBindFamilyName,
UsdShadeValidatorNameTokens->subsetsMaterialBindFamily
UsdShadeValidatorNameTokens->subsetsMaterialBindFamily,
UsdShadeValidatorNameTokens->encapsulationValidator
};

const UsdValidationRegistry& registry =
UsdValidationRegistry::GetInstance();
UsdValidationRegistry::GetInstance();

// Since other validators can be registered with the same keywords,
// our validators registered in usdShade are/may be a subset of the
Expand All @@ -60,7 +62,7 @@ TestUsdShadeValidators()

UsdValidatorMetadataVector metadata =
registry.GetValidatorMetadataForPlugin(_tokens->usdShadePlugin);
TF_AXIOM(metadata.size() == 7);
TF_AXIOM(metadata.size() == 8);
for (const UsdValidatorMetadata& metadata : metadata) {
validatorMetadataNameSet.insert(metadata.name);
}
Expand Down Expand Up @@ -557,6 +559,213 @@ TestUsdShadeEncapsulationRulesValidator()
}
}

void ValidateError(const UsdValidationErrorVector& errors,
const TfToken& expectedErrorIdentifier,
const SdfPath& expectedPrimPath,
const std::string& expectedErrorMsg,
UsdValidationErrorType expectedErrorType = UsdValidationErrorType::Error)
{
TF_AXIOM(errors.size() == 1);
TF_AXIOM(errors[0].GetIdentifier() == expectedErrorIdentifier);
TF_AXIOM(errors[0].GetType() == expectedErrorType);
TF_AXIOM(errors[0].GetSites().size() == 1);
TF_AXIOM(errors[0].GetSites()[0].IsValid());
TF_AXIOM(errors[0].GetSites()[0].IsPrim());
TF_AXIOM(errors[0].GetSites()[0].GetPrim().GetPath() ==
expectedPrimPath);
TF_AXIOM(errors[0].GetMessage() == expectedErrorMsg);
}

void
TestUsdShadeNormalMapTextureValidator()
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tallytalwar There are quite a few failure conditions in this test and validator, let me know if you think it would be better to break this up in to multiple validators or not

{
UsdValidationRegistry &registry = UsdValidationRegistry::GetInstance();
const UsdValidator *validator = registry.GetOrLoadValidatorByName(
UsdShadeValidatorNameTokens->normalMapTextureValidator);
TF_AXIOM(validator);

// Create a Stage, Material, and Two Shaders (UsdPreviewSurface,
// UsdUVTexture).
UsdStageRefPtr usdStage = UsdStage::CreateInMemory();
UsdShadeMaterial material = UsdShadeMaterial::Define(usdStage,
SdfPath("/RootMaterial"));

const std::string usdPreviewSurfaceShaderPath =
"/RootMaterial/UsdPreviewSurface";
UsdShadeShader usdPreviewSurfaceShader = UsdShadeShader::Define(
usdStage, SdfPath(usdPreviewSurfaceShaderPath));
usdPreviewSurfaceShader.CreateIdAttr(
VtValue(TfToken("UsdPreviewSurface")));
UsdPrim usdPreviewSurfaceShaderPrim = usdPreviewSurfaceShader.GetPrim();

UsdShadeShader usdUvTextureShader = UsdShadeShader::Define(
usdStage, SdfPath("/RootMaterial/NormalTexture"));
usdUvTextureShader.CreateIdAttr(VtValue(TfToken("UsdUVTexture")));

// Add initial valid file and sourceColorSpace input values.
std::string textureAssetPath = "./normalMap.jpg";
UsdShadeInput fileInput = usdUvTextureShader.CreateInput(
TfToken("file"), SdfValueTypeNames->Asset);
fileInput.Set(SdfAssetPath(textureAssetPath));
UsdShadeInput sourceColorSpaceInput = usdUvTextureShader.CreateInput(
TfToken("sourceColorSpace"), SdfValueTypeNames->Token);
const TfToken rawToken("raw");
sourceColorSpaceInput.Set(rawToken);

// Connect the output of the UsdUVTexture Shader to the normal of the
// UsdPreviewSurface Shader.
usdUvTextureShader.CreateOutput(TfToken("rgb"), SdfValueTypeNames->Float3);
UsdShadeInput normalInput = usdPreviewSurfaceShader.CreateInput(
TfToken("normal"), SdfValueTypeNames->Normal3f);
normalInput.ConnectToSource(
SdfPath("/RootMaterial/NormalTexture.outputs:rgb"));

// Verify invalid bias & scale error, they should exists and do
// not exist at this point.
UsdValidationErrorVector errors = validator->Validate(
usdPreviewSurfaceShaderPrim);
TfToken expectedErrorIdentifier(
"usdShade:NormalMapTextureValidator.NonCompliantBiasAndScale");
std::string expectedErrorMsg =
TfStringPrintf("UsdUVTexture prim <%s> reads 8 bit Normal Map "
"@./normalMap.jpg@, which requires that "
"inputs:scale be set to (2, 2, 2, 1) and "
"inputs:bias be set to (-1, -1, -1, 0) for proper "
"interpretation as per the UsdPreviewSurface and "
"UsdUVTexture docs.",
usdUvTextureShader.GetPath().GetText());
ValidateError(errors,
expectedErrorIdentifier,
usdUvTextureShader.GetPath(),
expectedErrorMsg);

// Add bias and scale, but add a non-compliant bias value.
UsdShadeInput biasInput = usdUvTextureShader.CreateInput(
TfToken("bias"), SdfValueTypeNames->Float4);
const GfVec4f compliantBias = GfVec4f(-1, -1, -1, 0);
const GfVec4f nonCompliantVector = GfVec4f(-9, -9, -9, -9);
biasInput.Set(nonCompliantVector);
UsdShadeInput scaleInput = usdUvTextureShader.CreateInput(
TfToken("scale"), SdfValueTypeNames->Float4);
const GfVec4f compliantScale = GfVec4f(2, 2, 2, 1);
scaleInput.Set(compliantScale);

// Verify the non-compliant bias value error occurs.
errors = validator->Validate(usdPreviewSurfaceShaderPrim);
expectedErrorIdentifier = TfToken(
"usdShade:NormalMapTextureValidator.NonCompliantBiasValues");
expectedErrorMsg =
TfStringPrintf("UsdUVTexture prim <%s> reads an 8 bit Normal "
"Map, but has non-standard inputs:bias value of "
"(%.6g, %.6g, %.6g, %.6g). inputs:bias must be set to "
"[-1,-1,-1,0] so as to fulfill the requirements "
"of the normals to be in tangent space of "
"[(-1,-1,-1), (1,1,1)] as documented in the "
"UsdPreviewSurface and UsdUVTexture docs.",
usdUvTextureShader.GetPath().GetText(),
nonCompliantVector[0], nonCompliantVector[1],
nonCompliantVector[2], nonCompliantVector[3]);
ValidateError(errors,
expectedErrorIdentifier,
usdUvTextureShader.GetPath(),
expectedErrorMsg);

// Update to a compliant bias and a non-compliant scale value.
biasInput.Set(compliantBias);
scaleInput.Set(nonCompliantVector);

// Verify the non-compliant scale value error occurs.
errors = validator->Validate(usdPreviewSurfaceShaderPrim);
expectedErrorIdentifier = TfToken(
"usdShade:NormalMapTextureValidator.NonCompliantScaleValues");
expectedErrorMsg =
TfStringPrintf("UsdUVTexture prim <%s> reads an 8 bit Normal "
"Map, but has non-standard inputs:scale value "
"of (%.6g, %.6g, %.6g, %.6g). inputs:scale must "
"be set to (2, 2, 2, 1) so as fulfill the "
"requirements of the normals to be in tangent "
"space of [(-1,-1,-1), (1,1,1)] as documented in "
"the UsdPreviewSurface and UsdUVTexture docs.",
usdUvTextureShader.GetPath().GetText(),
nonCompliantVector[0], nonCompliantVector[1],
nonCompliantVector[2], nonCompliantVector[3]);
ValidateError(errors,
expectedErrorIdentifier,
usdUvTextureShader.GetPath(),
expectedErrorMsg,
UsdValidationErrorType::Warn);

// Set a compliant scale value, and an invalid sourceColorSpace.
scaleInput.Set(compliantScale);
sourceColorSpaceInput.Set(TfToken("error"));

// Verify the invalid sourceColorSpace error occurs.
errors = validator->Validate(usdPreviewSurfaceShaderPrim);
expectedErrorIdentifier = TfToken(
"usdShade:NormalMapTextureValidator.InvalidSourceColorSpace");
expectedErrorMsg =
TfStringPrintf("UsdUVTexture prim <%s> that reads"
" Normal Map @%s@ should set "
"inputs:sourceColorSpace to 'raw'.",
usdUvTextureShader.GetPath().GetText(),
textureAssetPath.c_str());
ValidateError(errors,
expectedErrorIdentifier,
usdUvTextureShader.GetPath(),
expectedErrorMsg);

// Correct the sourceColorSpace, hook up the normal input of
// UsdPreviewSurface to a non-shader output.
sourceColorSpaceInput.Set(rawToken);
UsdGeomXform nonShaderPrim = UsdGeomXform::Define(
usdStage, SdfPath("/RootMaterial/Xform"));
UsdShadeConnectableAPI connectableNonShaderAPI(nonShaderPrim.GetPrim());
UsdShadeOutput nonShaderOutput = connectableNonShaderAPI.CreateOutput(
TfToken("myOutput"), SdfValueTypeNames->Float3);
nonShaderOutput.Set(GfVec3f(1.0f, 2.0f, 3.0f));
normalInput.ConnectToSource(nonShaderOutput);

// Verify a non-shader connection error occurs.
errors = validator->Validate(usdPreviewSurfaceShaderPrim);
expectedErrorIdentifier = TfToken(
"usdShade:NormalMapTextureValidator.NonShaderConnection");
expectedErrorMsg =
TfStringPrintf("UsdPreviewSurface.normal on prim <%s> is connected "
"to a non-Shader prim.",
usdPreviewSurfaceShaderPath.c_str());
ValidateError(errors,
expectedErrorIdentifier,
usdPreviewSurfaceShader.GetPath(),
expectedErrorMsg);

// Set the normal input back to a valid shader and update the file input
// to an invalid file path.
normalInput.ConnectToSource(
SdfPath("/RootMaterial/NormalTexture.outputs:rgb"));
fileInput.Set(SdfAssetPath("./doesNotExist.jpg"));

// Verify the invalid input file error occurs.
errors = validator->Validate(usdPreviewSurfaceShaderPrim);
expectedErrorIdentifier =
TfToken("usdShade:NormalMapTextureValidator.InvalidFile");
expectedErrorMsg =
TfStringPrintf("UsdUVTexture prim <%s> has invalid or unresolvable "
"inputs:file of @%s@",
usdUvTextureShader.GetPath().GetText(),
"./doesNotExist.jpg");
ValidateError(errors,
expectedErrorIdentifier,
usdUvTextureShader.GetPath(),
expectedErrorMsg);

// Reset the file to a valid path.
fileInput.Set(SdfAssetPath("./normalMap.jpg"));

// Verify no errors exist.
errors = validator->Validate(usdPreviewSurfaceShaderPrim);
TF_AXIOM(errors.empty());
}

int
main()
{
Expand All @@ -568,6 +777,7 @@ main()
TestUsdShadeSubsetMaterialBindFamilyName();
TestUsdShadeSubsetsMaterialBindFamily();
TestUsdShadeEncapsulationRulesValidator();
TestUsdShadeNormalMapTextureValidator();

return EXIT_SUCCESS;
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 14 additions & 6 deletions pxr/usd/usdShade/validatorTokens.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@
PXR_NAMESPACE_OPEN_SCOPE

#define USD_SHADE_VALIDATOR_NAME_TOKENS \
((encapsulationValidator, "usdShade:EncapsulationRulesValidator")) \
((materialBindingApiAppliedValidator, "usdShade:MaterialBindingApiAppliedValidator")) \
((materialBindingRelationships, "usdShade:MaterialBindingRelationships")) \
((materialBindingCollectionValidator, "usdShade:MaterialBindingCollectionValidator")) \
((shaderSdrCompliance, "usdShade:ShaderSdrCompliance")) \
((subsetMaterialBindFamilyName, "usdShade:SubsetMaterialBindFamilyName")) \
((encapsulationValidator, "usdShade:EncapsulationRulesValidator")) \
((materialBindingApiAppliedValidator, "usdShade:MaterialBindingApiAppliedValidator")) \
((materialBindingRelationships, "usdShade:MaterialBindingRelationships")) \
((materialBindingCollectionValidator, "usdShade:MaterialBindingCollectionValidator")) \
((normalMapTextureValidator, "usdShade:NormalMapTextureValidator")) \
((shaderSdrCompliance, "usdShade:ShaderSdrCompliance")) \
((subsetMaterialBindFamilyName, "usdShade:SubsetMaterialBindFamilyName")) \
((subsetsMaterialBindFamily, "usdShade:SubsetsMaterialBindFamily"))

#define USD_SHADE_VALIDATOR_KEYWORD_TOKENS \
Expand All @@ -43,6 +44,13 @@ PXR_NAMESPACE_OPEN_SCOPE
((mismatchPropertyType, "MismatchedPropertyType")) \
((missingFamilyNameOnGeomSubset, "MissingFamilyNameOnGeomSubset")) \
((invalidFamilyType, "InvalidFamilyType")) \
((nonShaderConnection, "NonShaderConnection")) \
((invalidFile, "InvalidFile")) \
((invalidShaderPrim, "InvalidShaderPrim")) \
((invalidSourceColorSpace, "InvalidSourceColorSpace")) \
((nonCompliantBiasAndScale, "NonCompliantBiasAndScale")) \
((nonCompliantScale, "NonCompliantScaleValues")) \
((nonCompliantBias, "NonCompliantBiasValues")) \

/// \def USD_SHADE_VALIDATOR_NAME_TOKENS
/// Tokens representing validator names. Note that for plugin provided
Expand Down
Loading