Fluid rendering

Obi supports two fluid rendering techniques: ellipsoidal impostors and isosurface meshing, implemented by ObiParticleRenderer and ObiFluidSurfaceMesher, respectively.

Obi Particle Renderer


Impostors are a special type of billboards (camera-facing quads) which look like perfectly-lit, highly tesselated 3D ellipsoids, at a fraction of the cost. The ObiParticleRenderer component can be used to render your fluid particles as tiny ellipsoids using this technique. While typically not suitable for final rendering, this is useful to visualize and debug the simulation. It can also come in handy to render granulars.

In order to do this, just add the ObiParticleRenderer component to any actor (for instance an ObiEmitter, but it can also be used to draw cloth or ropes, see particle rendering). You can also set the color of the particles, and scale the radius used when rendering them.

The default material used for particle rendering has been created using Shader Graph. You can find it Obi/Resources/ObiMaterials/Common/ParticleShader.shadergraph.

Obi Fluid Surface Mesher


This component renders the surface of the fluid as a mesh. Setting up fluid rendering requires three steps:

  • Add a ObiFluidSurfaceMesher component to the emitters we want to render. Surface meshers take a ObiFluidRenderingPass as input.
  • Create a ObiFluidRenderingPass asset, which contains rendering parameters for all meshers using it.
  • Create a ObiFluidRendererFeature, which is used to render additional data for transparent fluids.

1) Creating a surface mesher

By default, ObiEmitters created from the Object menu already have a ObiFluidSurfaceMesher component in them. Should you need to create one manually, go to Component→Physics→Obi→ObiFluidSurfaceMesher.


Pass

Rendering pass used. Note: Make sure the pass you use is also assigned to a ObiFluidRendererFeature, otherwise no renderer using this pass will be visible.


2) Creating a fluid rendering pass

ObiFluidSurfaceMesher requires a fluid rendering pass asset as input. You can create one by clicking the "Create" button in the mesher's inspector (the newly created pass will then be automatically assigned to the mesher), or by right clicking on a project folder and selecting Create→Obi→Fluid Rendering Pass and then assigning it manually to the mesher.

Passes determine how fluid meshers are grouped together to form a single fluid surface: meshers sharing the same pass will be merged and produce a single mesh, meshers using different passes will generate separate meshes.

Two surface meshers sharing the same pass: only one mesh is generated, particle colors are transferred to mesh vertices.
Two surface meshers using a different pass each: two distinct meshes are generated.

Passes expose a set of parameters that determine how the mesh is generated and rendered. Let's take a look at them:


Mesh generation

These parameters control how the surface mesh is built from the particle-based fluid.

Voxel size
Controls overall mesh quality. Small voxel sizes lead to highly detailed surfaces that are costly to generate - while large voxel sizes lead to cheaper, less detailed surfaces. If your voxel size is too small performance will be negatively affected, and if it's too large, small drops and other fine details will be lost.

Surface mesh using a large voxel size (0.08)
Surface mesh using a small voxel size (0.04)

Isosurface
Offsets the fluid surface, blending particle properties together to obtain a smooth surface. At a value of 0, the fluid surface sits at the border of each particle's smoothing radius, where fluid density reaches 0. Increasing the isosurface value will offset the surface inwards, towards areas of greater density.

High isosurface value (0.1)
Low isosurface value (0)

Descent iterations
Amount of gradient descent iterations. Once the initial isosurface has been sampled at the specified voxel size, surface vertices are further moved towards higher density regions using gradient descent. The resulting effect is denser sampling at virtually no cost. This parameter controls the amount of descent iterations.

Descent isosurface
Target isosurface value to descend towards. This is added to the Isosurface value to determine how much vertices are shrunk during gradient descent.

Descent speed
How much each descent iteration moves vertices towards the descent isosurface.

Smoothing
Amount of laplacian smoothing applied to the surface mesh. More iterations/higher intensity yield smoother surfaces. Higher smoothing tends to shrink the mesh, obtaining thinner tendril and sheet-like structures.

Smoothing iterations set to 0. No smoothing is performed, the raw voxelized mesh is used.
Smoothing iterations set to 4, intensity set to 1. Fluid surface is smoothed, and shrunk in the process.

Bevel (2D only)
When the solver is in 2D mode, controls the amount of beveling applied to the fluid edges to obtain a 3D effect.

2D beveled fluid (bevel set to 1)
2D flat fluid (bevel set to 0)

Rendering

This controls how the mesh is rendered.

Material type
Determines the material used to render the fluid. Obi comes with two materials intended for opaque and transparent fluids, and you can use your own custom material too.

The included opaque material works in all render pipelines, provided that you add your pipeline as a target in ShaderGraph. The transparent one works in both the built-in pipeline and URP, however HDRP has much better transparent shading capabilities out of the box so you should use a custom transparent material in HDRP. For information on how to write a custom shader, see Custom fluid shaders at the end of this page.

Opaque fluid
Transparent fluid

Smoothness
Controls overall surface smoothness of the fluid. Higher values lead to more pronounced specular highlights.

Smoothness set to 0.1
Smoothness set to 0.9

Thickness
How much light is absorbed by the fluid, as light travels trough it: higher values will tint the background with the fluid color.

Low thickness (2)
High thickness (14)

Thickness downsample
Downsampling of the thickness buffer. Increase it to use a lower-resolution buffer.
Refraction intensity
Amount of light refraction. This effect is more noticeable in thicker regions of the fluid.

Refraction intensity set to 0. Notice how the background is undistorted.
Refraction intensity set to 0.1. Notice how fluid distorts the background.

Thickness downsample
Downsampling of the refraction buffer. Increase it to use a lower-resolution buffer.

3) Creating a fluid renderer feature

Creating a renderer feature is done in a slightly different way depending on which scriptable render pipeline you're using. Note the HDRP rendering pipeline is not currently supported:

Built-in
Add a ObiFluidBuiltInRendererFeature component to any object in the scene.
Universal (URP)
Add a ObiFluidRendererFeature to the feature list of your pipeline's Renderer asset, see the following picture:

Memory budget

Obi uses an algorithm called surface nets to build the fluid surface mesh. This algorithm is based on voxels, Obi stores these voxels in groups of 64 voxels called chunks. You can set a memory budget for storing surface chunks in the ObiSolver component, under the "Memory budget" foldout:


It is possible for fluid rendering to run out of chunks, in that case the simulation will slow down considerably and you'll notice holes and missing chunks in the fluid mesh (see the following image for an example of how this looks like). If this is your case, increase the surface memory budget.

Fluid mesh with missing chunks due to insufficient memory budget.

Custom fluid shaders

You can use ShaderGraph to build custom fluid shaders. The only difference with a regular shader is that fluid meshes use a custom compressed vertex format, so you must add a specific node to your graph to uncompress vertex data. Which node to use depends on whether your shader is intended for the Burst or Compute backends

When using the Burst backend, fluid mesh vertices are sent from the CPU to the GPU. Vertex normals use octahedral encoding, so you need the DecodeNormal node to decode them. Here's a minimal fluid shader for the Burst backend:


When using the Compute backend, fluid mesh vertices are generated and rendered by the GPU. You need to use the GetIndirectVertex node to retrieve vertex data. Here's a minimal fluid shader for the Compute backend: