244 lines
9.6 KiB
C#
244 lines
9.6 KiB
C#
// Crest Water System
|
|
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
|
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using WaveHarmonic.Crest.Internal;
|
|
|
|
namespace WaveHarmonic.Crest
|
|
{
|
|
partial class SurfaceRenderer
|
|
{
|
|
partial class ShaderIDs
|
|
{
|
|
public static readonly int s_DummyTarget = Shader.PropertyToID("_Crest_DummyTarget");
|
|
public static readonly int s_WorldToShadow = Shader.PropertyToID("_Crest_WorldToShadow");
|
|
|
|
public static class Unity
|
|
{
|
|
public static readonly int s_BuiltInSurface = Shader.PropertyToID("_BUILTIN_Surface");
|
|
public static readonly int s_BuiltInTransparentReceiveShadows = Shader.PropertyToID("_BUILTIN_TransparentReceiveShadows");
|
|
}
|
|
}
|
|
|
|
CommandBuffer _DrawWaterSurfaceBuffer;
|
|
|
|
void OnBeginCameraRenderingLegacy(Camera camera)
|
|
{
|
|
_Water.UpdateMatrices(camera);
|
|
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying)
|
|
{
|
|
OnPreRenderWaterLevelDepthTexture(camera);
|
|
}
|
|
#endif
|
|
|
|
// Everything from here depends on the material being transparent.
|
|
if (!IsTransparent(Material))
|
|
{
|
|
return;
|
|
}
|
|
|
|
camera.depthTextureMode |= DepthTextureMode.Depth;
|
|
|
|
_DrawWaterSurfaceBuffer ??= new() { name = WaterRenderer.k_DrawWater };
|
|
_DrawWaterSurfaceBuffer.Clear();
|
|
|
|
// Create or update RT.
|
|
_Water.OnBeginCameraOpaqueTexture(camera);
|
|
|
|
SetUpShadows(camera);
|
|
|
|
|
|
if (_Water.RenderBeforeTransparency)
|
|
{
|
|
Draw(_DrawWaterSurfaceBuffer, camera);
|
|
}
|
|
|
|
camera.AddCommandBuffer(CameraEvent.BeforeForwardAlpha, _DrawWaterSurfaceBuffer);
|
|
}
|
|
|
|
void OnEndCameraRenderingLegacy(Camera camera)
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying)
|
|
{
|
|
OnPostRenderWaterLevelDepthTexture(camera);
|
|
}
|
|
#endif
|
|
|
|
_Water.OnEndCameraOpaqueTexture(camera);
|
|
|
|
if (_DrawWaterSurfaceBuffer != null)
|
|
{
|
|
camera.RemoveCommandBuffer(CameraEvent.BeforeForwardAlpha, _DrawWaterSurfaceBuffer);
|
|
}
|
|
|
|
if (QualitySettings.shadows != ShadowQuality.Disable && _Water.PrimaryLight != null)
|
|
{
|
|
if (_ScreenSpaceShadowMapBuffer != null)
|
|
{
|
|
_Water.PrimaryLight.RemoveCommandBuffer(LightEvent.AfterScreenspaceMask, _ScreenSpaceShadowMapBuffer);
|
|
}
|
|
|
|
if (_DeferredShadowMapBuffer != null)
|
|
{
|
|
_Water.PrimaryLight.RemoveCommandBuffer(LightEvent.AfterShadowMap, _DeferredShadowMapBuffer);
|
|
}
|
|
}
|
|
|
|
Shader.SetGlobalTexture(Crest.ShaderIDs.Unity.s_ShadowMapTexture, Texture2D.whiteTexture);
|
|
}
|
|
|
|
// Draws the water surface including lighting.
|
|
internal void Draw(CommandBuffer commands, Camera camera)
|
|
{
|
|
commands.BeginSample(k_DrawWaterSurface);
|
|
|
|
CoreUtils.SetRenderTarget(commands, BuiltinRenderTextureType.CameraTarget);
|
|
|
|
var sun = RenderSettings.sun;
|
|
if (sun != null)
|
|
{
|
|
// 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
|
|
commands.SetGlobalVector(Crest.ShaderIDs.Unity.s_LightColor0, sun.FinalColor());
|
|
commands.SetGlobalVector(Crest.ShaderIDs.Unity.s_WorldSpaceLightPos0, -sun.transform.forward);
|
|
}
|
|
|
|
// Always enabled.
|
|
commands.SetShaderKeyword("LIGHTPROBE_SH", true);
|
|
|
|
UpdateChunkVisibility(camera);
|
|
|
|
foreach (var chunk in Chunks)
|
|
{
|
|
var renderer = chunk.Rend;
|
|
|
|
if (chunk.Rend == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!chunk._Visible)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (chunk._Culled)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!chunk._WaterDataHasBeenBound)
|
|
{
|
|
chunk.Bind();
|
|
}
|
|
|
|
var mpb = new PropertyWrapperMPB(chunk._MaterialPropertyBlock);
|
|
mpb.SetSHCoefficients(chunk.transform.position);
|
|
commands.DrawMesh(chunk._Mesh, chunk.transform.localToWorldMatrix, renderer.sharedMaterial, 0, 0, chunk._MaterialPropertyBlock);
|
|
}
|
|
|
|
commands.EndSample(k_DrawWaterSurface);
|
|
}
|
|
}
|
|
|
|
partial class SurfaceRenderer
|
|
{
|
|
Material _ForceShadowsMaterial;
|
|
ComputeBuffer _ShadowMatrixBuffer;
|
|
readonly Matrix4x4[] _ShadowMatrixDefaults = { Matrix4x4.zero, Matrix4x4.zero, Matrix4x4.zero, Matrix4x4.zero };
|
|
Material _CaptureShadowMatrices;
|
|
|
|
CommandBuffer _DeferredShadowMapBuffer;
|
|
CommandBuffer _ScreenSpaceShadowMapBuffer;
|
|
|
|
void LegacyOnEnable()
|
|
{
|
|
_ShadowMatrixBuffer ??= new(4, sizeof(float) * 16, ComputeBufferType.Structured);
|
|
_ShadowMatrixBuffer.SetData(_ShadowMatrixDefaults);
|
|
}
|
|
|
|
void LegacyOnDisable()
|
|
{
|
|
_ShadowMatrixBuffer?.Dispose();
|
|
_ShadowMatrixBuffer = null;
|
|
}
|
|
|
|
void SetUpShadows(Camera camera)
|
|
{
|
|
if (QualitySettings.shadows == ShadowQuality.Disable || _Water.PrimaryLight == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var transform = camera.transform;
|
|
|
|
if (_ForceShadowsMaterial == null)
|
|
{
|
|
_ForceShadowsMaterial = new Material(WaterResources.Instance.Shaders._ForceShadows);
|
|
}
|
|
|
|
// Force shadows, as Unity ignores transparent shadow receivers, otherwise shadow
|
|
// passes will skip if caster or receiver out of view. ShadowLod also depends on this.
|
|
Graphics.RenderMesh
|
|
(
|
|
new(_ForceShadowsMaterial)
|
|
{
|
|
receiveShadows = true,
|
|
shadowCastingMode = ShadowCastingMode.Off,
|
|
},
|
|
mesh: Helpers.QuadMesh,
|
|
submeshIndex: 0,
|
|
objectToWorld: QualitySettings.shadowProjection == ShadowProjection.StableFit
|
|
? Matrix4x4.TRS(transform.position + transform.forward, Quaternion.LookRotation(transform.forward), Vector3.one * 0.01f)
|
|
// TODO: render water level inputs to support shadows for varying water level.
|
|
// Sort of works for close fit. But will decrease shadow quality.
|
|
: Matrix4x4.TRS(Vector3.up * _Water.SeaLevel, Quaternion.LookRotation(-Vector3.up), Vector3.one * 100f)
|
|
);
|
|
|
|
if (!Material.IsKeywordEnabled("_BUILTIN_TRANSPARENT_RECEIVES_SHADOWS"))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_CaptureShadowMatrices == null)
|
|
{
|
|
_CaptureShadowMatrices = new Material(WaterResources.Instance.Shaders._CaptureShadowMatrices);
|
|
}
|
|
|
|
// Used ComputeBuffer must always be bound!
|
|
Shader.SetGlobalBuffer(ShaderIDs.s_WorldToShadow, _ShadowMatrixBuffer);
|
|
// Capture shadow matrices, as Unity clears all but the first cascade.
|
|
_ScreenSpaceShadowMapBuffer ??= new() { name = WaterRenderer.k_DrawWater };
|
|
_ScreenSpaceShadowMapBuffer.Clear();
|
|
// Cannot set target to None, as it will make some UI black (Unity bug?).
|
|
_ScreenSpaceShadowMapBuffer.GetTemporaryRT(ShaderIDs.s_DummyTarget, new RenderTextureDescriptor(4, 4));
|
|
CoreUtils.SetRenderTarget(_ScreenSpaceShadowMapBuffer, ShaderIDs.s_DummyTarget);
|
|
// Setting the buffer (SetGlobalBuffer) and writing to it only worked with Metal.
|
|
// For other graphics APIs, had to use SetRandomWriteTarget.
|
|
_ScreenSpaceShadowMapBuffer.ClearRandomWriteTargets();
|
|
_ScreenSpaceShadowMapBuffer.SetRandomWriteTarget(1, _ShadowMatrixBuffer);
|
|
_ScreenSpaceShadowMapBuffer.DrawProcedural(Matrix4x4.identity, _CaptureShadowMatrices, 0, MeshTopology.Triangles, 3);
|
|
_ScreenSpaceShadowMapBuffer.ClearRandomWriteTargets();
|
|
_ScreenSpaceShadowMapBuffer.ReleaseTemporaryRT(ShaderIDs.s_DummyTarget);
|
|
_Water.PrimaryLight.AddCommandBuffer(LightEvent.AfterScreenspaceMask, _ScreenSpaceShadowMapBuffer);
|
|
|
|
// Make shadow map available to transparents.
|
|
// Call this regardless of rendering path as it has no negative consequences for forward.
|
|
_DeferredShadowMapBuffer ??= new() { name = WaterRenderer.k_DrawWater };
|
|
_DeferredShadowMapBuffer.Clear();
|
|
_DeferredShadowMapBuffer.SetGlobalTexture(Crest.ShaderIDs.Unity.s_ShadowMapTexture, BuiltinRenderTextureType.CurrentActive);
|
|
_Water.PrimaryLight.AddCommandBuffer(LightEvent.AfterShadowMap, _DeferredShadowMapBuffer);
|
|
|
|
// Set up shadow keywords.
|
|
_DrawWaterSurfaceBuffer.SetKeyword(new("SHADOWS_SINGLE_CASCADE"), QualitySettings.shadowCascades == 1);
|
|
_DrawWaterSurfaceBuffer.SetKeyword(new("SHADOWS_SPLIT_SPHERES"), QualitySettings.shadowProjection == ShadowProjection.StableFit);
|
|
_DrawWaterSurfaceBuffer.SetKeyword(new("SHADOWS_SOFT"), QualitySettings.shadows == ShadowQuality.All);
|
|
}
|
|
}
|
|
}
|