To give each surface of a mesh a solid colour, every triangle needs to have it's own set of vertices.
The reason for this is due to the fact that normal data is stored on a per vertex basis, meaning that shared vertices between two triangles will create an unwanted smooth edge.
Thus, when creating a water mesh we need to make sure a quad consists of six vertices. This is where the problem is introduced however;
Since we will be modifying the vertex positions at run-time, we will also need some way to recalculate the normals of each vertex in order for our mesh to be shaded correctly.
There are two different ways of modifying our water:
- Attaching a C# script to our mesh that loops through our vertices
- A custom shader that offsets vertex positions
In this case we will be using the second method since our shader already has to access each vertex, meaning that there is almost no extra cost to offset a vertices position before passing it to the fragment function.
So far so good, but back to the problem on how to recalculate the triangle normals. Our vertex function isn't aware of any other vertices and thus we can't tell which triangle this vertex is a part of.
This means we have to take the two adjacent vertices we think are connected and create a normal from that.
Of course, this doesn't entirely work because we need to calculate the normal between three very specific verts: our current vertex and the two connected ones forming this triangle of the mesh.
At this point you may be thinking:
Never mind we can just create a c# script, offset the positions there and then finally call Mesh.RecalculateNormals() which will automatically calculate the correct normals for the modified mesh.
This works and is the simplest solution, however the performance hit of recalculating all the normals every frame using this method is quite considerable, especially if you have multiple water planes in your scene at the same time (this can easily halve the frame rate).
OK so back to our custom water shader - we need some way of marking our vertices to show which corner of a triangle they belong to.
What better way than to simply use vertex colours to pass this info to the shader!
Vertex colors are represented as float4 values ranging from 0 to 1, which means we will need to be able to identify each of the six unique vertex positions in a quad through the use of floats. In this case we will only use the red channel, assigning red color values to our quads as follows:
Now we can simply check the color of the vertex we are processing in our vert function and based on that know where its two neighboring verts must be
(e.g. a value of 0.2 would tell us we are at the top left point of a triangle).
To avoid using conditional statements in our shader code, we can turn our float color values (0, 0.2, 0.4, ..) into array indices (0, 1, 2, ..):
int index = round(v.color.r * 5);
This allows us to create an array containing the relative positions of neighbors, meaning we don't need to write code for each of the six scenarios.
Once we know all three corner positions of a triangle we can easily calculate the normals as well.
That got a bit complicated towards the end, so here's the project files if you want to look at the shader source.