using com.zibra.liquid.DataStructures; using com.zibra.liquid.Manipulators; using com.zibra.liquid.SDFObjects; using com.zibra.liquid.Utilities; 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; #if UNITY_PIPELINE_HDRP using UnityEngine.Rendering.HighDefinition; #endif // UNITY_PIPELINE_HDRP #if !ZIBRA_LIQUID_PAID_VERSION && !ZIBRA_LIQUID_FREE_VERSION #error Missing plugin version definition #endif namespace com.zibra.liquid.Solver { /// /// Main ZibraFluid solver component /// [AddComponentMenu("Zibra/Zibra Liquid")] [RequireComponent(typeof(ZibraLiquidMaterialParameters))] [RequireComponent(typeof(ZibraLiquidSolverParameters))] [RequireComponent(typeof(ZibraLiquidAdvancedRenderParameters))] [RequireComponent(typeof(ZibraManipulatorManager))] [ExecuteInEditMode] // Careful! This makes script execute in edit mode. // Use "EditorApplication.isPlaying" for play mode only check. // Encase this check and "using UnityEditor" in "#if UNITY_EDITOR" preprocessor directive to prevent build errors public class ZibraLiquid : MonoBehaviour { public const string PluginVersion = "1.4.5"; /// /// A list of all instances of the ZibraFluid solver /// public static List AllFluids = new List(); public static int ms_NextInstanceId = 0; public const int MPM_THREADS = 256; public const int RADIX_THREADS = 128; public const int HISTO_WIDTH = 32; public const int STATISTICS_PER_MANIPULATOR = 8; // Unique ID that always present in each baked state asset public const int BAKED_LIQUID_HEADER_VALUE = 0x071B9AA1; // private const int DENSITY_COMPUTE_BLOCK = 5; #if UNITY_PIPELINE_URP static int upscaleColorTextureID = Shader.PropertyToID("Zibra_DownscaledLiquidColor"); static int upscaleDepthTextureID = Shader.PropertyToID("Zibra_DownscaledLiquidDepth"); #endif #if UNITY_EDITOR // Used to update editors public event Action onChanged; public void NotifyChange() { if (onChanged != null) { onChanged.Invoke(); } } #endif #region PARTICLES [StructLayout(LayoutKind.Sequential)] private class RegisterParticlesBuffersBridgeParams { public IntPtr PositionMass; public IntPtr AffineVelocity0; public IntPtr AffineVelocity1; public IntPtr PositionRadius; public IntPtr ParticleNumber; } [StructLayout(LayoutKind.Sequential)] private class InitializeGPUReadbackParams { public UInt32 readbackBufferSize; public Int32 maxFramesInFlight; } [StructLayout(LayoutKind.Sequential)] private class RegisterManipulatorsBridgeParams { public Int32 ManipulatorNum; public IntPtr ManipulatorBufferDynamic; public IntPtr ManipulatorBufferConst; public IntPtr ManipulatorBufferStatistics; public IntPtr ManipulatorParams; public Int32 ConstDataSize; public IntPtr ConstManipulatorData; public IntPtr ManipIndices; public IntPtr EmbeddingsTexture; public IntPtr SDFGridTexture; } [StructLayout(LayoutKind.Sequential)] private class RegisterSolverBuffersBridgeParams { public IntPtr SimParams; public IntPtr PositionMassCopy; public IntPtr GridData; public IntPtr IndexGrid; public IntPtr GridBlur0; public IntPtr GridBlur1; public IntPtr GridNormal; public IntPtr GridSDF; 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 GridNormals; public IntPtr GridDensity; public IntPtr TransferDataBuffer; public IntPtr MeshRenderIndexBuffer; public IntPtr VertexData; } [StructLayout(LayoutKind.Sequential)] private class RegisterRenderResourcesBridgeParams { public IntPtr Depth; public IntPtr Color0; public IntPtr Color1; public IntPtr AtomicGrid; public IntPtr JFA0; public IntPtr JFA1; public IntPtr SDFRender; } [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 Vector2 CameraResolution; Single CameraParamsPadding1; Single CameraParamsPadding2; } [StructLayout(LayoutKind.Sequential)] private class MeshRenderGlobalParams { public Vector3 ContainerPosition; public float LiquidIOR; public float RayMarchIsoSurface; public float UnusedParameter; public float RayMarchStepSize; public float RayMarchStepFactor; public int RayMarchMaxSteps; public int TwoBouncesEnabled; public int PerPixelRender; }; [StructLayout(LayoutKind.Sequential)] private class RenderParams { public Single BlurRadius; public Single Diameter; public Single NeuralSamplingDistance; public Single SDFDebug; public Int32 RenderingMode; public Int32 VertexOptimizationIterations; public Int32 MeshOptimizationIterations; public Int32 JFAIterations; public Single DualContourIsoValue; public Single MeshOptimizationStep; public Int32 RenderParamsPadding1; public Int32 RenderParamsPadding2; } public 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; } } public class CameraResources { public RenderTexture background; public MaterialPair liquidMaterial; public MaterialPair upscaleMaterial; public bool isDirty = true; } [NonSerialized] public RenderTexture color0; [NonSerialized] public RenderTexture color1; [NonSerialized] public RenderTexture SDFRender; [NonSerialized] public RenderTexture depth; [NonSerialized] public ComputeBuffer atomicGrid; [NonSerialized] public ComputeBuffer JFAGrid0; [NonSerialized] public ComputeBuffer JFAGrid1; // TODO: Test LDS atomics + global atomic vs AppendBuffer performance [NonSerialized] public ComputeBuffer Counters; // [NonSerialized] // public ComputeBuffer IndirectArguments; [NonSerialized] public ComputeBuffer VertexIDGrid; [NonSerialized] public GraphicsBuffer VertexBuffer0; [NonSerialized] public GraphicsBuffer VertexBuffer1; [NonSerialized] public ComputeBuffer QuadBuffer; [NonSerialized] // Buffer for transferring data between incompatible buffer types. public ComputeBuffer TransferDataBuffer; [NonSerialized] public GraphicsBuffer MeshRenderIndexBuffer; [NonSerialized] public GraphicsBuffer VertexProperties; [NonSerialized] public RenderTexture GridNormalTexture; [NonSerialized] public RenderTexture DensityTexture; [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 List cameras = new List(); public Dictionary cameraResources = new Dictionary(); public Dictionary camNativeParams = new Dictionary(); Dictionary camMeshRenderParams = new Dictionary(); Dictionary camResolutions = new Dictionary(); #if ZIBRA_LIQUID_PAID_VERSION [Range(1024, 10000000)] #else // Increasing this limit won't allow you to spawn more particles [Range(1024, 2097152)] #endif public int MaxNumParticles = 262144; public ComputeBuffer PositionMass { get; private set; } public ComputeBuffer PositionRadius { get; private set; } // in world space public ComputeBuffer Velocity { get; private set; } public ComputeBuffer[] Affine { get; private set; } public ComputeBuffer ParticleNumber { get; private set; } [NonSerialized] public bool isEnabled = true; [NonSerialized] public float particleDiameter = 0.0f; [NonSerialized] public float particleMass = 1.0f; public Bounds bounds; // If set to false resolution is always 100% // If set to true DownscaleFactor is applied to liquid rendering public bool EnableDownscale = false; // Scale width/height of liquid render target // Pixel count is decreased by factor of DownscaleFactor * DownscaleFactor // So DownscaleFactor of 0.7 result in about 50% less pixels in render target // Doesn't have any effect unless EnableDownscale is set to true [Range(0.2f, 0.99f)] public float DownscaleFactor = 0.5f; private bool usingCustomReflectionProbe; private CameraParams cameraRenderParams; private MeshRenderGlobalParams meshRenderGlobalParams; private RenderParams renderParams; #endregion #region SOLVER #if ZIBRA_LIQUID_PAID_VERSION /// /// Types of initial conditions /// public enum InitialStateType { NoParticles, BakedLiquidState } [Serializable] public class BakedInitialState { [SerializeField] public int ParticleCount; [SerializeField] public Vector4[] Positions; [SerializeField] public Vector2Int[] AffineVelocity; } public InitialStateType InitialState = InitialStateType.NoParticles; [Tooltip("Baked state saved with Baking Utility. Will reset to None if incompatible file is detected.")] public TextAsset BakedInitialStateAsset; #endif /// /// Native solver instance ID number /// [NonSerialized] public int CurrentInstanceID; [StructLayout(LayoutKind.Sequential)] private class SimulationParams { public Vector3 GridSize; public Int32 ParticleCount; public Vector3 ContainerScale; public Int32 NodeCount; public Vector3 ContainerPos; public Single TimeStep; public Vector3 Gravity; public Int32 SimulationFrame; public Single DensityBlurRadius; public Single LiquidIsosurfaceThreshold; public Single VertexOptimizationStep; public Single AffineAmmount; public Vector3 ParticleTranslation; public Single VelocityLimit; public Single LiquidStiffness; public Single RestDensity; public Single SurfaceTension; public Single AffineDivergenceDecay; public Single MinimumVelocity; public Single BlurNormalizationConstant; public Int32 MaxParticleCount; public Int32 VisualizeSDF; } private const int BlockDim = 8; public ComputeBuffer GridData { get; private set; } public ComputeBuffer IndexGrid { get; private set; } public ComputeBuffer GridBlur0 { get; private set; } public ComputeBuffer GridNormal { get; private set; } public ComputeBuffer GridSDF { get; private set; } public ComputeBuffer SurfaceGridType { get; private set; } public Texture3D SDFGridTexture { get; private set; } public Texture3D EmbeddingsTexture { get; private set; } /// /// Current timestep /// public float timestep = 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; private int numNodes = 0; private SimulationParams fluidParameters; private ComputeBuffer positionMassCopy; private ComputeBuffer GridBlur1; private ComputeBuffer nodeParticlePairs0; private ComputeBuffer nodeParticlePairs1; private ComputeBuffer RadixGroupData1; private ComputeBuffer RadixGroupData2; private ComputeBuffer RadixGroupData3; private IntPtr updateColliderParamsNative; private CommandBuffer solverCommandBuffer; #endregion public enum RenderingMode { ParticleRender, MeshRender } // Don't change this parameter on active liquid // It won't work in future versions public RenderingMode CurrentRenderingMode = RenderingMode.MeshRender; private RenderingMode ActiveRenderingMode = RenderingMode.MeshRender; // Only used on SRP public CameraEvent CurrentInjectionPoint = CameraEvent.BeforeForwardAlpha; private CameraEvent ActiveInjectionPoint = CameraEvent.BeforeForwardAlpha; public bool IsSimulatingInBackground { get; set; } /// /// The grid size of the simulation /// public Vector3Int GridSize { get; private set; } [NonSerialized] [Obsolete( "reflectionProbe is deprecated. Use reflectionProbeSRP or reflectionProbeHDRP instead depending on your Rendering Pipeline (URP uses reflectionProbeSRP).", true)] public ReflectionProbe reflectionProbe; #if UNITY_PIPELINE_HDRP [FormerlySerializedAs("reflectionProbe")] [Tooltip("Use a custom reflection probe")] public HDProbe reflectionProbeHDRP; [Tooltip("Use a custom light")] public Light customLightHDRP; #else [FormerlySerializedAs("reflectionProbe")] #endif // UNITY_PIPELINE_HDRP [Tooltip("Use a custom reflection probe")] public ReflectionProbe reflectionProbeSRP; [Tooltip("The maximum allowed simulation timestep")] [Range(0.0f, 1.0f)] public float timeStepMax = 1.00f; [Tooltip("Fallback max frame latency. Used when it isn't possible to retrieve Unity's max frame latency.")] [Range(2, 16)] public UInt32 maxFramesInFlight = 3; [Tooltip("The speed of the simulation, how many simulation time units per second")] [Range(0.0f, 100.0f)] public float simTimePerSec = 40.0f; public int activeParticleNumber { get; private set; } = 0; [Tooltip("The number of solver iterations per frame, in most cases one iteration is sufficient")] [Range(1, 10)] public int iterationsPerFrame = 1; public float CellSize { get; private set; } [Tooltip("Sets the resolution of the largest sid of the grids container equal to this value")] [Min(16)] public int gridResolution = 128; [Range(1e-2f, 16.0f)] public float emitterDensity = 1.0f; public bool runSimulation = true; public bool runRendering = true; public bool visualizeSceneSDF = false; /// /// Main parameters of the simulation /// public ZibraLiquidSolverParameters solverParameters; /// /// Main rendering parameters /// public ZibraLiquidMaterialParameters materialParameters; /// /// Advanced rendering parameters /// public ZibraLiquidAdvancedRenderParameters renderingParameters; /// /// Solver container size /// public Vector3 containerSize = new Vector3(10, 10, 10); /// /// Solver container position /// public Vector3 containerPos; /// /// Initial velocity of the fluid /// public Vector3 fluidInitialVelocity; /// /// Manager for all objects interacting in some way with the simulation /// [HideInInspector] [SerializeField] public ZibraManipulatorManager manipulatorManager; private IntPtr NativeManipData; private IntPtr NativeFluidData; /// /// Compute buffer with dynamic manipulator data /// public ComputeBuffer DynamicManipulatorData { get; private set; } /// /// Compute buffer with constant manipulator data /// public ComputeBuffer ConstManipulatorData { get; private set; } /// /// Compute buffer with statistics about the manipulators /// public ComputeBuffer ManipulatorStatistics { get; private set; } /// /// List of used SDF colliders /// [SerializeField] private List sdfColliders = new List(); /// /// List of used manipulators /// [SerializeField] private List manipulators = new List(); public int avgFrameRate; public float deltaTime; public float smoothDeltaTime; public bool forceTextureUpdate = false; /// /// Is solver initialized /// //[NonSerialized] public bool initialized { get; private set; } = false; /// /// Is solver using fixed unity time steps /// public bool useFixedTimestep = false; #if UNITY_EDITOR private bool ForceRepaint = false; #endif #if UNITY_PIPELINE_HDRP private LiquidHDRPRenderComponent hdrpRenderer; #endif // UNITY_PIPELINE_HDRP enum GraphicsBufferType { Vertex, Index } GraphicsBuffer CreateGraphicsBuffer(GraphicsBufferType type, int count, int stride) { // Unity 2019 don't have UAV in graphics buffers // So we have to create them internally #if UNITY_2020_1_OR_NEWER return new GraphicsBuffer(type == GraphicsBufferType.Vertex ? GraphicsBuffer.Target.Raw | GraphicsBuffer.Target.Vertex : GraphicsBuffer.Target.Raw | GraphicsBuffer.Target.Index, count, stride); #else return null; #endif } IntPtr GetNativePtr(ComputeBuffer buffer) { return buffer == null ? IntPtr.Zero : buffer.GetNativeBufferPtr(); } IntPtr GetNativePtr(GraphicsBuffer buffer) { return buffer == null ? IntPtr.Zero : buffer.GetNativeBufferPtr(); } IntPtr GetNativePtr(RenderTexture texture) { return texture == null ? IntPtr.Zero : texture.GetNativeTexturePtr(); } IntPtr GetNativePtr(Texture3D texture) { return texture == null ? IntPtr.Zero : texture.GetNativeTexturePtr(); } public bool IsRenderingEnabled() { // We need at least 2 simulation frames before we can start rendering return initialized && runRendering && (simulationInternalFrame > 1); } /// /// Activate the solver /// public void Run() { runSimulation = true; } /// /// Stop the solver /// public void Stop() { runSimulation = false; } void SetupScriptableRenderComponents() { #if UNITY_PIPELINE_HDRP #if UNITY_EDITOR if (RenderPipelineDetector.GetRenderPipelineType() == RenderPipelineDetector.RenderPipeline.HDRP) { hdrpRenderer = gameObject.GetComponent(); if (hdrpRenderer == null) { hdrpRenderer = gameObject.AddComponent(); hdrpRenderer.injectionPoint = CustomPassInjectionPoint.BeforePostProcess; 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 } void ForceCloseCommandEncoder(CommandBuffer cmdList) { #if UNITY_EDITOR_OSX || (!UNITY_EDITOR && UNITY_STANDALONE_OSX) // 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 gracefuly if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Metal) { cmdList.SetRenderTarget(color0); cmdList.DrawProcedural(new Matrix4x4(), materialParameters.NoOpMaterial, 0, MeshTopology.Triangles, 3); } #endif } IntPtr MakeTextureNativeBridge(RenderTexture texture) { var unityTextureBridge = new ZibraLiquidBridge.UnityTextureBridge(); if (texture != null) { unityTextureBridge.texture = GetNativePtr(texture); unityTextureBridge.format = ZibraLiquidBridge.ToBridgeTextureFormat(texture.graphicsFormat); } else { unityTextureBridge.texture = IntPtr.Zero; unityTextureBridge.format = ZibraLiquidBridge.TextureFormat.None; } IntPtr nativePtr = Marshal.AllocHGlobal(Marshal.SizeOf(unityTextureBridge)); Marshal.StructureToPtr(unityTextureBridge, nativePtr, true); return nativePtr; } IntPtr MakeTextureNativeBridge(Texture3D texture) { var unityTextureBridge = new ZibraLiquidBridge.UnityTextureBridge(); unityTextureBridge.texture = GetNativePtr(texture); unityTextureBridge.format = ZibraLiquidBridge.ToBridgeTextureFormat(texture.graphicsFormat); IntPtr nativePtr = Marshal.AllocHGlobal(Marshal.SizeOf(unityTextureBridge)); Marshal.StructureToPtr(unityTextureBridge, nativePtr, true); return nativePtr; } void OnDrawGizmosSelected() { Gizmos.color = Color.yellow; Gizmos.DrawWireCube(transform.position, containerSize); Gizmos.color = Color.cyan; 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); } void OnDrawGizmos() { OnDrawGizmosSelected(); } void Start() { materialParameters = gameObject.GetComponent(); solverParameters = gameObject.GetComponent(); renderingParameters = gameObject.GetComponent(); manipulatorManager = gameObject.GetComponent(); } protected void OnEnable() { SetupScriptableRenderComponents(); #if ZIBRA_LIQUID_PAID_VERSION if (!ZibraLiquidBridge.IsPaidVersion()) { Debug.LogError( "Free version of native plugin used with paid version of C# plugin. If you just replaced your Zibra Liquids version you need to restart Unity Editor."); } #else if (ZibraLiquidBridge.IsPaidVersion()) { Debug.LogError( "Paid version of native plugin used with free version of C# plugin. If you just replaced your Zibra Liquids version you need to restart Unity Editor."); } #endif AllFluids?.Add(this); #if UNITY_EDITOR if (!UnityEditor.EditorApplication.isPlaying) { return; } #endif Init(); } public void UpdateGridSize() { CellSize = Math.Max(containerSize.x, Math.Max(containerSize.y, containerSize.z)) / gridResolution; GridSize = Vector3Int.CeilToInt(containerSize / CellSize); } private void InitializeParticles() { UpdateGridSize(); fluidParameters = new SimulationParams(); NativeFluidData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SimulationParams))); isEnabled = true; var numParticlesRounded = (int)Math.Ceiling((double)MaxNumParticles / MPM_THREADS) * MPM_THREADS; // round to workgroup size PositionMass = new ComputeBuffer(MaxNumParticles, 4 * sizeof(float)); PositionRadius = 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)); #if ZIBRA_LIQUID_DEBUG PositionMass.name = "PositionMass"; PositionRadius.name = "PositionRadius"; Affine[0].name = "Affine0"; Affine[1].name = "Affine1"; ParticleNumber.name = "ParticleNumber"; #endif #if ZIBRA_LIQUID_PAID_VERSION // We mush apply state before we send buffers to native plugin // SetData seems to recreate buffers at least on Metal ApplyInitialState(); #endif int[] Pnums = new int[128]; for (int i = 0; i < 128; i++) { Pnums[i] = 0; } ParticleNumber.SetData(Pnums); if (manipulatorManager != null) { manipulatorManager.UpdateConst(manipulators, sdfColliders); manipulatorManager.UpdateDynamic(this); #if ZIBRA_LIQUID_PAID_VERSION if (manipulatorManager.NeuralColliders > 0) { EmbeddingsTexture = new Texture3D(ZibraNeuralCollider.EMBEDDING_GRID_DIMENSION * ZibraNeuralCollider.EMBEDDING_SIZE, ZibraNeuralCollider.EMBEDDING_GRID_DIMENSION, ZibraNeuralCollider.EMBEDDING_GRID_DIMENSION * manipulatorManager.NeuralColliders, TextureFormat.RGBA32, 0); SDFGridTexture = new Texture3D( ZibraNeuralCollider.SDF_APPROX_DIMENSION, ZibraNeuralCollider.SDF_APPROX_DIMENSION, ZibraNeuralCollider.SDF_APPROX_DIMENSION * manipulatorManager.NeuralColliders, TextureFormat.RHalf, 0); EmbeddingsTexture.filterMode = FilterMode.Trilinear; SDFGridTexture.filterMode = FilterMode.Trilinear; EmbeddingsTexture.SetPixelData(manipulatorManager.Embeddings, 0); SDFGridTexture.SetPixelData(manipulatorManager.SDFGrid, 0); EmbeddingsTexture.Apply(false); SDFGridTexture.Apply(false); } else #endif { EmbeddingsTexture = new Texture3D(1, 1, 1, TextureFormat.RGBA32, 0); SDFGridTexture = new Texture3D(1, 1, 1, TextureFormat.RHalf, 0); } int ManipSize = Marshal.SizeOf(typeof(ZibraManipulatorManager.ManipulatorParam)); // Need to create at least some buffer to bind to shaders NativeManipData = Marshal.AllocHGlobal(manipulatorManager.Elements * ManipSize); DynamicManipulatorData = new ComputeBuffer(Math.Max(manipulatorManager.Elements, 1), ManipSize); int ConstDataLength = manipulatorManager.ConstAdditionalData.Length; ConstManipulatorData = new ComputeBuffer(Math.Max(ConstDataLength, 1), sizeof(int)); ManipulatorStatistics = new ComputeBuffer( Math.Max(STATISTICS_PER_MANIPULATOR * manipulatorManager.Elements, 1), sizeof(int)); int constDataSize = ConstDataLength * sizeof(int); #if ZIBRA_LIQUID_DEBUG DynamicManipulatorData.name = "DynamicManipulatorData"; ConstManipulatorData.name = "ConstManipulatorData"; ManipulatorStatistics.name = "ManipulatorStatistics"; #endif var gcparamBuffer0 = GCHandle.Alloc(manipulatorManager.ManipulatorParams.ToArray(), GCHandleType.Pinned); IntPtr gcparamBuffer1Native = IntPtr.Zero; GCHandle gcparamBuffer1 = new GCHandle(); if (ConstDataLength > 0) { gcparamBuffer1 = GCHandle.Alloc(manipulatorManager.ConstAdditionalData, GCHandleType.Pinned); gcparamBuffer1Native = gcparamBuffer1.AddrOfPinnedObject(); } var gcparamBuffer2 = GCHandle.Alloc(manipulatorManager.indices, GCHandleType.Pinned); var registerManipulatorsBridgeParams = new RegisterManipulatorsBridgeParams(); registerManipulatorsBridgeParams.ManipulatorNum = manipulatorManager.Elements; registerManipulatorsBridgeParams.ManipulatorBufferDynamic = GetNativePtr(DynamicManipulatorData); registerManipulatorsBridgeParams.ManipulatorBufferConst = GetNativePtr(ConstManipulatorData); registerManipulatorsBridgeParams.ManipulatorBufferStatistics = ManipulatorStatistics.GetNativeBufferPtr(); registerManipulatorsBridgeParams.ManipulatorParams = gcparamBuffer0.AddrOfPinnedObject(); registerManipulatorsBridgeParams.ConstDataSize = constDataSize; registerManipulatorsBridgeParams.ConstManipulatorData = gcparamBuffer1Native; registerManipulatorsBridgeParams.ManipIndices = gcparamBuffer2.AddrOfPinnedObject(); registerManipulatorsBridgeParams.EmbeddingsTexture = MakeTextureNativeBridge(EmbeddingsTexture); registerManipulatorsBridgeParams.SDFGridTexture = MakeTextureNativeBridge(SDFGridTexture); IntPtr nativeRegisterManipulatorsBridgeParams = Marshal.AllocHGlobal(Marshal.SizeOf(registerManipulatorsBridgeParams)); Marshal.StructureToPtr(registerManipulatorsBridgeParams, nativeRegisterManipulatorsBridgeParams, true); solverCommandBuffer.Clear(); ZibraLiquidBridge.SubmitInstanceEvent(solverCommandBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.RegisterManipulators, nativeRegisterManipulatorsBridgeParams); Graphics.ExecuteCommandBuffer(solverCommandBuffer); gcparamBuffer0.Free(); if (ConstDataLength > 0) { gcparamBuffer1.Free(); } gcparamBuffer2.Free(); } else { Debug.LogWarning("No manipulator manipulatorManager has been set"); } cameraRenderParams = new CameraParams(); renderParams = new RenderParams(); meshRenderGlobalParams = new MeshRenderGlobalParams(); var registerParticlesBuffersParams = new RegisterParticlesBuffersBridgeParams(); registerParticlesBuffersParams.PositionMass = GetNativePtr(PositionMass); registerParticlesBuffersParams.AffineVelocity0 = GetNativePtr(Affine[0]); registerParticlesBuffersParams.AffineVelocity1 = GetNativePtr(Affine[1]); registerParticlesBuffersParams.PositionRadius = GetNativePtr(PositionRadius); registerParticlesBuffersParams.ParticleNumber = GetNativePtr(ParticleNumber); IntPtr nativeRegisterParticlesBuffersParams = Marshal.AllocHGlobal(Marshal.SizeOf(registerParticlesBuffersParams)); Marshal.StructureToPtr(registerParticlesBuffersParams, nativeRegisterParticlesBuffersParams, true); solverCommandBuffer.Clear(); ZibraLiquidBridge.SubmitInstanceEvent(solverCommandBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.RegisterParticlesBuffers, nativeRegisterParticlesBuffersParams); Graphics.ExecuteCommandBuffer(solverCommandBuffer); } public int GetParticleCountRounded() { return (int)Math.Ceiling((double)MaxNumParticles / MPM_THREADS) * MPM_THREADS; // round to workgroup size; } public ulong GetParticleCountFootprint() { ulong result = 0; int particleCountRounded = GetParticleCountRounded(); result += (ulong)(MaxNumParticles * 4 * sizeof(float)); // PositionMass result += (ulong)(MaxNumParticles * 4 * sizeof(float)); // PositionRadius 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)(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; } public ulong GetCollidersFootprint() { ulong result = 0; foreach (var collider in sdfColliders) { result += collider.GetMemoryFootrpint(); } int ManipSize = Marshal.SizeOf(typeof(ZibraManipulatorManager.ManipulatorParam)); result += (ulong)(manipulators.Count * ManipSize); // DynamicManipData result += (ulong)(manipulators.Count * sizeof(int)); // ConstManipData return result; } public ulong GetGridFootprint() { ulong result = 0; GridSize = Vector3Int.CeilToInt(containerSize / CellSize); numNodes = GridSize[0] * GridSize[1] * GridSize[2]; result += (ulong)(numNodes * 4 * sizeof(int)); // GridData result += (ulong)(numNodes * 4 * sizeof(float)); // GridNormal result += (ulong)(numNodes * sizeof(float)); // GridBlur0 result += (ulong)(numNodes * sizeof(float)); // GridBlur1 result += (ulong)(numNodes * sizeof(float)); // GridSDF result += (ulong)(numNodes * 2 * sizeof(int)); // IndexGrid result += (ulong)(numNodes * sizeof(int)); // VertexIDGrid result += (ulong)(numNodes * 4 * sizeof(float)); // VertexBuffer result += (ulong)(numNodes * sizeof(uint)); // QuadBuffer result += (ulong)(numNodes * (sizeof(uint) * 4)); // VertexProperties result += (ulong)(numNodes * 2 * sizeof(float)); // GridNormalTexture result += (ulong)(numNodes * sizeof(float) / 2); // DensityTexture return result; } 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; numNodes = GridSize[0] * GridSize[1] * GridSize[2]; GridData = new ComputeBuffer(numNodes * 4, sizeof(int)); GridNormal = new ComputeBuffer(numNodes, 4 * sizeof(float)); GridBlur0 = new ComputeBuffer(numNodes, sizeof(float)); GridBlur1 = new ComputeBuffer(numNodes, sizeof(float)); GridSDF = new ComputeBuffer(numNodes, sizeof(float)); // TODO: Test LDS atomics + global atomic vs AppendBuffer performance Counters = new ComputeBuffer(8, sizeof(uint)); VertexIDGrid = new ComputeBuffer(numNodes, sizeof(int)); VertexBuffer0 = CreateGraphicsBuffer(GraphicsBufferType.Vertex, 4 * numNodes, sizeof(uint)); VertexBuffer1 = CreateGraphicsBuffer(GraphicsBufferType.Vertex, 4 * numNodes, sizeof(uint)); TransferDataBuffer = new ComputeBuffer(1, sizeof(uint)); MeshRenderIndexBuffer = CreateGraphicsBuffer(GraphicsBufferType.Index, 3 * numNodes, sizeof(uint)); // the max number of quads possible is about 3*numNodes, but in reality it should not be more than numNodes // in any case QuadBuffer = new ComputeBuffer(numNodes, sizeof(int)); VertexProperties = CreateGraphicsBuffer(GraphicsBufferType.Vertex, numNodes, 4 * sizeof(uint)); IndexGrid = new ComputeBuffer(numNodes, 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.R16_SFloat, FormatUsage.LoadStore) ? GraphicsFormat.R16_SFloat : GraphicsFormat.R32_SFloat); DensityTexture.name = "DensityTexture"; int NumParticlesRounded = GetParticleCountRounded(); positionMassCopy = new ComputeBuffer(NumParticlesRounded, 4 * sizeof(float)); nodeParticlePairs0 = new ComputeBuffer(2 * NumParticlesRounded, sizeof(int)); nodeParticlePairs1 = new ComputeBuffer(2 * NumParticlesRounded, sizeof(int)); 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_LIQUID_DEBUG GridData.name = "GridData"; GridNormal.name = "GridNormal"; GridBlur0.name = "GridBlur0"; GridBlur1.name = "GridBlur1"; GridSDF.name = "GridSDF"; IndexGrid.name = "IndexGrid"; positionMassCopy.name = "positionMassCopy"; nodeParticlePairs0.name = "NodeParticlePairs0"; nodeParticlePairs1.name = "NodeParticlePairs1"; RadixGroupData1.name = "RadixGroupData1"; RadixGroupData2.name = "RadixGroupData2"; RadixGroupData3.name = "RadixGroupData3"; #endif SetFluidParameters(); var gcparamBuffer = GCHandle.Alloc(fluidParameters, GCHandleType.Pinned); var registerSolverBuffersBridgeParams = new RegisterSolverBuffersBridgeParams(); registerSolverBuffersBridgeParams.SimParams = gcparamBuffer.AddrOfPinnedObject(); registerSolverBuffersBridgeParams.PositionMassCopy = GetNativePtr(positionMassCopy); registerSolverBuffersBridgeParams.GridData = GetNativePtr(GridData); registerSolverBuffersBridgeParams.IndexGrid = GetNativePtr(IndexGrid); registerSolverBuffersBridgeParams.GridBlur0 = GetNativePtr(GridBlur0); registerSolverBuffersBridgeParams.GridBlur1 = GetNativePtr(GridBlur1); registerSolverBuffersBridgeParams.GridNormal = GetNativePtr(GridNormal); registerSolverBuffersBridgeParams.GridSDF = GetNativePtr(GridSDF); registerSolverBuffersBridgeParams.NodeParticlePairs0 = GetNativePtr(nodeParticlePairs0); registerSolverBuffersBridgeParams.NodeParticlePairs1 = GetNativePtr(nodeParticlePairs1); 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.GridNormals = MakeTextureNativeBridge(GridNormalTexture); registerSolverBuffersBridgeParams.TransferDataBuffer = GetNativePtr(TransferDataBuffer); registerSolverBuffersBridgeParams.MeshRenderIndexBuffer = GetNativePtr(MeshRenderIndexBuffer); registerSolverBuffersBridgeParams.VertexData = GetNativePtr(VertexProperties); IntPtr nativeRegisterSolverBuffersBridgeParams = Marshal.AllocHGlobal(Marshal.SizeOf(registerSolverBuffersBridgeParams)); Marshal.StructureToPtr(registerSolverBuffersBridgeParams, nativeRegisterSolverBuffersBridgeParams, true); solverCommandBuffer.Clear(); ZibraLiquidBridge.SubmitInstanceEvent(solverCommandBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.RegisterSolverBuffers, nativeRegisterSolverBuffersBridgeParams); Graphics.ExecuteCommandBuffer(solverCommandBuffer); gcparamBuffer.Free(); solverCommandBuffer.Clear(); } /// /// Initializes a new instance of ZibraFluid /// public void Init() { if (initialized) { return; } initialized = true; try { #if UNITY_PIPELINE_HDRP if (RenderPipelineDetector.GetRenderPipelineType() == RenderPipelineDetector.RenderPipeline.HDRP) { bool missingRequiredParameter = false; if (customLightHDRP == null) { Debug.LogError("No Custom Light set in Zibra Liquid."); missingRequiredParameter = true; } if (reflectionProbeHDRP == null) { Debug.LogError("No reflection probe added to Zibra Liquid."); missingRequiredParameter = true; } if (missingRequiredParameter) { throw new Exception("Liquid creation failed due to missing parameter."); } } #endif #if ZIBRA_LIQUID_PAID_VERSION if (InitialState == ZibraLiquid.InitialStateType.NoParticles || BakedInitialStateAsset == null) #endif { bool haveEmitter = false; foreach (var manipulator in manipulators) { if (manipulator.ManipType == Manipulator.ManipulatorType.Emitter) { haveEmitter = true; break; } } if (!haveEmitter) { #if ZIBRA_LIQUID_PAID_VERSION throw new Exception("Liquid creation failed. Liquid have neither initial state nor emitters."); #else throw new Exception("Liquid creation failed. Liquid have don't have any emitters."); #endif } } Camera.onPreRender += RenderCallBack; solverCommandBuffer = new CommandBuffer { name = "ZibraLiquid.Solver" }; CurrentInstanceID = ms_NextInstanceId++; ForceCloseCommandEncoder(solverCommandBuffer); ZibraLiquidBridge.SubmitInstanceEvent(solverCommandBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.CreateFluidInstance); Graphics.ExecuteCommandBuffer(solverCommandBuffer); solverCommandBuffer.Clear(); InitializeParticles(); var initializeGPUReadbackParamsBridgeParams = new InitializeGPUReadbackParams(); #if ZIBRA_LIQUID_FREE_VERSION UInt32 manipSize = 0; #else UInt32 manipSize = (UInt32)manipulatorManager.Elements * STATISTICS_PER_MANIPULATOR * sizeof(Int32); #endif initializeGPUReadbackParamsBridgeParams.readbackBufferSize = sizeof(Int32) + manipSize; switch (SystemInfo.graphicsDeviceType) { case GraphicsDeviceType.Direct3D11: case GraphicsDeviceType.XboxOne: case GraphicsDeviceType.Switch: #if UNITY_2020_3_OR_NEWER case GraphicsDeviceType.Direct3D12: case GraphicsDeviceType.XboxOneD3D12: #endif initializeGPUReadbackParamsBridgeParams.maxFramesInFlight = QualitySettings.maxQueuedFrames + 1; break; default: initializeGPUReadbackParamsBridgeParams.maxFramesInFlight = (int)this.maxFramesInFlight; break; } IntPtr nativeCreateInstanceBridgeParams = Marshal.AllocHGlobal(Marshal.SizeOf(initializeGPUReadbackParamsBridgeParams)); Marshal.StructureToPtr(initializeGPUReadbackParamsBridgeParams, nativeCreateInstanceBridgeParams, true); solverCommandBuffer.Clear(); ZibraLiquidBridge.SubmitInstanceEvent(solverCommandBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.InitializeGpuReadback, nativeCreateInstanceBridgeParams); Graphics.ExecuteCommandBuffer(solverCommandBuffer); InitializeSolver(); initialized = true; // hack to make editor -> play mode transition work when the liquid is initialized forceTextureUpdate = true; } catch (Exception e) { Debug.LogError(e); ClearRendering(); ClearSolver(); initialized = false; } } protected void Update() { if (!initialized) { return; } ZibraLiquidGPUGarbageCollector.GCUpdateWrapper(); #if UNITY_EDITOR if (!UnityEditor.EditorApplication.isPlaying) { return; } #endif if (!useFixedTimestep) UpdateSimulation(Time.smoothDeltaTime); UpdateReadback(); } protected void FixedUpdate() { #if UNITY_EDITOR if (!UnityEditor.EditorApplication.isPlaying) { return; } #endif if (useFixedTimestep) UpdateSimulation(Time.fixedDeltaTime); } public void UpdateReadback() { solverCommandBuffer.Clear(); // This must be called at most ONCE PER FRAME // Otherwise you'll get deadlock ZibraLiquidBridge.SubmitInstanceEvent(solverCommandBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.UpdateReadback); Graphics.ExecuteCommandBuffer(solverCommandBuffer); /// ParticleNumber GPUReadback UInt32 size = sizeof(UInt32); IntPtr readbackData = ZibraLiquidBridge.GPUReadbackGetData(CurrentInstanceID, size); if (readbackData != IntPtr.Zero) { activeParticleNumber = Marshal.ReadInt32(readbackData); } UpdateManipulatorStatistics(); } public void UpdateSimulation(float deltaTime) { UpdateNativeRenderParams(); if (!initialized || !runSimulation) return; timestep = Math.Min(simTimePerSec * deltaTime / (float)iterationsPerFrame, timeStepMax); for (var i = 0; i < iterationsPerFrame; i++) { StepPhysics(); } solverCommandBuffer.Clear(); // copy grid data to 3d texture for rendering after physics steps Graphics.ExecuteCommandBuffer(solverCommandBuffer); #if UNITY_EDITOR NotifyChange(); #endif particleMass = 1.0f; } /// /// Update the material parameters /// public bool SetMaterialParams(Camera cam) { bool isDirty = false; CameraResources camRes = cameraResources[cam]; Material usedUpscaleMaterial = EnableDownscale ? materialParameters.UpscaleMaterial : null; isDirty = isDirty || camRes.upscaleMaterial.SetMaterial(usedUpscaleMaterial); Material CurrentSharedMaterial; switch (ActiveRenderingMode) { case RenderingMode.ParticleRender: CurrentSharedMaterial = materialParameters.FluidMaterial; break; case RenderingMode.MeshRender: CurrentSharedMaterial = materialParameters.FluidMeshMaterial; break; default: Debug.LogError("Unknown Rendering mode"); return false; } isDirty = isDirty || camRes.liquidMaterial.SetMaterial(CurrentSharedMaterial); Material CurrentMaterial = camRes.liquidMaterial.currentMaterial; 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 { if (reflectionProbeSRP != null) // custom reflection probe { usingCustomReflectionProbe = true; CurrentMaterial.SetTexture("ReflectionProbe", reflectionProbeSRP.texture); CurrentMaterial.SetVector("ReflectionProbe_HDR", reflectionProbeSRP.textureHDRDecodeValues); CurrentMaterial.SetVector("ReflectionProbe_BoxMax", reflectionProbeSRP.bounds.max); CurrentMaterial.SetVector("ReflectionProbe_BoxMin", reflectionProbeSRP.bounds.min); CurrentMaterial.SetVector("ReflectionProbe_ProbePosition", reflectionProbeSRP.transform.position); } else { usingCustomReflectionProbe = false; } } CurrentMaterial.SetFloat("AbsorptionAmount", materialParameters.AbsorptionAmount); CurrentMaterial.SetFloat("ScatteringAmount", materialParameters.ScatteringAmount); CurrentMaterial.SetFloat("Metalness", materialParameters.Metalness); 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); #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", containerPos); CurrentMaterial.SetVector("GridSize", (Vector3)GridSize); CurrentMaterial.SetFloat("FoamIntensity", materialParameters.FoamIntensity); CurrentMaterial.SetFloat("FoamAmount", materialParameters.FoamAmount * solverParameters.ParticleDensity); CurrentMaterial.SetFloat("ParticleDiameter", particleDiameter); CurrentMaterial.SetFloat("RefractionMinimumDepth", 1e-4f); CurrentMaterial.SetFloat("RefractionDepthBias", 1.25f); CurrentMaterial.SetTexture("GridNormals", GridNormalTexture); CurrentMaterial.SetTexture("MeshRenderData", color0); CurrentMaterial.SetTexture("MeshDepth", depth); CurrentMaterial.SetTexture("GridNormals", GridNormalTexture); CurrentMaterial.SetTexture("GridDensity", DensityTexture); if (renderingParameters.RefractionBounces == ZibraLiquidAdvancedRenderParameters.RayMarchingBounces.TwoBounces) { if (meshRenderGlobalParams.TwoBouncesEnabled == 0) isDirty = true; meshRenderGlobalParams.TwoBouncesEnabled = 1; CurrentMaterial.EnableKeyword("TWO_BOUNCES"); } else { if (meshRenderGlobalParams.TwoBouncesEnabled == 1) isDirty = true; meshRenderGlobalParams.TwoBouncesEnabled = 0; CurrentMaterial.DisableKeyword("TWO_BOUNCES"); } if (renderingParameters.RefractionQuality == ZibraLiquidAdvancedRenderParameters.LiquidRefractionQuality.PerVertexRender) { if (meshRenderGlobalParams.PerPixelRender == 1) isDirty = true; meshRenderGlobalParams.PerPixelRender = 0; CurrentMaterial.EnableKeyword("VERTEX_DEPTH"); } else { if (meshRenderGlobalParams.PerPixelRender == 0) isDirty = true; meshRenderGlobalParams.PerPixelRender = 1; CurrentMaterial.DisableKeyword("VERTEX_DEPTH"); } if (visualizeSceneSDF) { CurrentMaterial.EnableKeyword("VISUALIZE_SDF"); } else { CurrentMaterial.DisableKeyword("VISUALIZE_SDF"); } #if UNITY_IOS && !UNITY_EDITOR if (EnableDownscale && IsBackgroundCopyNeeded(cam)) { CurrentMaterial.EnableKeyword("FLIP_BACKGROUND"); } else { CurrentMaterial.DisableKeyword("FLIP_BACKGROUND"); } #endif 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 } else { if (usingCustomReflectionProbe) { CurrentMaterial.EnableKeyword("CUSTOM_REFLECTION_PROBE"); } else { CurrentMaterial.DisableKeyword("CUSTOM_REFLECTION_PROBE"); } } CurrentMaterial.SetTexture("SDFRender", SDFRender); return isDirty; } public Vector2Int ApplyDownscaleFactor(Vector2Int val) { if (!EnableDownscale) return val; return new Vector2Int((int)(val.x * DownscaleFactor), (int)(val.y * DownscaleFactor)); } private bool CreateTexture(ref RenderTexture texture, Vector2Int resolution, bool applyDownscaleFactor, FilterMode filterMode, int depth, RenderTextureFormat format, bool enableRandomWrite = false) { if (texture == null || texture.width != resolution.x || texture.height != resolution.y || forceTextureUpdate) { 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; } // Returns resolution that is enough for all cameras private Vector2Int GetRequiredTextureResolution() { if (camResolutions.Count == 0) Debug.Log("camResolutions dictionary was empty when GetRequiredTextureResolution was called."); Vector2Int result = new Vector2Int(0, 0); foreach (var item in camResolutions) { result = Vector2Int.Max(result, item.Value); } return result; } public bool IsBackgroundCopyNeeded(Camera cam) { return !EnableDownscale || (cam.activeTexture == null); } private RenderTexture GetBackgroundToBind(Camera cam) { if (!IsBackgroundCopyNeeded(cam)) return cam.activeTexture; return cameraResources[cam].background; } /// /// Removes disabled/inactive cameras from cameraResources /// private void UpdateCameraList() { List toRemove = new List(); foreach (var camResource in cameraResources) { if (camResource.Key == null || (!camResource.Key.isActiveAndEnabled && camResource.Key.cameraType != CameraType.SceneView)) { toRemove.Add(camResource.Key); continue; } } foreach (var cam in toRemove) { if (cameraResources[cam].background) { cameraResources[cam].background.Release(); cameraResources[cam].background = null; } cameraResources.Remove(cam); } } void UpdateCameraResolution(Camera cam) { Vector2Int cameraResolution = new Vector2Int(cam.pixelWidth, cam.pixelHeight); Vector2Int cameraResolutionDownscaled = ApplyDownscaleFactor(cameraResolution); camResolutions[cam] = cameraResolutionDownscaled; } /// /// Update Native textures for a given camera /// /// Camera public bool UpdateNativeTextures(Camera cam) { UpdateCameraList(); Vector2Int cameraResolution = new Vector2Int(cam.pixelWidth, cam.pixelHeight); Vector2Int textureResolution = GetRequiredTextureResolution(); int pixelCount = textureResolution.x * textureResolution.y; if (!cameras.Contains(cam)) { // add camera to list cameras.Add(cam); } int CameraID = cameras.IndexOf(cam); bool isGlobalTexturesDirty = false; bool isCameraDirty = cameraResources[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 cameraResources[cam].background, cameraResolution, false, FilterMode.Point, 0, RenderTextureFormat.ARGBHalf) || isCameraDirty; #endif } else { isCameraDirty = CreateTexture(ref cameraResources[cam].background, cameraResolution, false, FilterMode.Point, 0, RenderTextureFormat.RGB111110Float) || isCameraDirty; } } else { if (cameraResources[cam].background != null) { isCameraDirty = true; cameraResources[cam].background.Release(); cameraResources[cam].background = null; } } isGlobalTexturesDirty = CreateTexture(ref depth, textureResolution, true, defaultFilter, 32, RenderTextureFormat.Depth) || isGlobalTexturesDirty; isGlobalTexturesDirty = CreateTexture(ref color0, textureResolution, true, FilterMode.Point, 0, RenderTextureFormat.ARGBFloat, true) || isGlobalTexturesDirty; if (visualizeSceneSDF) { isGlobalTexturesDirty = CreateTexture(ref SDFRender, textureResolution, true, FilterMode.Point, 0, RenderTextureFormat.ARGBHalf, true) || isGlobalTexturesDirty; } else { ZibraLiquidGPUGarbageCollector.SafeRelease(SDFRender); SDFRender = null; } isGlobalTexturesDirty = CreateTexture(ref color1, textureResolution, true, defaultFilter, 0, RenderTextureFormat.ARGBFloat, true) || isGlobalTexturesDirty; if (isGlobalTexturesDirty || isCameraDirty || forceTextureUpdate) { if (isGlobalTexturesDirty || forceTextureUpdate) { foreach (var camera in cameraResources) { camera.Value.isDirty = true; } CurrentTextureResolution = textureResolution; ZibraLiquidGPUGarbageCollector.SafeRelease(atomicGrid); atomicGrid = null; ZibraLiquidGPUGarbageCollector.SafeRelease(JFAGrid0); JFAGrid0 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(JFAGrid1); JFAGrid1 = null; atomicGrid = new ComputeBuffer(pixelCount * 2, sizeof(uint)); JFAGrid0 = new ComputeBuffer(pixelCount, sizeof(uint)); JFAGrid1 = new ComputeBuffer(pixelCount, sizeof(uint)); } cameraResources[cam].isDirty = false; #if ZIBRA_LIQUID_DEBUG atomicGrid.name = "atomicGrid"; JFAGrid0.name = "JFAGrid0"; JFAGrid1.name = "JFAGrid1"; #endif var registerRenderResourcesBridgeParams = new RegisterRenderResourcesBridgeParams(); registerRenderResourcesBridgeParams.Depth = MakeTextureNativeBridge(depth); registerRenderResourcesBridgeParams.Color0 = MakeTextureNativeBridge(color0); registerRenderResourcesBridgeParams.Color1 = MakeTextureNativeBridge(color1); registerRenderResourcesBridgeParams.AtomicGrid = GetNativePtr(atomicGrid); registerRenderResourcesBridgeParams.JFA0 = GetNativePtr(JFAGrid0); registerRenderResourcesBridgeParams.JFA1 = GetNativePtr(JFAGrid1); registerRenderResourcesBridgeParams.SDFRender = MakeTextureNativeBridge(SDFRender); IntPtr nativeRegisterRenderResourcesBridgeParams = Marshal.AllocHGlobal(Marshal.SizeOf(registerRenderResourcesBridgeParams)); Marshal.StructureToPtr(registerRenderResourcesBridgeParams, nativeRegisterRenderResourcesBridgeParams, true); solverCommandBuffer.Clear(); ZibraLiquidBridge.SubmitInstanceEvent(solverCommandBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.RegisterRenderResources, nativeRegisterRenderResourcesBridgeParams); ZibraLiquidBridge.SubmitInstanceEvent(solverCommandBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.InitializeGraphicsPipeline); Graphics.ExecuteCommandBuffer(solverCommandBuffer); forceTextureUpdate = false; } return isGlobalTexturesDirty || isCameraDirty; } /// /// Render the liquid from the native plugin /// /// Command Buffer to add the rendering commands to public void RenderLiquidNative(CommandBuffer cmdBuffer, Camera cam, Rect? viewport = null) { ForceCloseCommandEncoder(cmdBuffer); ZibraLiquidBridge.SubmitInstanceEvent(cmdBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.SetCameraParams, camNativeParams[cam]); ZibraLiquidBridge.SubmitInstanceEvent(cmdBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.UpdateMeshRenderGlobalParameters, camMeshRenderParams[cam]); ZibraLiquidBridge.SubmitInstanceEvent(cmdBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.Draw); } public void RenderLiquidMain(CommandBuffer cmdBuffer, Camera cam, Rect? viewport = null) { switch (ActiveRenderingMode) { case RenderingMode.ParticleRender: RenderLiquidParticles(cmdBuffer, cam, viewport); break; case RenderingMode.MeshRender: RenderLiquidMesh(cmdBuffer, cam, viewport); break; default: Debug.LogError("Unknown Rendering mode"); break; } } /// /// Render the liquid surface to currently bound render target /// Used for URP where we can't change render targets /// public void RenderLiquidParticles(CommandBuffer cmdBuffer, Camera cam, Rect? viewport = null) { Vector2Int cameraRenderResolution = new Vector2Int(cam.pixelWidth, cam.pixelHeight); cameraRenderResolution = ApplyDownscaleFactor(cameraRenderResolution); Material CurrentMaterial = cameraResources[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.DrawProcedural(transform.localToWorldMatrix, CurrentMaterial, 0, MeshTopology.Triangles, 6); } /// /// 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 /// public void UpscaleLiquidDirect(CommandBuffer cmdBuffer, Camera cam, RenderTargetIdentifier? sourceColorTexture = null, RenderTargetIdentifier? sourceDepthTexture = null, Rect? viewport = null) { Material CurrentUpscaleMaterial = cameraResources[cam].upscaleMaterial.currentMaterial; cmdBuffer.SetViewport(new Rect(0, 0, cam.pixelWidth, cam.pixelHeight)); if (sourceColorTexture == null) { cmdBuffer.SetGlobalTexture("ShadedLiquid", color1); } else { cmdBuffer.SetGlobalTexture("ShadedLiquid", sourceColorTexture.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 /// Camera public void RenderFluid(CommandBuffer cmdBuffer, Camera cam, RenderTargetIdentifier? renderTargetParam = null, Rect? viewport = null) { RenderTargetIdentifier renderTarget = renderTargetParam ?? new RenderTargetIdentifier(BuiltinRenderTextureType.CameraTarget); // Render fluid to temporary RenderTexture if downscale enabled // Otherwise render straight to final RenderTexture if (EnableDownscale) { // color1 is used internally by the plugin but at this point texture can be temporarily reused cmdBuffer.SetRenderTarget(color1); cmdBuffer.ClearRenderTarget(true, true, Color.clear); } else { cmdBuffer.SetRenderTarget(renderTarget); } RenderLiquidMain(cmdBuffer, cam, viewport); // If downscale enabled then we need to blend it on top of final RenderTexture if (EnableDownscale) { cmdBuffer.SetRenderTarget(renderTarget); 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 /// Camera public void RenderLiquidMesh(CommandBuffer cmdBuffer, Camera cam, Rect? viewport = null) { Vector2Int cameraRenderResolution = new Vector2Int(cam.pixelWidth, cam.pixelHeight); cameraRenderResolution = ApplyDownscaleFactor(cameraRenderResolution); Material CurrentMaterial = cameraResources[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); } } #if UNITY_IOS && !UNITY_EDITOR if (EnableDownscale && IsBackgroundCopyNeeded(cam)) { CurrentMaterial.EnableKeyword("FLIP_BACKGROUND"); } else { CurrentMaterial.DisableKeyword("FLIP_BACKGROUND"); } #endif 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 } else { if (usingCustomReflectionProbe) { CurrentMaterial.EnableKeyword("CUSTOM_REFLECTION_PROBE"); } else { CurrentMaterial.DisableKeyword("CUSTOM_REFLECTION_PROBE"); } } cmdBuffer.SetGlobalTexture("SDFRender", SDFRender); cmdBuffer.DrawProcedural(transform.localToWorldMatrix, CurrentMaterial, 0, MeshTopology.Triangles, 6); } /// /// Update the camera parameters for the particle renderer /// /// Camera /// public void UpdateCamera(Camera cam) { Vector2Int resolution = new Vector2Int(cam.pixelWidth, cam.pixelHeight); resolution = ApplyDownscaleFactor(resolution); Material CurrentMaterial = cameraResources[cam].liquidMaterial.currentMaterial; Material CurrentUpscaleMaterial = cameraResources[cam].upscaleMaterial.currentMaterial; Matrix4x4 Projection = GL.GetGPUProjectionMatrix(cam.projectionMatrix, true); Matrix4x4 ProjectionInverse = Projection.inverse; Matrix4x4 View = cam.worldToCameraMatrix; Matrix4x4 ViewProjection = Projection * View; Matrix4x4 ViewProjectionInverse = ViewProjection.inverse; 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); meshRenderGlobalParams.LiquidIOR = materialParameters.IndexOfRefraction; meshRenderGlobalParams.RayMarchIsoSurface = renderingParameters.RayMarchIsoSurface; meshRenderGlobalParams.ContainerPosition = containerPos; meshRenderGlobalParams.RayMarchMaxSteps = renderingParameters.RayMarchMaxSteps; meshRenderGlobalParams.RayMarchStepSize = renderingParameters.RayMarchStepSize; meshRenderGlobalParams.RayMarchStepFactor = renderingParameters.RayMarchStepFactor; Marshal.StructureToPtr(meshRenderGlobalParams, camMeshRenderParams[cam], true); CurrentMaterial.SetMatrix("ProjectionInverse", cameraRenderParams.ProjectionInverse); CurrentMaterial.SetMatrix("ViewProjectionInverse", cameraRenderParams.ViewProjectionInverse); CurrentMaterial.SetMatrix("EyeRayCameraCoeficients", cameraRenderParams.EyeRayCameraCoeficients); // update the data at the pointer Marshal.StructureToPtr(cameraRenderParams, camNativeParams[cam], true); Vector2Int cameraRenderResolution = new Vector2Int(cam.pixelWidth, cam.pixelHeight); cameraRenderResolution = ApplyDownscaleFactor(cameraRenderResolution); Vector2 textureScale = new Vector2((float)cameraRenderResolution.x / CurrentTextureResolution.x, (float)cameraRenderResolution.y / CurrentTextureResolution.y); CurrentMaterial.SetVector("TextureScale", textureScale); if (renderingParameters.UnderwaterRender) { CurrentMaterial.EnableKeyword("UNDERWATER_RENDER"); } else { CurrentMaterial.DisableKeyword("UNDERWATER_RENDER"); } if (EnableDownscale) { CurrentUpscaleMaterial.SetVector("TextureScale", textureScale); } } /// /// Update render parameters for a given camera /// /// Camera public 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(meshRenderGlobalParams)); } } public void UpdateNativeRenderParams() { // Needs to be specifically in this place, to make sure that render mode in Unity and in native plugin are // in sync ActiveRenderingMode = CurrentRenderingMode; particleDiameter = materialParameters.ParticleScale * CellSize / (float)Math.Pow(solverParameters.ParticleDensity, 0.333f); renderParams.BlurRadius = materialParameters.BlurRadius; renderParams.Diameter = particleDiameter; #if ZIBRA_LIQUID_DEBUG renderParams.NeuralSamplingDistance = materialParameters.NeuralSamplingDistance; renderParams.SDFDebug = materialParameters.SDFDebug; #endif renderParams.RenderingMode = (int)ActiveRenderingMode; renderParams.VertexOptimizationIterations = renderingParameters.VertexOptimizationIterations; renderParams.MeshOptimizationIterations = renderingParameters.MeshOptimizationIterations; renderParams.JFAIterations = renderingParameters.AdditionalJFAIterations; renderParams.DualContourIsoValue = renderingParameters.DualContourIsoSurfaceLevel; renderParams.MeshOptimizationStep = renderingParameters.MeshOptimizationStep; GCHandle gcparamBuffer = GCHandle.Alloc(renderParams, GCHandleType.Pinned); solverCommandBuffer.Clear(); ZibraLiquidBridge.SubmitInstanceEvent(solverCommandBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.SetRenderParameters, gcparamBuffer.AddrOfPinnedObject()); Graphics.ExecuteCommandBuffer(solverCommandBuffer); gcparamBuffer.Free(); } /// /// Rendering callback which is called by every camera in the scene /// /// Camera public void RenderCallBack(Camera cam) { if (cam.cameraType == CameraType.Preview || cam.cameraType == CameraType.Reflection || cam.cameraType == CameraType.VR) { return; } UpdateCameraResolution(cam); // Need at least 2 simulation frames to start rendering if (!IsRenderingEnabled()) { return; } if (!cameraResources.ContainsKey(cam)) { cameraResources[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.SRP) || ((cam.cullingMask & (1 << this.gameObject.layer)) != 0); if (!isEnabled || !visibleInCamera || materialParameters.FluidMaterial == null || (EnableDownscale && materialParameters.UpscaleMaterial == null)) { if (cameraCBs.ContainsKey(cam)) { CameraEvent cameraEvent = (cam.actualRenderingPath == RenderingPath.Forward) ? CameraEvent.BeforeForwardAlpha : CameraEvent.AfterLighting; cam.RemoveCommandBuffer(cameraEvent, cameraCBs[cam]); cameraCBs[cam].Clear(); cameraCBs.Remove(cam); } return; } bool isDirty = SetMaterialParams(cam); isDirty = UpdateNativeTextures(cam) || isDirty; isDirty = !cameraCBs.ContainsKey(cam) || isDirty; #if UNITY_EDITOR isDirty = isDirty || ForceRepaint; #endif InitializeNativeCameraParams(cam); UpdateCamera(cam); if (RenderPipelineDetector.GetRenderPipelineType() != RenderPipelineDetector.RenderPipeline.SRP) { #if UNITY_PIPELINE_HDRP || UNITY_PIPELINE_URP // upload camera parameters solverCommandBuffer.Clear(); ZibraLiquidBridge.SubmitInstanceEvent(solverCommandBuffer, CurrentInstanceID, ZibraLiquidBridge.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, cameraResources[cam].background); } RenderLiquidNative(renderCommandBuffer, cam); RenderFluid(renderCommandBuffer, cam); } } } private void StepPhysics() { solverCommandBuffer.Clear(); ForceCloseCommandEncoder(solverCommandBuffer); ZibraLiquidBridge.SubmitInstanceEvent(solverCommandBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.ClearSDFAndID); SetFluidParameters(); manipulatorManager.UpdateDynamic(this, timestep / simTimePerSec); // Update fluid parameters Marshal.StructureToPtr(fluidParameters, NativeFluidData, true); ZibraLiquidBridge.SubmitInstanceEvent(solverCommandBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.UpdateLiquidParameters, NativeFluidData); if (manipulatorManager.Elements > 0) { // Update manipulator parameters // Interop magic long LongPtr = NativeManipData.ToInt64(); // Must work both on x86 and x64 for (int I = 0; I < manipulatorManager.ManipulatorParams.Count; I++) { IntPtr Ptr = new IntPtr(LongPtr); Marshal.StructureToPtr(manipulatorManager.ManipulatorParams[I], Ptr, true); LongPtr += Marshal.SizeOf(typeof(Manipulators.ZibraManipulatorManager.ManipulatorParam)); } ZibraLiquidBridge.SubmitInstanceEvent(solverCommandBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.UpdateManipulatorParameters, NativeManipData); } // execute simulation ZibraLiquidBridge.SubmitInstanceEvent(solverCommandBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.StepPhysics); Graphics.ExecuteCommandBuffer(solverCommandBuffer); // update internal time simulationInternalTime += timestep; simulationInternalFrame++; } void UpdateManipulatorStatistics() { #if ZIBRA_LIQUID_PAID_VERSION /// ManipulatorStatistics GPUReadback if (manipulatorManager.Elements > 0) { UInt32 size = (UInt32)manipulatorManager.Elements * STATISTICS_PER_MANIPULATOR; IntPtr readbackData = ZibraLiquidBridge.GPUReadbackGetData(CurrentInstanceID, size * sizeof(Int32)); if (readbackData != IntPtr.Zero) { Int32[] Stats = new Int32[size]; Marshal.Copy(readbackData, Stats, 0, (Int32)size); manipulatorManager.UpdateStatistics(Stats, manipulators, solverParameters, sdfColliders); } } #endif } // stability calibration curve fit private float DivergenceDecayCurve(float x) { float a = (0.177f - 0.85f * x + 9.0f * x * x) / 1.8f; return 1.8f * a / (a + 1); } private void SetFluidParameters() { solverParameters.ValidateGravity(); containerPos = transform.position; fluidParameters.GridSize = GridSize; fluidParameters.ContainerScale = containerSize; fluidParameters.NodeCount = numNodes; fluidParameters.ContainerPos = containerPos; fluidParameters.TimeStep = timestep; fluidParameters.Gravity = solverParameters.Gravity / 100.0f; fluidParameters.SimulationFrame = simulationInternalFrame; fluidParameters.DensityBlurRadius = materialParameters.FluidSurfaceBlur; fluidParameters.LiquidIsosurfaceThreshold = renderingParameters.IsoSurfaceLevel; fluidParameters.VertexOptimizationStep = renderingParameters.VertexOptimizationStep; fluidParameters.AffineAmmount = 4.0f * (1.0f - solverParameters.Viscosity); // ParticleTranslation set by native plugin fluidParameters.VelocityLimit = solverParameters.MaximumVelocity; fluidParameters.LiquidStiffness = solverParameters.FluidStiffness; fluidParameters.RestDensity = solverParameters.ParticleDensity; #if ZIBRA_LIQUID_PAID_VERSION fluidParameters.SurfaceTension = solverParameters.SurfaceTension; #endif fluidParameters.AffineDivergenceDecay = 1.0f; #if ZIBRA_LIQUID_PAID_VERSION fluidParameters.MinimumVelocity = solverParameters.MinimumVelocity; #endif // BlurNormalizationConstant set by native plugin fluidParameters.MaxParticleCount = MaxNumParticles; fluidParameters.VisualizeSDF = visualizeSceneSDF ? 1 : 0; } /// /// Disable fluid render for a given camera /// public void DisableForCamera(Camera cam) { CameraEvent cameraEvent = cam.actualRenderingPath == RenderingPath.Forward ? CameraEvent.AfterSkybox : CameraEvent.AfterLighting; cam.RemoveCommandBuffer(cameraEvent, cameraCBs[cam]); cameraCBs[cam].Dispose(); cameraCBs.Remove(cam); } protected void ClearRendering() { Camera.onPreRender -= RenderCallBack; foreach (var cam in cameraCBs) { if (cam.Key != null) { cam.Value.Clear(); } } cameraCBs.Clear(); cameras.Clear(); // free allocated memory foreach (var data in camNativeParams) { Marshal.FreeHGlobal(data.Value); } foreach (var resource in cameraResources) { if (resource.Value.background != null) { resource.Value.background.Release(); resource.Value.background = null; } } cameraResources.Clear(); ZibraLiquidGPUGarbageCollector.SafeRelease(atomicGrid); atomicGrid = null; ZibraLiquidGPUGarbageCollector.SafeRelease(color0); color0 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(color1); color1 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(SDFRender); SDFRender = null; ZibraLiquidGPUGarbageCollector.SafeRelease(JFAGrid0); JFAGrid0 = null; ZibraLiquidGPUGarbageCollector.SafeRelease(JFAGrid1); JFAGrid1 = 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(SDFGridTexture); SDFGridTexture = null; ZibraLiquidGPUGarbageCollector.SafeRelease(EmbeddingsTexture); EmbeddingsTexture = null; camNativeParams.Clear(); } protected void ClearSolver() { if (solverCommandBuffer != null) { ZibraLiquidBridge.SubmitInstanceEvent(solverCommandBuffer, CurrentInstanceID, ZibraLiquidBridge.EventID.ReleaseResources); Graphics.ExecuteCommandBuffer(solverCommandBuffer); } if (solverCommandBuffer != null) { solverCommandBuffer.Release(); solverCommandBuffer = null; } ZibraLiquidGPUGarbageCollector.SafeRelease(PositionMass); PositionMass = null; ZibraLiquidGPUGarbageCollector.SafeRelease(PositionRadius); PositionRadius = 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(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(GridSDF); GridSDF = null; ZibraLiquidGPUGarbageCollector.SafeRelease(ParticleNumber); ParticleNumber = null; ZibraLiquidGPUGarbageCollector.SafeRelease(DynamicManipulatorData); DynamicManipulatorData = null; ZibraLiquidGPUGarbageCollector.SafeRelease(ConstManipulatorData); ConstManipulatorData = null; ZibraLiquidGPUGarbageCollector.SafeRelease(ManipulatorStatistics); ManipulatorStatistics = null; ZibraLiquidGPUGarbageCollector.SafeRelease(Counters); Counters = null; Marshal.FreeHGlobal(NativeManipData); NativeManipData = IntPtr.Zero; Marshal.FreeHGlobal(NativeFluidData); NativeFluidData = IntPtr.Zero; CurrentTextureResolution = new Vector2Int(0, 0); GridSize = new Vector3Int(0, 0, 0); activeParticleNumber = 0; numNodes = 0; particleDiameter = 0.0f; simulationInternalFrame = 0; simulationInternalTime = 0.0f; timestep = 0.0f; camResolutions.Clear(); initialized = false; // 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; } } } } public float GetParticleSize() { return (float)(CellSize / Math.Pow(solverParameters.ParticleDensity, 1.0f / 3.0f)); } public ReadOnlyCollection GetColliderList() { return sdfColliders.AsReadOnly(); } public bool HasCollider(SDFCollider collider) { return sdfColliders.Contains(collider); } public void AddCollider(SDFCollider collider) { if (initialized) { Debug.LogWarning("We don't yet support changing number of manipulators/colliders at runtime."); return; } if (!sdfColliders.Contains(collider)) { #if !ZIBRA_LIQUID_PAID_VERSION if (manipulators.Count >= 5) { Debug.LogWarning("Can't add additional collider to liquid instance. Free version can only have up to 5 colliders."); return; } #endif sdfColliders.Add(collider); sdfColliders.Sort(new SDFColliderCompare()); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(this); #endif } } public void RemoveCollider(SDFCollider collider) { if (initialized) { Debug.LogWarning("We don't yet support changing number of manipulators/colliders at runtime."); return; } if (sdfColliders.Contains(collider)) { sdfColliders.Remove(collider); sdfColliders.Sort(new SDFColliderCompare()); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(this); #endif } } public bool HasEmitter() { foreach (var manipulator in manipulators) { if (manipulator.ManipType == Manipulator.ManipulatorType.Emitter) { return true; } } return false; } public ReadOnlyCollection GetManipulatorList() { return manipulators.AsReadOnly(); } public bool HasManipulator(Manipulator manipulator) { return manipulators.Contains(manipulator); } 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)) { #if !ZIBRA_LIQUID_PAID_VERSION foreach (var manip in manipulators) { if (manip.ManipType == manipulator.ManipType) { Debug.LogWarning("Can't add additional manipulator to liquid instance. Free version can only have single emitter and single force field."); return; } } #endif manipulators.Add(manipulator); manipulators.Sort(new ManipulatorCompare()); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(this); #endif } } 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 } } #if UNITY_EDITOR public void OnValidate() { transform.rotation = Quaternion.identity; transform.localScale = Vector3.one; containerSize[0] = Math.Max(containerSize[0], 1e-3f); containerSize[1] = Math.Max(containerSize[1], 1e-3f); containerSize[2] = Math.Max(containerSize[2], 1e-3f); CellSize = Math.Max(containerSize.x, Math.Max(containerSize.y, containerSize.z)) / gridResolution; 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); } if (sdfColliders != null) { int removed = sdfColliders.RemoveAll(item => item == null); if (removed > 0) { sdfColliders.Sort(new SDFColliderCompare()); UnityEditor.EditorUtility.SetDirty(this); } } if (manipulators != null) { int removed = manipulators.RemoveAll(item => item == null); if (removed > 0) { manipulators.Sort(new ManipulatorCompare()); UnityEditor.EditorUtility.SetDirty(this); } } #if ZIBRA_LIQUID_PAID_VERSION if (BakedInitialStateAsset) { int bakedLiquidHeader = BitConverter.ToInt32(BakedInitialStateAsset.bytes, 0); if (bakedLiquidHeader != BAKED_LIQUID_HEADER_VALUE) { BakedInitialStateAsset = null; UnityEditor.EditorUtility.SetDirty(this); } } #endif #if !ZIBRA_LIQUID_PAID_VERSION // Limit manipulator count to 1 bool emitterFound = false; bool forceFieldFound = false; List manipsToRemove = new List(); foreach (var manip in manipulators) { if (manip.ManipType == Manipulator.ManipulatorType.Emitter) { if (!emitterFound) { emitterFound = true; } else { manipsToRemove.Add(manip); } } if (manip.ManipType == Manipulator.ManipulatorType.ForceField) { if (!forceFieldFound) { forceFieldFound = true; } else { manipsToRemove.Add(manip); } } } if (manipsToRemove.Count != 0) { Debug.LogWarning("Too many manipulators, some will be removed. Free version limited to 1 emitter and 1 force field."); foreach (var manip in manipsToRemove) { RemoveManipulator(manip); } } if (sdfColliders.Count > 5) { Debug.LogWarning( "Too many colliders for free version of Zibra Liquids, some will be removed. Free version limited to 5 SDF colliders."); sdfColliders.RemoveRange(5, sdfColliders.Count - 5); } #endif } #endif protected void OnApplicationQuit() { // On quit we need to destroy liquid before destroying any colliders/manipulators OnDisable(); } public void StopSolver() { if (!initialized) { return; } initialized = false; ClearRendering(); ClearSolver(); isEnabled = 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(); } // dispose the objects protected void OnDisable() { StopSolver(); } 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; } #if ZIBRA_LIQUID_PAID_VERSION private BakedInitialState ConvertBytesToInitialState(byte[] data) { int startIndex = 0; int header = ByteArrayToInt(data, ref startIndex); if (header != BAKED_LIQUID_HEADER_VALUE) { 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]; 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 = 1.0f; } 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); } #if UNITY_EDITOR /// /// 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 - ZibraLiquidBridge.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); ForceRepaint = true; return initialStateData; } #endif /// /// Apply currently set initial conditions /// protected void ApplyInitialState() { switch (InitialState) { case InitialStateType.NoParticles: fluidParameters.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); fluidParameters.ParticleCount = initialStateData.ParticleCount; } else { fluidParameters.ParticleCount = 0; } break; } } #endif 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; } } }