升级水插件

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

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