Search Issue Tracker

By Design



Found in





Issue ID




Terrain with a custom shader receives a black tint while the Paint Texture tool is actively used



How to reproduce:
1. Open the attached project named "Case_1252634"
2. Open the Terrain Scene located in MicroSplat/Core/Examples/Scene
3. Select the Paint Texture tool and start painting the Terrain
4. Observe the Scene or Game window

Expected result: The Tile is rendered normally with no artifacts while painting
Actual result: The Terrain Tile receives a black tint while painting

Reproducible with: 2019.4.0f1, 2020.1.0b11, 2020.2.0a13
Could not test with: 2018.4.23f1(Project crashing upon launch)

1. Graphics APIs tested: D3D11, D3D12, Vulkan

  1. Response avatar

    Resolution Note (2020.2.X):

    Alright, tentatively resolving this one again.

    For some reason (not yet discovered) calling the Unity internal function 'WorldNormalVector' wrappped in a defined(UNITY_INSTANCING_ENABLED) doesn't yield a shader parsing that adds surfNeedsTangentSpace (which would populate the INTERNAL_DATA internalSurfaceTtoW0). The results is that 'WorldNormalVector' returns zero (which later causes a nan in:
    i.viewDir = normalize(mul( t2w, (_WorldSpaceCameraPos - i.worldPos)));) since all three tangent vectors are're defined as '0' by Unity.

    There is a workaround which is aligned with what MicroSplats does already: that is to explicitly use i.worldNormal _somwhere_ (which then causes the INTERNAL_DATA to be available)

    For example:
    l.Albedo *= saturate(normalize(i.viewDir + i.worldPos + i.worldNormal) + 9999);

    One thing I found out during this investigation is that i.viewDir is calculated by Unity as:

    half3 _unity_tbn_0 =;
    half3 _unity_tbn_1 =;
    half3 _unity_tbn_2 =;

    float3 worldPos = float3 ( IN.tSpace0.w , IN.tSpace1.w , IN.tSpace2.w ) ;
    float3 worldViewDir = normalize ( UnityWorldSpaceViewDir ( worldPos ) ) ;
    float3 viewDir = _unity_tbn_0 * worldViewDir.x + _unity_tbn_1 * worldViewDir.y + _unity_tbn_2 * worldViewDir.z ;
    surfIN.viewDir = viewDir;

    Where tSpace* are populated from the VS as:
    o . tSpace0 = float4 ( worldTangent . x , worldBinormal . x , worldNormal . x , worldPos . x ) ;
    o . tSpace1 = float4 ( worldTangent . y , worldBinormal . y , worldNormal . y , worldPos . y ) ;
    o . tSpace2 = float4 ( worldTangent . z , worldBinormal . z , worldNormal . z , worldPos . z ) ;

    I noticed that the MicroSplat/Example shader's path for UNITY_INSTANCING_ENABLED sets the normal as float3(0,1,0) which is then used in pixel shader _before_ the custom surface function is called to update the normal data. One thing I tried was to populate to read and feed the normal data from the vertex shader and that seemed to give a valid i.viewDir value in MicroSplatLayer SurfImpl (so theoretically no need to recalculate it). It could be something to investigate further (I noticed some weird shadows when I tried it so would need to investigate why that is).

    Hope this unblocks Jason, I will follow up with the core team to see if there would be a more elegant way to force the WorldNormalVector function to be valid without having to tickle i.worldNormal


Add comment

Log in to post comment

All about bugs

View bugs we have successfully reproduced, and vote for the bugs you want to see fixed most urgently.