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.

If you are using osgEarth, getting started is even easier. osgEarth includes a simple driver for Triton that can get you up and running quickly.

Configuring your project

Linking with Triton under C++ and Windows

Triton provides libraries for win32 and x64 applications created with Microsoft Visual Studio 2010, 2012, 2013, 2015, or 2017 (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 2010 libraries are in "lib/vc10", Visual Studio 2012 libraries are in "lib/vc11", Visual Studio 2013 libraries are in "lib/vc12", and Visual Studio 2015 or 2017 libraries are in "lib/vc14". 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 - 2017 libraries are linked against the Windows SDK 8, and only support desktop applications at this time.

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 suppo.nosp@m.rt@s.nosp@m.undog.nosp@m.-sof.nosp@m.t.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 will 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 suppo.nosp@m.rt@s.nosp@m.undog.nosp@m.-sof.nosp@m.t.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
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.

If you are using volumetric decals (see Applying decals to the water surface ) in your scene, you will also need to perform a second pass for the decals. To do this, call SetPatchShader() a second time with decalPass = true. If decals are present in the view, SetPatchShader() will return true, in which case you must render your water geometry a second time. Then balance that second call to SetPatchShader() with a UnsetPatchShader, also with decalPass = true.

Here's an example of rendering user-defined water geometry in OpenGL, with support for decals:

// 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, false);
// 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();
// Do another pass for decals, if needed. Note the "true" being passed on
// the decalPass parameters below.
if (ocean->SetPatchShader(time, 16, 0, false, modelMatrix, true)) {
glEnable(GL_CULL_FACE);
glFrontFace(GL_CW);
glDrawElements(GL_TRIANGLE_STRIP, nIndices, GL_UNSIGNED_INT, 0);
}
ocean->UnsetPatchShader(0.0, 0, true);

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.

Multi-threaded rendering with Triton

Prior to Triton 4.0, to support multi-threaded environments, the user had to specify the following flag in the config file, and do the drawing as usual:

thread-safe = yes

This is a very naive approach however, as it effectively has a top level mutex, forcing Triton to do its work for multiple views as well as the rendering in a serialized fashion.

Starting with Triton 4.0, Triton supports fully featured multi-threaded rendering for both DirectX 11 and OpenGL. But to leverage this the application must be carefully designed and the new APIs used correctly and in the correct order.

This is best explained using an example. Let's say that you have a VR application with left and right views, where each camera is a slightly different frustum corresponding to the left and right eye.

The first order of business is for Triton to generate the wave geometry and wake effects for the ocean. This necessitates a call to Ocean::UpdateSimulation. This call needs to happen in the main thread, where the DirectX 11 device/immediate context or the OpenGL context are current.

The appearance of local waves may change if the wind conditions differ between views. But for VR, the views are close enough to be considered to be at the same location for our purposes - so you can get away with calling Ocean::UpdateSimulation only once per frame. UpdateSimulation() requires a Camera parameter; you can just pass in either the left or right view's Camera object here, or create a dummy Camera positioned between the two eyepoints.

Following this, you need to do a Ocean::UpdateSimulation() and Ocean::DrawConcurrent() for the left and the right cameras respectively. These calls can happen in completely separate threads for each camera. And there is no limit on how many views you can render simultaneously (this example only has two.)

Following this, you need to call Ocean::PostDrawConcurrent(), in the main thread.

The sequence of calls therefore is:

Ocean::UpdateSimulation(time, main camera/viewing frustum) // in main thread
Ocean::UpdateSimulation(time, left camera)
Ocean::DrawConcurrent(left camera) // can happen in a completely different thread
Ocean::UpdateSimulation(time, right camera)
Ocean::DrawConcurrent(right camera) // can happen in a completely different thread
Ocean::PostDrawConcurrent() // in main thread, after both view threads are done calling DrawConcurrent()

If you have completely disparate views (e.g. separate windows with viewpoints that are many kilometers apart), and these viewpoints may have different wind conditions or different sets of nearby wakes, then the strategy of sharing a single Ocean geometry between your views no longer works well. You can still just call Ocean::DrawConcurrent()from separate threads using unique Camera objects to simulate and render the Ocean for each view concurrently, but you'll want to use separate Ocean instances for each view in this case.

Besides the Camera, one needs to pass in an additional 'context' parameter. Depending on whether we are using an OpenGL or DirectX 11 renderer, this means different things, and we will describe this next.

DirectX 11 multi-threaded rendering:

A key observation of any rendering is that the actual work of generating commands for the GPU to execute and then actually executing them on the GPU can effectively be decoupled.

Therefore, the command list generation for each different view can proceed in completely different threads. After all command lists for each view are generated, they can be rapidly executed on the GPU on the main thread.

To this end, DirectX 11 leverages 'deferred contexts' and 'command lists':

https://msdn.microsoft.com/en-us/library/windows/desktop/ff476892(v=vs.85).aspx

In DirectX 11 , you would want to create a 'deferred context/command list' pair for each view/thread in question and pass them in to the call to Ocean::DrawConcurrent, along with the camera for the view/thread in question. When a Ocean::DrawConcurrent is specified using a valid DirectX 11 deferred context and camera, Triton will generate the correct set of commands and append it to the context/command list passed in, but not actually do the rendering. Following this you actually 'execute' the command list using the appropriate DirectX 11 API.

We have a fully featured sample DirectX11MultiThreadedSample that demonstrates this multi-threaded rendering for DirectX 11.

OpenGL multi-threaded rendering:

OpenGL rendering follows the same paradigm as DirectX 11. Unfortunately, at this time, the OpenGL specification does not include command lists. Vendor specific APIs/extension are available however, the most complete being Nvidia's:

https://www.khronos.org/registry/OpenGL/extensions/NV/NV_command_list.txt

We however did not want to tie ourselves to a specific vendor/extension, therefore we created our own abstract API along the lines of DirectX 11 context/command list API. In OpenGL a 'context' means a completely different thing however (meaning the actual context for the window/offscreen buffer), so we call our command list/contexts for each view/thread an 'OpenGL Stream'.

Similar to DirectX 11, we create an OpenGL Stream for each view/thread in question using: Environment::CreateOpenGLStream. Once the stream is created (and prior to any drawing), you must call Ocean::Initialize with the stream and the camera in question as parameters, indicating your intent to render with this stream and camera. This call must happen where an OpenGL context is current. Following this, stream pointer and camera is what is going to be passed in as parameters to Ocean::DrawConcurrent. These calls can then proceed in completely different threads (an actual OpenGL context is not even required in each thread, since the Ocean::DrawConcurrent call is effectively just appending the commands to the OpenGL stream). Following this we execute each of the steams using Environment::ExecuteOpenGLStream. Again, execution must happen where there is a current OpenGL context.

We have a fully featured sample OpenGLMultiThreadedSample that demonstrates this multi-threaded rendering for OpenGL. There are various draw strategies that you can use. Please refer to SampleDeclares.h for additional information. Project files for Visual Studio 2013 and 2015/2017 are included; for Linux, refer to the README_LINUX file for build guidance.

One important thing to note is that you don't want to actually render/execute the streams in multiple threads, each thread having their own OpenGL contexts, because internally the GPU is going to serialize the calls anyway, and there is no performance improvement. If at all, there is degradation because of OpenGL context switches, etc. Nonetheless, some rendering engines out there (e.g. OpenSceneGraph) do support multi-context, threaded rendering, and for completeness we have provided a code path/draw strategy that demonstrates how to do multi-threaded rendering when there are actual multiple OpenGL contexts. Please also note that we currently only support shared OpenGL contexts, that is to say the contexts are sharing resources (textures, vertex buffers, etc), which is what you want to ideally do any way.

Lastly, and quite importantly, this flag:

thread-safe = yes

is effectively ignored when Ocean::DrawConcurrent is used. Triton internally is completely thread safe as long as you create/pass in the correct DirectX 11 deferred contexts/OpenGL streams, and cameras for the thread(s) in question as demonstrated in the DirectX 11 and OpenGL samples. You would also want to remove any additional mutexes in your application code and adjust the calls to Triton accordingly.

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;
}