Queries#

Usage#

All providers which can be queried use a similar interface. They also have an accompanying helper class (known as sample helpers) to make basic queries simpler.

The providers built into our system perform queries asynchronously; queries are offloaded to the GPU or to spare CPU cores for processing. This has a few non-trivial impacts on how the query API must be used.

Firstly, queries need to be registered with an ID so that the results can be tracked and retrieved later. This ID needs to be globally unique, and therefore should be acquired by calling GetHashCode() on an object/component which will be guaranteed to be unique. A primary reason why sample helpers are useful is that they are objects themselves and therefore can pass their own ID, hiding this complexity from the user.

Secondly, even if only a one-time query is needed, the query function should be called every frame until it indicates that the results were successfully retrieved. Querying a second time, in the same frame, using the same ID, will stomp over the last query points. Posting the query and polling for its result are done through the same function.

Finally, due to the above properties, the number of query points posted from a particular owner should be kept consistent across frames. The helper classes always submit a fixed number of points for the current frame, so satisfy this criteria.

Important

For each unique query ID:

  • Queries should only be made once per frame.

  • Number of query points posted should be kept consistent across frames.

The following code example uses the SampleCollisionHelper to query the water height at the current position. Since all the helpers have a similar interface, it can be applied to others with only some parameter changes.

using UnityEngine;
using WaveHarmonic.Crest;

public class ScriptExample : MonoBehaviour
{
   SampleCollisionHelper helper = new();

   // To avoid a one frame delay, call this before the WaterRenderer.LateUpdate call.
   void Update()
   {
      // Call only once per frame.
      if (helper.SampleHeight(transform.position, out float height))
      {
            Debug.Log($"Height is {height}.");
      }
   }
}

Tip

For the most user-friendly, use-case driven approach, which requires not scripting, there is the Query Events component.

Alternatively, if you wish to query the provider directly, then this is how:

using UnityEngine;
using WaveHarmonic.Crest;

public class ScriptExample : MonoBehaviour
{
   readonly Vector3[] _query = new Vector3[1];
   readonly Vector3[] _height = new Vector3[1];

   // To avoid a one frame delay, call this before the WaterRenderer.LateUpdate call.
   void Update()
   {
      if (WaterRenderer.Instance == null) return;

      var provider = WaterRenderer.Instance.AnimatedWavesLod.Provider;

      _query[0] = transform.position;

      // Call only once per frame.
      var status = provider.Query(GetHashCode(), 0, _query, _height, null, null);

      if (provider.RetrieveSucceeded(status))
      {
         Debug.Log($"Height is {_height[0]}.");
      }
   }
}

The advantage with the direct method is if you to query multiple points at a time (we are only querying a single point in the example). This is more efficient using this approach.

Collision Shape#

The system has a few paths for computing information about the water surface such as height, displacement, flow and surface velocity. These paths are covered in the following subsections, and are configured with Water Renderer ‣ Simulations ‣ Animated Waves ‣ Collision Source dropdown.

The system supports sampling the collision shape at different resolutions. The query functions have a parameter, Minimum Length, which is used to indicate how much detail is desired. Wavelengths smaller than half of this minimum spatial length will be excluded from consideration.

To simplify the code required to get the water height, or other data via scripting, the SampleCollisionHelper is provided.

Research

We use a technique called Fixed Point Iteration to calculate the water height. We gave a talk at GDC about this technique which may be useful to learn more: https://www.gdcvault.com/play/1023011/Fixed-Point-Iteration-A-Simple.

The Collision Area Visualizer debug component is useful for visualizing the collision shape for comparison against the render surface. It draws debug line crosses in the Scene View around the position of the component.

GPU Queries#

This is the default and recommended choice for when a GPU is present. Query positions are uploaded to a compute shader, which then samples the water data and returns the desired results. The result of the query accurately tracks the height of the surface, including all wave components, depth caches and other Crest features. Set Water Renderer ‣ Simulations ‣ Animated Waves ‣ Collision Source to GPU.

Collision Layers#

GPU queries support collision layers. Collision layers allow excluding collision sources by querying the displacement data at certain points.

For example, using the After Animated Waves layer includes all waves, but excludes Dynamic Waves. This has often been requires as when using the Sphere Water Interaction to produce wakes, it can cause a feedback loop and jitters.

Collision layers can be enabled with Water Renderer ‣ Simulations ‣ Animated Waves ‣ Collision Layers. When enabling a layer it will allow that layer to be included or excluded. On any object which queries collisions, like Floating Object, set the layer to what you want to include. The layers are in order (except Everything), and selecting a layer will include everything preceding it (eg After Dynamic Waves also includes Animated Waves).

Example

To have a Floating Object exclude dynamic waves, make sure Dynamic Waves layer is enabled on Collision Layers, and then set Floating Object ‣ Collision Layer to After Animated Waves.

See the boats in the Boats sample.

There is also the Displacement layer which is a layer at the end. It can be used to render inputs which affect underwater and have nested buoyant objects (eg floating barrel in a ship).

CPU Queries#

On platforms where a GPU is not present, or for authoritative servers, CPU queries are available.

Please see CPU Queries.

Flow#

Flow can also be queried, which is currently done on the GPU only. Query against the Flow Provider or use the SampleFlowHelper.

Water Depth#

Currently, the water depth cannot be sampled, but the distance to water’s edge can. See SampleDistanceToWaterEdge.