Tuesday, August 26, 2014

Water is Fancier Than You Think


Progress? On my project? It's more likely than you think. As you can probably see from the above picture, I've been working on water. While it may not look that impressive, this actually involved several significant changes. To get to this pretty scene, I had to do the following:
  1. Deal with having two blocks in the same spot (for water intersecting the shore).
  2. Add support for transparency.
  3. Remove internal faces from between world chunks.
  4. Add support for non-solid blocks (so that the player could walk through the water).
  5. Add support for animated textures.
Lets cover each of these in detail:

1. Deal with having two blocks in the same spot (for water intersecting the shore).

Allowing each cell to hold two different blocks would double the memory I'm using, so that wasn't a feasible solution. And if I ever needed three in the same spot, I'd be back where I started. 

The obvious answer was to use extensions (the same technique I'm using for vegetation and decals). However, I wanted the water to look like one large body, rather than a bunch of cubes. This meant eliminating internal faces. Unfortunately, extensions don't take adjacent cells into consideration during the mesh building phase.

So I introduced a new extension type for blocks. The meshing algorithm was modified to handle these the same way it handles normal blocks.

2. Add support for transparency.

For those unfamiliar with game programming, properly handling transparency is actually quite difficult. To get it looking right, you have to render the faces from back to front. Unfortunately, the most optimal way to render non-transparent things is front to back (and with blending turned off). Thus, the best solution was to create two meshes, one opaque, and one transparent. The opaque meshes are rendered first, front to back. Then blending is turned on, and the transparent faces are rendered back to front.

I also had to modify the meshing algorithm to account for the fact that transparent faces don't hide opaque ones. There are also still some problems that can arise from having multiple layers of water in the same chunk that I have yet to fix.

3. Remove internal faces from between world chunks.
Until I made this change, the meshing algorithm only took into account blocks in the same chunk. Unfortunately, this lead to underwater walls being generated between chunks. I had to modify the algorithm to take neighboring chunks into account. This also meant waiting until all those neighbors were loaded before running the meshing algorithm in the first place.

4. Add support for non-solid blocks (so that the player could walk through the water).

This was relatively easy. I simply added a flag to each material indicating whether it was solid or not (likewise with transparency). This flag could then simply be checked in physics, and non-solid blocks could be ignored.

5. Add support for animated textures.

I spent a long time thinking about ways to accomplish this. The solution I settled on ended up being stupidly simple. I simply built a table of animation info. that corresponded to each texture. This info is passed to the shader, which does some simple math to figure out which texture to draw each frame.

This did force me to start using array textures, but that also got rid of some aliasing, so it was a good move all around.



I also did a few other miscellaneous things, including some minor optimizations and bug fixes. All in all, it was a very productive week!


No comments:

Post a Comment