Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question modification of the standard terrain shader

Discussion in 'Shaders' started by Zimaell, Sep 15, 2023.

  1. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    I want to achieve normal display of the texture on the terrain so that it does not stretch, at the moment there are positive aspects, but it still needs to be improved.

    I used the standard terrain lit shader and changed the following lines

    Code (CSharp):
    1. diffAlbedo[0] = SAMPLE_TEXTURE2D(_Splat0, sampler_Splat0, uvSplat01.xy);
    2. diffAlbedo[1] = SAMPLE_TEXTURE2D(_Splat1, sampler_Splat0, uvSplat01.zw);
    3. diffAlbedo[2] = SAMPLE_TEXTURE2D(_Splat2, sampler_Splat0, uvSplat23.xy);
    4. diffAlbedo[3] = SAMPLE_TEXTURE2D(_Splat3, sampler_Splat0, uvSplat23.zw);
    replace diffAlbedo[0]

    Code (CSharp):
    1. half3 geometryNormal = normalize(cross(ddy(positionWS), ddx(positionWS)));
    2. float3 weights = geometryNormal * geometryNormal;
    3. weights *= weights;
    4. weights /= dot(weights, 1.0f);
    5.  
    6. diffAlbedo[0] = weights.x * SAMPLE_TEXTURE2D(_Splat0, sampler_Splat0, positionWS.yz)
    7.    + weights.y * SAMPLE_TEXTURE2D(_Splat0, sampler_Splat0, positionWS.zx)
    8.    + weights.z * SAMPLE_TEXTURE2D(_Splat0, sampler_Splat0, positionWS.xy);
    and this gave the full triplanar effect without any artifacts, the disadvantage is that there is no tiling, and I have not yet figured out how to do the same with the rest of the layers.

    Any ideas on how to finish this?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,221
    What do you mean it doesn't tile? Are you taking about how the texture no longer matches across the triplanar seams? It's not possible to fix that. Not unless the mesh this is applied to perfectly matches the repeating texture size, which isn't possible with Unity terrain.

    As for the other layers, copy paste the code 3 more times and replace the textures and samplers.
     
  3. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    I made the following changes
    Code (CSharp):
    1. //diffAlbedo[0] = SAMPLE_TEXTURE2D(_Splat0, sampler_Splat0, uvSplat01.xy);
    2.     //diffAlbedo[1] = SAMPLE_TEXTURE2D(_Splat1, sampler_Splat0, uvSplat01.zw);
    3.     //diffAlbedo[2] = SAMPLE_TEXTURE2D(_Splat2, sampler_Splat0, uvSplat23.xy);
    4.     //diffAlbedo[3] = SAMPLE_TEXTURE2D(_Splat3, sampler_Splat0, uvSplat23.zw);
    5.  
    6.     half3 geometryNormal = normalize(cross(ddy(positionWS), ddx(positionWS)));
    7.     float3 weights = geometryNormal * geometryNormal;
    8.     weights *= weights;
    9.     weights /= dot(weights, 1.0f);
    10.     float2 tiling = float2(1, 1); //float2(0.15, 0.15);
    11.  
    12.     diffAlbedo[0] = weights.x * SAMPLE_TEXTURE2D(_Splat0, sampler_Splat0, positionWS.zy * tiling)
    13.         + weights.y * SAMPLE_TEXTURE2D(_Splat0, sampler_Splat0, positionWS.xz * tiling)
    14.         + weights.z * SAMPLE_TEXTURE2D(_Splat0, sampler_Splat0, positionWS.xy * tiling);
    15.     diffAlbedo[1] = weights.x * SAMPLE_TEXTURE2D(_Splat1, sampler_Splat0, positionWS.zy * tiling)
    16.         + weights.y * SAMPLE_TEXTURE2D(_Splat1, sampler_Splat0, positionWS.xz * tiling)
    17.         + weights.z * SAMPLE_TEXTURE2D(_Splat1, sampler_Splat0, positionWS.xy * tiling);
    18.     diffAlbedo[2] = weights.x * SAMPLE_TEXTURE2D(_Splat2, sampler_Splat0, positionWS.zy * tiling)
    19.         + weights.y * SAMPLE_TEXTURE2D(_Splat2, sampler_Splat0, positionWS.xz * tiling)
    20.         + weights.z * SAMPLE_TEXTURE2D(_Splat2, sampler_Splat0, positionWS.xy * tiling);
    21.     diffAlbedo[3] = weights.x * SAMPLE_TEXTURE2D(_Splat3, sampler_Splat0, positionWS.zy * tiling)
    22.         + weights.y * SAMPLE_TEXTURE2D(_Splat3, sampler_Splat0, positionWS.xz * tiling)
    23.         + weights.z * SAMPLE_TEXTURE2D(_Splat3, sampler_Splat0, positionWS.xy * tiling);
    Code (CSharp):
    1. //nrm += splatControl.r * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal0, sampler_Normal0, uvSplat01.xy), _NormalScale0);
    2.         //nrm += splatControl.g * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal1, sampler_Normal0, uvSplat01.zw), _NormalScale1);
    3.         //nrm += splatControl.b * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal2, sampler_Normal0, uvSplat23.xy), _NormalScale2);
    4.         //nrm += splatControl.a * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal3, sampler_Normal0, uvSplat23.zw), _NormalScale3);
    5.  
    6.         nrm += splatControl.r * (weights.x * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal0, sampler_Normal0, positionWS.zy * tiling), _NormalScale0)
    7.             + weights.y * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal0, sampler_Normal0, positionWS.xz * tiling), _NormalScale0)
    8.             + weights.z * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal0, sampler_Normal0, positionWS.xy * tiling), _NormalScale0));
    9.         nrm += splatControl.g * (weights.x * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal1, sampler_Normal0, positionWS.zy * tiling), _NormalScale1)
    10.             + weights.y * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal1, sampler_Normal0, positionWS.xz * tiling), _NormalScale1)
    11.             + weights.z * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal1, sampler_Normal0, positionWS.xy * tiling), _NormalScale1));
    12.         nrm += splatControl.b * (weights.x * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal2, sampler_Normal0, positionWS.zy * tiling), _NormalScale2)
    13.             + weights.y * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal2, sampler_Normal0, positionWS.xz * tiling), _NormalScale2)
    14.             + weights.z * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal2, sampler_Normal0, positionWS.xy * tiling), _NormalScale2));
    15.         nrm += splatControl.a * (weights.x * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal3, sampler_Normal0, positionWS.zy * tiling), _NormalScale3)
    16.             + weights.y * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal3, sampler_Normal0, positionWS.xz * tiling), _NormalScale3)
    17.             + weights.z * UnpackNormalScale(SAMPLE_TEXTURE2D(_Normal3, sampler_Normal0, positionWS.xy * tiling), _NormalScale3));
    tile is a general size adjustment.

    in the screenshots you will see the result, now what exactly is the result:
    (I chose the texture for the test so that the result could be seen)

    The textures - suit me, although if it were possible to add some kind of blur at the joints themselves, it would be even better.

    Normals - and here’s the problem, firstly they are inverted on the left and back walls, secondly look at what happens on the walls when the height changes sharply.

    Tell me how to fix these shortcomings?
     

    Attached Files:

  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,221
    https://bgolus.medium.com/normal-mapping-for-a-triplanar-shader-10bf39dca05a

    The solution is to go back to the interpolated normals, but then you have to deal with other issues.

    Really, terrain isn't the ideal way to handle the kind of extreme stepped shapes you seem to be going for. A custom mesh with small transitions on the angles and weighted normals would help a lot.
     
    Zimaell likes this.
  5. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    I've seen a lot of examples for triplanar normals (mostly old ones), but they are for regular meshes, not for terrain.

    Tell me, should the examples for regular meshes and for terrain be different at all or do they have the same approach?

    What is the main difference between making a triplanar for a terrain and a regular surface?
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,221
    Nothing, apart from you're usually doing it multiple times for terrain.

    Biplanar mapping may be another option to make it a little better.
    https://iquilezles.org/articles/biplanar/

    However, the concepts in that article are agnostic to whatever method is used. It's really about how to handle using tangent space normal maps on procedural UVs vs using the existing UVs on a mesh, and why doing nothing causes problems.
     
    Zimaell likes this.
  7. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    I just can’t apply this to my examples, everything will turn out completely dark, I don’t know what I’m doing wrong...
    For example, I do one layer like this
    Code (CSharp):
    1. float2 uvX = positionWS.zy;
    2.     float2 uvY = positionWS.xz;
    3.     float2 uvZ = positionWS.xy;
    4.  
    5.     half3 tnormalX = UnpackNormal(SAMPLE_TEXTURE2D(_Normal0, sampler_Normal0, uvX));
    6.     half3 tnormalY = UnpackNormal(SAMPLE_TEXTURE2D(_Normal0, sampler_Normal0, uvY));
    7.     half3 tnormalZ = UnpackNormal(SAMPLE_TEXTURE2D(_Normal0, sampler_Normal0, uvZ));
    8.  
    9.     half3 axisSign = sign(geometryNormal);
    10.  
    11.     //tnormalX.x *= axisSign.x;
    12.     //tnormalY.x *= axisSign.y;
    13.     //tnormalZ.x *= -axisSign.z;
    14.  
    15.     tnormalX.z *= axisSign.x;
    16.     tnormalY.z *= axisSign.y;
    17.     tnormalZ.z *= axisSign.z;
    18.  
    19.     half3 triblend = saturate(pow(geometryNormal, 4));
    20.     triblend /= max(dot(triblend, half3(1,1,1)), 0.0001);
    21.  
    22.     nrm += splatControl.r * normalize(tnormalX.zyx * triblend.x + tnormalY.xzy * triblend.y + tnormalZ.xyz * triblend.z);
    but it doesn't work....
     
  8. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    if you are familiar with the standard terrain lit shader, tell me - a triplanad can be made in method SplatmapMix, or is this beyond its scope, I would just like to know what exactly to focus on...
    maybe I’m editing in the wrong place and some data further change the result I want...
     
  9. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    So I brought the code to a more normal form, at the moment the problem is only with the directions of the normals, I can’t understand how this should be in my code...
    Code (CSharp):
    1. float3 GetSideWeights(half3 geometryNormal){
    2.     float3 sideWeight = geometryNormal * geometryNormal;
    3.     sideWeight *= sideWeight;
    4.     sideWeight /= dot(sideWeight, 1.0f);
    5.     return sideWeight;
    6.     }
    7.  
    8. half4 GetAlbebo(Texture2D splatTX, sampler ss, float3 pos, float3 sideWeight){
    9.     return sideWeight.x * SAMPLE_TEXTURE2D(splatTX, ss, pos.zy)
    10.         + sideWeight.y * SAMPLE_TEXTURE2D(splatTX, ss, pos.xz)
    11.         + sideWeight.z * SAMPLE_TEXTURE2D(splatTX, ss, pos.xy);
    12.     }
    13.  
    14. half3 GetNormal(Texture2D normalTX, sampler ss, float3 pos, half3 normal, float3 sideWeight, float scale){
    15.     return sideWeight.x * UnpackNormalScale(SAMPLE_TEXTURE2D(normalTX, ss, pos.zy), scale)
    16.         + sideWeight.y * UnpackNormalScale(SAMPLE_TEXTURE2D(normalTX, ss, pos.xz), scale)
    17.         + sideWeight.z * UnpackNormalScale(SAMPLE_TEXTURE2D(normalTX, ss, pos.xy), scale);
    18.     }
    19.  
    20. void SplatmapMix(Varyings IN, inout half4 splatControl, out half4 mixedDiffuse, out half4 defaultSmoothness, inout half3 mixedNormal){
    21.     half4 diffAlbedo[4];
    22.  
    23.     half3 geometryNormal = normalize(cross(ddy(IN.positionWS), ddx(IN.positionWS)));
    24.     float3 sideWeight = GetSideWeights(geometryNormal);
    25.  
    26.     diffAlbedo[0] = GetAlbebo(_Splat0, sampler_Splat0, IN.positionWS, sideWeight);
    27.     diffAlbedo[1] = GetAlbebo(_Splat1, sampler_Splat0, IN.positionWS, sideWeight);
    28.     diffAlbedo[2] = GetAlbebo(_Splat2, sampler_Splat0, IN.positionWS, sideWeight);
    29.     diffAlbedo[3] = GetAlbebo(_Splat3, sampler_Splat0, IN.positionWS, sideWeight);
    30.  
    31.     defaultSmoothness = half4(diffAlbedo[0].a, diffAlbedo[1].a, diffAlbedo[2].a, diffAlbedo[3].a);
    32.     defaultSmoothness *= half4(_Smoothness0, _Smoothness1, _Smoothness2, _Smoothness3);
    33.     half weight = dot(splatControl, 1.0h);
    34.     #ifdef TERRAIN_SPLAT_ADDPASS
    35.         clip(weight <= 0.005h ? -1.0h : 1.0h);
    36.     #endif
    37.     #ifndef _TERRAIN_BASEMAP_GEN
    38.         splatControl /= (weight + HALF_MIN);
    39.     #endif
    40.     mixedDiffuse = 0.0h;
    41.     mixedDiffuse += diffAlbedo[0] * half4(_DiffuseRemapScale0.rgb * splatControl.rrr, 1.0h);
    42.     mixedDiffuse += diffAlbedo[1] * half4(_DiffuseRemapScale1.rgb * splatControl.ggg, 1.0h);
    43.     mixedDiffuse += diffAlbedo[2] * half4(_DiffuseRemapScale2.rgb * splatControl.bbb, 1.0h);
    44.     mixedDiffuse += diffAlbedo[3] * half4(_DiffuseRemapScale3.rgb * splatControl.aaa, 1.0h);
    45.  
    46.     //---------------------------------------------------------------------------------------------------------
    47.  
    48.     half3 nrm = half(0.0);
    49.     #if defined(_NORMALMAP)
    50.         nrm += splatControl.r * GetNormal(_Normal0, sampler_Normal0, IN.positionWS, geometryNormal, sideWeight, _NormalScale0);
    51.         nrm += splatControl.g * GetNormal(_Normal1, sampler_Normal0, IN.positionWS, geometryNormal, sideWeight, _NormalScale1);
    52.         nrm += splatControl.b * GetNormal(_Normal2, sampler_Normal0, IN.positionWS, geometryNormal, sideWeight, _NormalScale2);
    53.         nrm += splatControl.a * GetNormal(_Normal3, sampler_Normal0, IN.positionWS, geometryNormal, sideWeight, _NormalScale3);
    54.     #endif
    55.     mixedNormal = normalize(nrm.xyz);
    56.     }
    Tell me what exactly should I change and how?
     
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,221
    Read the first article I linked to.
     
    Zimaell likes this.
  11. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    I read, I made the changes suggested there, but the result was completely dark, I don’t know why, unfortunately it’s still difficult for me to understand this topic.

    The last code that I presented is completely working except for the sides, I can’t figure out how to apply it correctly. I'm stuck and can't figure out how to move forward...
     
  12. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,221
    You last code example isn’t implementing any of the multiple solutions described in that article. I can only really say read it again, all of it. Don’t just skip to the solutions. It discusses what the problem is, what tangent space normals are, and what needs to be done to fix it. Unless you understand that part, the solutions won’t make any sense. But you haven’t even seem to have done the minimal effort of copy pasting some of the solutions into your code and trying to get it to work.
     
    Zimaell likes this.
  13. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    according to the examples from the article, I just need to redo my GetNormal method from my code, I remade

    Before
    Code (CSharp):
    1. half3 GetNormal(Texture2D normalTX, sampler ss, float3 pos, half3 normal, float3 sideWeight, float scale){
    2.     return sideWeight.x * UnpackNormalScale(SAMPLE_TEXTURE2D(normalTX, ss, pos.zy), scale)
    3.         + sideWeight.y * UnpackNormalScale(SAMPLE_TEXTURE2D(normalTX, ss, pos.xz), scale)
    4.         + sideWeight.z * UnpackNormalScale(SAMPLE_TEXTURE2D(normalTX, ss, pos.xy), scale);
    5.     }
    After
    Code (CSharp):
    1. half3 GetNormal(Texture2D normalTX, sampler ss, float3 pos, half3 normal, float3 sideWeight, float scale){
    2.     half3 tnormalX = UnpackNormalScale(SAMPLE_TEXTURE2D(normalTX, ss, pos.zy), scale);
    3.     half3 tnormalY = UnpackNormalScale(SAMPLE_TEXTURE2D(normalTX, ss, pos.xz), scale);
    4.     half3 tnormalZ = UnpackNormalScale(SAMPLE_TEXTURE2D(normalTX, ss, pos.xy), scale);
    5.     half3 axisSign = sign(normal);
    6.     tnormalX.z *= axisSign.x;
    7.     tnormalY.z *= axisSign.y;
    8.     tnormalZ.z *= axisSign.z;
    9.     half3 normalOut = normalize(
    10.         tnormalX.zyx * sideWeight.x +
    11.         tnormalY.xzy * sideWeight.y +
    12.         tnormalZ.xyz * sideWeight.z
    13.         );
    14.     return normalOut;
    15.     }
    (I also experimented with the normal strength)
    but look at the result, what am I doing wrong?
     

    Attached Files:

  14. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,221
    Zimaell likes this.
  15. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    I can’t use method WorldNormalVector(), apparently due to the fact that the URP libraries are connected, I only get the error "undeclared identifier 'WorldNormalVector'", Is there any analogue of this method? I haven't found a replacement yet...
     
  16. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,221
    That depends on your shader code. URP has a built in
    TransformWorldToTangent()
    function. But also since this is your own code you can presumably just avoid calling
    TransformTangentToWorld()
    to begin with as the normal is already in world space.
     
    Zimaell likes this.
  17. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    this raises even more confusion and questions, I found such a function in the graph shader library
    https://github.com/Unity-Technologi...derGraphLibrary/ShaderVariablesFunctions.hlsl
    Code (CSharp):
    1. float3 TransformWorldToTangent(float3 dirWS, float3x3 worldToTangent)
    2. {
    3.     return mul(worldToTangent, dirWS);
    4. }
    worldToTangent create from CreateWorldToTangent, and there need tangent and flipSign....
    I'm completely confused...
     
  18. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,221
    I think you need a refresher on how to implement a tangent space normal map in a basic shader, and maybe what transform matrices are.

    As discussed in that linked article, tangent space normal maps are a way to store the direction a surface should appear to be facing relative to the mesh's UVs. That's what "tangent space" is, an orientation relative to the UVs. To convert from tangent space to world space you need to know what direction in world space the 3 axis of tangent space are facing. Normally this is calculated offline and stored in the mesh's vertex data, this is the vertex's normal (tangent space Z), and tangent (tangent space X) vectors. For Unity, and most real time renderers, the bitangent isn't directly stored, and instead reconstructed by a cross product of the normal and tangent (which returns a vector that's perpendicular to both), and a flip sign if the bitangent needs to be reversed from what the cross product produces. The resulting three vectors are put together to create a rotation transform matrix that is passed from the vertex shader to the fragment shader, and can be used to transform a tangent space vector to world space.

    The issue with triplanar shaders, or any shader that uses procedural UVs, the resulting UV most likely doesn't match the mesh's original UVs and thus that tangent to world transform matrix is wrong for any normal maps using procedural UVs.

    The main concept in the article I linked is taking advantage of the fact the procedural UVs for triplanar mapping are world space aligned, and thus you don't need to construct a full tangent to world transform matrix for each plane to get the tangent space normals into world space. (Though I do have example shaders that do that too.)


    So somewhere in the shader code you're working with, the vectors for tangent to world matrix are being passed from the vertex to the fragment shader, and being used to incorrectly transform the normal you're calculating. Without seeing the entire shader code to see exactly what you have I can't really tell you where or what you need to change.
     
    Zimaell likes this.
  19. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    my shader is a standard (URP)\Terrain\Lit shader + my changes have been made to it, which I wrote in full here...

    Unfortunately, I'm not very familiar with shaders, I just needed to add a triplanar to the standard shader, but I just settled on normals...
     
  20. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,221
    You'd need to be modifying, or overriding TerrainLitPasses.hlsl then. Search that file for
    TransformTangentToWorld
    , and know you'll need to override those lines to not apply the transform, but instead just use the normals that you already have, as they're already in world space.
     
  21. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    yes, this is the file I change, namely 2 methods SplatmapMix() and NormalMapMix(), Above I wrote how I changed them, I tried to change the normals in different ways, but it didn’t work.
    That's why I'm asking how...
     
  22. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,221
     
  23. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    Is it possible to somehow make a debug in the shader to see the direction of the normals from the vertices and what direction is obtained after the calculations?
     
  24. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,221
    There is, sort of. But you're missing the main point.

    When using normal maps and triplanar mapping you do not want to use the mesh's tangent space!
     
  25. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    I re-read and revised, and also decided to do it with a regular shader, in general, look what I did:

    I took the standard (URP) Lit shader (not Terrain Lit)
    made the following changes

    Before
    Code (CSharp):
    1. half4 albedoAlpha = SampleAlbedoAlpha(uv, TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap));
    2. .....
    3. outSurfaceData.normalTS = SampleNormal(uv, TEXTURE2D_ARGS(_BumpMap, sampler_BumpMap), _BumpScale);
    After
    Code (CSharp):
    1. half4 albedoAlpha = GetAlbebo(positionWS, normalWS, _BaseMap, sampler_BaseMap);
    2. ....
    3. outSurfaceData.normalTS = GetNormal(positionWS, normalWS, tangentWS, _BumpMap, sampler_BumpMap, _BumpScale);
    triplanar methods (mostly from official source)
    Code (CSharp):
    1. half4 GetAlbebo(float3 positionWS, float3 normalWS, Texture2D splatTX, sampler ss){
    2.     float3 Node_UV = positionWS;
    3.     float3 Node_Blend = pow(abs(normalWS), 1);
    4.     Node_Blend /= dot(Node_Blend, 1.0);
    5.     half4 Node_X = SAMPLE_TEXTURE2D(splatTX, ss, Node_UV.zy);
    6.     half4 Node_Y = SAMPLE_TEXTURE2D(splatTX, ss, Node_UV.xz);
    7.     half4 Node_Z = SAMPLE_TEXTURE2D(splatTX, ss, Node_UV.xy);
    8.     half4 Out = Node_X * Node_Blend.x + Node_Y * Node_Blend.y + Node_Z * Node_Blend.z;
    9.     return Out;
    10.     }
    11.  
    12. half3 GetNormal(float3 positionWS, float3 normalWS, half4 tangentWS, Texture2D normalTX, sampler ss, float scale){
    13.     float sgn = tangentWS.w;
    14.     float3 bitangent = sgn * cross(normalWS.xyz, tangentWS.xyz);
    15.     float3 Node_UV = positionWS;
    16.     float3 Node_Blend = max(pow(abs(normalWS), 1), 0);
    17.     Node_Blend /= (Node_Blend.x + Node_Blend.y + Node_Blend.z ).xxx;
    18.     float3 Node_X = UnpackNormalScale(SAMPLE_TEXTURE2D(normalTX, ss, Node_UV.zy), scale);
    19.     float3 Node_Y = UnpackNormalScale(SAMPLE_TEXTURE2D(normalTX, ss, Node_UV.xz), scale);
    20.     float3 Node_Z = UnpackNormalScale(SAMPLE_TEXTURE2D(normalTX, ss, Node_UV.xy), scale);
    21.     Node_X = float3(Node_X.xy + normalWS.zy, abs(Node_X.z) * normalWS.x);
    22.     Node_Y = float3(Node_Y.xy + normalWS.xz, abs(Node_Y.z) * normalWS.y);
    23.     Node_Z = float3(Node_Z.xy + normalWS.xy, abs(Node_Z.z) * normalWS.z);
    24.     float4 Out = float4(normalize(Node_X.zyx * Node_Blend.x + Node_Y.xzy * Node_Blend.y + Node_Z.xyz * Node_Blend.z), 1);
    25.     float3x3 Node_Transform = float3x3(tangentWS.xyz, bitangent, normalWS.xyz);
    26.     Out.rgb = TransformWorldToTangent(Out.rgb, Node_Transform);
    27.     return half3(Out.r, Out.g, Out.b);
    28.     }
    this time the result is much better and close to the desired one, but there are some artifacts, in the video I show what exactly is wrong.

    in the video on the left is my converted shader, on the right is the standard one.



    please tell me what could be wrong.

    upd: It seems that the front upper part has been rearranged, but everything is written as in the official description, I don’t understand what’s wrong. I also don’t understand what kind of texture is that is barely noticeable if the tiling is adjusted...
     
    Last edited: Sep 20, 2023
  26. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    I don’t know how realistic it is to do this with a URP shader for a terrain, I tried it for a regular one and it worked better, but it doesn’t work for a terrain...

    It was written above that the whole problem is to translate into tangent space, here it is translated...
    Code (CSharp):
    1. half3 GetNormal(Varyings IN, Texture2D normalTX, sampler ss, float scale){
    2.     float sgn = IN.tangent.w;
    3.     //float3 bitangent = sgn * cross(IN.normal.xyz, IN.tangent.xyz);
    4.     float3 Node_UV = IN.positionWS;
    5.     float3 Node_Blend = max(pow(abs(IN.normal), 1), 0);
    6.     Node_Blend /= (Node_Blend.x + Node_Blend.y + Node_Blend.z).xxx;
    7.     float3 Node_X = UnpackNormalScale(SAMPLE_TEXTURE2D(normalTX, ss, Node_UV.zy), scale);
    8.     float3 Node_Y = UnpackNormalScale(SAMPLE_TEXTURE2D(normalTX, ss, Node_UV.xz), scale);
    9.     float3 Node_Z = UnpackNormalScale(SAMPLE_TEXTURE2D(normalTX, ss, Node_UV.xy), scale);
    10.     Node_X = float3(Node_X.xy + IN.normal.zy, abs(Node_X.z) * IN.normal.x);
    11.     Node_Y = float3(Node_Y.xy + IN.normal.xz, abs(Node_Y.z) * IN.normal.y);
    12.     Node_Z = float3(Node_Z.xy + IN.normal.xy, abs(Node_Z.z) * IN.normal.z);
    13.     float4 Out = float4(normalize(Node_X.zyx * Node_Blend.x + Node_Y.xzy * Node_Blend.y + Node_Z.xyz * Node_Blend.z), 1);
    14.  
    15.     float3x3 Node_Transform = float3x3(IN.tangent.xyz, IN.bitangent.xyz, IN.normal.xyz);
    16.     return TransformWorldToTangent(Out.xyz, Node_Transform); // <-------------
    17.     }
    but it doesn't work, the normals are not displayed correctly...

    I don't know what else to do...
     
  27. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,221
    Again, the correct solution is to not do what you're trying to do, and instead modify the shader elsewhere to remove the code that applies the tangent to world transform all together.
     
  28. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    I think I found the reason, here
    Code (CSharp):
    1. inputData.tangentToWorld = half3x3(-IN.tangent.xyz, IN.bitangent.xyz, IN.normal.xyz);
    -IN.tangent.xyz i replace to IN.tangent.xyz
    Now the normals are all applied correctly, I don’t know why those who wrote this shader had such an idea, but after I removed the minus sign everything fell into place...

    Now there is still a small problem with smooth and metalic, but if omit them, it already looks correct...
     
  29. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,221
    My favorite bit of graphics programmer advice is "always have an even number of sign errors".
     
  30. Zimaell

    Zimaell

    Joined:
    May 7, 2020
    Posts:
    337
    By the way, these are the results now, I think we can stop there...
     

    Attached Files: