This commit is contained in:
zhangjiajia
2026-05-06 16:56:59 +08:00
parent 575626d3e1
commit 81ffaaeca6
1373 changed files with 145920 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
namespace WaveHarmonic.Crest
{
sealed class UnderwaterEffectPass
{
readonly UnderwaterRenderer _Renderer;
RTHandle _ColorTexture;
RTHandle _ColorTarget;
RTHandle _DepthTarget;
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)
{
Blitter.BlitCameraTexture(buffer, _ColorTarget, _ColorTexture);
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
(
Vector2.one,
TextureXR.slices,
dimension: TextureXR.dimension,
colorFormat: format,
depthBufferBits: DepthBits.None,
useDynamicScale: true,
wrapMode: TextureWrapMode.Clamp,
name: "_Crest_UnderwaterCameraColorTexture"
);
}
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");
}
public void Release()
{
_ColorTexture?.Release();
_ColorTexture = null;
}
public void Execute(Camera camera, CommandBuffer buffer, RTHandle color, RTHandle depth, MaterialPropertyBlock mpb = null)
{
_Renderer.UpdateEffectMaterial(camera);
_ColorTarget = color;
_DepthTarget = depth;
if (!_Renderer.RenderBeforeTransparency || _Renderer._NeedsColorTexture)
{
buffer.SetGlobalTexture(UnderwaterRenderer.ShaderIDs.s_CameraColorTexture, _ColorTexture);
}
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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6c581581e08ff40e689d952358cea7a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,154 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if d_UnityHDRP
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
namespace WaveHarmonic.Crest
{
sealed class UnderwaterEffectPassHDRP : CustomPass
{
const string k_Name = "Underwater Effect";
static UnderwaterRenderer s_Renderer;
static UnderwaterEffectPass s_UnderwaterEffectPass;
internal static UnderwaterEffectPassHDRP s_Instance;
static CopyDepthBufferPassHDRP s_CopyDepthBufferPassHDRP;
static ShaderTagId[] s_ForwardShaderTags;
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_CopyDepthBufferPassHDRP,
UnderwaterRenderer.k_DrawVolume,
CustomPassInjectionPoint.AfterOpaqueDepthAndNormal
);
var isBeforeTransparentPass = renderer.RenderBeforeTransparency;
CustomPassHelpers.CreateOrUpdate
(
gameObject,
ref s_Instance,
UnderwaterRenderer.k_DrawVolume,
GetInjectionPoint(isBeforeTransparentPass),
// Higher number (priority) means execute earlier. Volume executes first.
priority: 1
);
s_Renderer = renderer;
s_UnderwaterEffectPass = 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);
}
}
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)
{
var asset = GraphicsSettings.currentRenderPipeline as HDRenderPipelineAsset;
// Developers have a choice with the color buffer format. There is also a custom buffer buffer format but
// that is not relevant here. This will not cover the format change when scene filtering as Setup/Cleanup is
// not executed for this change.
s_UnderwaterEffectPass.Allocate((GraphicsFormat)asset.currentPlatformRenderPipelineSettings.colorBufferFormat);
// Taken from:
// https://github.com/Unity-Technologies/Graphics/blob/778ddac6207ade1689999b95380cd835b0669f2d/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPass/DrawRenderersCustomPass.cs#L136-L142
s_ForwardShaderTags ??= new[]
{
HDShaderPassNames.s_ForwardName, // HD Lit shader
HDShaderPassNames.s_ForwardOnlyName, // HD Unlit shader
HDShaderPassNames.s_SRPDefaultUnlitName, // Cross SRP Unlit shader
};
}
protected override void Cleanup()
{
s_UnderwaterEffectPass?.Release();
}
protected override void Execute(CustomPassContext context)
{
var camera = context.hdCamera.camera;
if (!s_Renderer.ShouldRender(camera, UnderwaterRenderer.Pass.Effect))
{
return;
}
// 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);
}
}
sealed class CopyDepthBufferPassHDRP : CustomPass
{
public RTHandle _DepthBufferCopy;
protected override void Execute(CustomPassContext context)
{
// Multiple cameras could have different settings.
RenderPipelineCompatibilityHelper.ReAllocateIfNeeded
(
ref _DepthBufferCopy,
context.cameraDepthBuffer.rt.descriptor,
FilterMode.Point,
name: "_Crest_UnderwaterCopiedDepthBuffer"
);
var buffer = context.cmd;
// 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.
CoreUtils.SetRenderTarget(buffer, BuiltinRenderTextureType.None, _DepthBufferCopy, ClearFlag.Stencil);
}
protected override void Cleanup()
{
_DepthBufferCopy?.Release();
_DepthBufferCopy = null;
}
}
}
#endif // d_UnityHDRP

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 03d7c1db420e64f7c9894f9c2bdaae4c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,122 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if d_UnityURP
#if UNITY_6000_0_OR_NEWER
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;
namespace WaveHarmonic.Crest
{
partial class UnderwaterEffectPassURP
{
readonly RenderGraphHelper.PassData _PassData = new();
public override void RecordRenderGraph(RenderGraph graph, ContextContainer frame)
{
using (var builder = graph.AddUnsafePass<RenderGraphHelper.PassData>(k_Name, out var data))
{
data.Init(frame, builder);
builder.AllowPassCulling(false);
builder.SetRenderFunc<RenderGraphHelper.PassData>((data, context) =>
{
var buffer = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
OnSetup(buffer, data);
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);
OnSetup(buffer, _PassData);
Execute(context, buffer, _PassData);
context.ExecuteCommandBuffer(buffer);
CommandBufferPool.Release(buffer);
}
}
partial class CopyDepthBufferPassURP
{
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
{
// 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);
}
}
}
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);
OnSetup(buffer, data);
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);
OnSetup(buffer, _PassData);
Execute(context, buffer, _PassData);
context.ExecuteCommandBuffer(buffer);
CommandBufferPool.Release(buffer);
}
}
}
#endif // UNITY_6000_0_OR_NEWER
#endif // d_UnityURP

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7fba7ac72b29f4b12a08eb07d80a2703
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,220 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if d_UnityURP
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace WaveHarmonic.Crest
{
sealed partial class UnderwaterEffectPassURP : ScriptableRenderPass
{
const string k_Name = "Crest.DrawWater/Volume";
UnderwaterRenderer _Renderer;
internal static UnderwaterEffectPassURP s_Instance;
UnderwaterEffectPass _UnderwaterEffectPass;
CopyDepthBufferPassURP _CopyDepthBufferPass;
RTHandle _ColorBuffer;
RTHandle _DepthBuffer;
public UnderwaterEffectPassURP()
{
ConfigureInput(ScriptableRenderPassInput.Color | ScriptableRenderPassInput.Depth);
}
public static void Enable(UnderwaterRenderer renderer)
{
if (s_Instance == null)
{
s_Instance = new();
s_Instance._Renderer = renderer;
s_Instance._CopyDepthBufferPass = new(RenderPassEvent.AfterRenderingOpaques);
}
RenderPipelineManager.activeRenderPipelineTypeChanged -= Disable;
RenderPipelineManager.activeRenderPipelineTypeChanged += Disable;
}
public static void Disable()
{
RenderPipelineManager.activeRenderPipelineTypeChanged -= Disable;
s_Instance?._UnderwaterEffectPass?.Release();
s_Instance?._CopyDepthBufferPass?.Release();
s_Instance = null;
}
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
if (renderer == null) return;
#endif
// Copy the depth buffer to create a new depth/stencil context.
if (_Renderer.UseStencilBuffer)
{
renderer.EnqueuePass(_CopyDepthBufferPass);
}
// Set up internal pass which houses shared code for SRPs.
_UnderwaterEffectPass ??= new(_Renderer);
renderer.EnqueuePass(s_Instance);
}
#if UNITY_6000_0_OR_NEWER
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, RenderGraphHelper.PassData data)
{
// Unity bug
if (_ColorBuffer?.rt == null)
{
return;
}
if (_Renderer.UseStencilBuffer)
{
_DepthBuffer = _CopyDepthBufferPass._DepthBufferCopy;
}
_UnderwaterEffectPass.Execute(data.cameraData.camera, buffer, _ColorBuffer, _DepthBuffer);
}
#else
public override void OnCameraSetup(CommandBuffer buffer, ref RenderingData data)
{
_ColorBuffer = data.cameraData.renderer.cameraColorTargetHandle;
_DepthBuffer = data.cameraData.renderer.cameraDepthTargetHandle;
// TODO: renderingData.cameraData.cameraTargetDescriptor?
_UnderwaterEffectPass.ReAllocate(_ColorBuffer.rt.descriptor);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData data)
{
var buffer = CommandBufferPool.Get(k_Name);
if (_Renderer.UseStencilBuffer)
{
_DepthBuffer = _CopyDepthBufferPass._DepthBufferCopy;
}
_UnderwaterEffectPass.Execute(data.cameraData.camera, buffer, _ColorBuffer, _DepthBuffer);
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;
public CopyDepthBufferPassURP(RenderPassEvent @event)
{
renderPassEvent = @event;
}
#if UNITY_6000_0_OR_NEWER
void OnSetup(CommandBuffer buffer, PassData data)
#else
public override void OnCameraSetup(CommandBuffer buffer, ref RenderingData data)
#endif
{
var descriptor = data.cameraData.cameraTargetDescriptor;
descriptor.graphicsFormat = GraphicsFormat.None;
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
}
#if UNITY_6000_0_OR_NEWER
void Execute(ScriptableRenderContext context, CommandBuffer buffer, PassData data)
#else
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
// NOTE: previously we cleared the target depth first due to artifacts.
buffer.CopyTexture(_DepthBuffer.rt, _DepthBufferCopy.rt);
// Clear the stencil component just in case.
// 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);
CommandBufferPool.Release(buffer);
#endif
}
public void Release()
{
_DepthBuffer = null;
_DepthBufferCopy?.Release();
}
}
}
#endif // d_UnityURP

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5ab3e34da699e48eaa28a35fba152510
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,2 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 034fbbb00c45d493294db385ff38a629
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,2 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3fa78b61faddf4493ae6381f85fb2572
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,2 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1ce8d0e0aca6a47a9b0b5a8a7544a064
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,32 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if UNITY_EDITOR
using UnityEngine;
using WaveHarmonic.Crest.Editor;
namespace WaveHarmonic.Crest
{
// Edit Mode.
partial class UnderwaterRenderer
{
static bool IsFogEnabledForEditorCamera(Camera camera)
{
// Check if scene view has disabled fog rendering.
if (camera.cameraType == CameraType.SceneView)
{
var sceneView = EditorHelpers.GetSceneViewFromSceneCamera(camera);
// Skip rendering if fog is disabled or for some reason we could not find the scene view.
if (sceneView == null || !sceneView.sceneViewState.fogEnabled)
{
return false;
}
}
return true;
}
}
}
#endif

View File

@@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: 9c58e49fb2a8646388cd64da7f35b182
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- _volumeGeometry: {instanceID: 0}
- _EffectMaterial: {instanceID: 0}
- _MaskMaterial: {instanceID: 0}
- _VolumeMaterial: {instanceID: 0}
- _fixMaskComputeShader: {fileID: 7200000, guid: 08549c36146ad4899a07193754b21ea2,
type: 3}
executionOrder: 201
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,301 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using UnityEngine.Rendering;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest
{
partial class UnderwaterRenderer
{
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_SampleSphericalHarmonicsMarker = "Crest.UnderwaterRenderer.SampleSphericalHarmonics";
static readonly Unity.Profiling.ProfilerMarker s_SampleSphericalHarmonicsMarker = new(k_SampleSphericalHarmonicsMarker);
static partial class ShaderIDs
{
// Global
public static readonly int s_CameraColorTexture = Shader.PropertyToID("_Crest_CameraColorTexture");
public static readonly int s_WaterVolumeStencil = Shader.PropertyToID("_Crest_WaterVolumeStencil");
public static readonly int s_AmbientLighting = Shader.PropertyToID("_Crest_AmbientLighting");
public static readonly int s_ExtinctionMultiplier = Shader.PropertyToID("_Crest_ExtinctionMultiplier");
public static readonly int s_UnderwaterEnvironmentalLightingWeight = Shader.PropertyToID("_Crest_UnderwaterEnvironmentalLightingWeight");
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");
}
// These map to passes in the underwater shader.
internal enum EffectPass
{
FullScreen,
Reflections,
}
CommandBuffer _EffectCommandBuffer;
Material _CurrentWaterMaterial;
readonly UnderwaterSphericalHarmonicsData _SphericalHarmonicsData = new();
System.Action<CommandBuffer> _CopyColor;
System.Action<CommandBuffer> _SetRenderTargetToBackBuffers;
RenderTargetIdentifier _ColorTarget = new
(
BuiltinRenderTextureType.CameraTarget,
0,
CubemapFace.Unknown,
-1
);
RenderTargetIdentifier _DepthStencilTarget = new
(
ShaderIDs.s_WaterVolumeStencil,
0,
CubemapFace.Unknown,
-1
);
RenderTargetIdentifier _ColorCopyTarget = new
(
ShaderIDs.s_CameraColorTexture,
0,
CubemapFace.Unknown,
-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
// differences which is very hard to get right for BIRP.
buffer.Blit(BuiltinRenderTextureType.CameraTarget, _ColorCopyTarget);
if (UseStencilBuffer)
{
_EffectCommandBuffer.SetRenderTarget(_ColorTarget, _DepthStencilTarget);
}
else
{
_EffectCommandBuffer.SetRenderTarget(_ColorTarget);
}
}
void SetupUnderwaterEffect()
{
_EffectCommandBuffer ??= new()
{
name = k_DrawVolume,
};
_CopyColor ??= new(CopyColorTexture);
_SetRenderTargetToBackBuffers ??= new(SetRenderTargetToBackBuffers);
}
void OnPreRenderUnderwaterEffect(Camera camera)
{
var descriptor = Rendering.BIRP.GetCameraTargetDescriptor(camera, _Water.FrameBufferFormatOverride);
descriptor.useDynamicScale = camera.allowDynamicResolution;
UpdateEffectMaterial(camera);
_EffectCommandBuffer.Clear();
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)
{
// 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(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)Helpers.k_DepthBits;
// bindMS is necessary in this case for depth.
descriptor.SetMSAASamples(camera);
descriptor.bindMS = descriptor.msaaSamples > 1;
// No need to clear as Blit will overwrite everything.
_EffectCommandBuffer.GetTemporaryRT(ShaderIDs.s_WaterVolumeStencil, descriptor);
// Use blit for MSAA. We should be able to use CopyTexture. Might be the following bug:
// https://issuetracker.unity3d.com/product/unity/issues/guid/1308132
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);
}
else
{
// Copy depth then clear stencil.
_EffectCommandBuffer.CopyTexture(BuiltinRenderTextureType.Depth, _DepthStencilTarget);
Helpers.Blit(_EffectCommandBuffer, _DepthStencilTarget, Helpers.UtilityMaterial, (int)Helpers.UtilityPass.ClearStencil);
}
if (RenderBeforeTransparency)
{
_EffectCommandBuffer.SetRenderTarget(BuiltinRenderTextureType.CameraTarget, _DepthStencilTarget);
}
}
if (!RenderBeforeTransparency)
{
CopyColorTexture(_EffectCommandBuffer);
}
ExecuteEffect(camera, _EffectCommandBuffer, _CopyColor, _SetRenderTargetToBackBuffers);
if (!RenderBeforeTransparency || _NeedsColorTexture)
{
_EffectCommandBuffer.ReleaseTemporaryRT(ShaderIDs.s_CameraColorTexture);
}
if (UseStencilBuffer)
{
_EffectCommandBuffer.ReleaseTemporaryRT(ShaderIDs.s_WaterVolumeStencil);
}
}
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
);
}
#if d_CrestPortals
else if (_Portals.Active && _Portals.Mode != Portals.PortalMode.Tunnel)
{
_Portals.RenderEffect(camera, buffer, _VolumeMaterial, copyColor, resetRenderTargets, properties);
}
#endif
else
{
buffer.DrawProcedural
(
Matrix4x4.identity,
_VolumeMaterial,
shaderPass: (int)EffectPass.FullScreen,
MeshTopology.Triangles,
vertexCount: 3,
instanceCount: 1,
properties
);
}
}
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, 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)
{
// 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)
{
if (_CopyWaterMaterialParametersEachFrame || _SurfaceMaterial != _CurrentWaterMaterial)
{
_CurrentWaterMaterial = _SurfaceMaterial;
if (_SurfaceMaterial != null)
{
_VolumeMaterial.CopyMatchingPropertiesFromMaterial(_SurfaceMaterial);
AfterCopyMaterial?.Invoke(_Water, _VolumeMaterial);
// Make volume properties available to surface and meniscus.
if (RenderBeforeTransparency)
{
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;
}
// Not applicable to reflection pass.
if (camera.cameraType != CameraType.Reflection)
{
// 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.
{
// We could pass in a renderer which would prime this lookup. However it doesnt make sense to use an existing render
// 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.
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], RenderBeforeTransparency);
s_SampleSphericalHarmonicsMarker.End();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 95fe330fa426a41c0b6379a1a2aae608
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,142 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using UnityEngine.Rendering;
namespace WaveHarmonic.Crest
{
partial class UnderwaterRenderer
{
const float k_DepthOutScattering = 0.25f;
Light _EnvironmentalLight;
float _EnvironmentalLightIntensity;
float _EnvironmentalAmbientIntensity;
float _EnvironmentalReflectionIntensity;
float _EnvironmentalFogDensity;
float _EnvironmentalAverageDensity = 0f;
bool _EnvironmentalInitialized = false;
bool _EnvironmentalNeedsRestoring;
void EnableEnvironmentalLighting()
{
if (!_EnvironmentalLightingEnable)
{
return;
}
#if d_UnitySRP
if (_EnvironmentalLightingVolume == null && !RenderPipelineHelper.IsLegacy)
{
// Create volume to weigh in underwater profile
var go = new GameObject();
go.transform.parent = _Water.Container.transform;
go.hideFlags = HideFlags.HideAndDontSave;
go.name = "Underwater Lighting Volume";
_EnvironmentalLightingVolume = go.AddComponent<Volume>();
_EnvironmentalLightingVolume.weight = 0;
_EnvironmentalLightingVolume.priority = 1000;
_EnvironmentalLightingVolume.profile = _EnvironmentalLightingVolumeProfile;
}
#endif
_EnvironmentalInitialized = true;
}
void DisableEnvironmentalLighting()
{
RestoreEnvironmentalLighting();
_EnvironmentalInitialized = false;
}
void RestoreEnvironmentalLighting()
{
if (!_EnvironmentalInitialized || !_EnvironmentalNeedsRestoring)
{
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;
RenderSettings.ambientIntensity = _EnvironmentalAmbientIntensity;
RenderSettings.reflectionIntensity = _EnvironmentalReflectionIntensity;
RenderSettings.fogDensity = _EnvironmentalFogDensity;
Shader.SetGlobalFloat(ShaderIDs.s_UnderwaterEnvironmentalLightingWeight, 0f);
if (_EnvironmentalLightingVolume != null) _EnvironmentalLightingVolume.weight = 0;
_EnvironmentalNeedsRestoring = false;
}
void UpdateEnvironmentalLighting(Camera camera, Vector3 extinction, float height)
{
if (!_EnvironmentalInitialized)
{
return;
}
#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;
}
// Store lighting settings.
{
_EnvironmentalLight = _Water.PrimaryLight;
if (_EnvironmentalLight) _EnvironmentalLightIntensity = _EnvironmentalLight.intensity;
_EnvironmentalAmbientIntensity = RenderSettings.ambientIntensity;
_EnvironmentalReflectionIntensity = RenderSettings.reflectionIntensity;
_EnvironmentalFogDensity = RenderSettings.fogDensity;
}
var density = extinction;
_EnvironmentalAverageDensity = (density.x + density.y + density.z) / 3f;
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)
{
_EnvironmentalLight.intensity = Mathf.Lerp(0, _EnvironmentalLightIntensity, multiplier);
}
RenderSettings.ambientIntensity = Mathf.Lerp(0, _EnvironmentalAmbientIntensity, multiplier);
RenderSettings.reflectionIntensity = Mathf.Lerp(0, _EnvironmentalReflectionIntensity, multiplier);
RenderSettings.fogDensity = Mathf.Lerp(0, _EnvironmentalFogDensity, multiplier);
Shader.SetGlobalFloat(ShaderIDs.s_UnderwaterEnvironmentalLightingWeight, 1f - multiplier);
#if d_UnitySRP
if (_EnvironmentalLightingVolume != null)
{
_EnvironmentalLightingVolume.weight = 1f - multiplier;
}
#endif
_EnvironmentalNeedsRestoring = true;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 42735f62770724c7488928b7e8185c9c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,56 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using UnityEngine.Rendering;
namespace WaveHarmonic.Crest
{
partial class UnderwaterRenderer
{
bool _HasEffectCommandBuffersBeenRegistered;
void OnEnableLegacy()
{
SetupUnderwaterEffect();
RenderPipelineManager.activeRenderPipelineTypeChanged -= OnDisableLegacy;
RenderPipelineManager.activeRenderPipelineTypeChanged += OnDisableLegacy;
}
void OnDisableLegacy()
{
RenderPipelineManager.activeRenderPipelineTypeChanged -= OnDisableLegacy;
}
// Listening to OnPreCull. Camera must have underwater layer.
void OnBeforeLegacyRender(Camera camera)
{
if (ShouldRender(camera, Pass.Effect))
{
_Water.UpdateMatrices(camera);
_Water.OnBeginCameraOpaqueTexture(camera);
var @event = RenderBeforeTransparency ? CameraEvent.BeforeForwardAlpha : CameraEvent.AfterForwardAlpha;
camera.AddCommandBuffer(@event, _EffectCommandBuffer);
OnPreRenderUnderwaterEffect(camera);
_HasEffectCommandBuffersBeenRegistered = true;
}
}
void OnAfterLegacyRender(Camera camera)
{
if (_HasEffectCommandBuffersBeenRegistered)
{
var @event = RenderBeforeTransparency ? CameraEvent.BeforeForwardAlpha : CameraEvent.AfterForwardAlpha;
camera.RemoveCommandBuffer(@event, _EffectCommandBuffer);
_EffectCommandBuffer?.Clear();
}
_Water.OnEndCameraOpaqueTexture(camera);
_HasEffectCommandBuffersBeenRegistered = false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f72cef5b74e7e43c3bfceff42401fa82
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,216 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using UnityEngine.Rendering;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest
{
partial class UnderwaterRenderer : MaskRenderer.IMaskReceiver, MaskRenderer.IMaskProvider
{
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 = 0;
internal const string k_ComputeShaderKernelFillMaskArtefacts = "FillMaskArtefacts";
static partial class ShaderIDs
{
// Local
public static readonly int s_FarPlaneOffset = Shader.PropertyToID("_Crest_FarPlaneOffset");
}
internal Material _MaskMaterial;
internal Material _HorizonMaskMaterial;
ComputeShader _ArtifactsShader;
bool _ArtifactsShaderInitialized;
int _ArtifactsKernel;
uint _ArtifactsThreadGroupSizeX;
uint _ArtifactsThreadGroupSizeY;
internal void OnEnableMask()
{
_Water._Mask.Add(this);
_Water._Mask.Add(k_VolumeMaskQueue, this);
SetUpArtifactsShader();
}
internal void OnDisableMask()
{
if (_Water == null) return;
_Water._Mask?.Remove(this as MaskRenderer.IMaskReceiver);
_Water._Mask?.Remove(this as MaskRenderer.IMaskProvider);
}
internal void SetUpArtifactsShader()
{
if (_ArtifactsShaderInitialized)
{
return;
}
_ArtifactsKernel = _ArtifactsShader.FindKernel(k_ComputeShaderKernelFillMaskArtefacts);
_ArtifactsShader.GetKernelThreadGroupSizes
(
_ArtifactsKernel,
out _ArtifactsThreadGroupSizeX,
out _ArtifactsThreadGroupSizeY,
out _
);
_ArtifactsShaderInitialized = true;
}
void MaskRenderer.IMaskProvider.OnMaskPass(CommandBuffer commands, Camera camera, MaskRenderer mask)
{
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
{
#if d_CrestPortals
if (_Water.Portals.Mode != Portals.PortalMode.VolumeFlyThrough)
#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;
}
var wrapper = new PropertyWrapperCompute(buffer, WaterResources.Instance.Compute._Mask, (int)RenderPipelineHelper.RenderPipeline);
var parameters = _Water.Surface._SurfaceDataParameters;
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);
// XR SPI will have a volume depth of two. If using RTHandles, then set manually as will be two for all cameras.
wrapper.SetKeyword(new(WaterResources.Instance.Compute._Mask, "STEREO_INSTANCING_ON"), descriptor.dimension == TextureDimension.Tex2DArray);
// 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)
{
if (_Debug._DisableArtifactCorrection)
{
return;
}
if (!_Water.Surface.Enabled && Portaled)
{
return;
}
buffer.SetComputeTextureParam(_ArtifactsShader, _ArtifactsKernel, MaskRenderer.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);
buffer.DispatchCompute
(
_ArtifactsShader,
_ArtifactsKernel,
// Viewport sizes are not perfect so round up to cover.
Mathf.CeilToInt((float)descriptor.width / _ArtifactsThreadGroupSizeX),
Mathf.CeilToInt((float)descriptor.height / _ArtifactsThreadGroupSizeY),
descriptor.volumeDepth
);
}
// Populates a screen space mask which will inform the underwater postprocess. As a future optimisation we may
// 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.
_HorizonMaskMaterial.SetFloat(ShaderIDs.s_FarPlaneOffset, Helpers.LinearDepthToNonLinear(_FarPlaneMultiplier, zBufferParameters));
// Render fullscreen triangle with horizon mask pass.
commandBuffer.BeginSample(k_DrawMaskHorizon);
commandBuffer.DrawProcedural(Matrix4x4.identity, _HorizonMaskMaterial, shaderPass: k_ShaderPassWaterHorizonMask, MeshTopology.Triangles, 3, 1);
commandBuffer.EndSample(k_DrawMaskHorizon);
}
// Get all water chunks and render them using cmd buffer, but with mask shader.
if (!_Debug._DisableMask)
{
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

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f64799bb3430e498a926913b81241f06
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,540 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using UnityEngine.Rendering;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest
{
/// <summary>
/// Renders the underwater effect.
/// </summary>
[System.Serializable]
public sealed partial class UnderwaterRenderer
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
internal const float k_CullLimitMinimum = 0.000001f;
internal const float k_CullLimitMaximum = 0.01f;
[@Space(10)]
[Tooltip("Whether the underwater effect is enabled.\n\nAllocates/releases resources if state has changed.")]
[@GenerateAPI(Setter.Custom)]
[@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]
[SerializeField]
int _Layer = 4; // Water
[Tooltip("The underwater material. The water surface material is copied into this material.")]
[@AttachMaterialEditor(order: 2)]
[@MaterialField(k_ShaderNameEffect, name: "Underwater", title: "Create Underwater Material")]
[@GenerateAPI]
[SerializeField]
internal Material _Material;
[@Heading("Environmental Lighting")]
[@Label("Enable")]
[Tooltip("Provides out-scattering based on the camera's underwater depth.\n\nIt scales down environmental lighting (sun, reflections, ambient etc) with the underwater depth. This works with vanilla lighting, but uncommon or custom lighting will require a custom solution (use this for reference)")]
[@GenerateAPI(Setter.Custom, name: "AffectsEnvironmentalLighting")]
[@DecoratedField, SerializeField]
internal bool _EnvironmentalLightingEnable;
[@Label("Weight")]
[Tooltip("How much this effect applies.\n\nValues less than 1 attenuate light less underwater. Value of 1 is physically based.")]
[@Range(0, 3)]
[@GenerateAPI]
[SerializeField]
internal float _EnvironmentalLightingWeight = 1f;
#if d_UnitySRP
[@Label("Volume")]
[Tooltip("This profile will be weighed in the deeper underwater the camera goes.")]
[@Predicated(RenderPipeline.HighDefinition, hide: true)]
[@DecoratedField, SerializeField]
VolumeProfile _EnvironmentalLightingVolumeProfile = null;
Volume _EnvironmentalLightingVolume;
#endif
[@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.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _AllCameras;
[Tooltip("Copying parameters each frame ensures underwater appearance stays consistent with the water surface.\n\nHas a small overhead so should be disabled if not needed.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _CopyWaterMaterialParametersEachFrame = true;
[Tooltip("Adjusts the far plane for horizon line calculation. Helps with horizon line issue.")]
[@Range(0f, 1f)]
[@GenerateAPI]
[SerializeField]
float _FarPlaneMultiplier = 0.68f;
[Tooltip("Proportion of visibility below which the water surface will be culled when underwater.\n\nThe larger the number, the closer to the camera the water tiles will be culled.")]
[@Range(k_CullLimitMinimum, k_CullLimitMaximum)]
[@GenerateAPI]
[SerializeField]
internal float _CullLimit = 0.001f;
[@Space(10)]
[@DecoratedField, SerializeField]
DebugFields _Debug = new();
[System.Serializable]
sealed class DebugFields
{
[SerializeField]
internal bool _VisualizeMask;
[SerializeField]
internal bool _DisableMask;
[SerializeField]
internal bool _VisualizeStencil;
[SerializeField]
internal bool _DisableHeightAboveWaterOptimization;
[SerializeField]
internal bool _DisableArtifactCorrection;
[SerializeField]
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;
internal bool Portaled => _Portals.Active;
#else
bool Portaled => false;
#endif
int _MaterialLastUpdatedFrame = -1;
internal bool UseStencilBuffer { get; set; }
internal enum Pass
{
Culling,
Mask,
Effect,
}
// These are the materials we actually use, overridable by Water Body.
Material _SurfaceMaterial;
Material _VolumeMaterial;
readonly SampleCollisionHelper _SamplingHeightHelper = new();
float _ViewerWaterHeight;
internal static partial class ShaderIDs
{
// Empty.
}
internal void OnEnable()
{
_VolumeMaterial = _Material;
if (_MaskMaterial == null)
{
_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
UnderwaterEffectPassURP.Enable(this);
#endif
}
else if (RenderPipelineHelper.IsHighDefinition)
{
#if d_UnityHDRP
UnderwaterEffectPassHDRP.Enable(this);
#endif
}
else
{
OnEnableLegacy();
}
EnableEnvironmentalLighting();
RenderPipelineManager.activeRenderPipelineTypeChanged -= OnActiveRenderPipelineTypeChanged;
RenderPipelineManager.activeRenderPipelineTypeChanged += OnActiveRenderPipelineTypeChanged;
}
void OnActiveRenderPipelineTypeChanged()
{
// Disable is handled by another handler so we need to run enabled.
if (_Water.isActiveAndEnabled)
{
OnEnable();
}
}
internal void OnDisable()
{
RenderPipelineManager.activeRenderPipelineTypeChanged -= OnActiveRenderPipelineTypeChanged;
OnDisableMask();
#if d_UnityURP
UnderwaterEffectPassURP.Disable();
#endif
#if d_UnityHDRP
UnderwaterEffectPassHDRP.Disable();
#endif
OnDisableLegacy();
DisableEnvironmentalLighting();
_ArtifactsShader = null;
}
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 (!WaterRenderer.ShouldRender(camera, _Layer))
{
return false;
}
// Skip entire mask pass if possible.
if (pass == Pass.Mask && !_Water.Surface.Enabled)
{
return false;
}
#if UNITY_EDITOR
if (GL.wireframe)
{
return false;
}
// Skip camera if fog is disabled. Do not skip if mask pass and a portal or volume as we want it to still
// mask the water surface.
if ((pass != Pass.Mask || !Portaled) && !IsFogEnabledForEditorCamera(camera))
{
return false;
}
#endif
var isReflectionCamera = camera.cameraType == CameraType.Reflection;
// Mask or culling is not needed for reflections.
if (isReflectionCamera && pass != Pass.Effect)
{
return false;
}
if (_Debug._OnlyReflectionCameras && !isReflectionCamera)
{
return false;
}
// Option to exclude cameras that is not the view camera or our reflection camera.
// 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.GetViewer(includeSceneCamera: false) && camera.cameraType != CameraType.SceneView && camera != WaterReflections.CurrentCamera)
{
return false;
}
if (!_Debug._DisableHeightAboveWaterOptimization && !Portaled)
{
_Water.UpdatePerCameraHeight(camera);
_ViewerWaterHeight = _Water._ViewerHeightAboveWaterPerCamera;
if (_ViewerWaterHeight > 2f)
{
return false;
}
}
return true;
}
void RevertCulling()
{
foreach (var tile in _Water.Surface.Chunks)
{
if (tile.Rend == null || tile._Culled)
{
continue;
}
tile.Rend.enabled = true;
}
}
// 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;
}
// Only one camera supported due to LOD center dependency.
if (!UseLegacyMask && ShouldRender(camera, Pass.Mask) && camera == _Water.Viewer)
{
_Water.Surface.UpdateDisplacedSurfaceData(camera);
}
#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)
{
if (body.AboveOrBelowSurfaceMaterial == null && body._VolumeMaterial == null)
{
continue;
}
var bounds = body.AABB;
var contained =
viewpoint.x >= bounds.min.x && viewpoint.x <= bounds.max.x &&
viewpoint.z >= bounds.min.z && viewpoint.z <= bounds.max.z;
if (contained)
{
if (body.AboveOrBelowSurfaceMaterial != null) _SurfaceMaterial = body.AboveOrBelowSurfaceMaterial;
if (body.VolumeMaterial != null) _VolumeMaterial = body.VolumeMaterial;
// Water bodies should not overlap so grab the first one.
break;
}
}
#if UNITY_EDITOR
if (_VolumeMaterial.shader != WaterResources.Instance.Shaders._UnderwaterEffect)
{
return;
}
#endif
var extinction = Vector3.zero;
float minimumFogDensity = 0;
// Calculate extinction.
if (_SurfaceMaterial != null)
{
var densityFactor = _VolumeMaterial.GetFloat(ShaderIDs.s_ExtinctionMultiplier);
// Get absorption from current material.
if (_SurfaceMaterial.HasVector(WaterRenderer.ShaderIDs.s_Absorption))
{
extinction = _SurfaceMaterial.GetVector(WaterRenderer.ShaderIDs.s_Absorption);
Shader.SetGlobalVector(WaterRenderer.ShaderIDs.s_Absorption, extinction);
}
// Do not use for culling because:
// - Scattering is not uniform due to anisotropy
// - Also need to take sun light into account
if (_SurfaceMaterial.HasProperty(WaterRenderer.ShaderIDs.s_Scattering))
{
var volumeExtinction = extinction + _SurfaceMaterial.GetVector(WaterRenderer.ShaderIDs.s_Scattering).XYZ();
volumeExtinction *= densityFactor;
minimumFogDensity = Mathf.Min(Mathf.Min(volumeExtinction.x, volumeExtinction.y), volumeExtinction.z);
Shader.SetGlobalFloat(WaterRenderer.ShaderIDs.s_VolumeExtinctionLength, -Mathf.Log(k_CullLimitMinimum) / minimumFogDensity);
}
extinction *= densityFactor;
minimumFogDensity = Mathf.Min(Mathf.Min(extinction.x, extinction.y), extinction.z);
// Prevent divide by zero.
minimumFogDensity = Mathf.Max(minimumFogDensity, 0.0001f);
}
if (_EnvironmentalInitialized)
{
_Water.UpdatePerCameraHeight(camera);
_ViewerWaterHeight = _Water._ViewerHeightAboveWaterPerCamera;
UpdateEnvironmentalLighting(camera, extinction, _ViewerWaterHeight);
}
if (Portaled || _ViewerWaterHeight > -5f)
{
RevertCulling();
return;
}
var extinctionLength = -Mathf.Log(_CullLimit) / minimumFogDensity;
foreach (var tile in _Water.Surface.Chunks)
{
if (tile.Rend == null || tile._Culled)
{
continue;
}
// Cull tiles the viewer cannot see through the underwater fog.
// Only run optimisation in play mode due to shared height above water.
if ((viewpoint - tile.Rend.bounds.ClosestPoint(viewpoint)).magnitude >= extinctionLength)
{
tile.Rend.enabled = false;
}
else
{
// Previous camera might have culled in underwater pass.
tile.Rend.enabled = true;
}
}
}
internal void OnEndCameraRendering(Camera camera)
{
RestoreEnvironmentalLighting();
RevertCulling();
_DoneMaskRead = false;
if (RenderPipelineHelper.IsLegacy)
{
OnAfterLegacyRender(camera);
}
}
void SetEnabled(bool previous, bool current)
{
if (previous == current) return;
if (_Water == null || !_Water.isActiveAndEnabled) return;
if (_Enabled) OnEnable(); else OnDisable();
}
void SetAffectsEnvironmentalLighting(bool previous, bool current)
{
if (previous == current) return;
if (_Water == null || !_Water.isActiveAndEnabled || !_Enabled) return;
if (_EnvironmentalLightingEnable) EnableEnvironmentalLighting(); else DisableEnvironmentalLighting();
}
#if UNITY_EDITOR
[@OnChange]
void OnChange(string propertyPath, object previousValue)
{
switch (propertyPath)
{
case nameof(_Enabled):
SetEnabled((bool)previousValue, _Enabled);
break;
case nameof(_EnvironmentalLightingEnable):
SetAffectsEnvironmentalLighting((bool)previousValue, _EnvironmentalLightingEnable);
break;
}
}
#endif
}
}

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: f2a407448acc440bab3414c17e06e7ba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- _VolumeGeometry: {instanceID: 0}
- _EffectMaterial: {instanceID: 0}
- _MaskMaterial: {instanceID: 0}
- _VolumeMaterial: {instanceID: 0}
- _ArtifactsShader: {fileID: 7200000, guid: 08549c36146ad4899a07193754b21ea2, type: 3}
executionOrder: 201
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: