[UE5.5.4] Groom validation fails when Strands are present but unused

Unreal Engine 5.5.4 introduces GroomComponent validation between the Groom asset and Binding asset via GHairStrands_GroomBindingValidationOnComponent (command line : r.HairStrands.GroomBindingValidationOnComponent)

We are seeing a Validation failure with assets that have Strands data but are set to use Cards for LOD 0.

The failure occurs here:

// Check strands

if (bValid && GroomResources.Strands.IsValid())

{

bValid = bValid && BindingResource.RenRootResources && BindingResource.RenRootResources->GetLODCount() > 0;

}

There appears to be logical mismatch between InitResources, which only allocates RenRootResources if Strands are enabled vs Validation, which tests for RenRootResources if Strands simply exist.

In our tests, enabling Strands instead of Cards at LOD0 did cause Validation to succeed. However, the Validation logic still appears to be incorrect.

Thanks!

  • Kelson Gist

Steps to Reproduce
Create a Groom Asset and Binding with both Strand and Cards and multiple LODs.

At LOD0, select Cards, rather than Strands as the Geometry Type

Run the game and note that the hair is not binding correctly (does not follow animation).

When debugging, note that validation fails here:

// Check strands

if (bValid && GroomResources.Strands.IsValid())

{

bValid = bValid && BindingResource.RenRootResources && BindingResource.RenRootResources->GetLODCount() > 0;

}

Hi,

Sorry for the late reply. Indeed, good catch, this is the first time I see a report of that issue.

Could you try the following code on your side? Base on the repro steps I managed to repro the issue on my side, and the code below fix the issue.

`if (LocalBindingAsset && GHairStrands_GroomBindingValidationOnComponent > 0)
{
// Ensure the groom binding asset’s resources match the groom asset’s resources.
// They can mismatch when the groom asset has been edited and the groom binding hasn’t been updated/finished to be built yet.
const uint32 NumGroup = LocalBindingAsset->GetHairGroupResources().Num();
check(NumGroup == GroomAsset->GetHairGroupsResources().Num());
for (uint32 GroupIt = 0; GroupIt < NumGroup; ++GroupIt)
{
const UGroomBindingAsset::FHairGroupResource& BindingResource = LocalBindingAsset->GetHairGroupResources()[GroupIt];
const FHairGroupResources& GroomResources = GroomAsset->GetHairGroupsResources()[GroupIt];

const bool bHasGuides = GroomAsset->NeedsInterpolationData(GroupIt);
const bool bHasStrands = GroomAsset->HasGeometryType(GroupIt, EGroomGeometryType::Strands);
const bool bHasCards = GroomAsset->HasGeometryType(GroupIt, EGroomGeometryType::Cards);

bool bValid = true;
// Check guides
if (GroomResources.Guides.IsValid() && bHasGuides)
{
bValid = bValid && BindingResource.SimRootResources && BindingResource.SimRootResources->GetLODCount() > 0;
}
// Check strands
if (bValid && GroomResources.Strands.IsValid() && bHasStrands)
{
bValid = bValid && BindingResource.RenRootResources && BindingResource.RenRootResources->GetLODCount() > 0;
}
// Check cards
if (bValid && GroomResources.Cards.LODs.Num() > 0 && bHasCards)
{
bValid = bValid && GroomResources.Cards.LODs.Num() == BindingResource.CardsRootResources.Num();
if (bValid)
{
for (uint32 CardIt = 0, CardCount = GroomResources.Cards.LODs.Num(); CardIt < CardCount; ++CardIt)
{
if (GroomResources.Cards.LODs[CardIt].IsValid())
{
bValid = bValid && BindingResource.CardsRootResources[CardIt] && BindingResource.CardsRootResources[CardIt]->GetLODCount() > 0;
}
}
}
}

if (!bValid)
{
LocalBindingAsset = nullptr;
break;
}
}
}`Thank you!

/Charles.

Great! The proposed fix works for our use cases.

Thanks [mention removed]​!

-Kelson

Perfect! The fix will be part of 5.6.

Thank you again for taking the time to report that issue!

/Charles.