Files
Fishing2/Packages/com.waveharmonic.crest/Runtime/Scripts/Volume/UnderwaterRenderer.Mask.cs
2025-05-10 12:49:47 +08:00

254 lines
11 KiB
C#

// Crest Water System
// 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
{
// 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 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 string k_ComputeShaderKernelFillMaskArtefacts = "FillMaskArtefacts";
static partial class ShaderIDs
{
// 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;
ComputeShader _ArtifactsShader;
bool _ArtifactsShaderInitialized;
int _ArtifactsKernel;
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";
SetUpArtifactsShader();
}
internal void OnDisableMask()
{
if (_MaskRT != null) _MaskRT.Release();
if (_DepthRT != null) _DepthRT.Release();
}
internal void SetUpArtifactsShader()
{
if (_ArtifactsShaderInitialized)
{
return;
}
_ArtifactsKernel = _ArtifactsShader.FindKernel(k_ComputeShaderKernelFillMaskArtefacts);
_ArtifactsShader.GetKernelThreadGroupSizes
(
_ArtifactsKernel,
out _ArtifactsThreadGroupSizeX,
out _ArtifactsThreadGroupSizeY,
out _
);
_ArtifactsShaderInitialized = true;
}
internal void SetUpMaskTextures(RenderTextureDescriptor descriptor)
{
if (!Helpers.RenderTargetTextureNeedsUpdating(_MaskRT, descriptor))
{
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);
#if d_CrestPortals
// Populate water volume before mask so we can use the stencil.
if (_Portals.Active)
{
_Portals.ReAllocate(descriptor);
_Portals.RenderMask(camera, _MaskCommandBuffer, _MaskMaterial);
_Portals.RenderStencil(_MaskCommandBuffer, _DepthRT, _DepthTarget);
}
#endif
_MaskCommandBuffer.SetRenderTarget(_MaskTarget, _DepthTarget);
SetUpMask(_MaskCommandBuffer, _MaskTarget, _DepthTarget);
PopulateMask(_MaskCommandBuffer, camera);
FixMaskArtefacts(_MaskCommandBuffer, descriptor, _MaskTarget);
}
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);
}
internal void FixMaskArtefacts(CommandBuffer buffer, RenderTextureDescriptor descriptor, RenderTargetIdentifier target)
{
if (_Debug._DisableArtifactCorrection)
{
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);
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)
{
// 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));
// Render fullscreen triangle with horizon mask pass.
commandBuffer.DrawProcedural(Matrix4x4.identity, _MaskMaterial, shaderPass: k_ShaderPassWaterHorizonMask, MeshTopology.Triangles, 3, 1);
}
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
}
}
}
}