Providing Triton with a height map of your underwater terrain enables features like smooth coastline/water blending, breaking waves at shorelines, and dampening of waves in shallow water. But getting it right can be tricky. Here are a few tips for getting good results:
Dynamically Creating Height Maps
If you’re using OpenSceneGraph, be sure to study the OSGDynamicHeightMap sample included with the Triton SDK. It illustrates rendering a top-down view of the surrounding terrain to a height map, and passing it into Triton using the Environment::SetHeightMap() API. Even if you’re not using OpenSceneGraph, you may find the details of how it’s setting up the shaders to be helpful in your own integration.
It’s usually not necessary to actually render and submit a height map every frame; height maps typically cover a fairly large area (around 20×20 km or so, at least.) You might consider an approach that only generates and submits a new height map when the camera moves by more than a kilometer or so, in order to maximize performance.
It’s important that your underwater terrain is realistic; good blending will only be achieved if there’s a smooth slope falling off from your coastline.
Of course, if your simulation covers a known, fixed area – you might not need to dynamically create your height maps at all. You can always generate one offline for a given area ahead of time, and load it at initialization.
Blending with the Coastline
You’ll want to adjust the coastline blending depending on your scene for best results. By default, the opacity of the water will be determined by the underwater visibility you specify in Environment::SetBelowWaterVisibility(). So, you’ll want to set that to the depth at which you want to start blending out the water near the shoreline. The higher the underwater visibility, the more blending – and that can help cover up z-fighting artifacts and other anomalies near the shore.
It is also possible to set the depth at which blending and wave dampening starts independently from the underwater visibility distance, if needed. Use the Environment::SetWaveBlendDepth method for this.
The resolution of your height map is also crucial to good coastline blending. Try to have a resolution of at least ten meters per height map texel; that would allow you to cover a 40x40km area with a 4096×4096 height map, for example.
Dealing with Depth Buffer Precision
A very common problem is “z-fighting” artifacts near the shore, resulting from our water surface sitting just above your terrain close to the shoreline. Simulations that cover large ranges of depth will find this to be a challenge.
Fundamentally, all you can really do is make the most of the depth buffer resolution that your system has. Often the most impactful improvement is dynamically setting the near and far clipping planes of your projection matrix based on what’s actually in the scene. If the closest object to the camera is 100 meters away, setting your near clip plane to 100 meters can make a huge difference in your depth buffer resolution.
If you card supports 32-bit depth buffers, be sure your OpenGL context or DirectX device is configured to take full advantage of it.
You might also want to consider moving toward a reverse depth buffer and/or logarithmic depth buffer if all else fails. Triton has support for such setups, as described here and here.
In practice, you’ll have to do the best you can in maximizing depth buffer precision in your scene, and increasing the water/terrain blending using the below water visibility distance in order to cover up z-fighting near the shore.
Dealing with Dry Terrain Below Sea Level
Some scenes have inland areas that are below sea level. While you could somehow manipulate the height map to cover these up (Triton won’t draw over areas at or above a height of 0), an alternative is a technique called depth masking. At a high level, the idea is to draw a transparent polygon over the below-sea-level area – after the terrain has been drawn, but before Triton is drawn. If this transparent polygon is drawn with depth writes enabled, it will prevent Triton from drawing over it.
Maximizing Performance
If you initialized Triton with support for real-time height queries on the water, then Triton must make a CPU copy of your height map when you call Environment::SetHeightMap() in order to take wave dampening in shallow water into account when you call Ocean::GetHeight().
Under OpenGL, this copy is done asynchronously and usually won’t affect performance too badly. But there are a couple of ways to avoid the cost of this copy if needed:
- Do you really care about wave dampening in shallow water? If you aren’t going to be simulating ship buoyancy in shallow harbors or very near the shoreline, perhaps you can get away with disabling the CPU copy of the height map entirely. You can use the do-height-map-copy setting in the Triton.config file to disable this copy, if you don’t really need it.
- Do you already have a fast means of doing height queries on your underwater terrain? You can provide a callback function to Triton for CPU terrain height lookups using the Environment::SetUserHeightCB() method. If provided, you can set do-height-map-copy to “no” and use your own callback for these lookups instead.
Alternative Approaches
We also provide the Environment::SetDepthMap method as an alternative to height maps. If you already have a copy of your scene’s depth texture handy, you can just provide that to Triton and it will use it for water/terrain blending. Using a depth map instead of a height map comes with some caveats that are documented with the SetDepthMap function, but they may be worth it if you have such a texture available anyhow and can provide it to Triton at no additional cost.