'push'
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c581581e08ff40e689d952358cea7a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03d7c1db420e64f7c9894f9c2bdaae4c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7fba7ac72b29f4b12a08eb07d80a2703
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ab3e34da699e48eaa28a35fba152510
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,2 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 034fbbb00c45d493294db385ff38a629
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,2 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fa78b61faddf4493ae6381f85fb2572
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,2 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ce8d0e0aca6a47a9b0b5a8a7544a064
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -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:
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95fe330fa426a41c0b6379a1a2aae608
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42735f62770724c7488928b7e8185c9c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f72cef5b74e7e43c3bfceff42401fa82
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f64799bb3430e498a926913b81241f06
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
Reference in New Issue
Block a user