升级水插件

This commit is contained in:
2026-01-08 22:30:55 +08:00
parent febff82d24
commit ca68084264
415 changed files with 18138 additions and 7134 deletions

View File

@@ -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;
}
}
}