Implementierung der räumlichen Lookup in der Unity -Fluid -Simulation
Posted: 21 Feb 2025, 00:55
Ich versuche, meine Fluidsimulation zu optimieren, indem ich einen räumlichen Suchalgorithmus implementieren habe, über den ich online gelesen habe, aber ich habe Schwierigkeiten, es mit Unitys Jobsystem zu arbeiten. < /p>
Ich habe es Sehr begrenzte Erfahrungen mit Unitys Jobsystem und räumlichen Suchalgorithmen, daher bin ich mir nicht ganz sicher, was einige Fehler verursacht. Ich bekomme viele Lecks im Konsolenprotokoll < /p>
Ich glaube, ich habe meine Spatialllookup -Klasse mit dem Jobsystem von Unity kompatibel gemacht, aber das Hauptproblem sind die Lecks, die ich nicht finden und beheben kann < /p>
Hier ist der Code für mein Jobsystem und mein Spatialllookup- und Zellklassen < /p>
Klassen: < /p>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unity.Collections;
using Unity.Mathematics;
public class SpatialLookup
{
public NativeArray cells; // Array of cells for spatial indexing
private float cellSize;
public int gridHeight; // Number of rows
public int gridWidth; // Number of columns
public SpatialLookup(float smoothingRadius, int cameraWidth, int cameraHeight, int maxMoleculesPerCell, Allocator allocator)
{
this.cellSize = smoothingRadius;
this.gridWidth = (int)Math.Ceiling(cameraWidth / cellSize); // Calculated number of columns
this.gridHeight = (int)Math.Ceiling(cameraHeight / cellSize); // Calculated number of rows
cells = new NativeArray(gridWidth * gridHeight, allocator); // Create cells array
// Create a cell for each space in the array
for (int i = 0; i < cells.Length; i++)
{
cells = new Cell(maxMoleculesPerCell, allocator);
}
}
public (int, int) GetCellIndex(float2 position)
{
// Calculate the cell a molecule belongs in based on its position
int cellX = (int)math.clamp(position.x / cellSize, 0, gridWidth - 1);
int cellY = (int)math.clamp(position.y / cellSize, 0, gridHeight - 1);
return (cellX, cellY);
}
public void AddMolecule(int index, float2 position)
{
var (cellX, cellY) = GetCellIndex(position); // Get cell index
if (IsValidCell(cellX, cellY)) // If valid...
{
cells[cellX + cellY * gridWidth].Add(index); // Add molecule index to the cell
}
}
public NativeArray GetNeighbouringMolecules(float2 position, Allocator allocator)
{
NativeList neighbours = new NativeList(allocator);
(int cellX, int cellY) = GetCellIndex(position);
// Loop through the neighbouring cells (3x3 around current cell)
for (int dx = -1; dx = 0 && cellY < gridHeight;
}
public void Dispose()
{
// Dispose resources for each cell so there are no leaks (bad)
for (int i = 0; i < cells.Length; i++)
{
cells.MoleculeIndices.Dispose();
}
if (cells.IsCreated)
cells.Dispose(); // Dispose cell array
}
}
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using System.Collections.Generic;
public struct Cell
{
public NativeArray MoleculeIndices; // Holds indicies of molecules (from molecules list) in this cell
public int Count; // Holds the number of molecules currently in the cell
public Cell(int maxSize, Allocator allocator)
{
MoleculeIndices = new NativeArray(maxSize, allocator);
Count = 0;
}
// woah we learnt this in lesson
public void Add(int index)
{
if (Count < MoleculeIndices.Length)
{
MoleculeIndices[Count++] = index;
}
}
public void Clear()
{
Count = 0; // Clear cell
}
}
< /code>
Jobsystem < /p>
int maxNeighboursPerCell = 1000;
public float smoothingRadius = 2f;
public float restDensity = 1f;
public float pressureMultiplier = 1f;
public float deltaTime;
///
/// Calculates the repulsive force between the molecules
///
public void CalculateForces()
{
SpatialLookup spatialLookup = new SpatialLookup(smoothingRadius, 18, 10, maxNeighboursPerCell, Allocator.Temp);
int moleculeCount = molecules.Count;
if (molecules.Count == 0) { return; }
deltaTime = Time.deltaTime;
// Create the native arrays for the job. I use native arrays to take advantage of the multithreading used in Jobs
NativeArray positions = new(moleculeCount, Allocator.TempJob);
NativeArray velocities = new(moleculeCount, Allocator.TempJob);
NativeArray densities = new(moleculeCount, Allocator.TempJob);
NativeArray pressures = new(moleculeCount, Allocator.TempJob);
NativeArray neighbourMoleculesPos = new(moleculeCount, Allocator.TempJob);
NativeArray neighbourArray = new NativeArray(moleculeCount * maxNeighboursPerCell, Allocator.TempJob); // Or other appropriate allocator
// Copy data to the arrays
for (int i = 0; i < moleculeCount; i++)
{
positions = molecules.position;
velocities = molecules.velocity;
}
GetNeighbourMoleculesJob neighbourJob = new()
{
positions = positions,
neighbouringMoleculesPos = neighbourMoleculesPos,
spatialLookup = spatialLookup,
neighbouringMoleculesInt = neighbourArray,
maxNeighboursPerCell = maxNeighboursPerCell,
};
JobHandle neighbourHandle = neighbourJob.Schedule(moleculeCount, 64);
CalculateDensitiesJob densityJob = new()
{
positions = positions,
velocities = velocities,
densities = densities,
smoothingRadius = smoothingRadius,
deltaTime = deltaTime
};
JobHandle densityHandle = densityJob.Schedule(moleculeCount, 64, neighbourHandle);
CalculatePressureForcesJob pressureJob = new()
{
positions = positions,
velocities = velocities,
densities = densities,
pressures = pressures,
smoothingRadius = smoothingRadius,
restDensity = restDensity,
pressureMultiplier = pressureMultiplier,
deltaTime = deltaTime
};
JobHandle pressureHandle = pressureJob.Schedule(moleculeCount, 64, densityHandle);
pressureHandle.Complete();
for (int i = 0; i < moleculeCount; i++)
{
molecules.ApplyForce(pressures);
}
positions.Dispose();
velocities.Dispose();
densities.Dispose();
pressures.Dispose();
neighbourMoleculesPos.Dispose();
neighbourArray.Dispose();
}
public static float KernelSmoother(float x, float r)
{
if (x >= r) return 0;
float volume = (math.PI * math.pow(r, 4) / 6);
return (r - x) * (r - x) / volume;
}
public static float KernelSmootherGradient(float x, float r)
{
if (x >= r) return 0;
float scale = 12 / (math.pow(r, 4) * math.PI);
return (x - r) * scale;
}
}
[BurstCompile]
struct CalculateDensitiesJob : IJobParallelFor
{
[ReadOnly] public NativeArray positions;
[ReadOnly] public NativeArray velocities;
[WriteOnly] public NativeArray densities;
public float smoothingRadius;
public float deltaTime;
public void Execute(int i)
{
float density = 0f;
const float mass = 1f;
float2 m1Pos = positions + velocities * deltaTime;
for (int j = 0; j < positions.Length; j++)
{
float dst = math.length(positions[j] - m1Pos);
float influence = MoleculeManager.KernelSmoother(dst, smoothingRadius);
density += mass * influence;
}
densities[i] = density;
}
}
[BurstCompile]
struct CalculatePressureForcesJob : IJobParallelFor
{
[ReadOnly] public NativeArray positions;
[ReadOnly] public NativeArray velocities;
[ReadOnly] public NativeArray densities;
public NativeArray pressures;
public float smoothingRadius;
const float mass = 1f;
public float restDensity;
public float pressureMultiplier;
public float deltaTime;
public void Execute(int i)
{
float2 pressureForce = float2.zero;
float2 acceleration = pressures[i] / densities[i]; // Approximate acceleration
float2 predictedVelocity = velocities[i] + acceleration * deltaTime;
float2 m1Pos = positions[i] + predictedVelocity * deltaTime;
for (int j = 0; j < positions.Length; j++)
{
if (i == j) continue;
float dst = math.length(m1Pos - positions[j]);
if (dst == 0) continue;
float2 dir = (m1Pos - positions[j]) / dst;
float slope = MoleculeManager.KernelSmootherGradient(dst, smoothingRadius);
float density = densities[i];
float sharedPressure = CalculateSharedPressure(density, densities[j]);
pressureForce += sharedPressure * mass * slope * dir / density;
}
pressures[i] = pressureForce;
}
private readonly float ConvertDensityToPressure(float density, float restDensity, float pressureMultiplier)
{
float densityOffset = density - restDensity;
float pressure = densityOffset * pressureMultiplier;
return pressure;
}
private readonly float CalculateSharedPressure(float density_i, float density_j)
{
float pressure_i = -ConvertDensityToPressure(density_i, restDensity, pressureMultiplier);
float pressure_j = -ConvertDensityToPressure(density_j, restDensity, pressureMultiplier);
return (pressure_i + pressure_j) / 2;
}
}
[BurstCompile]
struct GetNeighbourMoleculesJob : IJobParallelFor
{
[ReadOnly] public NativeArray positions;
[WriteOnly] public NativeArray neighbouringMoleculesPos;
[ReadOnly] public SpatialLookup spatialLookup;
public NativeArray neighbouringMoleculesInt;
public int maxNeighboursPerCell;
public void Execute(int i)
{
// Get neighbouring molecules for the position
int count = 0; // Track how many neighbouring molecules are found
NativeArray tempNeighbours = spatialLookup.GetNeighbouringMolecules(positions[i], Allocator.Temp);
count = tempNeighbours.Length;
// Store neighbouring molecule positions
for (int j = 0; j < count; j++)
{
neighbouringMoleculesPos[i * maxNeighboursPerCell + j] = positions[tempNeighbours[j]];
}
}
}
< /code>
Wenn es eine bessere Möglichkeit gibt, dies zu handhaben, wäre das sehr geschätzt. Danke!
Ich habe es Sehr begrenzte Erfahrungen mit Unitys Jobsystem und räumlichen Suchalgorithmen, daher bin ich mir nicht ganz sicher, was einige Fehler verursacht. Ich bekomme viele Lecks im Konsolenprotokoll < /p>
Ich glaube, ich habe meine Spatialllookup -Klasse mit dem Jobsystem von Unity kompatibel gemacht, aber das Hauptproblem sind die Lecks, die ich nicht finden und beheben kann < /p>
Hier ist der Code für mein Jobsystem und mein Spatialllookup- und Zellklassen < /p>
Klassen: < /p>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unity.Collections;
using Unity.Mathematics;
public class SpatialLookup
{
public NativeArray cells; // Array of cells for spatial indexing
private float cellSize;
public int gridHeight; // Number of rows
public int gridWidth; // Number of columns
public SpatialLookup(float smoothingRadius, int cameraWidth, int cameraHeight, int maxMoleculesPerCell, Allocator allocator)
{
this.cellSize = smoothingRadius;
this.gridWidth = (int)Math.Ceiling(cameraWidth / cellSize); // Calculated number of columns
this.gridHeight = (int)Math.Ceiling(cameraHeight / cellSize); // Calculated number of rows
cells = new NativeArray(gridWidth * gridHeight, allocator); // Create cells array
// Create a cell for each space in the array
for (int i = 0; i < cells.Length; i++)
{
cells = new Cell(maxMoleculesPerCell, allocator);
}
}
public (int, int) GetCellIndex(float2 position)
{
// Calculate the cell a molecule belongs in based on its position
int cellX = (int)math.clamp(position.x / cellSize, 0, gridWidth - 1);
int cellY = (int)math.clamp(position.y / cellSize, 0, gridHeight - 1);
return (cellX, cellY);
}
public void AddMolecule(int index, float2 position)
{
var (cellX, cellY) = GetCellIndex(position); // Get cell index
if (IsValidCell(cellX, cellY)) // If valid...
{
cells[cellX + cellY * gridWidth].Add(index); // Add molecule index to the cell
}
}
public NativeArray GetNeighbouringMolecules(float2 position, Allocator allocator)
{
NativeList neighbours = new NativeList(allocator);
(int cellX, int cellY) = GetCellIndex(position);
// Loop through the neighbouring cells (3x3 around current cell)
for (int dx = -1; dx = 0 && cellY < gridHeight;
}
public void Dispose()
{
// Dispose resources for each cell so there are no leaks (bad)
for (int i = 0; i < cells.Length; i++)
{
cells.MoleculeIndices.Dispose();
}
if (cells.IsCreated)
cells.Dispose(); // Dispose cell array
}
}
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using System.Collections.Generic;
public struct Cell
{
public NativeArray MoleculeIndices; // Holds indicies of molecules (from molecules list) in this cell
public int Count; // Holds the number of molecules currently in the cell
public Cell(int maxSize, Allocator allocator)
{
MoleculeIndices = new NativeArray(maxSize, allocator);
Count = 0;
}
// woah we learnt this in lesson
public void Add(int index)
{
if (Count < MoleculeIndices.Length)
{
MoleculeIndices[Count++] = index;
}
}
public void Clear()
{
Count = 0; // Clear cell
}
}
< /code>
Jobsystem < /p>
int maxNeighboursPerCell = 1000;
public float smoothingRadius = 2f;
public float restDensity = 1f;
public float pressureMultiplier = 1f;
public float deltaTime;
///
/// Calculates the repulsive force between the molecules
///
public void CalculateForces()
{
SpatialLookup spatialLookup = new SpatialLookup(smoothingRadius, 18, 10, maxNeighboursPerCell, Allocator.Temp);
int moleculeCount = molecules.Count;
if (molecules.Count == 0) { return; }
deltaTime = Time.deltaTime;
// Create the native arrays for the job. I use native arrays to take advantage of the multithreading used in Jobs
NativeArray positions = new(moleculeCount, Allocator.TempJob);
NativeArray velocities = new(moleculeCount, Allocator.TempJob);
NativeArray densities = new(moleculeCount, Allocator.TempJob);
NativeArray pressures = new(moleculeCount, Allocator.TempJob);
NativeArray neighbourMoleculesPos = new(moleculeCount, Allocator.TempJob);
NativeArray neighbourArray = new NativeArray(moleculeCount * maxNeighboursPerCell, Allocator.TempJob); // Or other appropriate allocator
// Copy data to the arrays
for (int i = 0; i < moleculeCount; i++)
{
positions = molecules.position;
velocities = molecules.velocity;
}
GetNeighbourMoleculesJob neighbourJob = new()
{
positions = positions,
neighbouringMoleculesPos = neighbourMoleculesPos,
spatialLookup = spatialLookup,
neighbouringMoleculesInt = neighbourArray,
maxNeighboursPerCell = maxNeighboursPerCell,
};
JobHandle neighbourHandle = neighbourJob.Schedule(moleculeCount, 64);
CalculateDensitiesJob densityJob = new()
{
positions = positions,
velocities = velocities,
densities = densities,
smoothingRadius = smoothingRadius,
deltaTime = deltaTime
};
JobHandle densityHandle = densityJob.Schedule(moleculeCount, 64, neighbourHandle);
CalculatePressureForcesJob pressureJob = new()
{
positions = positions,
velocities = velocities,
densities = densities,
pressures = pressures,
smoothingRadius = smoothingRadius,
restDensity = restDensity,
pressureMultiplier = pressureMultiplier,
deltaTime = deltaTime
};
JobHandle pressureHandle = pressureJob.Schedule(moleculeCount, 64, densityHandle);
pressureHandle.Complete();
for (int i = 0; i < moleculeCount; i++)
{
molecules.ApplyForce(pressures);
}
positions.Dispose();
velocities.Dispose();
densities.Dispose();
pressures.Dispose();
neighbourMoleculesPos.Dispose();
neighbourArray.Dispose();
}
public static float KernelSmoother(float x, float r)
{
if (x >= r) return 0;
float volume = (math.PI * math.pow(r, 4) / 6);
return (r - x) * (r - x) / volume;
}
public static float KernelSmootherGradient(float x, float r)
{
if (x >= r) return 0;
float scale = 12 / (math.pow(r, 4) * math.PI);
return (x - r) * scale;
}
}
[BurstCompile]
struct CalculateDensitiesJob : IJobParallelFor
{
[ReadOnly] public NativeArray positions;
[ReadOnly] public NativeArray velocities;
[WriteOnly] public NativeArray densities;
public float smoothingRadius;
public float deltaTime;
public void Execute(int i)
{
float density = 0f;
const float mass = 1f;
float2 m1Pos = positions + velocities * deltaTime;
for (int j = 0; j < positions.Length; j++)
{
float dst = math.length(positions[j] - m1Pos);
float influence = MoleculeManager.KernelSmoother(dst, smoothingRadius);
density += mass * influence;
}
densities[i] = density;
}
}
[BurstCompile]
struct CalculatePressureForcesJob : IJobParallelFor
{
[ReadOnly] public NativeArray positions;
[ReadOnly] public NativeArray velocities;
[ReadOnly] public NativeArray densities;
public NativeArray pressures;
public float smoothingRadius;
const float mass = 1f;
public float restDensity;
public float pressureMultiplier;
public float deltaTime;
public void Execute(int i)
{
float2 pressureForce = float2.zero;
float2 acceleration = pressures[i] / densities[i]; // Approximate acceleration
float2 predictedVelocity = velocities[i] + acceleration * deltaTime;
float2 m1Pos = positions[i] + predictedVelocity * deltaTime;
for (int j = 0; j < positions.Length; j++)
{
if (i == j) continue;
float dst = math.length(m1Pos - positions[j]);
if (dst == 0) continue;
float2 dir = (m1Pos - positions[j]) / dst;
float slope = MoleculeManager.KernelSmootherGradient(dst, smoothingRadius);
float density = densities[i];
float sharedPressure = CalculateSharedPressure(density, densities[j]);
pressureForce += sharedPressure * mass * slope * dir / density;
}
pressures[i] = pressureForce;
}
private readonly float ConvertDensityToPressure(float density, float restDensity, float pressureMultiplier)
{
float densityOffset = density - restDensity;
float pressure = densityOffset * pressureMultiplier;
return pressure;
}
private readonly float CalculateSharedPressure(float density_i, float density_j)
{
float pressure_i = -ConvertDensityToPressure(density_i, restDensity, pressureMultiplier);
float pressure_j = -ConvertDensityToPressure(density_j, restDensity, pressureMultiplier);
return (pressure_i + pressure_j) / 2;
}
}
[BurstCompile]
struct GetNeighbourMoleculesJob : IJobParallelFor
{
[ReadOnly] public NativeArray positions;
[WriteOnly] public NativeArray neighbouringMoleculesPos;
[ReadOnly] public SpatialLookup spatialLookup;
public NativeArray neighbouringMoleculesInt;
public int maxNeighboursPerCell;
public void Execute(int i)
{
// Get neighbouring molecules for the position
int count = 0; // Track how many neighbouring molecules are found
NativeArray tempNeighbours = spatialLookup.GetNeighbouringMolecules(positions[i], Allocator.Temp);
count = tempNeighbours.Length;
// Store neighbouring molecule positions
for (int j = 0; j < count; j++)
{
neighbouringMoleculesPos[i * maxNeighboursPerCell + j] = positions[tempNeighbours[j]];
}
}
}
< /code>
Wenn es eine bessere Möglichkeit gibt, dies zu handhaben, wäre das sehr geschätzt. Danke!