using UnityEngine;
namespace SimplestarGame.Wave
{
///
/// Wave Simulator
///
///
/// original idea is from this qiita topic https://qiita.com/aa_debdeb/items/1d69d49333630b06f6ce
///
public class WaveSimulator : MonoBehaviour
{
///
/// Quad Mesh Resolution
///
[SerializeField] [Range(64, 512)] internal int resolution = 256;
///
/// Compute Shader for Wave Simulation
///
[SerializeField] ComputeShader computeShader;
///
/// Viscosity Coefficient
///
[SerializeField] [Range(0.0f, 3.0f)] float viscosityCoefficient = 0.25f;
///
/// Wave Interactive Function
///
/// AddWavePoint
/// PointVelocity
internal void AddWave(Vector3 point, Vector3 velocity)
{
var localPoint = this.transform.worldToLocalMatrix.MultiplyPoint(point) * 2.0f; // x:-1.0 ~ 1.0, y:-1.0 ~ 1.0
var waveHeight = this.transform.worldToLocalMatrix.MultiplyVector(velocity);
this.computeShader.SetFloats("addPoint", localPoint.x, localPoint.y, Mathf.Clamp(waveHeight.z, -4, 4));
this.computeShader.Dispatch(this.kernelAddWave, Mathf.CeilToInt(this.currWaveTexture.width / this.threadSizeAddWave.x), Mathf.CeilToInt(this.currWaveTexture.height / this.threadSizeAddWave.y), 1);
}
void Start()
{
// Get ComputeShader Kernel Index;
this.kernelInitialize = this.computeShader.FindKernel("Initialize");
this.kernelAddWave = this.computeShader.FindKernel("AddWave");
this.kernelUpdate = this.computeShader.FindKernel("Update");
this.kernelReplace = this.computeShader.FindKernel("Replace");
// Create Float Texture x 3
this.lastWaveTexture = new RenderTexture(this.resolution, this.resolution, 0, RenderTextureFormat.RFloat);
this.lastWaveTexture.wrapMode = TextureWrapMode.Clamp;
this.lastWaveTexture.enableRandomWrite = true;
this.lastWaveTexture.Create();
this.currWaveTexture = new RenderTexture(this.resolution, this.resolution, 0, RenderTextureFormat.RFloat);
this.currWaveTexture.wrapMode = TextureWrapMode.Clamp;
this.currWaveTexture.enableRandomWrite = true;
this.currWaveTexture.Create();
this.nextWaveTexture = new RenderTexture(this.resolution, this.resolution, 0, RenderTextureFormat.RFloat);
this.nextWaveTexture.wrapMode = TextureWrapMode.Clamp;
this.nextWaveTexture.enableRandomWrite = true;
this.nextWaveTexture.Create();
// Get Thread Size
uint threadSizeX, threadSizeY, threadSizeZ;
this.computeShader.GetKernelThreadGroupSizes(this.kernelInitialize, out threadSizeX, out threadSizeY, out threadSizeZ);
this.threadSizeInitialize = new Vector3Int((int)threadSizeX, (int)threadSizeY, (int)threadSizeZ);
this.computeShader.GetKernelThreadGroupSizes(this.kernelAddWave, out threadSizeX, out threadSizeY, out threadSizeZ);
this.threadSizeAddWave = new Vector3Int((int)threadSizeX, (int)threadSizeY, (int)threadSizeZ);
this.computeShader.GetKernelThreadGroupSizes(this.kernelUpdate, out threadSizeX, out threadSizeY, out threadSizeZ);
this.threadSizeUpdate = new Vector3Int((int)threadSizeX, (int)threadSizeY, (int)threadSizeZ);
this.computeShader.GetKernelThreadGroupSizes(this.kernelReplace, out threadSizeX, out threadSizeY, out threadSizeZ);
this.threadSizeReplace = new Vector3Int((int)threadSizeX, (int)threadSizeY, (int)threadSizeZ);
// Initialize Input Textures
this.computeShader.SetTexture(this.kernelInitialize, "lastWaveTexture", this.lastWaveTexture);
this.computeShader.SetTexture(this.kernelInitialize, "currWaveTexture", this.currWaveTexture);
this.computeShader.Dispatch(this.kernelInitialize, Mathf.CeilToInt(this.currWaveTexture.width / this.threadSizeInitialize.x), Mathf.CeilToInt(this.currWaveTexture.height / this.threadSizeInitialize.y), 1);
// Set to Update Kernel
this.computeShader.SetTexture(this.kernelUpdate, "lastWaveTexture", this.lastWaveTexture);
this.computeShader.SetTexture(this.kernelUpdate, "currWaveTexture", this.currWaveTexture);
this.computeShader.SetTexture(this.kernelUpdate, "nextWaveTexture", this.nextWaveTexture);
// Set to AddWave Kernel
this.computeShader.SetTexture(this.kernelAddWave, "currWaveTexture", this.currWaveTexture);
// Set to Replace Kernel
this.computeShader.SetTexture(this.kernelReplace, "lastWaveTexture", this.lastWaveTexture);
this.computeShader.SetTexture(this.kernelReplace, "currWaveTexture", this.currWaveTexture);
this.computeShader.SetTexture(this.kernelReplace, "nextWaveTexture", this.nextWaveTexture);
// Set Default Wave Coef.
this.computeShader.SetFloat("deltaSize", deltaSize);
this.computeShader.SetFloat("waveCoef", waveCoef);
// Set Result Texture to Material Param
if (TryGetComponent(out Renderer renderer))
{
var material = renderer.material;
if (null != material)
{
material.SetTexture("_WaveHeightMap", this.nextWaveTexture);
material.SetFloat("_Resolution", this.resolution);
}
}
}
void FixedUpdate()
{
if (this.lastViscosityCoefficient != this.viscosityCoefficient)
{
this.lastViscosityCoefficient = this.viscosityCoefficient;
this.computeShader.SetFloat("deltaTime", waveCoefficient * (7f / (this.lastViscosityCoefficient + 1f)));
}
// Compute Next Wave Heights
this.computeShader.Dispatch(this.kernelUpdate, Mathf.CeilToInt(this.currWaveTexture.width / this.threadSizeUpdate.x), Mathf.CeilToInt(this.currWaveTexture.height / this.threadSizeUpdate.y), 1);
this.computeShader.Dispatch(this.kernelReplace, Mathf.CeilToInt(this.currWaveTexture.width / this.threadSizeReplace.x), Mathf.CeilToInt(this.currWaveTexture.height / this.threadSizeReplace.y), 1);
}
float lastViscosityCoefficient = -1;
const float waveCoefficient = 0.01f;
const float deltaSize = 0.1f;
const float waveCoef = 1.0f;
///
/// Height Textures
///
RenderTexture lastWaveTexture;
RenderTexture currWaveTexture;
///
/// Wave Height Map (float 32 R).
///
public RenderTexture nextWaveTexture;
///
/// ComputeShader Kernels
///
int kernelInitialize;
int kernelAddWave;
int kernelUpdate;
int kernelReplace;
///
/// ThreadSize
///
Vector3Int threadSizeInitialize;
Vector3Int threadSizeAddWave;
Vector3Int threadSizeUpdate;
Vector3Int threadSizeReplace;
}
}