The Labs R&D team was presented with the challenge of creating a believable ocean water surface for IO’s Hitman game. Water rendering in real-time is notoriously difficult, and solutions usually comprise of several subsystems (and compromises) to complete the effect. Ocean rendering for Hitman presented several interesting challenges, most notably in the Sapienza level which featured transparent water, an arbitrary coastline including a beach the player could walk onto and an unrestricted view to the horizon in several directions. This implied a seamless transition of surface detail from close up to kilometers away. The solution also had to support some physics interactions (ragdolls, floating objects, etc) along with achieving a good visual benchmark while maintaining a relatively low performance footprint.
Supporting physics became the initial focus, as it could have design implications for the whole system. We also desired a lightweight solution that “just works”, meaning minimal requirements for code specific to water rendering, minimal setup work and art assets. We set forth to produce a practical, easy to use solution using adaptive hardware tessellation. The implementation offers believable surface deformation, a camera-to-horizon visibility distance and intrinsic geometry degradation (level of detail).
With the exception of the beach variant base mesh (explained later), the base geometry is very sparse. It is a radial ocean mesh roughly 800m across, divided into hectare patches. The total mesh diameter was found by placing circles representing the desired view distance at several extreme positions in the map. These areas were then encapsulated within a larger circle representing the final extents of the ocean surface geometry. Base geometric density (pre-tessellation) is 1 vertex per every 10m. Keeping a uniform distribution of triangles will allow to better control tessellation density.
Tessellation is adaptive over camera distance, and further restricted to the camera frustum. The camera distance function is computed in the vertex shader and passed on to other stages for modulating the tessellation factor, displacement amplitude and other effect weights.
Surface wave deformation is based completely on stateless functions, which means no displacement maps or other pregenerated data. This allows to sample surface displacement at any [X,Y] point given a specific time. Additionally the same function can be simultaneously run on CPU for physics, eliminating any frame latency that could have been problematic with a compute shader based approach.
For the functions themselves, Fast Fourier Transforms [Simulating Ocean Water, J.Tessendorf] were naturally considered but not used. Although FFT ocean simulation offers very convincing results, it suffers visibly from both spatial and temporal tiling that would have been a problem for the large expanses we wished to simulate. It is also expensive compared to other options. However, the tiling nature of FFT’s did have an advantage we could use : our water normal maps were baked from an offline FFT ocean simulation and tile perfectly.
Gerstner waves, on the other hand, are quite cheap and easy to implement. Conceptually, an ocean surface can be thought of as a chaotic mix of an endless amount of simple waves. Realistically though, ocean waves would interact (i.e collide and exchange energy). However in our method Gerstner waves are computed as surface delta positions and simply added. Adding together many waves at first did not give good results: when adding together many layers of random values, everything tends to converge towards the median value and cancel out the effect. In this case less was more.
Waves are divided into two categories : Omnidirectional waves and localized waves. Omnidirectional waves are the base of the effect and cover the entire surface (within range). They are a mix of 8 wavefronts : 3 primary wavefronts and 5 smaller secondary wavefronts. Odd numbers are used in order to radially distribute the waves evenly while avoiding symmetry.
Additionally to open water waves, the shader allows for localized waves which are applied using artist-placeable “wave objects”. These objects work a bit like forward lights in the shader. Each wave object adds 3 wave samples per vertex, and the shader allows for up to 4 wave objects per mesh. The worst case scenario is therefore 20 wave samples (8 omnidirectional + 4 * 3 localized) per tessellated vertex. Each wave object holds a set of parameters and passes these to the shader as constants. This allows the user to control area of effect size (radius), direction, wave amplitudes and curvature. In order to support nonlinear coastal topology, it is possible to warp the wavefront along the wave direction: The wave curve parameter allows for bidirectional warping in order to obtain a convex or concave curved wavefront.
Beaches are handled as a special case. The base surface mesh in this case supports arbitrary triangle size and vertex placement along the beach edge. There were mainly two problems to solve for beach meshes : How to get the water to “slide” onto the sand and how to keep the tessellation relatively uniform given an arbitrary mesh topology.
For the latter, the mesh was analysed offline by a simple script which computed each triangle’s area and normalized it to the area of a triangle on our 10m grid. This value was then averaged per vertex (to prevent tearing) and stored in a vertex color channel. This value is then used in the shader to bias the tessellation factor for those triangles. Smaller triangles are therefore proportionally tessellated to a lower value.
Beach interaction relies on a baked depth slice of the scenery, which is sampled by the ocean shader for the beach variant and allows the vertices to know the height of the beach at their location during displacement. This allowed to have the ocean mesh “slide onto” the beach mesh when moving. The depth map only stores a small strip of depth (hardcoded at a 4m [-2m,+2m] spread) which allows for an acceptable height resolution (~1.56cm) when packed into an 8bit texture channel. For area, roughly 4 texels per square meter seemed sufficient.
Shading parameters were mostly simplified to absorption and scattering, represented using a LUT texture mapped to linear view depth. The shader also made use of a quarter resolution render target for underwater geometries, allowing blurring, refraction, caustics and optionally diffraction. Impulses from interacting objects also create small ripples on the surface, which perturb the surface normals and in turn create additional caustics on the underwater floor.
The generic caustic effect was applied in the quarter resolution buffer by reconstructing the world position of geometries behind the water plane using the depth buffer. This position is then used to map an animated caustic effect.
A copy of the quarter resolution render target is then blurred and recombined with the unblurred version using a depth mask, keeping geometry near the surface sharper. This and subsequent steps are done in the ocean shader as we require the pixel depth of the surface.
Water coloring is done by using a LUT texture which maps to linear depth starting from the water surface to a given depth along the eye ray. Substance Designer was very useful here in order to create a procedural LUT asset which allowed rapid adjustments of the color ramps without going back and forth between the game and image editors. Using a LUT also allows to precompute complex falloff functions which are then simply mapped linearly at runtime. Finally the surface is shaded, which here mostly amounts to lighting, reflections and ocean foam.
Although the base geometry extended out several hundred meters, the edge of the mesh could be seen especially from certain elevated vantage points. “Horizon mapping” was used to render the ocean to the horizon line. The technique uses the camera vector and camera position to generate world space horizontal planar coordinates onto arbitrary geometry. Pixels above the “horizon” (cv.z > 0) are clipped. This method is used to render the ocean to infinity on simple and relatively near geometry, without having to resort to placing huge plane geometries in the distance. Because the generated coordinates are world space, they can be matched to the ocean shader for a seamless transition.
Here are a few images from Hitman game, provided by IO-Interactive team. It shows the multiple usage of the ocean technology throughout their games. Although this was design specifically for vast open water views, they also used it for most of their water needs including calm pool water or cinematic trailers. We are grateful of having worked with such a talented team, and make sure to put your hands onto a copy for Hitman.
Eidos-Montréal is always looking for great talent to help us shape the next generation of games. If you believe that you have what it takes, we definitely want to hear from you. Look at the current positions available at Eidos-Montréal and join our talented teams now!
Nicolas Longchamps | Principal Technical Artist
LABS R&D – Eidos-Montréal
Jean Normand Bucci | LABS R&D Director – Eidos-Montréal
Samuel Delmont | Senior Graphics Programmer
Jonas Meyer | Lead Programmer
Martin Vestergaard Kummel | Technical Director
Labs R&D team
Eidos-Montréal & IO-Interactive