升级水插件
This commit is contained in:
@@ -16,14 +16,15 @@ namespace WaveHarmonic.Crest
|
||||
RTHandle _ColorTarget;
|
||||
RTHandle _DepthTarget;
|
||||
|
||||
bool _FirstRender = true;
|
||||
|
||||
readonly System.Action<CommandBuffer> _CopyColorTexture;
|
||||
readonly System.Action<CommandBuffer> _SetRenderTargetToBackBuffers;
|
||||
|
||||
public UnderwaterEffectPass(UnderwaterRenderer renderer)
|
||||
{
|
||||
_Renderer = renderer;
|
||||
_CopyColorTexture = new(CopyColorTexture);
|
||||
_SetRenderTargetToBackBuffers = new(SetRenderTargetToBackBuffers);
|
||||
}
|
||||
|
||||
void CopyColorTexture(CommandBuffer buffer)
|
||||
@@ -32,10 +33,20 @@ namespace WaveHarmonic.Crest
|
||||
CoreUtils.SetRenderTarget(buffer, _ColorTarget, _DepthTarget, ClearFlag.None);
|
||||
}
|
||||
|
||||
void SetRenderTargetToBackBuffers(CommandBuffer commands)
|
||||
{
|
||||
CoreUtils.SetRenderTarget(commands, _ColorTarget, _DepthTarget, ClearFlag.None);
|
||||
}
|
||||
|
||||
public void Allocate(GraphicsFormat format)
|
||||
{
|
||||
if (_Renderer.RenderBeforeTransparency && !_Renderer._NeedsColorTexture)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: There may other settings we want to set or bring in. Not MSAA since this is a resolved texture.
|
||||
_ColorTexture = RTHandles.Alloc
|
||||
_ColorTexture ??= RTHandles.Alloc
|
||||
(
|
||||
Vector2.one,
|
||||
TextureXR.slices,
|
||||
@@ -50,6 +61,11 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
public void ReAllocate(RenderTextureDescriptor descriptor)
|
||||
{
|
||||
if (_Renderer.RenderBeforeTransparency && !_Renderer._NeedsColorTexture)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Descriptor will not have MSAA bound.
|
||||
RenderPipelineCompatibilityHelper.ReAllocateIfNeeded(ref _ColorTexture, descriptor, name: "_Crest_UnderwaterCameraColorTexture");
|
||||
}
|
||||
@@ -62,22 +78,32 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
public void Execute(Camera camera, CommandBuffer buffer, RTHandle color, RTHandle depth, MaterialPropertyBlock mpb = null)
|
||||
{
|
||||
_Renderer.UpdateEffectMaterial(camera, _FirstRender);
|
||||
_Renderer.UpdateEffectMaterial(camera);
|
||||
|
||||
_ColorTarget = color;
|
||||
_DepthTarget = depth;
|
||||
CopyColorTexture(buffer);
|
||||
|
||||
buffer.SetGlobalTexture(UnderwaterRenderer.ShaderIDs.s_CameraColorTexture, _ColorTexture);
|
||||
if (!_Renderer.RenderBeforeTransparency || _Renderer._NeedsColorTexture)
|
||||
{
|
||||
buffer.SetGlobalTexture(UnderwaterRenderer.ShaderIDs.s_CameraColorTexture, _ColorTexture);
|
||||
}
|
||||
|
||||
_Renderer.ExecuteEffect(camera, buffer, _CopyColorTexture, mpb);
|
||||
if (!_Renderer.RenderBeforeTransparency)
|
||||
{
|
||||
CopyColorTexture(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: needed for HDRP, but can set it on pass instead.
|
||||
CoreUtils.SetRenderTarget(buffer, _ColorTarget, _DepthTarget, ClearFlag.None);
|
||||
}
|
||||
|
||||
_Renderer.ExecuteEffect(camera, buffer, _CopyColorTexture, _SetRenderTargetToBackBuffers, mpb);
|
||||
|
||||
// The last pass (uber post) does not resolve the texture.
|
||||
// Although, this is wasteful if the pass after this does a resolve.
|
||||
// Possibly a bug with Unity?
|
||||
buffer.ResolveAntiAliasedSurface(color);
|
||||
|
||||
_FirstRender = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.HighDefinition;
|
||||
using UnityEngine.Rendering.RendererUtils;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
@@ -17,13 +16,11 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
static UnderwaterRenderer s_Renderer;
|
||||
static UnderwaterEffectPass s_UnderwaterEffectPass;
|
||||
static UnderwaterEffectPassHDRP s_Instance;
|
||||
internal static UnderwaterEffectPassHDRP s_Instance;
|
||||
static CopyDepthBufferPassHDRP s_CopyDepthBufferPassHDRP;
|
||||
|
||||
static ShaderTagId[] s_ForwardShaderTags;
|
||||
|
||||
GameObject _GameObject;
|
||||
|
||||
public static void Enable(UnderwaterRenderer renderer)
|
||||
{
|
||||
var gameObject = CustomPassHelpers.CreateOrUpdate
|
||||
@@ -37,25 +34,24 @@ namespace WaveHarmonic.Crest
|
||||
(
|
||||
gameObject,
|
||||
ref s_CopyDepthBufferPassHDRP,
|
||||
"Copy Depth Buffer",
|
||||
UnderwaterRenderer.k_DrawVolume,
|
||||
CustomPassInjectionPoint.AfterOpaqueDepthAndNormal
|
||||
);
|
||||
|
||||
var isBeforeTransparentPass = renderer.RenderBeforeTransparency;
|
||||
|
||||
CustomPassHelpers.CreateOrUpdate
|
||||
(
|
||||
gameObject,
|
||||
ref s_Instance,
|
||||
k_Name,
|
||||
CustomPassInjectionPoint.BeforePostProcess
|
||||
UnderwaterRenderer.k_DrawVolume,
|
||||
GetInjectionPoint(isBeforeTransparentPass),
|
||||
// Higher number (priority) means execute earlier. Volume executes first.
|
||||
priority: 1
|
||||
);
|
||||
|
||||
s_Instance._GameObject = gameObject;
|
||||
|
||||
s_Renderer = renderer;
|
||||
s_UnderwaterEffectPass = new(renderer);
|
||||
|
||||
RenderPipelineManager.beginCameraRendering -= s_Instance.OnBeginCameraRendering;
|
||||
RenderPipelineManager.beginCameraRendering += s_Instance.OnBeginCameraRendering;
|
||||
}
|
||||
|
||||
public static void Disable()
|
||||
@@ -68,9 +64,17 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
}
|
||||
|
||||
void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
|
||||
static CustomPassInjectionPoint GetInjectionPoint(bool isBeforeTransparentPass)
|
||||
{
|
||||
return isBeforeTransparentPass
|
||||
? CustomPassInjectionPoint.BeforeTransparent
|
||||
: CustomPassInjectionPoint.BeforePostProcess;
|
||||
}
|
||||
|
||||
internal void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
|
||||
{
|
||||
s_CopyDepthBufferPassHDRP.enabled = s_Renderer.UseStencilBuffer;
|
||||
s_Instance._Volume.injectionPoint = GetInjectionPoint(s_Renderer.RenderBeforeTransparency);
|
||||
}
|
||||
|
||||
protected override void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd)
|
||||
@@ -94,8 +98,6 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
protected override void Cleanup()
|
||||
{
|
||||
RenderPipelineManager.beginCameraRendering -= s_Instance.OnBeginCameraRendering;
|
||||
|
||||
s_UnderwaterEffectPass?.Release();
|
||||
}
|
||||
|
||||
@@ -108,44 +110,15 @@ namespace WaveHarmonic.Crest
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate here in case user changes options and we skipped allocation in Setup.
|
||||
s_UnderwaterEffectPass.Allocate(context.cameraColorBuffer.rt.graphicsFormat);
|
||||
|
||||
// Create a separate stencil buffer context by using a depth buffer copy if needed.
|
||||
var depthBuffer = s_Renderer.UseStencilBuffer
|
||||
? s_CopyDepthBufferPassHDRP._DepthBufferCopy
|
||||
: context.cameraDepthBuffer;
|
||||
|
||||
s_UnderwaterEffectPass.Execute(camera, context.cmd, context.cameraColorBuffer, depthBuffer, context.propertyBlock);
|
||||
|
||||
// Renders transparent objects after the underwater effect. Using the correct
|
||||
// shader, the above water portion of the object is rendered normally (in the
|
||||
// transparent pass), and the below water portion is rendered here with underwater
|
||||
// applied.
|
||||
// See the following for reference:
|
||||
// https://github.com/Unity-Technologies/Graphics/blob/master/Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPass/DrawRenderersCustomPass.cs
|
||||
if (s_Renderer.EnableShaderAPI)
|
||||
{
|
||||
var renderConfig = HDUtils.GetRendererConfiguration
|
||||
(
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
context.hdCamera.frameSettings.IsEnabled(FrameSettingsField.AdaptiveProbeVolume),
|
||||
#else
|
||||
context.hdCamera.frameSettings.IsEnabled(FrameSettingsField.ProbeVolume),
|
||||
#endif
|
||||
context.hdCamera.frameSettings.IsEnabled(FrameSettingsField.Shadowmask)
|
||||
);
|
||||
|
||||
var result = new RendererListDesc(s_ForwardShaderTags, context.cullingResults, context.hdCamera.camera)
|
||||
{
|
||||
rendererConfiguration = renderConfig,
|
||||
renderQueueRange = GetRenderQueueRange(RenderQueueType.AllTransparent),
|
||||
sortingCriteria = SortingCriteria.CommonTransparent,
|
||||
excludeObjectMotionVectors = false,
|
||||
layerMask = s_Renderer._TransparentObjectLayers,
|
||||
};
|
||||
|
||||
context.cmd.EnableShaderKeyword(UnderwaterRenderer.k_KeywordUnderwaterObjects);
|
||||
CoreUtils.DrawRendererList(context.renderContext, context.cmd, context.renderContext.CreateRendererList(result));
|
||||
context.cmd.DisableShaderKeyword(UnderwaterRenderer.k_KeywordUnderwaterObjects);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,13 +139,11 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
var buffer = context.cmd;
|
||||
|
||||
buffer.SetRenderTarget(BuiltinRenderTextureType.None, _DepthBufferCopy);
|
||||
buffer.ClearRenderTarget(RTClearFlags.Depth, Color.black, 1, 0);
|
||||
|
||||
// NOTE: previously we cleared the target depth first due to artifacts.
|
||||
buffer.CopyTexture(context.cameraDepthBuffer.rt, _DepthBufferCopy.rt);
|
||||
|
||||
// Clear the stencil component just in case.
|
||||
buffer.ClearRenderTarget(RTClearFlags.Stencil, Color.black, 1, 0);
|
||||
CoreUtils.SetRenderTarget(buffer, BuiltinRenderTextureType.None, _DepthBufferCopy, ClearFlag.Stencil);
|
||||
}
|
||||
|
||||
protected override void Cleanup()
|
||||
|
||||
@@ -12,46 +12,16 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
partial class UnderwaterEffectPassURP
|
||||
{
|
||||
class PassData
|
||||
{
|
||||
#pragma warning disable IDE1006 // Naming Styles
|
||||
public UniversalCameraData cameraData;
|
||||
public RenderGraphHelper.Handle colorTargetHandle;
|
||||
public RenderGraphHelper.Handle depthTargetHandle;
|
||||
#pragma warning restore IDE1006 // Naming Styles
|
||||
|
||||
public void Init(ContextContainer frameData, IUnsafeRenderGraphBuilder builder = null)
|
||||
{
|
||||
var resources = frameData.Get<UniversalResourceData>();
|
||||
cameraData = frameData.Get<UniversalCameraData>();
|
||||
|
||||
if (builder == null)
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
colorTargetHandle = cameraData.renderer.cameraColorTargetHandle;
|
||||
depthTargetHandle = cameraData.renderer.cameraDepthTargetHandle;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
else
|
||||
{
|
||||
colorTargetHandle = resources.activeColorTexture;
|
||||
depthTargetHandle = resources.activeDepthTexture;
|
||||
builder.UseTexture(colorTargetHandle, AccessFlags.ReadWrite);
|
||||
builder.UseTexture(depthTargetHandle, AccessFlags.ReadWrite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly PassData _PassData = new();
|
||||
readonly RenderGraphHelper.PassData _PassData = new();
|
||||
|
||||
public override void RecordRenderGraph(RenderGraph graph, ContextContainer frame)
|
||||
{
|
||||
using (var builder = graph.AddUnsafePass<PassData>(k_Name, out var data))
|
||||
using (var builder = graph.AddUnsafePass<RenderGraphHelper.PassData>(k_Name, out var data))
|
||||
{
|
||||
data.Init(frame, builder);
|
||||
builder.AllowPassCulling(false);
|
||||
|
||||
builder.SetRenderFunc<PassData>((data, context) =>
|
||||
builder.SetRenderFunc<RenderGraphHelper.PassData>((data, context) =>
|
||||
{
|
||||
var buffer = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
|
||||
OnSetup(buffer, data);
|
||||
@@ -76,60 +46,6 @@ namespace WaveHarmonic.Crest
|
||||
context.ExecuteCommandBuffer(buffer);
|
||||
CommandBufferPool.Release(buffer);
|
||||
}
|
||||
|
||||
partial class RenderObjectsWithoutFogPass
|
||||
{
|
||||
class PassData
|
||||
{
|
||||
#pragma warning disable IDE1006 // Naming Styles
|
||||
public UniversalCameraData cameraData;
|
||||
public UniversalLightData lightData;
|
||||
public UniversalRenderingData renderingData;
|
||||
public CullingResults cullResults;
|
||||
#pragma warning restore IDE1006 // Naming Styles
|
||||
|
||||
public void Init(ContextContainer frameData, IUnsafeRenderGraphBuilder builder = null)
|
||||
{
|
||||
cameraData = frameData.Get<UniversalCameraData>();
|
||||
lightData = frameData.Get<UniversalLightData>();
|
||||
renderingData = frameData.Get<UniversalRenderingData>();
|
||||
cullResults = renderingData.cullResults;
|
||||
}
|
||||
}
|
||||
|
||||
readonly PassData _PassData = new();
|
||||
|
||||
public override void RecordRenderGraph(RenderGraph graph, ContextContainer frame)
|
||||
{
|
||||
using (var builder = graph.AddUnsafePass<PassData>(k_Name, out var data))
|
||||
{
|
||||
data.Init(frame, builder);
|
||||
builder.AllowPassCulling(false);
|
||||
|
||||
builder.SetRenderFunc<PassData>((data, context) =>
|
||||
{
|
||||
var buffer = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
|
||||
Execute(context.GetRenderContext(), buffer, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[System.Obsolete]
|
||||
public override void OnCameraSetup(CommandBuffer buffer, ref RenderingData data)
|
||||
{
|
||||
_PassData.Init(data.GetFrameData());
|
||||
}
|
||||
|
||||
[System.Obsolete]
|
||||
public override void Execute(ScriptableRenderContext context, ref RenderingData data)
|
||||
{
|
||||
_PassData.Init(data.GetFrameData());
|
||||
var buffer = CommandBufferPool.Get(k_Name);
|
||||
Execute(context, buffer, _PassData);
|
||||
context.ExecuteCommandBuffer(buffer);
|
||||
CommandBufferPool.Release(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial class CopyDepthBufferPassURP
|
||||
@@ -150,11 +66,15 @@ namespace WaveHarmonic.Crest
|
||||
if (builder == null)
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
colorTargetHandle = cameraData.renderer.cameraColorTargetHandle;
|
||||
depthTargetHandle = cameraData.renderer.cameraDepthTargetHandle;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
else
|
||||
{
|
||||
// We need reset render targets to these before the next pass, but we do not read
|
||||
// or write to the color target.
|
||||
colorTargetHandle = resources.activeColorTexture;
|
||||
depthTargetHandle = resources.activeDepthTexture;
|
||||
builder.UseTexture(depthTargetHandle, AccessFlags.ReadWrite);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#if d_UnityURP
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
@@ -13,12 +12,11 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed partial class UnderwaterEffectPassURP : ScriptableRenderPass
|
||||
{
|
||||
const string k_Name = "Crest Underwater Effect";
|
||||
const string k_Name = "Crest.DrawWater/Volume";
|
||||
|
||||
UnderwaterRenderer _Renderer;
|
||||
|
||||
static UnderwaterEffectPassURP s_Instance;
|
||||
RenderObjectsWithoutFogPass _ApplyFogToTransparentObjects;
|
||||
internal static UnderwaterEffectPassURP s_Instance;
|
||||
UnderwaterEffectPass _UnderwaterEffectPass;
|
||||
CopyDepthBufferPassURP _CopyDepthBufferPass;
|
||||
|
||||
@@ -27,7 +25,6 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
public UnderwaterEffectPassURP()
|
||||
{
|
||||
renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
|
||||
ConfigureInput(ScriptableRenderPassInput.Color | ScriptableRenderPassInput.Depth);
|
||||
}
|
||||
|
||||
@@ -38,18 +35,14 @@ namespace WaveHarmonic.Crest
|
||||
s_Instance = new();
|
||||
s_Instance._Renderer = renderer;
|
||||
s_Instance._CopyDepthBufferPass = new(RenderPassEvent.AfterRenderingOpaques);
|
||||
s_Instance._ApplyFogToTransparentObjects = new();
|
||||
}
|
||||
|
||||
RenderPipelineManager.beginCameraRendering -= s_Instance.EnqueuePass;
|
||||
RenderPipelineManager.beginCameraRendering += s_Instance.EnqueuePass;
|
||||
RenderPipelineManager.activeRenderPipelineTypeChanged -= Disable;
|
||||
RenderPipelineManager.activeRenderPipelineTypeChanged += Disable;
|
||||
}
|
||||
|
||||
public static void Disable()
|
||||
{
|
||||
if (s_Instance != null) RenderPipelineManager.beginCameraRendering -= s_Instance.EnqueuePass;
|
||||
RenderPipelineManager.activeRenderPipelineTypeChanged -= Disable;
|
||||
|
||||
s_Instance?._UnderwaterEffectPass?.Release();
|
||||
@@ -57,13 +50,15 @@ namespace WaveHarmonic.Crest
|
||||
s_Instance = null;
|
||||
}
|
||||
|
||||
void EnqueuePass(ScriptableRenderContext context, Camera camera)
|
||||
internal void EnqueuePass(ScriptableRenderContext context, Camera camera)
|
||||
{
|
||||
if (!_Renderer.ShouldRender(camera, UnderwaterRenderer.Pass.Effect))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_Instance.renderPassEvent = _Renderer.RenderBeforeTransparency ? WaterRenderer.k_WaterRenderPassEvent : RenderPassEvent.AfterRenderingTransparents;
|
||||
|
||||
var renderer = camera.GetUniversalAdditionalCameraData().scriptableRenderer;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
@@ -80,25 +75,40 @@ namespace WaveHarmonic.Crest
|
||||
_UnderwaterEffectPass ??= new(_Renderer);
|
||||
|
||||
renderer.EnqueuePass(s_Instance);
|
||||
|
||||
if (_Renderer.EnableShaderAPI)
|
||||
{
|
||||
renderer.EnqueuePass(_ApplyFogToTransparentObjects);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
void OnSetup(CommandBuffer buffer, PassData data)
|
||||
bool _ErrorMissingColorTarget;
|
||||
|
||||
void OnSetup(CommandBuffer buffer, RenderGraphHelper.PassData data)
|
||||
{
|
||||
_ColorBuffer = data.colorTargetHandle.Texture;
|
||||
_DepthBuffer = data.depthTargetHandle.Texture;
|
||||
|
||||
// Unity bug
|
||||
if (_ColorBuffer?.rt == null)
|
||||
{
|
||||
if (!_ErrorMissingColorTarget)
|
||||
{
|
||||
Debug.LogError($"Crest: Your current URP setup has a Unity bug which prevents underwater from rendering on this camera ({data.cameraData.camera.name}). It is too complicated for us to advise which combination of settings are the issue (sorry), but they will be on either the URP asset or renderer file.");
|
||||
_ErrorMissingColorTarget = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: renderingData.cameraData.cameraTargetDescriptor?
|
||||
_UnderwaterEffectPass.ReAllocate(_ColorBuffer.rt.descriptor);
|
||||
}
|
||||
|
||||
void Execute(ScriptableRenderContext context, CommandBuffer buffer, PassData data)
|
||||
void Execute(ScriptableRenderContext context, CommandBuffer buffer, RenderGraphHelper.PassData data)
|
||||
{
|
||||
// Unity bug
|
||||
if (_ColorBuffer?.rt == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_Renderer.UseStencilBuffer)
|
||||
{
|
||||
_DepthBuffer = _CopyDepthBufferPass._DepthBufferCopy;
|
||||
@@ -131,120 +141,13 @@ namespace WaveHarmonic.Crest
|
||||
CommandBufferPool.Release(buffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Renders transparent objects after the underwater effect. Using the correct
|
||||
// shader, the above water portion of the object is rendered normally (in the
|
||||
// transparent pass), and the below water portion is rendered here with underwater
|
||||
// applied.
|
||||
sealed partial class RenderObjectsWithoutFogPass : ScriptableRenderPass
|
||||
{
|
||||
FilteringSettings _FilteringSettings;
|
||||
|
||||
static readonly List<ShaderTagId> s_ShaderTagIdList = new()
|
||||
{
|
||||
new("SRPDefaultUnlit"),
|
||||
new("UniversalForward"),
|
||||
new("UniversalForwardOnly"),
|
||||
new("LightweightForward"),
|
||||
};
|
||||
|
||||
public RenderObjectsWithoutFogPass()
|
||||
{
|
||||
renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
|
||||
_FilteringSettings = new(RenderQueueRange.transparent, 0);
|
||||
}
|
||||
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
void Execute(ScriptableRenderContext context, CommandBuffer buffer, PassData renderingData)
|
||||
#else
|
||||
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
|
||||
#endif
|
||||
{
|
||||
_FilteringSettings.layerMask = s_Instance._Renderer._TransparentObjectLayers;
|
||||
|
||||
#if !UNITY_6000_0_OR_NEWER
|
||||
var buffer = CommandBufferPool.Get("Crest Underwater Objects");
|
||||
#endif
|
||||
|
||||
// Disable Unity's fog keywords as there is no option to ignore fog for the Shader Graph.
|
||||
if (RenderSettings.fog)
|
||||
{
|
||||
switch (RenderSettings.fogMode)
|
||||
{
|
||||
case FogMode.Exponential:
|
||||
buffer.DisableShaderKeyword("FOG_EXP");
|
||||
break;
|
||||
case FogMode.Linear:
|
||||
buffer.DisableShaderKeyword("FOG_LINEAR");
|
||||
break;
|
||||
case FogMode.ExponentialSquared:
|
||||
buffer.DisableShaderKeyword("FOG_EXP2");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.EnableShaderKeyword(UnderwaterRenderer.k_KeywordUnderwaterObjects);
|
||||
// If we want anything to apply to DrawRenderers, it has to be executed before:
|
||||
// https://docs.unity3d.com/ScriptReference/Rendering.ScriptableRenderContext.DrawRenderers.html
|
||||
context.ExecuteCommandBuffer(buffer);
|
||||
buffer.Clear();
|
||||
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
var drawingSettings = RenderingUtils.CreateDrawingSettings
|
||||
(
|
||||
s_ShaderTagIdList,
|
||||
renderingData.renderingData,
|
||||
renderingData.cameraData,
|
||||
renderingData.lightData,
|
||||
SortingCriteria.CommonTransparent
|
||||
);
|
||||
|
||||
var parameters = new RendererListParams(renderingData.cullResults, drawingSettings, _FilteringSettings);
|
||||
var list = context.CreateRendererList(ref parameters);
|
||||
|
||||
buffer.DrawRendererList(list);
|
||||
#else
|
||||
var drawingSettings = CreateDrawingSettings
|
||||
(
|
||||
s_ShaderTagIdList,
|
||||
ref renderingData,
|
||||
SortingCriteria.CommonTransparent
|
||||
);
|
||||
|
||||
context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref _FilteringSettings);
|
||||
#endif
|
||||
|
||||
// Revert fog keywords.
|
||||
if (RenderSettings.fog)
|
||||
{
|
||||
switch (RenderSettings.fogMode)
|
||||
{
|
||||
case FogMode.Exponential:
|
||||
buffer.EnableShaderKeyword("FOG_EXP");
|
||||
break;
|
||||
case FogMode.Linear:
|
||||
buffer.EnableShaderKeyword("FOG_LINEAR");
|
||||
break;
|
||||
case FogMode.ExponentialSquared:
|
||||
buffer.EnableShaderKeyword("FOG_EXP2");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.DisableShaderKeyword(UnderwaterRenderer.k_KeywordUnderwaterObjects);
|
||||
|
||||
#if !UNITY_6000_0_OR_NEWER
|
||||
context.ExecuteCommandBuffer(buffer);
|
||||
CommandBufferPool.Release(buffer);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copies the depth buffer to avoid conflicts when using the stencil buffer.
|
||||
sealed partial class CopyDepthBufferPassURP : ScriptableRenderPass
|
||||
{
|
||||
const string k_Name = "Crest Copy Depth Buffer";
|
||||
RTHandle _ColorBuffer;
|
||||
RTHandle _DepthBuffer;
|
||||
public RTHandle _DepthBufferCopy;
|
||||
|
||||
@@ -264,9 +167,11 @@ namespace WaveHarmonic.Crest
|
||||
descriptor.bindMS = descriptor.msaaSamples > 1;
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
RenderingUtils.ReAllocateHandleIfNeeded(ref _DepthBufferCopy, descriptor, FilterMode.Point, name: "Crest Copied Depth Buffer");
|
||||
_ColorBuffer = data.colorTargetHandle;
|
||||
_DepthBuffer = data.depthTargetHandle;
|
||||
#else
|
||||
RenderingUtils.ReAllocateIfNeeded(ref _DepthBufferCopy, descriptor, FilterMode.Point, name: "Crest Copied Depth Buffer");
|
||||
_ColorBuffer = data.cameraData.renderer.cameraColorTargetHandle;
|
||||
_DepthBuffer = data.cameraData.renderer.cameraDepthTargetHandle;
|
||||
#endif
|
||||
}
|
||||
@@ -277,19 +182,26 @@ namespace WaveHarmonic.Crest
|
||||
public override void Execute(ScriptableRenderContext context, ref RenderingData data)
|
||||
#endif
|
||||
{
|
||||
// Just in case.
|
||||
if (_ColorBuffer == null || _DepthBuffer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if !UNITY_6000_0_OR_NEWER
|
||||
var buffer = CommandBufferPool.Get(k_Name);
|
||||
#endif
|
||||
|
||||
// Must clear even though we are overwriting or there will be strange artifacts on new writes.
|
||||
// This could be a Unity bug and may be worth reporting.
|
||||
buffer.SetRenderTarget(BuiltinRenderTextureType.None, _DepthBufferCopy);
|
||||
buffer.ClearRenderTarget(RTClearFlags.Depth, Color.black, 1, 0);
|
||||
|
||||
// NOTE: previously we cleared the target depth first due to artifacts.
|
||||
buffer.CopyTexture(_DepthBuffer.rt, _DepthBufferCopy.rt);
|
||||
|
||||
// Clear the stencil component just in case.
|
||||
buffer.ClearRenderTarget(RTClearFlags.Stencil, Color.black, 1, 0);
|
||||
// Previously we passed BuiltinRenderTextureType.None for color but this made the
|
||||
// scene disappear in the scene view on DX11 only.
|
||||
CoreUtils.SetRenderTarget(buffer, _ColorBuffer, _DepthBufferCopy, ClearFlag.Stencil);
|
||||
|
||||
// Required for Unity 6+.
|
||||
CoreUtils.SetRenderTarget(buffer, _ColorBuffer, _DepthBuffer);
|
||||
|
||||
#if !UNITY_6000_0_OR_NEWER
|
||||
context.ExecuteCommandBuffer(buffer);
|
||||
|
||||
@@ -1,140 +1,2 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class UnderwaterMaskPass
|
||||
{
|
||||
readonly UnderwaterRenderer _Renderer;
|
||||
#if d_CrestPortals
|
||||
readonly Portals.PortalRenderer _Portals;
|
||||
#endif
|
||||
|
||||
RTHandle _MaskTexture;
|
||||
RTHandle _DepthTexture;
|
||||
RenderTargetIdentifier _MaskTarget;
|
||||
RenderTargetIdentifier _DepthTarget;
|
||||
|
||||
public UnderwaterMaskPass(UnderwaterRenderer renderer)
|
||||
{
|
||||
_Renderer = renderer;
|
||||
#if d_CrestPortals
|
||||
_Portals = renderer._Portals;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Allocate()
|
||||
{
|
||||
_MaskTexture = RTHandles.Alloc
|
||||
(
|
||||
scaleFactor: Vector2.one,
|
||||
slices: TextureXR.slices,
|
||||
dimension: TextureXR.dimension,
|
||||
depthBufferBits: DepthBits.None,
|
||||
colorFormat: GraphicsFormat.R16_SFloat,
|
||||
enableRandomWrite: true,
|
||||
useDynamicScale: true,
|
||||
name: "_Crest_WaterMask"
|
||||
);
|
||||
|
||||
_MaskTarget = new(_MaskTexture, mipLevel: 0, CubemapFace.Unknown, depthSlice: -1);
|
||||
|
||||
_DepthTexture = RTHandles.Alloc
|
||||
(
|
||||
scaleFactor: Vector2.one,
|
||||
slices: TextureXR.slices,
|
||||
dimension: TextureXR.dimension,
|
||||
depthBufferBits: UnderwaterRenderer.k_DepthBits,
|
||||
colorFormat: GraphicsFormat.None,
|
||||
enableRandomWrite: false,
|
||||
useDynamicScale: true,
|
||||
name: "_Crest_WaterMaskDepth"
|
||||
);
|
||||
|
||||
#if d_CrestPortals
|
||||
// For HDRP we cannot allocate in OnEnable as RTHandle will complain.
|
||||
if (_Portals.Active)
|
||||
{
|
||||
_Portals.Allocate();
|
||||
}
|
||||
#endif
|
||||
|
||||
_DepthTarget = new(_DepthTexture, mipLevel: 0, CubemapFace.Unknown, depthSlice: -1);
|
||||
|
||||
_Renderer.SetUpArtifactsShader();
|
||||
}
|
||||
|
||||
// We should not have to reallocate, but URP will raise errors when an option like HDR is changed if we do not.
|
||||
public void ReAllocate(RenderTextureDescriptor descriptor)
|
||||
{
|
||||
// Shared settings. Enabling MSAA might be a good idea except cannot enable random writes. Having a raster
|
||||
// shader to remove artifacts is a workaround.
|
||||
descriptor.bindMS = false;
|
||||
descriptor.msaaSamples = 1;
|
||||
|
||||
descriptor.graphicsFormat = GraphicsFormat.None;
|
||||
|
||||
if (RenderPipelineCompatibilityHelper.ReAllocateIfNeeded(ref _DepthTexture, descriptor, name: "_Crest_WaterMaskDepth"))
|
||||
{
|
||||
_DepthTarget = new(_DepthTexture, mipLevel: 0, CubemapFace.Unknown, depthSlice: -1);
|
||||
}
|
||||
|
||||
#if d_CrestPortals
|
||||
if (_Portals.Active)
|
||||
{
|
||||
_Portals.ReAllocate(descriptor);
|
||||
}
|
||||
#endif
|
||||
|
||||
descriptor.graphicsFormat = GraphicsFormat.R16_SFloat;
|
||||
descriptor.enableRandomWrite = true;
|
||||
descriptor.depthBufferBits = 0;
|
||||
|
||||
if (RenderPipelineCompatibilityHelper.ReAllocateIfNeeded(ref _MaskTexture, descriptor, name: "_Crest_WaterMask"))
|
||||
{
|
||||
_MaskTarget = new(_MaskTexture, mipLevel: 0, CubemapFace.Unknown, depthSlice: -1);
|
||||
}
|
||||
}
|
||||
|
||||
public void Release()
|
||||
{
|
||||
_MaskTexture?.Release();
|
||||
_DepthTexture?.Release();
|
||||
|
||||
#if d_CrestPortals
|
||||
if (_Portals.Active)
|
||||
{
|
||||
_Portals.Release();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Execute(Camera camera, CommandBuffer buffer)
|
||||
{
|
||||
#if d_CrestPortals
|
||||
// Populate water volume before mask so we can use the stencil.
|
||||
if (_Portals.Active)
|
||||
{
|
||||
_Portals.RenderMask(camera, buffer, _Renderer._MaskMaterial);
|
||||
_Portals.RenderStencil(buffer, _DepthTexture);
|
||||
}
|
||||
#endif
|
||||
|
||||
// For HDRP software dynamic scaling to work.
|
||||
CoreUtils.SetRenderTarget(buffer, _MaskTexture, _DepthTexture);
|
||||
Helpers.ScaleViewport(camera, buffer, _MaskTexture);
|
||||
|
||||
_Renderer.SetUpMask(buffer, _MaskTarget, _DepthTarget);
|
||||
_Renderer.PopulateMask(buffer, camera);
|
||||
|
||||
var size = _MaskTexture.GetScaledSize(_MaskTexture.rtHandleProperties.currentViewportSize);
|
||||
var descriptor = _MaskTexture.rt.descriptor;
|
||||
descriptor.width = size.x; descriptor.height = size.y;
|
||||
_Renderer.FixMaskArtefacts(buffer, descriptor, _MaskTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +1,2 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
#if d_UnityHDRP
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.HighDefinition;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class UnderwaterMaskPassHDRP : CustomPass
|
||||
{
|
||||
const string k_Name = "Underwater Mask";
|
||||
|
||||
static UnderwaterRenderer s_Renderer;
|
||||
static UnderwaterMaskPass s_UnderwaterMaskPass;
|
||||
static UnderwaterMaskPassHDRP s_Instance;
|
||||
GameObject _GameObject;
|
||||
|
||||
public static void Enable(UnderwaterRenderer renderer)
|
||||
{
|
||||
var gameObject = CustomPassHelpers.CreateOrUpdate
|
||||
(
|
||||
parent: renderer._Water.Container.transform,
|
||||
k_Name,
|
||||
hide: !renderer._Water._Debug._ShowHiddenObjects
|
||||
);
|
||||
|
||||
CustomPassHelpers.CreateOrUpdate
|
||||
(
|
||||
gameObject,
|
||||
ref s_Instance,
|
||||
k_Name,
|
||||
CustomPassInjectionPoint.BeforeRendering
|
||||
);
|
||||
|
||||
s_Instance._GameObject = gameObject;
|
||||
|
||||
s_Renderer = renderer;
|
||||
s_UnderwaterMaskPass = new(renderer);
|
||||
}
|
||||
|
||||
public static void Disable()
|
||||
{
|
||||
// It should be safe to rely on this reference for this reference to fail.
|
||||
if (s_Instance != null && s_Instance._GameObject != null)
|
||||
{
|
||||
// Will also trigger Cleanup below.
|
||||
s_Instance._GameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Setup(ScriptableRenderContext renderContext, CommandBuffer cmd)
|
||||
{
|
||||
s_UnderwaterMaskPass.Allocate();
|
||||
}
|
||||
|
||||
protected override void Cleanup()
|
||||
{
|
||||
s_UnderwaterMaskPass?.Release();
|
||||
}
|
||||
|
||||
protected override void Execute(CustomPassContext context)
|
||||
{
|
||||
var camera = context.hdCamera.camera;
|
||||
|
||||
if (!s_Renderer.ShouldRender(camera, UnderwaterRenderer.Pass.Mask))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_UnderwaterMaskPass.Execute(camera, context.cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // d_UnityHDRP
|
||||
|
||||
@@ -1,106 +1,2 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
#if d_UnityURP
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed partial class UnderwaterMaskPassURP : ScriptableRenderPass
|
||||
{
|
||||
const string k_Name = "Crest Underwater Mask";
|
||||
static UnderwaterMaskPassURP s_Instance;
|
||||
UnderwaterRenderer _Renderer;
|
||||
UnderwaterMaskPass _UnderwaterMaskPass;
|
||||
|
||||
public UnderwaterMaskPassURP()
|
||||
{
|
||||
// Will always execute and matrices will be ready.
|
||||
renderPassEvent = RenderPassEvent.BeforeRenderingPrePasses;
|
||||
}
|
||||
|
||||
public static void Enable(UnderwaterRenderer renderer)
|
||||
{
|
||||
s_Instance ??= new();
|
||||
s_Instance._Renderer = renderer;
|
||||
|
||||
RenderPipelineManager.beginCameraRendering -= s_Instance.EnqueuePass;
|
||||
RenderPipelineManager.beginCameraRendering += s_Instance.EnqueuePass;
|
||||
RenderPipelineManager.activeRenderPipelineTypeChanged -= Disable;
|
||||
RenderPipelineManager.activeRenderPipelineTypeChanged += Disable;
|
||||
}
|
||||
|
||||
public static void Disable()
|
||||
{
|
||||
if (s_Instance != null) RenderPipelineManager.beginCameraRendering -= s_Instance.EnqueuePass;
|
||||
RenderPipelineManager.activeRenderPipelineTypeChanged -= Disable;
|
||||
|
||||
s_Instance?._UnderwaterMaskPass?.Release();
|
||||
s_Instance = null;
|
||||
}
|
||||
|
||||
void EnqueuePass(ScriptableRenderContext context, Camera camera)
|
||||
{
|
||||
if (!_Renderer.ShouldRender(camera, UnderwaterRenderer.Pass.Mask))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var renderer = camera.GetUniversalAdditionalCameraData().scriptableRenderer;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (renderer == null) return;
|
||||
#endif
|
||||
|
||||
if (_UnderwaterMaskPass == null)
|
||||
{
|
||||
_UnderwaterMaskPass = new(_Renderer);
|
||||
_UnderwaterMaskPass.Allocate();
|
||||
}
|
||||
|
||||
// Enqueue the pass. This happens every frame.
|
||||
renderer.EnqueuePass(this);
|
||||
}
|
||||
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
class PassData
|
||||
{
|
||||
public UniversalCameraData _CameraData;
|
||||
public UnderwaterMaskPass _UnderwaterMaskPass;
|
||||
}
|
||||
|
||||
public override void RecordRenderGraph(UnityEngine.Rendering.RenderGraphModule.RenderGraph graph, ContextContainer frame)
|
||||
{
|
||||
using (var builder = graph.AddUnsafePass<PassData>(k_Name, out var data))
|
||||
{
|
||||
builder.AllowPassCulling(false);
|
||||
|
||||
data._CameraData = frame.Get<UniversalCameraData>();
|
||||
data._UnderwaterMaskPass = _UnderwaterMaskPass;
|
||||
|
||||
builder.SetRenderFunc<PassData>((data, context) =>
|
||||
{
|
||||
var buffer = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
|
||||
data._UnderwaterMaskPass.ReAllocate(data._CameraData.cameraTargetDescriptor);
|
||||
data._UnderwaterMaskPass.Execute(data._CameraData.camera, buffer);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[System.Obsolete]
|
||||
#endif
|
||||
public override void Execute(ScriptableRenderContext context, ref RenderingData data)
|
||||
{
|
||||
var buffer = CommandBufferPool.Get(k_Name);
|
||||
_UnderwaterMaskPass.ReAllocate(data.cameraData.cameraTargetDescriptor);
|
||||
_UnderwaterMaskPass.Execute(data.cameraData.camera, buffer);
|
||||
context.ExecuteCommandBuffer(buffer);
|
||||
CommandBufferPool.Release(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // d_UnityURP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Crest Water System
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
@@ -9,16 +9,16 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
partial class UnderwaterRenderer
|
||||
{
|
||||
const string k_KeywordFullScreenEffect = "_FULL_SCREEN_EFFECT";
|
||||
internal const string k_ShaderNameEffect = "Crest/Underwater";
|
||||
internal const string k_DrawVolume = "Crest.DrawWater/Volume";
|
||||
const string k_KeywordDebugVisualizeMask = "_DEBUG_VISUALIZE_MASK";
|
||||
const string k_KeywordDebugVisualizeStencil = "_DEBUG_VISUALIZE_STENCIL";
|
||||
internal const string k_KeywordUnderwaterObjects = "CREST_UNDERWATER_OBJECTS_PASS";
|
||||
internal const string k_SampleSphericalHarmonicsMarker = "Crest.UnderwaterRenderer.SampleSphericalHarmonics";
|
||||
|
||||
static readonly Unity.Profiling.ProfilerMarker s_SampleSphericalHarmonicsMarker = new(k_SampleSphericalHarmonicsMarker);
|
||||
|
||||
static partial class ShaderIDs
|
||||
{
|
||||
// Local
|
||||
public static readonly int s_HorizonNormal = Shader.PropertyToID("_Crest_HorizonNormal");
|
||||
|
||||
// Global
|
||||
public static readonly int s_CameraColorTexture = Shader.PropertyToID("_Crest_CameraColorTexture");
|
||||
public static readonly int s_WaterVolumeStencil = Shader.PropertyToID("_Crest_WaterVolumeStencil");
|
||||
@@ -26,9 +26,10 @@ namespace WaveHarmonic.Crest
|
||||
public static readonly int s_ExtinctionMultiplier = Shader.PropertyToID("_Crest_ExtinctionMultiplier");
|
||||
public static readonly int s_UnderwaterEnvironmentalLightingWeight = Shader.PropertyToID("_Crest_UnderwaterEnvironmentalLightingWeight");
|
||||
|
||||
// Built-ins
|
||||
public static readonly int s_WorldSpaceLightPos0 = Shader.PropertyToID("_WorldSpaceLightPos0");
|
||||
public static readonly int s_LightColor0 = Shader.PropertyToID("_LightColor0");
|
||||
public static readonly int s_OutScatteringFactor = Shader.PropertyToID("_Crest_OutScatteringFactor");
|
||||
public static readonly int s_OutScatteringExtinctionFactor = Shader.PropertyToID("_Crest_OutScatteringExtinctionFactor");
|
||||
public static readonly int s_SunBoost = Shader.PropertyToID("_Crest_SunBoost");
|
||||
public static readonly int s_DataSliceOffset = Shader.PropertyToID("_Crest_DataSliceOffset");
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +44,7 @@ namespace WaveHarmonic.Crest
|
||||
Material _CurrentWaterMaterial;
|
||||
readonly UnderwaterSphericalHarmonicsData _SphericalHarmonicsData = new();
|
||||
System.Action<CommandBuffer> _CopyColor;
|
||||
System.Action<CommandBuffer> _SetRenderTargetToBackBuffers;
|
||||
|
||||
RenderTargetIdentifier _ColorTarget = new
|
||||
(
|
||||
@@ -66,12 +68,20 @@ namespace WaveHarmonic.Crest
|
||||
-1
|
||||
);
|
||||
|
||||
// Requested the temporary color texture.
|
||||
internal bool _NeedsColorTexture;
|
||||
|
||||
sealed class UnderwaterSphericalHarmonicsData
|
||||
{
|
||||
internal Color[] _AmbientLighting = new Color[1];
|
||||
internal Vector3[] _DirectionsSH = { new(0.0f, 0.0f, 0.0f) };
|
||||
}
|
||||
|
||||
void SetRenderTargetToBackBuffers(CommandBuffer commands)
|
||||
{
|
||||
commands.SetRenderTarget(_ColorTarget);
|
||||
}
|
||||
|
||||
void CopyColorTexture(CommandBuffer buffer)
|
||||
{
|
||||
// Use blit instead of CopyTexture as it will smooth out issues with format
|
||||
@@ -92,38 +102,28 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
_EffectCommandBuffer ??= new()
|
||||
{
|
||||
name = "Underwater Pass",
|
||||
name = k_DrawVolume,
|
||||
};
|
||||
|
||||
_CopyColor ??= new(CopyColorTexture);
|
||||
_SetRenderTargetToBackBuffers ??= new(SetRenderTargetToBackBuffers);
|
||||
}
|
||||
|
||||
void OnPreRenderUnderwaterEffect(Camera camera)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// Do not use this to prevent the mask from rendering due to portals and volumes feature.
|
||||
if (!IsFogEnabledForEditorCamera(camera))
|
||||
{
|
||||
_EffectCommandBuffer?.Clear();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
var descriptor = XRHelpers.GetRenderTextureDescriptor(camera);
|
||||
var descriptor = Rendering.BIRP.GetCameraTargetDescriptor(camera, _Water.FrameBufferFormatOverride);
|
||||
descriptor.useDynamicScale = camera.allowDynamicResolution;
|
||||
|
||||
// Format must be correct for CopyTexture to work. Hopefully this is good enough.
|
||||
if (camera.allowHDR && QualitySettings.activeColorSpace == ColorSpace.Linear)
|
||||
{
|
||||
descriptor.graphicsFormat = SystemInfo.GetGraphicsFormat(_Water.LikelyFrameBufferFormat);
|
||||
}
|
||||
|
||||
UpdateEffectMaterial(camera, _FirstRender);
|
||||
UpdateEffectMaterial(camera);
|
||||
|
||||
_EffectCommandBuffer.Clear();
|
||||
|
||||
// No need to clear as Blit will overwrite everything.
|
||||
_EffectCommandBuffer.GetTemporaryRT(ShaderIDs.s_CameraColorTexture, descriptor);
|
||||
if (!RenderBeforeTransparency || _NeedsColorTexture)
|
||||
{
|
||||
// No need to clear as Blit will overwrite everything.
|
||||
_EffectCommandBuffer.GetTemporaryRT(ShaderIDs.s_CameraColorTexture, descriptor);
|
||||
_EffectCommandBuffer.SetGlobalTexture(ShaderIDs.s_CameraColorTexture, _ColorCopyTarget);
|
||||
}
|
||||
|
||||
var sun = RenderSettings.sun;
|
||||
if (sun != null)
|
||||
@@ -131,15 +131,16 @@ namespace WaveHarmonic.Crest
|
||||
// Unity does not set up lighting for us so we will get the last value which could incorrect.
|
||||
// SetGlobalColor is just an alias for SetGlobalVector (no color space conversion like Material.SetColor):
|
||||
// https://docs.unity3d.com/2017.4/Documentation/ScriptReference/Shader.SetGlobalColor.html
|
||||
_EffectCommandBuffer.SetGlobalVector(ShaderIDs.s_LightColor0, sun.FinalColor());
|
||||
_EffectCommandBuffer.SetGlobalVector(ShaderIDs.s_WorldSpaceLightPos0, -sun.transform.forward);
|
||||
_EffectCommandBuffer.SetGlobalVector(Crest.ShaderIDs.Unity.s_LightColor0, sun.FinalColor());
|
||||
_EffectCommandBuffer.SetGlobalVector(Crest.ShaderIDs.Unity.s_WorldSpaceLightPos0, -sun.transform.forward);
|
||||
_EffectCommandBuffer.SetShaderKeyword("DIRECTIONAL_COOKIE", sun.cookie != null);
|
||||
}
|
||||
|
||||
// Create a separate stencil buffer context by copying the depth texture.
|
||||
if (UseStencilBuffer)
|
||||
{
|
||||
descriptor.colorFormat = RenderTextureFormat.Depth;
|
||||
descriptor.depthBufferBits = (int)k_DepthBits;
|
||||
descriptor.depthBufferBits = (int)Helpers.k_DepthBits;
|
||||
// bindMS is necessary in this case for depth.
|
||||
descriptor.SetMSAASamples(camera);
|
||||
descriptor.bindMS = descriptor.msaaSamples > 1;
|
||||
@@ -152,150 +153,128 @@ namespace WaveHarmonic.Crest
|
||||
if (Helpers.IsMSAAEnabled(camera))
|
||||
{
|
||||
// Blit with a depth write shader to populate the depth buffer.
|
||||
Helpers.Blit(_EffectCommandBuffer, _DepthStencilTarget, Helpers.UtilityMaterial, (int)Helpers.UtilityPass.CopyDepth);
|
||||
Helpers.Blit(_EffectCommandBuffer, _DepthStencilTarget, Rendering.BIRP.UtilityMaterial, (int)Rendering.BIRP.UtilityPass.CopyDepth);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy depth then clear stencil.
|
||||
// Copy depth texture. Since this is not depth buffer, no need to clear stencil.
|
||||
// SRPs copy the depth buffer, because they can.
|
||||
_EffectCommandBuffer.CopyTexture(BuiltinRenderTextureType.Depth, _DepthStencilTarget);
|
||||
Helpers.Blit(_EffectCommandBuffer, _DepthStencilTarget, Helpers.UtilityMaterial, (int)Helpers.UtilityPass.ClearStencil);
|
||||
CoreUtils.SetRenderTarget(_EffectCommandBuffer, _DepthStencilTarget);
|
||||
}
|
||||
|
||||
if (RenderBeforeTransparency)
|
||||
{
|
||||
_EffectCommandBuffer.SetRenderTarget(BuiltinRenderTextureType.CameraTarget, _DepthStencilTarget);
|
||||
}
|
||||
}
|
||||
|
||||
CopyColorTexture(_EffectCommandBuffer);
|
||||
if (!RenderBeforeTransparency)
|
||||
{
|
||||
CopyColorTexture(_EffectCommandBuffer);
|
||||
}
|
||||
|
||||
_EffectCommandBuffer.SetGlobalTexture(ShaderIDs.s_CameraColorTexture, _ColorCopyTarget);
|
||||
ExecuteEffect(camera, _EffectCommandBuffer, _CopyColor, _SetRenderTargetToBackBuffers);
|
||||
|
||||
ExecuteEffect(camera, _EffectCommandBuffer, _CopyColor);
|
||||
if (!RenderBeforeTransparency || _NeedsColorTexture)
|
||||
{
|
||||
_EffectCommandBuffer.ReleaseTemporaryRT(ShaderIDs.s_CameraColorTexture);
|
||||
}
|
||||
|
||||
_EffectCommandBuffer.ReleaseTemporaryRT(ShaderIDs.s_CameraColorTexture);
|
||||
if (UseStencilBuffer)
|
||||
{
|
||||
_EffectCommandBuffer.ReleaseTemporaryRT(ShaderIDs.s_WaterVolumeStencil);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ExecuteEffect(Camera camera, CommandBuffer buffer, System.Action<CommandBuffer> copyColor, MaterialPropertyBlock properties = null)
|
||||
internal void ExecuteEffect(Camera camera, CommandBuffer buffer, System.Action<CommandBuffer> copyColor, System.Action<CommandBuffer> resetRenderTargets, MaterialPropertyBlock properties = null)
|
||||
{
|
||||
if (camera.cameraType == CameraType.Reflection)
|
||||
{
|
||||
buffer.DrawProcedural
|
||||
(
|
||||
Matrix4x4.identity,
|
||||
_VolumeMaterial,
|
||||
shaderPass: (int)EffectPass.Reflections,
|
||||
MeshTopology.Triangles,
|
||||
vertexCount: 3,
|
||||
instanceCount: 1,
|
||||
properties
|
||||
);
|
||||
}
|
||||
var isFullScreenRequired = true;
|
||||
|
||||
#if d_CrestPortals
|
||||
else if (_Portals.Active && _Portals.Mode != Portals.PortalMode.Tunnel)
|
||||
if (_Portals.Active)
|
||||
{
|
||||
_Portals.RenderEffect(camera, buffer, _VolumeMaterial, copyColor, properties);
|
||||
isFullScreenRequired = _Portals.RenderEffect(camera, buffer, _VolumeMaterial, copyColor, resetRenderTargets, properties);
|
||||
}
|
||||
#endif
|
||||
else
|
||||
|
||||
if (!isFullScreenRequired)
|
||||
{
|
||||
buffer.DrawProcedural
|
||||
(
|
||||
Matrix4x4.identity,
|
||||
_VolumeMaterial,
|
||||
shaderPass: (int)EffectPass.FullScreen,
|
||||
MeshTopology.Triangles,
|
||||
vertexCount: 3,
|
||||
instanceCount: 1,
|
||||
properties
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.DrawProcedural
|
||||
(
|
||||
Matrix4x4.identity,
|
||||
_VolumeMaterial,
|
||||
shaderPass: (int)(camera.cameraType == CameraType.Reflection ? EffectPass.Reflections : EffectPass.FullScreen),
|
||||
MeshTopology.Triangles,
|
||||
vertexCount: 3,
|
||||
instanceCount: 1,
|
||||
properties
|
||||
);
|
||||
}
|
||||
|
||||
internal static void UpdateGlobals(Material waterMaterial)
|
||||
internal static void UpdateGlobals(Material source)
|
||||
{
|
||||
// We will have the wrong color values if we do not use linear:
|
||||
// https://forum.unity.com/threads/fragment-shader-output-colour-has-incorrect-values-when-hardcoded.377657/
|
||||
|
||||
// _CrestAbsorption is already set as global in Water Renderer.
|
||||
Shader.SetGlobalColor(WaterRenderer.ShaderIDs.s_Scattering, waterMaterial.GetColor(WaterRenderer.ShaderIDs.s_Scattering).MaybeLinear());
|
||||
Shader.SetGlobalFloat(WaterRenderer.ShaderIDs.s_Anisotropy, waterMaterial.GetFloat(WaterRenderer.ShaderIDs.s_Anisotropy));
|
||||
Shader.SetGlobalColor(WaterRenderer.ShaderIDs.s_Scattering, source.GetColor(WaterRenderer.ShaderIDs.s_Scattering).MaybeLinear());
|
||||
Shader.SetGlobalFloat(WaterRenderer.ShaderIDs.s_Anisotropy, source.GetFloat(WaterRenderer.ShaderIDs.s_Anisotropy));
|
||||
Shader.SetGlobalFloat(WaterRenderer.ShaderIDs.s_AmbientTerm, source.GetFloat(WaterRenderer.ShaderIDs.s_AmbientTerm));
|
||||
Shader.SetGlobalFloat(WaterRenderer.ShaderIDs.s_DirectTerm, source.GetFloat(WaterRenderer.ShaderIDs.s_DirectTerm));
|
||||
Shader.SetGlobalFloat(WaterRenderer.ShaderIDs.s_ShadowsAffectsAmbientFactor, source.GetFloat(WaterRenderer.ShaderIDs.s_ShadowsAffectsAmbientFactor));
|
||||
|
||||
Shader.SetGlobalFloat(ShaderIDs.s_ExtinctionMultiplier, source.GetFloat(ShaderIDs.s_ExtinctionMultiplier));
|
||||
Shader.SetGlobalFloat(ShaderIDs.s_OutScatteringFactor, source.GetFloat(ShaderIDs.s_OutScatteringFactor));
|
||||
Shader.SetGlobalFloat(ShaderIDs.s_OutScatteringExtinctionFactor, source.GetFloat(ShaderIDs.s_OutScatteringExtinctionFactor));
|
||||
Shader.SetGlobalFloat(ShaderIDs.s_SunBoost, source.GetFloat(ShaderIDs.s_SunBoost));
|
||||
Shader.SetGlobalInteger(ShaderIDs.s_DataSliceOffset, source.GetInteger(ShaderIDs.s_DataSliceOffset));
|
||||
}
|
||||
|
||||
internal void UpdateEffectMaterial(Camera camera, bool isFirstRender)
|
||||
internal void UpdateEffectMaterial(Camera camera)
|
||||
{
|
||||
// Copy water material parameters to underwater material.
|
||||
// WBs can change the material per camera, so disable optimization.
|
||||
if (_MaterialLastUpdatedFrame < Time.frameCount || WaterBody.WaterBodies.Count > 0)
|
||||
{
|
||||
var material = _SurfaceMaterial;
|
||||
|
||||
if (_CopyWaterMaterialParametersEachFrame || isFirstRender || material != _CurrentWaterMaterial)
|
||||
if (_CopyWaterMaterialParametersEachFrame || _SurfaceMaterial != _CurrentWaterMaterial)
|
||||
{
|
||||
_CurrentWaterMaterial = material;
|
||||
_CurrentWaterMaterial = _SurfaceMaterial;
|
||||
|
||||
if (material != null)
|
||||
if (_SurfaceMaterial != null)
|
||||
{
|
||||
_VolumeMaterial.CopyMatchingPropertiesFromMaterial(material);
|
||||
_VolumeMaterial.CopyMatchingPropertiesFromMaterial(_SurfaceMaterial);
|
||||
|
||||
if (_EnableShaderAPI)
|
||||
AfterCopyMaterial?.Invoke(_Water, _VolumeMaterial);
|
||||
|
||||
// Make volume properties available to surface and meniscus.
|
||||
if (RenderBeforeTransparency)
|
||||
{
|
||||
UpdateGlobals(material);
|
||||
UpdateGlobals(_VolumeMaterial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enabling/disabling keywords each frame don't seem to have large measurable overhead
|
||||
_VolumeMaterial.SetKeyword(k_KeywordDebugVisualizeMask, _Debug._VisualizeMask);
|
||||
_VolumeMaterial.SetKeyword(k_KeywordDebugVisualizeStencil, _Debug._VisualizeStencil);
|
||||
|
||||
// We use this for caustics to get the displacement.
|
||||
_VolumeMaterial.SetInteger(Lod.ShaderIDs.s_LodIndex, 0);
|
||||
|
||||
_MaterialLastUpdatedFrame = Time.frameCount;
|
||||
}
|
||||
|
||||
// Enabling/disabling keywords each frame don't seem to have large measurable overhead
|
||||
_VolumeMaterial.SetKeyword(k_KeywordDebugVisualizeMask, _Debug._VisualizeMask);
|
||||
_VolumeMaterial.SetKeyword(k_KeywordDebugVisualizeStencil, _Debug._VisualizeStencil);
|
||||
|
||||
// We use this for caustics to get the displacement.
|
||||
_VolumeMaterial.SetInteger(Lod.ShaderIDs.s_LodIndex, 0);
|
||||
|
||||
if (!Portaled && camera.cameraType != CameraType.Reflection)
|
||||
// Not applicable to reflection pass.
|
||||
if (camera.cameraType != CameraType.Reflection)
|
||||
{
|
||||
var seaLevel = _Water.SeaLevel;
|
||||
|
||||
// We don't both setting the horizon value if we know we are going to be having to apply the effect
|
||||
// full-screen anyway.
|
||||
var forceFullShader = _Water.ViewerHeightAboveWater < -2f;
|
||||
if (!forceFullShader)
|
||||
{
|
||||
var maxWaterVerticalDisplacement = _Water.MaximumVerticalDisplacement * 0.5f;
|
||||
var cameraYPosition = camera.transform.position.y;
|
||||
float nearPlaneFrustumWorldHeight;
|
||||
{
|
||||
var current = camera.ViewportToWorldPoint(new(0f, 0f, camera.nearClipPlane)).y;
|
||||
float maxY = current, minY = current;
|
||||
|
||||
current = camera.ViewportToWorldPoint(new(0f, 1f, camera.nearClipPlane)).y;
|
||||
maxY = Mathf.Max(maxY, current);
|
||||
minY = Mathf.Min(minY, current);
|
||||
|
||||
current = camera.ViewportToWorldPoint(new(1f, 0f, camera.nearClipPlane)).y;
|
||||
maxY = Mathf.Max(maxY, current);
|
||||
minY = Mathf.Min(minY, current);
|
||||
|
||||
current = camera.ViewportToWorldPoint(new(1f, 1f, camera.nearClipPlane)).y;
|
||||
maxY = Mathf.Max(maxY, current);
|
||||
minY = Mathf.Min(minY, current);
|
||||
|
||||
nearPlaneFrustumWorldHeight = maxY - minY;
|
||||
}
|
||||
|
||||
forceFullShader = (cameraYPosition + nearPlaneFrustumWorldHeight + maxWaterVerticalDisplacement) <= seaLevel;
|
||||
}
|
||||
|
||||
_VolumeMaterial.SetKeyword(k_KeywordFullScreenEffect, forceFullShader);
|
||||
}
|
||||
|
||||
// Project water normal onto camera plane.
|
||||
{
|
||||
var projectedNormal = new Vector2
|
||||
(
|
||||
Vector3.Dot(Vector3.up, camera.transform.right),
|
||||
Vector3.Dot(Vector3.up, camera.transform.up)
|
||||
);
|
||||
|
||||
_VolumeMaterial.SetVector(ShaderIDs.s_HorizonNormal, projectedNormal);
|
||||
// Skip work if camera is far enough below the surface.
|
||||
var forceFullShader = !_Water.Surface.Enabled || (_Water._ViewerHeightAboveWaterPerCamera < -8f && !Portaled);
|
||||
_VolumeMaterial.SetKeyword("d_Crest_NoMaskColor", forceFullShader);
|
||||
_VolumeMaterial.SetKeyword("d_Crest_NoMaskDepth", !_Water.Surface.Enabled || RenderBeforeTransparency);
|
||||
}
|
||||
|
||||
// Compute ambient lighting SH.
|
||||
@@ -304,11 +283,11 @@ namespace WaveHarmonic.Crest
|
||||
// at different position, as this would then thrash it and negate the priming functionality. We could create a dummy invis GO
|
||||
// with a dummy Renderer which might be enough, but this is hacky enough that we'll wait for it to become a problem
|
||||
// rather than add a pre-emptive hack.
|
||||
UnityEngine.Profiling.Profiler.BeginSample("Crest: Underwater Sample Spherical Harmonics");
|
||||
s_SampleSphericalHarmonicsMarker.Begin(_Water);
|
||||
LightProbes.GetInterpolatedProbe(camera.transform.position, null, out var sphericalHarmonicsL2);
|
||||
sphericalHarmonicsL2.Evaluate(_SphericalHarmonicsData._DirectionsSH, _SphericalHarmonicsData._AmbientLighting);
|
||||
Helpers.SetShaderVector(_VolumeMaterial, ShaderIDs.s_AmbientLighting, _SphericalHarmonicsData._AmbientLighting[0], _EnableShaderAPI);
|
||||
UnityEngine.Profiling.Profiler.EndSample();
|
||||
Helpers.SetShaderVector(_VolumeMaterial, ShaderIDs.s_AmbientLighting, _SphericalHarmonicsData._AmbientLighting[0], RenderBeforeTransparency);
|
||||
s_SampleSphericalHarmonicsMarker.End();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,14 @@ namespace WaveHarmonic.Crest
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Only repaint, otherwise changes might persist.
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Restore lighting settings.
|
||||
if (_EnvironmentalLight != null) _EnvironmentalLight.intensity = _EnvironmentalLightIntensity;
|
||||
_EnvironmentalLight = null;
|
||||
@@ -77,7 +85,15 @@ namespace WaveHarmonic.Crest
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_Water.Material.HasColor(WaterRenderer.ShaderIDs.s_AbsorptionColor))
|
||||
#if UNITY_EDITOR
|
||||
// Only repaint, otherwise changes might persist.
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!_Water.Surface.Material.HasColor(WaterRenderer.ShaderIDs.s_AbsorptionColor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -94,7 +110,13 @@ namespace WaveHarmonic.Crest
|
||||
var density = extinction;
|
||||
_EnvironmentalAverageDensity = (density.x + density.y + density.z) / 3f;
|
||||
|
||||
var multiplier = Mathf.Exp(_EnvironmentalAverageDensity * Mathf.Min(height * k_DepthOutScattering, 0f) * _EnvironmentalLightingWeight);
|
||||
var outScatteringFactor = 1f;
|
||||
if (_VolumeMaterial.HasFloat(ShaderIDs.s_OutScatteringFactor))
|
||||
{
|
||||
outScatteringFactor = _VolumeMaterial.GetFloat(ShaderIDs.s_OutScatteringFactor);
|
||||
}
|
||||
|
||||
var multiplier = Mathf.Exp(_EnvironmentalAverageDensity * Mathf.Min(height * k_DepthOutScattering * outScatteringFactor, 0f) * _EnvironmentalLightingWeight);
|
||||
|
||||
// Darken environmental lighting when viewer underwater.
|
||||
if (_EnvironmentalLight != null)
|
||||
|
||||
@@ -8,94 +8,49 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
partial class UnderwaterRenderer
|
||||
{
|
||||
bool _HasMaskCommandBuffersBeenRegistered;
|
||||
bool _HasEffectCommandBuffersBeenRegistered;
|
||||
|
||||
void OnEnableLegacy()
|
||||
{
|
||||
SetupMask();
|
||||
OnEnableMask();
|
||||
SetupUnderwaterEffect();
|
||||
|
||||
// Handle this internally rather than relying on Water Renderer.
|
||||
Camera.onPreRender -= OnBeforeLegacyRender;
|
||||
Camera.onPreRender += OnBeforeLegacyRender;
|
||||
Camera.onPostRender -= OnAfterLegacyRender;
|
||||
Camera.onPostRender += OnAfterLegacyRender;
|
||||
RenderPipelineManager.activeRenderPipelineTypeChanged -= OnDisableLegacy;
|
||||
RenderPipelineManager.activeRenderPipelineTypeChanged += OnDisableLegacy;
|
||||
}
|
||||
|
||||
void OnDisableLegacy()
|
||||
{
|
||||
Camera.onPreRender -= OnBeforeLegacyRender;
|
||||
Camera.onPostRender -= OnAfterLegacyRender;
|
||||
RenderPipelineManager.activeRenderPipelineTypeChanged -= OnDisableLegacy;
|
||||
|
||||
OnDisableMask();
|
||||
}
|
||||
|
||||
internal void LateUpdate()
|
||||
{
|
||||
if (!Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!RenderPipelineHelper.IsLegacy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Helpers.SetGlobalKeyword("CREST_UNDERWATER_BEFORE_TRANSPARENT", _EnableShaderAPI);
|
||||
}
|
||||
|
||||
// Listening to OnPreCull. Camera must have underwater layer.
|
||||
void OnBeforeLegacyRender(Camera camera)
|
||||
{
|
||||
XRHelpers.Update(camera);
|
||||
XRHelpers.SetInverseViewProjectionMatrix(camera);
|
||||
|
||||
if (ShouldRender(camera, Pass.Mask))
|
||||
{
|
||||
// It could be either one event.
|
||||
camera.AddCommandBuffer(CameraEvent.BeforeGBuffer, _MaskCommandBuffer);
|
||||
camera.AddCommandBuffer(CameraEvent.BeforeDepthTexture, _MaskCommandBuffer);
|
||||
OnPreRenderMask(camera);
|
||||
_HasMaskCommandBuffersBeenRegistered = true;
|
||||
}
|
||||
|
||||
if (ShouldRender(camera, Pass.Effect))
|
||||
{
|
||||
var @event = _EnableShaderAPI ? CameraEvent.BeforeForwardAlpha : CameraEvent.AfterForwardAlpha;
|
||||
_Water.UpdateMatrices(camera);
|
||||
|
||||
_Water.OnBeginCameraOpaqueTexture(camera);
|
||||
|
||||
var @event = RenderBeforeTransparency ? CameraEvent.BeforeForwardAlpha : CameraEvent.AfterForwardAlpha;
|
||||
camera.AddCommandBuffer(@event, _EffectCommandBuffer);
|
||||
OnPreRenderUnderwaterEffect(camera);
|
||||
_HasEffectCommandBuffersBeenRegistered = true;
|
||||
}
|
||||
|
||||
_FirstRender = false;
|
||||
}
|
||||
|
||||
void OnAfterLegacyRender(Camera camera)
|
||||
{
|
||||
if (_HasMaskCommandBuffersBeenRegistered)
|
||||
{
|
||||
// It could be either one event.
|
||||
camera.RemoveCommandBuffer(CameraEvent.BeforeGBuffer, _MaskCommandBuffer);
|
||||
camera.RemoveCommandBuffer(CameraEvent.BeforeDepthTexture, _MaskCommandBuffer);
|
||||
_MaskCommandBuffer?.Clear();
|
||||
}
|
||||
|
||||
if (_HasEffectCommandBuffersBeenRegistered)
|
||||
{
|
||||
var @event = _EnableShaderAPI ? CameraEvent.BeforeForwardAlpha : CameraEvent.AfterForwardAlpha;
|
||||
var @event = RenderBeforeTransparency ? CameraEvent.BeforeForwardAlpha : CameraEvent.AfterForwardAlpha;
|
||||
camera.RemoveCommandBuffer(@event, _EffectCommandBuffer);
|
||||
_EffectCommandBuffer?.Clear();
|
||||
}
|
||||
|
||||
_HasMaskCommandBuffersBeenRegistered = false;
|
||||
_HasEffectCommandBuffersBeenRegistered = false;
|
||||
_Water.OnEndCameraOpaqueTexture(camera);
|
||||
|
||||
OnAfterCameraRender(camera);
|
||||
_HasEffectCommandBuffersBeenRegistered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,34 +2,21 @@
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
partial class UnderwaterRenderer
|
||||
partial class UnderwaterRenderer : MaskRenderer.IMaskReceiver, MaskRenderer.IMaskProvider
|
||||
{
|
||||
// Adapted from:
|
||||
// Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderer.cs
|
||||
#if UNITY_SWITCH || UNITY_ANDROID || UNITY_EMBEDDED_LINUX || UNITY_QNX
|
||||
internal const GraphicsFormat k_DepthStencilFormat = GraphicsFormat.D24_UNorm_S8_UInt;
|
||||
internal const int k_DepthBufferBits = 24;
|
||||
internal const DepthBits k_DepthBits = DepthBits.Depth24;
|
||||
#else
|
||||
internal const GraphicsFormat k_DepthStencilFormat = GraphicsFormat.D32_SFloat_S8_UInt;
|
||||
internal const int k_DepthBufferBits = 32;
|
||||
internal const DepthBits k_DepthBits = DepthBits.Depth32;
|
||||
#endif
|
||||
internal const string k_DrawMask = "Crest.DrawMask";
|
||||
const string k_DrawMaskHorizon = "Horizon";
|
||||
const string k_DrawMaskSurface = "Surface";
|
||||
|
||||
internal const int k_VolumeMaskQueue = 1000;
|
||||
|
||||
internal const int k_ShaderPassWaterSurfaceMask = 0;
|
||||
internal const int k_ShaderPassWaterSurfaceDepth = 1;
|
||||
internal const int k_ShaderPassWaterHorizonMask = 2;
|
||||
|
||||
// NOTE: Must match CREST_MASK_BELOW_SURFACE in Constants.hlsl.
|
||||
const float k_MaskBelowSurface = -1f;
|
||||
// NOTE: Must match CREST_MASK_BELOW_SURFACE_CULLED in Constants.hlsl.
|
||||
const float k_MaskBelowSurfaceCull = -2f;
|
||||
internal const int k_ShaderPassWaterHorizonMask = 0;
|
||||
|
||||
internal const string k_ComputeShaderKernelFillMaskArtefacts = "FillMaskArtefacts";
|
||||
|
||||
@@ -37,25 +24,10 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
// Local
|
||||
public static readonly int s_FarPlaneOffset = Shader.PropertyToID("_Crest_FarPlaneOffset");
|
||||
public static readonly int s_MaskBelowSurface = Shader.PropertyToID("_Crest_MaskBelowSurface");
|
||||
|
||||
// Global
|
||||
public static readonly int s_WaterMaskTexture = Shader.PropertyToID("_Crest_WaterMaskTexture");
|
||||
public static readonly int s_WaterMaskDepthTexture = Shader.PropertyToID("_Crest_WaterMaskDepthTexture");
|
||||
|
||||
public static readonly int s_StencilRef = Shader.PropertyToID("_StencilRef");
|
||||
}
|
||||
|
||||
internal Material _MaskMaterial;
|
||||
|
||||
internal RenderTargetIdentifier _MaskTarget;
|
||||
internal RenderTargetIdentifier _DepthTarget;
|
||||
|
||||
internal readonly Plane[] _CameraFrustumPlanes = new Plane[6];
|
||||
CommandBuffer _MaskCommandBuffer;
|
||||
|
||||
internal RenderTexture _MaskRT;
|
||||
RenderTexture _DepthRT;
|
||||
internal Material _HorizonMaskMaterial;
|
||||
|
||||
ComputeShader _ArtifactsShader;
|
||||
bool _ArtifactsShaderInitialized;
|
||||
@@ -63,30 +35,19 @@ namespace WaveHarmonic.Crest
|
||||
uint _ArtifactsThreadGroupSizeX;
|
||||
uint _ArtifactsThreadGroupSizeY;
|
||||
|
||||
void SetupMask()
|
||||
{
|
||||
_MaskCommandBuffer ??= new()
|
||||
{
|
||||
name = "Crest: Underwater Mask",
|
||||
};
|
||||
}
|
||||
|
||||
internal void OnEnableMask()
|
||||
{
|
||||
// Create a reference to handle the RT. The RT properties will be replaced with a descriptor before the
|
||||
// native object is created, and since it is lazy it is near zero cost.
|
||||
Helpers.CreateRenderTargetTextureReference(ref _MaskRT, ref _MaskTarget);
|
||||
_MaskRT.name = "_Crest_WaterMaskTexture";
|
||||
Helpers.CreateRenderTargetTextureReference(ref _DepthRT, ref _DepthTarget);
|
||||
_DepthRT.name = "_Crest_WaterMaskDepthTexture";
|
||||
_Water._Mask.Add(this);
|
||||
_Water._Mask.Add(k_VolumeMaskQueue, this);
|
||||
|
||||
SetUpArtifactsShader();
|
||||
}
|
||||
|
||||
internal void OnDisableMask()
|
||||
{
|
||||
if (_MaskRT != null) _MaskRT.Release();
|
||||
if (_DepthRT != null) _DepthRT.Release();
|
||||
if (_Water == null) return;
|
||||
_Water._Mask?.Remove(this as MaskRenderer.IMaskReceiver);
|
||||
_Water._Mask?.Remove(this as MaskRenderer.IMaskProvider);
|
||||
}
|
||||
|
||||
internal void SetUpArtifactsShader()
|
||||
@@ -108,70 +69,69 @@ namespace WaveHarmonic.Crest
|
||||
_ArtifactsShaderInitialized = true;
|
||||
}
|
||||
|
||||
internal void SetUpMaskTextures(RenderTextureDescriptor descriptor)
|
||||
void MaskRenderer.IMaskProvider.OnMaskPass(CommandBuffer commands, Camera camera, MaskRenderer mask)
|
||||
{
|
||||
if (!Helpers.RenderTargetTextureNeedsUpdating(_MaskRT, descriptor))
|
||||
var color = mask.ColorRTH;
|
||||
var depth = mask.DepthRTH;
|
||||
|
||||
var size = color.GetScaledSize(color.rtHandleProperties.currentViewportSize);
|
||||
var descriptor = color.rt.descriptor;
|
||||
descriptor.width = size.x; descriptor.height = size.y;
|
||||
|
||||
if (UseLegacyMask)
|
||||
{
|
||||
// Portals changes the target.
|
||||
// When using the stencil we are already clearing depth and do not want to clear the stencil too. Clear
|
||||
// color only when using the stencil as the horizon effectively clears it when not using it.
|
||||
CoreUtils.SetRenderTarget(commands, color, depth, UseStencilBuffer ? ClearFlag.Color : ClearFlag.DepthStencil);
|
||||
Helpers.ScaleViewport(camera, commands, color);
|
||||
|
||||
PopulateMask(commands, camera);
|
||||
FixMaskArtefacts(commands, descriptor, mask._ColorRTI);
|
||||
}
|
||||
// Portals have their own fitted to the portal bounds.
|
||||
else
|
||||
#if d_CrestPortals
|
||||
if (!Portaled || _Water.Portals.RequiresFullScreenMask)
|
||||
#endif
|
||||
{
|
||||
RenderLineMask(commands, camera, mask.ColorRT.descriptor, mask._ColorRTI);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RenderLineMask(CommandBuffer buffer, Camera camera, RenderTextureDescriptor descriptor, RenderTargetIdentifier target)
|
||||
{
|
||||
if (!_Water.Surface.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// This will disable MSAA for our textures as MSAA will break sampling later on. This looks safe to do as
|
||||
// Unity's CopyDepthPass does the same, but a possible better way or supporting MSAA is worth looking into.
|
||||
descriptor.msaaSamples = 1;
|
||||
|
||||
// @Memory: We could investigate making this an 8-bit texture instead to reduce GPU memory usage.
|
||||
// @Memory: We could potentially try a half resolution mask as the mensicus could mask resolution issues.
|
||||
// Intel iGPU for Metal and DirectX both had issues with R16. 2021.11.18
|
||||
descriptor.colorFormat = Helpers.IsIntelGPU() ? RenderTextureFormat.RFloat : RenderTextureFormat.RHalf;
|
||||
descriptor.depthBufferBits = 0;
|
||||
descriptor.enableRandomWrite = true;
|
||||
|
||||
_MaskRT.Release();
|
||||
_MaskRT.descriptor = descriptor;
|
||||
|
||||
descriptor.colorFormat = RenderTextureFormat.Depth;
|
||||
descriptor.depthBufferBits = (int)k_DepthBits;
|
||||
descriptor.enableRandomWrite = false;
|
||||
|
||||
_DepthRT.Release();
|
||||
_DepthRT.descriptor = descriptor;
|
||||
}
|
||||
|
||||
void OnPreRenderMask(Camera camera)
|
||||
{
|
||||
_MaskCommandBuffer.Clear();
|
||||
|
||||
var descriptor = XRHelpers.GetRenderTextureDescriptor(camera);
|
||||
|
||||
descriptor.useDynamicScale = camera.allowDynamicResolution;
|
||||
|
||||
// Keywords and other things.
|
||||
SetUpMaskTextures(descriptor);
|
||||
var keep = false;
|
||||
|
||||
#if d_CrestPortals
|
||||
// Populate water volume before mask so we can use the stencil.
|
||||
if (_Portals.Active)
|
||||
if (_Water.Portals.Active)
|
||||
{
|
||||
_Portals.ReAllocate(descriptor);
|
||||
_Portals.RenderMask(camera, _MaskCommandBuffer, _MaskMaterial);
|
||||
_Portals.RenderStencil(_MaskCommandBuffer, _DepthRT, _DepthTarget);
|
||||
keep = _Water.Portals.RenderLineMask(buffer, target);
|
||||
}
|
||||
#endif
|
||||
|
||||
_MaskCommandBuffer.SetRenderTarget(_MaskTarget, _DepthTarget);
|
||||
SetUpMask(_MaskCommandBuffer, _MaskTarget, _DepthTarget);
|
||||
PopulateMask(_MaskCommandBuffer, camera);
|
||||
var wrapper = new PropertyWrapperCompute(buffer, WaterResources.Instance.Compute._Mask, (int)RenderPipelineHelper.RenderPipeline);
|
||||
|
||||
FixMaskArtefacts(_MaskCommandBuffer, descriptor, _MaskTarget);
|
||||
}
|
||||
var parameters = _Water.Surface._SurfaceDataParameters;
|
||||
|
||||
internal void SetUpMask(CommandBuffer buffer, RenderTargetIdentifier maskTarget, RenderTargetIdentifier depthTarget)
|
||||
{
|
||||
// When using the stencil we are already clearing depth and do not want to clear the stencil too. Clear
|
||||
// color only when using the stencil as the horizon effectively clears it when not using it.
|
||||
buffer.ClearRenderTarget(!UseStencilBuffer, UseStencilBuffer, Color.black);
|
||||
buffer.SetGlobalTexture(ShaderIDs.s_WaterMaskTexture, maskTarget);
|
||||
buffer.SetGlobalTexture(ShaderIDs.s_WaterMaskDepthTexture, depthTarget);
|
||||
wrapper.SetTexture(SurfaceRenderer.ShaderIDs.s_WaterLine, _Water.Surface.HeightRT);
|
||||
wrapper.SetVector(SurfaceRenderer.ShaderIDs.s_WaterLineSnappedPosition, parameters._SnappedPosition);
|
||||
wrapper.SetVector(SurfaceRenderer.ShaderIDs.s_WaterLineResolution, parameters._Resolution);
|
||||
wrapper.SetFloat(SurfaceRenderer.ShaderIDs.s_WaterLineTexel, parameters._Texel);
|
||||
|
||||
// Only write if not written (ie zero).
|
||||
wrapper.SetKeyword(new(WaterResources.Instance.Compute._Mask, "d_KeepValue"), keep);
|
||||
|
||||
// Setting this sets unity_CameraToWorld.
|
||||
wrapper.SetMatrix(Crest.ShaderIDs.Unity.s_CameraToWorld, camera.cameraToWorldMatrix);
|
||||
|
||||
// Viewport sizes are not perfect so round up to cover.
|
||||
wrapper.Dispatch(Mathf.CeilToInt(descriptor.width / 8f), Mathf.CeilToInt(descriptor.height / 8f), descriptor.volumeDepth);
|
||||
}
|
||||
|
||||
internal void FixMaskArtefacts(CommandBuffer buffer, RenderTextureDescriptor descriptor, RenderTargetIdentifier target)
|
||||
@@ -181,9 +141,16 @@ namespace WaveHarmonic.Crest
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.SetComputeTextureParam(_ArtifactsShader, _ArtifactsKernel, ShaderIDs.s_WaterMaskTexture, target);
|
||||
// XR SPI will have a volume depth of two. If using RTHandles, then set manually as will be two for all cameras.
|
||||
_ArtifactsShader.SetKeyword("STEREO_INSTANCING_ON", descriptor.dimension == TextureDimension.Tex2DArray);
|
||||
if (!_Water.Surface.Enabled && Portaled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// This will not be set automatically unless we use they RP variant approach
|
||||
// similar to Mask.compute.
|
||||
_ArtifactsShader.SetKeyword(new LocalKeyword(_ArtifactsShader, "STEREO_INSTANCING_ON"), descriptor.dimension == TextureDimension.Tex2DArray);
|
||||
|
||||
buffer.SetComputeTextureParam(_ArtifactsShader, _ArtifactsKernel, MaskRenderer.ShaderIDs.s_WaterMaskTexture, target);
|
||||
|
||||
buffer.DispatchCompute
|
||||
(
|
||||
@@ -200,54 +167,55 @@ namespace WaveHarmonic.Crest
|
||||
// be able to avoid this pass completely if we can reuse the camera depth after transparents are rendered.
|
||||
internal void PopulateMask(CommandBuffer commandBuffer, Camera camera)
|
||||
{
|
||||
if (!_Water.Surface.Enabled && Portaled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Render horizon into mask using a fullscreen triangle at the far plane. Horizon must be rendered first or
|
||||
// it will overwrite the mask with incorrect values.
|
||||
{
|
||||
var zBufferParameters = Helpers.GetZBufferParameters(camera);
|
||||
// Take 0-1 linear depth and convert non-linear depth.
|
||||
_MaskMaterial.SetFloat(ShaderIDs.s_FarPlaneOffset, Helpers.LinearDepthToNonLinear(_FarPlaneMultiplier, zBufferParameters));
|
||||
_HorizonMaskMaterial.SetFloat(ShaderIDs.s_FarPlaneOffset, Helpers.LinearDepthToNonLinear(_FarPlaneMultiplier, zBufferParameters));
|
||||
|
||||
// Render fullscreen triangle with horizon mask pass.
|
||||
commandBuffer.DrawProcedural(Matrix4x4.identity, _MaskMaterial, shaderPass: k_ShaderPassWaterHorizonMask, MeshTopology.Triangles, 3, 1);
|
||||
commandBuffer.BeginSample(k_DrawMaskHorizon);
|
||||
commandBuffer.DrawProcedural(Matrix4x4.identity, _HorizonMaskMaterial, shaderPass: k_ShaderPassWaterHorizonMask, MeshTopology.Triangles, 3, 1);
|
||||
commandBuffer.EndSample(k_DrawMaskHorizon);
|
||||
}
|
||||
|
||||
GeometryUtility.CalculateFrustumPlanes(camera, _CameraFrustumPlanes);
|
||||
|
||||
// Get all water chunks and render them using cmd buffer, but with mask shader.
|
||||
if (!_Debug._DisableMask)
|
||||
{
|
||||
// Spends approx 0.2-0.3ms here on 2018 Dell XPS 15.
|
||||
foreach (var chunk in _Water.Chunks)
|
||||
{
|
||||
var renderer = chunk.Rend;
|
||||
// Can happen in edit mode.
|
||||
if (renderer == null) continue;
|
||||
var bounds = renderer.bounds;
|
||||
if (GeometryUtility.TestPlanesAABB(_CameraFrustumPlanes, bounds))
|
||||
{
|
||||
if ((!chunk._WaterDataHasBeenBound) && chunk.enabled)
|
||||
{
|
||||
chunk.Bind(camera);
|
||||
}
|
||||
|
||||
// Handle culled tiles for when underwater is rendered before the transparent pass.
|
||||
chunk._MaterialPropertyBlock.SetFloat(ShaderIDs.s_MaskBelowSurface, !_EnableShaderAPI || renderer.enabled ? k_MaskBelowSurface : k_MaskBelowSurfaceCull);
|
||||
renderer.SetPropertyBlock(chunk._MaterialPropertyBlock);
|
||||
|
||||
commandBuffer.DrawRenderer(renderer, _MaskMaterial, submeshIndex: 0, shaderPass: k_ShaderPassWaterSurfaceMask);
|
||||
|
||||
chunk._Visible = true;
|
||||
}
|
||||
chunk._WaterDataHasBeenBound = false;
|
||||
}
|
||||
|
||||
#if d_CrestPortals
|
||||
if (_Portals.Active)
|
||||
{
|
||||
_Portals.PopulateMask(commandBuffer, _MaskMaterial);
|
||||
}
|
||||
#endif // d_CrestPortals
|
||||
commandBuffer.BeginSample(k_DrawMaskSurface);
|
||||
_Water.Surface.Render(camera, commandBuffer, _MaskMaterial, k_ShaderPassWaterSurfaceMask);
|
||||
commandBuffer.EndSample(k_DrawMaskSurface);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool _MaskRead;
|
||||
bool _DoneMaskRead;
|
||||
|
||||
MaskRenderer.MaskInput MaskRenderer.IMaskProvider.Allocate()
|
||||
{
|
||||
return MaskRenderer.MaskInput.Both;
|
||||
}
|
||||
|
||||
MaskRenderer.MaskInput MaskRenderer.IMaskReceiver.Allocate()
|
||||
{
|
||||
return MaskRenderer.MaskInput.Both;
|
||||
}
|
||||
|
||||
MaskRenderer.MaskInput MaskRenderer.IMaskProvider.Write(Camera camera)
|
||||
{
|
||||
if (!_DoneMaskRead)
|
||||
{
|
||||
_MaskRead = ShouldRender(camera, Pass.Mask);
|
||||
_DoneMaskRead = true;
|
||||
}
|
||||
|
||||
return _MaskRead ? _Water.Surface.Enabled ? MaskRenderer.MaskInput.Both : MaskRenderer.MaskInput.Color : MaskRenderer.MaskInput.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Crest Water System
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
@@ -28,6 +28,8 @@ namespace WaveHarmonic.Crest
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _Enabled = true;
|
||||
|
||||
// This is mainly for reflection probes (HDRP planar specifically). It gives
|
||||
// developers the option to make a TIR probe which should not render the surface.
|
||||
[Tooltip("Any camera or probe with this layer in its culling mask will render underwater.")]
|
||||
[@Layer]
|
||||
[@GenerateAPI]
|
||||
@@ -35,8 +37,8 @@ namespace WaveHarmonic.Crest
|
||||
int _Layer = 4; // Water
|
||||
|
||||
[Tooltip("The underwater material. The water surface material is copied into this material.")]
|
||||
[@AttachMaterialEditor]
|
||||
[@MaterialField("Crest/Underwater", name: "Underwater", title: "Create Underwater Material")]
|
||||
[@AttachMaterialEditor(order: 2)]
|
||||
[@MaterialField(k_ShaderNameEffect, name: "Underwater", title: "Create Underwater Material")]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
internal Material _Material;
|
||||
@@ -68,21 +70,6 @@ namespace WaveHarmonic.Crest
|
||||
#endif
|
||||
|
||||
|
||||
[@Heading("Shader API")]
|
||||
|
||||
[Tooltip("Renders the underwater effect before the transparent pass (instead of after).\n\nSo one can apply the underwater fog themselves to transparent objects. Cannot be changed at runtime.")]
|
||||
[@DecoratedField, SerializeField]
|
||||
[HideInInspector]
|
||||
bool _EnableShaderAPI = false;
|
||||
internal bool EnableShaderAPI { get => _EnableShaderAPI; set => _EnableShaderAPI = value; }
|
||||
|
||||
[@Predicated(nameof(_EnableShaderAPI))]
|
||||
[@Predicated(RenderPipeline.Legacy, inverted: true, hide: true)]
|
||||
[@DecoratedField, SerializeField]
|
||||
[HideInInspector]
|
||||
internal LayerMask _TransparentObjectLayers;
|
||||
|
||||
|
||||
[@Heading("Advanced")]
|
||||
|
||||
[Tooltip("Whether to execute for all cameras.\n\nIf disabled, then additionally ignore any camera that is not the view camera or our reflection camera. It will require managing culling masks of all cameras.")]
|
||||
@@ -134,18 +121,33 @@ namespace WaveHarmonic.Crest
|
||||
internal bool _OnlyReflectionCameras;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised after copying the water material properties to the underwater material.
|
||||
/// </summary>
|
||||
public static System.Action<WaterRenderer, Material> AfterCopyMaterial { get; set; }
|
||||
|
||||
// Always render before surface, unless legacy mode which always renders after transparency.
|
||||
#if d_Crest_LegacyUnderwater
|
||||
internal bool UseLegacyMask => true;
|
||||
internal bool RenderBeforeTransparency => false;
|
||||
#else
|
||||
// Legacy mask works except for negative volumes. Not officially supported.
|
||||
internal bool UseLegacyMask => _AllCameras;
|
||||
internal bool RenderBeforeTransparency => true;
|
||||
#endif
|
||||
|
||||
internal WaterRenderer _Water;
|
||||
|
||||
#if d_CrestPortals
|
||||
// BUG: NonSerialized as Unity shows a serialization depth warning even though field is internal.
|
||||
[System.NonSerialized]
|
||||
internal Portals.PortalRenderer _Portals;
|
||||
bool Portaled => _Portals.Active;
|
||||
internal bool Portaled => _Portals.Active;
|
||||
#else
|
||||
bool Portaled => false;
|
||||
#endif
|
||||
|
||||
bool _FirstRender = true;
|
||||
int _MaterialLastUpdatedFrame = -1;
|
||||
|
||||
internal bool UseStencilBuffer { get; set; }
|
||||
|
||||
@@ -168,9 +170,6 @@ namespace WaveHarmonic.Crest
|
||||
// Empty.
|
||||
}
|
||||
|
||||
// Disable underwater effect if height enough above surface.
|
||||
internal bool Active => _Enabled && _Material != null && _ViewerWaterHeight < 2f || Portaled || _Debug._DisableHeightAboveWaterOptimization;
|
||||
|
||||
internal void OnEnable()
|
||||
{
|
||||
_VolumeMaterial = _Material;
|
||||
@@ -180,22 +179,27 @@ namespace WaveHarmonic.Crest
|
||||
_MaskMaterial = new(WaterResources.Instance.Shaders._UnderwaterMask);
|
||||
}
|
||||
|
||||
if (_HorizonMaskMaterial == null)
|
||||
{
|
||||
_HorizonMaskMaterial = new(WaterResources.Instance.Shaders._HorizonMask);
|
||||
}
|
||||
|
||||
if (_ArtifactsShader == null)
|
||||
{
|
||||
_ArtifactsShader = WaterResources.Instance.Compute._UnderwaterArtifacts;
|
||||
}
|
||||
|
||||
OnEnableMask();
|
||||
|
||||
if (RenderPipelineHelper.IsUniversal)
|
||||
{
|
||||
#if d_UnityURP
|
||||
UnderwaterMaskPassURP.Enable(this);
|
||||
UnderwaterEffectPassURP.Enable(this);
|
||||
#endif
|
||||
}
|
||||
else if (RenderPipelineHelper.IsHighDefinition)
|
||||
{
|
||||
#if d_UnityHDRP
|
||||
UnderwaterMaskPassHDRP.Enable(this);
|
||||
UnderwaterEffectPassHDRP.Enable(this);
|
||||
#endif
|
||||
}
|
||||
@@ -223,13 +227,13 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
RenderPipelineManager.activeRenderPipelineTypeChanged -= OnActiveRenderPipelineTypeChanged;
|
||||
|
||||
OnDisableMask();
|
||||
|
||||
#if d_UnityURP
|
||||
UnderwaterMaskPassURP.Disable();
|
||||
UnderwaterEffectPassURP.Disable();
|
||||
#endif
|
||||
|
||||
#if d_UnityHDRP
|
||||
UnderwaterMaskPassHDRP.Disable();
|
||||
UnderwaterEffectPassHDRP.Disable();
|
||||
#endif
|
||||
|
||||
@@ -243,27 +247,36 @@ namespace WaveHarmonic.Crest
|
||||
internal void OnDestroy()
|
||||
{
|
||||
Helpers.Destroy(_MaskMaterial);
|
||||
Helpers.Destroy(_HorizonMaskMaterial);
|
||||
// Without will cause exception in editor in play mode if disable Write Motion Vectors.
|
||||
_MaskMaterial = null;
|
||||
_HorizonMaskMaterial = null;
|
||||
}
|
||||
|
||||
internal bool ShouldRender(Camera camera, Pass pass)
|
||||
{
|
||||
if (!_Enabled || _Material == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_Water == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Helpers.MaskIncludesLayer(camera.cullingMask, _Layer))
|
||||
if (!WaterRenderer.ShouldRender(camera, _Layer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip entire mask pass if possible.
|
||||
if (pass == Pass.Mask && !_Water.Surface.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Do not execute when editor is not active to conserve power and prevent possible leaks.
|
||||
if (!UnityEditorInternal.InternalEditorUtility.isApplicationActive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GL.wireframe)
|
||||
{
|
||||
return false;
|
||||
@@ -275,16 +288,6 @@ namespace WaveHarmonic.Crest
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_Water.IsProxyPlaneRendering)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (camera.cameraType == CameraType.Preview)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
var isReflectionCamera = camera.cameraType == CameraType.Reflection;
|
||||
@@ -304,14 +307,20 @@ namespace WaveHarmonic.Crest
|
||||
// Otherwise, filtering depends on the camera's culling mask which is not always
|
||||
// accessible like with the global "Reflection Probes Camera". But whether those
|
||||
// cameras triggering camera events is a bug is TBD as it is intermittent.
|
||||
if (!_AllCameras && camera != _Water.Viewer && camera.cameraType != CameraType.SceneView && camera != WaterReflections.CurrentCamera)
|
||||
if (!_AllCameras && camera != _Water.GetViewer(includeSceneCamera: false) && camera.cameraType != CameraType.SceneView && camera != WaterReflections.CurrentCamera)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pass != Pass.Culling && !Active)
|
||||
if (!_Debug._DisableHeightAboveWaterOptimization && !Portaled)
|
||||
{
|
||||
return false;
|
||||
_Water.UpdatePerCameraHeight(camera);
|
||||
_ViewerWaterHeight = _Water._ViewerHeightAboveWaterPerCamera;
|
||||
|
||||
if (_ViewerWaterHeight > 2f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -319,7 +328,7 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
void RevertCulling()
|
||||
{
|
||||
foreach (var tile in _Water.Chunks)
|
||||
foreach (var tile in _Water.Surface.Chunks)
|
||||
{
|
||||
if (tile.Rend == null || tile._Culled)
|
||||
{
|
||||
@@ -330,27 +339,65 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnBeforeCameraRender(Camera camera)
|
||||
// Called by WaterRenderer. Camera must have water layer.
|
||||
internal void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
|
||||
{
|
||||
OnBeginCameraRendering(camera);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Populated by this point.
|
||||
if (_VolumeMaterial.shader != WaterResources.Instance.Shaders._UnderwaterEffect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if d_UnityURP
|
||||
if (RenderPipelineHelper.IsUniversal)
|
||||
{
|
||||
UnderwaterEffectPassURP.s_Instance?.EnqueuePass(context, camera);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
|
||||
#if d_UnityHDRP
|
||||
if (RenderPipelineHelper.IsHighDefinition)
|
||||
{
|
||||
UnderwaterEffectPassHDRP.s_Instance?.OnBeginCameraRendering(context, camera);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
|
||||
{
|
||||
OnBeforeLegacyRender(camera);
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnBeginCameraRendering(Camera camera)
|
||||
{
|
||||
if (!ShouldRender(camera, Pass.Culling))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var viewpoint = camera.transform.position;
|
||||
_SamplingHeightHelper.SampleHeight(System.HashCode.Combine(GetHashCode(), camera.GetHashCode()), viewpoint, out var height, allowMultipleCallsPerFrame: true);
|
||||
_ViewerWaterHeight = viewpoint.y - height;
|
||||
|
||||
// ShouldRender has a special case for this pass which skips the Active check so we
|
||||
// can always continue sampling.
|
||||
if (!Active)
|
||||
// Only one camera supported due to LOD center dependency.
|
||||
if (!UseLegacyMask && ShouldRender(camera, Pass.Mask) && camera == _Water.Viewer)
|
||||
{
|
||||
return;
|
||||
_Water.Surface.UpdateDisplacedSurfaceData(camera);
|
||||
}
|
||||
|
||||
_SurfaceMaterial = _Water.AboveOrBelowSurfaceMaterial;
|
||||
#if d_UnityHDRP
|
||||
if (RenderPipelineHelper.IsHighDefinition)
|
||||
{
|
||||
_Water.UpdateHighDefinitionLighting(camera);
|
||||
}
|
||||
#endif
|
||||
|
||||
_SurfaceMaterial = _Water.Surface.AboveOrBelowSurfaceMaterial;
|
||||
_VolumeMaterial = _Material;
|
||||
|
||||
var viewpoint = camera.transform.position;
|
||||
|
||||
// Grab material from a water body if camera is within its XZ bounds.
|
||||
foreach (var body in WaterBody.WaterBodies)
|
||||
{
|
||||
@@ -373,6 +420,13 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (_VolumeMaterial.shader != WaterResources.Instance.Shaders._UnderwaterEffect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
var extinction = Vector3.zero;
|
||||
float minimumFogDensity = 0;
|
||||
|
||||
@@ -405,7 +459,12 @@ namespace WaveHarmonic.Crest
|
||||
minimumFogDensity = Mathf.Max(minimumFogDensity, 0.0001f);
|
||||
}
|
||||
|
||||
UpdateEnvironmentalLighting(camera, extinction, _ViewerWaterHeight);
|
||||
if (_EnvironmentalInitialized)
|
||||
{
|
||||
_Water.UpdatePerCameraHeight(camera);
|
||||
_ViewerWaterHeight = _Water._ViewerHeightAboveWaterPerCamera;
|
||||
UpdateEnvironmentalLighting(camera, extinction, _ViewerWaterHeight);
|
||||
}
|
||||
|
||||
if (Portaled || _ViewerWaterHeight > -5f)
|
||||
{
|
||||
@@ -415,7 +474,7 @@ namespace WaveHarmonic.Crest
|
||||
|
||||
var extinctionLength = -Mathf.Log(_CullLimit) / minimumFogDensity;
|
||||
|
||||
foreach (var tile in _Water.Chunks)
|
||||
foreach (var tile in _Water.Surface.Chunks)
|
||||
{
|
||||
if (tile.Rend == null || tile._Culled)
|
||||
{
|
||||
@@ -436,10 +495,16 @@ namespace WaveHarmonic.Crest
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnAfterCameraRender(Camera camera)
|
||||
internal void OnEndCameraRendering(Camera camera)
|
||||
{
|
||||
RestoreEnvironmentalLighting();
|
||||
RevertCulling();
|
||||
_DoneMaskRead = false;
|
||||
|
||||
if (RenderPipelineHelper.IsLegacy)
|
||||
{
|
||||
OnAfterLegacyRender(camera);
|
||||
}
|
||||
}
|
||||
|
||||
void SetEnabled(bool previous, bool current)
|
||||
|
||||
Reference in New Issue
Block a user