Triton
Visual simulation library for ocean rendering.

Getting started with Triton

Triton includes over 60,000 lines of code, but it will only take a few to integrate it into your application.

Here's how.

Overview of the sample projects

Examining the "Samples" directory of the SDK is a quick way to get started. You'll find simple applications illustrating the use of Triton in OpenGL, DirectX9, DirectX11, C#, OpenSceneGraph, and Ogre applications, with some handy functions for initializing, updating, and shutting down Triton that you can use in your own app. Each sample also includes a sky box class, which is used not just to make the sample look prettier, but also illustrates the integration of environment cube maps with Triton for more realistic reflections from the sky.

You'll find examples of using Triton to render infinite oceans, as well as smaller bodies of water using user-defined patches of geometry.

The OpenGL sample is built on OpenGL 2.0 functionality. However, the core code of the OpenGL sample avoids use of the fixed function pipeline, and should be illustrative for developers working under OpenGL 3, 4, and beyond as well.

Configuring your project

Linking with Triton under C++ and Windows

Triton provides libraries for win32 and x64 applications created with Microsoft Visual Studio 2005, 2008, 2010, 2012, or 2013 (with the latest service packs applied and security patches) for every runtime library flavor. You'll find the libraries in the lib directory of the SDK. In your project properties, add the appropriate library to your linker inputs.

The Triton SDK installer defines the environment variable TRITON_PATH that you may use when referencing Triton's libraries and headers in your project properties.

For example, for a Win32 application developed with Visual Studio 2010 and using the "multi-threaded DLL" runtime, you'd link against

 $(TRITON_PATH)/lib/vc10/win32/Triton-MT-DLL.lib. 

Visual Studio 2003.NET libraries are found in "lib/vc7", Visual Studio 2005 libraries are found in "lib/vc8", Visual Studio 2008 libraries are found in "lib/vc9", Visual Studio 2010 libraries are in "lib/vc10", Visual Studio 2012 libraries are in "lib/vc11", and Visual Studio 2013 libraries are in "lib/vc12". Refer to the following table for matching the appropriate library file with the runtime your project is using (which you can find under the "C/C++ / Code Generation" property page in your project.)

Our Visual Studio 2012 and 2013 libraries are linked against the Windows SDK 8, and only support desktop applications at this time.

One special note for Visual Studio 2013 users - since the version of CUDA we use does not include Visual Studio 2013 support, our CUDA DLL for Visual Studio 2013 is built with Visual Studio 2012 tools. This means you may need to install Visual Studio 2012 express edition in order to get the necessary runtime DLL's on your system to develop with.

Runtime flavorTriton library
Multi-threadedTriton-MT.dll
Multi-threaded DebugTriton-MTD.dll
Multi-threaded DLLTriton-MT-DLL.dll
Multi-threaded Debug DLLTriton-MTD-DLL.dll

Even though we provide specific builds for individual compilers and runtimes, some of the third party DLL's we incorporate do not. If you run into trouble linking while using runtimes other than multi-threaded DLL, try adding MSVCRT to the "Ignore specific default libraries" field of the "Linker / Input" property page of your project.

You may also experience runtime problems if your application is built with special flags for the standard runtime library, such as _SECURE_SCL or _HAS_ITERATOR_DEBUGGING = 0. If you have trouble linking either at compile or runtime, please contact support@sundog-soft.com. We can provide you with an SDK that includes obfuscated source code, allowing you to build Triton with whatever development environment and compile-time flags you need.

Linking with Triton using C#

Inside the Samples/CSharpSample folder, you'll find TritonDLL.dll. This is Triton's native code, packaged as a DLL that may be invoked from C# code. Deploy this DLL into the working directory of your C# application.

Also inside the CSharpSample, you'll find the TritonClassLibrary project. This is a C# wrapper over Triton's C++ API that will be your interface to Triton. Include this project in your solution, and reference it from your own app's project.

The TritonXNA project is a sample illustrating using Triton from a C# XNA Game Framework 4.0 application. Refer to the readme.txt file inside the CSharpSample folder for more details on how it works, and how to use Triton from your own C# project.

The TritonClassLibrary is automatically generated from our C++ API; you may refer to the C++ API reference in our docs folder for the C# classes - they work exactly the same way.

Linking with Triton under Linux

The evaulation version of Triton for Linux is distributed as obfuscated source code, which you will build against your own system. Licensed users receive unobfuscated source.

You will need to have the latest graphics drivers installed. Notably, the open-source ATI drivers provided with Ubuntu are, as of this writing, known to not properly support OpenCL/OpenGL interoperability which will cause Triton to fail on ATI cards. You'll need to uninstall the fglrx drivers and install the latest from AMD's website if this happens.

Building Triton for Linux requires several third-party development kits to be installed first:

CMake: http://www.cmake.org/ FFTSS: http://www.ssisc.org/fftss/download.html AMD's APP SDK (optional): http://developer.amd.com/sdks/AMDAPPSDK/downloads/Pages/default.aspx NVidia's CUDA Toolkit (optional): http://developer.nvidia.com/cuda-toolkit AMD's APPML (optional): http://developer.amd.com/libraries/appmathlibs/Pages/default.aspx Intel's Integrated Performance Primitives (highly recommended): http://software.intel.com/en-us/articles/intel-ipp/#support

If you're running with an NVidia card, installing the CUDA toolkit may result in big performance gains. Similarly, AMD/ATI customers should install the AMD APP SDK.

Once the prerequisites are installed, run our installer script as super-user to build Triton on your system, along with the OpenGL sample application. If CMake gives you errors about missing variables such as GLUT_Xi_LIBRARY or GLUT_Xmu_LIBRARY, you may need to install the following packages first:

sudo apt-get install libxmu-dev libxi-dev

Please don't hesitate to contact support@sundog-soft.com if you have trouble building Triton on your system.

Using Triton's headers

Under the "Additional include directories" field in your project's "C/C++" property page, add the following:

$(TRITON_PATH)/Public Headers

With this in place, you can simply

#include "Triton.h"

to gain access to Triton's capabilties.

C# developers will simply reference the TritonClassLibrary project in their own project, and place a

using Triton;

prior to referencing classes within the TritonClassLibrary.

Intializing Triton

There are three main objects you'll need to create at startup in order to use Triton.

The first thing you'll need is a Triton::ResourceLoader. This class lets Triton access its shaders, DLL's, and texture resources, and it's what you'll use to tell Triton where these resources are located. The SDK includes a "resources" directory that you may redistribute, and you just need to provide an absolute or relative path to where you installed this directory in the Triton::ResourceLoader's constructor. If you're interested in integrating Triton with your own resource manager, see Integrating with your own resource manager.

Next, you'll need to create and initialize a Triton::Environment object, using the Triton::ResourceLoader you just made. The Triton::Environment class lets you specify the coordinate system, rendering system, and environmental conditions affecting Triton's water. For example, to integrate Triton with a DirectX11-based simluation application in a geocentric coordinate system using the WGS84 ellipsoid with the Z axis pointing through the poles, you'd call Triton::Environment::Initialize() with the parameters Triton::WGS84_ZUP and Triton::DIRECTX_11 as well as the ResourceLoader you created. You'll find support for flat-earth coordinate systems and spherical systems as well, with "up" on the Z or Y axes, and for OpenGL 2.x, 3.x, 4.x, DirectX9, and DirectX11. Be sure to check for error codes from Triton::Environment::Initialize() - the most likely problem is that the resource path used to create your ResourceLoader isn't quite right.

If you have purchased a license for Triton, call Triton::Environment::SetLicenseCode() to remove the evaluation restrictions on the SDK.

You'll then need to add some wind to Triton's simulation, or there won't be any waves. Create one or more Triton::WindFetch objects and add them via Triton::Environment::AddWindFetch(). A wind fetch represents a region of a specific wind speed and direction, which may be localized to an ellipsoidal area. If more than one wind fetch is present at a location, they'll be added together. Alternately, you can use the Triton::Environment::SimulateSeaState() method to quickly simulate a given state on the Beaufort scale.

Finally, you'll create the Triton::Ocean object itself, using the Triton::Environment you've created. There are three Triton::WaterModelTypes you can select from when constructing an Ocean: TESSENDORF, JONSWAP, and PIERSON-MOSKOWITZ. All three use fast-Fourier transforms to simulate thousands of waves at once. TESSENDORF uses the same algorithms used in feature films, while JONSWAP provides a more realisitc wave spectrum that is a better fit for maritime training applications. PIERSON-MOSKOWITZ is similar to JONSWAP, but cannot handle the effects of wind "fetch length," or how far the wind has been travelling. Usually we recommend starting with JONSWAP.

If Triton::Ocean::Create returns a null pointer, see Troubleshooting tips to figure out what's going on.

A complete example of initializing Triton's objects follows.

// Create the Triton objects at startup, once we have a valid GL context in place
bool InitTriton()
{
    // We use the default resource loader that just loads files from disk. You'll need
    // to redistribute the resources folder if using this. You can also extend the
    // ResourceLoader class to hook into your own resource manager if you wish.
    resourceLoader = new Triton::ResourceLoader("..\\..\\resources\\");

    // Create an environment for the water, with a flat-Earth coordinate system with Y
    // pointing up and using an OpenGL 2.0 capable context.
    environment = new Triton::Environment();
    Triton::EnvironmentError err = environment->Initialize(Triton::FLAT_YUP,
        Triton::OPENGL_2_0, resourceLoader);
    if (err != Triton::SUCCEEDED) {
        ::MessageBoxA(NULL, "Failed to initialize Triton - is the resource path passed in to "
            "the ResouceLoader constructor valid?", "Triton error", MB_OK | MB_ICONEXCLAMATION);
        return false;
    }

    // Substitute your own license name and code, otherwise the app will terminate after
    // 5 minutes. Visit www.sundog-soft.com to purchase a license if you're so inclined.
    environment->SetLicenseCode("Your license name", "Your license code");

    // Set up wind of 10 m/s blowing North
    Triton::WindFetch wf;
    wf.SetWind(10.0, 0.0);
    environment->AddWindFetch(wf);

    // Finally, create the Ocean object using the environment we've created.
    // If NULL is returned, something's wrong - enable the enable-debug-messages option
    // in resources/triton.config to get more details on the problem.
    ocean = Triton::Ocean::Create(environment, Triton::TESSENDORF);

    return (ocean != NULL);
}

Rendering each frame

If your camera includes a region of your scene containing water, you'll need to render your Ocean object as part of your scene. Doing involves three steps:

Updating Triton's lighting conditions

Call Triton::Environment::SetDirectionalLight() to specify the color and direction of sunlight (or moonlight.) This information will be used to create specular reflections of the sun or moon in the water. Note this is the direction to the sun or moon, not from it - getting the direction wrong will lead to invalid coloration of the water.

Triton::Environment::SetAmbientLight() provides the ambient skylight used to light the seafoam and the water itself, if not environment map is provided (see Integrating environment cube maps with Triton)

You might use Triton in conjunction with a system that provides scene lighting from dynamic time of day effects, such as Sundog Software's SilverLining library (see http://www.sundog-soft.com/) Or, these lighting values might be derived from the skybox texture you're using.

For example:

// Position the sun 45 degrees up in the sky at full brightness:
Triton::Vector3 lightPosition(0, 1.0 / sqrt(2.0), 1.0 / sqrt(2.0));
environment->SetDirectionalLight(lightPosition, Triton::Vector3(1.0, 1.0, 1.0));

// Ambient color of the sky:
environment->SetAmbientLight(Triton::Vector3(0.6, 0.9, 0.9);

Updating Triton's camera matrices

Since Triton does not rely on fixed function pipelines, you'll need to tell it explicitly what your camera matrices are so we may render the ocean consistently with the rest of your scene. Just pass in the modelview and projection matrices for your scene using Triton::Environment::SetCameraMatrix() and Triton::Environment::SetProjectionMatrix(). Both methods take in an array of 16 doubles. For example:

double projection[16];
double modelview[16];

// How you retrieve your camera matrices will vary depending on the engine
// you're using, so we'll just postulate the existence of these methods:
GetProjectionMatrix(projection);
GetModelviewMatrix(modelview);

environment->SetCameraMatrix(modelview);
environment->SetProjectionMatrix(projection);

Engines sometimes vary on whether they expose row-major or column-major matrices. If your ocean isn't rendering properly, try transposing the matrix before passing it in. If all else fails, try constructing these matrices from scratch using the camera's position, field of view, clip planes, and aspect ratio. Refer to the sample code included with Triton for examples.

Passing in your matrices in C# is a little tricky. Here's an example of creating a projection matrix in XNA and passing it into Triton; use the same technique for the camera matrix.

projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 10.0f, 100000.0f);

double[] m = new double[16];

m[0] = projection.M11; m[1] = projection.M12; m[2] = projection.M13; m[3] = projection.M14;
m[4] = projection.M21; m[5] = projection.M22; m[6] = projection.M23; m[7] = projection.M24;
m[8] = projection.M31; m[9] = projection.M32; m[10] = projection.M33; m[11] = projection.M34;
m[12] = projection.M41; m[13] = projection.M42; m[14] = projection.M43; m[15] = projection.M44;

SWIGTYPE_p_double matrix4 = TritonEnvironment.new_double_array(16);
for (int i = 0; i < 16; i++)
{
    TritonEnvironment.double_array_setitem(matrix4, i, m[i]);
}
environment.SetProjectionMatrix(matrix4);
TritonEnvironment.delete_double_array(matrix4);

Rendering the Ocean

With the lighting and camera information in place, we can now draw our ocean. Just call Triton::Ocean::Draw() and you're done. For example:

// Draw the ocean for the current time sample
if (ocean) {
    DWORD millis = timeGetTime();
    ocean->Draw((double)millis * 0.001);
}

Note that an explicit time sample is passed in, so you may manipulate the passage of time however you wish. Since the ocean may draw transparent spray particles, you'll usually want to call Triton::Ocean::Draw() at the end of your frame for proper sorting. If you need to draw the ocean surface and the particles separately, you'll find parameters on Ocean::Draw() to let you do that.

Ocean::Draw() renders an infinite ocean; to draw user-defined patches of water geometry, see Rendering User-Defined Patches of Geometry.

Rendering User-Defined Patches of Geometry

In addition to drawing infinite oceans with the Triton::Ocean::Draw() method, Triton may also be used to shade your own water geometry. This allows you to use Triton for smaller bodies of water. When used together with Triton::Ocean::SetDepth(), you may use Triton to render ponds, lakes, and water of any size and depth.

The OpenGLPatchSample, DirectX9PatchSample, and DirectX11PatchSample illustrate this technique. You'll use the Triton::Ocean::SetPatchShader() method to set up the state and shaders required to draw Triton's water, then draw a flat mesh where you want your water to appear, and finally call Triton::Ocean::UnsetPatchShader() to restore the previous state.

As you'll be doing your own drawing, you'll need to take care that the proper culling state is in place for your water patch. Also ensure that your depth test is set to "less than or equal" for best results.

Here's an example of rendering user-defined water geometry in OpenGL:

// Explicitly update the ocean simulation once per frame
environment->SetSeaLevel(5);
ocean->UpdateSimulation(time);

// Bind our vertex and index arrays to draw our mesh
glBindBuffer(GL_ARRAY_BUFFER, vboID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idxID);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(4, GL_FLOAT, 0, 0);
glDepthFunc(GL_LEQUAL);

// Have Triton set all the state required to render our mesh as water
// with floating-point vertices with a stride of 16 bytes.
// We optionally pass in a 4x4 model matrix to translate the patch
ocean->SetPatchShader(time, 16, 0, false, modelMatrix);

// Draw our mesh - Triton will make it look like water...
glEnable(GL_CULL_FACE);
glFrontFace(GL_CW);
glDrawElements(GL_TRIANGLE_STRIP, nIndices, GL_UNSIGNED_INT, 0);

// Restore the previous state.
ocean->UnsetPatchShader();

Additional water patches per frame may be drawn in the same manner, but be sure that Ocean::UpdateSimulation() is only called once per frame to preserve performance. You may call Ocean::UpdateSimulation() from another thread if you wish. Also, take care that every call to Ocean::SetPatchShader() is balanced with a call to Ocean::UnsetPatchShader().

If you are drawing many patches in one scene, it will be most efficient to call Ocean::SetPatchShader() once prior to drawing all of them, and then call Ocean::SetPatchMatrix() to position each individual mesh. This allows you to avoid the overhead of SetPatchShader() on every individual mesh, when all you need to do is change the position for each one.

Refer to the sample code provided for DirectX for more examples. Drawing user-defined geometry is supported in geocentric and flat-Earth coordinate systems, but only works with Tessendorf waves.

C# users will want to refer to Updating Triton's camera matrices for an example of passing a 4x4 matrix from C# into Triton, if you need to pass a translation matrix in to Ocean::SetPatchShader(). The TritonOcean class in the TritonClassLibrary also includes the helper functions for manipulating double arrays used in that example with TritonEnvironment.

Integrating environment cube maps with Triton

By default, Triton will just reflect whatever color you passed in with Triton::Environment::SetAmbientLight() in the water. For more realistic reflections, you'll want to set an environmental cube map using Triton::Environment::SetEnvironmentMap(). Doing so is optional, but it makes a big difference.

The type of texture parameter passed into this method will vary depending on what renderer you're using. OpenGL users should pass in a GLuint representing the texture ID of the cube map. DirectX9 users should pass in a LPDIRECT3DCUBETEXTURE9. (C# users may obtain this from the pComPtr member of their texture object.) DirectX11 users should pass a ID3D11ShaderResourceView pointer.

If you find that reflections seem to be coming from the wrong face of your cube map, you can correct for this using the Matrix3 parameter passed into Environment::SetEnvironmentMap(). This is especially common with DirectX due to the left-handed convention of DirectX cube maps. For example, if you seem to be getting reflections from the bottom of your environment map instead of from the top, and your "up" direction is the positive Y axis, pass in a scaling matrix to scale Y by -1.

Examples of loading and constructing properly constructed cube maps from disk may be found in the SkyBox classes in the SDK's sample code. You might also dynamically generate these cube maps based on a physical simulation of the sky such as Sundog Software's SilverLining library. The demo application for Triton found on our website does exactly that.

Shutting down

At shutdown time, C++ users just delete the Triton objects in the reverse order in which they were created - first your Triton::Ocean, then your Triton::Environment, and finally your Triton::ResourceLoader. We'll clean up our memory and resources. For example:

// Clean up our resources
void Destroy()
{
    if (ocean) delete ocean;
    if (environment) delete environment;
    if (resourceLoader) delete resourceLoader;
}
 All Classes Files Functions Variables Friends