You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

482 lines
24 KiB

using Unity.Collections;
using Unity.Mathematics;
using UnityEngine.Experimental.Rendering;
namespace UnityEngine.Rendering.HighDefinition
{
partial class WaterSystem
{
const float k_FoamIntensityThreshold = 0.015f;
const string k_DeformPassName = nameof(WaterDecal.PassType.Deformation);
const string k_FoamPassName = nameof(WaterDecal.PassType.Foam);
const string k_SimulationMaskPassName = nameof(WaterDecal.PassType.SimulationMask);
const string k_LargeCurrentPassName = nameof(WaterDecal.PassType.LargeCurrent);
const string k_RipplesCurrentPassName = nameof(WaterDecal.PassType.RipplesCurrent);
bool m_ActiveWaterDecals;
int m_DecalAtlasSize;
int m_MaxDecalCount;
GlobalKeyword horizontalDeformationKeyword;
// Buffers used to hold all the water foam generators
WaterDecalData[] m_WaterDecalDataCPU;
ComputeBuffer m_WaterDecalData;
// CPU culling
Vector4[] m_WaterRegions;
VisibleDecalData[] m_VisibleDecals;
int m_NumActiveWaterDecals = 0;
float m_MaxWaterDeformation = 0;
bool m_ActiveFoam;
internal bool m_ActiveMask, m_ActiveDeformation;
internal bool m_ActiveLargeCurrent, m_ActiveRipplesCurrent;
internal bool HasActiveFoam() => m_ActiveFoam || (m_MaxInjectedFoamIntensity > k_FoamIntensityThreshold);
// Deformation
ComputeShader m_WaterDeformationCS;
int m_FilterDeformationKernel;
int m_EvaluateDeformationSurfaceGradientKernel;
// Foam
ComputeShader m_WaterFoamCS;
int m_ReprojectFoamKernel;
int m_AttenuateFoamKernel;
// Decals
Material m_DecalMaterial;
int m_DeformationDecalPass;
int m_FoamDecalPass;
int m_MaskDecalPass;
int m_LargeCurrentDecalPass;
int m_RipplesCurrentDecalPass;
int m_AttenuationPass;
// Keeps track of maximum possible foam intensity in to estimate when there is no more foam
float m_MaxInjectedFoamIntensity = 0.0f;
// Atlas used to hold the various decal output
// R: Deformation, G: Surface Foam, B: Deep Foam
// RGB: Simulation Mask for each band, A: Simulation Foam Mask
// RG: Large Current Direction, B: Large Current Influence
// RG: Ripples Current Direction, B: Ripples Current Influence
// Note: deformation and foam are rendered together cause they often share intermediate computations
PowerOfTwoTextureAtlas m_DecalAtlas;
void InitializeWaterDecals()
{
m_ActiveWaterDecals = m_RenderPipeline.asset.currentPlatformRenderPipelineSettings.supportWaterDecals;
if (!m_ActiveWaterDecals)
return;
horizontalDeformationKeyword = GlobalKeyword.Create("HORIZONTAL_DEFORMATION");
m_DecalAtlasSize = (int)m_RenderPipeline.asset.currentPlatformRenderPipelineSettings.waterDecalAtlasSize;
m_MaxDecalCount = m_RenderPipeline.asset.currentPlatformRenderPipelineSettings.maximumWaterDecalCount;
m_WaterRegions = new Vector4[k_MaxNumWaterSurfaceProfiles];
m_VisibleDecals = new VisibleDecalData[m_MaxDecalCount];
m_WaterDecalDataCPU = new WaterDecalData[m_MaxDecalCount];
m_WaterDecalData = new ComputeBuffer(m_MaxDecalCount, System.Runtime.InteropServices.Marshal.SizeOf<WaterDecalData>());
m_DecalAtlas = new PowerOfTwoTextureAtlas(m_DecalAtlasSize, 0, GraphicsFormat.R16G16B16A16_SNorm, name: "Water Decal Atlas", useMipMap: false);
// Decals
m_DecalMaterial = CoreUtils.CreateEngineMaterial(m_RuntimeResources.waterDecalPS);
m_DeformationDecalPass = m_DecalMaterial.FindPass("DeformationDecal");
m_FoamDecalPass = m_DecalMaterial.FindPass("FoamDecal");
m_MaskDecalPass = m_DecalMaterial.FindPass("MaskDecal");
m_LargeCurrentDecalPass = m_DecalMaterial.FindPass("LargeCurrentDecal");
m_RipplesCurrentDecalPass = m_DecalMaterial.FindPass("RipplesCurrentDecal");
m_AttenuationPass = m_DecalMaterial.FindPass("FoamAttenuation");
// Deformation
m_WaterDeformationCS = m_RuntimeResources.waterDeformationCS;
m_FilterDeformationKernel = m_WaterDeformationCS.FindKernel("FilterDeformation");
m_EvaluateDeformationSurfaceGradientKernel = m_WaterDeformationCS.FindKernel("EvaluateDeformationSurfaceGradient");
// Foam
m_WaterFoamCS = m_RuntimeResources.waterFoamCS;
m_ReprojectFoamKernel = m_WaterFoamCS.FindKernel("ReprojectFoam");
m_AttenuateFoamKernel = m_WaterFoamCS.FindKernel("AttenuateFoam");
}
void ReleaseWaterDecals()
{
if (!m_ActiveWaterDecals)
return;
CoreUtils.SafeRelease(m_WaterDecalData);
CoreUtils.Destroy(m_DecalMaterial);
m_DecalAtlas.Release();
}
#if UNITY_EDITOR
internal void UpdateWaterDecalAtlas(Shader shader)
{
// When a water decal shadergraph is saved, update all decals that are affected
foreach (var decal in WaterDecal.instances)
{
if (decal.material != null && decal.material.shader == shader)
decal.RequestUpdate();
}
}
#endif
static internal bool IsAffectingProperty(WaterDecal decal, int nameId)
{
if (decal.material.HasProperty(nameId))
return decal.material.GetFloat(nameId) != 0.0f;
return false;
}
struct VisibleDecalData
{
public WaterDecal decal;
public int materialId;
public readonly int resX => decal.resolution.x;
public readonly int resY => decal.resolution.y;
public int MaterialId(WaterDecal.PassType passType)
{
int hash = 17;
hash = hash * 23 + (int)passType;
hash = hash * 23 + materialId;
return hash;
}
}
bool CullWaterDecals()
{
// Update decal regions based on camera position
Transform anchor = Camera.main != null ? Camera.main.transform : null;
#if UNITY_EDITOR
if (!UnityEditor.EditorApplication.isPlaying)
anchor = UnityEditor.SceneView.lastActiveSceneView?.camera.transform;
#endif
var waterSurfaces = WaterSurface.instancesAsArray;
int numWaterSurfaces = Mathf.Min(WaterSurface.instanceCount, k_MaxNumWaterSurfaceProfiles);
for (int i = 0; i < numWaterSurfaces; ++i)
{
waterSurfaces[i].UpdateDecalRegion(anchor);
waterSurfaces[i].GetDecalRegion(out var decalRegionCenter, out var decalRegionSize);
m_WaterRegions[i] = new Vector4(decalRegionCenter.x, decalRegionCenter.y, decalRegionSize.x * 0.5f, decalRegionSize.y * 0.5f);
}
// Reset counters
m_NumActiveWaterDecals = 0;
m_MaxWaterDeformation = 0.0f;
m_ActiveMask = m_ActiveDeformation = m_ActiveFoam = false;
m_ActiveLargeCurrent = m_ActiveRipplesCurrent = false;
// Reserve slot in atlas for decals affecting at least one surface
bool needRelayout = false;
foreach (var decal in WaterDecal.instances)
{
if (!decal.IsValidMaterial())
continue;
float3 scale = decal.effectiveScale;
Vector3 posWS = decal.transform.position;
float2 size = (float2)decal.regionSize * 0.5f * scale.xz;
bool visible = false;
bool affectDeformation = IsAffectingProperty(decal, HDShaderIDs._AffectDeformation);
bool affectFoam = IsAffectingProperty(decal, HDShaderIDs._AffectsFoam);
bool affectMask = IsAffectingProperty(decal, HDShaderIDs._AffectsSimulationMask);
bool affectLarge = IsAffectingProperty(decal, HDShaderIDs._AffectsLargeCurrent);
bool affectRipples = IsAffectingProperty(decal, HDShaderIDs._AffectsRipplesCurrent);
// Decals can be rotated, use a bounding circle to simplify computations
float radiusSquare = size.x * size.x + size.y * size.y;
for (int i = 0; i < numWaterSurfaces; i++)
{
float distX = Mathf.Abs(posWS.x - m_WaterRegions[i].x) - m_WaterRegions[i].z;
float distY = Mathf.Abs(posWS.z - m_WaterRegions[i].y) - m_WaterRegions[i].w;
if (distX > 0.0f && distX*distX > radiusSquare) continue;
if (distY > 0.0f && distY*distY > radiusSquare) continue;
visible |= waterSurfaces[i].deformation && affectDeformation;
visible |= waterSurfaces[i].foam && affectFoam;
if (m_EnableDecalWorkflow)
{
visible |= (waterSurfaces[i].simulationMask || waterSurfaces[i].supportSimulationFoamMask) && affectMask;
if (waterSurfaces[i].surfaceType == WaterSurfaceType.Pool)
{
visible |= waterSurfaces[i].supportRipplesCurrent && affectRipples;
}
else
{
visible |= waterSurfaces[i].supportLargeCurrent && affectLarge;
visible |= waterSurfaces[i].UsesRipplesCurrent() && affectRipples;
}
}
if (visible)
break;
}
// If the decal is visible, prepare GPU data and mark atlas slot as used
if (visible)
{
if (m_NumActiveWaterDecals >= m_MaxDecalCount)
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Debug.LogWarning("Maximum amount of visible Water Decals reached. Some of them will be ignored.");
#endif
break;
}
ref var cpuData = ref m_VisibleDecals[m_NumActiveWaterDecals];
cpuData.materialId = decal.GetMaterialAtlasingId();
cpuData.decal = decal;
ref var gpuData = ref m_WaterDecalDataCPU[m_NumActiveWaterDecals];
float angle = -decal.transform.eulerAngles.y * Mathf.Deg2Rad;
gpuData.positionXZ.Set(posWS.x, posWS.z);
gpuData.forwardXZ.Set(Mathf.Cos(angle), Mathf.Sin(angle));
gpuData.regionSize = 2.0f * size;
gpuData.amplitude = decal.amplitude * scale.y;
gpuData.surfaceFoamDimmer = decal.surfaceFoamDimmer;
gpuData.deepFoamDimmer = decal.deepFoamDimmer;
if (decal.updateMode == CustomRenderTextureUpdateMode.Realtime)
decal.updateCount++;
bool ReserveAtlasSpace(WaterDecal.PassType passType, in VisibleDecalData cpuData) => m_DecalAtlas.ReserveSpace(cpuData.MaterialId(passType), cpuData.resX, cpuData.resY);
if (affectDeformation && !ReserveAtlasSpace(WaterDecal.PassType.Deformation, in cpuData))
needRelayout = true;
if (affectFoam && !ReserveAtlasSpace(WaterDecal.PassType.Foam, in cpuData))
needRelayout = true;
if (affectMask && !ReserveAtlasSpace(WaterDecal.PassType.SimulationMask, in cpuData))
needRelayout = true;
if (affectLarge && !ReserveAtlasSpace(WaterDecal.PassType.LargeCurrent, in cpuData))
needRelayout = true;
if (affectRipples && !ReserveAtlasSpace(WaterDecal.PassType.RipplesCurrent, in cpuData))
needRelayout = true;
m_NumActiveWaterDecals++;
m_ActiveMask |= affectMask;
m_ActiveDeformation |= affectDeformation;
m_ActiveFoam |= affectFoam;
m_ActiveLargeCurrent |= affectLarge;
m_ActiveRipplesCurrent |= affectRipples;
if (affectDeformation)
m_MaxWaterDeformation = Mathf.Max(m_MaxWaterDeformation, Mathf.Abs(decal.amplitude));
}
}
// Relayout if needed
if (needRelayout && !m_DecalAtlas.RelayoutEntries())
{
Debug.LogError($"No more space in the Water Decal Atlas. To solve this issue, increase the resolution of the atlas in the current HDRP asset.");
m_NumActiveWaterDecals = 0;
m_ActiveMask = m_ActiveDeformation = m_ActiveFoam = false;
m_ActiveLargeCurrent = m_ActiveRipplesCurrent = false;
return false;
}
return true;
}
void ProcessWaterDecals(CommandBuffer cmd)
{
// Render decals in atlas if needed
void FetchCoords(in VisibleDecalData cpuData, WaterDecal.PassType passType, string passName, ref Vector4 scaleBias)
{
int id = cpuData.MaterialId(passType);
if (!m_DecalAtlas.IsCached(out scaleBias, id))
{
// Used in WaterDecal.shader to discard decal
scaleBias.x = -1;
}
else if (m_DecalAtlas.NeedsUpdate(id, cpuData.decal.updateCount, false))
{
// It would be nice to somehow cache these
int pass = cpuData.decal.material.FindPass(passName);
cmd.SetRenderTarget(m_DecalAtlas.AtlasTexture);
cmd.SetViewport(new Rect(scaleBias.z * m_DecalAtlasSize, scaleBias.w * m_DecalAtlasSize, scaleBias.x * m_DecalAtlasSize, scaleBias.y * m_DecalAtlasSize));
cmd.DrawProcedural(Matrix4x4.identity, cpuData.decal.material, pass, MeshTopology.Triangles, 3, 1, cpuData.decal.mpb);
}
}
for (int i = 0; i < m_NumActiveWaterDecals; ++i)
{
ref var cpuData = ref m_VisibleDecals[i];
ref var gpuData = ref m_WaterDecalDataCPU[i];
// Note: we could have all of these in the same pass like for regular SG decals since they render to the same atlas
FetchCoords(in cpuData, WaterDecal.PassType.Deformation, k_DeformPassName, ref gpuData.deformScaleOffset);
FetchCoords(in cpuData, WaterDecal.PassType.Foam, k_FoamPassName, ref gpuData.foamScaleOffset);
FetchCoords(in cpuData, WaterDecal.PassType.SimulationMask, k_SimulationMaskPassName, ref gpuData.maskScaleOffset);
FetchCoords(in cpuData, WaterDecal.PassType.LargeCurrent, k_LargeCurrentPassName, ref gpuData.largeCurrentScaleOffset);
FetchCoords(in cpuData, WaterDecal.PassType.RipplesCurrent, k_RipplesCurrentPassName, ref gpuData.ripplesCurrentScaleOffset);
}
m_DecalAtlas.ResetRequestedTexture();
}
void UpdateWaterDecalData(CommandBuffer cmd)
{
if (!m_ActiveWaterDecals)
return;
if (CullWaterDecals())
ProcessWaterDecals(cmd);
m_WaterDecalData.SetData(m_WaterDecalDataCPU);
cmd.SetGlobalBuffer(HDShaderIDs._WaterDecalData, m_WaterDecalData);
cmd.SetGlobalTexture(HDShaderIDs._WaterDecalAtlas, m_DecalAtlas.AtlasTexture);
}
void UpdateWaterDecals(CommandBuffer cmd, WaterSurface currentWater)
{
if (!m_ActiveWaterDecals)
return;
var perSurfaceCB = m_ShaderVariablesWaterPerSurface[currentWater.surfaceIndex];
currentWater.mpb.SetConstantBuffer(HDShaderIDs._ShaderVariablesWaterPerSurface, perSurfaceCB, 0, perSurfaceCB.stride);
currentWater.CheckDeformationResources(HDRenderPipeline.currentAsset.currentPlatformRenderPipelineSettings.supportWaterHorizontalDeformation);
currentWater.CheckFoamResources(cmd);
currentWater.CheckMaskResources();
currentWater.CheckCurrentResources();
// Needed to see decals in wireframe mode
bool wireframe = GL.wireframe;
if (wireframe)
cmd.SetWireframe(false);
if (currentWater.foam)
{
ref var cb = ref m_ShaderVariablesPerSurfaceArray[currentWater.surfaceIndex];
// Track if reprojected foam is still visible even when no generators are alive
bool activeFoam = HasActiveFoam();
if (m_ActiveFoam)
m_MaxInjectedFoamIntensity = 1.0f;
else if (activeFoam)
{
// Attenuation formula must be in sync with WaterDecal.shader
m_MaxInjectedFoamIntensity *= Mathf.Exp(-cb._DeltaTime * cb._FoamPersistenceMultiplier * 0.5f);
}
if (activeFoam)
{
using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.WaterDecalFoam)))
{
RTHandle currentFoamBuffer = currentWater.FoamBuffer();
// Check if we need to reproj
if (currentWater.previousFoamRegionScaleOffset.x != cb._DecalRegionScale.x ||
currentWater.previousFoamRegionScaleOffset.y != cb._DecalRegionScale.y ||
currentWater.previousFoamRegionScaleOffset.z != cb._DecalRegionOffset.x ||
currentWater.previousFoamRegionScaleOffset.w != cb._DecalRegionOffset.y)
{
RTHandle tmpFoamBuffer = currentWater.TmpFoamBuffer();
BindPerSurfaceConstantBuffer(cmd, m_WaterFoamCS, perSurfaceCB);
// Reproject the previous frame's foam buffer
int tileC = HDUtils.DivRoundUp((int)currentWater.foamResolution, 8);
cmd.SetComputeVectorParam(m_WaterFoamCS, HDShaderIDs._PreviousFoamRegionScaleOffset, currentWater.previousFoamRegionScaleOffset);
cmd.SetComputeTextureParam(m_WaterFoamCS, m_ReprojectFoamKernel, HDShaderIDs._WaterFoamBuffer, currentFoamBuffer);
cmd.SetComputeTextureParam(m_WaterFoamCS, m_ReprojectFoamKernel, HDShaderIDs._WaterFoamBufferRW, tmpFoamBuffer);
cmd.DispatchCompute(m_WaterFoamCS, m_ReprojectFoamKernel, tileC, tileC, 1);
// Attenuate the foam buffer
cmd.SetComputeTextureParam(m_WaterFoamCS, m_AttenuateFoamKernel, HDShaderIDs._WaterFoamBuffer, tmpFoamBuffer);
cmd.SetComputeTextureParam(m_WaterFoamCS, m_AttenuateFoamKernel, HDShaderIDs._WaterFoamBufferRW, currentFoamBuffer);
cmd.DispatchCompute(m_WaterFoamCS, m_AttenuateFoamKernel, tileC, tileC, 1);
// Update the foam data for the next frame
currentWater.previousFoamRegionScaleOffset = new float4(cb._DecalRegionScale, cb._DecalRegionOffset);
}
else
{
// Attenuate the foam buffer
CoreUtils.SetRenderTarget(cmd, currentFoamBuffer);
cmd.DrawProcedural(Matrix4x4.identity, m_DecalMaterial, m_AttenuationPass, MeshTopology.Triangles, 3, 1, currentWater.mpb);
}
cmd.DrawProcedural(Matrix4x4.identity, m_DecalMaterial, m_FoamDecalPass, MeshTopology.Quads, 4, m_NumActiveWaterDecals, currentWater.mpb);
}
}
}
if (currentWater.deformation && m_ActiveDeformation)
{
using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.WaterDecalDeformation)))
{
// Bind the constant buffers
BindPerSurfaceConstantBuffer(cmd, m_WaterDeformationCS, perSurfaceCB);
// Clear the render target to black and draw all the deformers to the texture
// We render in the SG buffer, blur pass will then output to deformation buffer
CoreUtils.SetRenderTarget(cmd, currentWater.deformationSGBuffer, clearFlag: ClearFlag.Color, Color.clear);
cmd.DrawProcedural(Matrix4x4.identity, m_DecalMaterial, m_DeformationDecalPass, MeshTopology.Quads, 4, m_NumActiveWaterDecals, currentWater.mpb);
currentWater.deformationBuffer.rt.IncrementUpdateCount(); // For the CPU Simulation
// Evaluate the normals
int numTiles = HDUtils.DivRoundUp((int)currentWater.deformationRes, 8);
cmd.SetKeyword(horizontalDeformationKeyword, HDRenderPipeline.currentAsset.currentPlatformRenderPipelineSettings.supportWaterHorizontalDeformation);
// First we need to clear the edge pixel and blur the deformation a bit
cmd.SetComputeTextureParam(m_WaterDeformationCS, m_FilterDeformationKernel, HDShaderIDs._WaterDeformationBuffer, currentWater.deformationSGBuffer);
cmd.SetComputeTextureParam(m_WaterDeformationCS, m_FilterDeformationKernel, HDShaderIDs._WaterDeformationBufferRW, currentWater.deformationBuffer);
cmd.DispatchCompute(m_WaterDeformationCS, m_FilterDeformationKernel, numTiles, numTiles, 1);
cmd.SetComputeTextureParam(m_WaterDeformationCS, m_EvaluateDeformationSurfaceGradientKernel, HDShaderIDs._WaterDeformationBuffer, currentWater.deformationBuffer);
cmd.SetComputeTextureParam(m_WaterDeformationCS, m_EvaluateDeformationSurfaceGradientKernel, HDShaderIDs._WaterDeformationSGBufferRW, currentWater.deformationSGBuffer);
cmd.DispatchCompute(m_WaterDeformationCS, m_EvaluateDeformationSurfaceGradientKernel, numTiles, numTiles, 1);
}
}
if (m_EnableDecalWorkflow)
{
if ((currentWater.simulationMask || currentWater.supportSimulationFoamMask) && m_ActiveMask)
{
using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.WaterDecalMask)))
{
CoreUtils.SetRenderTarget(cmd, currentWater.maskBuffer, clearFlag: ClearFlag.Color, Color.white);
cmd.DrawProcedural(Matrix4x4.identity, m_DecalMaterial, m_MaskDecalPass, MeshTopology.Quads, 4, m_NumActiveWaterDecals, currentWater.mpb);
currentWater.maskBuffer.rt.IncrementUpdateCount(); // For the CPU Simulation
}
}
if (m_ActiveLargeCurrent || m_ActiveRipplesCurrent)
{
using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.WaterDecalCurrent)))
{
if (currentWater.supportLargeCurrent && m_ActiveLargeCurrent)
{
CoreUtils.SetRenderTarget(cmd, currentWater.largeCurrentBuffer, clearFlag: ClearFlag.Color, Color.black);
cmd.DrawProcedural(Matrix4x4.identity, m_DecalMaterial, m_LargeCurrentDecalPass, MeshTopology.Quads, 4, m_NumActiveWaterDecals, currentWater.mpb);
currentWater.largeCurrentBuffer.rt.IncrementUpdateCount(); // For the CPU Simulation
}
if (currentWater.supportRipplesCurrent && m_ActiveRipplesCurrent)
{
CoreUtils.SetRenderTarget(cmd, currentWater.ripplesCurrentBuffer, clearFlag: ClearFlag.Color, Color.black);
cmd.DrawProcedural(Matrix4x4.identity, m_DecalMaterial, m_RipplesCurrentDecalPass, MeshTopology.Quads, 4, m_NumActiveWaterDecals, currentWater.mpb);
currentWater.ripplesCurrentBuffer.rt.IncrementUpdateCount(); // For the CPU Simulation
}
}
}
}
// Reenable wireframe if needed
if (wireframe)
cmd.SetWireframe(true);
}
}
}