@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?
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
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.
// 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
}
}
}
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;
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
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?
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:
I guess for fine-tuning this might be interesting.
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!