升级水插件

This commit is contained in:
2026-01-08 22:30:55 +08:00
parent febff82d24
commit ca68084264
415 changed files with 18138 additions and 7134 deletions

View File

@@ -16,14 +16,15 @@ namespace WaveHarmonic.Crest
RTHandle _ColorTarget;
RTHandle _DepthTarget;
bool _FirstRender = true;
readonly System.Action<CommandBuffer> _CopyColorTexture;
readonly System.Action<CommandBuffer> _SetRenderTargetToBackBuffers;
public UnderwaterEffectPass(UnderwaterRenderer renderer)
{
_Renderer = renderer;
_CopyColorTexture = new(CopyColorTexture);
_SetRenderTargetToBackBuffers = new(SetRenderTargetToBackBuffers);
}
void CopyColorTexture(CommandBuffer buffer)
@@ -32,10 +33,20 @@ namespace WaveHarmonic.Crest
CoreUtils.SetRenderTarget(buffer, _ColorTarget, _DepthTarget, ClearFlag.None);
}
void SetRenderTargetToBackBuffers(CommandBuffer commands)
{
CoreUtils.SetRenderTarget(commands, _ColorTarget, _DepthTarget, ClearFlag.None);
}
public void Allocate(GraphicsFormat format)
{
if (_Renderer.RenderBeforeTransparency && !_Renderer._NeedsColorTexture)
{
return;
}
// TODO: There may other settings we want to set or bring in. Not MSAA since this is a resolved texture.
_ColorTexture = RTHandles.Alloc
_ColorTexture ??= RTHandles.Alloc
(
Vector2.one,
TextureXR.slices,
@@ -50,6 +61,11 @@ namespace WaveHarmonic.Crest
public void ReAllocate(RenderTextureDescriptor descriptor)
{
if (_Renderer.RenderBeforeTransparency && !_Renderer._NeedsColorTexture)
{
return;
}
// Descriptor will not have MSAA bound.
RenderPipelineCompatibilityHelper.ReAllocateIfNeeded(ref _ColorTexture, descriptor, name: "_Crest_UnderwaterCameraColorTexture");
}
@@ -62,22 +78,32 @@ namespace WaveHarmonic.Crest
public void Execute(Camera camera, CommandBuffer buffer, RTHandle color, RTHandle depth, MaterialPropertyBlock mpb = null)
{
_Renderer.UpdateEffectMaterial(camera, _FirstRender);
_Renderer.UpdateEffectMaterial(camera);
_ColorTarget = color;
_DepthTarget = depth;
CopyColorTexture(buffer);
buffer.SetGlobalTexture(UnderwaterRenderer.ShaderIDs.s_CameraColorTexture, _ColorTexture);
if (!_Renderer.RenderBeforeTransparency || _Renderer._NeedsColorTexture)
{
buffer.SetGlobalTexture(UnderwaterRenderer.ShaderIDs.s_CameraColorTexture, _ColorTexture);
}
_Renderer.ExecuteEffect(camera, buffer, _CopyColorTexture, mpb);
if (!_Renderer.RenderBeforeTransparency)
{
CopyColorTexture(buffer);
}
else
{
// TODO: needed for HDRP, but can set it on pass instead.
CoreUtils.SetRenderTarget(buffer, _ColorTarget, _DepthTarget, ClearFlag.None);
}
_Renderer.ExecuteEffect(camera, buffer, _CopyColorTexture, _SetRenderTargetToBackBuffers, mpb);
// The last pass (uber post) does not resolve the texture.
// Although, this is wasteful if the pass after this does a resolve.
// Possibly a bug with Unity?
buffer.ResolveAntiAliasedSurface(color);
_FirstRender = false;
}
}
}

View File

@@ -7,7 +7,6 @@ using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using UnityEngine.Rendering.RendererUtils;
namespace WaveHarmonic.Crest
{
@@ -17,13 +16,11 @@ namespace WaveHarmonic.Crest
static UnderwaterRenderer s_Renderer;
static UnderwaterEffectPass s_UnderwaterEffectPass;
static UnderwaterEffectPassHDRP s_Instance;
internal static UnderwaterEffectPassHDRP s_Instance;
static CopyDepthBufferPassHDRP s_CopyDepthBufferPassHDRP;
static ShaderTagId[] s_ForwardShaderTags;
GameObject _GameObject;
public static void Enable(UnderwaterRenderer renderer)
{
var gameObject = CustomPassHelpers.CreateOrUpdate
@@ -37,25 +34,24 @@ namespace WaveHarmonic.Crest
(
gameObject,
ref s_CopyDepthBufferPassHDRP,
"Copy Depth Buffer",
UnderwaterRenderer.k_DrawVolume,
CustomPassInjectionPoint.AfterOpaqueDepthAndNormal
);
var isBeforeTransparentPass = renderer.RenderBeforeTransparency;
CustomPassHelpers.CreateOrUpdate
(
gameObject,
ref s_Instance,
k_Name,
CustomPassInjectionPoint.BeforePostProcess
UnderwaterRenderer.k_DrawVolume,
GetInjectionPoint(isBeforeTransparentPass),
// Higher number (priority) means execute earlier. Volume executes first.
priority: 1
);
s_Instance._GameObject = gameObject;
s_Renderer = renderer;
s_UnderwaterEffectPass = new(renderer);
RenderPipelineManager.beginCameraRendering -= s_Instance.OnBeginCameraRendering;
RenderPipelineManager.beginCameraRendering += s_Instance.OnBeginCameraRendering;
}
public static void Disable()
@@ -68,9 +64,17 @@ namespace WaveHarmonic.Crest
}
}
void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
static CustomPassInjectionPoint GetInjectionPoint(bool isBeforeTransparentPass)
{
return isBeforeTransparentPass
? CustomPassInjectionPoint.BeforeTransparent
: CustomPassInjectionPoint.BeforePostProcess;
}
internal void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
{
s_CopyDepthBufferPassHDRP.enabled = s_Renderer.UseStencilBuffer;
s_Instance._Volume.injectionPoint = GetInjectionPoint(s_Renderer.RenderBeforeTransparency);
}
protected override void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd)
@@ -94,8 +98,6 @@ namespace WaveHarmonic.Crest
protected override void Cleanup()
{
RenderPipelineManager.beginCameraRendering -= s_Instance.OnBeginCameraRendering;
s_UnderwaterEffectPass?.Release();
}
@@ -108,44 +110,15 @@ namespace WaveHarmonic.Crest
return;
}
// Allocate here in case user changes options and we skipped allocation in Setup.
s_UnderwaterEffectPass.Allocate(context.cameraColorBuffer.rt.graphicsFormat);
// Create a separate stencil buffer context by using a depth buffer copy if needed.
var depthBuffer = s_Renderer.UseStencilBuffer
? s_CopyDepthBufferPassHDRP._DepthBufferCopy
: context.cameraDepthBuffer;
s_UnderwaterEffectPass.Execute(camera, context.cmd, context.cameraColorBuffer, depthBuffer, context.propertyBlock);
// Renders transparent objects after the underwater effect. Using the correct
// shader, the above water portion of the object is rendered normally (in the
// transparent pass), and the below water portion is rendered here with underwater
// applied.
// See the following for reference:
// https://github.com/Unity-Technologies/Graphics/blob/master/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPass/DrawRenderersCustomPass.cs
if (s_Renderer.EnableShaderAPI)
{
var renderConfig = HDUtils.GetRendererConfiguration
(
#if UNITY_6000_0_OR_NEWER
context.hdCamera.frameSettings.IsEnabled(FrameSettingsField.AdaptiveProbeVolume),
#else
context.hdCamera.frameSettings.IsEnabled(FrameSettingsField.ProbeVolume),
#endif
context.hdCamera.frameSettings.IsEnabled(FrameSettingsField.Shadowmask)
);
var result = new RendererListDesc(s_ForwardShaderTags, context.cullingResults, context.hdCamera.camera)
{
rendererConfiguration = renderConfig,
renderQueueRange = GetRenderQueueRange(RenderQueueType.AllTransparent),
sortingCriteria = SortingCriteria.CommonTransparent,
excludeObjectMotionVectors = false,
layerMask = s_Renderer._TransparentObjectLayers,
};
context.cmd.EnableShaderKeyword(UnderwaterRenderer.k_KeywordUnderwaterObjects);
CoreUtils.DrawRendererList(context.renderContext, context.cmd, context.renderContext.CreateRendererList(result));
context.cmd.DisableShaderKeyword(UnderwaterRenderer.k_KeywordUnderwaterObjects);
}
}
}
@@ -166,13 +139,11 @@ namespace WaveHarmonic.Crest
var buffer = context.cmd;
buffer.SetRenderTarget(BuiltinRenderTextureType.None, _DepthBufferCopy);
buffer.ClearRenderTarget(RTClearFlags.Depth, Color.black, 1, 0);
// NOTE: previously we cleared the target depth first due to artifacts.
buffer.CopyTexture(context.cameraDepthBuffer.rt, _DepthBufferCopy.rt);
// Clear the stencil component just in case.
buffer.ClearRenderTarget(RTClearFlags.Stencil, Color.black, 1, 0);
CoreUtils.SetRenderTarget(buffer, BuiltinRenderTextureType.None, _DepthBufferCopy, ClearFlag.Stencil);
}
protected override void Cleanup()

View File

@@ -12,46 +12,16 @@ namespace WaveHarmonic.Crest
{
partial class UnderwaterEffectPassURP
{
class PassData
{
#pragma warning disable IDE1006 // Naming Styles
public UniversalCameraData cameraData;
public RenderGraphHelper.Handle colorTargetHandle;
public RenderGraphHelper.Handle depthTargetHandle;
#pragma warning restore IDE1006 // Naming Styles
public void Init(ContextContainer frameData, IUnsafeRenderGraphBuilder builder = null)
{
var resources = frameData.Get<UniversalResourceData>();
cameraData = frameData.Get<UniversalCameraData>();
if (builder == null)
{
#pragma warning disable CS0618 // Type or member is obsolete
colorTargetHandle = cameraData.renderer.cameraColorTargetHandle;
depthTargetHandle = cameraData.renderer.cameraDepthTargetHandle;
#pragma warning restore CS0618 // Type or member is obsolete
}
else
{
colorTargetHandle = resources.activeColorTexture;
depthTargetHandle = resources.activeDepthTexture;
builder.UseTexture(colorTargetHandle, AccessFlags.ReadWrite);
builder.UseTexture(depthTargetHandle, AccessFlags.ReadWrite);
}
}
}
readonly PassData _PassData = new();
readonly RenderGraphHelper.PassData _PassData = new();
public override void RecordRenderGraph(RenderGraph graph, ContextContainer frame)
{
using (var builder = graph.AddUnsafePass<PassData>(k_Name, out var data))
using (var builder = graph.AddUnsafePass<RenderGraphHelper.PassData>(k_Name, out var data))
{
data.Init(frame, builder);
builder.AllowPassCulling(false);
builder.SetRenderFunc<PassData>((data, context) =>
builder.SetRenderFunc<RenderGraphHelper.PassData>((data, context) =>
{
var buffer = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
OnSetup(buffer, data);
@@ -76,60 +46,6 @@ namespace WaveHarmonic.Crest
context.ExecuteCommandBuffer(buffer);
CommandBufferPool.Release(buffer);
}
partial class RenderObjectsWithoutFogPass
{
class PassData
{
#pragma warning disable IDE1006 // Naming Styles
public UniversalCameraData cameraData;
public UniversalLightData lightData;
public UniversalRenderingData renderingData;
public CullingResults cullResults;
#pragma warning restore IDE1006 // Naming Styles
public void Init(ContextContainer frameData, IUnsafeRenderGraphBuilder builder = null)
{
cameraData = frameData.Get<UniversalCameraData>();
lightData = frameData.Get<UniversalLightData>();
renderingData = frameData.Get<UniversalRenderingData>();
cullResults = renderingData.cullResults;
}
}
readonly PassData _PassData = new();
public override void RecordRenderGraph(RenderGraph graph, ContextContainer frame)
{
using (var builder = graph.AddUnsafePass<PassData>(k_Name, out var data))
{
data.Init(frame, builder);
builder.AllowPassCulling(false);
builder.SetRenderFunc<PassData>((data, context) =>
{
var buffer = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
Execute(context.GetRenderContext(), buffer, data);
});
}
}
[System.Obsolete]
public override void OnCameraSetup(CommandBuffer buffer, ref RenderingData data)
{
_PassData.Init(data.GetFrameData());
}
[System.Obsolete]
public override void Execute(ScriptableRenderContext context, ref RenderingData data)
{
_PassData.Init(data.GetFrameData());
var buffer = CommandBufferPool.Get(k_Name);
Execute(context, buffer, _PassData);
context.ExecuteCommandBuffer(buffer);
CommandBufferPool.Release(buffer);
}
}
}
partial class CopyDepthBufferPassURP
@@ -150,11 +66,15 @@ namespace WaveHarmonic.Crest
if (builder == null)
{
#pragma warning disable CS0618 // Type or member is obsolete
colorTargetHandle = cameraData.renderer.cameraColorTargetHandle;
depthTargetHandle = cameraData.renderer.cameraDepthTargetHandle;
#pragma warning restore CS0618 // Type or member is obsolete
}
else
{
// We need reset render targets to these before the next pass, but we do not read
// or write to the color target.
colorTargetHandle = resources.activeColorTexture;
depthTargetHandle = resources.activeDepthTexture;
builder.UseTexture(depthTargetHandle, AccessFlags.ReadWrite);
}

View File

@@ -3,7 +3,6 @@
#if d_UnityURP
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
@@ -13,12 +12,11 @@ namespace WaveHarmonic.Crest
{
sealed partial class UnderwaterEffectPassURP : ScriptableRenderPass
{
const string k_Name = "Crest Underwater Effect";
const string k_Name = "Crest.DrawWater/Volume";
UnderwaterRenderer _Renderer;
static UnderwaterEffectPassURP s_Instance;
RenderObjectsWithoutFogPass _ApplyFogToTransparentObjects;
internal static UnderwaterEffectPassURP s_Instance;
UnderwaterEffectPass _UnderwaterEffectPass;
CopyDepthBufferPassURP _CopyDepthBufferPass;
@@ -27,7 +25,6 @@ namespace WaveHarmonic.Crest
public UnderwaterEffectPassURP()
{
renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
ConfigureInput(ScriptableRenderPassInput.Color | ScriptableRenderPassInput.Depth);
}
@@ -38,18 +35,14 @@ namespace WaveHarmonic.Crest
s_Instance = new();
s_Instance._Renderer = renderer;
s_Instance._CopyDepthBufferPass = new(RenderPassEvent.AfterRenderingOpaques);
s_Instance._ApplyFogToTransparentObjects = new();
}
RenderPipelineManager.beginCameraRendering -= s_Instance.EnqueuePass;
RenderPipelineManager.beginCameraRendering += s_Instance.EnqueuePass;
RenderPipelineManager.activeRenderPipelineTypeChanged -= Disable;
RenderPipelineManager.activeRenderPipelineTypeChanged += Disable;
}
public static void Disable()
{
if (s_Instance != null) RenderPipelineManager.beginCameraRendering -= s_Instance.EnqueuePass;
RenderPipelineManager.activeRenderPipelineTypeChanged -= Disable;
s_Instance?._UnderwaterEffectPass?.Release();
@@ -57,13 +50,15 @@ namespace WaveHarmonic.Crest
s_Instance = null;
}
void EnqueuePass(ScriptableRenderContext context, Camera camera)
internal void EnqueuePass(ScriptableRenderContext context, Camera camera)
{
if (!_Renderer.ShouldRender(camera, UnderwaterRenderer.Pass.Effect))
{
return;
}
s_Instance.renderPassEvent = _Renderer.RenderBeforeTransparency ? WaterRenderer.k_WaterRenderPassEvent : RenderPassEvent.AfterRenderingTransparents;
var renderer = camera.GetUniversalAdditionalCameraData().scriptableRenderer;
#if UNITY_EDITOR
@@ -80,25 +75,40 @@ namespace WaveHarmonic.Crest
_UnderwaterEffectPass ??= new(_Renderer);
renderer.EnqueuePass(s_Instance);
if (_Renderer.EnableShaderAPI)
{
renderer.EnqueuePass(_ApplyFogToTransparentObjects);
}
}
#if UNITY_6000_0_OR_NEWER
void OnSetup(CommandBuffer buffer, PassData data)
bool _ErrorMissingColorTarget;
void OnSetup(CommandBuffer buffer, RenderGraphHelper.PassData data)
{
_ColorBuffer = data.colorTargetHandle.Texture;
_DepthBuffer = data.depthTargetHandle.Texture;
// Unity bug
if (_ColorBuffer?.rt == null)
{
if (!_ErrorMissingColorTarget)
{
Debug.LogError($"Crest: Your current URP setup has a Unity bug which prevents underwater from rendering on this camera ({data.cameraData.camera.name}). It is too complicated for us to advise which combination of settings are the issue (sorry), but they will be on either the URP asset or renderer file.");
_ErrorMissingColorTarget = true;
}
return;
}
// TODO: renderingData.cameraData.cameraTargetDescriptor?
_UnderwaterEffectPass.ReAllocate(_ColorBuffer.rt.descriptor);
}
void Execute(ScriptableRenderContext context, CommandBuffer buffer, PassData data)
void Execute(ScriptableRenderContext context, CommandBuffer buffer, RenderGraphHelper.PassData data)
{
// Unity bug
if (_ColorBuffer?.rt == null)
{
return;
}
if (_Renderer.UseStencilBuffer)
{
_DepthBuffer = _CopyDepthBufferPass._DepthBufferCopy;
@@ -131,120 +141,13 @@ namespace WaveHarmonic.Crest
CommandBufferPool.Release(buffer);
}
#endif
// Renders transparent objects after the underwater effect. Using the correct
// shader, the above water portion of the object is rendered normally (in the
// transparent pass), and the below water portion is rendered here with underwater
// applied.
sealed partial class RenderObjectsWithoutFogPass : ScriptableRenderPass
{
FilteringSettings _FilteringSettings;
static readonly List<ShaderTagId> s_ShaderTagIdList = new()
{
new("SRPDefaultUnlit"),
new("UniversalForward"),
new("UniversalForwardOnly"),
new("LightweightForward"),
};
public RenderObjectsWithoutFogPass()
{
renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
_FilteringSettings = new(RenderQueueRange.transparent, 0);
}
#if UNITY_6000_0_OR_NEWER
void Execute(ScriptableRenderContext context, CommandBuffer buffer, PassData renderingData)
#else
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
#endif
{
_FilteringSettings.layerMask = s_Instance._Renderer._TransparentObjectLayers;
#if !UNITY_6000_0_OR_NEWER
var buffer = CommandBufferPool.Get("Crest Underwater Objects");
#endif
// Disable Unity's fog keywords as there is no option to ignore fog for the Shader Graph.
if (RenderSettings.fog)
{
switch (RenderSettings.fogMode)
{
case FogMode.Exponential:
buffer.DisableShaderKeyword("FOG_EXP");
break;
case FogMode.Linear:
buffer.DisableShaderKeyword("FOG_LINEAR");
break;
case FogMode.ExponentialSquared:
buffer.DisableShaderKeyword("FOG_EXP2");
break;
}
}
buffer.EnableShaderKeyword(UnderwaterRenderer.k_KeywordUnderwaterObjects);
// If we want anything to apply to DrawRenderers, it has to be executed before:
// https://docs.unity3d.com/ScriptReference/Rendering.ScriptableRenderContext.DrawRenderers.html
context.ExecuteCommandBuffer(buffer);
buffer.Clear();
#if UNITY_6000_0_OR_NEWER
var drawingSettings = RenderingUtils.CreateDrawingSettings
(
s_ShaderTagIdList,
renderingData.renderingData,
renderingData.cameraData,
renderingData.lightData,
SortingCriteria.CommonTransparent
);
var parameters = new RendererListParams(renderingData.cullResults, drawingSettings, _FilteringSettings);
var list = context.CreateRendererList(ref parameters);
buffer.DrawRendererList(list);
#else
var drawingSettings = CreateDrawingSettings
(
s_ShaderTagIdList,
ref renderingData,
SortingCriteria.CommonTransparent
);
context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref _FilteringSettings);
#endif
// Revert fog keywords.
if (RenderSettings.fog)
{
switch (RenderSettings.fogMode)
{
case FogMode.Exponential:
buffer.EnableShaderKeyword("FOG_EXP");
break;
case FogMode.Linear:
buffer.EnableShaderKeyword("FOG_LINEAR");
break;
case FogMode.ExponentialSquared:
buffer.EnableShaderKeyword("FOG_EXP2");
break;
}
}
buffer.DisableShaderKeyword(UnderwaterRenderer.k_KeywordUnderwaterObjects);
#if !UNITY_6000_0_OR_NEWER
context.ExecuteCommandBuffer(buffer);
CommandBufferPool.Release(buffer);
#endif
}
}
}
// Copies the depth buffer to avoid conflicts when using the stencil buffer.
sealed partial class CopyDepthBufferPassURP : ScriptableRenderPass
{
const string k_Name = "Crest Copy Depth Buffer";
RTHandle _ColorBuffer;
RTHandle _DepthBuffer;
public RTHandle _DepthBufferCopy;
@@ -264,9 +167,11 @@ namespace WaveHarmonic.Crest
descriptor.bindMS = descriptor.msaaSamples > 1;
#if UNITY_6000_0_OR_NEWER
RenderingUtils.ReAllocateHandleIfNeeded(ref _DepthBufferCopy, descriptor, FilterMode.Point, name: "Crest Copied Depth Buffer");
_ColorBuffer = data.colorTargetHandle;
_DepthBuffer = data.depthTargetHandle;
#else
RenderingUtils.ReAllocateIfNeeded(ref _DepthBufferCopy, descriptor, FilterMode.Point, name: "Crest Copied Depth Buffer");
_ColorBuffer = data.cameraData.renderer.cameraColorTargetHandle;
_DepthBuffer = data.cameraData.renderer.cameraDepthTargetHandle;
#endif
}
@@ -277,19 +182,26 @@ namespace WaveHarmonic.Crest
public override void Execute(ScriptableRenderContext context, ref RenderingData data)
#endif
{
// Just in case.
if (_ColorBuffer == null || _DepthBuffer == null)
{
return;
}
#if !UNITY_6000_0_OR_NEWER
var buffer = CommandBufferPool.Get(k_Name);
#endif
// Must clear even though we are overwriting or there will be strange artifacts on new writes.
// This could be a Unity bug and may be worth reporting.
buffer.SetRenderTarget(BuiltinRenderTextureType.None, _DepthBufferCopy);
buffer.ClearRenderTarget(RTClearFlags.Depth, Color.black, 1, 0);
// NOTE: previously we cleared the target depth first due to artifacts.
buffer.CopyTexture(_DepthBuffer.rt, _DepthBufferCopy.rt);
// Clear the stencil component just in case.
buffer.ClearRenderTarget(RTClearFlags.Stencil, Color.black, 1, 0);
// Previously we passed BuiltinRenderTextureType.None for color but this made the
// scene disappear in the scene view on DX11 only.
CoreUtils.SetRenderTarget(buffer, _ColorBuffer, _DepthBufferCopy, ClearFlag.Stencil);
// Required for Unity 6+.
CoreUtils.SetRenderTarget(buffer, _ColorBuffer, _DepthBuffer);
#if !UNITY_6000_0_OR_NEWER
context.ExecuteCommandBuffer(buffer);

View File

@@ -1,140 +1,2 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Experimental.Rendering;
namespace WaveHarmonic.Crest
{
sealed class UnderwaterMaskPass
{
readonly UnderwaterRenderer _Renderer;
#if d_CrestPortals
readonly Portals.PortalRenderer _Portals;
#endif
RTHandle _MaskTexture;
RTHandle _DepthTexture;
RenderTargetIdentifier _MaskTarget;
RenderTargetIdentifier _DepthTarget;
public UnderwaterMaskPass(UnderwaterRenderer renderer)
{
_Renderer = renderer;
#if d_CrestPortals
_Portals = renderer._Portals;
#endif
}
public void Allocate()
{
_MaskTexture = RTHandles.Alloc
(
scaleFactor: Vector2.one,
slices: TextureXR.slices,
dimension: TextureXR.dimension,
depthBufferBits: DepthBits.None,
colorFormat: GraphicsFormat.R16_SFloat,
enableRandomWrite: true,
useDynamicScale: true,
name: "_Crest_WaterMask"
);
_MaskTarget = new(_MaskTexture, mipLevel: 0, CubemapFace.Unknown, depthSlice: -1);
_DepthTexture = RTHandles.Alloc
(
scaleFactor: Vector2.one,
slices: TextureXR.slices,
dimension: TextureXR.dimension,
depthBufferBits: UnderwaterRenderer.k_DepthBits,
colorFormat: GraphicsFormat.None,
enableRandomWrite: false,
useDynamicScale: true,
name: "_Crest_WaterMaskDepth"
);
#if d_CrestPortals
// For HDRP we cannot allocate in OnEnable as RTHandle will complain.
if (_Portals.Active)
{
_Portals.Allocate();
}
#endif
_DepthTarget = new(_DepthTexture, mipLevel: 0, CubemapFace.Unknown, depthSlice: -1);
_Renderer.SetUpArtifactsShader();
}
// We should not have to reallocate, but URP will raise errors when an option like HDR is changed if we do not.
public void ReAllocate(RenderTextureDescriptor descriptor)
{
// Shared settings. Enabling MSAA might be a good idea except cannot enable random writes. Having a raster
// shader to remove artifacts is a workaround.
descriptor.bindMS = false;
descriptor.msaaSamples = 1;
descriptor.graphicsFormat = GraphicsFormat.None;
if (RenderPipelineCompatibilityHelper.ReAllocateIfNeeded(ref _DepthTexture, descriptor, name: "_Crest_WaterMaskDepth"))
{
_DepthTarget = new(_DepthTexture, mipLevel: 0, CubemapFace.Unknown, depthSlice: -1);
}
#if d_CrestPortals
if (_Portals.Active)
{
_Portals.ReAllocate(descriptor);
}
#endif
descriptor.graphicsFormat = GraphicsFormat.R16_SFloat;
descriptor.enableRandomWrite = true;
descriptor.depthBufferBits = 0;
if (RenderPipelineCompatibilityHelper.ReAllocateIfNeeded(ref _MaskTexture, descriptor, name: "_Crest_WaterMask"))
{
_MaskTarget = new(_MaskTexture, mipLevel: 0, CubemapFace.Unknown, depthSlice: -1);
}
}
public void Release()
{
_MaskTexture?.Release();
_DepthTexture?.Release();
#if d_CrestPortals
if (_Portals.Active)
{
_Portals.Release();
}
#endif
}
public void Execute(Camera camera, CommandBuffer buffer)
{
#if d_CrestPortals
// Populate water volume before mask so we can use the stencil.
if (_Portals.Active)
{
_Portals.RenderMask(camera, buffer, _Renderer._MaskMaterial);
_Portals.RenderStencil(buffer, _DepthTexture);
}
#endif
// For HDRP software dynamic scaling to work.
CoreUtils.SetRenderTarget(buffer, _MaskTexture, _DepthTexture);
Helpers.ScaleViewport(camera, buffer, _MaskTexture);
_Renderer.SetUpMask(buffer, _MaskTarget, _DepthTarget);
_Renderer.PopulateMask(buffer, camera);
var size = _MaskTexture.GetScaledSize(_MaskTexture.rtHandleProperties.currentViewportSize);
var descriptor = _MaskTexture.rt.descriptor;
descriptor.width = size.x; descriptor.height = size.y;
_Renderer.FixMaskArtefacts(buffer, descriptor, _MaskTarget);
}
}
}

View File

@@ -1,78 +1,2 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if d_UnityHDRP
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
namespace WaveHarmonic.Crest
{
sealed class UnderwaterMaskPassHDRP : CustomPass
{
const string k_Name = "Underwater Mask";
static UnderwaterRenderer s_Renderer;
static UnderwaterMaskPass s_UnderwaterMaskPass;
static UnderwaterMaskPassHDRP s_Instance;
GameObject _GameObject;
public static void Enable(UnderwaterRenderer renderer)
{
var gameObject = CustomPassHelpers.CreateOrUpdate
(
parent: renderer._Water.Container.transform,
k_Name,
hide: !renderer._Water._Debug._ShowHiddenObjects
);
CustomPassHelpers.CreateOrUpdate
(
gameObject,
ref s_Instance,
k_Name,
CustomPassInjectionPoint.BeforeRendering
);
s_Instance._GameObject = gameObject;
s_Renderer = renderer;
s_UnderwaterMaskPass = new(renderer);
}
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 Setup(ScriptableRenderContext renderContext, CommandBuffer cmd)
{
s_UnderwaterMaskPass.Allocate();
}
protected override void Cleanup()
{
s_UnderwaterMaskPass?.Release();
}
protected override void Execute(CustomPassContext context)
{
var camera = context.hdCamera.camera;
if (!s_Renderer.ShouldRender(camera, UnderwaterRenderer.Pass.Mask))
{
return;
}
s_UnderwaterMaskPass.Execute(camera, context.cmd);
}
}
}
#endif // d_UnityHDRP

View File

@@ -1,106 +1,2 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if d_UnityURP
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace WaveHarmonic.Crest
{
sealed partial class UnderwaterMaskPassURP : ScriptableRenderPass
{
const string k_Name = "Crest Underwater Mask";
static UnderwaterMaskPassURP s_Instance;
UnderwaterRenderer _Renderer;
UnderwaterMaskPass _UnderwaterMaskPass;
public UnderwaterMaskPassURP()
{
// Will always execute and matrices will be ready.
renderPassEvent = RenderPassEvent.BeforeRenderingPrePasses;
}
public static void Enable(UnderwaterRenderer renderer)
{
s_Instance ??= new();
s_Instance._Renderer = renderer;
RenderPipelineManager.beginCameraRendering -= s_Instance.EnqueuePass;
RenderPipelineManager.beginCameraRendering += s_Instance.EnqueuePass;
RenderPipelineManager.activeRenderPipelineTypeChanged -= Disable;
RenderPipelineManager.activeRenderPipelineTypeChanged += Disable;
}
public static void Disable()
{
if (s_Instance != null) RenderPipelineManager.beginCameraRendering -= s_Instance.EnqueuePass;
RenderPipelineManager.activeRenderPipelineTypeChanged -= Disable;
s_Instance?._UnderwaterMaskPass?.Release();
s_Instance = null;
}
void EnqueuePass(ScriptableRenderContext context, Camera camera)
{
if (!_Renderer.ShouldRender(camera, UnderwaterRenderer.Pass.Mask))
{
return;
}
var renderer = camera.GetUniversalAdditionalCameraData().scriptableRenderer;
#if UNITY_EDITOR
if (renderer == null) return;
#endif
if (_UnderwaterMaskPass == null)
{
_UnderwaterMaskPass = new(_Renderer);
_UnderwaterMaskPass.Allocate();
}
// Enqueue the pass. This happens every frame.
renderer.EnqueuePass(this);
}
#if UNITY_6000_0_OR_NEWER
class PassData
{
public UniversalCameraData _CameraData;
public UnderwaterMaskPass _UnderwaterMaskPass;
}
public override void RecordRenderGraph(UnityEngine.Rendering.RenderGraphModule.RenderGraph graph, ContextContainer frame)
{
using (var builder = graph.AddUnsafePass<PassData>(k_Name, out var data))
{
builder.AllowPassCulling(false);
data._CameraData = frame.Get<UniversalCameraData>();
data._UnderwaterMaskPass = _UnderwaterMaskPass;
builder.SetRenderFunc<PassData>((data, context) =>
{
var buffer = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
data._UnderwaterMaskPass.ReAllocate(data._CameraData.cameraTargetDescriptor);
data._UnderwaterMaskPass.Execute(data._CameraData.camera, buffer);
});
}
}
[System.Obsolete]
#endif
public override void Execute(ScriptableRenderContext context, ref RenderingData data)
{
var buffer = CommandBufferPool.Get(k_Name);
_UnderwaterMaskPass.ReAllocate(data.cameraData.cameraTargetDescriptor);
_UnderwaterMaskPass.Execute(data.cameraData.camera, buffer);
context.ExecuteCommandBuffer(buffer);
CommandBufferPool.Release(buffer);
}
}
}
#endif // d_UnityURP

View File

@@ -1,4 +1,4 @@
// Crest Water System
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if UNITY_EDITOR

View File

@@ -9,16 +9,16 @@ namespace WaveHarmonic.Crest
{
partial class UnderwaterRenderer
{
const string k_KeywordFullScreenEffect = "_FULL_SCREEN_EFFECT";
internal const string k_ShaderNameEffect = "Crest/Underwater";
internal const string k_DrawVolume = "Crest.DrawWater/Volume";
const string k_KeywordDebugVisualizeMask = "_DEBUG_VISUALIZE_MASK";
const string k_KeywordDebugVisualizeStencil = "_DEBUG_VISUALIZE_STENCIL";
internal const string k_KeywordUnderwaterObjects = "CREST_UNDERWATER_OBJECTS_PASS";
internal const string k_SampleSphericalHarmonicsMarker = "Crest.UnderwaterRenderer.SampleSphericalHarmonics";
static readonly Unity.Profiling.ProfilerMarker s_SampleSphericalHarmonicsMarker = new(k_SampleSphericalHarmonicsMarker);
static partial class ShaderIDs
{
// Local
public static readonly int s_HorizonNormal = Shader.PropertyToID("_Crest_HorizonNormal");
// Global
public static readonly int s_CameraColorTexture = Shader.PropertyToID("_Crest_CameraColorTexture");
public static readonly int s_WaterVolumeStencil = Shader.PropertyToID("_Crest_WaterVolumeStencil");
@@ -26,9 +26,10 @@ namespace WaveHarmonic.Crest
public static readonly int s_ExtinctionMultiplier = Shader.PropertyToID("_Crest_ExtinctionMultiplier");
public static readonly int s_UnderwaterEnvironmentalLightingWeight = Shader.PropertyToID("_Crest_UnderwaterEnvironmentalLightingWeight");
// Built-ins
public static readonly int s_WorldSpaceLightPos0 = Shader.PropertyToID("_WorldSpaceLightPos0");
public static readonly int s_LightColor0 = Shader.PropertyToID("_LightColor0");
public static readonly int s_OutScatteringFactor = Shader.PropertyToID("_Crest_OutScatteringFactor");
public static readonly int s_OutScatteringExtinctionFactor = Shader.PropertyToID("_Crest_OutScatteringExtinctionFactor");
public static readonly int s_SunBoost = Shader.PropertyToID("_Crest_SunBoost");
public static readonly int s_DataSliceOffset = Shader.PropertyToID("_Crest_DataSliceOffset");
}
@@ -43,6 +44,7 @@ namespace WaveHarmonic.Crest
Material _CurrentWaterMaterial;
readonly UnderwaterSphericalHarmonicsData _SphericalHarmonicsData = new();
System.Action<CommandBuffer> _CopyColor;
System.Action<CommandBuffer> _SetRenderTargetToBackBuffers;
RenderTargetIdentifier _ColorTarget = new
(
@@ -66,12 +68,20 @@ namespace WaveHarmonic.Crest
-1
);
// Requested the temporary color texture.
internal bool _NeedsColorTexture;
sealed class UnderwaterSphericalHarmonicsData
{
internal Color[] _AmbientLighting = new Color[1];
internal Vector3[] _DirectionsSH = { new(0.0f, 0.0f, 0.0f) };
}
void SetRenderTargetToBackBuffers(CommandBuffer commands)
{
commands.SetRenderTarget(_ColorTarget);
}
void CopyColorTexture(CommandBuffer buffer)
{
// Use blit instead of CopyTexture as it will smooth out issues with format
@@ -92,38 +102,28 @@ namespace WaveHarmonic.Crest
{
_EffectCommandBuffer ??= new()
{
name = "Underwater Pass",
name = k_DrawVolume,
};
_CopyColor ??= new(CopyColorTexture);
_SetRenderTargetToBackBuffers ??= new(SetRenderTargetToBackBuffers);
}
void OnPreRenderUnderwaterEffect(Camera camera)
{
#if UNITY_EDITOR
// Do not use this to prevent the mask from rendering due to portals and volumes feature.
if (!IsFogEnabledForEditorCamera(camera))
{
_EffectCommandBuffer?.Clear();
return;
}
#endif
var descriptor = XRHelpers.GetRenderTextureDescriptor(camera);
var descriptor = Rendering.BIRP.GetCameraTargetDescriptor(camera, _Water.FrameBufferFormatOverride);
descriptor.useDynamicScale = camera.allowDynamicResolution;
// Format must be correct for CopyTexture to work. Hopefully this is good enough.
if (camera.allowHDR && QualitySettings.activeColorSpace == ColorSpace.Linear)
{
descriptor.graphicsFormat = SystemInfo.GetGraphicsFormat(_Water.LikelyFrameBufferFormat);
}
UpdateEffectMaterial(camera, _FirstRender);
UpdateEffectMaterial(camera);
_EffectCommandBuffer.Clear();
// No need to clear as Blit will overwrite everything.
_EffectCommandBuffer.GetTemporaryRT(ShaderIDs.s_CameraColorTexture, descriptor);
if (!RenderBeforeTransparency || _NeedsColorTexture)
{
// No need to clear as Blit will overwrite everything.
_EffectCommandBuffer.GetTemporaryRT(ShaderIDs.s_CameraColorTexture, descriptor);
_EffectCommandBuffer.SetGlobalTexture(ShaderIDs.s_CameraColorTexture, _ColorCopyTarget);
}
var sun = RenderSettings.sun;
if (sun != null)
@@ -131,15 +131,16 @@ namespace WaveHarmonic.Crest
// Unity does not set up lighting for us so we will get the last value which could incorrect.
// SetGlobalColor is just an alias for SetGlobalVector (no color space conversion like Material.SetColor):
// https://docs.unity3d.com/2017.4/Documentation/ScriptReference/Shader.SetGlobalColor.html
_EffectCommandBuffer.SetGlobalVector(ShaderIDs.s_LightColor0, sun.FinalColor());
_EffectCommandBuffer.SetGlobalVector(ShaderIDs.s_WorldSpaceLightPos0, -sun.transform.forward);
_EffectCommandBuffer.SetGlobalVector(Crest.ShaderIDs.Unity.s_LightColor0, sun.FinalColor());
_EffectCommandBuffer.SetGlobalVector(Crest.ShaderIDs.Unity.s_WorldSpaceLightPos0, -sun.transform.forward);
_EffectCommandBuffer.SetShaderKeyword("DIRECTIONAL_COOKIE", sun.cookie != null);
}
// Create a separate stencil buffer context by copying the depth texture.
if (UseStencilBuffer)
{
descriptor.colorFormat = RenderTextureFormat.Depth;
descriptor.depthBufferBits = (int)k_DepthBits;
descriptor.depthBufferBits = (int)Helpers.k_DepthBits;
// bindMS is necessary in this case for depth.
descriptor.SetMSAASamples(camera);
descriptor.bindMS = descriptor.msaaSamples > 1;
@@ -152,150 +153,128 @@ namespace WaveHarmonic.Crest
if (Helpers.IsMSAAEnabled(camera))
{
// Blit with a depth write shader to populate the depth buffer.
Helpers.Blit(_EffectCommandBuffer, _DepthStencilTarget, Helpers.UtilityMaterial, (int)Helpers.UtilityPass.CopyDepth);
Helpers.Blit(_EffectCommandBuffer, _DepthStencilTarget, Rendering.BIRP.UtilityMaterial, (int)Rendering.BIRP.UtilityPass.CopyDepth);
}
else
{
// Copy depth then clear stencil.
// Copy depth texture. Since this is not depth buffer, no need to clear stencil.
// SRPs copy the depth buffer, because they can.
_EffectCommandBuffer.CopyTexture(BuiltinRenderTextureType.Depth, _DepthStencilTarget);
Helpers.Blit(_EffectCommandBuffer, _DepthStencilTarget, Helpers.UtilityMaterial, (int)Helpers.UtilityPass.ClearStencil);
CoreUtils.SetRenderTarget(_EffectCommandBuffer, _DepthStencilTarget);
}
if (RenderBeforeTransparency)
{
_EffectCommandBuffer.SetRenderTarget(BuiltinRenderTextureType.CameraTarget, _DepthStencilTarget);
}
}
CopyColorTexture(_EffectCommandBuffer);
if (!RenderBeforeTransparency)
{
CopyColorTexture(_EffectCommandBuffer);
}
_EffectCommandBuffer.SetGlobalTexture(ShaderIDs.s_CameraColorTexture, _ColorCopyTarget);
ExecuteEffect(camera, _EffectCommandBuffer, _CopyColor, _SetRenderTargetToBackBuffers);
ExecuteEffect(camera, _EffectCommandBuffer, _CopyColor);
if (!RenderBeforeTransparency || _NeedsColorTexture)
{
_EffectCommandBuffer.ReleaseTemporaryRT(ShaderIDs.s_CameraColorTexture);
}
_EffectCommandBuffer.ReleaseTemporaryRT(ShaderIDs.s_CameraColorTexture);
if (UseStencilBuffer)
{
_EffectCommandBuffer.ReleaseTemporaryRT(ShaderIDs.s_WaterVolumeStencil);
}
}
internal void ExecuteEffect(Camera camera, CommandBuffer buffer, System.Action<CommandBuffer> copyColor, MaterialPropertyBlock properties = null)
internal void ExecuteEffect(Camera camera, CommandBuffer buffer, System.Action<CommandBuffer> copyColor, System.Action<CommandBuffer> resetRenderTargets, MaterialPropertyBlock properties = null)
{
if (camera.cameraType == CameraType.Reflection)
{
buffer.DrawProcedural
(
Matrix4x4.identity,
_VolumeMaterial,
shaderPass: (int)EffectPass.Reflections,
MeshTopology.Triangles,
vertexCount: 3,
instanceCount: 1,
properties
);
}
var isFullScreenRequired = true;
#if d_CrestPortals
else if (_Portals.Active && _Portals.Mode != Portals.PortalMode.Tunnel)
if (_Portals.Active)
{
_Portals.RenderEffect(camera, buffer, _VolumeMaterial, copyColor, properties);
isFullScreenRequired = _Portals.RenderEffect(camera, buffer, _VolumeMaterial, copyColor, resetRenderTargets, properties);
}
#endif
else
if (!isFullScreenRequired)
{
buffer.DrawProcedural
(
Matrix4x4.identity,
_VolumeMaterial,
shaderPass: (int)EffectPass.FullScreen,
MeshTopology.Triangles,
vertexCount: 3,
instanceCount: 1,
properties
);
return;
}
buffer.DrawProcedural
(
Matrix4x4.identity,
_VolumeMaterial,
shaderPass: (int)(camera.cameraType == CameraType.Reflection ? EffectPass.Reflections : EffectPass.FullScreen),
MeshTopology.Triangles,
vertexCount: 3,
instanceCount: 1,
properties
);
}
internal static void UpdateGlobals(Material waterMaterial)
internal static void UpdateGlobals(Material source)
{
// We will have the wrong color values if we do not use linear:
// https://forum.unity.com/threads/fragment-shader-output-colour-has-incorrect-values-when-hardcoded.377657/
// _CrestAbsorption is already set as global in Water Renderer.
Shader.SetGlobalColor(WaterRenderer.ShaderIDs.s_Scattering, waterMaterial.GetColor(WaterRenderer.ShaderIDs.s_Scattering).MaybeLinear());
Shader.SetGlobalFloat(WaterRenderer.ShaderIDs.s_Anisotropy, waterMaterial.GetFloat(WaterRenderer.ShaderIDs.s_Anisotropy));
Shader.SetGlobalColor(WaterRenderer.ShaderIDs.s_Scattering, source.GetColor(WaterRenderer.ShaderIDs.s_Scattering).MaybeLinear());
Shader.SetGlobalFloat(WaterRenderer.ShaderIDs.s_Anisotropy, source.GetFloat(WaterRenderer.ShaderIDs.s_Anisotropy));
Shader.SetGlobalFloat(WaterRenderer.ShaderIDs.s_AmbientTerm, source.GetFloat(WaterRenderer.ShaderIDs.s_AmbientTerm));
Shader.SetGlobalFloat(WaterRenderer.ShaderIDs.s_DirectTerm, source.GetFloat(WaterRenderer.ShaderIDs.s_DirectTerm));
Shader.SetGlobalFloat(WaterRenderer.ShaderIDs.s_ShadowsAffectsAmbientFactor, source.GetFloat(WaterRenderer.ShaderIDs.s_ShadowsAffectsAmbientFactor));
Shader.SetGlobalFloat(ShaderIDs.s_ExtinctionMultiplier, source.GetFloat(ShaderIDs.s_ExtinctionMultiplier));
Shader.SetGlobalFloat(ShaderIDs.s_OutScatteringFactor, source.GetFloat(ShaderIDs.s_OutScatteringFactor));
Shader.SetGlobalFloat(ShaderIDs.s_OutScatteringExtinctionFactor, source.GetFloat(ShaderIDs.s_OutScatteringExtinctionFactor));
Shader.SetGlobalFloat(ShaderIDs.s_SunBoost, source.GetFloat(ShaderIDs.s_SunBoost));
Shader.SetGlobalInteger(ShaderIDs.s_DataSliceOffset, source.GetInteger(ShaderIDs.s_DataSliceOffset));
}
internal void UpdateEffectMaterial(Camera camera, bool isFirstRender)
internal void UpdateEffectMaterial(Camera camera)
{
// Copy water material parameters to underwater material.
// WBs can change the material per camera, so disable optimization.
if (_MaterialLastUpdatedFrame < Time.frameCount || WaterBody.WaterBodies.Count > 0)
{
var material = _SurfaceMaterial;
if (_CopyWaterMaterialParametersEachFrame || isFirstRender || material != _CurrentWaterMaterial)
if (_CopyWaterMaterialParametersEachFrame || _SurfaceMaterial != _CurrentWaterMaterial)
{
_CurrentWaterMaterial = material;
_CurrentWaterMaterial = _SurfaceMaterial;
if (material != null)
if (_SurfaceMaterial != null)
{
_VolumeMaterial.CopyMatchingPropertiesFromMaterial(material);
_VolumeMaterial.CopyMatchingPropertiesFromMaterial(_SurfaceMaterial);
if (_EnableShaderAPI)
AfterCopyMaterial?.Invoke(_Water, _VolumeMaterial);
// Make volume properties available to surface and meniscus.
if (RenderBeforeTransparency)
{
UpdateGlobals(material);
UpdateGlobals(_VolumeMaterial);
}
}
}
// Enabling/disabling keywords each frame don't seem to have large measurable overhead
_VolumeMaterial.SetKeyword(k_KeywordDebugVisualizeMask, _Debug._VisualizeMask);
_VolumeMaterial.SetKeyword(k_KeywordDebugVisualizeStencil, _Debug._VisualizeStencil);
// We use this for caustics to get the displacement.
_VolumeMaterial.SetInteger(Lod.ShaderIDs.s_LodIndex, 0);
_MaterialLastUpdatedFrame = Time.frameCount;
}
// Enabling/disabling keywords each frame don't seem to have large measurable overhead
_VolumeMaterial.SetKeyword(k_KeywordDebugVisualizeMask, _Debug._VisualizeMask);
_VolumeMaterial.SetKeyword(k_KeywordDebugVisualizeStencil, _Debug._VisualizeStencil);
// We use this for caustics to get the displacement.
_VolumeMaterial.SetInteger(Lod.ShaderIDs.s_LodIndex, 0);
if (!Portaled && camera.cameraType != CameraType.Reflection)
// Not applicable to reflection pass.
if (camera.cameraType != CameraType.Reflection)
{
var seaLevel = _Water.SeaLevel;
// We don't both setting the horizon value if we know we are going to be having to apply the effect
// full-screen anyway.
var forceFullShader = _Water.ViewerHeightAboveWater < -2f;
if (!forceFullShader)
{
var maxWaterVerticalDisplacement = _Water.MaximumVerticalDisplacement * 0.5f;
var cameraYPosition = camera.transform.position.y;
float nearPlaneFrustumWorldHeight;
{
var current = camera.ViewportToWorldPoint(new(0f, 0f, camera.nearClipPlane)).y;
float maxY = current, minY = current;
current = camera.ViewportToWorldPoint(new(0f, 1f, camera.nearClipPlane)).y;
maxY = Mathf.Max(maxY, current);
minY = Mathf.Min(minY, current);
current = camera.ViewportToWorldPoint(new(1f, 0f, camera.nearClipPlane)).y;
maxY = Mathf.Max(maxY, current);
minY = Mathf.Min(minY, current);
current = camera.ViewportToWorldPoint(new(1f, 1f, camera.nearClipPlane)).y;
maxY = Mathf.Max(maxY, current);
minY = Mathf.Min(minY, current);
nearPlaneFrustumWorldHeight = maxY - minY;
}
forceFullShader = (cameraYPosition + nearPlaneFrustumWorldHeight + maxWaterVerticalDisplacement) <= seaLevel;
}
_VolumeMaterial.SetKeyword(k_KeywordFullScreenEffect, forceFullShader);
}
// Project water normal onto camera plane.
{
var projectedNormal = new Vector2
(
Vector3.Dot(Vector3.up, camera.transform.right),
Vector3.Dot(Vector3.up, camera.transform.up)
);
_VolumeMaterial.SetVector(ShaderIDs.s_HorizonNormal, projectedNormal);
// Skip work if camera is far enough below the surface.
var forceFullShader = !_Water.Surface.Enabled || (_Water._ViewerHeightAboveWaterPerCamera < -8f && !Portaled);
_VolumeMaterial.SetKeyword("d_Crest_NoMaskColor", forceFullShader);
_VolumeMaterial.SetKeyword("d_Crest_NoMaskDepth", !_Water.Surface.Enabled || RenderBeforeTransparency);
}
// Compute ambient lighting SH.
@@ -304,11 +283,11 @@ namespace WaveHarmonic.Crest
// at different position, as this would then thrash it and negate the priming functionality. We could create a dummy invis GO
// with a dummy Renderer which might be enough, but this is hacky enough that we'll wait for it to become a problem
// rather than add a pre-emptive hack.
UnityEngine.Profiling.Profiler.BeginSample("Crest: Underwater Sample Spherical Harmonics");
s_SampleSphericalHarmonicsMarker.Begin(_Water);
LightProbes.GetInterpolatedProbe(camera.transform.position, null, out var sphericalHarmonicsL2);
sphericalHarmonicsL2.Evaluate(_SphericalHarmonicsData._DirectionsSH, _SphericalHarmonicsData._AmbientLighting);
Helpers.SetShaderVector(_VolumeMaterial, ShaderIDs.s_AmbientLighting, _SphericalHarmonicsData._AmbientLighting[0], _EnableShaderAPI);
UnityEngine.Profiling.Profiler.EndSample();
Helpers.SetShaderVector(_VolumeMaterial, ShaderIDs.s_AmbientLighting, _SphericalHarmonicsData._AmbientLighting[0], RenderBeforeTransparency);
s_SampleSphericalHarmonicsMarker.End();
}
}
}

View File

@@ -58,6 +58,14 @@ namespace WaveHarmonic.Crest
return;
}
#if UNITY_EDITOR
// Only repaint, otherwise changes might persist.
if (Event.current.type != EventType.Repaint)
{
return;
}
#endif
// Restore lighting settings.
if (_EnvironmentalLight != null) _EnvironmentalLight.intensity = _EnvironmentalLightIntensity;
_EnvironmentalLight = null;
@@ -77,7 +85,15 @@ namespace WaveHarmonic.Crest
return;
}
if (!_Water.Material.HasColor(WaterRenderer.ShaderIDs.s_AbsorptionColor))
#if UNITY_EDITOR
// Only repaint, otherwise changes might persist.
if (Event.current.type != EventType.Repaint)
{
return;
}
#endif
if (!_Water.Surface.Material.HasColor(WaterRenderer.ShaderIDs.s_AbsorptionColor))
{
return;
}
@@ -94,7 +110,13 @@ namespace WaveHarmonic.Crest
var density = extinction;
_EnvironmentalAverageDensity = (density.x + density.y + density.z) / 3f;
var multiplier = Mathf.Exp(_EnvironmentalAverageDensity * Mathf.Min(height * k_DepthOutScattering, 0f) * _EnvironmentalLightingWeight);
var outScatteringFactor = 1f;
if (_VolumeMaterial.HasFloat(ShaderIDs.s_OutScatteringFactor))
{
outScatteringFactor = _VolumeMaterial.GetFloat(ShaderIDs.s_OutScatteringFactor);
}
var multiplier = Mathf.Exp(_EnvironmentalAverageDensity * Mathf.Min(height * k_DepthOutScattering * outScatteringFactor, 0f) * _EnvironmentalLightingWeight);
// Darken environmental lighting when viewer underwater.
if (_EnvironmentalLight != null)

View File

@@ -8,94 +8,49 @@ namespace WaveHarmonic.Crest
{
partial class UnderwaterRenderer
{
bool _HasMaskCommandBuffersBeenRegistered;
bool _HasEffectCommandBuffersBeenRegistered;
void OnEnableLegacy()
{
SetupMask();
OnEnableMask();
SetupUnderwaterEffect();
// Handle this internally rather than relying on Water Renderer.
Camera.onPreRender -= OnBeforeLegacyRender;
Camera.onPreRender += OnBeforeLegacyRender;
Camera.onPostRender -= OnAfterLegacyRender;
Camera.onPostRender += OnAfterLegacyRender;
RenderPipelineManager.activeRenderPipelineTypeChanged -= OnDisableLegacy;
RenderPipelineManager.activeRenderPipelineTypeChanged += OnDisableLegacy;
}
void OnDisableLegacy()
{
Camera.onPreRender -= OnBeforeLegacyRender;
Camera.onPostRender -= OnAfterLegacyRender;
RenderPipelineManager.activeRenderPipelineTypeChanged -= OnDisableLegacy;
OnDisableMask();
}
internal void LateUpdate()
{
if (!Active)
{
return;
}
if (!RenderPipelineHelper.IsLegacy)
{
return;
}
Helpers.SetGlobalKeyword("CREST_UNDERWATER_BEFORE_TRANSPARENT", _EnableShaderAPI);
}
// Listening to OnPreCull. Camera must have underwater layer.
void OnBeforeLegacyRender(Camera camera)
{
XRHelpers.Update(camera);
XRHelpers.SetInverseViewProjectionMatrix(camera);
if (ShouldRender(camera, Pass.Mask))
{
// It could be either one event.
camera.AddCommandBuffer(CameraEvent.BeforeGBuffer, _MaskCommandBuffer);
camera.AddCommandBuffer(CameraEvent.BeforeDepthTexture, _MaskCommandBuffer);
OnPreRenderMask(camera);
_HasMaskCommandBuffersBeenRegistered = true;
}
if (ShouldRender(camera, Pass.Effect))
{
var @event = _EnableShaderAPI ? CameraEvent.BeforeForwardAlpha : CameraEvent.AfterForwardAlpha;
_Water.UpdateMatrices(camera);
_Water.OnBeginCameraOpaqueTexture(camera);
var @event = RenderBeforeTransparency ? CameraEvent.BeforeForwardAlpha : CameraEvent.AfterForwardAlpha;
camera.AddCommandBuffer(@event, _EffectCommandBuffer);
OnPreRenderUnderwaterEffect(camera);
_HasEffectCommandBuffersBeenRegistered = true;
}
_FirstRender = false;
}
void OnAfterLegacyRender(Camera camera)
{
if (_HasMaskCommandBuffersBeenRegistered)
{
// It could be either one event.
camera.RemoveCommandBuffer(CameraEvent.BeforeGBuffer, _MaskCommandBuffer);
camera.RemoveCommandBuffer(CameraEvent.BeforeDepthTexture, _MaskCommandBuffer);
_MaskCommandBuffer?.Clear();
}
if (_HasEffectCommandBuffersBeenRegistered)
{
var @event = _EnableShaderAPI ? CameraEvent.BeforeForwardAlpha : CameraEvent.AfterForwardAlpha;
var @event = RenderBeforeTransparency ? CameraEvent.BeforeForwardAlpha : CameraEvent.AfterForwardAlpha;
camera.RemoveCommandBuffer(@event, _EffectCommandBuffer);
_EffectCommandBuffer?.Clear();
}
_HasMaskCommandBuffersBeenRegistered = false;
_HasEffectCommandBuffersBeenRegistered = false;
_Water.OnEndCameraOpaqueTexture(camera);
OnAfterCameraRender(camera);
_HasEffectCommandBuffersBeenRegistered = false;
}
}
}

View File

@@ -2,34 +2,21 @@
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest
{
partial class UnderwaterRenderer
partial class UnderwaterRenderer : MaskRenderer.IMaskReceiver, MaskRenderer.IMaskProvider
{
// Adapted from:
// Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderer.cs
#if UNITY_SWITCH || UNITY_ANDROID || UNITY_EMBEDDED_LINUX || UNITY_QNX
internal const GraphicsFormat k_DepthStencilFormat = GraphicsFormat.D24_UNorm_S8_UInt;
internal const int k_DepthBufferBits = 24;
internal const DepthBits k_DepthBits = DepthBits.Depth24;
#else
internal const GraphicsFormat k_DepthStencilFormat = GraphicsFormat.D32_SFloat_S8_UInt;
internal const int k_DepthBufferBits = 32;
internal const DepthBits k_DepthBits = DepthBits.Depth32;
#endif
internal const string k_DrawMask = "Crest.DrawMask";
const string k_DrawMaskHorizon = "Horizon";
const string k_DrawMaskSurface = "Surface";
internal const int k_VolumeMaskQueue = 1000;
internal const int k_ShaderPassWaterSurfaceMask = 0;
internal const int k_ShaderPassWaterSurfaceDepth = 1;
internal const int k_ShaderPassWaterHorizonMask = 2;
// NOTE: Must match CREST_MASK_BELOW_SURFACE in Constants.hlsl.
const float k_MaskBelowSurface = -1f;
// NOTE: Must match CREST_MASK_BELOW_SURFACE_CULLED in Constants.hlsl.
const float k_MaskBelowSurfaceCull = -2f;
internal const int k_ShaderPassWaterHorizonMask = 0;
internal const string k_ComputeShaderKernelFillMaskArtefacts = "FillMaskArtefacts";
@@ -37,25 +24,10 @@ namespace WaveHarmonic.Crest
{
// Local
public static readonly int s_FarPlaneOffset = Shader.PropertyToID("_Crest_FarPlaneOffset");
public static readonly int s_MaskBelowSurface = Shader.PropertyToID("_Crest_MaskBelowSurface");
// Global
public static readonly int s_WaterMaskTexture = Shader.PropertyToID("_Crest_WaterMaskTexture");
public static readonly int s_WaterMaskDepthTexture = Shader.PropertyToID("_Crest_WaterMaskDepthTexture");
public static readonly int s_StencilRef = Shader.PropertyToID("_StencilRef");
}
internal Material _MaskMaterial;
internal RenderTargetIdentifier _MaskTarget;
internal RenderTargetIdentifier _DepthTarget;
internal readonly Plane[] _CameraFrustumPlanes = new Plane[6];
CommandBuffer _MaskCommandBuffer;
internal RenderTexture _MaskRT;
RenderTexture _DepthRT;
internal Material _HorizonMaskMaterial;
ComputeShader _ArtifactsShader;
bool _ArtifactsShaderInitialized;
@@ -63,30 +35,19 @@ namespace WaveHarmonic.Crest
uint _ArtifactsThreadGroupSizeX;
uint _ArtifactsThreadGroupSizeY;
void SetupMask()
{
_MaskCommandBuffer ??= new()
{
name = "Crest: Underwater Mask",
};
}
internal void OnEnableMask()
{
// Create a reference to handle the RT. The RT properties will be replaced with a descriptor before the
// native object is created, and since it is lazy it is near zero cost.
Helpers.CreateRenderTargetTextureReference(ref _MaskRT, ref _MaskTarget);
_MaskRT.name = "_Crest_WaterMaskTexture";
Helpers.CreateRenderTargetTextureReference(ref _DepthRT, ref _DepthTarget);
_DepthRT.name = "_Crest_WaterMaskDepthTexture";
_Water._Mask.Add(this);
_Water._Mask.Add(k_VolumeMaskQueue, this);
SetUpArtifactsShader();
}
internal void OnDisableMask()
{
if (_MaskRT != null) _MaskRT.Release();
if (_DepthRT != null) _DepthRT.Release();
if (_Water == null) return;
_Water._Mask?.Remove(this as MaskRenderer.IMaskReceiver);
_Water._Mask?.Remove(this as MaskRenderer.IMaskProvider);
}
internal void SetUpArtifactsShader()
@@ -108,70 +69,69 @@ namespace WaveHarmonic.Crest
_ArtifactsShaderInitialized = true;
}
internal void SetUpMaskTextures(RenderTextureDescriptor descriptor)
void MaskRenderer.IMaskProvider.OnMaskPass(CommandBuffer commands, Camera camera, MaskRenderer mask)
{
if (!Helpers.RenderTargetTextureNeedsUpdating(_MaskRT, descriptor))
var color = mask.ColorRTH;
var depth = mask.DepthRTH;
var size = color.GetScaledSize(color.rtHandleProperties.currentViewportSize);
var descriptor = color.rt.descriptor;
descriptor.width = size.x; descriptor.height = size.y;
if (UseLegacyMask)
{
// Portals changes the target.
// When using the stencil we are already clearing depth and do not want to clear the stencil too. Clear
// color only when using the stencil as the horizon effectively clears it when not using it.
CoreUtils.SetRenderTarget(commands, color, depth, UseStencilBuffer ? ClearFlag.Color : ClearFlag.DepthStencil);
Helpers.ScaleViewport(camera, commands, color);
PopulateMask(commands, camera);
FixMaskArtefacts(commands, descriptor, mask._ColorRTI);
}
// Portals have their own fitted to the portal bounds.
else
#if d_CrestPortals
if (!Portaled || _Water.Portals.RequiresFullScreenMask)
#endif
{
RenderLineMask(commands, camera, mask.ColorRT.descriptor, mask._ColorRTI);
}
}
internal void RenderLineMask(CommandBuffer buffer, Camera camera, RenderTextureDescriptor descriptor, RenderTargetIdentifier target)
{
if (!_Water.Surface.Enabled)
{
return;
}
// This will disable MSAA for our textures as MSAA will break sampling later on. This looks safe to do as
// Unity's CopyDepthPass does the same, but a possible better way or supporting MSAA is worth looking into.
descriptor.msaaSamples = 1;
// @Memory: We could investigate making this an 8-bit texture instead to reduce GPU memory usage.
// @Memory: We could potentially try a half resolution mask as the mensicus could mask resolution issues.
// Intel iGPU for Metal and DirectX both had issues with R16. 2021.11.18
descriptor.colorFormat = Helpers.IsIntelGPU() ? RenderTextureFormat.RFloat : RenderTextureFormat.RHalf;
descriptor.depthBufferBits = 0;
descriptor.enableRandomWrite = true;
_MaskRT.Release();
_MaskRT.descriptor = descriptor;
descriptor.colorFormat = RenderTextureFormat.Depth;
descriptor.depthBufferBits = (int)k_DepthBits;
descriptor.enableRandomWrite = false;
_DepthRT.Release();
_DepthRT.descriptor = descriptor;
}
void OnPreRenderMask(Camera camera)
{
_MaskCommandBuffer.Clear();
var descriptor = XRHelpers.GetRenderTextureDescriptor(camera);
descriptor.useDynamicScale = camera.allowDynamicResolution;
// Keywords and other things.
SetUpMaskTextures(descriptor);
var keep = false;
#if d_CrestPortals
// Populate water volume before mask so we can use the stencil.
if (_Portals.Active)
if (_Water.Portals.Active)
{
_Portals.ReAllocate(descriptor);
_Portals.RenderMask(camera, _MaskCommandBuffer, _MaskMaterial);
_Portals.RenderStencil(_MaskCommandBuffer, _DepthRT, _DepthTarget);
keep = _Water.Portals.RenderLineMask(buffer, target);
}
#endif
_MaskCommandBuffer.SetRenderTarget(_MaskTarget, _DepthTarget);
SetUpMask(_MaskCommandBuffer, _MaskTarget, _DepthTarget);
PopulateMask(_MaskCommandBuffer, camera);
var wrapper = new PropertyWrapperCompute(buffer, WaterResources.Instance.Compute._Mask, (int)RenderPipelineHelper.RenderPipeline);
FixMaskArtefacts(_MaskCommandBuffer, descriptor, _MaskTarget);
}
var parameters = _Water.Surface._SurfaceDataParameters;
internal void SetUpMask(CommandBuffer buffer, RenderTargetIdentifier maskTarget, RenderTargetIdentifier depthTarget)
{
// When using the stencil we are already clearing depth and do not want to clear the stencil too. Clear
// color only when using the stencil as the horizon effectively clears it when not using it.
buffer.ClearRenderTarget(!UseStencilBuffer, UseStencilBuffer, Color.black);
buffer.SetGlobalTexture(ShaderIDs.s_WaterMaskTexture, maskTarget);
buffer.SetGlobalTexture(ShaderIDs.s_WaterMaskDepthTexture, depthTarget);
wrapper.SetTexture(SurfaceRenderer.ShaderIDs.s_WaterLine, _Water.Surface.HeightRT);
wrapper.SetVector(SurfaceRenderer.ShaderIDs.s_WaterLineSnappedPosition, parameters._SnappedPosition);
wrapper.SetVector(SurfaceRenderer.ShaderIDs.s_WaterLineResolution, parameters._Resolution);
wrapper.SetFloat(SurfaceRenderer.ShaderIDs.s_WaterLineTexel, parameters._Texel);
// Only write if not written (ie zero).
wrapper.SetKeyword(new(WaterResources.Instance.Compute._Mask, "d_KeepValue"), keep);
// Setting this sets unity_CameraToWorld.
wrapper.SetMatrix(Crest.ShaderIDs.Unity.s_CameraToWorld, camera.cameraToWorldMatrix);
// Viewport sizes are not perfect so round up to cover.
wrapper.Dispatch(Mathf.CeilToInt(descriptor.width / 8f), Mathf.CeilToInt(descriptor.height / 8f), descriptor.volumeDepth);
}
internal void FixMaskArtefacts(CommandBuffer buffer, RenderTextureDescriptor descriptor, RenderTargetIdentifier target)
@@ -181,9 +141,16 @@ namespace WaveHarmonic.Crest
return;
}
buffer.SetComputeTextureParam(_ArtifactsShader, _ArtifactsKernel, ShaderIDs.s_WaterMaskTexture, target);
// XR SPI will have a volume depth of two. If using RTHandles, then set manually as will be two for all cameras.
_ArtifactsShader.SetKeyword("STEREO_INSTANCING_ON", descriptor.dimension == TextureDimension.Tex2DArray);
if (!_Water.Surface.Enabled && Portaled)
{
return;
}
// This will not be set automatically unless we use they RP variant approach
// similar to Mask.compute.
_ArtifactsShader.SetKeyword(new LocalKeyword(_ArtifactsShader, "STEREO_INSTANCING_ON"), descriptor.dimension == TextureDimension.Tex2DArray);
buffer.SetComputeTextureParam(_ArtifactsShader, _ArtifactsKernel, MaskRenderer.ShaderIDs.s_WaterMaskTexture, target);
buffer.DispatchCompute
(
@@ -200,54 +167,55 @@ namespace WaveHarmonic.Crest
// be able to avoid this pass completely if we can reuse the camera depth after transparents are rendered.
internal void PopulateMask(CommandBuffer commandBuffer, Camera camera)
{
if (!_Water.Surface.Enabled && Portaled)
{
return;
}
// Render horizon into mask using a fullscreen triangle at the far plane. Horizon must be rendered first or
// it will overwrite the mask with incorrect values.
{
var zBufferParameters = Helpers.GetZBufferParameters(camera);
// Take 0-1 linear depth and convert non-linear depth.
_MaskMaterial.SetFloat(ShaderIDs.s_FarPlaneOffset, Helpers.LinearDepthToNonLinear(_FarPlaneMultiplier, zBufferParameters));
_HorizonMaskMaterial.SetFloat(ShaderIDs.s_FarPlaneOffset, Helpers.LinearDepthToNonLinear(_FarPlaneMultiplier, zBufferParameters));
// Render fullscreen triangle with horizon mask pass.
commandBuffer.DrawProcedural(Matrix4x4.identity, _MaskMaterial, shaderPass: k_ShaderPassWaterHorizonMask, MeshTopology.Triangles, 3, 1);
commandBuffer.BeginSample(k_DrawMaskHorizon);
commandBuffer.DrawProcedural(Matrix4x4.identity, _HorizonMaskMaterial, shaderPass: k_ShaderPassWaterHorizonMask, MeshTopology.Triangles, 3, 1);
commandBuffer.EndSample(k_DrawMaskHorizon);
}
GeometryUtility.CalculateFrustumPlanes(camera, _CameraFrustumPlanes);
// Get all water chunks and render them using cmd buffer, but with mask shader.
if (!_Debug._DisableMask)
{
// Spends approx 0.2-0.3ms here on 2018 Dell XPS 15.
foreach (var chunk in _Water.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);
}
// Handle culled tiles for when underwater is rendered before the transparent pass.
chunk._MaterialPropertyBlock.SetFloat(ShaderIDs.s_MaskBelowSurface, !_EnableShaderAPI || renderer.enabled ? k_MaskBelowSurface : k_MaskBelowSurfaceCull);
renderer.SetPropertyBlock(chunk._MaterialPropertyBlock);
commandBuffer.DrawRenderer(renderer, _MaskMaterial, submeshIndex: 0, shaderPass: k_ShaderPassWaterSurfaceMask);
chunk._Visible = true;
}
chunk._WaterDataHasBeenBound = false;
}
#if d_CrestPortals
if (_Portals.Active)
{
_Portals.PopulateMask(commandBuffer, _MaskMaterial);
}
#endif // d_CrestPortals
commandBuffer.BeginSample(k_DrawMaskSurface);
_Water.Surface.Render(camera, commandBuffer, _MaskMaterial, k_ShaderPassWaterSurfaceMask);
commandBuffer.EndSample(k_DrawMaskSurface);
}
}
internal bool _MaskRead;
bool _DoneMaskRead;
MaskRenderer.MaskInput MaskRenderer.IMaskProvider.Allocate()
{
return MaskRenderer.MaskInput.Both;
}
MaskRenderer.MaskInput MaskRenderer.IMaskReceiver.Allocate()
{
return MaskRenderer.MaskInput.Both;
}
MaskRenderer.MaskInput MaskRenderer.IMaskProvider.Write(Camera camera)
{
if (!_DoneMaskRead)
{
_MaskRead = ShouldRender(camera, Pass.Mask);
_DoneMaskRead = true;
}
return _MaskRead ? _Water.Surface.Enabled ? MaskRenderer.MaskInput.Both : MaskRenderer.MaskInput.Color : MaskRenderer.MaskInput.None;
}
}
}

View File

@@ -1,4 +1,4 @@
// Crest Water System
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
@@ -28,6 +28,8 @@ namespace WaveHarmonic.Crest
[@DecoratedField, SerializeField]
internal bool _Enabled = true;
// This is mainly for reflection probes (HDRP planar specifically). It gives
// developers the option to make a TIR probe which should not render the surface.
[Tooltip("Any camera or probe with this layer in its culling mask will render underwater.")]
[@Layer]
[@GenerateAPI]
@@ -35,8 +37,8 @@ namespace WaveHarmonic.Crest
int _Layer = 4; // Water
[Tooltip("The underwater material. The water surface material is copied into this material.")]
[@AttachMaterialEditor]
[@MaterialField("Crest/Underwater", name: "Underwater", title: "Create Underwater Material")]
[@AttachMaterialEditor(order: 2)]
[@MaterialField(k_ShaderNameEffect, name: "Underwater", title: "Create Underwater Material")]
[@GenerateAPI]
[SerializeField]
internal Material _Material;
@@ -68,21 +70,6 @@ namespace WaveHarmonic.Crest
#endif
[@Heading("Shader API")]
[Tooltip("Renders the underwater effect before the transparent pass (instead of after).\n\nSo one can apply the underwater fog themselves to transparent objects. Cannot be changed at runtime.")]
[@DecoratedField, SerializeField]
[HideInInspector]
bool _EnableShaderAPI = false;
internal bool EnableShaderAPI { get => _EnableShaderAPI; set => _EnableShaderAPI = value; }
[@Predicated(nameof(_EnableShaderAPI))]
[@Predicated(RenderPipeline.Legacy, inverted: true, hide: true)]
[@DecoratedField, SerializeField]
[HideInInspector]
internal LayerMask _TransparentObjectLayers;
[@Heading("Advanced")]
[Tooltip("Whether to execute for all cameras.\n\nIf disabled, then additionally ignore any camera that is not the view camera or our reflection camera. It will require managing culling masks of all cameras.")]
@@ -134,18 +121,33 @@ namespace WaveHarmonic.Crest
internal bool _OnlyReflectionCameras;
}
/// <summary>
/// Raised after copying the water material properties to the underwater material.
/// </summary>
public static System.Action<WaterRenderer, Material> AfterCopyMaterial { get; set; }
// Always render before surface, unless legacy mode which always renders after transparency.
#if d_Crest_LegacyUnderwater
internal bool UseLegacyMask => true;
internal bool RenderBeforeTransparency => false;
#else
// Legacy mask works except for negative volumes. Not officially supported.
internal bool UseLegacyMask => _AllCameras;
internal bool RenderBeforeTransparency => true;
#endif
internal WaterRenderer _Water;
#if d_CrestPortals
// BUG: NonSerialized as Unity shows a serialization depth warning even though field is internal.
[System.NonSerialized]
internal Portals.PortalRenderer _Portals;
bool Portaled => _Portals.Active;
internal bool Portaled => _Portals.Active;
#else
bool Portaled => false;
#endif
bool _FirstRender = true;
int _MaterialLastUpdatedFrame = -1;
internal bool UseStencilBuffer { get; set; }
@@ -168,9 +170,6 @@ namespace WaveHarmonic.Crest
// Empty.
}
// Disable underwater effect if height enough above surface.
internal bool Active => _Enabled && _Material != null && _ViewerWaterHeight < 2f || Portaled || _Debug._DisableHeightAboveWaterOptimization;
internal void OnEnable()
{
_VolumeMaterial = _Material;
@@ -180,22 +179,27 @@ namespace WaveHarmonic.Crest
_MaskMaterial = new(WaterResources.Instance.Shaders._UnderwaterMask);
}
if (_HorizonMaskMaterial == null)
{
_HorizonMaskMaterial = new(WaterResources.Instance.Shaders._HorizonMask);
}
if (_ArtifactsShader == null)
{
_ArtifactsShader = WaterResources.Instance.Compute._UnderwaterArtifacts;
}
OnEnableMask();
if (RenderPipelineHelper.IsUniversal)
{
#if d_UnityURP
UnderwaterMaskPassURP.Enable(this);
UnderwaterEffectPassURP.Enable(this);
#endif
}
else if (RenderPipelineHelper.IsHighDefinition)
{
#if d_UnityHDRP
UnderwaterMaskPassHDRP.Enable(this);
UnderwaterEffectPassHDRP.Enable(this);
#endif
}
@@ -223,13 +227,13 @@ namespace WaveHarmonic.Crest
{
RenderPipelineManager.activeRenderPipelineTypeChanged -= OnActiveRenderPipelineTypeChanged;
OnDisableMask();
#if d_UnityURP
UnderwaterMaskPassURP.Disable();
UnderwaterEffectPassURP.Disable();
#endif
#if d_UnityHDRP
UnderwaterMaskPassHDRP.Disable();
UnderwaterEffectPassHDRP.Disable();
#endif
@@ -243,27 +247,36 @@ namespace WaveHarmonic.Crest
internal void OnDestroy()
{
Helpers.Destroy(_MaskMaterial);
Helpers.Destroy(_HorizonMaskMaterial);
// Without will cause exception in editor in play mode if disable Write Motion Vectors.
_MaskMaterial = null;
_HorizonMaskMaterial = null;
}
internal bool ShouldRender(Camera camera, Pass pass)
{
if (!_Enabled || _Material == null)
{
return false;
}
if (_Water == null)
{
return false;
}
if (!Helpers.MaskIncludesLayer(camera.cullingMask, _Layer))
if (!WaterRenderer.ShouldRender(camera, _Layer))
{
return false;
}
// Skip entire mask pass if possible.
if (pass == Pass.Mask && !_Water.Surface.Enabled)
{
return false;
}
#if UNITY_EDITOR
// Do not execute when editor is not active to conserve power and prevent possible leaks.
if (!UnityEditorInternal.InternalEditorUtility.isApplicationActive)
{
return false;
}
if (GL.wireframe)
{
return false;
@@ -275,16 +288,6 @@ namespace WaveHarmonic.Crest
{
return false;
}
if (_Water.IsProxyPlaneRendering)
{
return false;
}
if (camera.cameraType == CameraType.Preview)
{
return false;
}
#endif
var isReflectionCamera = camera.cameraType == CameraType.Reflection;
@@ -304,14 +307,20 @@ namespace WaveHarmonic.Crest
// Otherwise, filtering depends on the camera's culling mask which is not always
// accessible like with the global "Reflection Probes Camera". But whether those
// cameras triggering camera events is a bug is TBD as it is intermittent.
if (!_AllCameras && camera != _Water.Viewer && camera.cameraType != CameraType.SceneView && camera != WaterReflections.CurrentCamera)
if (!_AllCameras && camera != _Water.GetViewer(includeSceneCamera: false) && camera.cameraType != CameraType.SceneView && camera != WaterReflections.CurrentCamera)
{
return false;
}
if (pass != Pass.Culling && !Active)
if (!_Debug._DisableHeightAboveWaterOptimization && !Portaled)
{
return false;
_Water.UpdatePerCameraHeight(camera);
_ViewerWaterHeight = _Water._ViewerHeightAboveWaterPerCamera;
if (_ViewerWaterHeight > 2f)
{
return false;
}
}
return true;
@@ -319,7 +328,7 @@ namespace WaveHarmonic.Crest
void RevertCulling()
{
foreach (var tile in _Water.Chunks)
foreach (var tile in _Water.Surface.Chunks)
{
if (tile.Rend == null || tile._Culled)
{
@@ -330,27 +339,65 @@ namespace WaveHarmonic.Crest
}
}
internal void OnBeforeCameraRender(Camera camera)
// Called by WaterRenderer. Camera must have water layer.
internal void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
{
OnBeginCameraRendering(camera);
#if UNITY_EDITOR
// Populated by this point.
if (_VolumeMaterial.shader != WaterResources.Instance.Shaders._UnderwaterEffect)
{
return;
}
#endif
#if d_UnityURP
if (RenderPipelineHelper.IsUniversal)
{
UnderwaterEffectPassURP.s_Instance?.EnqueuePass(context, camera);
}
else
#endif
#if d_UnityHDRP
if (RenderPipelineHelper.IsHighDefinition)
{
UnderwaterEffectPassHDRP.s_Instance?.OnBeginCameraRendering(context, camera);
}
else
#endif
{
OnBeforeLegacyRender(camera);
}
}
internal void OnBeginCameraRendering(Camera camera)
{
if (!ShouldRender(camera, Pass.Culling))
{
return;
}
var viewpoint = camera.transform.position;
_SamplingHeightHelper.SampleHeight(System.HashCode.Combine(GetHashCode(), camera.GetHashCode()), viewpoint, out var height, allowMultipleCallsPerFrame: true);
_ViewerWaterHeight = viewpoint.y - height;
// ShouldRender has a special case for this pass which skips the Active check so we
// can always continue sampling.
if (!Active)
// Only one camera supported due to LOD center dependency.
if (!UseLegacyMask && ShouldRender(camera, Pass.Mask) && camera == _Water.Viewer)
{
return;
_Water.Surface.UpdateDisplacedSurfaceData(camera);
}
_SurfaceMaterial = _Water.AboveOrBelowSurfaceMaterial;
#if d_UnityHDRP
if (RenderPipelineHelper.IsHighDefinition)
{
_Water.UpdateHighDefinitionLighting(camera);
}
#endif
_SurfaceMaterial = _Water.Surface.AboveOrBelowSurfaceMaterial;
_VolumeMaterial = _Material;
var viewpoint = camera.transform.position;
// Grab material from a water body if camera is within its XZ bounds.
foreach (var body in WaterBody.WaterBodies)
{
@@ -373,6 +420,13 @@ namespace WaveHarmonic.Crest
}
}
#if UNITY_EDITOR
if (_VolumeMaterial.shader != WaterResources.Instance.Shaders._UnderwaterEffect)
{
return;
}
#endif
var extinction = Vector3.zero;
float minimumFogDensity = 0;
@@ -405,7 +459,12 @@ namespace WaveHarmonic.Crest
minimumFogDensity = Mathf.Max(minimumFogDensity, 0.0001f);
}
UpdateEnvironmentalLighting(camera, extinction, _ViewerWaterHeight);
if (_EnvironmentalInitialized)
{
_Water.UpdatePerCameraHeight(camera);
_ViewerWaterHeight = _Water._ViewerHeightAboveWaterPerCamera;
UpdateEnvironmentalLighting(camera, extinction, _ViewerWaterHeight);
}
if (Portaled || _ViewerWaterHeight > -5f)
{
@@ -415,7 +474,7 @@ namespace WaveHarmonic.Crest
var extinctionLength = -Mathf.Log(_CullLimit) / minimumFogDensity;
foreach (var tile in _Water.Chunks)
foreach (var tile in _Water.Surface.Chunks)
{
if (tile.Rend == null || tile._Culled)
{
@@ -436,10 +495,16 @@ namespace WaveHarmonic.Crest
}
}
internal void OnAfterCameraRender(Camera camera)
internal void OnEndCameraRendering(Camera camera)
{
RestoreEnvironmentalLighting();
RevertCulling();
_DoneMaskRead = false;
if (RenderPipelineHelper.IsLegacy)
{
OnAfterLegacyRender(camera);
}
}
void SetEnabled(bool previous, bool current)