Last active
May 23, 2022 03:50
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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