Files
Fishing2/Packages/com.waveharmonic.crest/Runtime/Scripts/Volume/UnderwaterRenderer.Mask.cs
2026-03-05 00:14:42 +08:00

222 lines
8.6 KiB
C#

// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using UnityEngine.Rendering;
namespace WaveHarmonic.Crest
{
partial class UnderwaterRenderer : MaskRenderer.IMaskReceiver, MaskRenderer.IMaskProvider
{
internal const string k_DrawMask = "Crest.DrawMask";
const string k_DrawMaskHorizon = "Horizon";
const string k_DrawMaskSurface = "Surface";
internal const int k_VolumeMaskQueue = 1000;
internal const int k_ShaderPassWaterSurfaceMask = 0;
internal const int k_ShaderPassWaterSurfaceDepth = 1;
internal const int k_ShaderPassWaterHorizonMask = 0;
internal const string k_ComputeShaderKernelFillMaskArtefacts = "FillMaskArtefacts";
static partial class ShaderIDs
{
// Local
public static readonly int s_FarPlaneOffset = Shader.PropertyToID("_Crest_FarPlaneOffset");
}
internal Material _MaskMaterial;
internal Material _HorizonMaskMaterial;
ComputeShader _ArtifactsShader;
bool _ArtifactsShaderInitialized;
int _ArtifactsKernel;
uint _ArtifactsThreadGroupSizeX;
uint _ArtifactsThreadGroupSizeY;
internal void OnEnableMask()
{
_Water._Mask.Add(this);
_Water._Mask.Add(k_VolumeMaskQueue, this);
SetUpArtifactsShader();
}
internal void OnDisableMask()
{
if (_Water == null) return;
_Water._Mask?.Remove(this as MaskRenderer.IMaskReceiver);
_Water._Mask?.Remove(this as MaskRenderer.IMaskProvider);
}
internal void SetUpArtifactsShader()
{
if (_ArtifactsShaderInitialized)
{
return;
}
_ArtifactsKernel = _ArtifactsShader.FindKernel(k_ComputeShaderKernelFillMaskArtefacts);
_ArtifactsShader.GetKernelThreadGroupSizes
(
_ArtifactsKernel,
out _ArtifactsThreadGroupSizeX,
out _ArtifactsThreadGroupSizeY,
out _
);
_ArtifactsShaderInitialized = true;
}
void MaskRenderer.IMaskProvider.OnMaskPass(CommandBuffer commands, Camera camera, MaskRenderer mask)
{
var color = mask.ColorRTH;
var depth = mask.DepthRTH;
var size = color.GetScaledSize(color.rtHandleProperties.currentViewportSize);
var descriptor = color.rt.descriptor;
descriptor.width = size.x; descriptor.height = size.y;
if (UseLegacyMask)
{
// Portals changes the target.
// When using the stencil we are already clearing depth and do not want to clear the stencil too. Clear
// color only when using the stencil as the horizon effectively clears it when not using it.
CoreUtils.SetRenderTarget(commands, color, depth, UseStencilBuffer ? ClearFlag.Color : ClearFlag.DepthStencil);
Helpers.ScaleViewport(camera, commands, color);
PopulateMask(commands, camera);
FixMaskArtefacts(commands, descriptor, mask._ColorRTI);
}
// Portals have their own fitted to the portal bounds.
else
#if d_CrestPortals
if (!Portaled || _Water.Portals.RequiresFullScreenMask)
#endif
{
RenderLineMask(commands, camera, mask.ColorRT.descriptor, mask._ColorRTI);
}
}
internal void RenderLineMask(CommandBuffer buffer, Camera camera, RenderTextureDescriptor descriptor, RenderTargetIdentifier target)
{
if (!_Water.Surface.Enabled)
{
return;
}
var keep = false;
#if d_CrestPortals
if (_Water.Portals.Active)
{
keep = _Water.Portals.RenderLineMask(buffer, target);
}
#endif
var wrapper = new PropertyWrapperCompute(buffer, WaterResources.Instance.Compute._Mask, (int)RenderPipelineHelper.RenderPipeline);
var parameters = _Water.Surface._SurfaceDataParameters;
wrapper.SetTexture(SurfaceRenderer.ShaderIDs.s_WaterLine, _Water.Surface.HeightRT);
wrapper.SetVector(SurfaceRenderer.ShaderIDs.s_WaterLineSnappedPosition, parameters._SnappedPosition);
wrapper.SetVector(SurfaceRenderer.ShaderIDs.s_WaterLineResolution, parameters._Resolution);
wrapper.SetFloat(SurfaceRenderer.ShaderIDs.s_WaterLineTexel, parameters._Texel);
// 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)
{
if (_Debug._DisableArtifactCorrection)
{
return;
}
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
(
_ArtifactsShader,
_ArtifactsKernel,
// Viewport sizes are not perfect so round up to cover.
Mathf.CeilToInt((float)descriptor.width / _ArtifactsThreadGroupSizeX),
Mathf.CeilToInt((float)descriptor.height / _ArtifactsThreadGroupSizeY),
descriptor.volumeDepth
);
}
// Populates a screen space mask which will inform the underwater postprocess. As a future optimisation we may
// be able to avoid this pass completely if we can reuse the camera depth after transparents are rendered.
internal void PopulateMask(CommandBuffer commandBuffer, Camera camera)
{
if (!_Water.Surface.Enabled && Portaled)
{
return;
}
// Render horizon into mask using a fullscreen triangle at the far plane. Horizon must be rendered first or
// it will overwrite the mask with incorrect values.
{
var zBufferParameters = Helpers.GetZBufferParameters(camera);
// Take 0-1 linear depth and convert non-linear depth.
_HorizonMaskMaterial.SetFloat(ShaderIDs.s_FarPlaneOffset, Helpers.LinearDepthToNonLinear(_FarPlaneMultiplier, zBufferParameters));
// Render fullscreen triangle with horizon mask pass.
commandBuffer.BeginSample(k_DrawMaskHorizon);
commandBuffer.DrawProcedural(Matrix4x4.identity, _HorizonMaskMaterial, shaderPass: k_ShaderPassWaterHorizonMask, MeshTopology.Triangles, 3, 1);
commandBuffer.EndSample(k_DrawMaskHorizon);
}
// Get all water chunks and render them using cmd buffer, but with mask shader.
if (!_Debug._DisableMask)
{
commandBuffer.BeginSample(k_DrawMaskSurface);
_Water.Surface.Render(camera, commandBuffer, _MaskMaterial, k_ShaderPassWaterSurfaceMask);
commandBuffer.EndSample(k_DrawMaskSurface);
}
}
internal bool _MaskRead;
bool _DoneMaskRead;
MaskRenderer.MaskInput MaskRenderer.IMaskProvider.Allocate()
{
return MaskRenderer.MaskInput.Both;
}
MaskRenderer.MaskInput MaskRenderer.IMaskReceiver.Allocate()
{
return MaskRenderer.MaskInput.Both;
}
MaskRenderer.MaskInput MaskRenderer.IMaskProvider.Write(Camera camera)
{
if (!_DoneMaskRead)
{
_MaskRead = ShouldRender(camera, Pass.Mask);
_DoneMaskRead = true;
}
return _MaskRead ? _Water.Surface.Enabled ? MaskRenderer.MaskInput.Both : MaskRenderer.MaskInput.Color : MaskRenderer.MaskInput.None;
}
}
}