// Crest Water System // Copyright © 2024 Wave Harmonic. All rights reserved. // FIXME: Broken for BIRP on MacOS. Either platform specific problem or bug in Unity. using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.HighDefinition; using UnityEngine.Rendering.Universal; namespace WaveHarmonic.Crest { partial class WaterRenderer { CommandBuffer _WaterLevelDepthBuffer; RenderTexture _WaterLevelDepthTexture; internal RenderTexture WaterLevelDepthTexture => _WaterLevelDepthTexture; RenderTargetIdentifier _WaterLevelDepthTarget; Material _WaterLevelDepthMaterial; internal readonly Plane[] _CameraFrustumPlanes = new Plane[6]; const string k_WaterLevelDepthTextureName = "Crest Water Level Depth Texture"; void RenderWaterSurface(CommandBuffer buffer, Camera camera, Material material) { GeometryUtility.CalculateFrustumPlanes(camera, _CameraFrustumPlanes); // Spends approx 0.2-0.3ms here on 2018 Dell XPS 15. foreach (var chunk in Chunks) { var renderer = chunk.Rend; // Can happen in edit mode. if (renderer == null) continue; var bounds = renderer.bounds; if (GeometryUtility.TestPlanesAABB(_CameraFrustumPlanes, bounds)) { if ((!chunk._WaterDataHasBeenBound) && chunk.enabled) { chunk.Bind(camera); } renderer.SetPropertyBlock(chunk._MaterialPropertyBlock); // Assume correct pass is zero. Use to be k_ShaderPassWaterSurfaceMask. buffer.DrawRenderer(renderer, material, submeshIndex: 0, shaderPass: 0); } chunk._WaterDataHasBeenBound = false; } } void ExecuteWaterLevelDepthTexture(Camera camera, CommandBuffer buffer) { Helpers.CreateRenderTargetTextureReference(ref _WaterLevelDepthTexture, ref _WaterLevelDepthTarget); _WaterLevelDepthTexture.name = k_WaterLevelDepthTextureName; if (_WaterLevelDepthMaterial == null) { _WaterLevelDepthMaterial = new(Shader.Find("Hidden/Crest/Editor/Water Level (Depth)")); } var descriptor = new RenderTextureDescriptor(camera.pixelWidth, camera.pixelHeight) { graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.None, depthBufferBits = 32, }; // Depth texture. // Always release to handle screen size changes. _WaterLevelDepthTexture.Release(); descriptor.graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.R32_SFloat; descriptor.depthBufferBits = 0; Helpers.SafeCreateRenderTexture(ref _WaterLevelDepthTexture, descriptor); _WaterLevelDepthTexture.Create(); // Depth buffer. buffer.GetTemporaryRT(Helpers.ShaderIDs.s_MainTexture, descriptor); buffer.SetRenderTarget(Helpers.ShaderIDs.s_MainTexture); buffer.ClearRenderTarget(true, false, Color.clear); RenderWaterSurface(buffer, camera, _WaterLevelDepthMaterial); // Convert. Helpers.Blit(buffer, _WaterLevelDepthTarget, Helpers.UtilityMaterial, (int)Helpers.UtilityPass.Copy); buffer.ReleaseTemporaryRT(Helpers.ShaderIDs.s_MainTexture); } void EnableWaterLevelDepthTexture() { if (Application.isPlaying) return; #if d_UnityURP if (RenderPipelineHelper.IsUniversal) { WaterLevelDepthTextureURP.Enable(); } #endif #if d_UnityHDRP if (RenderPipelineHelper.IsHighDefinition) { WaterLevelDepthTextureHDRP.Enable(); } #endif } void DisableWaterLevelDepthTexture() { if (Application.isPlaying) return; #if d_UnityURP WaterLevelDepthTextureURP.Disable(); #endif #if d_UnityHDRP WaterLevelDepthTextureHDRP.Disable(); #endif } void OnPreRenderWaterLevelDepthTexture(Camera camera) { if (camera.cameraType != CameraType.SceneView || camera != Viewer) { return; } _WaterLevelDepthBuffer ??= new() { name = k_WaterLevelDepthTextureName }; _WaterLevelDepthBuffer.Clear(); ExecuteWaterLevelDepthTexture(camera, _WaterLevelDepthBuffer); // Both forward and deferred. camera.AddCommandBuffer(CameraEvent.BeforeDepthTexture, _WaterLevelDepthBuffer); camera.AddCommandBuffer(CameraEvent.BeforeGBuffer, _WaterLevelDepthBuffer); } void OnPostRenderWaterLevelDepthTexture(Camera camera) { if (_WaterLevelDepthBuffer != null) { // Both forward and deferred. camera.RemoveCommandBuffer(CameraEvent.BeforeDepthTexture, _WaterLevelDepthBuffer); camera.RemoveCommandBuffer(CameraEvent.BeforeGBuffer, _WaterLevelDepthBuffer); } } #if d_UnityURP sealed class WaterLevelDepthTextureURP : ScriptableRenderPass { static WaterLevelDepthTextureURP s_Instance; internal WaterLevelDepthTextureURP() { // Will always execute and matrices will be ready. renderPassEvent = RenderPassEvent.BeforeRenderingPrePasses; } internal static void Enable() { s_Instance ??= new(); RenderPipelineManager.beginCameraRendering -= s_Instance.EnqueuePass; RenderPipelineManager.beginCameraRendering += s_Instance.EnqueuePass; RenderPipelineManager.activeRenderPipelineTypeChanged -= Disable; RenderPipelineManager.activeRenderPipelineTypeChanged += Disable; } internal static void Disable() { // FIXME: Out of range exception when no null check but shouldn't be necessary. if (s_Instance != null) RenderPipelineManager.beginCameraRendering -= s_Instance.EnqueuePass; RenderPipelineManager.activeRenderPipelineTypeChanged -= Disable; } void EnqueuePass(ScriptableRenderContext context, Camera camera) { if (camera.cameraType != CameraType.SceneView || camera != Instance.Viewer) { return; } // Enqueue the pass. This happens every frame. camera.GetUniversalAdditionalCameraData().scriptableRenderer.EnqueuePass(this); } #if UNITY_6000_0_OR_NEWER class PassData { public UniversalCameraData _CameraData; } public override void RecordRenderGraph(UnityEngine.Rendering.RenderGraphModule.RenderGraph graph, ContextContainer frame) { using (var builder = graph.AddUnsafePass(k_WaterLevelDepthTextureName, out var data)) { builder.AllowPassCulling(false); data._CameraData = frame.Get(); builder.SetRenderFunc((data, context) => { var buffer = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd); Instance.ExecuteWaterLevelDepthTexture(data._CameraData.camera, buffer); }); } } [System.Obsolete] #endif public override void Execute(ScriptableRenderContext context, ref RenderingData data) { var buffer = CommandBufferPool.Get(k_WaterLevelDepthTextureName); Instance.ExecuteWaterLevelDepthTexture(data.cameraData.camera, buffer); context.ExecuteCommandBuffer(buffer); CommandBufferPool.Release(buffer); } } #endif #if d_UnityHDRP sealed class WaterLevelDepthTextureHDRP : CustomPass { static WaterLevelDepthTextureHDRP s_Instance; GameObject _GameObject; internal static void Enable() { var gameObject = CustomPassHelpers.CreateOrUpdate ( parent: Instance.Container.transform, k_WaterLevelDepthTextureName, hide: !Instance._Debug._ShowHiddenObjects ); CustomPassHelpers.CreateOrUpdate ( gameObject, ref s_Instance, k_WaterLevelDepthTextureName, CustomPassInjectionPoint.BeforeRendering ); s_Instance._GameObject = gameObject; } public static void Disable() { // It should be safe to rely on this reference for this reference to fail. if (s_Instance != null && s_Instance._GameObject != null) { // Will also trigger Cleanup below. s_Instance._GameObject.SetActive(false); } } protected override void Execute(CustomPassContext context) { var camera = context.hdCamera.camera; if (camera.cameraType != CameraType.SceneView || camera != Instance.Viewer) { return; } Instance.ExecuteWaterLevelDepthTexture(camera, context.cmd); } } #endif } }