How to draw a grid over parts of the terrain?

Hello there,

at the moment, I try to get a grid on a terrain.
I read several answers on this and the basic suggestions were:

  • Include the grid in the texture,
  • use a projector,
  • use a directional light.

Now, I’ve tried all of them, giving me the following results:

Grid in texture:

Picture 1

Unfortunately, I could not figure out how this projector works (even after reading the corresponding tutorial).
The directional light:

Picture 2

But now, I try to get such an grid only around the mouse. The size should be varying (from 1 square upwards).
Here an example (from RTC3):

RCT3

Now the final two questions: Which of the above methods is the best for drawing a whole grid? And how can I potentially draw the grid only around the cursor?

Regarding the RCT3 screenshot, you can also see the grid behind the hill. Is this possible with unity, too? (But I don’t want to do very complex stuff as I am just a beginner :wink: ).

Thanks in advance for any answer on this. :slight_smile:

I really liked this question, and was surprised there was no answer, so I decided to have a go myself.

This only answers the second half of the question how can I potentially draw the grid only around the cursor. I had a laugh at I don’t want to do very complex stuff as I am just a beginner then reading your modification of the alphamap script, I wouldn’t have a clue on modifying the splat/texture information!

So, this is for drawing a grid only around the mouse position. What I have done is created a mesh, then from a raycast from the camera to the terrain, reading the heightmap information, then modifying the mesh vertices based on the heights around the ray hit position. I have included some clampsing to make sure there are no array out of range problems.

Note : When I wrote this, the terrain and the object I attached the script to were positioned at (0,0,0)

indicatorSize changes the size of the grid. The grid is just a texture so it doesn’t add more squares (it’s possible with some modification to the uv calculations). indicatorOffsetY is to raise the mesh from the terrain, for some reason converting the height from terrain size to heightmap size and back again messes the actual height value. And further away from the terrain, there is some z-axis fighting. This could probably be fixed by adding more vertices to the mesh.

Unfortunately this is in uJS, but at least shows a proof-of-concept. If this is what you were after, I could try a conversion. If not, I’ll just delete this answer =]

This was a lot of fun, I really like a challenge, and playing around with meshes and terrain data.

Here’s the script :

// ===========================================================================
//   Plane Mesh that Forms to the Terrain Profile script
//   written by Alucard J ( Jay Kay )
//   April 2013
// ===========================================================================

#pragma strict
@script RequireComponent( MeshFilter )
@script RequireComponent( MeshRenderer )

// --------------------------------------------------------------------------- Terrain Data

public var terrain : Terrain;
private var terrainData : TerrainData;
private var terrainSize : Vector3;
private var heightmapWidth : int;
private var heightmapHeight : int;
private var heightmapData : float[,];


function GetTerrainData() 
{
	if ( !terrain )
	{
		terrain = Terrain.activeTerrain;
	}
	
	terrainData = terrain.terrainData;
	
	terrainSize = terrain.terrainData.size;
	
	heightmapWidth = terrain.terrainData.heightmapWidth;
	heightmapHeight = terrain.terrainData.heightmapHeight;
	
	heightmapData = terrainData.GetHeights( 0, 0, heightmapWidth, heightmapHeight );
}


// --------------------------------------------------------------------------- Raycast To Terrain

private var rayHitPoint : Vector3;
private var heightmapPos : Vector3;


function Start() 
{
	GetTerrainData();
	ConstructMesh();
}


function Update() 
{
	// raycast to the terrain
	RaycastToTerrain();
	
	// find the heightmap position of that hit
	GetHeightmapPosition();
	
	// Calculate Grid
	CalculateGrid();
	
	// Update Mesh
	UpdateMesh();
}


function RaycastToTerrain() 
{
	var hit : RaycastHit;
	var rayPos : Ray = Camera.main.ScreenPointToRay( Input.mousePosition );
	
	if ( Physics.Raycast( rayPos, hit, Mathf.Infinity ) ) // also consider a layermask to just the terrain layer
	{
		Debug.DrawLine( Camera.main.transform.position, hit.point, Color.red );
		rayHitPoint = hit.point;
	}
}


function GetHeightmapPosition() 
{
	// find the heightmap position of that hit
	heightmapPos.x = ( rayHitPoint.x / terrainSize.x ) * parseFloat( heightmapWidth );
	heightmapPos.z = ( rayHitPoint.z / terrainSize.z ) * parseFloat( heightmapHeight );
	
	// convert to integer
	heightmapPos.x = Mathf.Round( heightmapPos.x );
	heightmapPos.z = Mathf.Round( heightmapPos.z );
	
	// clamp to heightmap dimensions to avoid errors
	heightmapPos.x = Mathf.Clamp( heightmapPos.x, 0, heightmapWidth - 1 );
	heightmapPos.z = Mathf.Clamp( heightmapPos.z, 0, heightmapHeight - 1 );
}


// --------------------------------------------------------------------------- Calculate Grid

private var mapGrid : Vector3[,] = new Vector3[ 9, 9 ];

public var indicatorSize : float = 1.0;
public var indicatorOffsetY : float = 5.0;


function CalculateGrid() 
{
	for ( var z : int = -4; z < 5; z ++ )
	{
		for ( var x : int = -4; x < 5; x ++ )
		{
			var calcVector : Vector3;
			
			calcVector.x = heightmapPos.x + ( x * indicatorSize );
			calcVector.x /= parseFloat( heightmapWidth ); 
			calcVector.x *= terrainSize.x;
			
			var calcPosX : float = heightmapPos.x + ( x * indicatorSize );
			calcPosX = Mathf.Clamp( calcPosX, 0, heightmapWidth - 1 );
			
			var calcPosZ : float = heightmapPos.z + ( z * indicatorSize );
			calcPosZ = Mathf.Clamp( calcPosZ, 0, heightmapHeight - 1 );
			
			calcVector.y = heightmapData[ calcPosZ, calcPosX ] * terrainSize.y; // heightmapData is Y,X ; not X,Y (reversed)
			calcVector.y += indicatorOffsetY; // raise slightly above terrain
			
			calcVector.z = heightmapPos.z + ( z * indicatorSize );
			calcVector.z /= parseFloat( heightmapHeight ); 
			calcVector.z *= terrainSize.z;
			
			mapGrid[ x + 4, z + 4 ] = calcVector;
		}
	}
}


// --------------------------------------------------------------------------- INDICATOR MESH

private var mesh : Mesh;

private var verts : Vector3[];
private var uvs : Vector2[];
private var tris : int[];


function ConstructMesh()
{
	if ( !mesh )
	{
		mesh = new Mesh();
		GetComponent(MeshFilter).mesh = mesh;
		mesh.name = gameObject.name + "Mesh";
	}
	
	mesh.Clear();	
	
	verts = new Vector3[9 * 9]; 
	uvs = new Vector2[9 * 9];
	tris = new int[ 8 * 2 * 8 * 3];
	
	var uvStep : float = 1.0 / 8.0;
	
	var index : int = 0;
	var triIndex : int = 0;
	
	for ( var z : int = 0; z < 9; z ++ )
	{
		for ( var x : int = 0; x < 9; x ++ )
		{
			verts[ index ] = new Vector3( x, 0, z );
			uvs[ index ] = new Vector2( parseFloat(x) * uvStep, parseFloat(z) * uvStep );
			
			if ( x < 8 && z < 8 )
			{
				tris[ triIndex + 0 ] = index + 0;
				tris[ triIndex + 1 ] = index + 9;
				tris[ triIndex + 2 ] = index + 1;
				
				tris[ triIndex + 3 ] = index + 1;
				tris[ triIndex + 4 ] = index + 9;
				tris[ triIndex + 5 ] = index + 10;
				
				triIndex += 6;
			}
			
			index ++;
		}
	}
	
	
	// - Build Mesh -
	mesh.vertices = verts; 
	mesh.uv = uvs;
	mesh.triangles = tris;
	
	mesh.RecalculateBounds();	
	mesh.RecalculateNormals();
}


function UpdateMesh()
{
	var index : int = 0;
	
	for ( var z : int = 0; z < 9; z ++ )
	{
		for ( var x : int = 0; x < 9; x ++ )
		{
			verts[ index ] = mapGrid[ x, z ];
			
			index ++;
		}
	}
	
	// assign to mesh
	mesh.vertices = verts;
	
	mesh.RecalculateBounds();
	mesh.RecalculateNormals();
}


// ---------------------------------------------------------------------------

How about writing a simple shader that gets the cursor position as a property, then draws a grid around it based on some rules - such as, if the world coordinate at that pixel is a whole number or not, or divisible by some number defining the scale, and so on, depending on how large and in what shape you want your grid. You could either take into account or ignore the shape of the terrain, as you please (vertex shaders).

This way you could add all kinds of pretty effects too, such as soft fading out of the grid based on distance from the cursor, “glow” effects, variable coloring and so on. And using shaders would probably be among the cheapest ways of doing something like this, at least if you keep it simple :slight_smile:

EDIT:

I need a grid myself, so I went ahead and tested this idea in practice. So here’s a Cg grid shader, based on this excellent Wikibook by Martin Kraus: http://en.wikibooks.org/wiki/Cg_Programming/Unity

Attach the shader to a new Material, then attach that Material to a Plane object (or whatever, really). You’ll get adjustable parameters (grid cell size, line thickness, line color and the color of the “outside” bits) in the Material properties. Note that I made the grid for the X and Y plane, you can change this to X and Z for example for the default Unity plane position, or whatever you need.

It’s pretty lightweight, I’m on a laptop with no discrete graphics device and I don’t notice any impact on rendering time. If you want to make the grid follow the cursor, just add a property for cursor position and update it through a script (and remember to use renderer.sharedMaterial instead of just renderer.material). For more info, take a look at the Shader Properties section on this page, it pretty much directly tells how to do this: http://en.wikibooks.org/wiki/Cg_Programming/Unity/Shading_in_World_Space

Shader "Grid" {
   
   Properties {
      _GridThickness ("Grid Thickness", Float) = 0.02
      _GridSpacing ("Grid Spacing", Float) = 10.0
      _GridColor ("Grid Color", Color) = (0.5, 0.5, 1.0, 1.0)
      _OutsideColor ("Color Outside Grid", Color) = (0.0, 0.0, 0.0, 0.0)
   }

   SubShader {
      Tags {
         "Queue" = "Transparent"
            // draw after all opaque geometry has been drawn
      }

      Pass {
         ZWrite Off
            // don't write to depth buffer in order not to occlude other objects
         Blend SrcAlpha OneMinusSrcAlpha
            // use alpha blending

         CGPROGRAM
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         uniform float _GridThickness;
         uniform float _GridSpacing;
         uniform float4 _GridColor;
         uniform float4 _OutsideColor;

         struct vertexInput {
            float4 vertex : POSITION;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 worldPos : TEXCOORD0;
         };
 
         vertexOutput vert(vertexInput input) {
            vertexOutput output; 
 
            output.pos =  mul(UNITY_MATRIX_MVP, input.vertex);
            output.worldPos = mul(_Object2World, input.vertex);
               // transformation of input.vertex from object coordinates to world coordinates;
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR {
            if (frac(input.worldPos.x/_GridSpacing) < _GridThickness 
            || frac(input.worldPos.y/_GridSpacing) < _GridThickness) {
               return _GridColor;
            } else {
               return _OutsideColor;
            }
         }
 
         ENDCG  
      }
   }
}

https://bitbucket.org/xuanyusong/unity-terraingrid

An alternative solution is to have a series of gameobjects set apart from one another as if to mimic the intersections of your grid and put these into a prefab.

Following this after you cast a ray onto your terrain update the position of this prefab to be above the hitPoint by an arbitrary distance. (With a more fancy system you could get it to snap over your grid if you wanted).

Following this cast a ray directly downwards from each gamOject in the prefab and use variables / an array to store the vector3 information about where they hit (use layermasks as necessary to make sure you only hit the terrain).

Finally draw a lineRenderer with an appropriately thick line between these points.

If you are concerned about Bezier slopes on your terrain, then simply add more gameobjects to your prefab to ‘fake’ any curvature issues.

This mthod will allow you to not only use a square based grid system, but also a hexagonal / triangular one, which can be very handy.

Hope that all makes sense in theory.

http://rene.klacan.sk/unity3d/games/2015/01/23/draw-grid-on-the-terrain-in-unity/ one of the possible solutions without using projectors or shaders

My grid only seems to show one direction flow of lines and has only half the plane rendering the shader. Is this obsolete for the latest Unity builds?

The solution I devised for this was to create a grid overlay of game objects (squares or hexes) above the terrain and then drop each object down on top of the terrain per vertex.

All you need do is iterate over all the vertices in the mesh and then raycast them down. You’ll need to transform the vertices to world space and then back again.

Just be sure to use objects that have enough vertices to follow your terrain. Quads won’t work, but planes with 128 (vertices) will work fine. Of course, creating your own hex or square in blender and then subdividing it might be best.

Ideally this is something you would do once and then serialize the array.

/*
code assumes you have a 2d array of gameobjects in your class called overlayGrid and that it already sits above your terrain (<20 height)
*/
    private void DropOverlaySquare(int xpos, int zpos)
    {
        RaycastHit hit;
        GameObject obj = overlayGrid[xpos, zpos];
        OverlaySquare sq = obj.GetComponent<OverlaySquare>();

        Mesh m = sq.GetComponent<MeshFilter>().mesh;

        Vector3[] vertices = m.vertices;
        Vector3[] normals = m.normals;

        float x = 0;
        float y = 0;
        float z = 0;

        Vector3 localTilePoint;
        for (var i = 0; i < vertices.Length; i++)
        {

            x = sq.transform.TransformPoint(vertices*).x;*

y = sq.transform.TransformPoint(vertices*).y;*
z = sq.transform.TransformPoint(vertices*).z;*

Physics.Raycast(new Vector3(x, y - 5.0f, z), new Vector3(0, -20.0f, 0), out hit);
// Debug.DrawRay(new Vector3(x, y, z), new Vector3(0, -20f, 0), Color.red, 200, false);
// Debug.DrawLine(new Vector3(x, y, z), hit.transform.position, Color.yellow, 200, false);

localTilePoint = sq.transform.InverseTransformPoint(hit.point);
localTilePoint.y += 1f; // bump it up above the terrain.
vertices = localTilePoint;
// Debug.Log(“verts x:” + hit.point.x.ToString() + " y:" + hit.point.y.ToString() + " z:" + hit.point.z.ToString());
// Debug.Log(i.ToString() + " hit.TilePoint x:" + hit.point.x.ToString() + " y:" + hit.point.y.ToString() + " z:" + hit.point.z.ToString());
}
m.vertices = vertices;
m.RecalculateBounds();
m.RecalculateNormals();
}