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);
glLoadIdentity();
gluOrtho2D(0.0, m_Width, 0.0, m_Height);
// There should be no modelview transformations either...
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// 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
};
// Load the draw buffers...
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);
// Commit data to shader...
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();
}
Vertex shader:
// 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);
}
Fragment shader:
// 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))
discard;
// 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