Search Unity

World Bending Shader Effect Messes Up With Procedurally Generated Terrain Textures

Discussion in 'General Discussion' started by PJacouF, Aug 3, 2022.

  1. PJacouF

    PJacouF

    Joined:
    Jul 22, 2020
    Posts:
    9
    Hello everyone, I recently finished Sebastian Lague's procedural terrain generation playlist. To be honest, because I'm kind of a beginner of this whole procedural thing, I couln't understand most of the code. I played it ariund a little and decided to add a world bending effect with a surface shader code I found on the internet for a planet-like look. I don't actually know how shaders work but I managed to merge the shader code I found with the custom shader Sebastian created in the series and it worked but there is a problem.

    The code works but because of the bending effect, the height values are much below their orginal values without the effect. As a result, the textures are not working as intended. For example, the far sides of the map is just all water and the textures shift from water to grass as the player moves.

    For context, this is without the world bending effect:

    upload_2022-8-3_21-35-25.png

    And this is with the world bending effect:

    upload_2022-8-3_21-37-29.png

    This is the world bending shader code I found:

    Code (CSharp):
    1. Shader "playmint/curved/curved"
    2. {
    3.     Properties
    4.     {
    5.         _Color("Main Colour", Color) = (1,1,1,1)
    6.         _MainTex("Base (RGB)", 2D) = "white" {}
    7.     }
    8.  
    9.         SubShader
    10.     {
    11.         Tags { "RenderType" = "Opaque" }
    12.         LOD 200
    13.  
    14.         CGPROGRAM
    15.         #pragma surface surf Lambert vertex:vert addshadow
    16.  
    17.         // Global Shader values
    18.         uniform float2 _BendAmount;
    19.         uniform float3 _BendOrigin;
    20.         uniform float _BendFalloff;
    21.  
    22.         sampler2D _MainTex;
    23.         fixed4 _Color;
    24.  
    25.         struct Input
    26.         {
    27.               float2 uv_MainTex;
    28.         };
    29.  
    30.         float4 Curve(float4 v)
    31.         {
    32.             //HACK: Considerably reduce amount of Bend
    33.             _BendAmount *= .0001;
    34.  
    35.             float4 world = mul(unity_ObjectToWorld, v);
    36.  
    37.             float dist = length(world.xz - _BendOrigin.xz);
    38.  
    39.             dist = max(0, dist - _BendFalloff);
    40.  
    41.             // Distance squared
    42.             dist = dist * dist;
    43.  
    44.             world.xy += dist * _BendAmount;
    45.             return mul(unity_WorldToObject, world);
    46.         }
    47.  
    48.       void vert(inout appdata_full v)
    49.       {
    50.             v.vertex = Curve(v.vertex);
    51.       }
    52.  
    53.       void surf(Input IN, inout SurfaceOutput o)
    54.       {
    55.             fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    56.             o.Albedo = c.rgb;
    57.             o.Alpha = c.a;
    58.       }
    59.  
    60.       ENDCG
    61.     }
    62.         Fallback "Mobile/Diffuse"
    63. }
    And this is the shader code from attempted merge:

    Code (CSharp):
    1. Shader "Custom/Terrain"
    2. {
    3.  
    4.     Properties
    5.     {
    6.         testTexture("Texture", 2D) = "white"{}
    7.         testScale("Scale", Float) = 1
    8.  
    9.     }
    10.  
    11.     SubShader
    12.     {
    13.         Tags { "RenderType"="Opaque" }
    14.         LOD 200
    15.      
    16.         CGPROGRAM
    17.         // Physically based Standard lighting model, and enable shadows on all light types
    18.         #pragma surface surf Standard fullforwardshadows vertex:vert addshadow // vert add
    19.  
    20.         // Use shader model 3.0 target, to get nicer looking lighting
    21.         #pragma target 3.0
    22.  
    23.         const static int maxLayerCount = 8;
    24.         const static float epsilon = 1E-4;
    25.  
    26.         int layerCount;
    27.         float3 baseColours[maxLayerCount];
    28.         float baseStartHeights[maxLayerCount];
    29.         float baseBlends[maxLayerCount];
    30.         float baseColourStrength[maxLayerCount];
    31.         float baseTextureScales[maxLayerCount];
    32.  
    33.         float minHeight;
    34.         float maxHeight;
    35.  
    36.         // Global Shader values
    37.         uniform float2 _BendAmount;
    38.         uniform float3 _BendOrigin;
    39.         uniform float _BendFalloff;
    40.  
    41.         sampler2D testTexture;
    42.         float testScale;
    43.  
    44.         UNITY_DECLARE_TEX2DARRAY(baseTextures);
    45.  
    46.         struct Input
    47.         {
    48.             float3 worldPos;
    49.             float3 worldNormal;
    50.  
    51.         };
    52.  
    53.         float inverseLerp(float a, float b, float value)
    54.         {
    55.             return saturate((value-a)/(b-a));
    56.         }
    57.  
    58.         float3 triplanar(float3 worldPos, float scale, float3 blendAxes, int textureIndex)
    59.         {
    60.             float3 scaledWorldPos = worldPos / scale;
    61.             float3 xProjection = UNITY_SAMPLE_TEX2DARRAY(baseTextures, float3(scaledWorldPos.y, scaledWorldPos.z, textureIndex)) * blendAxes.x;
    62.             float3 yProjection = UNITY_SAMPLE_TEX2DARRAY(baseTextures, float3(scaledWorldPos.x, scaledWorldPos.z, textureIndex)) * blendAxes.y;
    63.             float3 zProjection = UNITY_SAMPLE_TEX2DARRAY(baseTextures, float3(scaledWorldPos.x, scaledWorldPos.y, textureIndex)) * blendAxes.z;
    64.             return xProjection + yProjection + zProjection;
    65.         }
    66.  
    67.         float4 Curve(float4 v)
    68.         {
    69.             //HACK: Considerably reduce amount of Bend
    70.             _BendAmount *= .0001;
    71.  
    72.             float4 world = mul(unity_ObjectToWorld, v);
    73.  
    74.             float dist = length(world.xz - _BendOrigin.xz);
    75.  
    76.             dist = max(0, dist - _BendFalloff);
    77.  
    78.             // Distance squared
    79.             dist = dist * dist;
    80.  
    81.             world.xy += dist * _BendAmount;
    82.             return mul(unity_WorldToObject, world);
    83.         }
    84.  
    85.         void vert(inout appdata_full v)
    86.         {
    87.             v.vertex = Curve(v.vertex);
    88.         }
    89.  
    90.         void surf (Input IN, inout SurfaceOutputStandard o)
    91.         {
    92.             float heightPercent = inverseLerp(minHeight,maxHeight, IN.worldPos.y);
    93.             float3 blendAxes = abs(IN.worldNormal);
    94.             blendAxes /= blendAxes.x + blendAxes.y + blendAxes.z;
    95.  
    96.             for (int i = 0; i < layerCount; i ++)
    97.             {
    98.                 float drawStrength = inverseLerp(-baseBlends[i]/2 - epsilon, baseBlends[i]/2, heightPercent - baseStartHeights[i]);
    99.  
    100.                 float3 baseColour = baseColours[i] * baseColourStrength[i];
    101.                 float3 textureColour = triplanar(IN.worldPos, baseTextureScales[i], blendAxes, i) * (1-baseColourStrength[i]);
    102.  
    103.                 o.Albedo = o.Albedo * (1-drawStrength) + (baseColour+textureColour) * drawStrength;
    104.             }
    105.         }
    106.  
    107.         ENDCG
    108.     }
    109.     FallBack "Diffuse"
    110. }
    As I said, Sebastian's code felt a bit hard for me to understand and I have near zero experience with shaders. I would like to ask if there is a way to fix this problem.
     
  2. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,569
    Yes, there is. You need to understand what shader is doing and alter its behavior.

    Sebastian's shader uses computed world position. And your curve shader ALTERS that position (expect it to break the lighting too, because it does appear to alter normals).

    So you need to compute world position before it is "bent" and pass THAT position into the shader. Or you need to compute height while taking curvature into effect.

    Also sebastian's shader simply uses tri-planar mapping. You can look it up on google.
     
  3. PJacouF

    PJacouF

    Joined:
    Jul 22, 2020
    Posts:
    9
    Thank you for replying, and I see that's the logical approach, but as I said before, I have near zero knowledge of how shaders work. So how do you suggest I implement that?
     
  4. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,980
    Just take a look at curved world on the asset store instead, as if you have 0 shader knowledge you wont get this done alone, and people wont want to handhold to this degree

    Good luck :)
     
  5. PJacouF

    PJacouF

    Joined:
    Jul 22, 2020
    Posts:
    9
    You are right, but I don't want to pay 45 dollars for something I am experimenting with. I'm not saying "I don't know shaders, write me a code", I'm saying my shader knowledge is minimal so I need some specific help to learn.
     
  6. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,980
    Right but to actually do this properly can become an extremely in depth topic even for a skilled and experienced shader/graphics dev, there isnt exactly a specific resource we can point you in that will get this done for you in a way you can just continue without issues later down the line that you are unable to solve. That said check out catlike coding to get knowledge on how shaders work and what is actually going on in the shader.

    But baring the difficulty of this task in mind, here is the basics of what you need to figure out / solve in case you want to try anyway:

    Doing something like this can affect physics, lighting, everything depending how you approach it. To do it correctly you need to do what curved world shader does, and thats render everything curved whilst not actually changing any of the positions or underlying data. That way physics and lighting etc will just work. This also means this has to be applied as an effect that happens after everything else is calculated, but before you render.

    So you can investigate/google in this order:

    - how to perform this as a post effect / full screen effect instead of per material
    - how to perform the curvature without actually changing the underlying co-ordinates so that all the other systems in unity continue to work.

    Good luck :)
     
    Last edited: Aug 4, 2022
  7. BIGTIMEMASTER

    BIGTIMEMASTER

    Joined:
    Jun 1, 2017
    Posts:
    5,181
    stupid question here, but why not work from a sphere to begin with and procedurally create displacement map to apply to the sphere?
     
  8. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,980
    Doing things like physics on a sphere is much much harder than on a quad shaped world. In general doing most calculations around a sphere will be more difficult and potentially more computationally expensive depending on what the calculation is

    This will make a lot of the programming side of things very difficult, and make things like PhysX (All of the unity physics system basically) no longer be an option without modifications made to allow it to work around a sphere (and usually if these are not trivial, these involve disabling gravity and applying a constant mock-gravity force in the direction of the centre of the sphere which can cause all sorts of other issues).

    It definitely can be done though! Just harder
     
    PJacouF and BIGTIMEMASTER like this.
  9. PJacouF

    PJacouF

    Joined:
    Jul 22, 2020
    Posts:
    9
    Because I don't want the extra work for spherical physics, I just want the effect. Oh and the game will be kind of an infinite runner.
     
  10. PJacouF

    PJacouF

    Joined:
    Jul 22, 2020
    Posts:
    9
    Thank you for your reply, but I am already aware of what you explained. Playing a little demo from what I have now shows there are no issues with physics, so it seems the problem is with shaders. Correct me if I'm wrong but from what I understood so far is that the shader code first bends the world and then applies the textures.

    I have done a ton of research on this but as you said, there isn't enough resources and my knowledge with shaders is a downside, so that's why I posted here.

    The thing is I'm now aware of what's going on, but I'm unable to fix/alter it because of my shader knowledge, so I guess what I need is now to figure out how to apply the textures first and then bend the world.

    Thanks again for your time.
     
  11. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,569
    Acquire the knowledge first.
    https://docs.unity3d.com/Manual/shader-writing.html

    Because using sphere is extra work, and with shader you can change curve angle at runtime without rebuilding the model.
     
  12. PJacouF

    PJacouF

    Joined:
    Jul 22, 2020
    Posts:
    9
    Thanks to helpful people in Official Unity Discord, I fixed it! And the good part is, the answer was not complicated as some people said. They explained me what I should do step by step without even looking at the code.

    I added my own variable to struct Input and used
     mul(unity_ObjectToWorld, v.vertex).xyz; 
    before I modify the vertex. And in the surf function, I used my own custom variable instead.

    I suggest anyone who is trying to assign different textures according to the heights of the different regions of a mesh/terrain and trying to use vertex bending shaders at the same time to have a look at this.

    Here is the modified code (marked the new additions as "//add"):

    Code (CSharp):
    1. Shader "Custom/CustomCurvedTerrain"
    2. {
    3.     Properties
    4.     {
    5.         testTexture("Texture", 2D) = "white"{}
    6.         testScale("Scale", Float) = 1
    7.  
    8.     }
    9.  
    10.         SubShader
    11.         {
    12.             Tags { "RenderType" = "Opaque" }
    13.             LOD 200
    14.  
    15.             CGPROGRAM
    16.             // Physically based Standard lighting model, and enable shadows on all light types
    17.             #pragma surface surf Standard fullforwardshadows vertex:vert addshadow // vert add
    18.  
    19.             // Use shader model 3.0 target, to get nicer looking lighting
    20.             #pragma target 3.0
    21.  
    22.             const static int maxLayerCount = 8;
    23.             const static float epsilon = 1E-4;
    24.  
    25.             int layerCount;
    26.             float3 baseColours[maxLayerCount];
    27.             float baseStartHeights[maxLayerCount];
    28.             float baseBlends[maxLayerCount];
    29.             float baseColourStrength[maxLayerCount];
    30.             float baseTextureScales[maxLayerCount];
    31.  
    32.             float minHeight;
    33.             float maxHeight;
    34.  
    35.             // Global Shader values
    36.             uniform float2 _BendAmount;
    37.             uniform float3 _BendOrigin;
    38.             uniform float _BendFalloff;
    39.  
    40.             sampler2D testTexture;
    41.             float testScale;
    42.  
    43.             UNITY_DECLARE_TEX2DARRAY(baseTextures);
    44.  
    45.             struct Input
    46.             {
    47.                 float3 worldPos;
    48.                 float3 worldNormal;
    49.  
    50.                 float3 customWorldPos; //add
    51.             };
    52.  
    53.             float4 Curve(float4 v)
    54.             {
    55.                 //HACK: Considerably reduce amount of Bend
    56.                 _BendAmount *= .0001;
    57.  
    58.                 float4 world = mul(unity_ObjectToWorld, v);
    59.  
    60.                 float dist = length(world.xz - _BendOrigin.xz);
    61.  
    62.                 dist = max(0, dist - _BendFalloff);
    63.  
    64.                 // Distance squared
    65.                 dist = dist * dist;
    66.  
    67.                 world.xy += dist * _BendAmount;
    68.                 return mul(unity_WorldToObject, world);
    69.             }
    70.  
    71.             void vert(inout appdata_full v, out Input o)
    72.             {
    73.                 //add
    74.                 UNITY_INITIALIZE_OUTPUT(Input, o);
    75.                 o.customWorldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    76.                 //add
    77.  
    78.                 v.vertex = Curve(v.vertex);
    79.             }
    80.  
    81.             float inverseLerp(float a, float b, float value)
    82.             {
    83.                 return saturate((value - a) / (b - a));
    84.             }
    85.  
    86.             float3 triplanar(float3 worldPos, float scale, float3 blendAxes, int textureIndex)
    87.             {
    88.                 float3 scaledWorldPos = worldPos / scale;
    89.                 float3 xProjection = UNITY_SAMPLE_TEX2DARRAY(baseTextures, float3(scaledWorldPos.y, scaledWorldPos.z, textureIndex)) * blendAxes.x;
    90.                 float3 yProjection = UNITY_SAMPLE_TEX2DARRAY(baseTextures, float3(scaledWorldPos.x, scaledWorldPos.z, textureIndex)) * blendAxes.y;
    91.                 float3 zProjection = UNITY_SAMPLE_TEX2DARRAY(baseTextures, float3(scaledWorldPos.x, scaledWorldPos.y, textureIndex)) * blendAxes.z;
    92.                 return xProjection + yProjection + zProjection;
    93.             }
    94.  
    95.             void surf(Input IN, inout SurfaceOutputStandard o)
    96.             {
    97.                 float heightPercent = inverseLerp(minHeight,maxHeight, IN.customWorldPos.y); //add
    98.                 float3 blendAxes = abs(IN.worldNormal);
    99.                 blendAxes /= blendAxes.x + blendAxes.y + blendAxes.z;
    100.  
    101.                 for (int i = 0; i < layerCount; i++)
    102.                 {
    103.                     float drawStrength = inverseLerp(-baseBlends[i] / 2 - epsilon, baseBlends[i] / 2, heightPercent - baseStartHeights[i]);
    104.  
    105.                     float3 baseColour = baseColours[i] * baseColourStrength[i];
    106.                     float3 textureColour = triplanar(IN.customWorldPos, baseTextureScales[i], blendAxes, i) * (1 - baseColourStrength[i]); //add
    107.  
    108.                     o.Albedo = o.Albedo * (1 - drawStrength) + (baseColour + textureColour) * drawStrength;
    109.                 }
    110.             }
    111.  
    112.             ENDCG
    113.         }
    114.             FallBack "Diffuse"
    115. }
    People in discord are really helpful for beginners and are always online. They will always try to help you and find you a solution rather than saying "go learn it yourself".
     
  13. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,569
    Who was I writing this for, I wonder?
    The thing about discord is that things you learned there do not remain available to the people who will face the same problem in the future. They're hidden and aren't indexed search engine. So in the future same question will be asked again and again and again and again, until the helpful people get tired and stop answering.

    That's why a good idea is to learn how to learn, and not rely on free help....

    But whatever.
     
  14. PJacouF

    PJacouF

    Joined:
    Jul 22, 2020
    Posts:
    9
    I wasn't praising discord in particular nor I was aiming directly at you. That's usually true for almost all forums.

    Your description for discord is right but free help is still help. I also think free help is more encouraging than linking the documentation. People can't always spend most of their times learning, for something they won't look into in the near future.

    I know it seems that way but I don't want to spread negativity, I'm sorry if you feel offended. I can't say you didn't provide a single help, at least you wrote something in your previous comment so thanks for that. But I also know this doesn't change anything, the comment is already posted and I don't feel deleting/editing it. So I hope I expressed myself well for the other people here as well.
     
  15. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,569
    If you are attempting game/software development, learning is what you'll always doing as long as you maintain that interest.

    If you can't be bothered to learn, then you should hire someone else to do it for you.