3D Compute shader-based particle flowfield
3D Compute shader directional flow field created on Unity's newest Universal Rendering Pipeline.
Flow fields are a great way to simulate forces on any given object in 2D or 3D space. The concept is simple. You create a grid of points that each represent a position with a direction in space. As your agents/particles travel through this grid space, they will read the nearest point within that flow field to determine it's current direction. For this example, I am showing how you can achieve these result with a compute shader operation in Unity's Universal Rendering Pipeline.
protected void GenerateFlowFieldBuffer()
{
flowFieldBuffer = new ComputeBuffer(FLOWFIELD_POINTS_NUM, Marshal.SizeOf(typeof(FlowFieldPointData)));
var flowPoints = new FlowFieldPointData[FLOWFIELD_POINTS_NUM];
int iterations = 0;
for (int x = 0; x < xPointCount; x++)
{
for (int y = 0; y < yPointCount; y++)
{
for (int z = 0; z < zPointCount; z++)
{
var index = (x * yPointCount + y) * zPointCount + z;
Vector3 position = new Vector3(
simulationSpace.x / xPointCount * x + cellSize/2,
simulationSpace.y / yPointCount * y + cellSize/2,
simulationSpace.z / zPointCount * z + cellSize/2
);
position += this.transform.position - simulationSpace/2;
flowPoints[index] = CreateFlowFieldPoint(position);
iterations++;
}
}
}
flowFieldBuffer.SetData(flowPoints);
}
protected void DispatchFlowField()
{
flowFieldCS.SetBuffer(flowFieldKernelIndex, "_FlowFieldPointBuffer", flowFieldBuffer);
flowFieldCS.Dispatch(flowFieldKernelIndex, (Mathf.CeilToInt(FLOWFIELD_POINTS_NUM / (int)TCOUNT_X) + 1), 1, 1);
}
protected void RenderFlowField()
{
flowFieldMat.SetPass(0);
flowFieldMat.SetBuffer("_FlowFieldBuffer", flowFieldBuffer);
Graphics.DrawProceduralNow(MeshTopology.Points, flowFieldBuffer.count);
}
uint GrabGridIndex(float3 position)
{
float x = floor((position.x - (_BoundsPosition.x - _BoundsDimensions.x/2)) / ((_BoundsDimensions.x / _XCellCount)));
float y = floor((position.y - (_BoundsPosition.y - _BoundsDimensions.y/2)) / ((_BoundsDimensions.y / _YCellCount)));
float z = floor((position.z - (_BoundsPosition.z - _BoundsDimensions.z/2)) / ((_BoundsDimensions.z / _ZCellCount)));
uint flowFieldIndex = (x * _YCellCount + y) * _ZCellCount + z;
return flowFieldIndex;
}
uint flowIndex = GrabGridIndex(particle.position);
float3 flowDirection = _FlowFieldPointBuffer[flowIndex].direction;
Open-source! Use for whatever you want.
If you enjoy these open-source projects, the most direct way to support the development of future releases is through patreon.