using UnityEngine; using System; using System.Collections.Generic; using UnityEngine.TerrainTools; namespace UnityEditor.TerrainTools.Erosion { [Serializable] internal class HydraulicEroder : ITerrainEroder { [SerializeField] public HydraulicErosionSettings m_ErosionSettings = new HydraulicErosionSettings(); //we need to ping-pong these [NonSerialized] private RTHandle[] m_HeightmapRT = { null, null }; [NonSerialized] private RTHandle[] m_WaterRT = { null, null }; [NonSerialized] private RTHandle[] m_WaterVelRT = { null, null }; [NonSerialized] private RTHandle[] m_FluxRT = { null, null }; [NonSerialized] private RTHandle[] m_SedimentRT = { null, null }; [NonSerialized] private RTHandle m_ErodedRT = null; private RTHandle m_HardnessRT = null; [NonSerialized] Vector2Int m_RTSize = new Vector2Int(0, 0); [NonSerialized] private ComputeShader m_HydraulicCS = null; [NonSerialized] private ComputeShader m_ThermalCS = null; private ComputeShader GetHydraulicCS() { if (m_HydraulicCS == null) { m_HydraulicCS = ComputeUtility.GetShader("Hydraulic"); } return m_HydraulicCS; } private ComputeShader GetThermalCS() { if (m_ThermalCS == null) { m_ThermalCS = ComputeUtility.GetShader("Thermal"); } return m_ThermalCS; } private void CreateRTHandles(Vector2Int dim) { m_RTSize = dim; var r = RTUtils.GetDescriptorRW(dim.x, dim.y, 0, RenderTextureFormat.RFloat); var rg = RTUtils.GetDescriptorRW(dim.x, dim.y, 0, RenderTextureFormat.RGFloat); var argb = RTUtils.GetDescriptorRW(dim.x, dim.y, 0, RenderTextureFormat.ARGBFloat); for (int i = 0; i < 2; i++) { m_HeightmapRT[i] = RTUtils.GetNewHandle(r).WithName("HydroErosion_Height" + i); m_WaterRT[i] = RTUtils.GetNewHandle(r).WithName("HydroErosion_Water" + i); m_WaterVelRT[i] = RTUtils.GetNewHandle(rg).WithName("HydroErosion_WaterVel" + i); m_FluxRT[i] = RTUtils.GetNewHandle(argb).WithName("HydroErosion_Flux" + i); m_SedimentRT[i] = RTUtils.GetNewHandle(r).WithName("HydroErosion_Sediment" + i); m_HeightmapRT[i].RT.Create(); m_WaterRT[i].RT.Create(); m_WaterVelRT[i].RT.Create(); m_FluxRT[i].RT.Create(); m_SedimentRT[i].RT.Create(); } m_ErodedRT = RTUtils.GetNewHandle(r).WithName("HydroErosion_Eroded"); m_ErodedRT.RT.Create(); m_HardnessRT = RTUtils.GetNewHandle(r).WithName("HydroErosion_Hardness"); m_HardnessRT.RT.Create(); } private void ReleaseRTHandles() { for (int i = 0; i < 2; i++) { RTUtils.Release(m_HeightmapRT[i]); RTUtils.Release(m_WaterRT[i]); RTUtils.Release(m_WaterVelRT[i]); RTUtils.Release(m_FluxRT[i]); RTUtils.Release(m_SedimentRT[i]); } RTUtils.Release(m_ErodedRT); RTUtils.Release(m_HardnessRT); } private void ClearRTHandles() { RenderTexture tmp = RenderTexture.active; for (int i = 0; i < 2; i++) { Graphics.Blit(Texture2D.blackTexture, m_WaterRT[i]); Graphics.Blit(Texture2D.blackTexture, m_WaterVelRT[i]); Graphics.Blit(Texture2D.blackTexture, m_FluxRT[i]); Graphics.Blit(Texture2D.blackTexture, m_SedimentRT[i]); } Graphics.Blit(Texture2D.blackTexture, m_ErodedRT); Graphics.Blit(Texture2D.blackTexture, m_HardnessRT); RenderTexture.active = tmp; } public Dictionary inputTextures { get; set; } = new Dictionary(); [SerializeField] private bool m_ShowControls = true; [SerializeField] private bool m_ShowAdvancedUI = false; [SerializeField] private bool m_ShowThermalUI = false; [SerializeField] private bool m_ShowWaterUI = false; [SerializeField] private bool m_ShowSedimentUI = false; [SerializeField] private bool m_ShowRiverBankUI = false; public void OnEnable() { } public void OnInspectorGUI(Terrain terrain, IOnInspectorGUI editContext) { m_ShowControls = TerrainToolGUIHelper.DrawHeaderFoldoutForErosion(Erosion.Styles.m_HydroErosionControls, m_ShowControls, ResetToolVar); if (m_ShowControls) { EditorGUILayout.BeginVertical("GroupBox"); m_ErosionSettings.m_SimScale.DrawInspectorGUI(); EditorGUI.indentLevel++; m_ShowAdvancedUI = TerrainToolGUIHelper.DrawSimpleFoldout(new GUIContent("Advanced"), m_ShowAdvancedUI); if (m_ShowAdvancedUI) { //m_ErosionSettings.m_IterationBlendScalar.DrawInspectorGUI(); m_ErosionSettings.m_HydroTimeDelta.DrawInspectorGUI(); m_ErosionSettings.m_HydroIterations.DrawInspectorGUI(); //m_ErosionSettings.m_GravitationalConstant = EditorGUILayout.Slider(Erosion.Styles.m_GravitationConstant, m_ErosionSettings.m_GravitationalConstant, 0.0f, -100.0f); EditorGUI.indentLevel++; m_ShowThermalUI = TerrainToolGUIHelper.DrawSimpleFoldout(new GUIContent("Thermal Smoothing"), m_ShowThermalUI, 1); if (m_ShowThermalUI) { //m_ErosionSettings.m_DoThermal = EditorGUILayout.Toggle(Erosion.Styles.m_DoThermal, m_ErosionSettings.m_DoThermal); m_ErosionSettings.m_ThermalTimeDelta = EditorGUILayout.Slider(Erosion.Styles.m_ThermalDTScalar, m_ErosionSettings.m_ThermalTimeDelta, 0.0001f, 10.0f); m_ErosionSettings.m_ThermalIterations = EditorGUILayout.IntSlider(Erosion.Styles.m_NumIterations, m_ErosionSettings.m_ThermalIterations, 0, 100); m_ErosionSettings.m_ThermalReposeAngle = EditorGUILayout.IntSlider(Erosion.Styles.m_AngleOfRepose, m_ErosionSettings.m_ThermalReposeAngle, 0, 90); } m_ShowWaterUI = TerrainToolGUIHelper.DrawSimpleFoldout(new GUIContent("Water Transport"), m_ShowWaterUI, 1); if (m_ShowWaterUI) { //m_ErosionSettings.m_WaterLevelScale = EditorGUILayout.Slider(Erosion.Styles.m_WaterLevelScale, m_ErosionSettings.m_WaterLevelScale, 0.0f, 100.0f); m_ErosionSettings.m_PrecipRate.DrawInspectorGUI(); m_ErosionSettings.m_EvaporationRate.DrawInspectorGUI(); m_ErosionSettings.m_FlowRate.DrawInspectorGUI(); } m_ShowSedimentUI = TerrainToolGUIHelper.DrawSimpleFoldout(new GUIContent("Sediment Transport"), m_ShowSedimentUI, 1); if (m_ShowSedimentUI) { //m_ErosionSettings.m_SedimentScale = EditorGUILayout.Slider(Erosion.Styles.m_SedimentScale, m_ErosionSettings.m_SedimentScale, 0.0f, 10.0f); m_ErosionSettings.m_SedimentCapacity.DrawInspectorGUI(); m_ErosionSettings.m_SedimentDepositRate.DrawInspectorGUI(); m_ErosionSettings.m_SedimentDissolveRate.DrawInspectorGUI(); } m_ShowRiverBankUI = TerrainToolGUIHelper.DrawSimpleFoldout(new GUIContent("Riverbank"), m_ShowRiverBankUI, 1); if (m_ShowRiverBankUI) { m_ErosionSettings.m_RiverBankDepositRate.DrawInspectorGUI(); m_ErosionSettings.m_RiverBankDissolveRate.DrawInspectorGUI(); m_ErosionSettings.m_RiverBedDepositRate.DrawInspectorGUI(); m_ErosionSettings.m_RiverBedDissolveRate.DrawInspectorGUI(); } } EditorGUILayout.EndVertical(); } } public void ResetToolVar() { m_ErosionSettings.Reset(); } public void OnMaterialInspectorGUI(Terrain terrain, IOnInspectorGUI editContext) { m_ShowControls = EditorGUILayout.Foldout(m_ShowControls, "Hydraulic Erosion Controls"); if (m_ShowControls) { EditorGUILayout.BeginVertical("GroupBox"); string[] maskSourceNames = new string[] { "Sediment", "Heightmap Differential", "Water Flux", "Water Level", "Water Speed" }; m_ErosionSettings.m_MaskSourceSelection = (HydraulicErosionSettings.MaskSource)EditorGUILayout.Popup("Mask Source", (int)m_ErosionSettings.m_MaskSourceSelection, maskSourceNames); m_ErosionSettings.m_SimScale.DrawInspectorGUI(); EditorGUI.BeginChangeCheck(); m_ErosionSettings.m_MaterialSpread.DrawInspectorGUI(); if (EditorGUI.EndChangeCheck()) { switch (m_ErosionSettings.m_MaskSourceSelection) { case HydraulicErosionSettings.MaskSource.HeightDiff: case HydraulicErosionSettings.MaskSource.Sediment: m_ErosionSettings.m_SimScale.value = Mathf.Lerp(0.0f, 10.0f, m_ErosionSettings.m_MaterialSpread.value); m_ErosionSettings.m_ThermalTimeDelta = Mathf.Lerp(0.001f, 0.0001f, m_ErosionSettings.m_MaterialSpread.value); break; case HydraulicErosionSettings.MaskSource.WaterFlux: m_ErosionSettings.m_SimScale.value = Mathf.Lerp(10.0f, 0.0f, m_ErosionSettings.m_MaterialSpread.value); break; } } m_ErosionSettings.m_MaterialOpacity = EditorGUILayout.Slider(Erosion.Styles.m_MaterialOpacity, m_ErosionSettings.m_MaterialOpacity, 0.0f, 1.0f); EditorGUI.indentLevel++; m_ShowAdvancedUI = EditorGUILayout.Foldout(m_ShowAdvancedUI, "Advanced"); if (m_ShowAdvancedUI) { m_ErosionSettings.m_IterationBlendScalar.DrawInspectorGUI(); m_ErosionSettings.m_HydroTimeDelta.DrawInspectorGUI(); m_ErosionSettings.m_HydroIterations.DrawInspectorGUI(); //m_ErosionSettings.m_GravitationalConstant = EditorGUILayout.Slider(Erosion.Styles.m_GravitationConstant, m_ErosionSettings.m_GravitationalConstant, 0.0f, -100.0f); EditorGUI.indentLevel++; m_ShowThermalUI = EditorGUILayout.Foldout(m_ShowThermalUI, "Thermal Erosion"); if (m_ShowThermalUI) { m_ErosionSettings.m_DoThermal = EditorGUILayout.Toggle(Erosion.Styles.m_DoThermal, m_ErosionSettings.m_DoThermal); m_ErosionSettings.m_ThermalTimeDelta = EditorGUILayout.Slider(Erosion.Styles.m_ThermalDTScalar, m_ErosionSettings.m_ThermalTimeDelta, 0.0000f, 0.001f); m_ErosionSettings.m_ThermalIterations = EditorGUILayout.IntSlider(Erosion.Styles.m_NumIterations, m_ErosionSettings.m_ThermalIterations, 0, 100); m_ErosionSettings.m_ThermalReposeAngle = EditorGUILayout.IntSlider(Erosion.Styles.m_AngleOfRepose, m_ErosionSettings.m_ThermalReposeAngle, 0, 90); } m_ShowWaterUI = EditorGUILayout.Foldout(m_ShowWaterUI, "Water Transport"); if (m_ShowWaterUI) { m_ErosionSettings.m_WaterLevelScale = EditorGUILayout.Slider(Erosion.Styles.m_WaterLevelScale, m_ErosionSettings.m_WaterLevelScale, 0.0f, 100.0f); m_ErosionSettings.m_PrecipRate.DrawInspectorGUI(); m_ErosionSettings.m_EvaporationRate.DrawInspectorGUI(); m_ErosionSettings.m_FlowRate.DrawInspectorGUI(); } m_ShowSedimentUI = EditorGUILayout.Foldout(m_ShowSedimentUI, "Sediment Transport"); if (m_ShowSedimentUI) { m_ErosionSettings.m_SedimentScale = EditorGUILayout.Slider(Erosion.Styles.m_SedimentScale, m_ErosionSettings.m_SedimentScale, 0.0f, 1.0f); m_ErosionSettings.m_SedimentDepositRate.DrawInspectorGUI(); m_ErosionSettings.m_SedimentCapacity.DrawInspectorGUI(); m_ErosionSettings.m_SedimentDissolveRate.DrawInspectorGUI(); } m_ShowRiverBankUI = EditorGUILayout.Foldout(m_ShowRiverBankUI, "Riverbank"); if (m_ShowRiverBankUI) { m_ErosionSettings.m_RiverBankDepositRate.DrawInspectorGUI(); m_ErosionSettings.m_RiverBankDissolveRate.DrawInspectorGUI(); m_ErosionSettings.m_RiverBedDepositRate.DrawInspectorGUI(); m_ErosionSettings.m_RiverBedDissolveRate.DrawInspectorGUI(); } } EditorGUILayout.EndVertical(); } } public void ErodeHeightmap(RenderTexture dest, Vector3 terrainDimensions, Rect domainRect, Vector2 texelSize, bool invertEffect = false) { ErodeHelper(dest, terrainDimensions, texelSize, invertEffect, false); } public void GetShaderParams(ref int pass, ref RenderTexture maskRT) { //TODO: betterify this. switch (m_ErosionSettings.m_MaskSourceSelection) { case Erosion.HydraulicErosionSettings.MaskSource.Sediment: pass = 0; maskRT = m_ErodedRT; break; case Erosion.HydraulicErosionSettings.MaskSource.HeightDiff: pass = 0; maskRT = m_ErodedRT; //TODO - this isn't quite right since we're actually putting height diff into the eroded RT break; case Erosion.HydraulicErosionSettings.MaskSource.WaterFlux: pass = 2; maskRT = m_FluxRT[1]; break; case Erosion.HydraulicErosionSettings.MaskSource.WaterLevel: pass = 0; maskRT = m_WaterRT[1]; break; case Erosion.HydraulicErosionSettings.MaskSource.WaterSpeed: pass = 1; maskRT = m_WaterVelRT[1]; break; } } private void ErodeHelper(RenderTexture dest, Vector3 terrainScale, Vector2 texelSize, bool invertEffect, bool lowRes) { ComputeShader hydraulicCS = GetHydraulicCS(); ComputeShader thermalCS = GetThermalCS(); //this one is mandatory if (!inputTextures.ContainsKey("Height")) { throw (new Exception("No input heightfield specified!")); } int[] numWorkGroups = { 8, 8, 1 }; //figure out what size we need our render targets to be Vector2Int domainRes = new Vector2Int(inputTextures["Height"].width, inputTextures["Height"].height); int rx = domainRes.x - (numWorkGroups[0] * (domainRes.x / numWorkGroups[0])); int ry = domainRes.y - (numWorkGroups[1] * (domainRes.y / numWorkGroups[1])); domainRes.x += numWorkGroups[0] - rx; domainRes.y += numWorkGroups[1] - ry; if (lowRes) { domainRes.x /= 2; domainRes.y /= 2; } CreateRTHandles(new Vector2Int(domainRes.x, domainRes.y)); RenderTexture rt = RenderTexture.active; Graphics.Blit(inputTextures["Height"], m_HeightmapRT[0]); Graphics.Blit(inputTextures["Height"], m_HeightmapRT[1]); if (inputTextures.ContainsKey("Hardness")) { Graphics.Blit(inputTextures["Hardness"], m_HardnessRT); } else { Graphics.Blit(Texture2D.blackTexture, m_HardnessRT); } RenderTexture.active = rt; ClearRTHandles(); int sedimentKernelIdx = hydraulicCS.FindKernel("HydraulicErosion"); int flowKernelIdx = hydraulicCS.FindKernel("SimulateWaterFlow"); int thermalKernelIdx = thermalCS.FindKernel("ThermalErosion"); float precipRate = 0.00001f * m_ErosionSettings.m_PrecipRate.value; float evaporationRate = 0.00001f * m_ErosionSettings.m_EvaporationRate.value; float flowRate = 0.0001f * m_ErosionSettings.m_FlowRate.value; float sedimentCap = 0.1f * m_ErosionSettings.m_SedimentCapacity.value; float sedimentDissolveRate = 0.0001f * m_ErosionSettings.m_SedimentDissolveRate.value; float sedimentDepositRate = 0.0001f * m_ErosionSettings.m_SedimentDepositRate.value; float simScale = 0.001f * Mathf.Max(m_ErosionSettings.m_SimScale.value, 0.000001f); float dx = (float)texelSize.x * simScale; float dy = (float)texelSize.y * simScale; float dxdy = Mathf.Sqrt(dx * dx + dy * dy); float effectScalar = m_ErosionSettings.m_IterationBlendScalar.value; //constants for both kernels hydraulicCS.SetFloat("EffectScalar", invertEffect ? effectScalar : -effectScalar); hydraulicCS.SetFloat("DT", m_ErosionSettings.m_HydroTimeDelta.value); hydraulicCS.SetVector("dxdy", new Vector4(dx, dy, 1.0f / dx, 1.0f / dy)); hydraulicCS.SetVector("WaterTransportScalars", new Vector4(m_ErosionSettings.m_WaterLevelScale, precipRate, flowRate * m_ErosionSettings.m_GravitationalConstant, evaporationRate)); hydraulicCS.SetVector("SedimentScalars", new Vector4(m_ErosionSettings.m_SedimentScale, sedimentCap, sedimentDissolveRate, sedimentDepositRate)); hydraulicCS.SetVector("RiverBedScalars", new Vector4(m_ErosionSettings.m_RiverBedDissolveRate.value, m_ErosionSettings.m_RiverBedDepositRate.value, m_ErosionSettings.m_RiverBankDissolveRate.value, m_ErosionSettings.m_RiverBankDepositRate.value)); hydraulicCS.SetVector("terrainDim", new Vector4(terrainScale.x, terrainScale.y, terrainScale.z)); hydraulicCS.SetVector("texDim", new Vector4((float)domainRes.x, (float)domainRes.y, 0.0f, 0.0f)); if (m_ErosionSettings.m_DoThermal) { //thermal kernel inputs Vector2 thermal_m = new Vector2(Mathf.Tan((float)m_ErosionSettings.m_ThermalReposeAngle * Mathf.Deg2Rad), Mathf.Tan((float)m_ErosionSettings.m_ThermalReposeAngle * Mathf.Deg2Rad)); thermalCS.SetFloat("dt", m_ErosionSettings.m_ThermalTimeDelta * m_ErosionSettings.m_HydroTimeDelta.value); thermalCS.SetFloat("EffectScalar", invertEffect ? effectScalar : -effectScalar); thermalCS.SetVector("angleOfRepose", new Vector4(thermal_m.x, thermal_m.y, 0.0f, 0.0f)); thermalCS.SetVector("dxdy", new Vector4(dx, dy, 1.0f / dx, 1.0f / dy)); thermalCS.SetFloat("InvDiagMag", 1.0f / Mathf.Sqrt(dx * dx + dy * dy)); thermalCS.SetVector("terrainDim", new Vector4(terrainScale.x, terrainScale.y, terrainScale.z)); thermalCS.SetVector("texDim", new Vector4((float)domainRes.x, (float)domainRes.y, 0.0f, 0.0f)); thermalCS.SetTexture(thermalKernelIdx, "TerrainHeightPrev", m_HeightmapRT[0]); thermalCS.SetTexture(thermalKernelIdx, "TerrainHeight", m_HeightmapRT[1]); thermalCS.SetTexture(thermalKernelIdx, "SedimentPrev", m_SedimentRT[0]); thermalCS.SetTexture(thermalKernelIdx, "Sediment", m_SedimentRT[1]); //thermalCS.SetTexture(thermalKernelIdx, "Collision", inputTextures["Collision"]); } int pingPongIdx = 0; for (int i = 0; i < (lowRes ? m_ErosionSettings.m_HydroLowResIterations : m_ErosionSettings.m_HydroIterations.value); i++) { int a = pingPongIdx; int b = (a + 1) % 2; //flow kernel textures hydraulicCS.SetTexture(flowKernelIdx, "TerrainHeightPrev", m_HeightmapRT[a]); hydraulicCS.SetTexture(flowKernelIdx, "WaterPrev", m_WaterRT[a]); hydraulicCS.SetTexture(flowKernelIdx, "Water", m_WaterRT[b]); hydraulicCS.SetTexture(flowKernelIdx, "WaterVelPrev", m_WaterVelRT[a]); hydraulicCS.SetTexture(flowKernelIdx, "WaterVel", m_WaterVelRT[b]); hydraulicCS.SetTexture(flowKernelIdx, "FluxPrev", m_FluxRT[a]); hydraulicCS.SetTexture(flowKernelIdx, "Flux", m_FluxRT[b]); hydraulicCS.Dispatch(flowKernelIdx, domainRes.x / numWorkGroups[0], domainRes.y / numWorkGroups[1], numWorkGroups[2]); //Sediment Setup hydraulicCS.SetTexture(sedimentKernelIdx, "TerrainHeightPrev", m_HeightmapRT[a]); hydraulicCS.SetTexture(sedimentKernelIdx, "TerrainHeight", m_HeightmapRT[b]); hydraulicCS.SetTexture(sedimentKernelIdx, "Water", m_WaterRT[a]); hydraulicCS.SetTexture(sedimentKernelIdx, "WaterPrev", m_WaterRT[b]); hydraulicCS.SetTexture(sedimentKernelIdx, "WaterVel", m_WaterVelRT[b]); hydraulicCS.SetTexture(sedimentKernelIdx, "Flux", m_FluxRT[b]); hydraulicCS.SetTexture(sedimentKernelIdx, "SedimentPrev", m_SedimentRT[a]); hydraulicCS.SetTexture(sedimentKernelIdx, "Sediment", m_SedimentRT[b]); hydraulicCS.SetTexture(sedimentKernelIdx, "Hardness", m_HardnessRT); hydraulicCS.SetTexture(sedimentKernelIdx, "Eroded", m_ErodedRT); hydraulicCS.Dispatch(sedimentKernelIdx, domainRes.x / numWorkGroups[0], domainRes.y / numWorkGroups[1], numWorkGroups[2]); //Thermal Smoothing //now do a few thermal iterations to let things settle int thermalPingPongIdx = 0; for (int j = 0; m_ErosionSettings.m_DoThermal && (j < m_ErosionSettings.m_ThermalIterations); j++) { int ta = thermalPingPongIdx; int tb = (ta + 1) % 2; thermalCS.SetTexture(thermalKernelIdx, "TerrainHeightPrev", m_HeightmapRT[ta]); thermalCS.SetTexture(thermalKernelIdx, "TerrainHeight", m_HeightmapRT[tb]); thermalCS.SetTexture(thermalKernelIdx, "Hardness", m_HardnessRT); thermalCS.Dispatch(thermalKernelIdx, domainRes.x / numWorkGroups[0], domainRes.y / numWorkGroups[1], numWorkGroups[2]); thermalPingPongIdx = (thermalPingPongIdx + 1) % 2; } pingPongIdx = (pingPongIdx + 1) % 2; } // set up the output render textures Graphics.Blit(m_HeightmapRT[1], dest); ReleaseRTHandles(); } } }