Rasagar/Assets/ProceduralTerrainPainter/Editor/HeatmapPreview.cs
2024-08-26 23:07:20 +03:00

149 lines
7.4 KiB
C#

using UnityEngine;
#if UNITY_2021_2_OR_NEWER
using UnityEngine.TerrainTools;
#else
using UnityEngine.Experimental.TerrainAPI;
#endif
namespace sc.terrain.proceduralpainter
{
public class HeatmapPreview
{
private static Material utilsMat;
private static Shader utilsShader;
#if UNITY_2019_3_OR_NEWER
private static float kNormalizedHeightScale => PaintContext.kNormalizedHeightScale;
#else
private const float kNormalizedHeightScale = 0.4999771f;
#endif
public static void CreateHeatmaps(Terrain[] terrains, int layerIndex, ref RenderTexture[] heatmaps)
{
heatmaps = new RenderTexture[terrains.Length];
for (int i = 0; i < heatmaps.Length; i++)
{
heatmaps[i] = CreateHeatmap(terrains[i], layerIndex);
}
}
private static RenderTexture CreateHeatmap(Terrain terrain, int layerIndex)
{
RenderTexture rt = new RenderTexture(terrain.terrainData.alphamapResolution, terrain.terrainData.alphamapResolution, 0, RenderTextureFormat.R8);
var splatIndex = Utilities.GetSplatmapIndex(layerIndex);
var channelIndex = Utilities.GetChannelIndex(layerIndex);
Texture2D splatmap = terrain.terrainData.GetAlphamapTexture(splatIndex);
if (utilsShader == null) utilsShader = Shader.Find("Hidden/TerrainEngine/TerrainLayerUtils");
if (utilsMat == null) utilsMat = new Material(utilsShader);
utilsMat.SetTexture("_MainTex", splatmap);
utilsMat.SetVector("_LayerMask", Utilities.GetVectorMask(channelIndex));
//Pass 0 = Select one channel and copy it into R channel
Graphics.Blit(splatmap, rt, utilsMat, 0);
return rt;
}
private static Material heatmapMat;
public static void Draw(Terrain terrain, TerrainLayer layer, Texture alphaMap, bool contour, bool tilingPreview)
{
if (!terrain || !layer) return;
if (!heatmapMat) heatmapMat = new Material(Shader.Find("Hidden/TerrainPainter/Heatmap"));
Texture heightmapTexture = terrain.terrainData.heightmapTexture;
RectInt pixelRect = new RectInt(0, 0, heightmapTexture.width, heightmapTexture.height);
Vector2 pixelSize = new Vector2(terrain.terrainData.size.x / heightmapTexture.width, terrain.terrainData.size.z / heightmapTexture.height);
BrushTransform brushXform = TerrainPaintUtility.CalculateBrushTransform(terrain, new Vector2(0.5f, 0.5f), terrain.terrainData.size.x, 0.0f);
// we want to build a quad mesh, with one vertex for each pixel in the heightmap
// i.e. a 3x3 heightmap would create a mesh that looks like this:
//
// +-+-+
// |\|\|
// +-+-+
// |\|\|
// +-+-+
//
int quadsX = pixelRect.width+1;
int quadsY = pixelRect.height+1;
int vertexCount = quadsX * quadsY * (2 * 3); // two triangles (2 * 3 vertices) per quad
// issue: the 'int vertexID' in the shader is often stored in an fp32
// which can only represent exact integers up to 16777216 ~== 6 * 1672^2
// once we have more than 16777216 vertices, the vertexIDs start skipping odd values, resulting in missing triangles
// the solution is to reduce vertex count by halving our mesh resolution before we hit that point
const int kMaxFP32Int = 16777216;
int vertSkip = 1;
while (vertexCount > kMaxFP32Int / 2) // in practice we want to stay well below 16 million verts, for perf sanity
{
quadsX = (quadsX + 1) / 2;
quadsY = (quadsY + 1) / 2;
vertexCount = quadsX * quadsY * (2 * 3);
vertSkip *= 2;
}
// this is used to tessellate the quad mesh (from within the vertex shader)
heatmapMat.SetVector("_QuadRez", new Vector4(quadsX, quadsY, vertexCount, vertSkip));
// paint context pixels to heightmap uv: uv = (pixels + 0.5) / width
float invWidth = 1.0f / heightmapTexture.width;
float invHeight = 1.0f / heightmapTexture.height;
heatmapMat.SetVector("_HeightmapUV_PCPixelsX", new Vector4(invWidth, 0.0f, 0.0f, 0.0f));
heatmapMat.SetVector("_HeightmapUV_PCPixelsY", new Vector4(0.0f, invHeight, 0.0f, 0.0f));
heatmapMat.SetVector("_HeightmapUV_Offset", new Vector4(0.5f * invWidth, 0.5f * invHeight, 0.0f, 0.0f));
heatmapMat.SetTexture("_Heightmap", heightmapTexture);
heatmapMat.SetTexture("_NormalMap", terrain.normalmapTexture);
// paint context pixels to object (terrain) position
// objectPos.x = scaleX * pcPixels.x + heightmapRect.xMin * scaleX
// objectPos.y = scaleY * H
// objectPos.z = scaleZ * pcPixels.y + heightmapRect.yMin * scaleZ
float scaleX = pixelSize.x;
float scaleY = (terrain.terrainData.heightmapScale.y) / kNormalizedHeightScale;
float scaleZ = pixelSize.y;
heatmapMat.SetVector("_ObjectPos_PCPixelsX", new Vector4(scaleX, 0.0f, 0.0f, 0.0f));
heatmapMat.SetVector("_ObjectPos_HeightMapSample", new Vector4(0.0f, scaleY, 0.0f, 0.0f));
heatmapMat.SetVector("_ObjectPos_PCPixelsY", new Vector4(0.0f, 0.0f, scaleZ, 0.0f));
//Note slightly offset, so raise up so it doesn't clip through terrain
heatmapMat.SetVector("_ObjectPos_Offset", new Vector4((pixelRect.xMin * scaleX), 3f , (pixelRect.yMin * scaleZ) + (pixelSize.y * 0.0f) , 1.0f));
// paint context origin in terrain space
// (note this is the UV space origin and size, not the mesh origin & size)
float pcOriginX = pixelRect.xMin * pixelSize.x;
float pcOriginZ = pixelRect.yMin * pixelSize.y;
float pcSizeX = pixelSize.x;
float pcSizeZ = pixelSize.y;
Vector2 scaleU = pcSizeX * brushXform.targetX;
Vector2 scaleV = pcSizeZ * brushXform.targetY;
Vector2 offset = brushXform.targetOrigin + pcOriginX * brushXform.targetX + pcOriginZ * brushXform.targetY;
heatmapMat.SetVector("_BrushUV_PCPixelsX", new Vector4(scaleU.x, scaleU.y, 0.0f, 0.0f));
heatmapMat.SetVector("_BrushUV_PCPixelsY", new Vector4(scaleV.x, scaleV.y, 0.0f, 0.0f));
heatmapMat.SetVector("_BrushUV_Offset", new Vector4(offset.x, offset.y, 0.0f, 1.0f));
heatmapMat.SetTexture("_LayerMaskTex", alphaMap);
heatmapMat.SetFloat("_ContourOn", contour ? 1 : 0);
heatmapMat.SetFloat("_TilingOn", tilingPreview ? 1 : 0);
//Tiling in world-space
heatmapMat.SetVector("_LayerTiling", new Vector4(layer.diffuseTexture.width / terrain.terrainData.size.x / layer.tileSize.x, layer.diffuseTexture.height / terrain.terrainData.size.z / layer.tileSize.y, 0, 0));
heatmapMat.SetVector("_TerrainObjectToWorldOffset", terrain.GetPosition());
heatmapMat.SetPass(0);
#if UNITY_2019_1_OR_NEWER
Graphics.DrawProceduralNow(MeshTopology.Triangles, vertexCount);
#else
Graphics.DrawProcedural(MeshTopology.Triangles, vertexCount);
#endif
}
}
}