# Weird Waves

So I have a 2D grid of vertices, each with a Z value representing the displacement at that point. Collectively, these form waves on the surface.

I initialize this Z-map to all zeros (state of rest), with a disturbance in the middle (-0.5, and -0.3 to surrounding 8 vertices).

A 9x9 grid:

``````
0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00
0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00
0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00
0.00	0.00	0.00	-0.30	-0.30	-0.30	0.00	0.00	0.00
0.00	0.00	0.00	-0.30	-0.50	-0.30	0.00	0.00	0.00
0.00	0.00	0.00	-0.30	-0.30	-0.30	0.00	0.00	0.00
0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00
0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00
0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00

``````

I call a method EvaluateWaves() at regular intervals to update the Z values. It takes into account preset constants, such as desired wave speed, fluid viscosity, and so on.

Since a disturbance in the middle should result in the same thing happening outward radially in all directions, I was surprised to see the result after an EvaluateWaves() call resulting in some non-symmetrical results:

``````
0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00
0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00
0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00
0.00	0.00	0.00	0.00	-0.11	-0.11	-0.11	0.00	0.00
0.00	0.00	0.00	-0.11	-0.08	-0.26	-0.08	-0.11	0.00
0.00	0.00	0.00	-0.11	-0.26	-0.21	-0.26	-0.11	0.00
0.00	0.00	0.00	-0.11	-0.08	-0.26	-0.08	-0.11	0.00
0.00	0.00	0.00	0.00	-0.11	-0.11	-0.11	0.00	0.00
0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00	0.00

``````

I am sending the grid to my fragment shader which performs the calculations and writes it out to a texture bound to the framebuffer object. The texture is ping-ponged back and forth at each evaluation.

The EvaluateWaves() call:

``````
// Re-evaluate the waves, but must be called at constant interval...
void FluidSurface::EvaluateWaves()
{
// Variables...
GLint   Viewport[4] = { 0, 0, 0, 0 };

// Setup viewport for one to one pixel = texel = geometry mapping...

// Projection should not transform...
glMatrixMode(GL_PROJECTION);
gluOrtho2D(0.0, m_Width, 0.0, m_Height);

// There should be no modelview transformations either...
glMatrixMode(GL_MODELVIEW);

// Lastly, backup viewport size, and resize to the size of grid maps...
glGetIntegerv(GL_VIEWPORT, Viewport);
glViewport(0, 0, m_Width, m_Height);

// Redirect rendering through our framebuffer object...
glBindFramebuffer(GL_FRAMEBUFFER, m_FrameBufferObject);

// Check for OpenGL errors...
PrintOpenGLErrors();

// Prepare shader state and dispatch grid location data to program...

// Install program...
glUseProgram(m_PO_EvaluateWaves);

// Prepare texture units...

// First texture unit always contains first z-map...
glActiveTexture(GL_TEXTURE0 + m_ZMaps_TextureUnit[0]);
glBindTexture(GL_TEXTURE_2D, m_ZMaps_TextureID[0]);

// Second texture unit always contains second z-map...
glActiveTexture(GL_TEXTURE0 + m_ZMaps_TextureUnit[1]);
glBindTexture(GL_TEXTURE_2D, m_ZMaps_TextureID[1]);

// Check for OpenGL errors...
PrintOpenGLErrors();

// Update uniforms...

// Z-maps...

// Previous...
glUniform1i(GetUniformIndex(m_PO_EvaluateWaves, "ZMapPrevious"),
m_ZMaps_TextureUnit[!m_SourceBufferSwitch]);

// Current...
glUniform1i(GetUniformIndex(m_PO_EvaluateWaves, "ZMapCurrent"),
m_ZMaps_TextureUnit[m_SourceBufferSwitch]);

// Bind fragment shader outputs to respective data buffers...

// Create list of attachment points for each fragment output ID...
GLuint const DrawBuffers[] = {
m_ZMaps_AttachmentPoint[!m_SourceBufferSwitch],
m_NormalMap_AttachmentPoint,
m_TangentMap_AttachmentPoint
};

glDrawBuffers(sizeof(DrawBuffers) / sizeof(DrawBuffers[0]), DrawBuffers);

// Enable required vertex arrays...
glEnableVertexAttribArray(m_GVA_GridLocations);

// Select the grid indices...
glBindBuffer(GL_ARRAY_BUFFER, m_VBO_GridLocations);

glDrawArrays(GL_POINTS, 0, m_Width * m_Height);

// Disable required vertex arrays...
glDisableVertexAttribArray(m_GVA_GridLocations);

// Swap the source buffer for next evaluation...
m_SourceBufferSwitch = !m_SourceBufferSwitch;

// Check for OpenGL errors...
PrintOpenGLErrors();

// Restore state...

// Default framebuffer...
glBindFramebuffer(GL_FRAMEBUFFER, 0);

// Viewport back to original dimensions...
glViewport(Viewport[0], Viewport[1], Viewport[2], Viewport[3]);

// Check for OpenGL errors...
PrintOpenGLErrors();
}

``````

``````
// Input variables...

// Grid location...
in vec2         GridLocation;

// Uniform variables...

// Dimensions...
uniform uint    Width;
uniform uint    Height;

// Variables for the fragment shader, none of which need be interpolated...

// Normalized location cooresponding to grid location...
out vec2        NormalizedLocation;

// Texture location cooresponding to grid location...
out vec2        TextureLocation;

// Entry point...
void main()
{
// Calculate texture location which has components in [0, 1] range...
TextureLocation = GridLocation / vec2(Width, Height);

// Calculate normalized location which has components in [-1, 1] range...
NormalizedLocation = TextureLocation * vec2(2.0, 2.0) - vec2(1.0, 1.0);

// Generate a fragment...
gl_Position = vec4(NormalizedLocation, 0.0, 1.0);
}

``````

``````
// Input variables...

// Normalized location cooresponding to grid location...
in vec2             NormalizedLocation;

// Texture location cooresponding to grid location...
in vec2             TextureLocation;

// Uniform variables...

// Dimensions...
uniform uint        Width;
uniform uint        Height;

// Grid spacing between vertices...
uniform float       DistanceBetweenVertices;

// Pre-computed equation constants...
uniform float       CachedConstant1;
uniform float       CachedConstant2;
uniform float       CachedConstant3;

// Z-displacement maps...
uniform sampler2D   ZMapPrevious;
uniform sampler2D   ZMapCurrent;

// Output variables...

// Z-displacement output buffer...
out float           ZPreviousOut;

// Normal...
out vec3            NormalOut;

// Tangent...
out vec3            TangentOut;

// Take a grid location and transform to a normalized texture coordinate...
vec2 GridToTexture(const uvec2 Location)
{
// Transform to [0..1] and return it...
return (Location / vec2(Width, Height));
}

// Take coordinate normalized in [-1..1] and transform to [0..Width or Height]...
uvec2 NormalizedPointToGrid(const vec2 Normalized)
{
// Transform and return it... p = (d/2)(p'+1)
return uvec2((vec2(Width, Height) * vec2(0.5, 0.5)) * (Normalized + vec2(1.0, 1.0)));
}

// Transform coordinate in [-1..1] to [0..1]
vec2 NormalizedPointToTexture(const vec2 Normalized)
{
// Transform normalized form to texture coordinate... p_t = (p' + <1,1>) / 2
return (Normalized + vec2(1.0, 1.0)) * vec2(0.5, 0.5);
}

// Entry point...
void main()
{
// Get location on grid...
uvec2 GridLocation = NormalizedPointToGrid(NormalizedLocation);

// We assume the outer edge of fluid surface is fixed. This also makes
//  querying neighbours easier since we can assume there always will be
//  exactly eight all around it after this, though we don't affect the
//  diagonal neighbours...

if(GridLocation.x == 0u || GridLocation.x == (Width - 1u) ||
GridLocation.y == 0u || GridLocation.y == (Height - 1u))

// Lookup displacement of current location...
float CurrentZ      = texture(ZMapCurrent, TextureLocation).r;

// Lookup displacements of current location's neighbours...
float AboveCurrentZ = texture(ZMapCurrent, GridToTexture(GridLocation + uvec2( 0,  1))).r;
float BelowCurrentZ = texture(ZMapCurrent, GridToTexture(GridLocation + uvec2( 0, -1))).r;
float RightCurrentZ = texture(ZMapCurrent, GridToTexture(GridLocation + uvec2( 1,  0))).r;
float LeftCurrentZ  = texture(ZMapCurrent, GridToTexture(GridLocation + uvec2(-1,  0))).r;

// Lookup displacement of last passes displacement at current location...
float PreviousZ     = texture(ZMapPrevious, TextureLocation).r;

// Update previous buffer's displacement here using equation 12.25, p.335,
//  of Mathematics for 3D Game Programming and Computer Graphics. */
ZPreviousOut    = CachedConstant1 * CurrentZ +                        /* first term */
CachedConstant2 * PreviousZ +                       /* second term */
CachedConstant3 * (RightCurrentZ + LeftCurrentZ +   /* third term */
AboveCurrentZ + BelowCurrentZ);

// Calculate normal...
NormalOut       = vec3(LeftCurrentZ  - RightCurrentZ,
BelowCurrentZ - AboveCurrentZ,
2.0 * DistanceBetweenVertices);

// Calculate tangent...
TangentOut      = vec3(2.0 * DistanceBetweenVertices,
0.0,
RightCurrentZ - LeftCurrentZ);
}

``````

I know this isn’t a trivial algorithm, but any light one can shed on this is appreciated.

Kip

That looks pretty symmetrical to me. What were you expecting?

No because the -0.08 marks where the disturbance happened originally, but you’ll note the values it is surrounded by vary non-symmetrically (-0.11 on the top, -0.26 on the bottom).

Figured it out. Everywhere where I was using Width or Height variable in the vertex and fragment shader, I changed to Width or Height - 1u.

Still getting used to this shader [censored]. It’s a brave new world and I am still thinking in the CPU way of things.

Kip