Skip to content

Instantly share code, notes, and snippets.

@Seneral
Last active May 23, 2022 03:50
Show Gist options
  • Save Seneral/6362c420a7a80228fc6614ff94763000 to your computer and use it in GitHub Desktop.
Save Seneral/6362c420a7a80228fc6614ff94763000 to your computer and use it in GitHub Desktop.
Fast and simple way to visualize terrain heightmaps in Unity 3D - simple offset surface shader included - to use, assign material properties per script and use TerrainMeshVisualizer.DrawVisualization to draw a temporary visualization
Shader "TerrainViz/Terrain_Simple"
{
Properties
{
_WorldSpaceUVTrans ("WorldSpace UV Transformation", Vector) = (1, 1, 0, 0)
_TerrainSize ("TerrainSize", int) = 1024
// Base Terrain
_TerrainField ("Terrain Field", 2D) = "white" {}
_TerrainNormalStrength ("Terrain Normal Strength", Range (0, 4)) = 1
_TerrainBaseColor ("Terrain Base Color", Color) = (0.5, 0.45, 0.4)
// Detail
_DetailDiffuse ("Detail Diffuse Texture", 2D) = "white" {}
_DetailDiffuseColor ("Detail Color", Color) = (1, 1, 1)
_DetailDiffuseBrightness ("Detail Brightness", Range (0, 5)) = 1
_DetailNormal ("Detail Normal Map", 2D) = "bump" {}
_DetailNormalStrength ("Detail Normal Strength", Range (0, 1)) = 1
_DetailUVTrans ("Detail Tiling/Offset", Vector) = (1, 1, 0, 0)
}
SubShader
{
Tags { "RenderType" = "Opaque" }
CGPROGRAM
#pragma target 3.0
#include "UnityCG.cginc"
#pragma surface surf Lambert addshadow
#pragma vertex vert
#pragma shader_feature VERTEX_NORMALS
uniform float4 _WorldSpaceUVTrans;
uniform int _TerrainSize;
uniform sampler2D_float _TerrainField;
uniform float _TerrainNormalStrength;
uniform float4 _TerrainBaseColor;
uniform float4 _BaseColor, _DetailDiffuseColor;
uniform sampler2D _DetailDiffuse, _DetailNormal;
uniform float _DetailDiffuseBrightness, _DetailNormalStrength;
uniform float4 _DetailUVTrans;
struct Input
{
float2 terrainUV;
float2 detailUV;
float3 nrm;
};
// ---------
float TerrainHeightLOD (float2 uv)
{
return tex2Dlod (_TerrainField, float4(uv, 0, 0)).x;
}
float3 HTNormal_Smooth (float3x3 ht, float scale)
{
float3 nrm;
nrm.x = scale * ((ht[2][0]-ht[2][2]) + 2*(ht[1][0]-ht[1][2]) + (ht[0][0]-ht[0][2]));
nrm.y = 1/_TerrainNormalStrength;
nrm.z = scale * -((ht[0][0]-ht[0][2]) + 2*(ht[1][0]-ht[1][2]) + (ht[2][0]-ht[2][2]));
return normalize (nrm);
}
float3 TerrainNormalLOD (float2 uv)
{
float3 offset = float3 (-1.0/_TerrainSize, 0, 1.0/_TerrainSize);
float3x3 heights = {{ TerrainHeightLOD (uv + offset.xx), TerrainHeightLOD (uv + offset.yx), TerrainHeightLOD (uv + offset.zx) },
{ TerrainHeightLOD (uv + offset.xy), TerrainHeightLOD (uv + offset.yy), TerrainHeightLOD (uv + offset.zy) },
{ TerrainHeightLOD (uv + offset.xz), TerrainHeightLOD (uv + offset.yz), TerrainHeightLOD (uv + offset.zz) } };
return HTNormal_Smooth (heights, 1);
}
float3 combineNormal (float3 base, float3 detail)
{
base += float3 (0, 0, 1);
detail *= float3 (-1, -1, 1);
return base * dot (base, detail) / base.z - detail;
}
float3 scaleNormal (float3 nrm, float strenght)
{
nrm.z = nrm.z / strenght;
return normalize (nrm);
}
// ---------
void vert (inout appdata_full v, out Input o)
{
UNITY_INITIALIZE_OUTPUT (Input, o);
// setup worldspace uvs
o.terrainUV = mul (unity_ObjectToWorld, v.vertex).xz * _WorldSpaceUVTrans.xy + _WorldSpaceUVTrans.zw;
o.detailUV = o.terrainUV * _DetailUVTrans.xy + _DetailUVTrans.zw;
// calculate normals
o.nrm = TerrainNormalLOD (o.terrainUV).xzy;
// Offset terrain
v.vertex.y += TerrainHeightLOD (o.terrainUV);
}
void surf (Input IN, inout SurfaceOutput SUR)
{
// Terrain and Detail normals
float3 terrainNRM = IN.nrm;
float3 detailNRM = UnpackNormal (tex2D (_DetailNormal, IN.detailUV));
// Blend Normals
float3 finalNRM = combineNormal (terrainNRM, scaleNormal (detailNRM, _DetailNormalStrength));
SUR.Albedo = _TerrainBaseColor * _DetailDiffuseColor * tex2D (_DetailDiffuse, IN.detailUV).rgb * _DetailDiffuseBrightness;
SUR.Normal = finalNRM;
}
ENDCG
}
FallBack "Diffuse"
}
using UnityEngine;
[System.Serializable]
public class TerrainMeshVisualizer
{
public Material material;
private const int tileRes = 128;
private Mesh _tileMesh;
/// <summary>
/// Creates a visualizer with the specified material
/// </summary>
public TerrainMeshVisualizer (Material Material)
{
if (Material == null)
throw new System.ArgumentNullException ("Material");
material = Material;
}
/// <summary>
/// Draws a temporary tile mesh representation with the material and the given parameters for this frame only
/// </summary>
public void DrawVisualization (Vector3 origin, Vector2 vizSize, Vector2 vizRes, float height)
{
// Prepare tile setup
vizRes.x = Mathf.Max (vizRes.x, tileRes);
vizRes.y = Mathf.Max (vizRes.y, tileRes);
int tilesX = Mathf.CeilToInt ((float)vizRes.x/tileRes), tilesY = Mathf.CeilToInt ((float)vizRes.y/tileRes);
// Prepare material
material.SetVector ("_WorldSpaceUVTrans", new Vector4 (1f/vizSize.x, 1f/vizSize.y, -origin.x/vizSize.x, -origin.z/vizSize.y));
// Prepare tile transformation
Vector3 tileScale = new Vector3 ((float)vizSize.x/vizRes.x, height, (float)vizSize.y/vizRes.y);
Vector2 tileSize = new Vector2 (vizSize.x/tilesX, vizSize.y/tilesY);
// Get tile mesh
Mesh tileMesh = GetTileMesh (height);
// Draw tile grid
for (int xTile = 0; xTile < tilesX; xTile++)
{
for (int yTile = 0; yTile < tilesY; yTile++)
{
Matrix4x4 drawMatrix = Matrix4x4.TRS (origin + new Vector3 ((xTile + 0.5f) * tileSize.x, 0, (yTile + 0.5f) * tileSize.y), Quaternion.identity, tileScale);
Graphics.DrawMesh (tileMesh, drawMatrix, material, 0);
}
}
}
private Mesh GetTileMesh (float height)
{
if (_tileMesh == null)
_tileMesh = CreatePlane (tileRes, tileRes, new Rect (0, 0, 1, 1), true);
_tileMesh.bounds = new Bounds (new Vector3 (0, height/2, 0), new Vector3 (tileRes, height, tileRes));
return _tileMesh;
}
#region Utility
private static Mesh CreatePlane (int res, float size, Rect uvRect, bool calcUVs)
{
int segments = res-1;
float segWidth = size/segments;
// Initialize vertex data
Vector3[] vertices = new Vector3 [res*res];
Vector2[] uvs = calcUVs? new Vector2 [res*res] : null;
Vector3[] normals = new Vector3 [res*res];
Vector4[] tangents = new Vector4 [res*res];
int[] triangles = new int [segments*segments*6];
Vector4 uniformTangent = new Vector4 (1, 0, 0, -1);
Vector4 uniformNormal = Vector3.up;
// Build vertices and their data
for (int x = 0; x < res; x++)
{
for (int y = 0; y < res; y++)
{
int index = y + x * res;
vertices [index] = new Vector3 (x * segWidth - size/2, 0, y * segWidth - size/2);
if (calcUVs) uvs [index] = new Vector2 (uvRect.x + uvRect.width*(float)x/segments, uvRect.y + uvRect.height*(float)y/segments);
normals [index] = uniformNormal;
tangents [index] = uniformTangent;
}
}
// Build triangles
int num = 0;
for (int x = 0; x < segments; x++)
{
for (int y = 0; y < segments; y++)
{
int index = y + x * res;
triangles [num++] = index;
triangles [num++] = index + 1;
triangles [num++] = index + res;
triangles [num++] = index + res;
triangles [num++] = index + 1;
triangles [num++] = index + res + 1;
}
}
// Build mesh
Mesh mesh = new Mesh();
mesh.vertices = vertices;
if (calcUVs) mesh.uv = uvs;
mesh.normals = normals;
mesh.tangents = tangents;
mesh.triangles = triangles;
return mesh;
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment