Leveling Terrain below GameObjects

@wyatttt I was just prototyping and wondering if we’ll see a Leveling Terrain Tool in the not too distant future. Can you shed light on this? If you guys will provide one, I’ll save myself the work before I create my own.

Example usage: I put a Wind Turbine on the terrain and would have the terrain leveled from top and bottom, see red arrows:


Same would apply for e. g. Houses and would be a very general use case, hence the question.

If Unity doesn’t provide one, would you guys (or anyone else, please feel free to join in) have any pointers about how to approach this? I’d simply use the Terrain Tools that Unity provides and if the brush is on a GameObject, I’d get the lowest point and use the Set Height Tool. Or is there a much better approach?

1 Like

@Rowlan Not sure if you’ve seen this, but Terrain Former can do what you’re describing … it’s on the Asset Store: Terrain Former | Terrain | Unity Asset Store

It can both level as well as “blend” the terrain to the surrounding mesh / game object … very handy

Thanks, I don’t mind buying an asset, but my current motivation regarding terrain - which is very much in flux - is to either stick with the Unity Standard or create something on my own and give away for free :slight_smile:

Hey! We will at some point but I don’t know if that will also be included in the Terrain Tools package.

As for pointers, when I was thinking about doing this for Terrain Tools, I was going to take the projection code for the Mesh Stamp tool to get the area that should be flattened for the whatever mesh I had intersecting the Terrain. You could probably just have a MonoBehavior attached to those GameObjects that modifies the heightmap whenever it’s transform changes.

To make it a little more non-destructive, you will want to store state for the mesh stamp or Terrain so that you can “undo” the effects of the stamp when the stamp is moved elsewhere. You could do this by caching a texture for that area of the Terrain ( this might lead to a lot of memory usage though ).

If the transform for the mesh changes, either apply the inverse of the mesh stamp or apply a cached portion of the heightmap that existed before the stamp was placed there then apply the stamp in the new location for the transform. This breaks when the mesh is being stamped at an elevation that causes the Terrain height to be 0 or the Terrain’s max height; that’s when you start to lose your height information.

Actually, caching a subset of the heightmap is probably the better way to go. You might lose height information depending on how you flatten the Terrain under the stamp.

You might also want to apply a blur on the mask

Once you have the mask, then you’d want to modify the Terrain height by lerping the existing height towards the intersection height ( which could just be the bottom of the mesh bounds in Terrain space ) based on the texel value in the mask texture. You can use the Terrain Tools API to do all this. It’s the same calls: BeginPaintHeightmap, EndPaintHeightmap, etc.

1 Like

this is the function call in the Mesh Stamp Tool

// generate a mask for the mesh to be used in the compositing shader
TerrainTools.MeshUtility.RenderTopdownProjection(
    activeMesh, //  the mesh to be rendered
    model, // model transformation matrix to be used
    renderTextureDestination, // render texture to blit to
    TerrainTools.MeshUtility.defaultProjectionMaterial, // you can replace this with your own if you want. just needs to render the height or depth
    TerrainTools.MeshUtility.ShaderPass.Height ); // this could just be zero if using the builtin material. the second pass always returns 1. can't remember why i did that

here is the code for the projection / height shader:

Shader "Hidden/TerrainTools/MeshUtility"
{
    Properties
    {
        _MainTex ( "Texture", any ) = "" {}
    }

    SubShader
    {
        ZTest LEQUAL Cull OFF ZWrite ON

        HLSLINCLUDE

        #include "UnityCG.cginc"

        sampler2D _MainTex;

        struct appdata_t
        {
            float4 vertex : POSITION;
            float2 texcoord : TEXCOORD0;
        };

        float4x4 _Matrix_M;
        float4x4 _Matrix_MV;
        float4x4 _Matrix_MVP;

        ENDHLSL

        Pass // render mesh depth to rendertexture
        {
            HLSLPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float4 worldPos : TEXCOORD1;
                // float4 viewPos : TEXCOORD2;
            };

            v2f vert( appdata_t v )
            {
                v2f o;

                float2 b = float2( 0, 1 );
            
                o.worldPos = mul( _Matrix_M, float4( v.vertex.xyz, 1 ) );        // world space position
                o.vertex = mul( _Matrix_MVP, float4( v.vertex.xyz, 1 ) );   // clip space position

                return o;
            }

            float4 frag( v2f i ) : SV_Target
            {
                return i.worldPos.y;
            }

            ENDHLSL
        }

        Pass // render mask
        {
            HLSLPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            v2f vert( appdata_t v )
            {
                v2f o;

                o.vertex = mul( _Matrix_MVP, float4( v.vertex.xyz, 1 ) );   // clip space position

                return o;
            }

            float4 frag( v2f i ) : SV_Target
            {
                return 1;
            }

            ENDHLSL
        }
    }
}
1 Like

Thank you very much for the quick reply! I’ll check it out.

and this is how i build the model matrix in the Mesh Stamp tool:

            Matrix4x4 toolMatrix = Matrix4x4.TRS( Vector3.zero, toolSettings.rotation, toolSettings.scale );

            Bounds modelBounds = activeMesh.bounds;
            float maxModelScale = Mathf.Max( Mathf.Max( modelBounds.size.x, modelBounds.size.y ), modelBounds.size.z );
            float x = .5f;
            float y = .5f;
            float xy = Mathf.Sqrt( x * x + y * y );
            float z = .5f;
            float xyz = Mathf.Sqrt( xy * xy + z * z );
            maxModelScale *= xyz; // this gets the max length of any bounds axis for the mesh given any rotation and scales 
                                                  // it so it always fits within the texture bounds (until you apply more scaling to it ofc.
                                                  // but this will start as fitting in the rendertexture )

            // build the model matrix to transform the mesh with. we want to scale it to fit in the brush bounds and also center it in the brush bounds
            Matrix4x4 model = toolMatrix * Matrix4x4.Scale( Vector3.one / maxModelScale ) * Matrix4x4.Translate( -modelBounds.center );

            Vector3 translate = Vector3.up * ( toolSettings.stampHeight ) / commonUI.terrainUnderCursor.terrainData.size.y;
            model = Matrix4x4.Translate( translate ) * model;
1 Like

I believe lines 18 and 19 should involve the mesh.transform.y position instead of the toolSettings.stampHeight

Anytime! Let me know if you have further questions

1 Like

One thing to note is that this is not an intersection test and will give you odd results for shapes like bridge meshes and chairs. You’d think that the only part where the blending would occur is where the chair legs are but if you just use a plain topdown projection then it will also include the chair seat in the mask

That’s fine. It’s really only just for buildings in order to not have them float in the air.

1 Like

Cool. If you end up making something for this, let me know!

1 Like

Ooh that’s a cool way of doing it! I’ve played around with terrain flattening. My solution was to use the meshes bounding box and flatten it, the use of the new API’s is way way better. Would it be possible to make this not a terrain tool, but automated?

Aka me selecting an object, and running a “flatten terrain” command in a menu? Is it possible to do mesh stamping without having an active tool?

1 Like

Mesh bounding box would also work. You could make it a MonoBehaviour instead of a Terrain Tool which is probably what you would want anyway. Or have a system running in-editor that you can add these Meshes and transforms to that will modify the Terrain. If there’s an overarching system that is running, that might be better because you could then batch overlapping or closely clustered Mesh stamps together in a single render for better blending results instead of doing one Stamp after the other with will favor the blended state of the next Stamp instead of giving all the local Stamps equal blending weight

I actually gave scanning against the gameobjects of the scene and then using set height consistently a quick prototypic try. When I used it I noticed an advantage: You could paint e. g. alongside a house and the terrain height doesn’t necessarily have to be the gameobject’s bottom. It can be anywhere. Quick example usage with painting alongside a cube and a sphere:

5092814--501767--tp.gif

I guess for fine-tuning this might be interesting.

3 Likes

I like that a lot!

1 Like

Wow! I made my own spline tool that generates roads, because we wanted a tool that was tied to our specific needs instead of using EasyRoads or others, mainly to use with world streaming.

I’ve been dreaming of a tool like that, especially non-destructive. If anyone comes up with a little package that does that, I’d gladly donate!

What will be differences in Landscape tools in 2020x compared to this Terrain tools?

Better. Much better

2 Likes

You might see some things from Terrain Tools in our work for 2020 but the end goal is a whole lot more than what we did with Terrain Tools.

The main thing that we’ve announced is non-destructive layers and the workflows around that

3 Likes