Dynamic Water Surfaces with Mesh Deformation in Unity

Ultra-Optimized Mesh Generation in Unity Using Jobs and Burst
Ultra-Optimized Mesh Generation in Unity Using Jobs and Burst
July 1, 2026
Creating Dynamic Water with Vertex Shaders in Unity
Creating Dynamic Water with Vertex Shaders in Unity
July 1, 2026
Ultra-Optimized Mesh Generation in Unity Using Jobs and Burst
Ultra-Optimized Mesh Generation in Unity Using Jobs and Burst
July 1, 2026
Creating Dynamic Water with Vertex Shaders in Unity
Creating Dynamic Water with Vertex Shaders in Unity
July 1, 2026

Dynamic Water Surfaces with Mesh Deformation in Unity

Static water is boring. In modern games, players expect water to react to their presence, ripple when touched, and flow naturally.

One of the most effective ways to create interactive water is through Mesh Deformation. By programmatically moving the vertices of a water plane, we can simulate waves and ripples in real-time.


The Core Concept: Math Behind the Waves

To create a wave effect, we use trigonometric functions, specifically the Sine wave. The basic formula for a wave height at a given point is:

$y = A \cdot \sin(k \cdot x + \omega \cdot t)$

Where:

  • A is the Amplitude (Height of the wave)
  • k is the Wave Number (Frequency/Width)
  • t is Time

Setting Up the Water Mesh

To deform water, you need a mesh with a high density of vertices. A simple quad won’t work because it only has 4 vertices. You need a Plane or a custom grid mesh with many subdivisions.


C# Implementation: Simple Sine Waves

This script modifies the vertices of a mesh to create a simple waving surface.

using UnityEngine;

public class SimpleWater : MonoBehaviour
{
    public float power = 0.1f;
    public float scale = 1.0f;
    public float timeScale = 1.0f;

    private Mesh mesh;
    private Vector3[] baseVertices;

    void Start()
    {
        mesh = GetComponent<MeshFilter>().mesh;
        baseVertices = mesh.vertices;
    }

    void Update()
    {
        Vector3[] vertices = new Vector3[baseVertices.Length];

        for (int i = 0; i < vertices.Length; i++)
        {
            Vector3 vertex = baseVertices[i];
            
            // Calculate a wave based on X and Z position
            vertex.y += Mathf.Sin(Time.time * timeScale + (baseVertices[i].x * scale) + (baseVertices[i].z * scale)) * power;
            
            vertices[i] = vertex;
        }

        mesh.vertices = vertices;
        mesh.RecalculateNormals(); // Important for correct lighting!
    }
}

Adding Interactivity: Ripple Effects

To make the water react to objects (like a player jumping in), we need to track “Impact Points” and calculate a ripple that spreads from that center.

public void CreateRipple(Vector3 impactPoint, float intensity)
{
    // In a professional system, you would pass this point to a shader 
    // or a Job System to deform the mesh vertices locally.
}

Optimization: Moving to Jobs + Burst

Updating thousands of vertices every frame on the Main Thread is a recipe for a laggy game. For “Ultra-Optimized” water, you should use the Unity Job System.

  • Parallel Jobs: Calculate vertex offsets for each vertex on separate CPU cores.
  • Burst Compiler: Speed up the math calculations (Sin, Cos) significantly.
  • NativeArray: Use memory-efficient arrays to store vertex data.

Performance Comparison

MethodCPU UsageBest For
Simple Update LoopHighSmall Ponds / Stylized water
Vertex ShaderLow (GPU)Visual waves without physics
Jobs + BurstMedium (CPU Parallel)Interactive water with physics/buoyancy

Conclusion

Dynamic mesh-based water adds a layer of polish that makes your game feel high-quality. While it requires a bit of math, the result of seeing your player create ripples in a procedural lake is worth the effort.


 

Leave a Reply

Your email address will not be published. Required fields are marked *


Skip to toolbar