Foam Waves

Hello there!

I’m trying to add a sort of foam to the top of my waves, but I’m not sure exactly what approach to take.
Any help would be appreciated.

var WaterMeshManipulation = pc.createScript('waterMeshManipulation');

WaterMeshManipulation.attributes.add('waveAmplitude', { type: 'number', default: 0.05 });
WaterMeshManipulation.attributes.add('waveFrequency', { type: 'number', default: 2.0 });
WaterMeshManipulation.attributes.add('waveSpeed', { type: 'number', default: 1.0 });
WaterMeshManipulation.attributes.add('waveDirectionVariance', { type: 'number', default: 0.5 });

WaterMeshManipulation.prototype.initialize = function() {
    // Access the mesh instances for both planes
    this.meshInstances = [];
    this.meshInstances.push(this.entity.findByName('Plane1').render.meshInstances[0]);
    this.meshInstances.push(this.entity.findByName('Plane2').render.meshInstances[0]);

    // Check if all mesh instances are found
    for (var i = 0; i < this.meshInstances.length; i++) {
        if (!this.meshInstances[i]) {
            console.error("Mesh instance not found.");
            return;
        }
    }

    // Subdivide the plane meshes
    var subdivisionsX = 50; // Adjust as needed
    var subdivisionsY = 50; // Adjust as needed

    for (var i = 0; i < this.meshInstances.length; i++) {
        var planeMesh = pc.createPlane(this.app.graphicsDevice, {
            halfExtents: new pc.Vec2(5, 5), // Adjust the size of the plane
            widthSegments: subdivisionsX,
            lengthSegments: subdivisionsY
        });

        this.meshInstances[i].mesh = planeMesh;
    }

    // Get original vertex positions for the first plane
    var vertexBuffer = this.meshInstances[0].mesh.vertexBuffer;
    this.originalVertices = new Float32Array(vertexBuffer.lock());
    vertexBuffer.unlock();
};

WaterMeshManipulation.prototype.update = function(dt) {
    // Check if mesh instances are initialized
    for (var i = 0; i < this.meshInstances.length; i++) {
        if (!this.meshInstances[i]) {
            return;
        }
    }

    // Access the vertex buffer for the first plane
    var vertexBuffer = this.meshInstances[0].mesh.vertexBuffer;

    if (!vertexBuffer) {
        console.error("Vertex buffer not found.");
        return;
    }

    // Lock the vertex buffer to access its data for the first plane
    var vertexData = new Float32Array(vertexBuffer.lock());

    // Calculate wave offset based on time
    var waveOffset = (Date.now() / 1000) * this.waveSpeed;

    // Loop through each vertex of the first plane and apply wave effect
    for (var i = 0; i < vertexData.length; i += 3) {
        // Generate smooth random direction offset using Perlin noise
        var noiseValue = noise.perlin3(vertexData[i], vertexData[i + 1], vertexData[i + 2]);
        var directionOffset = noiseValue * this.waveDirectionVariance;

        // Modify vertex positions based on wave simulation algorithm with direction offset for the first plane
        vertexData[i + 1] = this.originalVertices[i + 1] + Math.sin((vertexData[i] + waveOffset + directionOffset) * this.waveFrequency) * this.waveAmplitude;
    }

    // Set the modified vertex data back to the buffer for the first plane
    vertexBuffer.setData(vertexData);
    vertexBuffer.unlock();

    // Apply the same wave effect to the second plane by copying the vertex data from the first plane
    var vertexBuffer2 = this.meshInstances[1].mesh.vertexBuffer;

    if (!vertexBuffer2) {
        console.error("Vertex buffer not found for the second plane.");
        return;
    }

    // Lock the vertex buffer to access its data for the second plane
    var vertexData2 = new Float32Array(vertexBuffer2.lock());

    // Copy the vertex data from the first plane to the second plane
    for (var i = 0; i < vertexData.length; i++) {
        vertexData2[i] = vertexData[i];
    }

    // Set the modified vertex data back to the buffer for the second plane
    vertexBuffer2.setData(vertexData2);
    vertexBuffer2.unlock();
};

You could do it with a custom shader for instance?

Maybe use the world Y-position in the shader to mask out just the tips of the waves and then make them appear foamy?
You could probably do some funky stuff with the screen space normals as well, perhaps in combination with the mask method above.