Docstoc

Waves

Document Sample
Waves Powered By Docstoc
					Wave
Simulation
MSc Computer
Games and
Entertainment

Matt Wash, Mihai Fota,
Panagiotis Armagos
Table of Contents
1     Introduction ........................................................................................................... 3
2     Aims and Objectives ............................................................................................ 4
3     Implementation .................................................................................................... 5
    3.1    Wave generation (Matt Wash) .................................................................... 5
      3.1.1      Waves class .............................................................................................. 5
      3.1.2      waveTrain class ........................................................................................ 8
    3.2    Height map (Panos Armagos) .................................................................... 12
    3.3    Sky box (Panos Armagos) ........................................................................... 14
    3.4    Textures (Panos Armagos) ........................................................................... 15
    3.5    Interface (Mihai Fota) .................................................................................. 15
    3.6    Rendering (Matt Wash) ............................................................................... 16
4     Remaining Issues ................................................................................................. 18
5     Conclusion ........................................................................................................... 19
6     References ........................................................................................................... 20




                                                          Page 2
1 Introduction
   This document contains implementation details pertaining to the wave
simulation project. This project has been created by a 3 man group
containing students from the MSc Computer Games and Entertainment
degree and has been developed using the requirements provided by the
Mathematics course in said degree, and described in more detail, from the
development team’s viewpoint, in section 2.0 Aims and Objectives.
  The group took an object oriented development process and classes were
created for the elements used in the simulation, which will be described in
more detail in section 3.0 Implementation.




                                  Page 3
2 Aims and Objectives
  This section contains the aims and objectives the team has strived to
achieve. Most of these are laid out in the requirements set out by Gareth
Edwards, lecturer at Goldsmith University of London.
The wave simulation will contain realistically rendered water, shore and sky.
This means that the waves shapes and movement must look real, be
affected by the depth of the ocean and the waves must be refracted and
break on the shore. The shore itself must be curved into a bay or similar.
  The user has an interface to change the parameters used in the wave
creation and movement.




                                   Page 4
3 Implementation

3.1 Wave generation (Matt Wash)
  The wave simulation was implemented in two main classes – waves and
waveTrain which are described below.

3.1.1 Waves class
   The waves class is responsible for the creating, updating and rendering a
LPD3DXMESH object and as a result is created in the rtvsD3dApp class
setupDX() method. It also has its own cleanupDX() method which is called by
rtvsD3dApp::cleanupDX() in order to release its DirectX COM objects which
include the wave mesh, three IDirect3DTextures and an ID3DXEffect object.

3.1.1.1 Creating a waves object
  An instance of the initInfo struct is created and its properties are initialized
before being passed to the waves class constructor in setupDX(). This struct is
used to specify the size of the wave mesh and the number of rows and
columns it will contain i.e. the vertex count. This struct also provides the depth
data that is used to simulate the sea bed and shoreline in the simulation:
      waves::initInfo wavesInfo;
      wavesInfo.rows = 64;
      wavesInfo.cols = 256;
      wavesInfo.xMin = -30.0f;
      wavesInfo.xMax = 30.0f;
      wavesInfo.zMin = -30.0f;
      wavesInfo.zMax = 30.0f;
      wavesInfo.heightMapFileName = "heightmaps/heightmap64.raw";


      _pWaves = new waves(wavesInfo, _dirLight, pd3dDevice);




                                     Page 5
   The waves class constructor also takes a struct defining the properties of a
directional light that is used when rendering the mesh and a pointer to the
Direct3D device. The constructor makes use of similar code to that provided
by Gareth Edwards to initialize its LPD3DXMESH member variable and create
a flat “plane” according to the dimensions, rows and columns in the initInfo
struct. A few modifications were made to the example code, however. Firstly
the mesh is created using the normalMapVertex vertex declaration defined
in vertex.h. This vertex format contains, position, tangent, binormal and a
single set of texture coordinates. Secondly, each calculated initial vertex
position is stored in a separate vector<D3DXVECTOR3> member variable:


// for each column of the mesh
      for (col=0; col<=celCols; col++)
      {
            ...
      _initialVertPositions.push_back(D3DXVECTOR3(x, 0.0f, z));
            ...
      }
   These values are stored as the parametric wave simulation requires the
resting position of each vertex in order to calculate the new position each
frame.
  D3DXComputeTangentFrame is then used to compute the tangent,
binormal and normal vectors for the (currently) flat mesh and various textures,
a material and an ID3DXEffect object are initialized ready for rendering the
mesh (see section 4.6).
    The waves class has two waveTrain pointer member variables which are
initialized next:


      _pWaveTrain1 = new waveTrain(0.4f, 0.7f, _depthMap);
      _pWaveTrain1->applyWind(_windSpeed, _windDirection);
      _pWaveTrain2 = new waveTrain(10.5f, 0.4f, 0.0f, 0.3f, _depthMap);




                                    Page 6
_pWaveTrain1’s properties are updated when the user changes various
parameters in the user interface (see section 4.5), one of which is wind speed
and direction, so we apply some defaults for these here. The amplitude and
angular velocity properties of the waveTrain object are calculated as a
result. This is in constrast to _pWaveTrain2, which is provided with fixed values
for amplitude and angular velocity (highlighted in green). The properties of
pWaveTrain2 are not affected by the UI controls and are fixed for the lifetime
of the program. For now, note how the depth data in the form of a
heightmap object is also passed to each waveTrain so that surface data
can be pre-computed. The waveTrain class, however, will be described fully
in section 4.1.2.
  Immediately after the waves object has been constructed, the
environment map loaded by the sky class is obtained for rendering purposes:


   _pWaves->setEnvMap(_pSky->getEnvMap());



3.1.1.2 Executing the simulation
  Every frame, in rtvsD3dApp:display(), the update() method is called
on the waves class instance:

      _pWaves->update(getDeltaTime(), elapsedTime);


update() first uses the number of seconds that have elapsed since the last
frame to scroll the texture coordinates for a pair of normal maps according to
a predefined velocity value. The idea of this is to create a slow rippling effect
on the water surface.
update()     then    passes      on      its  phaseShift argument   to
executeSimulation() which iterates over the vertices in the wave mesh,
evaluating each one for both waveTrains and interpolating between the
newly calculated vertex positions as follows:
      // for each vertex
      for (DWORD v=0; v<_vertices; v++)
      {
      // ask the wave trains to evaluate the vertex, returning a new position.
      D3DXVECTOR3 newPos1 = _pWaveTrain1->evaluateSurfacePoint(v,




                                     Page 7
      _initialVertPositions[v], pVertexData->pos.y, phaseShift);
           D3DXVECTOR3               newPos2           =           _pWaveTrain2-
      >evaluateSurfacePoint(v,
      _initialVertPositions[v], pVertexData->pos.y, phaseShift);
      // interpolate the resulting positions from each wave train to get the
      final position.
      D3DXVECTOR3 finalPos = (_waveTrainLerpVal * newPos1) + ((1.0f -
      _waveTrainLerpVal) * newPos2);
      // update the vertex in the buffer.
      pVertexData->pos.x = finalPos.x;
      pVertexData->pos.z = finalPos.z;
      pVertexData->pos.y = finalPos.y;
      pVertexData++;
      }
  Each waveTrain requires the index (or ordinal) of the vertex as its
precomputed surface data is stored in the same order as the vertices in the
wave mesh. The initial vertex position, current vertex y coordinate, and phase
shift value are also supplied. The value used to linearly interpolate between
the two wave train results, _waveTrainLerpVal, is a value between 0 and 1
and can be changed by the user via the slider control in the user interface
(see section 4.5).



3.1.1.3 Exposing wave train parameters
   The remaining public methods in the waves class are accessor methods for
various member variables that affect rendering and configure the wave
simulation.

3.1.2 waveTrain class
  As mentioned in the previous section, the waves class has two waveTrain
pointer member variables that are initialized in the waves class constructor.
The waveTrain class is designed to encapsulate all of the parameters and
functionality required to execute the simple model of ocean waves
described in Fournier1 for each vertex of a wave mesh. It has member
variables for wave amplitude (r), angular velocity, wind shear, wave shape,
wave number at infinite depth (Kinf) and wave direction.



                                     Page 8
  These member variables are initialized when a waveTrain object is
constructed but most can be also set via accessor methods. Wave number
at infinite depth is not set directly, but as a result updating either the wave
shape or wave amplitude properties as the following relationship holds:


                              Kinf = wave shape / r


   As Fournier explains, kr defines the wave shape and if it becomes greater
than 1 the waves begin to intersect. The higher the wave shape value, the
steeper the waves.



3.1.2.1 Precomputing wave data
  The depthMap parameter passed into the waveTrain constructor specifies
the relative depth (a floating point value between 0 and 1) at each vertex in
the wave mesh. As we have this information and the value of Kinf has been
calculated we can use the following equation defined in Fournier 1 to pre-
compute the value of k for each depth value:




   Fournier1 also models waves breaking on the shore by changing the major
orbits of the cycloids so they become increasingly more elliptical (until flat) as
the depth decreases, their major axis aligning with the shore. The equations
he defines depend on depth, three scaling constants - ko, kx and kz, and the
slope of the bottom in the direction of wave travel – y. However, by assuming
the slope, y, is also constant rather than calculating it, we can pre-compute
some of the components of the wave breaking equations and speed up the
simulation. In summary, we store all pre-computed values for each vertex in a
waveVertexData struct:
  struct waveVertexData
  {
      float depth;
      float k;
      float cosAlpha;
      float sinAlpha;




                                     Page 9
       float Sx;
       float Sz;
  };
   The waveTrain::precomputeWaveData() method iterates over each
value in the heightmap and performs the pre-computation, storing the results
in a vector of the aforementioned struct:
  void waveTrain::precomputeWaveData(heightMap& depthMap)
  {
       // scaling constants.
       const float ko = 1.0f;
       const float kx = 10.0f;
       const float kz = 1.0f;


       // slope of the ocean floor in the direction of wave travel.
       // the model is simplified by assuming this is constant.
       const float gamma = 0.2f;
       // loop through the depth map values and pre-compute the wave
       data.
       for(int i = 0; i < depthMap.numRows(); ++i)
       {
              for (int j = 0; j < depthMap.numCols(); ++j)
              {
                    waveVertexData data;


                    // depth
                    data.depth = depthMap(i, j);


                    // wave number
                    data.k = _kInf / (sqrtf(tanhf(_kInf * data.depth)));


                    // wave breaking data (elliptical orbits in shallow water)


                                       Page
                                        10
                        float alpha = gamma * expf(-ko * data.depth);
                        data.sinAlpha = sinf(alpha);
                        data.cosAlpha = cosf(alpha);
                        data.Sx = 1.0f / (1.0f - expf(-kx * data.depth));
                        data.Sz = data.Sx * (1.0f - expf(-kz * data.depth));


                        _precompData.push_back(data);
                    }
        }
  }


  Note: whenever the user changes the amplitude or wind speed
parameters for wave train 1 via the UI controls, only the wave number, k, must
be recomputed for each vertex. This is performed by the
waveTrain::recomputeWaveNumbers() method.



3.1.2.2 Evaluating each vertex
    As mentioned in 4.1.1.2, the waves::executeSimulation() method
iterates over each vertex in the wave mesh, passing each vertex ordinal,
initial position, current y coordinate and the phase shift as parameters to the
evaluateSurfacePoint() method on both wave trains.

  waveTrain::evaluateSurfacePoint(DWORD ordinal, D3DXVECTOR3 restPos,
  float deltaY, float phaseShift)
  The       thing evaluateSurfacePoint() does is look up the
            first
waveVertexData object for the given vertex to gain access to the relevant
pre-computed data:
  waveVertexData data = _precompData[ordinal];


  It then adjusts the wave amplitude according to the current vertex wave
number and wave shape property:


                             _amp = _waveShape / data.k;




                                           Page
                                            11
  Now, using modified versions of equations 1a and 1b from Fournier 1 that
take into account wave direction (Tessendorf2) and wind shear, we calculate
the phase angle as follows using pre-computed k:


       float phaseAngle = (data.k * D3DXVec3Dot(&_direction, &restPos)) -
       (_w * phaseShift) - (_windShear * deltaY);


  Note that in Fournier’s equation to model wind shear he multiplies the wind
shear factor, which is normally between 0 and 2.5, by Δt. We omit this
variable and use a reduced wind shear factor instead.
  The final part of the calculation uses the pre-computed values in the
waveVertexData struct to model waves breaking on the shore:

  D3DXVECTOR3 newPos;
  newPos.x = restPos.x + (_direction.x * _amp * data.cosAlpha * data.Sx *
  sinf(phaseAngle) + data.sinAlpha * data.Sz * cosf(phaseAngle));
  newPos.z = restPos.z + (_direction.z * _amp * data.cosAlpha * data.Sx *
  sinf(phaseAngle) + data.sinAlpha * data.Sz * cosf(phaseAngle));
  newPos.y = -_amp * data.cosAlpha * data.Sz * cosf(phaseAngle) +
  data.sinAlpha * data.Sx * sinf(phaseAngle);
// return the result
       return newPos;


  It can be seen from the orange-highlighted values that a fair amount of
computation is avoided using this mechanism.



3.2 Height map (Panos Armagos)
  There were many attempts and ideas on how to create the shoreline along
with a sea bed such as
      Implementing a static 3d object created in Blender representing an
       island with a seabed by exporting it in a .x format. The goal was to
       calculate the height value between the waves and the sea bed by
       calculating the distance between the vertices.




                                    Page
                                     12
      Introducing a height map so by reading the byte values of different
       channels (R,G,B) we could determine the depth of the seabed (using a
       .tga reader).
      Using .raw files for the same reason. This type of files contains minimally
       processed data and they are called digital negatives because the
       contents of raw files include more information, and potentially higher
       quality, than the converted results.
  Raw files were picked for this assignment. The reading and storage of the
byte data is simple enough. The data are stored in a vector called rawbytes.
       inFile.read((char*)&rawbytes[0], (streamsize)rawbytes.size());
  After this process scaling and offset (values are arguments of the class) is
applied to the height values and then they are copied to a lookup table
called mHeightmap.
       mHeightMap(i, j) = (float)rawbytes[k] * heightScale + heightOffset;
  The number of rows and columns are returned by these functions.
       int Heightmap::numRows()const
       {
       return mHeightMap.numRows();
       }
       int Heightmap::numCols()const
       {
       return mHeightMap.numCols();
       }
  Also the i , j element of the table can be returned by:
       float& Heightmap::operator()(int i, int j)
       {
       return mHeightMap(i, j);
       }
  In the heightmap class are also included three more funtions that refine the
height map’s outcome and checks for accuracy:
              inBounds: Returns true if the specified indices reference a
               heightmap entry; else returns false.




                                        Page
                                         13
            sampleHeight3x3 and filter3x3: Here is where every element of
             the map is filtered by averaging each element with his eight
             neighboring ones. sampleHeight3x3 just returns a filtered height
             map element.
  In the main class is where a 128 x 128 height map is created by loading the
raw file and scaling it by 1.0f.
             mHeightmap.loadRAW(128, 128, "5.raw", 1.0f, 0.0f);
  After that in the Beach function the grid created (which has the same size
as the raw file 128 x 128) has it’s height values (y) modified in order to create
the shore and the seabed. Because the height map used is grayscaled,
meaning that black has the value of 0 and white 255, we divide each height
by 255 so we can normalize the values to a range of 0 to 1.
      pVertexData[index].x = pMeshRestPositions[index].x;
      pVertexData[index].y = mHeightmap(row,col)/255.0f;
      pVertexData[index].z = pMeshRestPositions[index].z;
  Then the modified grid is drawn textured and translated in the appropriate
location to match our scenery.



3.3 Sky box (Panos Armagos)
    For the creation of the skybox Terragen 2.0 was used to create the sunset
and the enviroment. Six Cameras were setup accordingly in order to create
the six faces of a cube ( pointing North, South, East, West, Up, Down) which
later on were put together using DirectX texture tool to produce the final .dds
file.
  A sky class was created to impelment the skybox texture which take as
arguments the filename of the .dds the radius of the sky and the device. Then
the dome is created
      HR(D3DXCreateSphere(pd3dDevice, skyRadius, 30, 30, &_sphere, 0));
  The sky is always centered about the camera’s position
      D3DXVECTOR3 p = cam.getPosition();
      D3DXMatrixTranslation(&W, p.x, p.y, p.z);
      HR(_fx->SetMatrix(_hWVP, &(W * cam.getViewMatrix() * matProj)));




                                      Page
                                       14
3.4 Textures (Panos Armagos)
   All the textures where created in Photoshop. A combination of sand,
mountain, seabed tiles where used to recreate the final image. The file is a
.tga which is loaded and mipmapped according to the camera’s distance
from the scene.
  D3DXCreateTextureFromFile( pd3dDevice, "texture.tga", &pBTexture );
  pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );
  pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);

3.5 Interface (Mihai Fota)
  The interface has been created with the DXUT library. The interface allows
the user to change the parameter used in the simulation to change the
shape and speed of the waves.
    In the beginning the group wanted to use button inputs to change the
parameters, but after careful deliberations it was decided to use DXUT
elements to create user input elements. It was decided to use a combo box
and a slider to change the different parameters used in the simulation. The
slider will change the values of the parameter and the waves will change in
real time.
  The following code sets up the DXUT interface;
       DXUTSetCallbackMsgProc( msgProc );
       DXUTSetCallbackKeyboard( keyboardProc );
       DXUTSetWindow(g_hWnd, g_hWnd, g_hWnd, true);
       DXUTSetDevice(g_pd3dDevice);
       g_sampleUI.SetCallback( onGUIEvent );



   The callback function “OnGuiEvent” handles the user input to the
interface.


  The interface contains the following elements:
      Combo box – the combo box gives the user the ability to change the
       parameter he wants to change;
      Slider – the slider allows the user to change the values of the
       parameter;
      Checkbox – the checkbox is used to switch the environment map on
       and off.



                                      Page
                                       15
The following code adds the elements on the screen:
       g_sampleUI.AddSlider(   IDC_SLIDER,   200, 450,   200,   24, 10,   30,    20,
       false );
      g_sampleUI.AddCheckBox(IDC_CHECKBOX,     L"Environment    Map",   170,    450,
350, 24, false, 0, false );
CDXUTComboBox* pCombo;
      g_sampleUI.AddComboBox( IDC_COMBOBOX, 0, 0, 200, 24, L'O', false,
&pCombo );

  The following parameters can be changes:
      Wind speed – the speed of the waves, the waves will multiply and
       become smaller if the wind speed decreases;
      Wave Sheer – the wind sheer parameter controls the shape of the
       waves;
      Amplitude – the amplitude controls the size of the waves;
      Angular Velocity – the angular velocity controls the speed of the
       waves;
      Wind Direction Z – the wind direction controls the starting point of the
       wind;
      Foam Quantity – the foam quantity controls how much foam is visible
       on the waves.

3.6 Rendering (Matt Wash)
  Rendering was not the focus of this assignment so I will not spend a great
deal of time explaining exactly what was done, especially as a fair amount
was adapted from examples taken from Frank Luna’s book on DirectX 9.0c3.
  However, the techniques employed were chosen as I believed they would
enhance the wave simulation itself, making it more realistic. All rendering is
achieved through use of vertex and pixel shaders defined in the sky.fx and
waves.fx files. The flexibility that the DirectX effect framework provides was
particularly useful when it came to rendering the wave mesh.




                                     Page
                                      16
   A pair of normal maps is scrolled slowly over the surface of the mesh in the
vertex shader by adjusting the texture coordinates over time. The pixel shader
then uses the average normal vector from each map to calculate the
amount of specular and diffuse lighting for each pixel. This average normal
vector is also used to perturb the vertex normal slightly when reflecting the
“to eye” vector, which in turn is used to look up the pixel colour from the
sunset cube map previously obtained from the sky class (see section 4.1.1.1).
The reflected colour is then blended with the ambient and diffuse material
properties of the “water” before the final pixel colour and alpha are returned.
The overall effect is a slowly rippling surface that reflects the sky.
   In an attempt to simulate the generation of foam by breaking waves as
Fournier1 did, I decided to use the pixel shader to blend in a foam texture
where appropriate. It occurred to me that I could use the saturated dot
product of the pixel normal and a vector pointing directly upwards as an
indicator of where and how much foam texture to blend.


  First, the pixel color is sampled from the foam texture map:
      float3 foamColor = tex2D(FoamMap, tex0);


  The amount of foam is then calculated as described earlier and multiplied
by a user-specified bias to achieve the desired effect:
      float3 vecUp = {0.0f, 1.0f, 0.0f};
      float foamAmt = (1.0f - saturate(dot(vecUp, normalW))) * gFoamBias;


<< code omitted for brevity >>


  The final material and foam colours are then linearly interpolated
according to the foam amount:
  ambientMtrl = lerp(ambientMtrl, foamAmt, foamColor);
  diffuseMtrl = lerp(diffuseMtrl, foamAmt, foamColor);


  This results in a visible foam effect for steep waves, waves affected by wind
shear and those breaking on the shore.




                                           Page
                                            17
4 Remaining Issues
   Calling D3DXComputeTangentFrame after first creating the wave mesh in
the waves class constructor works fine, however, for some reason after
executing the wave simulation for each vertex in the wave mesh, subsequent
calls to D3DXComputeTangentFrame fail. I did not have time to resolve this
problem so resorted to calling D3DXComputeNormals instead which means
that the tangent frames used for normal mapping are incorrect. Even so, the
final result appears quite acceptable.
   Another issue that I would like to have fixed is that which occurs when
resizing the application window. It is related to the waves class and I suspect
that its DirectX resources are not being managed properly.
   Unfortunately, although the heightmap class was used to define the depth
values for each vertex and simulate a curved shoreline, we were unable to
integrate the code that uses the heightmap class to create a beach mesh
mentioned in section 3.2.




                                     Page
                                      18
5 Conclusion
   On the whole, and despite the issues mentioned in the previous section, I
think we have achieved the aims and objectives set out in the assignment
brief. Ocean wave simulation is an enormous topic and it would take a far
more in depth assignment than this one to explore it fully. However, there are
some areas that could have been improved within the project scope. For
example, although most of the Fournier’s mathematical model was
implemented, some equations were simplified. The concept of wave trains
could have been explored further, perhaps, and a particle system
implemented to simulate spray. We did experiment with a mechanism that
increased the number of vertices in the wave mesh as it approached the
shore but the results were not positive. I suspect this was a result of the way
the depth information was stored but would require more time to investigate.
A more general level of detail algorithm with multiple seamless meshes of
varying resolution would have been the next thing on our list of features to
add.




                                     Page
                                      19
6 References
a) Fournier, A. (1986). A Simple Model of Ocean Waves.
b) Tessendorf, J. (2001). Simulating Ocean Water.
c) Luna, F.D. (2006). Introduction to 3D Game Programming with DirectX 9.0c
   –A Shader Approach.




                                   Page
                                    20

				
DOCUMENT INFO
Shared By:
Categories:
Tags:
Stats:
views:9
posted:12/15/2011
language:English
pages:20