Files
2026-03-04 10:03:45 +08:00

421 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using UltimateWater.Internal;
using UnityEngine;
using UnityEngine.Rendering;
namespace UltimateWater
{
[RequireComponent(typeof(Light))]
public sealed class LightWaterEffects : MonoBehaviour
{
public enum CausticsMode
{
None = 0,
ProjectedTexture = 1,
Raymarching = 2
}
[Range(0f, 3f)]
public float Intensity = 1f;
public bool CastShadows = true;
public Texture2D ProjectedTexture;
[SerializeField]
public float UvScale = 1f;
[Range(0f, 0.25f)]
public float ScrollSpeed = 0.01f;
[Range(0f, 8f)]
public float Distortions1 = 1f;
[Range(0f, 8f)]
public float Distortions2 = 1f;
[Tooltip("Optional.")]
[SerializeField]
public Transform ScrollDirectionPointer;
[HideInInspector]
[SerializeField]
private Shader _WorldPosShader;
[HideInInspector]
[SerializeField]
private Shader _CausticsMapShader;
[HideInInspector]
[SerializeField]
private Shader _NormalMapperShader;
[HideInInspector]
[SerializeField]
private Shader _CausticUtilShader;
[SerializeField]
private CausticsMode _CausticsMode = CausticsMode.ProjectedTexture;
[SerializeField]
private LayerMask _CausticReceiversMask = int.MaxValue;
[SerializeField]
private Blur _Blur;
[Tooltip("Causes minor allocation per frame (no way around it), but makes caustics rendering a lot faster. Disable it, if you don't use terrains.")]
[SerializeField]
private bool _SkipTerrainTrees = true;
private Camera _RenderCamera;
private WaterCamera _WaterCamera;
private Material _CausticUtilMat;
private Light _LocalLight;
private Vector2 _Offset;
private Vector2 _Scroll;
private bool _RenderingPrepared;
private bool[] _TerrainSettingTemp;
private int _Id;
private CommandBuffer _CopyShadowmap;
private RenderTexture _WorldPosMap;
private RenderTexture _CausticsMap;
public static readonly List<LightWaterEffects> Lights = new List<LightWaterEffects>();
private static int _ShadowmapId;
public Light UnityLight => _LocalLight;
public CausticsMode Mode
{
get
{
return _CausticsMode;
}
set
{
_CausticsMode = value;
if (_CausticsMode == CausticsMode.None)
{
ResetCausticMaps();
Camera.onPreCull = (Camera.CameraCallback)Delegate.Remove(Camera.onPreCull, new Camera.CameraCallback(OnSomeCameraGlobalPreCull));
return;
}
if (_RenderCamera == null)
{
CreateCausticsCamera();
}
if (Camera.onPreCull != null)
{
Camera.onPreCull = (Camera.CameraCallback)Delegate.Remove(Camera.onPreCull, new Camera.CameraCallback(OnSomeCameraGlobalPreCull));
}
Camera.onPreCull = (Camera.CameraCallback)Delegate.Combine(Camera.onPreCull, new Camera.CameraCallback(OnSomeCameraGlobalPreCull));
}
}
private void Awake()
{
_ShadowmapId = ShaderVariables.WaterShadowmap;
_TerrainSettingTemp = new bool[32];
_LocalLight = GetComponent<Light>();
_CausticUtilMat = new Material(_CausticUtilShader)
{
hideFlags = HideFlags.DontSave
};
}
private void OnEnable()
{
Lights.Add(this);
OnValidate();
if (_CausticsMode != CausticsMode.None)
{
CreateCausticsCamera();
}
else
{
ResetCausticMaps();
}
_Id = Lights.Count - 1;
Camera.onPreCull = (Camera.CameraCallback)Delegate.Remove(Camera.onPreCull, new Camera.CameraCallback(OnSomeCameraGlobalPreCull));
Camera.onPreCull = (Camera.CameraCallback)Delegate.Combine(Camera.onPreCull, new Camera.CameraCallback(OnSomeCameraGlobalPreCull));
}
private void OnDisable()
{
Camera.onPreCull = (Camera.CameraCallback)Delegate.Remove(Camera.onPreCull, new Camera.CameraCallback(OnSomeCameraGlobalPreCull));
ResetCausticMaps();
Lights.Remove(this);
TextureUtility.Release(ref _WorldPosMap);
TextureUtility.Release(ref _CausticsMap);
if (_RenderCamera != null)
{
_RenderCamera.gameObject.Destroy();
_RenderCamera = null;
}
if (_WaterCamera != null)
{
_WaterCamera.gameObject.Destroy();
_WaterCamera = null;
}
}
private void Update()
{
if (_RenderCamera == null)
{
CreateCausticsCamera();
}
if (ScrollDirectionPointer != null)
{
Vector3 forward = ScrollDirectionPointer.forward;
float num = ScrollSpeed * UvScale * Time.deltaTime;
_Scroll.x += forward.x * num;
_Scroll.y += forward.z * num;
}
else
{
float num2 = 0.7f * ScrollSpeed * UvScale * Time.deltaTime;
_Scroll.x += num2;
_Scroll.y += num2;
}
}
private void OnValidate()
{
if (_WorldPosShader == null)
{
_WorldPosShader = Shader.Find("UltimateWater/Caustics/WorldPos");
}
if (_CausticsMapShader == null)
{
_CausticsMapShader = Shader.Find("UltimateWater/Caustics/Map");
}
if (_NormalMapperShader == null)
{
_NormalMapperShader = Shader.Find("UltimateWater/Caustics/NormalMapper");
}
if (_CausticUtilShader == null)
{
_CausticUtilShader = Shader.Find("UltimateWater/Caustics/Utility");
}
_Blur.Validate();
if (_CausticsMode == CausticsMode.None)
{
ResetCausticMaps();
}
}
public void PrepareRenderingOnCamera(WaterCamera targetCamera)
{
if (!_RenderingPrepared && base.isActiveAndEnabled)
{
_RenderingPrepared = true;
if (CastShadows)
{
PrepareShadows(targetCamera.CameraComponent);
}
PrepareCaustics(targetCamera);
}
}
public void CleanRenderingOnCamera()
{
if (_RenderingPrepared)
{
_RenderingPrepared = false;
if (_CopyShadowmap != null)
{
_LocalLight.RemoveCommandBuffer(LightEvent.AfterScreenspaceMask, _CopyShadowmap);
}
}
}
public void AddWorldSpaceOffset(Vector3 offset)
{
if (base.isActiveAndEnabled)
{
_Offset.x += Vector3.Dot(offset, _RenderCamera.transform.right) * UvScale / (_RenderCamera.orthographicSize * 2f);
_Offset.y += Vector3.Dot(offset, _RenderCamera.transform.up) * UvScale / (_RenderCamera.orthographicSize * 2f);
}
}
private void OnSomeCameraGlobalPreCull(Camera cameraComponent)
{
if (_RenderingPrepared)
{
base.transform.position = new Vector3(_Id, 6.137f, 0f);
}
}
private void CreateCausticsCamera()
{
if (_CausticsMode == CausticsMode.ProjectedTexture)
{
_CausticsMap = new RenderTexture(256, 256, 0, RenderTextureFormat.RGHalf, RenderTextureReadWrite.Linear)
{
hideFlags = HideFlags.DontSave,
wrapMode = TextureWrapMode.Repeat,
name = "[UWS] LightWaterEffects - Caustics Map"
};
}
else
{
_WorldPosMap = new RenderTexture(256, 256, 32, RenderTextureFormat.Depth, RenderTextureReadWrite.Linear)
{
hideFlags = HideFlags.DontSave,
wrapMode = TextureWrapMode.Clamp,
name = "[UWS] LightWaterEffects - WorldPosMap"
};
_CausticsMap = new RenderTexture(512, 512, 0, RenderTextureFormat.RHalf, RenderTextureReadWrite.Linear)
{
hideFlags = HideFlags.DontSave,
wrapMode = TextureWrapMode.Clamp,
name = "[UWS] LightWaterEffects - CausticsMap"
};
}
GameObject gameObject = new GameObject("Caustic Camera")
{
hideFlags = HideFlags.DontSave
};
gameObject.transform.position = base.transform.position;
gameObject.transform.rotation = base.transform.rotation;
_RenderCamera = gameObject.AddComponent<Camera>();
_RenderCamera.enabled = false;
_RenderCamera.orthographic = true;
_RenderCamera.orthographicSize = 85f;
_RenderCamera.farClipPlane = 5000f;
_RenderCamera.depthTextureMode = DepthTextureMode.None;
_RenderCamera.allowHDR = true;
_RenderCamera.useOcclusionCulling = false;
_RenderCamera.backgroundColor = new Color(0f, 0f, 0f, 0f);
_RenderCamera.renderingPath = RenderingPath.VertexLit;
_WaterCamera = gameObject.AddComponent<WaterCamera>();
_WaterCamera.RenderWaterDepth = false;
_WaterCamera.RenderVolumes = false;
_WaterCamera.Type = WaterCamera.CameraType.Effect;
_WaterCamera.GeometryType = WaterGeometryType.UniformGrid;
gameObject.hideFlags |= HideFlags.HideInHierarchy;
}
private void PrepareShadows(Camera cameraComponent)
{
if (_CopyShadowmap == null)
{
_CopyShadowmap = new CommandBuffer
{
name = "[UWS] LightWaterEffects._CopyShadowmap"
};
}
Shader.SetGlobalTexture(_ShadowmapId, DefaultTextures.Get(Color.white));
_CopyShadowmap.Clear();
_CopyShadowmap.GetTemporaryRT(_ShadowmapId, cameraComponent.pixelWidth, cameraComponent.pixelHeight, 32, FilterMode.Point, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
_CopyShadowmap.Blit(BuiltinRenderTextureType.CurrentActive, _ShadowmapId);
_CopyShadowmap.ReleaseTemporaryRT(_ShadowmapId);
_LocalLight.RemoveCommandBuffer(LightEvent.AfterScreenspaceMask, _CopyShadowmap);
_LocalLight.AddCommandBuffer(LightEvent.AfterScreenspaceMask, _CopyShadowmap);
}
private void PrepareCaustics(WaterCamera waterCamera)
{
switch (_CausticsMode)
{
case CausticsMode.ProjectedTexture:
UpdateCausticsCameraPosition(waterCamera);
RenderProjectedTextureCaustics();
break;
case CausticsMode.Raymarching:
UpdateCausticsCameraPosition(waterCamera);
RenderRaymarchedCaustics(waterCamera);
break;
}
}
private void UpdateCausticsCameraPosition(WaterCamera waterCamera)
{
Vector2 center = waterCamera.LocalMapsRect.center;
_RenderCamera.transform.position = new Vector3(center.x, 0f, center.y) - base.transform.forward * Mathf.Max(Mathf.Abs(waterCamera.transform.position.y * 2.2f), 300f);
_RenderCamera.transform.rotation = base.transform.rotation;
}
private void RenderProjectedTextureCaustics()
{
_RenderCamera.cullingMask = 1 << WaterProjectSettings.Instance.WaterLayer;
_WaterCamera.RenderWaterWithShader("[PW Water] Caustics Normal Map", _CausticsMap, _NormalMapperShader, surfaces: true, volumes: false, volumesTwoPass: false);
Vector3 position = _RenderCamera.transform.position;
float num = Vector3.Dot(position, _RenderCamera.transform.right) * UvScale / (_RenderCamera.orthographicSize * 3.5f);
float num2 = Vector3.Dot(position, _RenderCamera.transform.up) * UvScale / (_RenderCamera.orthographicSize * 2f);
Shader.SetGlobalTexture("_CausticsMap", ProjectedTexture);
Shader.SetGlobalTexture("_CausticsDistortionMap", _CausticsMap);
Shader.SetGlobalFloat("_CausticsMultiplier", Intensity * 5f);
Shader.SetGlobalVector("_CausticsOffsetScale", new Vector4(_Offset.x + _Scroll.x + num, _Offset.y + _Scroll.y + num2, UvScale, Distortions1 * 0.02f));
Shader.SetGlobalVector("_CausticsOffsetScale2", new Vector4(_Offset.x - _Scroll.x + num + 0.5f, _Offset.y - _Scroll.y + num2, UvScale, Distortions2 * 0.02f));
Shader.SetGlobalMatrix("_CausticsMapProj", GL.GetGPUProjectionMatrix(_RenderCamera.projectionMatrix, renderIntoTexture: true) * _RenderCamera.worldToCameraMatrix);
}
private void RenderRaymarchedCaustics(WaterCamera waterCamera)
{
_CausticUtilMat.SetMatrix("_InvProjMatrix", Matrix4x4.Inverse(_RenderCamera.projectionMatrix * _RenderCamera.worldToCameraMatrix));
Graphics.SetRenderTarget(_WorldPosMap);
GL.Clear(clearDepth: true, clearColor: true, new Color(1f, 0f, 0f, 0f), 1f);
Terrain[] array = null;
if (_SkipTerrainTrees)
{
array = Terrain.activeTerrains;
if (_TerrainSettingTemp.Length < array.Length)
{
Array.Resize(ref _TerrainSettingTemp, array.Length * 2);
}
for (int i = 0; i < array.Length; i++)
{
_TerrainSettingTemp[i] = array[i].drawTreesAndFoliage;
array[i].drawTreesAndFoliage = false;
}
}
_WaterCamera.enabled = false;
_RenderCamera.orthographicSize = waterCamera.LocalMapsRect.width * 0.6f;
_RenderCamera.clearFlags = CameraClearFlags.Depth;
_RenderCamera.cullingMask = _CausticReceiversMask;
_RenderCamera.targetTexture = _WorldPosMap;
_RenderCamera.RenderWithShader(_WorldPosShader, "RenderType");
if (_SkipTerrainTrees)
{
for (int j = 0; j < array.Length; j++)
{
array[j].drawTreesAndFoliage = _TerrainSettingTemp[j];
}
}
Shader.SetGlobalTexture("_WorldPosMap", _WorldPosMap);
Shader.SetGlobalVector("_CausticLightDir", base.transform.forward);
Shader.SetGlobalFloat("_CausticLightIntensity", _LocalLight.intensity * Intensity * 1.5f);
_WaterCamera.enabled = true;
_RenderCamera.clearFlags = CameraClearFlags.Color;
_RenderCamera.cullingMask = 1 << WaterProjectSettings.Instance.WaterLayer;
_RenderCamera.targetTexture = _CausticsMap;
_RenderCamera.RenderWithShader(_CausticsMapShader, "CustomType");
_Blur.Apply(_CausticsMap);
Graphics.Blit(null, _CausticsMap, _CausticUtilMat, 1);
Shader.SetGlobalTexture("_CausticsMap", _CausticsMap);
Shader.SetGlobalFloat("_CausticsMultiplier", 1f);
Shader.SetGlobalMatrix("_CausticsMapProj", GL.GetGPUProjectionMatrix(_RenderCamera.projectionMatrix, renderIntoTexture: true) * _RenderCamera.worldToCameraMatrix);
}
private void ResetCausticMaps()
{
Shader.SetGlobalFloat("_CausticsMultiplier", 0f);
}
}
}