Files
2025-06-04 09:09:39 +08:00

344 lines
12 KiB
C#

//////////////////////////////////////////////////////
// MicroSplat
// Copyright (c) Jason Booth
//////////////////////////////////////////////////////
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if USING_URP
using UnityEngine.Rendering.Universal;
#endif
namespace JBooth.MicroSplat
{
public class ShaderID
{
public static int _GMSTraxBuffer = Shader.PropertyToID ("_GMSTraxBuffer");
public static int _Offset = Shader.PropertyToID ("_Offset");
public static int _DepthRT = Shader.PropertyToID ("_DepthRT");
public static int _RepairDelay = Shader.PropertyToID ("_RepairDelay");
public static int _RepairRate = Shader.PropertyToID ("_RepairRate");
public static int _UseTime = Shader.PropertyToID ("_UseTime");
public static int _RepairTotal = Shader.PropertyToID ("_RepairTotal");
public static int _BufferBlend = Shader.PropertyToID ("_BufferBlend");
public static int _SinkStrength = Shader.PropertyToID ("_SinkStrength");
public static int _GMSTraxBufferPosition = Shader.PropertyToID ("_GMSTraxBufferPosition");
public static int _GMSTraxBufferWorldSize = Shader.PropertyToID ("_GMSTraxBufferWorldSize");
public static int _GMSTraxFudgeFactor = Shader.PropertyToID ("_GMSTraxFudgeFactor");
public static int _CamCaptureHeight = Shader.PropertyToID ("_CamCaptureHeight");
public static int _CamFarClipPlane = Shader.PropertyToID ("_CamFarClipPlane");
}
[ExecuteInEditMode]
public class TraxManager : MonoBehaviour
{
public enum Precision
{
Half,
Full
}
public Precision precision = Precision.Full;
public int bufferSize = 1024;
public float worldSize = 128;
public LayerMask layerMask;
public bool useTime;
public float repairDelay;
public float repairRate;
public float repairTotal;
// advanced
public float bufferBlend = 0.5f;
public float collsionDistance = 1.0f;
public float sinkStrength = 0.5f;
public int bufferBlits = 1;
[HideInInspector] public Camera cam;
RenderTexture depthRT;
RenderTexture bufferA;
RenderTexture bufferB;
bool bufferBActive;
Material bufferCopyMat;
Vector3 lastPosition = Vector3.zero;
#if UNITY_EDITOR
void OnDrawGizmosSelected ()
{
UnityEditor.Handles.color = Color.red;
UnityEditor.Handles.DrawWireDisc (transform.position, Vector3.up, worldSize);
}
#endif
public void Setup ()
{
TearDown ();
RenderTextureDescriptor d;
if (precision == Precision.Full)
{
depthRT = new RenderTexture (bufferSize, bufferSize, 32, RenderTextureFormat.Depth, RenderTextureReadWrite.Linear) { name = "rtTraxDepth" };
if (useTime && (repairTotal > 0 || repairDelay > 0))
{
d = new RenderTextureDescriptor (bufferSize, bufferSize, RenderTextureFormat.RGFloat, 0);
}
else
{
d = new RenderTextureDescriptor (bufferSize, bufferSize, RenderTextureFormat.RFloat, 0);
}
}
else
{
depthRT = new RenderTexture (bufferSize, bufferSize, 16, RenderTextureFormat.Depth, RenderTextureReadWrite.Linear) { name = "rtTraxDepth" };
if (useTime)
{
d = new RenderTextureDescriptor (bufferSize, bufferSize, RenderTextureFormat.RGFloat, 0);
}
else
{
d = new RenderTextureDescriptor (bufferSize, bufferSize, RenderTextureFormat.RHalf, 0);
}
}
bufferA = new RenderTexture (d) { name = "rtTraxBufferA" };
bufferB = new RenderTexture (d) { name = "rtTraxBufferB" };
if (cam == null)
{
GameObject go = new GameObject ("Trax Camera");
go.hideFlags = HideFlags.HideAndDontSave;
cam = go.AddComponent<Camera> ();
}
#if USING_HDRP
var camData = cam.gameObject.AddComponent<UnityEngine.Rendering.HighDefinition.HDAdditionalCameraData>();
camData.antialiasing = UnityEngine.Rendering.HighDefinition.HDAdditionalCameraData.AntialiasingMode.None;
camData.clearColorMode = UnityEngine.Rendering.HighDefinition.HDAdditionalCameraData.ClearColorMode.None;
camData.clearDepth = true;
camData.volumeLayerMask = 0;
camData.renderingPathCustomFrameSettings.litShaderMode = UnityEngine.Rendering.HighDefinition.LitShaderMode.Forward;
#endif
#if UNITY_URP && UNITY_2021_3_OR_NEWER
var camData = cam.gameObject.AddComponent<UniversalAdditionalCameraData>();
camData.SetRenderer(1);
#endif
cam.orthographic = true;
cam.orthographicSize = worldSize;
cam.transform.forward = Vector3.up;
cam.orthographicSize = worldSize;
cam.nearClipPlane = 0;
cam.farClipPlane = 2000;
cam.cullingMask = (int)layerMask;
cam.clearFlags = CameraClearFlags.Depth;
bufferCopyMat = new Material (Shader.Find ("Hidden/MicroSplat/TraxBuffer"));
// I tried initializing this with a Graphics.Blit, but it causes an issue where the
// texture starts uncleared
cam.transform.position = new Vector3 (0, -99999, 0); // move it where we won't render something
//cam.targetTexture = depthRT;
//cam.Render ();
//cam.targetTexture = bufferA;
//cam.Render ();
//cam.targetTexture = bufferB;
//cam.Render ();
RenderTexture.active = bufferA;
GL.Clear(true, true, new Color(99999, 0, 0));
RenderTexture.active = bufferB;
GL.Clear(true, true, new Color(99999, 0, 0));
RenderTexture.active = depthRT;
GL.Clear(true, true, new Color(99999, 0, 0));
// Material copyDepthMat = new Material(Shader.Find("Hidden/MicroSplat/CopyDepth"));
// Graphics.Blit(depthRT, bufferA, copyDepthMat);
// Graphics.Blit(depthRT, bufferB, copyDepthMat);
}
public void TearDown ()
{
RenderTexture.active = null;
if (cam != null)
{
cam.targetTexture = null;
DestroyImmediate (cam.gameObject);
}
DisposeRenderTexture (ref depthRT);
DisposeRenderTexture (ref bufferA);
DisposeRenderTexture (ref bufferB);
if (bufferCopyMat != null) DestroyImmediate (bufferCopyMat);
bufferCopyMat = null;
cam = null;
}
void DisposeRenderTexture (ref RenderTexture rt)
{
if (rt == null) return;
rt.Release ();
DestroyImmediate (rt);
rt = null;
}
Texture2D bufferFetch = null;
/// <summary>
/// Given a world position, will return how high the trax buffer is.
/// Note that the trax buffer only stores the world position of the depression, and starts infinitely
/// far away from the terrian. The trax buffer does not know where the terrain is, or how high off
/// the terrain tessellation can go. Rather, it only stores what the minimum value of any object
/// rendered into the trax buffer was, and if repair is used that value is essentially pushed up
/// over time. So a bird flying overhead will lower the buffer height, even though it will have no
/// effect on the terrain. The buffer is cleared to 99999 and any value outside the buffer will return
/// the same.
/// </summary>
/// <param name="terrainPosition"></param>
/// <returns></returns>
public float GetBufferAtPosition (Vector3 terrainPosition)
{
if (bufferA == null)
return 99999;
if (bufferFetch == null)
{
bufferFetch = new Texture2D (1, 1, TextureFormat.RGBAFloat, false, true);
}
RenderTexture t = bufferBActive ? bufferB : bufferA;
Vector2 uv = new Vector2 (terrainPosition.x, terrainPosition.z);
uv -= new Vector2 (transform.position.x, transform.position.z);
uv += new Vector2 (worldSize * 0.5f, worldSize * 0.5f);
uv /= worldSize;
uv *= t.width;
int x = (int)uv.x;
int y = (int)uv.y;
if (x > t.width || y > t.height || x < 0 || y < 0)
return 99999;
var old = RenderTexture.active;
RenderTexture.active = t;
bufferFetch.ReadPixels (new Rect (x, y, 1, 1), 0, 0);
bufferFetch.Apply ();
Color c = bufferFetch.GetPixel (0, 0);
RenderTexture.active = old;
return c.r;
}
/*
private void Update ()
{
Debug.Log (GetBufferAtPosition (this.transform.position + new Vector3 (UnityEngine.Random.value * 600 - 256, 0, Random.value * 600 - 256)));
}
*/
private void OnEnable ()
{
Setup ();
}
private void OnDisable ()
{
TearDown ();
}
private float SnapToPixel (float v, int textureSize, float orthoSize)
{
float worldPixel = (orthoSize * 2.0f) / textureSize;
v = (int)(v / worldPixel);
v *= worldPixel;
return v;
}
private void LateUpdate ()
{
//if (!Application.isPlaying)
{
// cam.targetTexture = bufferA;
// Shader.SetGlobalTexture (ShaderID._GMSTraxBuffer, bufferA);
// return;
}
Vector3 position = transform.position + Vector3.up;
position.x = SnapToPixel (position.x, bufferSize, worldSize);
position.z = SnapToPixel (position.z, bufferSize, worldSize);
position.y -= 1000;
cam.transform.position = position;
Vector3 offset = lastPosition - position;
offset.x = SnapToPixel (offset.x, bufferSize, worldSize);
offset.z = SnapToPixel (offset.z, bufferSize, worldSize);
offset.x /= worldSize;
offset.z /= worldSize;
offset.x *= 0.5f;
offset.z *= 0.5f;
bufferCopyMat.SetVector (ShaderID._Offset, new Vector2 (offset.x, offset.z));
// render into composite buffer
cam.targetTexture = depthRT;
bufferCopyMat.SetTexture (ShaderID._DepthRT, depthRT);
bufferCopyMat.SetFloat (ShaderID._RepairDelay, repairDelay);
bufferCopyMat.SetFloat (ShaderID._RepairRate, 1.0f / Mathf.Max (0.001f, repairRate));
bufferCopyMat.SetFloat (ShaderID._UseTime, useTime ? 1 : 0);
bufferCopyMat.SetFloat (ShaderID._RepairTotal, repairTotal);
bufferCopyMat.SetFloat (ShaderID._BufferBlend, bufferBlend);
bufferCopyMat.SetFloat (ShaderID._SinkStrength, sinkStrength);
bufferCopyMat.SetFloat (ShaderID._CamCaptureHeight, position.y);
bufferCopyMat.SetFloat (ShaderID._CamFarClipPlane, cam.farClipPlane);
RenderTexture A = bufferA;
RenderTexture B = bufferB;
if (bufferBActive) Swap (ref A, ref B);
Graphics.Blit (A, B, bufferCopyMat);
bufferBActive = !bufferBActive;
bufferCopyMat.SetVector (ShaderID._Offset, new Vector2 (0, 0));
bufferCopyMat.SetFloat (ShaderID._UseTime, 0);
Shader.SetGlobalTexture (ShaderID._GMSTraxBuffer, B);
for (int i = 0; i < bufferBlits; ++i)
{
Graphics.Blit (B, A, bufferCopyMat);
bufferBActive = !bufferBActive;
Shader.SetGlobalTexture (ShaderID._GMSTraxBuffer, A);
Swap (ref A, ref B);
}
Shader.SetGlobalVector (ShaderID._GMSTraxBufferPosition, position);
Shader.SetGlobalFloat (ShaderID._GMSTraxBufferWorldSize, worldSize);
Shader.SetGlobalFloat (ShaderID._GMSTraxFudgeFactor, collsionDistance);
lastPosition = position;
}
void Swap<T> (ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
}
}