Nanite Foliage and TwoSidedSign

Hello,

I have a few questions about Nanite Foliage, more specifically about the TwoSidedSign implementation for Nanite voxels.

GetNaniteVoxelTwoSidedSign (from NaniteVertexFactory.ush) currently always returns +1, meaning everything is regarded as front-face. This can cause issues with many two-sided foliage assets, not looking as they should when the geometry is switched to voxels For example because they darken albedo based on face orientation.

There is a even VOXELTODO there to implement it.

float GetNaniteVoxelTwoSidedSign(FInstanceSceneData InstanceData, FNaniteView NaniteView, FNaniteTransformedVert Vert)
{
    // VOXELTODO
    // Hack to determine TwoSidedSign from normals for voxel. Doesn't work well with trees that bend normals for shading
    const float3 WorldNormal = mul(Vert.TangentBasis.TangentZ, DFToFloat3x3(InstanceData.LocalToWorld));
    float Dot = dot(WorldNormal, NaniteView.ViewForward);

    //return Dot < 0.0 ? -1.0f : 1.0f;
    return 1.0f;
}

I tried enabling the commented-out normal test, but it looks that TangentBasis.TangentZ contains the principal axis of the fitted SGGX distribution rather than an oriented mean normal. So it appears to have an arbitrary sign.

Also, SGGX_Dvis uses saturate(VoM), which appears to restrict the normal to be camera-facing.

So my questions are:

1) Are there plans to implement GetNaniteVoxelTwoSidedSign properly?

2) Am I correct in saying the axis stored in TangentZ is really arbitrary? Or I am missing something?

3) It seems that it should be possible to at least store the axis orientation without any additional memory overhead. Is there a reason that was not done, or is it simply not done yet?

It seems that the idea was to sidestep this issue by using the currently disabled analytic SGGX shading path (using the currently disabled NANITE_USE_ANALYTIC_SGGX path). Looking at it closer the raw NDF axis would be stored in its own GBuffer channel and later used to evaluate during deferred lighting.

4) Is my reading of the analytic SGGX path correct?

5) What’s the status of the analytic SGGX shading path? The documentation at https://dev.epicgames.com/documentation/unreal\-engine/nanite\-foliage seems to say that this feature is upcoming.

6) Is the analytic SGGX path expected to work with Substrate? Some of its current gating still refers to the old MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE. Is missing Substrate integration the reason the path remains disabled?

Thank you.

[Attachment Removed]

Steps to Reproduce[Attachment Removed]

Hello,

Apologies for the delay. We haven’t made changes to this feature yet, but I’ve reached out to the developer working on this to find out if there’s any updates we can provide.

We’ve considered adding a new shading model instead, e.g. FoliageSGGX, and adding Substrate support for that. Making this work with Substrate, area lights and non-Nanite were some of the tricky implementation details we were looking into.

Hopefully, I’ll have more information soon.

[Attachment Removed]

1) Are there plans to implement GetNaniteVoxelTwoSidedSign properly?

There is an open task to work on this, but it can’t really be captured in SGGX because it’s symmetric. We’d like to avoid storing extra data to accomplish this and it might be good enough to use a hemisphere-align disk like distribution, in which the center axis of that which is stored as in the normal data can be chosen to point in the same hemisphere as the average front facing direction. The more disk like the distribution is the more it will be away from 0.5. This cannot capture facing or non-disk like distributions - basically it can’t be filtered and if that is true, it will likely need to be stochastically sampled which is an avenue for noise.

2) Am I correct in saying the axis stored in TangentZ is really arbitrary? Or I am missing something?

Yes, arbitrary by definition. Tangents do not form distribution functions and we don’t have plans to add that.

3) It seems that it should be possible to at least store the axis orientation without any additional memory overhead. Is there a reason that was not done, or is it simply not done yet? It seems that the idea was to sidestep this issue by using the currently disabled analytic SGGX shading path (using the currently disabled NANITE_USE_ANALYTIC_SGGX path). Looking at it closer the raw NDF axis would be stored in its own GBuffer channel and later used to evaluate during deferred lighting.

Correct. Initially we tried to replace GBuffer.WorldNormal with it but too many shader passes need a real normal and injecting stochastic sampling from the distribution into every such shader was more expensive than doing that once and writing it out. Applying normal maps to voxels may not be worthwhile given the analytic shading basically breaks down. There’s a likely assumption to be verified, that the normal map at the distance voxels kicks in actually has a negligible effect.

4) Is my reading of the analytic SGGX path correct?

5) What’s the status of the analytic SGGX shading path? The documentation at https://dev.epicgames.com/documentation/unreal-engine/nanite-foliage seems to say that this feature is upcoming.

Analytic SGGX shading is not fully there in 5.8. The math is and was used in the Witcher demo but the change to get it actually hooked up in UE release didn’t make it because Substrate was enabled by default and that path wasn’t supported. If you’re interested in those changes I can see about getting a patch for you to look at.

6) Is the analytic SGGX path expected to work with Substrate? Some of its current gating still refers to the old MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE. Is missing Substrate integration the reason the path remains disabled?

Ultimately the goal is to get support for analytic SGGX into the Substrate path. That is the last remaining feature of Nanite Foliage that existed in that demo that hasn’t made it to UE properly.

Are you currently using Substrate?

[Attachment Removed]

Yes, we are using Substrate.

I would be interested in the analytic shading path patch, but I’m not sure how soon would I be able to properly test it. Do you have any ETA about when it could land on UE6 Main?

In the mean time, I actually implemented TwoSidedVoxelSign at least for the fiber-like voxels stochastically and it does produce better results than treating everything as front face (ofc that might jut be true for our particular assets).

[Attachment Removed]

Here’s the analytic shading patch, we don’t have an ETA for getting an updated implementations into UE6 Main, but ideally it’d make it into the 5.9 release.

[Attachment Removed]