using com.zibra.liquid.DataStructures; using com.zibra.liquid.Manipulators; using com.zibra.common.Utilities; using com.zibra.liquid.Bridge; #if UNITY_EDITOR using com.zibra.liquid.Analytics; using com.zibra.common.PresetAnalytics; #endif using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering; using UnityEngine.Serialization; using com.zibra.common.SDFObjects; using com.zibra.common.Solver; #if UNITY_EDITOR using com.zibra.common.Editor.SDFObjects; using com.zibra.common.Editor; #endif #if UNITY_PIPELINE_HDRP using UnityEngine.Rendering.HighDefinition; #endif // UNITY_PIPELINE_HDRP namespace com.zibra.liquid.Solver { /// /// Main ZibraLiquid component. /// /// /// /// Each ZibraLiquid component corresponds to one instance of simulation. /// Different instances of simulation can't interact with each other. /// /// /// Some parameters can't be changed after simulation has started and we created GPU buffers. /// Normally, simulation starts in playmode in OnEnable and stops in OnDisable. /// To change those parameters in runtime you want to have this component disabled, /// and after setting them, enable this component. /// /// /// Liquid may run in the edit mode, specifically when you use initial state baking. /// In that case, you can't modify some parameters in edit mode too. /// /// /// OnEnable will allocate GPU buffers, which may cause stuttering. /// Consider enabling liquid on level load, but with simulation/render paused, /// to not pay the cost of liquid initialization during gameplay. /// /// /// Disabling liquid will free GPU buffers. /// This means that liquid state will be lost. /// /// /// Various parameters of the liquid are spread throught multiple components. /// This is done so you can use Unity's Preset system to only change part of parameters. /// /// [AddComponentMenu("Zibra Effects - Liquid/Zibra Liquid")] [DisallowMultipleComponent] [RequireComponent(typeof(ZibraLiquidMaterialParameters))] [RequireComponent(typeof(ZibraLiquidSolverParameters))] [RequireComponent(typeof(ZibraLiquidAdvancedRenderParameters))] [RequireComponent(typeof(ZibraManipulatorManager))] [ExecuteInEditMode] public class ZibraLiquid : MonoBehaviour, StatReporter { #region Public Interface #region Properties /// /// A list of all enabled instances of this component. /// public static List AllFluids = new List(); /// /// Header of initial state baked in the Paid version. /// /// /// /// Use instead, /// unless you need to check which version baked this state. /// /// /// You can compare this to first int in .bytes file, /// to check whether it is baked liquid state saved specifically by the Paid version. /// /// /// Baked states are compatible across versions, /// But state baked in the Pro version also contains data about particle species, /// So state from the Pro version has different format and a little bit larger. /// /// public const int BAKED_LIQUID_PAID_HEADER_VALUE = 0x071B9AA1; /// /// Header of initial state baked in the Pro version. /// /// /// /// Use instead, /// unless you need to check which version baked this state. /// /// /// You can compare this to first int in .bytes file, /// to check whether it is baked liquid state saved specifically by the Pro version. /// /// /// Baked states are compatible across versions, /// But state baked in the Pro version also contains data about particle species, /// So state from the Pro version has different format and a little bit larger. /// /// public const int BAKED_LIQUID_PRO_HEADER_VALUE = 0x171B9AA1; /// /// Default speed of liquid simulation. /// /// /// The defualt value of speed of liquid simulation that defines /// relation between simulation time units and seconds. /// public const float DEFAULT_SIMULATION_TIME_SCALE = 40.0f; /// /// Checks whether passed int is a valid header for baked liquid state. /// /// /// To use it, read first int from the .bytes file and pass it to this function. /// public bool IsValidBakedLiquidHeader(int header) { return header == BAKED_LIQUID_PAID_HEADER_VALUE || header == BAKED_LIQUID_PRO_HEADER_VALUE; } #if UNITY_EDITOR /// /// (Editor only) Event that is triggered when state of manipulator changes /// to trigger update of custom editor. /// /// /// This is only intended to update custom editors, /// You can trigger it when you change some state to update custom editor. /// But using it for anything else is a bad idea. /// public event Action OnChanged; /// /// (Editor only) Triggers custom editor update. /// /// /// Just triggers . /// public void NotifyChange() { if (OnChanged != null) { OnChanged.Invoke(); } } #endif /// /// Render target containing rendered mesh. /// /// /// /// This is RGBA float render target. /// Format during Mesh Render pass: /// * xyz - World position /// * w - Encoded surface normal /// /// Format during Visualse SDF pass: /// * xyz - Normal /// * w - Depth /// /// /// When Visualize SDF is enabled, it will execute after Mesh Render pass, /// and so it will overwrite rendered liquid. /// /// /// Only used in Mesh Render mode or Visualize SDF pass. /// /// [NonSerialized] public RenderTexture Color0; /// /// Render target containing raymarched data. /// /// /// /// This is RGBA float render target. /// Format: /// * x - Depth of first light bounce traveling inside liquid /// * y - Depth of first light bounce traveling outside liquid (if any) /// * z - Depth of second light bounce traveling inside liquid (if any) /// * w - 0 /// /// calculation of yz components require RefractionBounces /// in ZibraLiquidAdvancedRenderParameters to be set to TwoBounces. /// /// /// Unused if DisableRaymarch in ZibraLiquidAdvancedRenderParameters is enabled. /// /// /// Only used in Mesh Render mode. /// /// [NonSerialized] public RenderTexture Color1; /// /// Render target containing raymarched data. /// /// /// /// This is RGBA float render target. /// Format: /// * xyz - Concentrations of Material1/2/3 respectively. /// * w - 0 /// /// /// Unused if DisableRaymarch in ZibraLiquidAdvancedRenderParameters is enabled. /// /// /// Only used in Mesh Render mode. /// /// /// Texture exists in non Pro versions too for technical reasons, /// but has no functionality in non Pro versions. /// /// [NonSerialized] public RenderTexture Color2; /// /// Render target containing rendered liquid when using downscale. /// /// /// /// This is RGBA float render target. /// Format: /// * xyz - Rendered liquid /// * w - 1.0 in pixels with liquid, and 0 othewise /// /// /// Only used when is enabled. /// /// /// Only used in Mesh Render mode. /// /// [NonSerialized] public RenderTexture UpscaleColor; /// /// Render target containing liquid depth when using downscale. /// /// /// /// Only used when is enabled. /// /// /// Only used in Mesh Render mode. /// /// [NonSerialized] public RenderTexture UpscaleDepth; /// /// Depth buffer containing liquid depth. /// /// /// /// This is depth buffer. /// Format: /// * r - rendered liquid mesh depth. /// /// /// Only used in Mesh Render mode. /// /// [NonSerialized] public RenderTexture Depth; /// /// Render target containing rendered foam particles. /// /// /// /// Only used in Mesh Render mode. /// /// /// This is RGBA float render target. /// Format: /// * rgba - foam color. /// /// /// Current version only supports monochrome foam /// but in future update we'll use all 4 color components /// /// [NonSerialized] public RenderTexture ParticlesRT; /// /// Buffer containing generated mesh counters. /// /// /// This is an int buffer. /// Counters[0] = Number of quads. /// Counters[1] = Number of vertices. /// [NonSerialized] public ComputeBuffer Counters; /// /// Buffer containing indices of vertices corresponding to grid nodes. /// /// /// This is an int buffer. /// VertexIDGrid[nodeID] = Index of vertex corresponding to grid node with id nodeID /// [NonSerialized] public ComputeBuffer VertexIDGrid; /// /// Buffer containing indices of vertices corresponding to grid nodes. /// /// /// This is an uint buffer. /// VertexIDGrid[3 * vertexID + 0/1/2] = X/Y/Z coordinate /// of vertex in simulation space encoded with asuint. /// [NonSerialized] public GraphicsBuffer VertexBuffer0; /// /// Temporary buffer for internal calculations. /// /// /// /// Has same structure as , /// but only contains intermediate data. /// /// /// You can safely reuse it for your needs, to save VRAM, /// but it'll get overwritten during liquid mesh generation. /// /// [NonSerialized] public GraphicsBuffer VertexBuffer1; [NonSerialized] /// /// Buffer containing information about liquid mesh quads. /// /// /// This is an uint buffer. /// Each element contains encoded data about single quad. /// Data encoded as follows: /// Leas significant 29 bits - ID of grid node corresponding to quad. /// Next 2 bits - ID of axis of quad /// Next 1 bit - direction of quad, 1 = positive direction, 0 = negative direction /// public ComputeBuffer QuadBuffer; /// /// Temporary buffer for internal calculations. /// /// /// Used as intermediate to write to buffers that cannot normally be written from GPU. /// [NonSerialized] public ComputeBuffer TransferDataBuffer; /// /// Index buffer of liquid mesh. /// /// /// Also, used as intermediate, to copy data to Unity's mesh. /// But not used exclusively in Unire Render mode. /// [NonSerialized] public GraphicsBuffer MeshRenderIndexBuffer; /// /// Buffer containing vertex data of liquid mesh. /// /// /// /// Used as intermediate, to copy data to Unity's mesh. /// /// /// This is an uint buffer. /// Format: /// * VertexProperties[6 * VertexID + 0/1/2] = X/Y/Z coordinate in local space, /// encoded with asuint /// * VertexProperties[6 * VertexID + 3/4/5] = X/Y/Z normal encoded with asuint /// /// /// Only used in Unity Render mode. /// /// [NonSerialized] public GraphicsBuffer VertexProperties; /// /// Mesh used for rendering in case Unity Render mode is used. /// [NonSerialized] public Mesh LiquidMesh; /// /// 2D texture containing all heightmaps. /// /// /// This is a float 2d texture. Each texel corresponds to a height value. /// Format: /// * x - Height /// [NonSerialized] public RenderTexture HeightmapTexture; /// /// 3D texture containing liquid normals. /// /// /// This is a float 3d texture. Each texel corresponds to grid node. /// Format: /// * xyz - Normal /// * w - Blurred liquid density /// [NonSerialized] public RenderTexture GridNormalTexture; /// /// 3D texture containing liquid normals. /// /// /// This is a float 3d texture. Each texel corresponds to grid node. /// Format: /// * xyz - Concentrations of liquid materials /// * w - Smooth liquid density /// [NonSerialized] public RenderTexture DensityTexture; /// /// 3D texture containing liquid normals. /// /// /// This is a float 3d texture. Each texel corresponds to grid node. /// Format: /// * xyz - Momentum of the liquid /// * w - Mass of the liquid /// [NonSerialized] public RenderTexture VelocityTexture; /// /// Maximum number of particles simulation may have. /// /// /// /// Directly corresponds to maximum volume of liquid simulation may have. /// /// /// Has noticeable VRAM impact. /// /// /// Having more active particles in the simulation has noticeable performance impact. /// /// /// This parameter can not be changed when liquid has GPU resources initialized. /// (See ) /// /// /// For UI limit of 10000000 particles is set, /// and that's maximum number which guaranteed to work (if you have enough VRAM). /// But if you want to, you can set it higher. /// /// [Range(1024, 10000000)] [Tooltip( "Maximum number of particles simulation may have. Directly corresponds to maximum volume of liquid simulation may have. Has noticeable VRAM impact.")] public int MaxNumParticles = 262144; /// /// Buffer containing positions and particle species information. /// /// /// This is a float4 buffer. Each float4 corresponds to particle. /// Format: /// * PositionMass[i].xyz - Position of the particle in the simulation space /// * PositionMass[i].w - Particle species /// public ComputeBuffer PositionMass { get; private set; } /// /// Buffers containing affine velocity matrices, velocities /// and foaming values for each particle. /// /// /// /// There are exactly 2 buffers, and the active one is flipped each simulation step. /// /// /// This is a float4 buffer. Each pack of 4 float4's corresponds to particle. /// Format: /// * Affine[particleID * 4 + 0].xyz - 1st row of affine velocity matrix /// * Affine[particleID * 4 + 0].w - 1st particle specific random number /// * Affine[particleID * 4 + 1].xyz - 2nd row of affine velocity matrix /// * Affine[particleID * 4 + 1].w - 2nd particle specific random number /// * Affine[particleID * 4 + 2].xyz - 3rd row of affine velocity matrix /// * Affine[particleID * 4 + 2].w - 3rd particle specific random number /// * Affine[particleID * 4 + 3].xyz - Velocity /// * Affine[particleID * 4 + 3].w - Foaming value /// /// public ComputeBuffer[] Affine { get; private set; } /// /// Buffer containing number of active particles, as well as some additional counters. /// /// /// This is an int buffer. /// Format: /// * ParticleNumber[0] - Active particle count /// * ParticleNumber[1] - Particles emitted in the last simulation step /// * Other values are not useful outside of the simulation /// public ComputeBuffer ParticleNumber { get; private set; } /// /// If enabled, makes liquid render in lower resolution. /// /// /// /// Enabling downscale can significantly improve performance on mobile, /// by having way less pixels calculate pixel shader for the liquid. /// /// /// Has no effect in Unity Render mode. /// /// [Tooltip("If enabled, makes liquid render in lower resolution")] public bool EnableDownscale = false; /// /// Factor of resolution downscale. /// /// /// /// Lower factor corresponds to better performance, but lower visual quality. /// /// /// If you set this value too high, you may get lower performance compared to downscale disabled. /// This is due to fact that we need to do additional pass to upscale liquid, /// so when resolution downscale is too high, performance win from lower shading resolution /// can potentially be less than performance loss due to cost of doing upscale pass. /// That's why value of 1.0 is not allowed and you have to disable downscale for full resolution. /// /// /// Has no effect in Unity Render mode or when is disabled. /// /// [Range(0.2f, 0.99f)] [Tooltip( "Factor of resolution downscale. Lower factor corresponds to better performance, but lower visual quality.")] public float DownscaleFactor = 0.5f; /// /// See . /// public enum InitialStateType { NoParticles, BakedLiquidState } /// /// Baked initial state. /// [Serializable] public class BakedInitialState { /// /// Active particle count in baked state /// /// /// If baked initial state will have more particles it'll trigger an /// error. /// [SerializeField] public int ParticleCount; /// /// Particle data stored in same format as in buffer . /// [SerializeField] public Vector4[] Positions; /// /// Particle data stored in same format as in buffers . /// [SerializeField] public Vector2Int[] AffineVelocity; } /// /// Type of initial state of the liquid. /// /// /// Default is - No Particles, which means that there won't be any liquid on startup. /// Alternative is - Baked Liquid State, which uses /// to restore previously recorded liquid state. /// [Tooltip("Type of initial state of the liquid")] public InitialStateType InitialState = InitialStateType.NoParticles; /// /// Asset containing baked initial state data. /// /// /// /// This state is too large to store in the scene file, /// So it's stored separately, which has sideeffect of having TextAsset type. /// Since any TextAsset can be assigned to it, /// we have check to make sure that any specific TextAsset is a baked liquid state. /// See . /// /// /// Has no effect in case is not set to BakedLiquidState. /// /// [Tooltip("Asset containing baked initial state data")] public TextAsset BakedInitialStateAsset; /// /// ID of running liquid instance. /// /// /// /// Only valid when liquid resources are initialized. /// /// /// Guaranteed to be unique among all currently initialized liquids. /// /// public int CurrentInstanceID { get; private set; } /// /// Timestep used in last simulation iteration. /// public float Timestep { get; private set; } = 0.0f; /// /// Simulation time passed (in simulation time units). /// public float SimulationInternalTime { get; private set; } = 0.0f; /// /// Number of simulation iterations done so far. /// public int SimulationInternalFrame { get; private set; } = 0; /// /// Total number of grid nodes. /// /// /// Only valid when liquid resources are initialized. /// Or after call to /// public int GridNodeCount { get; private set; } = 0; /// /// See . /// public enum RenderingMode { [Obsolete("Particle Render is no longer support. Please switch to another render mode.", true)] ParticleRender = 0, MeshRender = 1, #if ZIBRA_EFFECTS_OTP_VERSION [Obsolete("Unity Render is not supported in OTP version.", true)] #endif UnityRender = 2 } /// /// Rendering mode of the liquid. /// /// /// /// You can choose between: /// /// * Mesh Render mode - mode in which we: /// Generate mesh from the liquid. /// Render it with DrawIndirect in Native Plugin /// (optionally) Do raymarching pass to calculate light bounching inside the liquid in Native plugin. /// Shading inside Unity with customizeable shader. /// (optionally) Upscale pass to allow shading in lower resolution. /// /// * Unity Render mode - mode in which we: /// Generate mesh from the liquid. /// Copy it to Unity's Mesh Renderer. /// And Unity takes care of rendering that mesh. /// /// /// To use Unity Render mode you'll need your own shader for liquid to render with. /// /// /// In Unity Render mode you won't have raymarching results, so visual quality will be lower. /// /// /// Unity Render mode has slight performance penalty, /// as it currently can not draw variable number of indices. /// /// /// In Unity Render, material parameters set in liquid object have no effect. /// Since liquid can not control arbitrary material that may be set to render the liquid. /// /// /// Mesh Render mode doesn't support VR at the moment, /// so will have to switch to Unity Render mode in order for VR to work. /// /// /// Changing this parameter when liquid is initialized has no effect. /// During initialization we allocate different set of resources based on render mode. /// So changing render mode requires re-initialization. /// /// /// See User Guide for more details. /// /// [Tooltip("Rendering mode of the liquid. Please see documentation for more details.")] public RenderingMode CurrentRenderingMode = RenderingMode.MeshRender; /// /// Injection point where we will insert liquid rendering. /// /// /// /// Only used in case of Built-in Render Pipeline. /// /// /// Has no effect when using Unity Render mode. /// /// [Tooltip("Injection point where we will insert liquid rendering")] public CameraEvent CurrentInjectionPoint = CameraEvent.AfterSkybox; /// /// Size of the simulation grid. /// /// /// /// Only valid when liquid resources are initialized. /// Or after call to /// /// /// Largest component is equal to . /// Other components are scaled so aspect ratio of GridSize /// matches aspect ratio of . /// /// public Vector3Int GridSize { get; private set; } #if UNITY_PIPELINE_HDRP /// /// (HDRP Only) Reflection proble used for liquid reflections. /// /// /// Must be set you are using HDRP and Mesh Render mode. /// Otherwise liquid won't inialize. /// [FormerlySerializedAs("reflectionProbe")] [FormerlySerializedAs("reflectionProbeHDRP")] [Tooltip("Reflection proble used for liquid reflections")] public HDProbe ReflectionProbeHDRP; /// /// (HDRP Only) Light used for liquid shading. /// /// /// Must be set you are using HDRP and Mesh Render mode. /// Otherwise liquid won't inialize. /// [FormerlySerializedAs("customLightHDRP")] [Tooltip("Light used for liquid shading")] public Light CustomLightHDRP; #endif // UNITY_PIPELINE_HDRP /// /// (URP/Built-in RP Only) Reflection proble used for liquid reflections. /// /// /// It's strongly recommended to set it if you are using URP/Built-in RP and Mesh Render mode. /// #if !UNITY_PIPELINE_HDRP [FormerlySerializedAs("reflectionProbe")] #endif // !UNITY_PIPELINE_HDRP [FormerlySerializedAs("reflectionProbeSRP")] [Tooltip("Reflection proble used for liquid reflections")] public ReflectionProbe ReflectionProbeBRP; /// /// Maximum timestep that is allowed in single simulation iteration. /// /// /// Higher values correspond to potentially less stable simulation. /// While lower values correspond to higher chance of liquid simulation slowing down during FPS drops. /// [Range(0.0f, 1.0f)] [FormerlySerializedAs("timeStepMax")] [Tooltip("Maximum timestep that is allowed in single simulation iteration")] public float MaxAllowedTimestep = 1.00f; /// /// Maximum allowed number of frames queued to render. /// /// /// /// Only used when QualitySettings.maxQueuedFrames is not available or invalid. /// /// /// Defines number of frames we'll wait between submitting simulation workload /// and reading back simulation information back to the CPU. /// Higher values correspond to more delay for simulation info readback, /// while lower values can potentially decreasing framerate. /// /// [Range(2, 16)] [FormerlySerializedAs("maxFramesInFlight")] [Tooltip("Fallback maximum allowed number of frames queued to render")] public UInt32 MaxFramesInFlight = 3; /// /// Speed of liquid simulation. /// /// /// /// Defines relation between simulation time units and seconds. /// /// /// You can change the speed of liquid simulation with this parameter dynamically. /// /// [Range(0.0f, 100.0f)] [FormerlySerializedAs("simTimePerSec")] [Tooltip("Speed of liquid simulation")] public float SimulationTimeScale = DEFAULT_SIMULATION_TIME_SCALE; /// /// Current number of particles in the simulation. /// /// /// /// Values greater than 0 correspond to having any liquid in the simulation. /// /// /// This parameter is updated with delay, since we need to read that data from the GPU. /// /// public int CurrentParticleNumber { get; private set; } = 0; /// /// Number of simulation iterations to execute on each update. /// /// /// /// Update for purposes of this parameter is Update() /// in case is disabled, /// and FixedUpdate() otherwise. /// /// /// It's strongly recommended to set it to 1 if you target mobile devices. /// /// [Range(1, 10)] [FormerlySerializedAs("iterationsPerFrame")] [Tooltip("Number of simulation iterations to execute on each update")] public int SimulationIterationsPerFrame = 1; /// /// Size of each grid node. /// /// /// /// Only valid when liquid resources are initialized. /// Or after call to /// /// /// Grid nodes are all same size and all of them are cubes. /// This parameter is length of side of that cube. /// /// public float NodeSize { get; private set; } /// /// Resolution of the simulation grid. /// /// /// /// Has major impact on performance and quality. /// This is the first option you want to configure when tweaking performance. /// /// /// Changing resolution while liquid resources are intialized has no effect. /// /// /// This parameter defines number of nodes in largest dimension of grid node /// /// [Min(16)] [FormerlySerializedAs("gridResolution")] [Tooltip( "Resolution of the simulation grid. Has major impact on performance and quality. Please see documentation for details.")] public int GridResolution = 128; /// /// Whether to run simulation. /// /// /// /// Has no effect when liquid is not initialized. /// /// /// Disabling simulation will improve performance. /// /// /// Simulation will run for 2 frames after liquid initializations independently of this option, /// since liquid can't be rendered otherwise. /// /// [FormerlySerializedAs("runSimulation")] public bool RunSimulation = true; /// /// Whether to render liquid. /// /// /// /// Has no effect when liquid is not initialized. /// /// /// Disabling rendering will improve performance. /// /// /// Liquid may still be simulated, /// which mean that it may still push objects with force interaction, /// update data in detectors/emitters/voids, /// and сost performance due to simulation calculatons. /// /// [FormerlySerializedAs("runRendering")] [Tooltip("Whether to render liquid")] public bool RunRendering = true; /// /// When enabled, during container movement, liquid stays in place in world space. /// /// /// If you want to move liquid container without disturbing simulation you can disable this. /// [Tooltip("When enabled, during container movement, liquid stays in place in world space")] public bool EnableContainerMovementFeedback = true; /// /// Whether to render visualised SDFs. /// /// /// /// Has no effect when liquid is not initialized. /// /// /// This option is only meant for debugging purposes. /// It's strongly recommended to not enable it in final builds. /// /// [FormerlySerializedAs("visualizeSceneSDF")] [Tooltip("Whether to render visualized SDFs")] public bool VisualizeSceneSDF = false; /// /// Reference to ZibraLiquidSolverParameters /// corresponding to this object. /// public ZibraLiquidSolverParameters SolverParameters { get { if (SolverParametersInternal == null) { SolverParametersInternal = gameObject.GetComponent(); if (SolverParametersInternal == null) { SolverParametersInternal = gameObject.AddComponent(); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(this); #endif } } return SolverParametersInternal; } } /// /// Reference to /// ZibraLiquidMaterialParameters /// corresponding to this object. /// public ZibraLiquidMaterialParameters MaterialParameters { get { if (MaterialParametersInternal == null) { MaterialParametersInternal = gameObject.GetComponent(); if (MaterialParametersInternal == null) { MaterialParametersInternal = gameObject.AddComponent(); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(this); #endif } } return MaterialParametersInternal; } } /// /// Reference to /// ZibraLiquidAdvancedRenderParameters /// corresponding to this object. /// public ZibraLiquidAdvancedRenderParameters AdvancedRenderParameters { get { if (AdvancedRenderParametersInternal == null) { AdvancedRenderParametersInternal = gameObject.GetComponent(); if (AdvancedRenderParametersInternal == null) { AdvancedRenderParametersInternal = gameObject.AddComponent(); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(this); #endif } } return AdvancedRenderParametersInternal; } } /// /// Liquid container size. /// /// /// /// Liquid container is always a axis aligned box, and this Vector3 is sides of the box. /// /// /// This indirectly affects performance, /// since aspect ratio of this box affects totan number of grid nodes. /// See . /// /// /// Liquid can not leave this box. /// You can, however, move this box. /// If you do that, liquid will try to stay in place in world space, /// unless is disabled. /// /// [FormerlySerializedAs("containerSize")] public Vector3 ContainerSize = new Vector3(10, 10, 10); /// /// Whether liquid resources are initialized. /// /// /// /// Can be true in edit mode (e.g. during initial state baking). /// Can be false in play mode (e.g. disabled liquid). /// /// /// When liquid resources are initialized, /// you won't be able to change a lot of liquid parameters. /// This is due to fact, that some resources are initialized based on those parameters /// and currently, can't be resized without re-initializing simulation. /// /// public bool Initialized { get; private set; } = false; /// /// Selects whether to update simulation in Update or FixedUpdate. /// /// /// /// True corresponds on updating simulation in FixedUpdate. /// /// /// When enabled, number of simulation iterations each frame /// may be inconsistent between frames. /// So use with care, as it may introduce stutter. /// /// /// If you want to change fixed timestep interval, /// change Fixed Timestep in Time section of Project settings. /// /// [Tooltip("Selects whether to update simulation in Update or FixedUpdate")] public bool UseFixedTimestep = false; #endregion #region Methods /// /// Simulation needs to do some loading before simulation can start. /// Loading starts during initialization of engine. /// If it takes too long and you start simulation too early /// it can stall engine until loading finishes. /// You can use this method to show loading screen to wait for loading to end /// and prevent stalling. /// /// /// true - if starting simulation won't trigger stall /// false - if starting simulation will trigger stall /// public bool IsLoaded() { return LiquidBridge.ZibraLiquid_IsLoaded() != 0; } /// /// Stalls engine until all loading is finished /// See /// public void WaitLoad() { LiquidBridge.ZibraLiquid_WaitLoad(); } /// /// Updates values of some constants based on and /// . /// /// /// /// Update values of , /// and . /// /// /// Has no effect when liquid is initialized, since you can't modify /// aforementioned parameters in this case. /// /// public void UpdateSimulationConstants() { if (Initialized) { return; } NodeSize = Math.Max(ContainerSize.x, Math.Max(ContainerSize.y, ContainerSize.z)) / GridResolution; GridSize = Vector3Int.CeilToInt(ContainerSize / NodeSize); GridNodeCount = GridSize[0] * GridSize[1] * GridSize[2]; } /// /// Returns aproximate size each particle will have in case of resting liquid. /// public float GetParticleSize() { UpdateSimulationConstants(); return (float)(NodeSize / Math.Pow(SolverParameters.ParticleDensity, 1.0f / 3.0f)); } /// /// Checks if liquid has at least one emitter manipulator. /// /// /// Liquid component must have emitter or non empty initial state, /// otherwise it won't be able to generate any particles /// and will never generate any actual liquid. /// public bool HasEmitter() { foreach (var manipulator in Manipulators) { if (manipulator.GetManipulatorType() == Manipulator.ManipulatorType.Emitter) { return true; } } return false; } /// /// Returns read-only list of colliders. /// public ReadOnlyCollection GetColliderList() { return SDFColliders.AsReadOnly(); } /// /// Checks whether collider list has specified collider. /// public bool HasCollider(ZibraLiquidCollider collider) { return SDFColliders.Contains(collider); } /// /// Adds collider to the liquid. /// /// /// Can only be used if liquid is not initialized yet, /// e.g. when liquid is disabled. /// public void AddCollider(ZibraLiquidCollider collider) { if (Initialized) { Debug.LogWarning( "We don't yet support changing number of manipulators/colliders while liquid's resources are initialized."); return; } if (!SDFColliders.Contains(collider)) { SDFColliders.Add(collider); SDFColliders.Sort(new SDFColliderCompare()); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(this); #endif } } /// /// Removes collider from the liquid. /// /// /// Can only be used if liquid is not initialized yet, /// e.g. when liquid is disabled. /// public void RemoveCollider(ZibraLiquidCollider collider) { if (Initialized) { Debug.LogWarning( "We don't yet support changing number of manipulators/colliders while liquid's resources are initialized."); return; } if (SDFColliders.Contains(collider)) { SDFColliders.Remove(collider); SDFColliders.Sort(new SDFColliderCompare()); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(this); #endif } } /// /// Returns read-only list of colliders. /// public ReadOnlyCollection GetManipulatorList() { return Manipulators.AsReadOnly(); } /// /// Checks whether manipulator list has specified manipulator. /// public bool HasManipulator(Manipulator manipulator) { return Manipulators.Contains(manipulator); } /// /// Adds manipulator to the liquid. /// /// /// Can only be used if liquid is not initialized yet, /// e.g. when liquid is disabled. /// public void AddManipulator(Manipulator manipulator) { if (Initialized) { Debug.LogWarning("We don't yet support changing number of manipulators/colliders at runtime."); return; } if (!Manipulators.Contains(manipulator)) { Manipulators.Add(manipulator); Manipulators.Sort(new ManipulatorCompare()); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(this); #endif } } /// /// Removes manipulator from the liquid. /// /// /// Can only be used if liquid is not initialized yet, /// e.g. when liquid is disabled. /// public void RemoveManipulator(Manipulator manipulator) { if (Initialized) { Debug.LogWarning("We don't yet support changing number of manipulators/colliders at runtime."); return; } if (Manipulators.Contains(manipulator)) { Manipulators.Remove(manipulator); Manipulators.Sort(new ManipulatorCompare()); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(this); #endif } } /// /// Returns approximate VRAM usage corresponding to . /// /// /// Approximate VRAM usage in bytes. /// public ulong GetParticleCountFootprint() { ulong result = 0; int particleCountRounded = GetParticleCountRounded(); result += (ulong)(MaxNumParticles * 4 * sizeof(float)); // PositionMass result += (ulong)(2 * 4 * particleCountRounded * 2 * sizeof(int)); // Affine result += (ulong)(particleCountRounded * 4 * sizeof(float)); // PositionMassCopy result += (ulong)(particleCountRounded * 2 * sizeof(int)); // nodeParticlePairs result += (ulong)(particleCountRounded * sizeof(uint)); // TmpSDFBuff result += (ulong)(4 * MaxNumParticles * sizeof(int)); // NodeParticlePairs0 NodeParticlePairs1 int RadixWorkGroups1 = (int)Math.Ceiling((float)MaxNumParticles / (float)(2 * RADIX_THREADS)); int RadixWorkGroups2 = (int)Math.Ceiling((float)MaxNumParticles / (float)(RADIX_THREADS * RADIX_THREADS)); int RadixWorkGroups3 = (int)Math.Ceiling((float)RadixWorkGroups2 / (float)RADIX_THREADS); result += (ulong)(RadixWorkGroups1 * HISTO_WIDTH * sizeof(int)); // RadixGroupData1 result += (ulong)(RadixWorkGroups2 * HISTO_WIDTH * sizeof(int)); // RadixGroupData2 result += (ulong)((RadixWorkGroups3 + 1) * HISTO_WIDTH * sizeof(int)); // RadixGroupData3 return result; } /// /// Returns approximate VRAM usage corresponding to manipulators/colliders SDFs. /// /// /// Approximate VRAM usage in bytes. /// public ulong GetSDFsFootprint() { ulong result = 0; foreach (var collider in SDFColliders) { if (collider == null) { continue; } var sdf = collider.gameObject.GetComponent(); if (sdf) result += sdf.GetVRAMFootprint(); } return result; } /// /// Calculates approximate VRAM usage corresponding to . /// /// /// Approximate VRAM usage in bytes. /// public ulong GetGridFootprint() { ulong result = 0; UpdateSimulationConstants(); result += (ulong)(GridNodeCount * 4 * sizeof(int)); // GridData result += (ulong)(GridNodeCount * 4 * sizeof(float)); // GridNormal result += (ulong)(GridNodeCount * sizeof(float)); // GridBlur0 result += (ulong)(GridNodeCount * sizeof(float)); // GridBlur1 result += (ulong)(GridNodeCount * sizeof(float)); // MassCopy result += (ulong)(GridNodeCount * 2 * sizeof(int)); // IndexGrid result += (ulong)(GridNodeCount * sizeof(int)); // VertexIDGrid result += (ulong)(GridNodeCount * 4 * sizeof(float)); // VertexBuffer result += (ulong)(GridNodeCount * sizeof(uint)); // QuadBuffer result += (ulong)(GridNodeCount * (sizeof(uint) * 4)); // VertexProperties result += (ulong)(GridNodeCount * 2 * sizeof(float)); // GridNormalTexture result += (ulong)(GridNodeCount * sizeof(float) / 2); // DensityTexture result += (ulong)(GridNodeCount * sizeof(float) / 2); // VelocityTexture return result; } /// /// Initializes liquid simulation resources. /// /// /// /// This is automatically called in OnEnable() if not in edit mode. /// To run liquid simulation in edit mode, you need to call it manually. /// /// /// On success, sets to true. /// /// /// On fail, cleans up simulation resources and throws an Exception. /// /// /// Initialization allocates GPU resources, /// so calling this at runtime may cause stutter. /// Prefer to initialize liquid on scene load. /// /// /// Has no effect if liquid is already initialized. /// /// public void InitializeSimulation() { if (Initialized) { return; } try { #if UNITY_IOS if (SystemInfo.graphicsDeviceName == "Apple iOS simulator GPU") { throw new Exception("Zibra Liquid doesn't support iOS simulator. " + "Liquid was disabled."); } #endif #if !ZIBRA_EFFECTS_NO_LICENSE_CHECK && UNITY_EDITOR if (!ServerAuthManager.GetInstance().IsLicenseVerified(PluginManager.Effect.Liquid)) { string errorMessage = "License wasn't verified. " + ServerAuthManager.GetInstance().GetErrorMessage(PluginManager.Effect.Liquid) + " Liquid won't run in editor."; throw new Exception(errorMessage); } #endif // Copy Rendering Mode to internal variable to prevent user from modifying it during liquid lifetime ActiveRenderingMode = CurrentRenderingMode; #if UNITY_PIPELINE_HDRP if (RenderPipelineDetector.GetRenderPipelineType() == RenderPipelineDetector.RenderPipeline.HDRP) { bool missingRequiredParameter = false; if (CustomLightHDRP == null #if !ZIBRA_EFFECTS_OTP_VERSION && ActiveRenderingMode != RenderingMode.UnityRender #endif ) { Debug.LogError("No Custom Light set in Zibra Liquid."); missingRequiredParameter = true; } if (ReflectionProbeHDRP == null #if !ZIBRA_EFFECTS_OTP_VERSION && ActiveRenderingMode != RenderingMode.UnityRender #endif ) { Debug.LogError("No reflection probe added to Zibra Liquid."); missingRequiredParameter = true; } if (missingRequiredParameter) { throw new Exception("Liquid creation failed due to missing parameter."); } } #endif ValidateColliders(); ValidateManipulators(); if (InitialState == ZibraLiquid.InitialStateType.NoParticles || BakedInitialStateAsset == null) { bool haveEmitter = false; foreach (var manipulator in Manipulators) { if (manipulator.GetManipulatorType() == Manipulator.ManipulatorType.Emitter && manipulator.GetComponent() != null) { haveEmitter = true; break; } } if (!haveEmitter) { throw new Exception( "Liquid creation failed. Liquid have neither initial state nor emitters, or all emitters missing SDF component."); } } Camera.onPreRender += RenderCallBackWrapper; SolverCommandBuffer = new CommandBuffer { name = "ZibraLiquid.Solver" }; CurrentInstanceID = NextInstanceId++; ForceCloseCommandEncoder(SolverCommandBuffer); LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.CreateFluidInstance); Graphics.ExecuteCommandBuffer(SolverCommandBuffer); SolverCommandBuffer.Clear(); InitializeParticles(); var initializeGPUReadbackParamsBridgeParams = new InitializeGPUReadbackParams(); UInt32 manipSize = (UInt32)ManipulatorManager.Elements * STATISTICS_PER_MANIPULATOR * sizeof(Int32); initializeGPUReadbackParamsBridgeParams.readbackBufferSize = sizeof(Int32) + manipSize; switch (SystemInfo.graphicsDeviceType) { case GraphicsDeviceType.Direct3D11: case GraphicsDeviceType.XboxOne: case GraphicsDeviceType.Switch: case GraphicsDeviceType.Direct3D12: case GraphicsDeviceType.XboxOneD3D12: initializeGPUReadbackParamsBridgeParams.maxFramesInFlight = QualitySettings.maxQueuedFrames + 1; break; default: initializeGPUReadbackParamsBridgeParams.maxFramesInFlight = (int)MaxFramesInFlight; break; } IntPtr nativeCreateInstanceBridgeParams = Marshal.AllocHGlobal(Marshal.SizeOf(initializeGPUReadbackParamsBridgeParams)); Marshal.StructureToPtr(initializeGPUReadbackParamsBridgeParams, nativeCreateInstanceBridgeParams, true); SolverCommandBuffer.Clear(); LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.InitializeGpuReadback, nativeCreateInstanceBridgeParams); Graphics.ExecuteCommandBuffer(SolverCommandBuffer); ToFreeOnExit.Add(nativeCreateInstanceBridgeParams); InitializeSolver(); Initialized = true; #if UNITY_EDITOR ZibraLiquidAnalytics.TrackSimulationInitialization(this); ZibraEffectsPresetAnalytics.SendPresetAnalyticsData(new string[] { AdvancedRenderParameters.PresetName, SolverParameters.PresetName, MaterialParameters.PresetName, }); #endif } catch (Exception) { ClearRendering(); ClearSolver(); throw; } } /// /// Releases liquid simulation resources. /// /// /// /// This is automatically called in OnDisable(). /// When running liquid simulation in edit mode, /// you may want to call it manually. /// /// /// Sets to false. /// /// /// Releases GPU resources and so frees up VRAM. /// /// /// Has no effect if liquid is not initialized. /// /// public void ReleaseSimulation() { if (!Initialized) { return; } ClearRendering(); ClearSolver(); Initialized = false; // If ZibraLiquid object gets disabled/destroyed // We still may need to do cleanup few frames later // So we create new gameobject which allows us to run cleanup code ZibraLiquidGPUGarbageCollector.CreateGarbageCollector(); } /// /// Runs liquid simulation. /// /// /// /// You don't need to call it manually, unless you want to run liquid in edit mode. /// In play mode it's called automatically in Update or FixedUpdate /// depending on /// /// /// Executes number of liquid simulation iterations. /// /// public void UpdateSimulation(float deltaTime) { UpdateUnityRender(); UpdateNativeRenderParams(); if (!IsSimulationEnabled()) { return; } Timestep = Math.Min(SimulationTimeScale * deltaTime / (float)SimulationIterationsPerFrame, MaxAllowedTimestep); for (var i = 0; i < SimulationIterationsPerFrame; i++) { StepPhysics(); } SolverCommandBuffer.Clear(); // copy grid data to 3d texture for rendering after physics steps Graphics.ExecuteCommandBuffer(SolverCommandBuffer); #if UNITY_EDITOR NotifyChange(); #endif } /// /// Updates Mesh object used for Unity Render. /// /// /// /// In case is set to Unity Render mode, /// creates or enabled mesh used for it. /// Otherwise disabled that mesh (if it exists). /// /// /// If you set to Unity Render mode via script, /// you may want to call this method so make liquid create liquid mesh used for rendering, /// so you can configure that newly created object. /// /// /// Executes number of liquid simulation iterations. /// /// /// /// GameObject used for Unity Render, or null if it doesn't exist. /// In case is set to Unity Render mode, /// valid GameObject is always returned. /// Otherwise object may be returned if it was created previously. /// public GameObject UpdateUnityRender() { // This function can be called independently of whether liquid is initialized or not // So need special care when querying rendering mode RenderingMode effectiveRenderingMode = CurrentRenderingMode; if (Initialized) { effectiveRenderingMode = ActiveRenderingMode; } #if !ZIBRA_EFFECTS_OTP_VERSION if (effectiveRenderingMode == RenderingMode.UnityRender) { Transform meshTransform = transform.Find("ZibraLiquidMesh"); if (meshTransform == null) { GameObject obj = new GameObject(); obj.name = "ZibraLiquidMesh"; meshTransform = obj.transform; meshTransform.SetParent(transform, false); } GameObject meshObject = meshTransform.gameObject; // Add renderer components if not present if (meshObject.GetComponent() == null) { meshObject.AddComponent(typeof(MeshFilter)); } if (meshObject.GetComponent() == null) { meshObject.AddComponent(typeof(MeshRenderer)); MeshRenderer meshRenderer = meshObject.GetComponent(); meshRenderer.material = new Material(Shader.Find("Diffuse")); meshRenderer.enabled = true; } else { MeshRenderer meshRenderer = meshObject.GetComponent(); meshRenderer.enabled = true; } MeshFilter meshFilter = meshObject.GetComponent(); if (meshFilter.sharedMesh != LiquidMesh) { meshFilter.sharedMesh = LiquidMesh; } meshObject.SetActive(RunRendering); return meshObject; } else #endif { Transform meshTransform = transform.Find("ZibraLiquidMesh"); if (meshTransform == null) return null; GameObject meshObject = meshTransform.gameObject; meshObject.SetActive(false); return meshObject; } } public List GetStats() { float ResolutionScale = EnableDownscale ? DownscaleFactor : 1.0f; float PixelCountScale = ResolutionScale * ResolutionScale; return new List { "Liquid Simulation", $"Instance: {name}", $"Grid size: {GridSize}", $"Render resolution: {ResolutionScale * 100.0f}%", $"Render pixel count: {PixelCountScale * 100.0f}%", $"Max particle count: {MaxNumParticles}", $"Current particle count: {CurrentParticleNumber}" }; } #if UNITY_EDITOR /// /// (Editor only) Validates liquid parameters and fixes them as needed. /// public void OnValidate() { transform.rotation = Quaternion.identity; transform.localScale = Vector3.one; switch (CurrentRenderingMode) { case RenderingMode.MeshRender: #if !ZIBRA_EFFECTS_OTP_VERSION case RenderingMode.UnityRender: #endif break; default: CurrentRenderingMode = RenderingMode.MeshRender; UnityEditor.EditorUtility.SetDirty(this); break; } ContainerSize[0] = Math.Max(ContainerSize[0], 1e-3f); ContainerSize[1] = Math.Max(ContainerSize[1], 1e-3f); ContainerSize[2] = Math.Max(ContainerSize[2], 1e-3f); UpdateSimulationConstants(); if (GetComponent() == null) { gameObject.AddComponent(); UnityEditor.EditorUtility.SetDirty(this); } if (GetComponent() == null) { gameObject.AddComponent(); UnityEditor.EditorUtility.SetDirty(this); } if (GetComponent() == null) { gameObject.AddComponent(); UnityEditor.EditorUtility.SetDirty(this); } if (GetComponent() == null) { gameObject.AddComponent(); UnityEditor.EditorUtility.SetDirty(this); } ValidateColliders(); ValidateManipulators(); if (BakedInitialStateAsset) { int bakedLiquidHeader = BitConverter.ToInt32(BakedInitialStateAsset.bytes, 0); if (!IsValidBakedLiquidHeader(bakedLiquidHeader)) { BakedInitialStateAsset = null; UnityEditor.EditorUtility.SetDirty(this); } } } /// /// (Editor only) Save current simulation state /// public BakedInitialState SerializeCurrentLiquidState() { int[] ParticleNumberArray = new int[1]; ParticleNumber.GetData(ParticleNumberArray, 0, 0, 1); BakedInitialState initialStateData = new BakedInitialState(); initialStateData.ParticleCount = ParticleNumberArray[0]; int currentAffineIndex = 1 - LiquidBridge.ZibraLiquid_GetCurrentAffineBufferIndex(CurrentInstanceID); InitialState = InitialStateType.BakedLiquidState; Array.Resize(ref initialStateData.Positions, initialStateData.ParticleCount); PositionMass.GetData(initialStateData.Positions); Array.Resize(ref initialStateData.AffineVelocity, 4 * initialStateData.ParticleCount); Affine[currentAffineIndex].GetData(initialStateData.AffineVelocity); return initialStateData; } #endif #endregion #endregion #region Deprecated /// @cond SHOW_DEPRECATED #region Properties #pragma warning disable 0067 /// @deprecated /// Only used for backwards compatibility [Obsolete("onChanged is deprecated. Please use OnChanged.", true)] public event Action onChanged; #pragma warning restore 0067 /// @deprecated /// Only used for backwards compatibility [Obsolete("color0 is deprecated. Please use Color0.", true)] [NonSerialized] public RenderTexture color0; [NonSerialized] /// @deprecated /// Only used for backwards compatibility [Obsolete("color1 is deprecated. Please use Color1.", true)] public RenderTexture color1; /// @deprecated /// Only used for backwards compatibility [Obsolete("color2 is deprecated. Please use Color2.", true)] [NonSerialized] public RenderTexture color2; [NonSerialized] /// @deprecated /// Only used for backwards compatibility [Obsolete("upscaleColor is deprecated. Please use UpscaleColor.", true)] public RenderTexture upscaleColor; /// @deprecated /// Only used for backwards compatibility [Obsolete("depth is deprecated. Please use Depth.", true)] [NonSerialized] public RenderTexture depth; /// @deprecated /// Only used for backwards compatibility [Obsolete("timestep is deprecated. Please use Timestep.", true)] [NonSerialized] public float timestep; /// @deprecated /// Only used for backwards compatibility [Obsolete("simulationInternalTime is deprecated. Please use SimulationInternalTime.", true)] [NonSerialized] public float simulationInternalTime; /// @deprecated /// Only used for backwards compatibility [Obsolete("simulationInternalFrame is deprecated. Please use SimulationInternalFrame.", true)] [NonSerialized] public int simulationInternalFrame; /// @deprecated /// Only used for backwards compatibility [NonSerialized] [Obsolete( "reflectionProbe is deprecated. Use ReflectionProbeBRP or ReflectionProbeHDRP instead depending on your Rendering Pipeline (URP uses ReflectionProbeBRP).", true)] public ReflectionProbe reflectionProbe; #if UNITY_PIPELINE_HDRP /// @deprecated /// Only used for backwards compatibility [Obsolete("reflectionProbeHDRP is deprecated. Please use ReflectionProbeHDRP.", true)] [NonSerialized] public HDProbe reflectionProbeHDRP; /// @deprecated /// Only used for backwards compatibility [Obsolete("customLightHDRP is deprecated. Please use CustomLightHDRP.", true)] [NonSerialized] public Light customLightHDRP; #endif // UNITY_PIPELINE_HDRP /// @deprecated /// Only used for backwards compatibility [Obsolete("reflectionProbeSRP is deprecated. Please use ReflectionProbeBRP.", true)] [NonSerialized] public ReflectionProbe reflectionProbeSRP; /// @deprecated /// Only used for backwards compatibility [Obsolete("timeStepMax is deprecated. Please use MaxAllowedTimestep.", true)] [NonSerialized] public float timeStepMax; /// @deprecated /// Only used for backwards compatibility [Obsolete("maxFramesInFlight is deprecated. Please use MaxFramesInFlight.", true)] [NonSerialized] public UInt32 maxFramesInFlight; /// @deprecated /// Only used for backwards compatibility [Obsolete("simTimePerSec is deprecated. Please use SimulationTimeScale.", true)] [NonSerialized] public float simTimePerSec; /// @deprecated /// Only used for backwards compatibility [Obsolete("activeParticleNumber is deprecated. Please use CurrentParticleNumber.", true)] [NonSerialized] public int activeParticleNumber; /// @deprecated /// Only used for backwards compatibility [Obsolete("iterationsPerFrame is deprecated. Please use SimulationIterationsPerFrame.", true)] [NonSerialized] public int iterationsPerFrame; /// @deprecated /// Only used for backwards compatibility [Obsolete("CellSize is deprecated. Please use NodeSize.", true)] [NonSerialized] public float CellSize; /// @deprecated /// Only used for backwards compatibility [Obsolete("gridResolution is deprecated. Please use GridResolution.", true)] [NonSerialized] public int gridResolution; /// @deprecated /// Only used for backwards compatibility [Obsolete("runSimulation is deprecated. Please use RunSimulation.", true)] [NonSerialized] public bool runSimulation; /// @deprecated /// Only used for backwards compatibility [Obsolete("runRendering is deprecated. Please use RunRendering.", true)] [NonSerialized] public bool runRendering; /// @deprecated /// Only used for backwards compatibility [Obsolete("visualizeSceneSDF is deprecated. Please use VisualizeSceneSDF.", true)] [NonSerialized] public bool visualizeSceneSDF; /// @deprecated /// Only used for backwards compatibility [Obsolete("solverParameters is deprecated. Please use SolverParameters.", true)] [NonSerialized] public ZibraLiquidSolverParameters solverParameters; /// @deprecated /// Only used for backwards compatibility [Obsolete("materialParameters is deprecated. Please use MaterialParameters.", true)] [NonSerialized] public ZibraLiquidSolverParameters materialParameters; /// @deprecated /// Only used for backwards compatibility [Obsolete("renderingParameters is deprecated. Please use AdvancedRenderParameters.", true)] [NonSerialized] public ZibraLiquidAdvancedRenderParameters renderingParameters; /// @deprecated /// Only used for backwards compatibility [Obsolete("containerSize is deprecated. Please use ContainerSize.", true)] [NonSerialized] public Vector3 containerSize; /// @deprecated /// Only used for backwards compatibility [Obsolete("initialized is deprecated. Please use Initialized.", true)] [NonSerialized] public bool initialized; /// @deprecated /// Only used for backwards compatibility [Obsolete("useFixedTimestep is deprecated. Please use UseFixedTimestep.", true)] [NonSerialized] public bool useFixedTimestep = false; #endregion #region Methods /// @deprecated /// Only used for backwards compatibility [Obsolete("Init is deprecated. Please use InitializeSimulation.", true)] public void Init() { } /// @deprecated /// Only used for backwards compatibility [Obsolete("StopSolver is deprecated. Please use ReleaseSimulation.", true)] public void StopSolver() { } #endregion /// @endcond #endregion #region Implementation details #region Interop structures [StructLayout(LayoutKind.Sequential)] private class UnityTextureBridge { public IntPtr texture; public LiquidBridge.TextureFormat format; } [StructLayout(LayoutKind.Sequential)] private class RegisterParticlesBuffersBridgeParams { public IntPtr PositionMass; public IntPtr AffineVelocity0; public IntPtr AffineVelocity1; public IntPtr ParticleNumber; } [StructLayout(LayoutKind.Sequential)] private class InitializeGPUReadbackParams { public UInt32 readbackBufferSize; public Int32 maxFramesInFlight; } [StructLayout(LayoutKind.Sequential)] private struct TextureUploadData { public IntPtr data; public Int32 dataSize; public Int32 rowPitch; public Int32 dimensionX; public Int32 dimensionY; public Int32 dimensionZ; }; [StructLayout(LayoutKind.Sequential)] private class RegisterManipulatorsBridgeParams { public Int32 ManipulatorNum; public IntPtr ManipulatorBufferDynamic; public IntPtr SDFObjectBuffer; public IntPtr ManipulatorBufferStatistics; public IntPtr ManipulatorParams; public Int32 SDFObjectCount; public IntPtr SDFObjectData; public IntPtr ManipIndices; public UnityTextureBridge EmbeddingsTexture; public UnityTextureBridge SDFGridTexture; public UnityTextureBridge HeightmapTexture; public TextureUploadData EmbeddigsData; public TextureUploadData SDFGridData; } [StructLayout(LayoutKind.Sequential)] private class RegisterSolverBuffersBridgeParams { public IntPtr SimulationParams; public IntPtr PositionMassCopy; public IntPtr GridData; public IntPtr IndexGrid; public IntPtr GridBlur0; public IntPtr GridBlur1; public IntPtr MassCopy; public IntPtr TmpSDFBuff; public IntPtr GridNormal; public IntPtr NodeParticlePairs0; public IntPtr NodeParticlePairs1; public IntPtr RadixGroupData1; public IntPtr RadixGroupData2; public IntPtr RadixGroupData3; public IntPtr Counters; public IntPtr VertexIDGrid; public IntPtr VertexBuffer0; public IntPtr VertexBuffer1; public IntPtr QuadBuffer; public IntPtr TransferDataBuffer; public IntPtr MeshRenderIndexBuffer; public IntPtr ParticleSpeciesData; public Int32 ParticleSpeciesCount; public IntPtr UnityMeshVertexBuffer; public IntPtr UnityMeshIndexBuffer; public IntPtr VertexData; public UnityTextureBridge GridNormals; public UnityTextureBridge GridDensity; public UnityTextureBridge GridVelocity; public IntPtr EffectParticleData0; public IntPtr EffectParticleData1; } [StructLayout(LayoutKind.Sequential)] private class RegisterRenderResourcesBridgeParams { public UnityTextureBridge Depth; public UnityTextureBridge Color0; public UnityTextureBridge Color1; public UnityTextureBridge Color2; public UnityTextureBridge SceneDepth; public UnityTextureBridge ParticlesRT; } [StructLayout(LayoutKind.Sequential)] private class CameraParams { public Matrix4x4 View; public Matrix4x4 Projection; public Matrix4x4 ProjectionInverse; public Matrix4x4 ViewProjection; public Matrix4x4 ViewProjectionInverse; public Matrix4x4 EyeRayCameraCoeficients; public Vector3 WorldSpaceCameraPos; public Int32 CameraID; public Vector4 ZBufferParams; public Vector2 CameraResolution; public Single CameraDownscaleFactor; private Single CameraParamsPadding1; } [StructLayout(LayoutKind.Sequential)] private class MeshRenderGlobalParams { public Vector2 RenderingParameterPadding1; public Int32 DisableRaymarch; public Single LiquidIOR; public Single RayMarchIsoSurface; public Int32 UnderwaterRender; public Single RayMarchStepSize; public Single RayMarchStepFactor; public Int32 RayMarchMaxSteps; public Int32 TwoBouncesEnabled; public Vector2Int RayMarchResolution; public Single FoamingIntensity; public Single FoamingDecay; public Single FoamingThreshold; public Single FoamBrightness; public Vector4 Absorption; public Single FoamMotionBlur; public Single FoamSize; public Single FoamDiffusion; public Single FoamSpawning; public Single FoamingDecaySmoothness; public Single FoamingOcclusionDistance; public Single SimulationParamPadding2; public Single SimulationParamPadding3; }; [StructLayout(LayoutKind.Sequential)] private class RenderParams { public Single BlurRadius; public Single RenderParamsPadding1; public Single NeuralSamplingDistance; public Single SDFDebug; public Int32 RenderingMode; public Int32 VertexOptimizationIterations; public Int32 MeshOptimizationIterations; public Single DualContourIsoValue; public Single MeshOptimizationStep; public Single CameraDensity; public Int32 MaxVertexBufferSize; public Int32 MaxIndexBufferSize; public Vector3 RenderParamsContainerPos; public float RenderParams_space0; } [StructLayout(LayoutKind.Sequential)] private class SimulationParams { public Vector3 GridSize; public Int32 ParticleCount; public Vector3 ContainerScale; public Int32 NodeCount; public Vector3 SimulationParamsContainerPos; public Single TimeStep; public Int32 SimulationFrame; public Single DensityBlurRadius; public Single LiquidIsosurfaceThreshold; public Single VertexOptimizationStep; public Vector3 ParticleTranslation; public Single GlobalVelocityLimit; public Single MinimumVelocity; public Single BlurNormalizationConstant; public Int32 MaxParticleCount; public Int32 VisualizeSDF; public Single SimulationTime; public Single FoamBuoyancy; public Int32 ParticleSpeciesCount; public Single SimulationParameterPadding; public Int32 MaxEffectParticleCount; public Int32 FoamParticleLifetime; public Single padding0; public Int32 EnableContainerMovementFeedback; public Int32 EnableFoam; public Single SimulationParamPadding1; public Single SimulationParamPadding2; public Single SimulationParamPadding3; } [StructLayout(LayoutKind.Sequential)] private class ParticleSpeciesParameters { public Vector3 Gravity; public Single AffineAmmount; public Single LiquidStiffness; public Single RestDensity; public Single SurfaceTension; public Single AffineDivergenceDecay; public Vector3 Material; public Single VelocityLimit; } #endregion private RenderTexture DepthTexture; internal const int MPM_THREADS = 256; internal const int STATISTICS_PER_MANIPULATOR = 8; private const int RADIX_THREADS = 128; private const int HISTO_WIDTH = 32; private const int DEPTH_COPY_WORKGROUP = 16; private const int ADDITIONAL_VERTICES = 3000; private static int NextInstanceId = 0; private int CopyDepthID; #if ZIBRA_EFFECTS_PROFILING_ENABLED [NonSerialized] internal LiquidBridge.DebugTimestampItem[] DebugTimestampsItems = new LiquidBridge.DebugTimestampItem[100]; #endif internal struct MaterialPair { public Material CurrentMaterial; public Material SharedMaterial; // Returns true if dirty public bool SetMaterial(Material mat) { if (SharedMaterial != mat) { CurrentMaterial = (mat != null ? Material.Instantiate(mat) : null); SharedMaterial = mat; return true; } return false; } } internal class CameraResources { public RenderTexture Background; public MaterialPair LiquidMaterial; public MaterialPair UpscaleMaterial; public MaterialPair SDFRenderMaterial; public bool IsDirty = true; } private enum GraphicsBufferType { Vertex, Index } private GraphicsBuffer CreateGraphicsBuffer(GraphicsBufferType type, int count, int stride) { return new GraphicsBuffer(type == GraphicsBufferType.Vertex ? GraphicsBuffer.Target.Raw | GraphicsBuffer.Target.Vertex : GraphicsBuffer.Target.Raw | GraphicsBuffer.Target.Index, count, stride); } // We need to keep MaxFoamParticlesCount constant during runtime so we cache it on solver init and null on stop private int MaxFoamParticles = 0; private ZibraLiquidSolverParameters SolverParametersInternal; private ZibraLiquidMaterialParameters MaterialParametersInternal; private ZibraLiquidAdvancedRenderParameters AdvancedRenderParametersInternal; private ZibraManipulatorManager ManipulatorManagerInternal; internal ZibraManipulatorManager ManipulatorManager { get { if (ManipulatorManagerInternal == null) { ManipulatorManagerInternal = gameObject.GetComponent(); if (ManipulatorManagerInternal == null) { ManipulatorManagerInternal = gameObject.AddComponent(); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(this); #endif } } return ManipulatorManagerInternal; } } [NonSerialized] private Vector2Int CurrentTextureResolution = new Vector2Int(0, 0); // List of all cameras we have added a command buffer to private readonly Dictionary CameraCBs = new Dictionary(); // Each camera needs its own resources private List Cameras = new List(); internal Dictionary CamNativeParams = new Dictionary(); private Dictionary CamMeshRenderParams = new Dictionary(); private Dictionary CamRenderResolutions = new Dictionary(); private Dictionary CamNativeResolutions = new Dictionary(); internal Dictionary CameraResourcesMap = new Dictionary(); private CameraParams CameraRenderParams; private MeshRenderGlobalParams MeshRenderGlobalParamsContainer; private RenderParams RenderParamsContainer; #if ZIBRA_EFFECTS_PROFILING_ENABLED [NonSerialized] public uint DebugTimestampsItemsCount = 0; #endif private SimulationParams LiquidParameters; private ComputeBuffer GridData; private ComputeBuffer IndexGrid; private ComputeBuffer GridNormal; private Texture3D SDFGridTexture; private Texture3D EmbeddingsTexture; private ComputeBuffer PositionMassCopy; private ComputeBuffer GridBlur0; private ComputeBuffer GridBlur1; private ComputeBuffer MassCopy; private ComputeBuffer TmpSDFBuff; private ComputeBuffer NodeParticlePairs0; private ComputeBuffer NodeParticlePairs1; private ComputeBuffer EffectParticleData0; private ComputeBuffer EffectParticleData1; private ComputeBuffer RadixGroupData1; private ComputeBuffer RadixGroupData2; private ComputeBuffer RadixGroupData3; private ComputeBuffer DynamicManipulatorData; private ComputeBuffer SDFObjectData; private ComputeBuffer ManipulatorStatistics; private ComputeBuffer ParticleSpeciesData; private CommandBuffer SolverCommandBuffer; private List ToFreeOnExit = new List(); private RenderingMode ActiveRenderingMode = RenderingMode.MeshRender; private CameraEvent ActiveInjectionPoint = CameraEvent.AfterSkybox; private IntPtr NativeManipData; private IntPtr NativeSDFData; private IntPtr NativeFluidData; private IntPtr NativeSolverData; [SerializeField] [FormerlySerializedAs("sdfColliders")] private List SDFColliders = new List(); [SerializeField] [FormerlySerializedAs("manipulators")] private List Manipulators = new List(); #if UNITY_PIPELINE_HDRP private LiquidHDRPRenderComponent HDRPRenderer; #endif // UNITY_PIPELINE_HDRP private IntPtr GetNativePtr(ComputeBuffer buffer) { return buffer == null ? IntPtr.Zero : buffer.GetNativeBufferPtr(); } private IntPtr GetNativePtr(GraphicsBuffer buffer) { return buffer == null ? IntPtr.Zero : buffer.GetNativeBufferPtr(); } private IntPtr GetNativePtr(RenderTexture texture) { return texture == null ? IntPtr.Zero : texture.GetNativeTexturePtr(); } private IntPtr GetNativePtr(Texture3D texture) { return texture == null ? IntPtr.Zero : texture.GetNativeTexturePtr(); } internal bool IsRenderingEnabled() { // We need at least 2 simulation frames before we can start rendering return Initialized && RunRendering && (SimulationInternalFrame > 1) #if !ZIBRA_EFFECTS_OTP_VERSION && (ActiveRenderingMode != RenderingMode.UnityRender || VisualizeSceneSDF) #endif ; } private bool IsSimulationEnabled() { // We need at least 2 simulation frames before we can start rendering // So we need to always simulate first 2 frames return Initialized && (RunSimulation || (SimulationInternalFrame <= 2)); } private void SetupScriptableRenderComponent() { #if UNITY_PIPELINE_HDRP #if UNITY_EDITOR if (RenderPipelineDetector.GetRenderPipelineType() == RenderPipelineDetector.RenderPipeline.HDRP) { HDRPRenderer = gameObject.GetComponent(); if (HDRPRenderer != null && HDRPRenderer.customPasses.Count == 0) { DestroyImmediate(HDRPRenderer); HDRPRenderer = null; } if (HDRPRenderer == null) { HDRPRenderer = gameObject.AddComponent(); HDRPRenderer.injectionPoint = CustomPassInjectionPoint.BeforePreRefraction; HDRPRenderer.AddPassOfType(typeof(LiquidHDRPRenderComponent.FluidHDRPRender)); LiquidHDRPRenderComponent.FluidHDRPRender renderer = HDRPRenderer.customPasses[0] as LiquidHDRPRenderComponent.FluidHDRPRender; renderer.name = "ZibraLiquidRenderer"; renderer.liquid = this; } } #endif #endif // UNITY_PIPELINE_HDRP } private void ForceCloseCommandEncoder(CommandBuffer cmdList) { #if UNITY_EDITOR_OSX || (!UNITY_EDITOR && UNITY_STANDALONE_OSX) || (!UNITY_EDITOR && UNITY_IOS) // Unity bug workaround // For whatever reason, Unity sometimes doesn't close command encoder when we request it from native plugin // So when we try to start our command encoder with active encoder already present it leads to crash // This happens when scene have Terrain (I still have no idea why) // So we force change command encoder like that, and this one closes gracefully if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Metal) { cmdList.DispatchCompute(MaterialParameters.NoOpCompute, 0, 1, 1, 1); } #endif } private UnityTextureBridge MakeTextureNativeBridge(RenderTexture texture) { var unityTextureBridge = new UnityTextureBridge(); if (texture != null) { unityTextureBridge.texture = GetNativePtr(texture); unityTextureBridge.format = LiquidBridge.ToBridgeTextureFormat(texture.graphicsFormat); } else { unityTextureBridge.texture = IntPtr.Zero; unityTextureBridge.format = LiquidBridge.TextureFormat.None; } return unityTextureBridge; } private UnityTextureBridge MakeTextureNativeBridge(Texture3D texture) { var unityTextureBridge = new UnityTextureBridge(); unityTextureBridge.texture = GetNativePtr(texture); unityTextureBridge.format = LiquidBridge.ToBridgeTextureFormat(texture.graphicsFormat); return unityTextureBridge; } void OnDrawGizmosInternal(bool isSelected) { if (!enabled) { return; } Gizmos.color = Color.yellow; if (!isSelected) { Gizmos.color = new Color(Gizmos.color.r, Gizmos.color.g, Gizmos.color.b, Gizmos.color.a * 0.5f); } Gizmos.DrawWireCube(transform.position, ContainerSize); Gizmos.color = new Color(0.2f, 0.8f, 0.8f); if (!isSelected) { Gizmos.color = new Color(Gizmos.color.r, Gizmos.color.g, Gizmos.color.b, Gizmos.color.a * 0.5f); } Vector3 voxelSize = new Vector3(ContainerSize.x / GridSize.x, ContainerSize.y / GridSize.y, ContainerSize.z / GridSize.z); const int GizmosVoxelCubeSize = 2; for (int i = -GizmosVoxelCubeSize; i <= GizmosVoxelCubeSize; i++) for (int j = -GizmosVoxelCubeSize; j <= GizmosVoxelCubeSize; j++) for (int k = -GizmosVoxelCubeSize; k <= GizmosVoxelCubeSize; k++) Gizmos.DrawWireCube(transform.position + new Vector3(i * voxelSize.x, j * voxelSize.y, k * voxelSize.z), voxelSize); } private void OnDrawGizmosSelected() { OnDrawGizmosInternal(true); } private void OnDrawGizmos() { OnDrawGizmosInternal(false); } private void Awake() { SetupScriptableRenderComponent(); } private void OnEnable() { #if UNITY_EDITOR foreach (var manipulator in Manipulators) { if (manipulator is ZibraLiquidEmitter) { ZibraLiquidEmitter emitter = manipulator as ZibraLiquidEmitter; if (emitter.InitialVelocity.magnitude > SolverParameters.MaximumVelocity) { Debug.LogWarning("Too high velocity magnitude " + emitter.InitialVelocity.magnitude + " on emitter '" + emitter.name + "'. Liquid instance '" + this.name + "' MaximumVelocity is " + SolverParameters.MaximumVelocity); } } } #endif SetupScriptableRenderComponent(); AllFluids?.Add(this); UpdateUnityRender(); #if UNITY_EDITOR if (!UnityEditor.EditorApplication.isPlaying) { return; } #endif AddToStatReporter(); InitializeSimulation(); } private void InitializeParticles() { UpdateSimulationConstants(); LiquidParameters = new SimulationParams(); NativeFluidData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SimulationParams))); NativeSolverData = Marshal.AllocHGlobal((SolverParameters.AdditionalParticleSpecies.Count + ZibraLiquidSolverParameters.MAX_RUNTIME_ADDED_SPECIES) * Marshal.SizeOf(typeof(ParticleSpeciesParameters))); var numParticlesRounded = (int)Math.Ceiling((double)MaxNumParticles / MPM_THREADS) * MPM_THREADS; // round to workgroup size PositionMass = new ComputeBuffer(MaxNumParticles, 4 * sizeof(float)); Affine = new ComputeBuffer[2]; Affine[0] = new ComputeBuffer(4 * numParticlesRounded, 2 * sizeof(int)); Affine[1] = new ComputeBuffer(4 * numParticlesRounded, 2 * sizeof(int)); ParticleNumber = new ComputeBuffer(128, sizeof(int)); int[] particleNumberInitialData = new int[128]; ParticleNumber.SetData(particleNumberInitialData); #if ZIBRA_EFFECTS_DEBUG PositionMass.name = "PositionMass"; Affine[0].name = "Affine0"; Affine[1].name = "Affine1"; ParticleNumber.name = "ParticleNumber"; #endif // We mush apply state before we send buffers to native plugin // SetData seems to recreate buffers, at least on Metal ApplyInitialState(); int[] Pnums = new int[128]; for (int i = 0; i < 128; i++) { Pnums[i] = 0; } ParticleNumber.SetData(Pnums); ManipulatorManager.UpdateConst(Manipulators, SDFColliders); ManipulatorManager.HeightmapCountSqrt = (int)Mathf.Ceil(Mathf.Sqrt(ManipulatorManager.HeightmapCount)); ManipulatorManager.HeightmapSize = Vector2Int.one * Mathf.Max(1, ManipulatorManager.HeightmapCountSqrt * SolverParameters.HeightmapResolution); CreateTexture(ref HeightmapTexture, ManipulatorManager.HeightmapSize, FilterMode.Point, 0, RenderTextureFormat.RHalf, false); ManipulatorManager.UpdateDynamic(SolverCommandBuffer, this); if (ManipulatorManager.TextureCount > 0) { EmbeddingsTexture = new Texture3D( ManipulatorManager.EmbeddingTextureDimension, ManipulatorManager.EmbeddingTextureDimension, ManipulatorManager.EmbeddingTextureDimension, TextureFormat.RGBA32, false); SDFGridTexture = new Texture3D(ManipulatorManager.SDFTextureDimension, ManipulatorManager.SDFTextureDimension, ManipulatorManager.SDFTextureDimension, TextureFormat.RHalf, false); EmbeddingsTexture.filterMode = FilterMode.Trilinear; SDFGridTexture.filterMode = FilterMode.Trilinear; } else { EmbeddingsTexture = new Texture3D(1, 1, 1, TextureFormat.RGBA32, 0); SDFGridTexture = new Texture3D(1, 1, 1, TextureFormat.RHalf, 0); EmbeddingsTexture.filterMode = FilterMode.Trilinear; SDFGridTexture.filterMode = FilterMode.Trilinear; } int ManipSize = Marshal.SizeOf(typeof(ZibraManipulatorManager.ManipulatorParam)); int SDFSize = Marshal.SizeOf(typeof(ZibraManipulatorManager.SDFObjectParams)); // Need to create at least some buffer to bind to shaders NativeManipData = Marshal.AllocHGlobal(ManipulatorManager.Elements * ManipSize); NativeSDFData = Marshal.AllocHGlobal(ManipulatorManager.SDFObjectList.Count * SDFSize); DynamicManipulatorData = new ComputeBuffer(Math.Max(ManipulatorManager.Elements, 1), ManipSize); SDFObjectData = new ComputeBuffer(Math.Max(ManipulatorManager.SDFObjectList.Count, 1), Marshal.SizeOf(typeof(ZibraManipulatorManager.SDFObjectParams))); int ManipulatorStatisticsSize = Math.Max(STATISTICS_PER_MANIPULATOR * ManipulatorManager.Elements, 1); // flag ComputeBufferType.IndirectArguments is needed to make R32_UINT buffer on d3d11 ManipulatorStatistics = new ComputeBuffer(ManipulatorStatisticsSize, sizeof(int), ComputeBufferType.IndirectArguments); int[] manipulatorStatisticsSizeInitialData = new int[ManipulatorStatisticsSize]; ManipulatorStatistics.SetData(manipulatorStatisticsSizeInitialData); #if ZIBRA_EFFECTS_DEBUG DynamicManipulatorData.name = "DynamicManipulatorData"; SDFObjectData.name = "SDFObjectData"; ManipulatorStatistics.name = "ManipulatorStatistics"; #endif var gcparamBuffer2 = GCHandle.Alloc(ManipulatorManager.Indices, GCHandleType.Pinned); UpdateInteropBuffers(); var registerManipulatorsBridgeParams = new RegisterManipulatorsBridgeParams(); registerManipulatorsBridgeParams.ManipulatorNum = ManipulatorManager.Elements; registerManipulatorsBridgeParams.ManipulatorBufferDynamic = GetNativePtr(DynamicManipulatorData); registerManipulatorsBridgeParams.SDFObjectBuffer = GetNativePtr(SDFObjectData); registerManipulatorsBridgeParams.ManipulatorBufferStatistics = ManipulatorStatistics.GetNativeBufferPtr(); registerManipulatorsBridgeParams.ManipulatorParams = NativeManipData; registerManipulatorsBridgeParams.SDFObjectCount = ManipulatorManager.SDFObjectList.Count; registerManipulatorsBridgeParams.SDFObjectData = NativeSDFData; registerManipulatorsBridgeParams.ManipIndices = gcparamBuffer2.AddrOfPinnedObject(); registerManipulatorsBridgeParams.EmbeddingsTexture = MakeTextureNativeBridge(EmbeddingsTexture); registerManipulatorsBridgeParams.SDFGridTexture = MakeTextureNativeBridge(SDFGridTexture); registerManipulatorsBridgeParams.HeightmapTexture = MakeTextureNativeBridge(HeightmapTexture); GCHandle embeddingDataHandle = default(GCHandle); if (ManipulatorManager.Embeddings.Length > 0) { embeddingDataHandle = GCHandle.Alloc(ManipulatorManager.Embeddings, GCHandleType.Pinned); registerManipulatorsBridgeParams.EmbeddigsData.dataSize = Marshal.SizeOf(new Color32()) * ManipulatorManager.Embeddings.Length; registerManipulatorsBridgeParams.EmbeddigsData.data = embeddingDataHandle.AddrOfPinnedObject(); registerManipulatorsBridgeParams.EmbeddigsData.rowPitch = Marshal.SizeOf(new Color32()) * EmbeddingsTexture.width; registerManipulatorsBridgeParams.EmbeddigsData.dimensionX = EmbeddingsTexture.width; registerManipulatorsBridgeParams.EmbeddigsData.dimensionY = EmbeddingsTexture.height; registerManipulatorsBridgeParams.EmbeddigsData.dimensionZ = EmbeddingsTexture.depth; } GCHandle sdfGridHandle = default(GCHandle); if (ManipulatorManager.SDFGrid.Length > 0) { sdfGridHandle = GCHandle.Alloc(ManipulatorManager.SDFGrid, GCHandleType.Pinned); registerManipulatorsBridgeParams.SDFGridData.dataSize = Marshal.SizeOf(new byte()) * ManipulatorManager.SDFGrid.Length; registerManipulatorsBridgeParams.SDFGridData.data = sdfGridHandle.AddrOfPinnedObject(); registerManipulatorsBridgeParams.SDFGridData.rowPitch = Marshal.SizeOf(new byte()) * 2 * SDFGridTexture.width; registerManipulatorsBridgeParams.SDFGridData.dimensionX = SDFGridTexture.width; registerManipulatorsBridgeParams.SDFGridData.dimensionY = SDFGridTexture.height; registerManipulatorsBridgeParams.SDFGridData.dimensionZ = SDFGridTexture.depth; } IntPtr nativeRegisterManipulatorsBridgeParams = Marshal.AllocHGlobal(Marshal.SizeOf(registerManipulatorsBridgeParams)); Marshal.StructureToPtr(registerManipulatorsBridgeParams, nativeRegisterManipulatorsBridgeParams, true); SolverCommandBuffer.Clear(); LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.RegisterManipulators, nativeRegisterManipulatorsBridgeParams); Graphics.ExecuteCommandBuffer(SolverCommandBuffer); gcparamBuffer2.Free(); CameraRenderParams = new CameraParams(); RenderParamsContainer = new RenderParams(); MeshRenderGlobalParamsContainer = new MeshRenderGlobalParams(); var registerParticlesBuffersParams = new RegisterParticlesBuffersBridgeParams(); registerParticlesBuffersParams.PositionMass = GetNativePtr(PositionMass); registerParticlesBuffersParams.AffineVelocity0 = GetNativePtr(Affine[0]); registerParticlesBuffersParams.AffineVelocity1 = GetNativePtr(Affine[1]); registerParticlesBuffersParams.ParticleNumber = GetNativePtr(ParticleNumber); IntPtr nativeRegisterParticlesBuffersParams = Marshal.AllocHGlobal(Marshal.SizeOf(registerParticlesBuffersParams)); Marshal.StructureToPtr(registerParticlesBuffersParams, nativeRegisterParticlesBuffersParams, true); SolverCommandBuffer.Clear(); LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.RegisterParticlesBuffers, nativeRegisterParticlesBuffersParams); Graphics.ExecuteCommandBuffer(SolverCommandBuffer); ToFreeOnExit.Add(nativeRegisterParticlesBuffersParams); } private int GetParticleCountRounded() { return (int)Math.Ceiling((double)MaxNumParticles / MPM_THREADS) * MPM_THREADS; // round to workgroup size; } private void InitVolumeTexture(ref RenderTexture volume, GraphicsFormat format) { if (volume) return; volume = new RenderTexture(GridSize.x, GridSize.y, 0, format); volume.volumeDepth = GridSize.z; volume.dimension = UnityEngine.Rendering.TextureDimension.Tex3D; volume.enableRandomWrite = true; volume.filterMode = FilterMode.Trilinear; volume.Create(); if (!volume.IsCreated()) { volume = null; throw new NotSupportedException("Failed to create 3D texture."); } } private void InitializeSolver() { SimulationInternalTime = 0.0f; SimulationInternalFrame = 0; MaxFoamParticles = MaterialParameters.MaxFoamParticles; GridNodeCount = GridSize[0] * GridSize[1] * GridSize[2]; GridData = new ComputeBuffer(GridNodeCount * 4, sizeof(uint)); GridNormal = new ComputeBuffer(GridNodeCount, 4 * sizeof(float)); GridBlur0 = new ComputeBuffer(GridNodeCount, sizeof(float)); GridBlur1 = new ComputeBuffer(GridNodeCount, sizeof(float)); MassCopy = new ComputeBuffer(GridNodeCount, sizeof(float)); ParticleSpeciesData = new ComputeBuffer(SolverParameters.AdditionalParticleSpecies.Count + ZibraLiquidSolverParameters.MAX_RUNTIME_ADDED_SPECIES, Marshal.SizeOf(typeof(ParticleSpeciesParameters))); Counters = new ComputeBuffer(8, sizeof(uint)); VertexIDGrid = new ComputeBuffer(GridNodeCount, sizeof(int)); VertexBuffer0 = CreateGraphicsBuffer(GraphicsBufferType.Vertex, 6 * GridNodeCount, sizeof(uint)); VertexBuffer1 = CreateGraphicsBuffer(GraphicsBufferType.Vertex, 4 * GridNodeCount, sizeof(uint)); TransferDataBuffer = new ComputeBuffer(1, sizeof(uint)); MeshRenderIndexBuffer = CreateGraphicsBuffer(GraphicsBufferType.Index, 3 * GridNodeCount, sizeof(uint)); #if !ZIBRA_EFFECTS_OTP_VERSION if (ActiveRenderingMode == RenderingMode.UnityRender) { LiquidMesh = new Mesh(); var layout = new[] { new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3), new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3), }; int maxVertexCount = GridNodeCount; int maxTriangleCount = (int)(maxVertexCount * AdvancedRenderParameters.MaxLiquidMeshSize / 3.0f + ADDITIONAL_VERTICES); int indexBufferSize = maxTriangleCount * 3; int vertexBufferSize = maxTriangleCount * 2; LiquidMesh.SetVertexBufferParams(indexBufferSize, layout); LiquidMesh.SetIndexBufferParams(vertexBufferSize, IndexFormat.UInt32); LiquidMesh.MarkDynamic(); LiquidMesh.SetVertices(new Vector3[vertexBufferSize], 0, vertexBufferSize); LiquidMesh.SetIndices(new int[indexBufferSize], MeshTopology.Triangles, 0); LiquidMesh.bounds = new Bounds(Vector3.zero, ContainerSize); LiquidMesh.vertexBufferTarget |= GraphicsBuffer.Target.CopyDestination; LiquidMesh.indexBufferTarget |= GraphicsBuffer.Target.CopyDestination; } #endif QuadBuffer = new ComputeBuffer(GridNodeCount, sizeof(int)); VertexProperties = CreateGraphicsBuffer(GraphicsBufferType.Vertex, GridNodeCount, 6 * sizeof(uint)); IndexGrid = new ComputeBuffer(GridNodeCount, 2 * sizeof(int)); InitVolumeTexture(ref GridNormalTexture, SystemInfo.IsFormatSupported(GraphicsFormat.R16G16B16A16_SFloat, FormatUsage.LoadStore) ? GraphicsFormat.R16G16B16A16_SFloat : GraphicsFormat.R32G32B32A32_SFloat); GridNormalTexture.name = "GridNormalTexture"; InitVolumeTexture(ref DensityTexture, SystemInfo.IsFormatSupported(GraphicsFormat.R16G16B16A16_SFloat, FormatUsage.LoadStore) ? GraphicsFormat.R16G16B16A16_SFloat : GraphicsFormat.R32G32B32A32_SFloat); DensityTexture.name = "DensityTexture"; InitVolumeTexture(ref VelocityTexture, SystemInfo.IsFormatSupported(GraphicsFormat.R16G16B16A16_SFloat, FormatUsage.LoadStore) ? GraphicsFormat.R16G16B16A16_SFloat : GraphicsFormat.R32G32B32A32_SFloat); VelocityTexture.name = "VelocityTexture"; int NumParticlesRounded = GetParticleCountRounded(); PositionMassCopy = new ComputeBuffer(NumParticlesRounded, 4 * sizeof(float)); TmpSDFBuff = new ComputeBuffer(NumParticlesRounded, sizeof(uint)); NodeParticlePairs0 = new ComputeBuffer(2 * NumParticlesRounded, sizeof(int)); NodeParticlePairs1 = new ComputeBuffer(2 * NumParticlesRounded, sizeof(int)); EffectParticleData0 = new ComputeBuffer(4 * Math.Max(MaxFoamParticles, 1), sizeof(uint)); EffectParticleData1 = new ComputeBuffer(4 * Math.Max(MaxFoamParticles, 1), sizeof(uint)); int RadixWorkGroups1 = (int)Math.Ceiling((float)MaxNumParticles / (float)(2 * RADIX_THREADS)); int RadixWorkGroups2 = (int)Math.Ceiling((float)MaxNumParticles / (float)(RADIX_THREADS * RADIX_THREADS)); int RadixWorkGroups3 = (int)Math.Ceiling((float)RadixWorkGroups2 / (float)RADIX_THREADS); RadixGroupData1 = new ComputeBuffer(RadixWorkGroups1 * HISTO_WIDTH, sizeof(uint)); RadixGroupData2 = new ComputeBuffer(RadixWorkGroups2 * HISTO_WIDTH, sizeof(uint)); RadixGroupData3 = new ComputeBuffer((RadixWorkGroups3 + 1) * HISTO_WIDTH, sizeof(uint)); #if ZIBRA_EFFECTS_DEBUG GridData.name = "GridData"; GridNormal.name = "GridNormal"; GridBlur0.name = "GridBlur0"; GridBlur1.name = "GridBlur1"; MassCopy.name = "MassCopy"; TmpSDFBuff.name = "TmpSDFBuff"; IndexGrid.name = "IndexGrid"; PositionMassCopy.name = "PositionMassCopy"; NodeParticlePairs0.name = "NodeParticlePairs0"; NodeParticlePairs1.name = "NodeParticlePairs1"; RadixGroupData1.name = "RadixGroupData1"; RadixGroupData2.name = "RadixGroupData2"; RadixGroupData3.name = "RadixGroupData3"; ParticleSpeciesData.name = "ParticleSpeciesData"; #endif SetFluidParameters(); var gcparamBuffer = GCHandle.Alloc(LiquidParameters, GCHandleType.Pinned); var registerSolverBuffersBridgeParams = new RegisterSolverBuffersBridgeParams(); registerSolverBuffersBridgeParams.SimulationParams = gcparamBuffer.AddrOfPinnedObject(); registerSolverBuffersBridgeParams.ParticleSpeciesCount = SolverParameters.AdditionalParticleSpecies.Count + 1; registerSolverBuffersBridgeParams.PositionMassCopy = GetNativePtr(PositionMassCopy); registerSolverBuffersBridgeParams.GridData = GetNativePtr(GridData); registerSolverBuffersBridgeParams.IndexGrid = GetNativePtr(IndexGrid); registerSolverBuffersBridgeParams.GridBlur0 = GetNativePtr(GridBlur0); registerSolverBuffersBridgeParams.GridBlur1 = GetNativePtr(GridBlur1); registerSolverBuffersBridgeParams.MassCopy = GetNativePtr(MassCopy); registerSolverBuffersBridgeParams.TmpSDFBuff = GetNativePtr(TmpSDFBuff); registerSolverBuffersBridgeParams.GridNormal = GetNativePtr(GridNormal); registerSolverBuffersBridgeParams.NodeParticlePairs0 = GetNativePtr(NodeParticlePairs0); registerSolverBuffersBridgeParams.NodeParticlePairs1 = GetNativePtr(NodeParticlePairs1); registerSolverBuffersBridgeParams.EffectParticleData0 = GetNativePtr(EffectParticleData0); registerSolverBuffersBridgeParams.EffectParticleData1 = GetNativePtr(EffectParticleData1); registerSolverBuffersBridgeParams.RadixGroupData1 = GetNativePtr(RadixGroupData1); registerSolverBuffersBridgeParams.RadixGroupData2 = GetNativePtr(RadixGroupData2); registerSolverBuffersBridgeParams.RadixGroupData3 = GetNativePtr(RadixGroupData3); registerSolverBuffersBridgeParams.Counters = GetNativePtr(Counters); registerSolverBuffersBridgeParams.VertexIDGrid = GetNativePtr(VertexIDGrid); registerSolverBuffersBridgeParams.VertexBuffer0 = GetNativePtr(VertexBuffer0); registerSolverBuffersBridgeParams.VertexBuffer1 = GetNativePtr(VertexBuffer1); registerSolverBuffersBridgeParams.QuadBuffer = GetNativePtr(QuadBuffer); registerSolverBuffersBridgeParams.GridDensity = MakeTextureNativeBridge(DensityTexture); registerSolverBuffersBridgeParams.GridVelocity = MakeTextureNativeBridge(VelocityTexture); registerSolverBuffersBridgeParams.GridNormals = MakeTextureNativeBridge(GridNormalTexture); #if !ZIBRA_EFFECTS_OTP_VERSION if (ActiveRenderingMode == RenderingMode.UnityRender) { registerSolverBuffersBridgeParams.UnityMeshVertexBuffer = LiquidMesh.GetNativeVertexBufferPtr(0); registerSolverBuffersBridgeParams.UnityMeshIndexBuffer = LiquidMesh.GetNativeIndexBufferPtr(); } else #endif { registerSolverBuffersBridgeParams.UnityMeshVertexBuffer = IntPtr.Zero; registerSolverBuffersBridgeParams.UnityMeshIndexBuffer = IntPtr.Zero; } registerSolverBuffersBridgeParams.TransferDataBuffer = GetNativePtr(TransferDataBuffer); registerSolverBuffersBridgeParams.MeshRenderIndexBuffer = GetNativePtr(MeshRenderIndexBuffer); registerSolverBuffersBridgeParams.VertexData = GetNativePtr(VertexProperties); registerSolverBuffersBridgeParams.ParticleSpeciesData = GetNativePtr(ParticleSpeciesData); IntPtr nativeRegisterSolverBuffersBridgeParams = Marshal.AllocHGlobal(Marshal.SizeOf(registerSolverBuffersBridgeParams)); Marshal.StructureToPtr(registerSolverBuffersBridgeParams, nativeRegisterSolverBuffersBridgeParams, true); SolverCommandBuffer.Clear(); LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.RegisterSolverBuffers, nativeRegisterSolverBuffersBridgeParams); Graphics.ExecuteCommandBuffer(SolverCommandBuffer); gcparamBuffer.Free(); SolverCommandBuffer.Clear(); CopyDepthID = MaterialParameters.RendererCompute.FindKernel("CS_CopyDepth"); ToFreeOnExit.Add(nativeRegisterSolverBuffersBridgeParams); } private void Update() { if (!Initialized) { return; } ZibraLiquidGPUGarbageCollector.GCUpdateWrapper(); #if UNITY_EDITOR if (!UnityEditor.EditorApplication.isPlaying) { return; } #endif if (!UseFixedTimestep) UpdateSimulation(Time.smoothDeltaTime); UpdateReadback(); #if ZIBRA_EFFECTS_PROFILING_ENABLED if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Vulkan) { UpdateDebugTimestamps(); } #endif } private void FixedUpdate() { #if UNITY_EDITOR if (!UnityEditor.EditorApplication.isPlaying) { return; } #endif if (UseFixedTimestep) UpdateSimulation(Time.fixedDeltaTime); } #if ZIBRA_EFFECTS_PROFILING_ENABLED public void UpdateDebugTimestamps() { if (!IsSimulationEnabled()) { return; } DebugTimestampsItemsCount = LiquidBridge.ZibraLiquid_GetDebugTimestamps(CurrentInstanceID, DebugTimestampsItems); } #endif private void UpdateReadback() { if (!IsSimulationEnabled()) { return; } SolverCommandBuffer.Clear(); // This must be called at most ONCE PER FRAME // Otherwise you'll get deadlock LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.UpdateReadback); Graphics.ExecuteCommandBuffer(SolverCommandBuffer); /// ParticleNumber GPUReadback UInt32 size = sizeof(UInt32); IntPtr readbackData = LiquidBridge.ZibraLiquid_GPUReadbackGetData(CurrentInstanceID, size); if (readbackData != IntPtr.Zero) { CurrentParticleNumber = Marshal.ReadInt32(readbackData); } UpdateManipulatorStatistics(); } /// /// Update the material parameters /// private bool SetMaterialParams(Camera cam) { bool isDirty = false; CameraResources camRes = CameraResourcesMap[cam]; Material usedUpscaleMaterial = EnableDownscale ? MaterialParameters.UpscaleMaterial : null; isDirty = camRes.UpscaleMaterial.SetMaterial(usedUpscaleMaterial) || isDirty; bool usingMainMaterial = ActiveRenderingMode == RenderingMode.MeshRender; Material CurrentSharedMaterial = usingMainMaterial ? MaterialParameters.FluidMeshMaterial : null; isDirty = camRes.LiquidMaterial.SetMaterial(CurrentSharedMaterial) || isDirty; Material CurrentMaterial = camRes.LiquidMaterial.CurrentMaterial; if (usingMainMaterial) { if (RenderPipelineDetector.GetRenderPipelineType() == RenderPipelineDetector.RenderPipeline.HDRP) { #if UNITY_PIPELINE_HDRP if (CustomLightHDRP == null) Debug.LogError("No Custom Light set in Zibra Liquid."); else CurrentMaterial.SetVector("WorldSpaceLightPos", CustomLightHDRP.transform.position); if (ReflectionProbeHDRP == null) Debug.LogError("No reflection probe added to Zibra Liquid."); #endif // UNITY_PIPELINE_HDRP } else { CurrentMaterial.SetKeyword(new LocalKeyword(CurrentMaterial.shader, "USE_CUBEMAP_REFRACTION"), MaterialParameters.UseCubemapRefraction); if (ReflectionProbeBRP != null) // custom reflection probe { CurrentMaterial.EnableKeyword("CUSTOM_REFLECTION_PROBE"); CurrentMaterial.SetTexture("ReflectionProbe", ReflectionProbeBRP.texture); CurrentMaterial.SetVector("ReflectionProbe_HDR", ReflectionProbeBRP.textureHDRDecodeValues); CurrentMaterial.SetVector("ReflectionProbe_BoxMax", ReflectionProbeBRP.bounds.max); CurrentMaterial.SetVector("ReflectionProbe_BoxMin", ReflectionProbeBRP.bounds.min); CurrentMaterial.SetVector("ReflectionProbe_ProbePosition", ReflectionProbeBRP.transform.position); } else { CurrentMaterial.DisableKeyword("CUSTOM_REFLECTION_PROBE"); } } CurrentMaterial.SetFloat("AbsorptionAmount", MaterialParameters.AbsorptionAmount); CurrentMaterial.SetFloat("ScatteringAmount", MaterialParameters.ScatteringAmount); CurrentMaterial.SetFloat("Metalness", MaterialParameters.Metalness); CurrentMaterial.SetFloat("FresnelStrength", MaterialParameters.FresnelStrength); CurrentMaterial.SetFloat("RefractionDistortion", MaterialParameters.IndexOfRefraction - 1.0f); CurrentMaterial.SetFloat("LiquidIOR", MaterialParameters.IndexOfRefraction); CurrentMaterial.SetFloat("Roughness", MaterialParameters.Roughness); CurrentMaterial.SetVector("RefractionColor", MaterialParameters.Color); CurrentMaterial.SetVector("ReflectionColor", MaterialParameters.ReflectionColor); CurrentMaterial.SetVector("EmissiveColor", MaterialParameters.EmissiveColor); CurrentMaterial.SetVector("Material1Color", MaterialParameters.Material1.Color); CurrentMaterial.SetVector("Material2Color", MaterialParameters.Material2.Color); CurrentMaterial.SetVector("Material3Color", MaterialParameters.Material3.Color); CurrentMaterial.SetVector("Material1Emission", MaterialParameters.Material1.EmissiveColor); CurrentMaterial.SetVector("Material2Emission", MaterialParameters.Material2.EmissiveColor); CurrentMaterial.SetVector("Material3Emission", MaterialParameters.Material3.EmissiveColor); CurrentMaterial.SetVector("MatMetalness", new Vector3(MaterialParameters.Material1.Metalness, MaterialParameters.Material2.Metalness, MaterialParameters.Material3.Metalness)); CurrentMaterial.SetVector("MatAbsorption", new Vector3(MaterialParameters.Material1.AbsorptionAmount, MaterialParameters.Material2.AbsorptionAmount, MaterialParameters.Material3.AbsorptionAmount)); CurrentMaterial.SetVector("MatScattering", new Vector3(MaterialParameters.Material1.ScatteringAmount, MaterialParameters.Material2.ScatteringAmount, MaterialParameters.Material3.ScatteringAmount)); CurrentMaterial.SetVector("MatRoughness", new Vector3(MaterialParameters.Material1.Roughness, MaterialParameters.Material2.Roughness, MaterialParameters.Material3.Roughness)); #if UNITY_PIPELINE_HDRP CurrentMaterial.SetVector("LightColor", CustomLightHDRP.color * Mathf.Log(CustomLightHDRP.intensity) / 8.0f); CurrentMaterial.SetVector("LightDirection", CustomLightHDRP.transform.rotation * new Vector3(0, 0, -1)); #endif CurrentMaterial.SetVector("ContainerScale", ContainerSize); CurrentMaterial.SetVector("ContainerPosition", transform.position); CurrentMaterial.SetVector("GridSize", (Vector3)GridSize); CurrentMaterial.SetFloat("RayMarchResolutionDownscale", AdvancedRenderParameters.RayMarchingResolutionDownscale); CurrentMaterial.SetFloat("RefractionMinimumDepth", 1e-4f); CurrentMaterial.SetFloat("RefractionDepthBias", 1.25f); CurrentMaterial.SetTexture("GridNormals", GridNormalTexture); CurrentMaterial.SetTexture("MeshRenderData", Color0); CurrentMaterial.SetTexture("MeshDepth", Depth, RenderTextureSubElement.Depth); CurrentMaterial.SetTexture("GridDensity", DensityTexture); if (AdvancedRenderParameters.RefractionBounces == ZibraLiquidAdvancedRenderParameters.RayMarchingBounces.TwoBounces) { if (MeshRenderGlobalParamsContainer.TwoBouncesEnabled == 0) isDirty = true; MeshRenderGlobalParamsContainer.TwoBouncesEnabled = 1; } else { if (MeshRenderGlobalParamsContainer.TwoBouncesEnabled == 1) isDirty = true; MeshRenderGlobalParamsContainer.TwoBouncesEnabled = 0; } #if UNITY_IOS && !UNITY_EDITOR if (!EnableDownscale) { CurrentMaterial.DisableKeyword("FLIP_BACKGROUND_TEXTURE"); CurrentMaterial.EnableKeyword("FLIP_NATIVE_TEXTURES"); CurrentMaterial.EnableKeyword("FLIP_PARTICLES_TEXTURE"); } else { CurrentMaterial.DisableKeyword("FLIP_NATIVE_TEXTURES"); CurrentMaterial.EnableKeyword("FLIP_BACKGROUND_TEXTURE"); CurrentMaterial.DisableKeyword("FLIP_PARTICLES_TEXTURE"); } #endif #if UNITY_ANDROID && !UNITY_EDITOR if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Vulkan && !EnableDownscale) { if (!EnableDownscale) { CurrentMaterial.DisableKeyword("FLIP_BACKGROUND_TEXTURE"); CurrentMaterial.EnableKeyword("FLIP_NATIVE_TEXTURES"); } else { CurrentMaterial.DisableKeyword("FLIP_NATIVE_TEXTURES"); CurrentMaterial.EnableKeyword("FLIP_BACKGROUND_TEXTURE"); } } #endif if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES3) { CurrentMaterial.EnableKeyword("FLIP_PARTICLES_TEXTURE"); } CurrentMaterial.SetTexture("Background", GetBackgroundToBind(cam)); CurrentMaterial.SetTexture("FluidColor", Color0); if (RenderPipelineDetector.GetRenderPipelineType() == RenderPipelineDetector.RenderPipeline.HDRP) { #if UNITY_PIPELINE_HDRP CurrentMaterial.SetTexture("ReflectionProbe", ReflectionProbeHDRP.texture); CurrentMaterial.SetVector("ReflectionProbe_HDR", new Vector4(0.01f, 1.0f)); CurrentMaterial.SetVector("ReflectionProbe_BoxMax", ReflectionProbeHDRP.bounds.max); CurrentMaterial.SetVector("ReflectionProbe_BoxMin", ReflectionProbeHDRP.bounds.min); CurrentMaterial.SetVector("ReflectionProbe_ProbePosition", ReflectionProbeHDRP.transform.position); CurrentMaterial.EnableKeyword("HDRP"); #endif } } Material usedSDFRenderMaterial = VisualizeSceneSDF ? MaterialParameters.SDFRenderMaterial : null; isDirty = camRes.SDFRenderMaterial.SetMaterial(usedSDFRenderMaterial) || isDirty; if (VisualizeSceneSDF) { Material CurrentSDFRenderMaterial = camRes.SDFRenderMaterial.CurrentMaterial; CurrentSDFRenderMaterial.SetTexture("SDFRender", Color0); if (RenderPipelineDetector.GetRenderPipelineType() == RenderPipelineDetector.RenderPipeline.HDRP) { #if UNITY_PIPELINE_HDRP CurrentSDFRenderMaterial.SetVector("LightColor", CustomLightHDRP.color * Mathf.Log(CustomLightHDRP.intensity) / 8.0f); CurrentSDFRenderMaterial.SetVector("LightDirection", CustomLightHDRP.transform.rotation * new Vector3(0, 0, -1)); CurrentSDFRenderMaterial.EnableKeyword("HDRP"); #endif } } return isDirty; } internal Vector2Int ApplyDownscaleFactor(Vector2Int val) { if (!EnableDownscale) return val; return new Vector2Int((int)(val.x * DownscaleFactor), (int)(val.y * DownscaleFactor)); } private Vector2Int ApplyRenderPipelineRenderScale(Vector2Int val, float renderPipelineRenderScale) { return new Vector2Int((int)(val.x * renderPipelineRenderScale), (int)(val.y * renderPipelineRenderScale)); } private bool CreateTexture(ref RenderTexture texture, Vector2Int resolution, FilterMode filterMode, int depth, RenderTextureFormat format, bool enableRandomWrite = false) { if (texture == null || texture.width != resolution.x || texture.height != resolution.y) { ZibraLiquidGPUGarbageCollector.SafeRelease(texture); texture = null; texture = new RenderTexture(resolution.x, resolution.y, depth, format); texture.enableRandomWrite = enableRandomWrite; texture.filterMode = filterMode; texture.Create(); return true; } return false; } private bool CreateRenderBuffer(ref ComputeBuffer buffer, Vector2Int resolution) { if (buffer == null || buffer.count != resolution.x * resolution.y) { ZibraLiquidGPUGarbageCollector.SafeRelease(buffer); buffer = new ComputeBuffer(resolution.x * resolution.y * 3, sizeof(uint)); return true; } return false; } // Returns resolution that is enough for all cameras private Vector2Int GetRequiredTextureResolution() { if (CamRenderResolutions.Count == 0) Debug.Log("camRenderResolutions dictionary was empty when GetRequiredTextureResolution was called."); Vector2Int result = new Vector2Int(0, 0); foreach (var item in CamRenderResolutions) { result = Vector2Int.Max(result, item.Value); } return result; } internal bool IsBackgroundCopyNeeded(Camera cam) { return !EnableDownscale || (cam.activeTexture == null); } private RenderTexture GetBackgroundToBind(Camera cam) { if (!IsBackgroundCopyNeeded(cam)) return cam.activeTexture; return CameraResourcesMap[cam].Background; } /// /// Removes disabled/inactive cameras from cameraResources /// private void UpdateCameraList() { List toRemove = new List(); foreach (var camResource in CameraResourcesMap) { if (camResource.Key == null || (!camResource.Key.isActiveAndEnabled && camResource.Key.cameraType != CameraType.SceneView)) { toRemove.Add(camResource.Key); continue; } } foreach (var cam in toRemove) { if (CameraResourcesMap[cam].Background) { CameraResourcesMap[cam].Background.Release(); CameraResourcesMap[cam].Background = null; } CameraResourcesMap.Remove(cam); } } private void UpdateCameraResolution(Camera cam, float renderPipelineRenderScale) { Vector2Int cameraResolution = new Vector2Int(cam.pixelWidth, cam.pixelHeight); cameraResolution = ApplyRenderPipelineRenderScale(cameraResolution, renderPipelineRenderScale); CamNativeResolutions[cam] = cameraResolution; Vector2Int cameraResolutionDownscaled = ApplyDownscaleFactor(cameraResolution); CamRenderResolutions[cam] = cameraResolutionDownscaled; } /// /// Update Native textures for a given camera /// private bool UpdateNativeTextures(Camera cam, float renderPipelineRenderScale) { UpdateCameraList(); Vector2Int cameraResolution = new Vector2Int(cam.pixelWidth, cam.pixelHeight); cameraResolution = ApplyRenderPipelineRenderScale(cameraResolution, renderPipelineRenderScale); Vector2Int textureResolution = GetRequiredTextureResolution(); int pixelCount = textureResolution.x * textureResolution.y; if (!Cameras.Contains(cam)) { Cameras.Add(cam); } int CameraID = Cameras.IndexOf(cam); bool isGlobalTexturesDirty = false; bool isCameraDirty = CameraResourcesMap[cam].IsDirty; FilterMode defaultFilter = EnableDownscale ? FilterMode.Bilinear : FilterMode.Point; if (IsBackgroundCopyNeeded(cam)) { if (RenderPipelineDetector.GetRenderPipelineType() == RenderPipelineDetector.RenderPipeline.HDRP) { #if UNITY_PIPELINE_HDRP isCameraDirty = CreateTexture(ref CameraResourcesMap[cam].Background, cameraResolution, FilterMode.Point, 0, RenderTextureFormat.ARGBHalf) || isCameraDirty; #endif } else { var format = SystemInfo.IsFormatSupported(GraphicsFormat.B10G11R11_UFloatPack32, FormatUsage.LoadStore) ? RenderTextureFormat.RGB111110Float : RenderTextureFormat.ARGB32; // 8 bits per component isCameraDirty = CreateTexture(ref CameraResourcesMap[cam].Background, cameraResolution, FilterMode.Point, 0, format) || isCameraDirty; } } else { if (CameraResourcesMap[cam].Background != null) { isCameraDirty = true; CameraResourcesMap[cam].Background.Release(); CameraResourcesMap[cam].Background = null; } } isGlobalTexturesDirty = CreateTexture(ref DepthTexture, cameraResolution, defaultFilter, 32, RenderTextureFormat.RFloat, true) || isGlobalTexturesDirty; isGlobalTexturesDirty = CreateTexture(ref Depth, textureResolution, defaultFilter, 32, RenderTextureFormat.Depth) || isGlobalTexturesDirty; isGlobalTexturesDirty = CreateTexture(ref Color0, textureResolution, FilterMode.Point, 0, RenderTextureFormat.ARGBFloat, true) || isGlobalTexturesDirty; isGlobalTexturesDirty = CreateTexture(ref Color1, textureResolution, FilterMode.Point, 0, RenderTextureFormat.ARGBFloat, true) || isGlobalTexturesDirty; isGlobalTexturesDirty = CreateTexture(ref Color2, textureResolution, FilterMode.Point, 0, RenderTextureFormat.ARGBFloat, true) || isGlobalTexturesDirty; isGlobalTexturesDirty = CreateTexture(ref UpscaleColor, textureResolution, FilterMode.Point, 0, RenderTextureFormat.ARGBHalf, false) || isGlobalTexturesDirty; isGlobalTexturesDirty = CreateTexture(ref UpscaleDepth, textureResolution, FilterMode.Point, 32, RenderTextureFormat.Depth, false) || isGlobalTexturesDirty; isGlobalTexturesDirty = CreateTexture(ref ParticlesRT, textureResolution, FilterMode.Point, 0, RenderTextureFormat.ARGBFloat, true) || isGlobalTexturesDirty; if (isGlobalTexturesDirty || isCameraDirty) { if (isGlobalTexturesDirty) { foreach (var camera in CameraResourcesMap) { camera.Value.IsDirty = true; } CurrentTextureResolution = textureResolution; } CameraResourcesMap[cam].IsDirty = false; var registerRenderResourcesBridgeParams = new RegisterRenderResourcesBridgeParams(); registerRenderResourcesBridgeParams.Depth = MakeTextureNativeBridge(Depth); registerRenderResourcesBridgeParams.Color0 = MakeTextureNativeBridge(Color0); registerRenderResourcesBridgeParams.Color1 = MakeTextureNativeBridge(Color1); registerRenderResourcesBridgeParams.Color2 = MakeTextureNativeBridge(Color2); registerRenderResourcesBridgeParams.SceneDepth = MakeTextureNativeBridge(DepthTexture); registerRenderResourcesBridgeParams.ParticlesRT = MakeTextureNativeBridge(ParticlesRT); IntPtr nativeRegisterRenderResourcesBridgeParams = Marshal.AllocHGlobal(Marshal.SizeOf(registerRenderResourcesBridgeParams)); Marshal.StructureToPtr(registerRenderResourcesBridgeParams, nativeRegisterRenderResourcesBridgeParams, true); SolverCommandBuffer.Clear(); LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.RegisterRenderResources, nativeRegisterRenderResourcesBridgeParams); LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.InitializeGraphicsPipeline); Graphics.ExecuteCommandBuffer(SolverCommandBuffer); ToFreeOnExit.Add(nativeRegisterRenderResourcesBridgeParams); } return isGlobalTexturesDirty || isCameraDirty; } private int IntDivCeil(int a, int b) { return (a + b - 1) / b; } /// /// Render the liquid from the native plugin /// /// Command Buffer to add the rendering commands to internal void RenderLiquidNative(CommandBuffer cmdBuffer, Camera cam, Rect? viewport = null) { var renderEffectParticles = MaterialParametersInternal.EnableFoam && MaxFoamParticles > 0; ForceCloseCommandEncoder(cmdBuffer); if (renderEffectParticles) { cmdBuffer.SetComputeVectorParam(MaterialParameters.RendererCompute, "Resolution", new Vector2(cam.pixelWidth, cam.pixelHeight)); cmdBuffer.SetComputeTextureParam(MaterialParameters.RendererCompute, CopyDepthID, "_DepthOUT", DepthTexture); cmdBuffer.DispatchCompute(MaterialParameters.RendererCompute, CopyDepthID, IntDivCeil(cam.pixelWidth, DEPTH_COPY_WORKGROUP), IntDivCeil(cam.pixelHeight, DEPTH_COPY_WORKGROUP), 1); } LiquidBridge.SubmitInstanceEvent(cmdBuffer, CurrentInstanceID, LiquidBridge.EventID.SetCameraParams, CamNativeParams[cam]); LiquidBridge.SubmitInstanceEvent(cmdBuffer, CurrentInstanceID, LiquidBridge.EventID.UpdateMeshRenderGlobalParameters, CamMeshRenderParams[cam]); if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Vulkan) { cmdBuffer.SetRenderTarget(Color0, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, Depth, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); cmdBuffer.ClearRenderTarget(true, true, Color.clear); } LiquidBridge.SubmitInstanceEvent(cmdBuffer, CurrentInstanceID, LiquidBridge.EventID.DrawLiquid); if (renderEffectParticles) { if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Vulkan) { cmdBuffer.SetRenderTarget(ParticlesRT); cmdBuffer.ClearRenderTarget(false, true, Color.clear); } LiquidBridge.SubmitInstanceEvent(cmdBuffer, CurrentInstanceID, LiquidBridge.EventID.DrawEffectParticles); } } internal void RenderLiquidMain(CommandBuffer cmdBuffer, Camera cam, Rect? viewport = null) { switch (ActiveRenderingMode) { case RenderingMode.MeshRender: RenderLiquidMesh(cmdBuffer, cam, viewport); break; #if !ZIBRA_EFFECTS_OTP_VERSION case RenderingMode.UnityRender: break; #endif default: Debug.LogError("Unknown Rendering mode"); break; } } /// /// Upscale the liquid surface to currently bound render target /// Used for URP where we can't change render targets /// Used for URP where we can't change render targets /// internal void UpscaleLiquidDirect(CommandBuffer cmdBuffer, Camera cam, RenderTargetIdentifier? sourceColorTexture = null, RenderTargetIdentifier? sourceDepthTexture = null, Rect? viewport = null) { Material CurrentUpscaleMaterial = CameraResourcesMap[cam].UpscaleMaterial.CurrentMaterial; Vector2Int cameraNativeResolution = CamNativeResolutions[cam]; cmdBuffer.SetViewport(new Rect(0, 0, cameraNativeResolution.x, cameraNativeResolution.y)); if (sourceColorTexture == null) { cmdBuffer.SetGlobalTexture("ShadedLiquid", UpscaleColor); cmdBuffer.SetGlobalTexture("ShadedLiquidDepth", UpscaleDepth); } else { cmdBuffer.SetGlobalTexture("ShadedLiquid", sourceColorTexture.Value); cmdBuffer.SetGlobalTexture("ShadedLiquidDepth", sourceDepthTexture.Value); } cmdBuffer.DrawProcedural(transform.localToWorldMatrix, CurrentUpscaleMaterial, 0, MeshTopology.Triangles, 6); } /// /// Render the liquid surface /// Camera's targetTexture must be copied to cameraResources[cam].background /// using corresponding Render Pipeline before calling this method /// /// Command Buffer to add the rendering commands to internal void RenderFluid(CommandBuffer cmdBuffer, Camera cam, RenderTargetIdentifier? renderTargetParam = null, RenderTargetIdentifier? depthTargetParam = null, Rect? viewport = null) { RenderTargetIdentifier renderTarget = renderTargetParam ?? new RenderTargetIdentifier(BuiltinRenderTextureType.CameraTarget); RenderTargetIdentifier depthTarget = depthTargetParam ?? new RenderTargetIdentifier(BuiltinRenderTextureType.CameraTarget); // Render fluid to temporary RenderTexture if downscale enabled // Otherwise render straight to final RenderTexture if (EnableDownscale) { cmdBuffer.SetRenderTarget(UpscaleColor, UpscaleDepth); cmdBuffer.ClearRenderTarget(true, true, Color.clear); } else { cmdBuffer.SetRenderTarget(renderTarget, depthTarget); } RenderLiquidMain(cmdBuffer, cam, viewport); if (VisualizeSceneSDF) { LiquidBridge.SubmitInstanceEvent(cmdBuffer, CurrentInstanceID, LiquidBridge.EventID.RenderSDF); if (EnableDownscale) { cmdBuffer.SetRenderTarget(UpscaleColor, UpscaleDepth); } else { cmdBuffer.SetRenderTarget(renderTarget, depthTarget); } RenderSDFVisualization(cmdBuffer, cam, viewport); } // If downscale enabled then we need to blend it on top of final RenderTexture if (EnableDownscale) { cmdBuffer.SetRenderTarget(renderTarget, depthTarget); UpscaleLiquidDirect(cmdBuffer, cam, null, null, viewport); } } /// /// Render the liquid surface /// Camera's targetTexture must be copied to cameraResources[cam].background /// using corresponding Render Pipeline before calling this method /// /// Command Buffer to add the rendering commands to private void RenderLiquidMesh(CommandBuffer cmdBuffer, Camera cam, Rect? viewport = null) { Vector2Int cameraRenderResolution = CamRenderResolutions[cam]; Material CurrentMaterial = CameraResourcesMap[cam].LiquidMaterial.CurrentMaterial; // Render fluid to temporary RenderTexture if downscale enabled // Otherwise render straight to final RenderTexture if (EnableDownscale) { cmdBuffer.SetViewport(new Rect(0, 0, cameraRenderResolution.x, cameraRenderResolution.y)); } else { if (viewport != null) { cmdBuffer.SetViewport(viewport.Value); } } cmdBuffer.SetGlobalTexture("Background", GetBackgroundToBind(cam)); if (RenderPipelineDetector.GetRenderPipelineType() == RenderPipelineDetector.RenderPipeline.HDRP) { #if UNITY_PIPELINE_HDRP cmdBuffer.SetGlobalTexture("ReflectionProbe", ReflectionProbeHDRP.texture); cmdBuffer.SetGlobalVector("ReflectionProbe_HDR", new Vector4(0.01f, 1.0f)); cmdBuffer.SetGlobalVector("ReflectionProbe_BoxMax", ReflectionProbeHDRP.bounds.max); cmdBuffer.SetGlobalVector("ReflectionProbe_BoxMin", ReflectionProbeHDRP.bounds.min); cmdBuffer.SetGlobalVector("ReflectionProbe_ProbePosition", ReflectionProbeHDRP.transform.position); CurrentMaterial.EnableKeyword("HDRP"); #endif } cmdBuffer.DrawProcedural(transform.localToWorldMatrix, CurrentMaterial, 0, MeshTopology.Triangles, 6); } internal void RenderSDFVisualization(CommandBuffer cmdBuffer, Camera cam, Rect? viewport = null) { Vector2Int cameraRenderResolution = CamRenderResolutions[cam]; Material CurrentMaterial = CameraResourcesMap[cam].SDFRenderMaterial.CurrentMaterial; // Render fluid to temporary RenderTexture if downscale enabled // Otherwise render straight to final RenderTexture if (EnableDownscale) { cmdBuffer.SetViewport(new Rect(0, 0, cameraRenderResolution.x, cameraRenderResolution.y)); } else { if (viewport != null) { cmdBuffer.SetViewport(viewport.Value); } } cmdBuffer.DrawProcedural(transform.localToWorldMatrix, CurrentMaterial, 0, MeshTopology.Triangles, 6); } /// /// Update the camera parameters for the particle renderer /// private void UpdateCamera(Camera cam) { Vector2Int resolution = CamRenderResolutions[cam]; Material CurrentMaterial = CameraResourcesMap[cam].LiquidMaterial.CurrentMaterial; Material CurrentUpscaleMaterial = CameraResourcesMap[cam].UpscaleMaterial.CurrentMaterial; Material CurrentSDFRenderMaterial = CameraResourcesMap[cam].SDFRenderMaterial.CurrentMaterial; Matrix4x4 Projection = GL.GetGPUProjectionMatrix(cam.projectionMatrix, true); Matrix4x4 ProjectionInverse = Projection.inverse; Matrix4x4 View = cam.worldToCameraMatrix; Matrix4x4 ViewProjection = Projection * View; Matrix4x4 ViewProjectionInverse = ViewProjection.inverse; CurrentMaterial.SetVector("Resolution", CameraRenderParams.CameraResolution); CameraRenderParams.View = cam.worldToCameraMatrix; CameraRenderParams.Projection = Projection; CameraRenderParams.ProjectionInverse = ProjectionInverse; CameraRenderParams.ViewProjection = ViewProjection; CameraRenderParams.ViewProjectionInverse = ViewProjectionInverse; CameraRenderParams.EyeRayCameraCoeficients = CalculateEyeRayCameraCoeficients(cam); CameraRenderParams.WorldSpaceCameraPos = cam.transform.position; CameraRenderParams.CameraResolution = new Vector2(resolution.x, resolution.y); CameraRenderParams.CameraID = Cameras.IndexOf(cam); CameraRenderParams.CameraDownscaleFactor = EnableDownscale ? DownscaleFactor : 1f; { // Same as Unity's built-in _ZBufferParams float y = cam.farClipPlane / cam.nearClipPlane; float x = 1 - y; CameraRenderParams.ZBufferParams = new Vector4(x, y, x / cam.farClipPlane, y / cam.farClipPlane); } MeshRenderGlobalParamsContainer.LiquidIOR = MaterialParameters.IndexOfRefraction; MeshRenderGlobalParamsContainer.RayMarchIsoSurface = AdvancedRenderParameters.RayMarchIsoSurface; MeshRenderGlobalParamsContainer.DisableRaymarch = AdvancedRenderParameters.DisableRaymarch ? 1 : 0; MeshRenderGlobalParamsContainer.UnderwaterRender = AdvancedRenderParameters.UnderwaterRender ? 1 : 0; MeshRenderGlobalParamsContainer.RayMarchMaxSteps = AdvancedRenderParameters.RayMarchMaxSteps; MeshRenderGlobalParamsContainer.RayMarchStepSize = AdvancedRenderParameters.RayMarchStepSize; MeshRenderGlobalParamsContainer.RayMarchStepFactor = AdvancedRenderParameters.RayMarchStepFactor; Vector2 renderingResolution = resolution; Vector2 rayMarchResolution = renderingResolution * AdvancedRenderParameters.RayMarchingResolutionDownscale; MeshRenderGlobalParamsContainer.RayMarchResolution = new Vector2Int((int)rayMarchResolution.x, (int)rayMarchResolution.y); MeshRenderGlobalParamsContainer.FoamingIntensity = MaterialParameters.FoamIntensity; MeshRenderGlobalParamsContainer.FoamingDecay = MaterialParameters.FoamDecay; MeshRenderGlobalParamsContainer.FoamingDecaySmoothness = MaterialParameters.FoamDecaySmoothness; MeshRenderGlobalParamsContainer.FoamingOcclusionDistance = MaterialParameters.FoamingOcclusionDistance; MeshRenderGlobalParamsContainer.FoamingThreshold = MaterialParameters.FoamingThreshold; MeshRenderGlobalParamsContainer.FoamBrightness = MaterialParameters.FoamBrightness; MeshRenderGlobalParamsContainer.FoamMotionBlur = MaterialParameters.FoamMotionBlur; MeshRenderGlobalParamsContainer.FoamSize = MaterialParameters.FoamSize; MeshRenderGlobalParamsContainer.FoamDiffusion = MaterialParameters.FoamDiffusion; MeshRenderGlobalParamsContainer.FoamSpawning = MaterialParameters.FoamSpawning; MeshRenderGlobalParamsContainer.Absorption = new Vector4( MaterialParameters.Material1.ScatteringAmount, MaterialParameters.Material2.ScatteringAmount, MaterialParameters.Material3.ScatteringAmount, MaterialParameters.ScatteringAmount); Marshal.StructureToPtr(MeshRenderGlobalParamsContainer, CamMeshRenderParams[cam], true); Vector2 textureScale = new Vector2(resolution.x, resolution.y) / GetRequiredTextureResolution(); // update the data at the pointer Marshal.StructureToPtr(CameraRenderParams, CamNativeParams[cam], true); if (ActiveRenderingMode == RenderingMode.MeshRender) { CurrentMaterial.SetMatrix("ProjectionInverse", CameraRenderParams.ProjectionInverse); CurrentMaterial.SetMatrix("ViewProjectionInverse", CameraRenderParams.ViewProjectionInverse); CurrentMaterial.SetMatrix("EyeRayCameraCoeficients", CameraRenderParams.EyeRayCameraCoeficients); CurrentMaterial.SetVector("TextureScale", textureScale); if (AdvancedRenderParameters.UnderwaterRender) { CurrentMaterial.EnableKeyword("UNDERWATER_RENDER"); } else { CurrentMaterial.DisableKeyword("UNDERWATER_RENDER"); } if (AdvancedRenderParameters.DisableRaymarch) { CurrentMaterial.EnableKeyword("RAYMARCH_DISABLED"); } else { CurrentMaterial.DisableKeyword("RAYMARCH_DISABLED"); CurrentMaterial.SetTexture("RayMarchData", Color1); CurrentMaterial.SetTexture("MaterialData", Color2); } #if !UNITY_ANDROID if (MaxFoamParticles > 0 && MaterialParameters.EnableFoam) { CurrentMaterial.DisableKeyword("FOAM_DISABLED"); CurrentMaterial.SetTexture("ParticlesTex", ParticlesRT); } else #endif { CurrentMaterial.EnableKeyword("FOAM_DISABLED"); } } if (EnableDownscale) { CurrentUpscaleMaterial.SetVector("TextureScale", textureScale); } if (VisualizeSceneSDF) { CurrentSDFRenderMaterial.SetVector("TextureScale", textureScale); CurrentSDFRenderMaterial.SetMatrix("EyeRayCameraCoeficients", CameraRenderParams.EyeRayCameraCoeficients); } } /// /// Update render parameters for a given camera /// private void InitializeNativeCameraParams(Camera cam) { if (!CamNativeParams.ContainsKey(cam)) { // allocate memory for camera parameters CamNativeParams[cam] = Marshal.AllocHGlobal(Marshal.SizeOf(CameraRenderParams)); } if (!CamMeshRenderParams.ContainsKey(cam)) { // allocate memory for mesh render parameters CamMeshRenderParams[cam] = Marshal.AllocHGlobal(Marshal.SizeOf(MeshRenderGlobalParamsContainer)); } } private void UpdateNativeRenderParams() { #if ZIBRA_EFFECTS_DEBUG RenderParamsContainer.NeuralSamplingDistance = MaterialParameters.NeuralSamplingDistance; RenderParamsContainer.SDFDebug = MaterialParameters.SDFDebug; #endif RenderParamsContainer.RenderingMode = (int)ActiveRenderingMode; RenderParamsContainer.VertexOptimizationIterations = AdvancedRenderParameters.VertexOptimizationIterations; RenderParamsContainer.MeshOptimizationIterations = AdvancedRenderParameters.MeshOptimizationIterations; RenderParamsContainer.DualContourIsoValue = AdvancedRenderParameters.DualContourIsoSurfaceLevel; RenderParamsContainer.MeshOptimizationStep = AdvancedRenderParameters.MeshOptimizationStep; int maxVertexCount = GridNodeCount; int maxTriangleCount = (int)(maxVertexCount * AdvancedRenderParameters.MaxLiquidMeshSize / 3.0f + ADDITIONAL_VERTICES); RenderParamsContainer.MaxVertexBufferSize = maxTriangleCount * 6; RenderParamsContainer.MaxIndexBufferSize = maxTriangleCount * 3; RenderParamsContainer.RenderParamsContainerPos = transform.position; GCHandle gcparamBuffer = GCHandle.Alloc(RenderParamsContainer, GCHandleType.Pinned); SolverCommandBuffer.Clear(); LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.SetRenderParameters, gcparamBuffer.AddrOfPinnedObject()); Graphics.ExecuteCommandBuffer(SolverCommandBuffer); gcparamBuffer.Free(); } private void ClearCameraCommandBuffers() { // clear all rendering command buffers if not rendering foreach (KeyValuePair entry in CameraCBs) { if (entry.Key != null) { entry.Key.RemoveCommandBuffer(ActiveInjectionPoint, entry.Value); } } CameraCBs.Clear(); Cameras.Clear(); } /// /// Rendering callback which is called by every camera in the scene /// internal void RenderCallBack(Camera cam, float renderPipelineRenderScale = 1.0f) { if (cam.cameraType == CameraType.Preview || cam.cameraType == CameraType.Reflection || cam.cameraType == CameraType.VR) { ClearCameraCommandBuffers(); return; } if (!cam.isActiveAndEnabled && cam.cameraType != CameraType.SceneView) { return; } UpdateCameraResolution(cam, renderPipelineRenderScale); // Need at least 2 simulation frames to start rendering if (!IsRenderingEnabled()) { return; } if (!CameraResourcesMap.ContainsKey(cam)) { CameraResourcesMap[cam] = new CameraResources(); } // Re-add command buffers to cameras with new injection points if (CurrentInjectionPoint != ActiveInjectionPoint) { foreach (KeyValuePair entry in CameraCBs) { entry.Key.RemoveCommandBuffer(ActiveInjectionPoint, entry.Value); entry.Key.AddCommandBuffer(CurrentInjectionPoint, entry.Value); } ActiveInjectionPoint = CurrentInjectionPoint; } bool visibleInCamera = (RenderPipelineDetector.GetRenderPipelineType() != RenderPipelineDetector.RenderPipeline.BuiltInRP) || ((cam.cullingMask & (1 << this.gameObject.layer)) != 0); if (!visibleInCamera || MaterialParameters.FluidMeshMaterial == null || (EnableDownscale && MaterialParameters.UpscaleMaterial == null) || (VisualizeSceneSDF && MaterialParameters.SDFRenderMaterial == null)) { if (CameraCBs.ContainsKey(cam)) { cam.RemoveCommandBuffer(ActiveInjectionPoint, CameraCBs[cam]); CameraCBs[cam].Clear(); CameraCBs.Remove(cam); } return; } bool isDirty = SetMaterialParams(cam); isDirty = UpdateNativeTextures(cam, renderPipelineRenderScale) || isDirty; isDirty = !CameraCBs.ContainsKey(cam) || isDirty; InitializeNativeCameraParams(cam); UpdateCamera(cam); if (RenderPipelineDetector.GetRenderPipelineType() != RenderPipelineDetector.RenderPipeline.BuiltInRP) { #if UNITY_PIPELINE_HDRP || UNITY_PIPELINE_URP // upload camera parameters SolverCommandBuffer.Clear(); LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.SetCameraParameters, CamNativeParams[cam]); Graphics.ExecuteCommandBuffer(SolverCommandBuffer); #endif } else { if (!CameraCBs.ContainsKey(cam) || isDirty) { CommandBuffer renderCommandBuffer; if (isDirty && CameraCBs.ContainsKey(cam)) { renderCommandBuffer = CameraCBs[cam]; renderCommandBuffer.Clear(); } else { // Create render command buffer renderCommandBuffer = new CommandBuffer { name = "ZibraLiquid.Render" }; // add command buffer to camera cam.AddCommandBuffer(ActiveInjectionPoint, renderCommandBuffer); // add camera to the list CameraCBs[cam] = renderCommandBuffer; } // enable depth texture cam.depthTextureMode = DepthTextureMode.Depth; // update native camera parameters if (IsBackgroundCopyNeeded(cam)) { renderCommandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, CameraResourcesMap[cam].Background); } if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Vulkan) { renderCommandBuffer.SetRenderTarget( Color0, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, Depth, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); renderCommandBuffer.ClearRenderTarget(true, true, Color.clear); } RenderLiquidNative(renderCommandBuffer, cam); RenderFluid(renderCommandBuffer, cam); } } } private ParticleSpeciesParameters GetSpeciesParametersDefault() { ParticleSpeciesParameters speciesParameters = new ParticleSpeciesParameters(); speciesParameters.Gravity = SolverParameters.Gravity / 100.0f; speciesParameters.AffineAmmount = 4.0f * (1.0f - SolverParameters.Viscosity); speciesParameters.LiquidStiffness = SolverParameters.FluidStiffness; speciesParameters.RestDensity = SolverParameters.ParticleDensity; speciesParameters.SurfaceTension = SolverParameters.SurfaceTension; speciesParameters.AffineDivergenceDecay = 1.0f; speciesParameters.Material = new Vector3(SolverParameters.Material1, SolverParameters.Material2, SolverParameters.Material3); speciesParameters.VelocityLimit = SolverParameters.MaximumVelocity; return speciesParameters; } private ParticleSpeciesParameters GetSpeciesParameters( ZibraLiquidSolverParameters.SolverSettings thisSolverParameters) { ParticleSpeciesParameters speciesParameters = new ParticleSpeciesParameters(); speciesParameters.Gravity = thisSolverParameters.Gravity / 100.0f; speciesParameters.AffineAmmount = 4.0f * (1.0f - thisSolverParameters.Viscosity); speciesParameters.LiquidStiffness = thisSolverParameters.FluidStiffness; speciesParameters.RestDensity = thisSolverParameters.ParticleDensity; speciesParameters.SurfaceTension = thisSolverParameters.SurfaceTension; speciesParameters.AffineDivergenceDecay = 1.0f; speciesParameters.Material = new Vector3(thisSolverParameters.Material1, thisSolverParameters.Material2, thisSolverParameters.Material3); speciesParameters.VelocityLimit = thisSolverParameters.MaximumVelocity; return speciesParameters; } private void SetInteropBuffer(IntPtr NativeBuffer, List list) { long LongPtr = NativeBuffer.ToInt64(); for (int I = 0; I < list.Count; I++) { IntPtr Ptr = new IntPtr(LongPtr); Marshal.StructureToPtr(list[I], Ptr, true); LongPtr += Marshal.SizeOf(typeof(T)); } } private void UpdateInteropBuffers() { Marshal.StructureToPtr(LiquidParameters, NativeFluidData, true); if (ManipulatorManager.Elements > 0) { SetInteropBuffer(NativeManipData, ManipulatorManager.ManipulatorParams); } if (ManipulatorManager.SDFObjectList.Count > 0) { SetInteropBuffer(NativeSDFData, ManipulatorManager.SDFObjectList); } List SpeciesList = new List(); SpeciesList.Add(GetSpeciesParametersDefault()); foreach (var species in SolverParameters.AdditionalParticleSpecies) { SpeciesList.Add(GetSpeciesParameters(species)); } SetInteropBuffer(NativeSolverData, SpeciesList); } private void UpdateSolverParameters() { // Update fluid parameters LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.UpdateLiquidParameters, NativeFluidData); if (ManipulatorManager.Elements > 0) { LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.UpdateManipulatorParameters, NativeManipData); } if (ManipulatorManager.SDFObjectList.Count > 0) { LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.UpdateSDFObjects, NativeSDFData); } LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.UpdateSolverParameters, NativeSolverData); } private void RenderCallBackWrapper(Camera cam) { try { RenderCallBack(cam); } catch (Exception e) { Debug.LogException(e); } } private void StepPhysics() { SolverCommandBuffer.Clear(); ForceCloseCommandEncoder(SolverCommandBuffer); LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.ClearSDFAndID); SetFluidParameters(); ManipulatorManager.UpdateDynamic(SolverCommandBuffer, this, Timestep); UpdateInteropBuffers(); UpdateSolverParameters(); // execute simulation LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.StepPhysics); Graphics.ExecuteCommandBuffer(SolverCommandBuffer); // update internal time SimulationInternalTime += Timestep; SimulationInternalFrame++; } private void UpdateManipulatorStatistics() { /// ManipulatorStatistics GPUReadback if (!IsSimulationEnabled() || ManipulatorManager.Elements == 0) { return; } UInt32 size = (UInt32)ManipulatorManager.Elements * STATISTICS_PER_MANIPULATOR; IntPtr readbackData = LiquidBridge.ZibraLiquid_GPUReadbackGetData(CurrentInstanceID, size * sizeof(Int32)); if (readbackData != IntPtr.Zero) { Int32[] Stats = new Int32[size]; Marshal.Copy(readbackData, Stats, 0, (Int32)size); ManipulatorManager.UpdateStatistics(this, Stats, Manipulators, SolverParameters, SDFColliders); } } private void SetFluidParameters() { SolverParameters.ValidateParameters(); LiquidParameters.GridSize = GridSize; LiquidParameters.ContainerScale = ContainerSize; LiquidParameters.NodeCount = GridNodeCount; LiquidParameters.SimulationParamsContainerPos = transform.position; LiquidParameters.TimeStep = Timestep; LiquidParameters.SimulationFrame = SimulationInternalFrame; LiquidParameters.DensityBlurRadius = MaterialParameters.FluidSurfaceBlur; LiquidParameters.LiquidIsosurfaceThreshold = AdvancedRenderParameters.IsoSurfaceLevel; LiquidParameters.VertexOptimizationStep = AdvancedRenderParameters.VertexOptimizationStep; LiquidParameters.EnableContainerMovementFeedback = EnableContainerMovementFeedback ? 1 : 0; // ParticleTranslation is set by native plugin #if UNITY_ANDROID var FoamParticlesEnabled = 0; var MaxFoamParticles = 0; #else var FoamParticlesEnabled = MaterialParameters.EnableFoam ? 1 : 0; var MaxFoamParticles = this.MaxFoamParticles; #endif float MaxVelocityLimit = SolverParameters.MaximumVelocity; for (int i = 0; i < SolverParameters.AdditionalParticleSpecies.Count; i++) { MaxVelocityLimit = Mathf.Max(MaxVelocityLimit, SolverParameters.AdditionalParticleSpecies[i].MaximumVelocity); } LiquidParameters.GlobalVelocityLimit = MaxVelocityLimit; LiquidParameters.MinimumVelocity = SolverParameters.MinimumVelocity; // BlurNormalizationConstant set by native plugin LiquidParameters.MaxParticleCount = MaxNumParticles; LiquidParameters.VisualizeSDF = VisualizeSceneSDF ? 1 : 0; LiquidParameters.SimulationTime = SimulationInternalTime; LiquidParameters.FoamParticleLifetime = MaterialParameters.FoamParticleLifetime; LiquidParameters.MaxEffectParticleCount = MaxFoamParticles; LiquidParameters.FoamBuoyancy = SolverParameters.FoamBuoyancy; LiquidParameters.ParticleSpeciesCount = SolverParameters.AdditionalParticleSpecies.Count + 1; LiquidParameters.EnableFoam = FoamParticlesEnabled; } private void ClearRendering() { Camera.onPreRender -= RenderCallBackWrapper; ClearCameraCommandBuffers(); // free allocated memory foreach (var data in CamNativeParams) { Marshal.FreeHGlobal(data.Value); } foreach (var resource in CameraResourcesMap) { if (resource.Value.Background != null) { resource.Value.Background.Release(); resource.Value.Background = null; } } CameraResourcesMap.Clear(); ZibraLiquidGPUGarbageCollector.SafeRelease(Color0); Color0 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(Color1); Color1 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(Color2); Color2 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(UpscaleColor); UpscaleColor = null; ZibraLiquidGPUGarbageCollector.SafeRelease(VertexIDGrid); VertexIDGrid = null; ZibraLiquidGPUGarbageCollector.SafeRelease(VertexBuffer0); VertexBuffer0 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(VertexBuffer1); VertexBuffer1 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(TransferDataBuffer); TransferDataBuffer = null; ZibraLiquidGPUGarbageCollector.SafeRelease(MeshRenderIndexBuffer); MeshRenderIndexBuffer = null; ZibraLiquidGPUGarbageCollector.SafeRelease(QuadBuffer); QuadBuffer = null; ZibraLiquidGPUGarbageCollector.SafeRelease(VertexProperties); VertexProperties = null; ZibraLiquidGPUGarbageCollector.SafeRelease(GridNormalTexture); GridNormalTexture = null; ZibraLiquidGPUGarbageCollector.SafeRelease(DensityTexture); DensityTexture = null; ZibraLiquidGPUGarbageCollector.SafeRelease(VelocityTexture); VelocityTexture = null; ZibraLiquidGPUGarbageCollector.SafeRelease(SDFGridTexture); SDFGridTexture = null; ZibraLiquidGPUGarbageCollector.SafeRelease(EmbeddingsTexture); EmbeddingsTexture = null; ZibraLiquidGPUGarbageCollector.SafeRelease(HeightmapTexture); HeightmapTexture = null; CamNativeParams.Clear(); MaxFoamParticles = 0; } private void ClearSolver() { if (SolverCommandBuffer != null) { LiquidBridge.SubmitInstanceEvent(SolverCommandBuffer, CurrentInstanceID, LiquidBridge.EventID.ReleaseResources); Graphics.ExecuteCommandBuffer(SolverCommandBuffer); } if (SolverCommandBuffer != null) { SolverCommandBuffer.Release(); SolverCommandBuffer = null; } ZibraLiquidGPUGarbageCollector.SafeRelease(PositionMass); PositionMass = null; if (Affine != null) { ZibraLiquidGPUGarbageCollector.SafeRelease(Affine[0]); Affine[0] = null; ZibraLiquidGPUGarbageCollector.SafeRelease(Affine[1]); Affine[1] = null; } ZibraLiquidGPUGarbageCollector.SafeRelease(GridData); GridData = null; ZibraLiquidGPUGarbageCollector.SafeRelease(IndexGrid); IndexGrid = null; ZibraLiquidGPUGarbageCollector.SafeRelease(NodeParticlePairs0); NodeParticlePairs0 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(NodeParticlePairs1); NodeParticlePairs1 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(EffectParticleData0); EffectParticleData0 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(EffectParticleData1); EffectParticleData1 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(RadixGroupData1); RadixGroupData1 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(RadixGroupData2); RadixGroupData2 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(RadixGroupData3); RadixGroupData3 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(PositionMassCopy); PositionMassCopy = null; ZibraLiquidGPUGarbageCollector.SafeRelease(GridNormal); GridNormal = null; ZibraLiquidGPUGarbageCollector.SafeRelease(GridBlur0); GridBlur0 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(GridBlur1); GridBlur1 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(MassCopy); MassCopy = null; ZibraLiquidGPUGarbageCollector.SafeRelease(TmpSDFBuff); TmpSDFBuff = null; ZibraLiquidGPUGarbageCollector.SafeRelease(ParticleNumber); ParticleNumber = null; ZibraLiquidGPUGarbageCollector.SafeRelease(DynamicManipulatorData); DynamicManipulatorData = null; ZibraLiquidGPUGarbageCollector.SafeRelease(ParticleSpeciesData); ParticleSpeciesData = null; ZibraLiquidGPUGarbageCollector.SafeRelease(SDFObjectData); SDFObjectData = null; ZibraLiquidGPUGarbageCollector.SafeRelease(ManipulatorStatistics); ManipulatorStatistics = null; ZibraLiquidGPUGarbageCollector.SafeRelease(Counters); Counters = null; if (!Application.isEditor) { Destroy(LiquidMesh); } else { DestroyImmediate(LiquidMesh); } Marshal.FreeHGlobal(NativeManipData); NativeManipData = IntPtr.Zero; Marshal.FreeHGlobal(NativeFluidData); NativeFluidData = IntPtr.Zero; CurrentTextureResolution = new Vector2Int(0, 0); GridSize = new Vector3Int(0, 0, 0); CurrentParticleNumber = 0; GridNodeCount = 0; SimulationInternalFrame = 0; SimulationInternalTime = 0.0f; Timestep = 0.0f; CamRenderResolutions.Clear(); CamNativeResolutions.Clear(); // DO NOT USE AllFluids.Remove(this) // This will not result in equivalent code // ZibraLiquid::Equals is overriden and don't have correct implementation if (AllFluids != null) { for (int i = 0; i < AllFluids.Count; i++) { var fluid = AllFluids[i]; if (ReferenceEquals(fluid, this)) { AllFluids.RemoveAt(i); break; } } } } private void OnApplicationQuit() { // On quit we need to destroy liquid before destroying any colliders/manipulators OnDisable(); } // dispose the objects private void OnDisable() { RemoveFromStatReporter(); ReleaseSimulation(); } private float ByteArrayToSingle(byte[] array, ref int startIndex) { float value = BitConverter.ToSingle(array, startIndex); startIndex += sizeof(float); return value; } private int ByteArrayToInt(byte[] array, ref int startIndex) { int value = BitConverter.ToInt32(array, startIndex); startIndex += sizeof(int); return value; } private BakedInitialState ConvertBytesToInitialState(byte[] data) { int startIndex = 0; int header = ByteArrayToInt(data, ref startIndex); if (!IsValidBakedLiquidHeader(header)) { throw new Exception("Invalid baked liquid data."); } int particleCount = ByteArrayToInt(data, ref startIndex); if (particleCount > MaxNumParticles) { throw new Exception("Baked data have more particles than max particle count."); } BakedInitialState initialStateData = new BakedInitialState(); initialStateData.ParticleCount = particleCount; initialStateData.Positions = new Vector4[particleCount]; if (header == BAKED_LIQUID_PAID_HEADER_VALUE) { for (int i = 0; i < particleCount; i++) { for (int j = 0; j < 3; j++) { initialStateData.Positions[i][j] = ByteArrayToSingle(data, ref startIndex); } initialStateData.Positions[i].w = 0.0f; } } else if (header == BAKED_LIQUID_PRO_HEADER_VALUE) { for (int i = 0; i < particleCount; i++) { for (int j = 0; j < 4; j++) { initialStateData.Positions[i][j] = ByteArrayToSingle(data, ref startIndex); } } } initialStateData.AffineVelocity = new Vector2Int[4 * particleCount]; for (int i = 0; i < particleCount; i++) { for (int j = 0; j < 2; j++) { initialStateData.AffineVelocity[4 * i + 3][j] = ByteArrayToInt(data, ref startIndex); } } return initialStateData; } private BakedInitialState LoadInitialStateAsset() { byte[] data = BakedInitialStateAsset.bytes; return ConvertBytesToInitialState(data); } /// /// Apply currently set initial conditions /// private void ApplyInitialState() { switch (InitialState) { case InitialStateType.NoParticles: LiquidParameters.ParticleCount = 0; break; case InitialStateType.BakedLiquidState: if (BakedInitialStateAsset) { BakedInitialState initialStateData = LoadInitialStateAsset(); PositionMass.SetData(initialStateData.Positions); Affine[0].SetData(initialStateData.AffineVelocity); Affine[1].SetData(initialStateData.AffineVelocity); LiquidParameters.ParticleCount = initialStateData.ParticleCount; } else { LiquidParameters.ParticleCount = 0; } break; } } private Matrix4x4 CalculateEyeRayCameraCoeficients(Camera cam) { float fovTan = Mathf.Tan(cam.fieldOfView * 0.5f * Mathf.Deg2Rad); if (cam.orthographic) { fovTan = 0.0f; } Vector3 r = cam.transform.right * cam.aspect * fovTan; Vector3 u = -cam.transform.up * fovTan; Vector3 v = cam.transform.forward; return new Matrix4x4(new Vector4(r.x, r.y, r.z, 0.0f), new Vector4(u.x, u.y, u.z, 0.0f), new Vector4(v.x, v.y, v.z, 0.0f), new Vector4(0.0f, 0.0f, 0.0f, 0.0f)) .transpose; } void ValidateColliders() { if (SDFColliders != null) { HashSet colliderSet = new HashSet(SDFColliders); colliderSet.Remove(null); SDFColliders = new List(colliderSet); SDFColliders.Sort(new SDFColliderCompare()); } } void ValidateManipulators() { if (Manipulators != null) { HashSet manipulatorsSet = new HashSet(Manipulators); manipulatorsSet.Remove(null); Manipulators = new List(manipulatorsSet); Manipulators.Sort(new ManipulatorCompare()); } } internal ZibraLiquidCollider HasGivenCollider(GameObject collider) { foreach (var col in SDFColliders) { if (col.gameObject == collider) { return col; } } return null; } void AddToStatReporter() { StatReporterCollection.Add(this); } void RemoveFromStatReporter() { StatReporterCollection.Remove(this); } #endregion } }