Files
Fishing2/Assets/KriptoFX/WaterSystem2/WaterResources/Scripts/Standard/CommandPass/VolumetricLightingPrePass.cs
2025-06-21 18:06:12 +08:00

441 lines
20 KiB
C#

#if !KWS_HDRP && !KWS_URP
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using Unity.Collections;
using UnityEngine.Experimental.Rendering;
using static KWS.KWS_WaterLights;
using static KWS.KWS_ShaderConstants_PlatformSpecific;
namespace KWS
{
internal class VolumetricLightingPrePass: WaterPass
{
internal override string PassName => "Water.VolumetricLightingPrePass";
NativeArray<LightData> lightsData;
NativeArray<ShadowLightData> shadowLightsData;
ComputeBuffer dirLightDataBuffer;
ComputeBuffer pointLightDataBuffer;
ComputeBuffer shadowPointLightDataBuffer;
ComputeBuffer spotLightDataBuffer;
ComputeBuffer shadowSpotLightDataBuffer;
List<VolumeLight> activeDirLights = new List<VolumeLight>(1);
List<VolumeLight> activePointLights = new List<VolumeLight>(10);
List<VolumeLight> activePointLightsShadows = new List<VolumeLight>(10);
List<VolumeLight> activeSpotLights = new List<VolumeLight>(10);
List<VolumeLight> activeSpotLightsShadows = new List<VolumeLight>(10);
private RenderTexture _defaultShadowmap;
private RenderTexture _defaultPointShadowmap;
public override void ExecuteBeforeCameraRendering(Camera cam, ScriptableRenderContext context)
{
UnityEngine.Profiling.Profiler.BeginSample("Water.CollectAllLightsToOneBuffer");
CollectAllLightsToOneBuffer(cam);
UnityEngine.Profiling.Profiler.EndSample();
}
internal VolumetricLightingPrePass()
{
SetDefaultBuffers();
}
void SetDefaultBuffers()
{
KWS_CoreUtils.SetFallbackBuffer<ShadowLightData>(ref dirLightDataBuffer, LightsID.KWS_DirLightsBuffer);
KWS_CoreUtils.SetFallbackBuffer<LightData>(ref pointLightDataBuffer, LightsID.KWS_PointLightsBuffer);
KWS_CoreUtils.SetFallbackBuffer<ShadowLightData>(ref shadowPointLightDataBuffer, LightsID.KWS_ShadowPointLightsBuffer);
KWS_CoreUtils.SetFallbackBuffer<LightData>(ref spotLightDataBuffer, LightsID.KWS_SpotLightsBuffer);
KWS_CoreUtils.SetFallbackBuffer<ShadowLightData>(ref shadowSpotLightDataBuffer, LightsID.KWS_ShadowSpotLightsBuffer);
}
void CollectAllLightsToOneBuffer(Camera cam)
{
if (!KWS_UpdateManager.FrustumCaches.TryGetValue(cam, out var frustumCache)) return;
var frustumCameraPlanes = frustumCache.FrustumPlanes;
var lights = KWS_WaterLights.Lights;
activeDirLights.Clear();
activePointLights.Clear();
activePointLightsShadows.Clear();
activeSpotLights.Clear();
activeSpotLightsShadows.Clear();
var camT = cam.transform;
var camPos = camT.position;
foreach (var volumeLight in lights)
{
volumeLight.IsVisible = IsLightVisible(frustumCameraPlanes, volumeLight);
if (volumeLight.IsVisible) volumeLight.SqrDistanceToCamera = Vector3.SqrMagnitude(camPos - volumeLight.LightTransform.position);
else volumeLight.SqrDistanceToCamera = float.MaxValue;
//volumeLight.ShadowIndex = -1;
}
lights.Sort((x, y) => x.SqrDistanceToCamera.CompareTo(y.SqrDistanceToCamera));
int shadowPointLightFreeIndex = 0;
int shadowSpotLightFreeIndex = 0;
foreach (var volumeLight in lights)
{
if (volumeLight.IsVisible)
{
var light = volumeLight.Light;
AddShadowLightByDistance(ref shadowPointLightFreeIndex, ref shadowSpotLightFreeIndex, volumeLight, light);
}
}
// Debug.Log("Point: " + activePointLights.Count + ", Point Shadows: " + activePointLightsShadows.Count + ", free indexes: " + KWS_WaterLights.FreeShadowIndexes[LightType.Point].Count);
//foreach (var l in activePointLightsShadows)
//{
// Debug.Log("shadow light: " + l.Light.name + " shadow index " + l.ShadowIndex);
//}
if (activeDirLights.Count > 0)
{
ComputeShadowLightsBuffer(activeDirLights, ref dirLightDataBuffer, LightType.Directional);
Shader.SetGlobalBuffer(LightsID.KWS_DirLightsBuffer, dirLightDataBuffer);
var useShadows = activeDirLights[0].Light.shadows != LightShadows.None;
var isSplit = activeDirLights.Count > 0 && QualitySettings.shadowProjection == ShadowProjection.StableFit;
Shader.SetGlobalInteger(LightsID.KWS_DirLightShadowStableFit, isSplit ? 1 : 0);
Shader.SetGlobalInteger(LightsID.KWS_DirLightShadowCascades, QualitySettings.shadowCascades);
Shader.SetGlobalInteger(LightsID.KWS_UseDirLightShadow, useShadows ? 1 : 0);
}
KWS_CoreUtils.SetKeyword(LightKeywords.KWS_USE_DIR_LIGHT, activeDirLights.Count > 0);
Shader.SetGlobalFloat(LightsID.KWS_DirLightsCount, 1);
if (activePointLights.Count > 0)
{
ComputeLightsBuffer(activePointLights, ref pointLightDataBuffer, LightType.Point);
Shader.SetGlobalBuffer(LightsID.KWS_PointLightsBuffer, pointLightDataBuffer);
}
KWS_CoreUtils.SetKeyword(LightKeywords.KWS_USE_POINT_LIGHTS, activePointLights.Count > 0 || activePointLightsShadows.Count > 0);
Shader.SetGlobalFloat(LightsID.KWS_PointLightsCount, activePointLights.Count);
if (activePointLightsShadows.Count > 0)
{
ComputeShadowLightsBuffer(activePointLightsShadows, ref shadowPointLightDataBuffer, LightType.Point);
Shader.SetGlobalBuffer(LightsID.KWS_ShadowPointLightsBuffer, shadowPointLightDataBuffer);
}
KWS_CoreUtils.SetKeyword(LightKeywords.KWS_USE_SHADOW_POINT_LIGHTS, activePointLightsShadows.Count > 0);
Shader.SetGlobalFloat(LightsID.KWS_ShadowPointLightsCount, activePointLightsShadows.Count);
if (activeSpotLights.Count > 0)
{
ComputeLightsBuffer(activeSpotLights, ref spotLightDataBuffer, LightType.Spot);
Shader.SetGlobalBuffer(LightsID.KWS_SpotLightsBuffer, spotLightDataBuffer);
}
KWS_CoreUtils.SetKeyword(LightKeywords.KWS_USE_SPOT_LIGHTS, activeSpotLights.Count > 0 || activeSpotLightsShadows.Count > 0);
Shader.SetGlobalFloat(LightsID.KWS_SpotLightsCount, activeSpotLights.Count);
if (activeSpotLightsShadows.Count > 0)
{
ComputeShadowLightsBuffer(activeSpotLightsShadows, ref shadowSpotLightDataBuffer, LightType.Spot);
Shader.SetGlobalBuffer(LightsID.KWS_ShadowSpotLightsBuffer, shadowSpotLightDataBuffer);
}
KWS_CoreUtils.SetKeyword(LightKeywords.KWS_USE_SHADOW_SPOT_LIGHTS, activeSpotLightsShadows.Count > 0);
Shader.SetGlobalFloat(LightsID.KWS_ShadowSpotLightsCount, activeSpotLightsShadows.Count);
UpdateEmptyTextureSlots();
foreach (var volumeLight in lights)
{
volumeLight.LightUpdate(camPos);
}
}
void UpdateEmptyTextureSlots()
{
if (_defaultShadowmap == null) _defaultShadowmap = new RenderTexture(2, 2, 0, GraphicsFormat.R16_UNorm);
if (_defaultPointShadowmap == null) _defaultPointShadowmap = new RenderTexture(2, 2, 0, GraphicsFormat.R16_UNorm) { dimension = TextureDimension.Cube };
Shader.SetGlobalTexture(LightShadowmapID[LightType.Directional][0].ShadowmapNameID, _defaultShadowmap);
for (int i = 0; i < 4; i++)
{
Shader.SetGlobalTexture(LightShadowmapID[LightType.Point][i].ShadowmapNameID, _defaultPointShadowmap);
Shader.SetGlobalTexture(LightShadowmapID[LightType.Spot][i].ShadowmapNameID, _defaultShadowmap);
}
}
private void AddShadowLightByDistance(ref int shadowPointLightFreeIndex, ref int shadowSpotLightFreeIndex, VolumeLight volumeLight, Light light)
{
switch (light.type)
{
case LightType.Directional:
{
volumeLight.ShadowIndex = 0;
activeDirLights.Add(volumeLight);
}
break;
case LightType.Point:
if (light.shadows != LightShadows.None && volumeLight.UseVolumetricShadows && activePointLightsShadows.Count < MaxShadowPointLights)
{
volumeLight.ShadowIndex = shadowPointLightFreeIndex;
activePointLightsShadows.Add(volumeLight);
shadowPointLightFreeIndex++;
}
else
{
volumeLight.ShadowIndex = -1;
activePointLights.Add(volumeLight);
}
break;
case LightType.Spot:
if (light.shadows != LightShadows.None && volumeLight.UseVolumetricShadows && activeSpotLightsShadows.Count < MaxShadowSpotLights)
{
volumeLight.ShadowIndex = shadowSpotLightFreeIndex;
activeSpotLightsShadows.Add(volumeLight);
shadowSpotLightFreeIndex++;
}
else
{
volumeLight.ShadowIndex = -1;
activeSpotLights.Add(volumeLight);
}
break;
}
}
private void ComputeLightsBuffer(List<VolumeLight> lights, ref ComputeBuffer buffer, LightType type)
{
buffer = KWS_CoreUtils.GetOrUpdateBuffer<LightData>(ref buffer, lights.Count);
lightsData = new NativeArray<LightData>(lights.Count, Allocator.Temp);
int idx = 0;
foreach (var light in lights)
{
var currentLight = light.Light;
var data = lightsData[idx];
data.color = currentLight.color.linear * currentLight.intensity;
if (type == LightType.Point || type == LightType.Spot)
{
data.position = light.LightTransform.position;
data.range = currentLight.range;
GetPointLightAttenuation(ref data.attenuation, currentLight.range);
}
if (type == LightType.Spot)
{
GetSpotLightAttenuation(ref data.attenuation, currentLight.spotAngle);
}
if (type == LightType.Directional || type == LightType.Spot) data.forward = -light.LightTransform.forward;
lightsData[idx] = data;
idx++;
}
buffer.SetData(lightsData);
}
private void ComputeShadowLightsBuffer(List<VolumeLight> lights, ref ComputeBuffer buffer, LightType type)
{
buffer = KWS_CoreUtils.GetOrUpdateBuffer<ShadowLightData>(ref buffer, lights.Count);
shadowLightsData = new NativeArray<ShadowLightData>(lights.Count, Allocator.Temp);
int idx = 0;
foreach (var light in lights)
{
var currentLight = light.Light;
var data = shadowLightsData[idx];
data.color = currentLight.color.linear * currentLight.intensity;
data.shadowStrength = currentLight.shadowStrength;
data.shadowIndex = light.ShadowIndex;
if (type == LightType.Point || type == LightType.Spot)
{
data.position = light.LightTransform.position;
data.range = currentLight.range;
GetPointLightAttenuation(ref data.attenuation, currentLight.range);
}
if (type == LightType.Point)
{
data.projectionParams = ComputePointLightProjectionParams(currentLight);
}
if (type == LightType.Spot)
{
GetSpotLightAttenuation(ref data.attenuation, currentLight.spotAngle);
data.worldToShadow = ComputeSpotLightShadowMatrix(currentLight);
}
if (type == LightType.Directional || type == LightType.Spot) data.forward = -light.LightTransform.forward;
shadowLightsData[idx] = data;
idx++;
}
buffer.SetData(shadowLightsData);
}
void GetPointLightAttenuation(ref Vector4 attenuation, float lightRange)
{
float lightRangeSqr = lightRange * lightRange;
float fadeStartDistanceSqr = 0.8f * 0.8f * lightRangeSqr;
float fadeRangeSqr = (fadeStartDistanceSqr - lightRangeSqr);
float lightRangeSqrOverFadeRangeSqr = -lightRangeSqr / fadeRangeSqr;
float oneOverLightRangeSqr = 1.0f / Mathf.Max(0.0001f, lightRange * lightRange);
attenuation.x = oneOverLightRangeSqr;
attenuation.y = lightRangeSqrOverFadeRangeSqr;
}
void GetSpotLightAttenuation(ref Vector4 attenuation, float spotAngle)
{
// Spot Attenuation with a linear falloff can be defined as
// (SdotL - cosOuterAngle) / (cosInnerAngle - cosOuterAngle)
// This can be rewritten as
// invAngleRange = 1.0 / (cosInnerAngle - cosOuterAngle)
// SdotL * invAngleRange + (-cosOuterAngle * invAngleRange)
// If we precompute the terms in a MAD instruction
spotAngle += 2;
float cosOuterAngle = Mathf.Cos(Mathf.Deg2Rad * spotAngle * 0.5f);
float cosInnerAngle = Mathf.Cos((2.0f * Mathf.Atan(Mathf.Tan(spotAngle * 0.5f * Mathf.Deg2Rad) * (64.0f - 18.0f) / 64.0f)) * 0.5f);
float smoothAngleRange = Mathf.Max(0.001f, cosInnerAngle - cosOuterAngle);
float invAngleRange = 1.0f / smoothAngleRange;
float add = -cosOuterAngle * invAngleRange;
attenuation.x = spotAngle;
attenuation.z = invAngleRange;
attenuation.w = add;
}
Vector4 ComputePointLightProjectionParams(Light light)
{
// for point light projection: x = zfar / (znear - zfar), y = (znear * zfar) / (znear - zfar), z=shadow bias, w=shadow scale bias
var far = light.range;
var near = light.shadowNearPlane;
return new Vector4(far / (near - far), (near * far) / (near - far), light.shadowBias, 0.97f);
}
public static bool IsLightVisible(Plane[] frustum, KWS_WaterLights.VolumeLight volumeLight)
{
var light = volumeLight.Light;
var isLightVisible = true;
if (light.type == LightType.Point) isLightVisible = IsPointLightVisible(frustum, volumeLight.LightTransform.position, light.range);
else if (light.type == LightType.Spot) isLightVisible = IsSpotLightVisible(frustum, volumeLight.LightTransform.position, volumeLight.LightTransform.forward, light.range, light.spotAngle);
return isLightVisible;
}
static bool IsPointLightVisible(Plane[] planes, Vector3 center, float radius)
{
for (int i = 0; i < planes.Length; i++)
{
if (planes[i].normal.x * center.x + planes[i].normal.y * center.y + planes[i].normal.z * center.z + planes[i].distance < -radius) return false;
}
return true;
}
static bool IsSpotLightVisible(Plane[] planes, Vector3 center, Vector3 direction, float radius, float angle)
{
var coneEndPosition = center + direction * radius;
var coneEndRadius = radius * Mathf.Tan(angle * Mathf.Deg2Rad * 0.5f);
for (int i = 0; i < planes.Length; i++)
{
if (planes[i].normal.x * center.x + planes[i].normal.y * center.y + planes[i].normal.z * center.z + planes[i].distance < -0.1 &&
planes[i].normal.x * coneEndPosition.x + planes[i].normal.y * coneEndPosition.y + planes[i].normal.z * coneEndPosition.z + planes[i].distance < -coneEndRadius) return false;
}
return true;
}
Quaternion GetRotationByCubeFace(CubemapFace face)
{
switch (face)
{
case CubemapFace.NegativeX: return Quaternion.Euler(0, -90, 0);
case CubemapFace.PositiveX: return Quaternion.Euler(0, 90, 0);
case CubemapFace.PositiveY: return Quaternion.Euler(90, 0, 0);
case CubemapFace.NegativeY: return Quaternion.Euler(-90, 0, 0);
case CubemapFace.PositiveZ: return Quaternion.Euler(0, 0, 0);
case CubemapFace.NegativeZ: return Quaternion.Euler(0, -180, 0);
}
return Quaternion.identity;
}
private Matrix4x4 ComputePointLightShadowMatrix(Light currentLight, CubemapFace face)
{
var clip = Matrix4x4.TRS(new Vector3(0.5f, 0.5f, 0.5f), Quaternion.identity, new Vector3(0.5f, 0.5f, 0.5f));
Matrix4x4 proj;
if (SystemInfo.usesReversedZBuffer)
proj = Matrix4x4.Perspective(90, 1, currentLight.range, currentLight.shadowNearPlane);
else
proj = Matrix4x4.Perspective(90, 1, currentLight.shadowNearPlane, currentLight.range);
var m = clip * proj;
m[0, 2] *= -1;
m[1, 2] *= -1;
m[2, 2] *= -1;
m[3, 2] *= -1;
var view = Matrix4x4.TRS(currentLight.transform.position, GetRotationByCubeFace(face), Vector3.one).inverse;
return m * view;
} //backup for atlas system instead of cubemaps
private Matrix4x4 ComputeSpotLightShadowMatrix(Light currentLight)
{
var clip = Matrix4x4.TRS(new Vector3(0.5f, 0.5f, 0.5f), Quaternion.identity, new Vector3(0.5f, 0.5f, 0.5f));
Matrix4x4 proj;
if (SystemInfo.usesReversedZBuffer)
proj = Matrix4x4.Perspective(currentLight.spotAngle, 1, currentLight.range, currentLight.shadowNearPlane);
else
proj = Matrix4x4.Perspective(currentLight.spotAngle, 1, currentLight.shadowNearPlane, currentLight.range);
var m = clip * proj;
m[0, 2] *= -1;
m[1, 2] *= -1;
m[2, 2] *= -1;
m[3, 2] *= -1;
var view = Matrix4x4.TRS(currentLight.transform.position, currentLight.transform.rotation, Vector3.one).inverse;
return m * view;
}
public override void Release()
{
foreach (var light in KWS_WaterLights.Lights)
{
light.IsVisible = false;
light.ReleaseLight();
}
KWS_CoreUtils.ReleaseComputeBuffers(dirLightDataBuffer, pointLightDataBuffer, shadowPointLightDataBuffer, spotLightDataBuffer, shadowSpotLightDataBuffer);
dirLightDataBuffer = pointLightDataBuffer = shadowPointLightDataBuffer = spotLightDataBuffer = shadowSpotLightDataBuffer = null;
if(_defaultShadowmap != null) _defaultShadowmap.Release();
if(_defaultPointShadowmap != null) _defaultPointShadowmap.Release();
Shader.DisableKeyword(LightKeywords.KWS_USE_DIR_LIGHT);
Shader.DisableKeyword(LightKeywords.KWS_USE_POINT_LIGHTS);
Shader.DisableKeyword(LightKeywords.KWS_USE_SHADOW_POINT_LIGHTS);
Shader.DisableKeyword(LightKeywords.KWS_USE_SPOT_LIGHTS);
Shader.DisableKeyword(LightKeywords.KWS_USE_SHADOW_SPOT_LIGHTS);
}
}
}
#endif