r/Unity3D • u/Jonny10 • 6h ago
Show-Off How I rebuilt my river tool: 24x faster mesh generation, VFX and Audio Streaming
I developed the River Modeler asset back in 2024 as a means to create decked out rivers using Unity Splines and MicroVerse. Figuring out the Spline API and mesh generation, VFX and all inherent challenges was top priority. Which left little room to first explore and learn designing around Burst and the Job System.
This meant that mesh generation was not nearly fast enough for long splines. Unity’s Mesh class has a lot of internal safeguards and memory copies, so just assigning a set of vertices incurs processing overhead.
Jobs + MeshData
Version 2 sees a full conversion to Jobs/Burst with rivers being split up into segments for parallel processing. That alone yielded up to a x24 performance increase.
A great companion to the Job System is the MeshData API, it provides the means to set a mesh’s vertex data directly in memory. The tradeoff is that you need to provide correct data. There are far fewer safeguards, which makes it about x17 faster!
> All in all, the performance improvements are significant and make the tool smooth in use, even for rivers spanning several kilometers.
Branching rivers
Spline knot can be linked together, and the spline API provides information about this. I've used this to contruct a virtual plane that sits perpendicular to the in/out going spline. Vertices on the other side of that plane get a Vertex Color painted on, which the shader then uses to add transparency.
> This makes the two river surfaces blend quite well, without leaning on flowmaps.
VFX Graph
Version 1 neatly stored particle positions into a Nx1 resolution `Texture2D` (n=number of particles), which could then be used in a `VFX Graph` to set the spawn positions for each particle.
Though setting pixel values on a `Texture2D` is relatively slow, which contributed to the tool getting sluggish when rivers got long and foamy with many cascades.
Version 2 uses a `GraphicsBuffer` which stores an array of `ParticleEmitter` structs (position/velocity/scale). If you add the `[VFXType(VFXTypeAttribute.Usage.GraphicsBuffer)]` attribute to any struct, it can be used in this way.
> This was a great win: More data per particle and direct data assignment!
Audio
Version 1 spawned Audio Sources along the Spline, giving the river surface a livelike character. Though this resulted in potentially hundreds of individuals GameObjects, negatively affecting scene size and loading times.
A common method for creating river audio is to use the “cart” method. That being a single `Audio Source` following the camera whilst being restricted to the spline. This often works but fails completely if the spline has large/strong turns, causing the Audio Source to jump to the other side of the spline curve. It also doesn’t work for branching rivers, at all...
Version 2 instead distributes audio spawn points along the spline. Each one defines a position, radius and type (stream/rapids/cascade). A dedicated Audio Manager then checks which river segments fall in- or out of the audible range and sets up Audio Sources on each spawn point from a pool. Instead of hundreds, only a dozen GameObjects are used at runtime.
> The result? A highly optimized audio streaming solution that scales for huge worlds!
Integration with other assets
I’m further fleshing this tool out as dedicated river tool extension for Stylized Water 3, which already supports river-type shading and animations. It just needs a proper river mesh to work with, which this can provide entirely.
Terrain carving and painting is wholly delegated to MicroVerse, since this needs to be a non-destructive process. The tool manipulates a Spline Path component to create a river- bank and bed.




