3D C/C++ tutorials - OpenGL 2.1 - Interactive water surface, light reflection and refraction, caustic
3D C/C++ tutorials -> OpenGL 2.1 -> Interactive water surface, light reflection and refraction, caustic
Use for personal or educational purposes only. Commercial and other profit uses strictly prohibited. Exploitation of content on a website or in a publication prohibited.
To compile and run these tutorials some or all of these libraries are required: FreeImage 3.16.0, GLEW 1.11.0, GLUT 3.7.6 / GLUT for Dev-C++, GLM 0.9.5.4
opengl_21_tutorials_win32_framework.h
...

#define MAX_WAVES 32

class CWave
{
public:
    float StartTime, Speed, MaxY, FrequencyMPIM2;
    vec2 Position;

public:
    CWave();
    ~CWave();
};

...

#define WMS 10.0f // water mesh size
#define WMR 256 // water mesh resolution
#define WNBMTR 512 // water normal bump map texture resolution, must be >= WMR
#define MAXY 0.0625f // waves amplitude

class COpenGLRenderer
{
protected:
    int Width, Height;
    mat3x3 NormalMatrix;
    mat4x4 ModelMatrix, ViewMatrix, ViewMatrixInverse, ProjectionMatrix, ProjectionBiasMatrixInverse;

protected:
    CTexture Texture[3];
    CShaderProgram SceneProgram, WaterBumpMapProgram, WaterNormalMapProgram, WaterProgram;
    GLuint TexCoordsVBO, NormalsVBO, VerticesVBO, WaterVerticesVBO;
    GLuint WaterNormalBumpMapTexture, ReflectionTexture, RefractionTexture, DepthTexture, FBO;
    float WaterLevel;
    CWave Waves[MAX_WAVES];
    int Wave, WaterVerticesCount;

public:
    bool Pause, WireFrame, Water, Caustic;
    vec3 LightColor, LightPosition;

public:
    CString Text;

public:
    COpenGLRenderer();
    ~COpenGLRenderer();

    bool Init();
    void Render(float FrameTime);
    void Resize(int Width, int Height);
    void Destroy();

    void RenderScene(int ClipType);

    void AddWaveByMouseClick(int x, int y);
    void SetWaveInShaders(int Wave);

    void IncreaseWaterLevel(float Increment);

    void InitWaterVertexBuffer();
    void InitSceneVertexBuffers();
};

...
opengl_21_tutorials_win32_framework.cpp
...

CWave::CWave()
{
    Speed = 1.0f;
    FrequencyMPIM2 = 4.0f * (float)M_PI * 2.0f;
}

CWave::~CWave()
{
}

...

COpenGLRenderer::COpenGLRenderer()
{
    Pause = false;
    WireFrame = false;
    Water = true;
    Caustic = true;

    WaterLevel = 0.5f;

    Wave = 0;

    Camera.SetViewMatrixPointer(&ViewMatrix, &ViewMatrixInverse);
}

COpenGLRenderer::~COpenGLRenderer()
{
}

bool COpenGLRenderer::Init()
{
    bool Error = false;

    if(!GLEW_ARB_texture_non_power_of_two)
    {
        ErrorLog.Append("GL_ARB_texture_non_power_of_two not supported!\r\n");
        Error = true;
    }

    if(!GLEW_ARB_texture_float)
    {
        ErrorLog.Append("GL_ARB_texture_float not supported!\r\n");
        Error = true;
    }

    if(!GLEW_EXT_framebuffer_object)
    {
        ErrorLog.Append("GL_EXT_framebuffer_object not supported!\r\n");
        Error = true;
    }

    char *TextureFileName[] = {"kocka.jpg", "podlaha.jpg", "stena.jpg"};

    for(int i = 0; i < 3; i++)
    {
        Error |= !Texture[i].LoadTexture2D(TextureFileName[i]);
    }

    Error |= !SceneProgram.Load("scene.vs", "scene.fs");
    Error |= !WaterBumpMapProgram.Load("waterbumpmap.vs", "waterbumpmap.fs");
    Error |= !WaterNormalMapProgram.Load("waternormalmap.vs", "waternormalmap.fs");
    Error |= !WaterProgram.Load("water.vs", "water.fs");

    if(Error)
    {
        return false;
    }

    glUseProgram(SceneProgram);
    glUniform1i(glGetUniformLocation(SceneProgram, "Texture"), 0);
    glUniform1i(glGetUniformLocation(SceneProgram, "WaterNormalBumpMapTexture"), 1);
    glUniform1f(glGetUniformLocation(SceneProgram, "ODWMS"), 1.0f / WMS);
    glUseProgram(0);

    glUseProgram(WaterBumpMapProgram);
    glUniform1f(glGetUniformLocation(WaterBumpMapProgram, "WMS"), WMS);
    glUniform1f(glGetUniformLocation(WaterBumpMapProgram, "WMSD2"), WMS / 2.0f);
    glUseProgram(0);

    glUseProgram(WaterNormalMapProgram);
    glUniform1f(glGetUniformLocation(WaterNormalMapProgram, "ODWNBMTR"), 1.0f / WNBMTR);
    glUniform1f(glGetUniformLocation(WaterNormalMapProgram, "WMSDWNBMTRM2"), WMS / WNBMTR * 2.0f);
    glUseProgram(0);

    glUseProgram(WaterProgram);
    glUniform1i(glGetUniformLocation(WaterProgram, "WaterNormalBumpMapTexture"), 0);
    glUniform1i(glGetUniformLocation(WaterProgram, "ReflectionTexture"), 1);
    glUniform1i(glGetUniformLocation(WaterProgram, "RefractionTexture"), 2);
    glUniform1f(glGetUniformLocation(WaterProgram, "ODWMS"), 1.0f / WMS);
    glUseProgram(0);

    SceneProgram.UniformLocations = new GLuint[6];
    SceneProgram.UniformLocations[0] = glGetUniformLocation(SceneProgram, "ClipType");
    SceneProgram.UniformLocations[1] = glGetUniformLocation(SceneProgram, "WaterLevel");
    SceneProgram.UniformLocations[2] = glGetUniformLocation(SceneProgram, "NormalMatrix");
    SceneProgram.UniformLocations[3] = glGetUniformLocation(SceneProgram, "ModelMatrix");
    SceneProgram.UniformLocations[4] = glGetUniformLocation(SceneProgram, "Texturing");
    SceneProgram.UniformLocations[5] = glGetUniformLocation(SceneProgram, "Caustic");

    WaterBumpMapProgram.UniformLocations = new GLuint[1 + MAX_WAVES * 5];
    WaterBumpMapProgram.UniformLocations[0] = glGetUniformLocation(WaterBumpMapProgram, "Time");
    CString Variable;
    for(int Wave = 0; Wave < MAX_WAVES; Wave++)
    {
        Variable.Set("Waves[%d].Position", Wave);
        WaterBumpMapProgram.UniformLocations[1 + Wave * 5 + 0] = glGetUniformLocation(WaterBumpMapProgram, Variable);
        Variable.Set("Waves[%d].StartTime", Wave);
        WaterBumpMapProgram.UniformLocations[1 + Wave * 5 + 1] = glGetUniformLocation(WaterBumpMapProgram, Variable);
        Variable.Set("Waves[%d].Speed", Wave);
        WaterBumpMapProgram.UniformLocations[1 + Wave * 5 + 2] = glGetUniformLocation(WaterBumpMapProgram, Variable);
        Variable.Set("Waves[%d].MaxY", Wave);
        WaterBumpMapProgram.UniformLocations[1 + Wave * 5 + 3] = glGetUniformLocation(WaterBumpMapProgram, Variable);
        Variable.Set("Waves[%d].FrequencyMPIM2", Wave);
        WaterBumpMapProgram.UniformLocations[1 + Wave * 5 + 4] = glGetUniformLocation(WaterBumpMapProgram, Variable);
    }

    WaterProgram.UniformLocations = new GLuint[3];
    WaterProgram.UniformLocations[0] = glGetUniformLocation(WaterProgram, "WaterLevel");
    WaterProgram.UniformLocations[1] = glGetUniformLocation(WaterProgram, "CameraPosition");
    WaterProgram.UniformLocations[2] = glGetUniformLocation(WaterProgram, "NormalMatrix");

    glGenBuffers(1, &TexCoordsVBO);
    glGenBuffers(1, &NormalsVBO);
    glGenBuffers(1, &VerticesVBO);
    glGenBuffers(1, &WaterVerticesVBO);

    InitSceneVertexBuffers();
    InitWaterVertexBuffer();

    glGenTextures(1, &WaterNormalBumpMapTexture);

    glBindTexture(GL_TEXTURE_2D, WaterNormalBumpMapTexture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, WNBMTR, WNBMTR, 0, GL_RGBA, GL_FLOAT, NULL);
    glBindTexture(GL_TEXTURE_2D, 0);

    glGenTextures(1, &ReflectionTexture);
    glGenTextures(1, &RefractionTexture);
    glGenTextures(1, &DepthTexture);

    glGenFramebuffersEXT(1, &FBO);

    LightColor = vec3(1.0f, 1.0f, 1.0f);
    LightPosition = vec3(0.0f, 2.75f, -4.75f);

    glLightfv(GL_LIGHT0, GL_AMBIENT, &vec4(LightColor * 0.25f, 1.0f));
    glLightfv(GL_LIGHT0, GL_DIFFUSE, &vec4(LightColor * 0.75f, 1.0f));
    glLightfv(GL_LIGHT0, GL_SPECULAR, &vec4(LightColor, 1.0f));
    glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0f / 128.0f);
    glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 1.0f / 256.0f);

    Camera.Look(vec3(0.0f, 1.75f, 5.0f), vec3(0.0f, 1.5f, 0.0f), true);
    // Camera.Look(vec3(0.0f, 1.75f, 4.0f), vec3(0.0f, 0.5f, 0.0f));

    srand(GetTickCount());

    return true;
}

void COpenGLRenderer::Render(float FrameTime)
{
    // add wave ---------------------------------------------------------------------------------------------------------------

    static DWORD LastTime = GetTickCount();

    if(!Pause)
    {
        DWORD Time = GetTickCount();

        if(Time - LastTime > 125)
        {
            Waves[Wave].Position.x = -WMS / 2.0f + WMS * (float)rand() / (float)RAND_MAX;
            Waves[Wave].Position.y = -WMS / 2.0f + WMS * (float)rand() / (float)RAND_MAX;

            Waves[Wave].StartTime = (float)Time * 0.001f;

            Waves[Wave].MaxY = MAXY * (float)rand() / (float)RAND_MAX;

            LastTime = Time;

            SetWaveInShaders(Wave++);

            Wave %= MAX_WAVES;
        }
    }

    // render water normal / bump map texture ---------------------------------------------------------------------------------

    glViewport(0, 0, WNBMTR, WNBMTR);

    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, FBO);
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, WaterNormalBumpMapTexture, 0);
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, 0, 0);

    glUseProgram(WaterBumpMapProgram);
    glUniform1f(WaterBumpMapProgram.UniformLocations[0], (float)GetTickCount() * 0.001f);
    glBegin(GL_QUADS);
        glVertex2f(0.0f, 0.0f);
        glVertex2f(1.0f, 0.0f);
        glVertex2f(1.0f, 1.0f);
        glVertex2f(0.0f, 1.0f);
    glEnd();
    glUseProgram(0);

    glBindTexture(GL_TEXTURE_2D, WaterNormalBumpMapTexture);
    glUseProgram(WaterNormalMapProgram);
    glBegin(GL_QUADS);
        glVertex2f(0.0f, 0.0f);
        glVertex2f(1.0f, 0.0f);
        glVertex2f(1.0f, 1.0f);
        glVertex2f(0.0f, 1.0f);
    glEnd();
    glUseProgram(0);
    glBindTexture(GL_TEXTURE_2D, 0);

    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

    // set viewport, reset modelview matrix and set light position ------------------------------------------------------------

    glViewport(0, 0, Width, Height);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glLightfv(GL_LIGHT0, GL_POSITION, &vec4(LightPosition, 1.0f));

    // render reflection texture ----------------------------------------------------------------------------------------------

    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, FBO);
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, ReflectionTexture, 0);
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, DepthTexture, 0);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glLoadMatrixf(&ViewMatrix);

    RenderScene(1);

    glTranslatef(0.0f, WaterLevel * 2.0f, 0.0f);
    glScalef(1.0f, -1.0f, 1.0f);

    glCullFace(GL_FRONT);

    RenderScene(1);

    glCullFace(GL_BACK);

    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

    // render scene / refraction texture --------------------------------------------------------------------------------------

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glLoadMatrixf(&ViewMatrix);

    RenderScene(0);

    glBindTexture(GL_TEXTURE_2D, RefractionTexture);
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, Width, Height);
    glBindTexture(GL_TEXTURE_2D, 0);

    // render water surface ------------------------------------------------------------------------------------------------------

    if(Water)
    {
        glEnable(GL_DEPTH_TEST);

        if(WireFrame)
        {
            glClear(GL_COLOR_BUFFER_BIT);
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        }

        glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, WaterNormalBumpMapTexture);
        glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, ReflectionTexture);
        glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, RefractionTexture);
        glBindBuffer(GL_ARRAY_BUFFER, WaterVerticesVBO);
        glEnableClientState(GL_VERTEX_ARRAY);
        glVertexPointer(2, GL_FLOAT, 8, (void*)0);
        glUseProgram(WaterProgram);
        glUniform1f(WaterProgram.UniformLocations[0], WaterLevel);
        glUniform3fv(WaterProgram.UniformLocations[1], 1, &Camera.Position);
        vec3 X = Camera.X, Y = vec3(0.0f, 1.0f, 0.0f), Z = cross(X, Y);
        NormalMatrix = mat3x3(X.x, Y.x, Z.x, X.y, Y.y, Z.y, X.z, Y.z, Z.z);
        glUniformMatrix3fv(WaterProgram.UniformLocations[2], 1, GL_FALSE, &NormalMatrix);
        glDrawArrays(GL_QUADS, 0, WaterVerticesCount);
        glUseProgram(0);
        glDisableClientState(GL_VERTEX_ARRAY);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, 0);
        glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0);
        glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0);

        if(WireFrame) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

        glDisable(GL_DEPTH_TEST);
    }

    // rotate object ----------------------------------------------------------------------------------------------------------

    if(!Pause)
    {
        static float a = 0.0f;

        ModelMatrix = translate(0.0f, 1.5f, 0.0f) * rotate(a, vec3(0.0f, 1.0f, 0.0f)) * rotate(a, vec3(1.0f, 0.0f, 0.0f));

        a += 22.5f * FrameTime;
    }
}

void COpenGLRenderer::Resize(int Width, int Height)
{
    this->Width = Width;
    this->Height = Height;

    ProjectionMatrix = perspective(45.0f, (float)Width / (float)Height, 0.125f, 512.0f);
    ProjectionBiasMatrixInverse = inverse(ProjectionMatrix) * BiasMatrixInverse;

    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(&ProjectionMatrix);

    glBindTexture(GL_TEXTURE_2D, ReflectionTexture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glBindTexture(GL_TEXTURE_2D, 0);

    glBindTexture(GL_TEXTURE_2D, RefractionTexture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glBindTexture(GL_TEXTURE_2D, 0);

    glBindTexture(GL_TEXTURE_2D, DepthTexture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, Width, Height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
    glBindTexture(GL_TEXTURE_2D, 0);
}

void COpenGLRenderer::Destroy()
{
    for(int i = 0; i < 3; i++)
    {
        Texture[i].Destroy();
    }

    SceneProgram.Destroy();
    WaterBumpMapProgram.Destroy();
    WaterNormalMapProgram.Destroy();
    WaterProgram.Destroy();

    glDeleteBuffers(1, &TexCoordsVBO);
    glDeleteBuffers(1, &NormalsVBO);
    glDeleteBuffers(1, &VerticesVBO);
    glDeleteBuffers(1, &WaterVerticesVBO);

    glDeleteTextures(1, &WaterNormalBumpMapTexture);
    glDeleteTextures(1, &ReflectionTexture);
    glDeleteTextures(1, &RefractionTexture);
    glDeleteTextures(1, &DepthTexture);

    if(GLEW_EXT_framebuffer_object)
    {
        glDeleteFramebuffersEXT(1, &FBO);
    }
}

void COpenGLRenderer::RenderScene(int ClipType)
{
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    glBindBuffer(GL_ARRAY_BUFFER, TexCoordsVBO);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glTexCoordPointer(2, GL_FLOAT, 8, (void*)0);

    glBindBuffer(GL_ARRAY_BUFFER, NormalsVBO);
    glEnableClientState(GL_NORMAL_ARRAY);
    glNormalPointer(GL_FLOAT, 12, (void*)0);

    glBindBuffer(GL_ARRAY_BUFFER, VerticesVBO);
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 12, (void*)0);

    glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, WaterNormalBumpMapTexture);

    glActiveTexture(GL_TEXTURE0);

    glUseProgram(SceneProgram);
    glUniform1i(SceneProgram.UniformLocations[0], ClipType);
    glUniform1f(SceneProgram.UniformLocations[1], WaterLevel);
    glUniform1i(SceneProgram.UniformLocations[5], Caustic ? 1 : 0);

    glUniformMatrix3fv(SceneProgram.UniformLocations[2], 1, GL_FALSE, &mat3x3());
    glUniformMatrix4fv(SceneProgram.UniformLocations[3], 1, GL_FALSE, &mat4x4());

    glUniform1i(SceneProgram.UniformLocations[4], 1);

    glColor3f(1.0f, 1.0f, 1.0f);

    glBindTexture(GL_TEXTURE_2D, Texture[0]);
    glDrawArrays(GL_QUADS, 0, 96);

    glBindTexture(GL_TEXTURE_2D, Texture[1]);
    glDrawArrays(GL_QUADS, 96, 4);

    glBindTexture(GL_TEXTURE_2D, Texture[2]);
    glDrawArrays(GL_QUADS, 100, 80);

    glUniform1i(SceneProgram.UniformLocations[4], 0);

    glDrawArrays(GL_QUADS, 180, 4);

    glUniformMatrix3fv(SceneProgram.UniformLocations[2], 1, GL_FALSE, &mat3x3(ModelMatrix));
    glUniformMatrix4fv(SceneProgram.UniformLocations[3], 1, GL_FALSE, &ModelMatrix);

    glColor3f(0.33f, 0.66f, 1.0f);
    glDrawArrays(GL_QUADS, 184, 72);

    glUseProgram(0);

    glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glDisable(GL_CULL_FACE);
    glDisable(GL_DEPTH_TEST);
}

void COpenGLRenderer::AddWaveByMouseClick(int x, int y)
{
    float s = (float)x / (float)(Width - 1);
    float t = 1.0f - (float)y / (float)(Height - 1);

    vec4 Position = ViewMatrixInverse * (ProjectionBiasMatrixInverse * vec4(s, t, 0.5f, 1.0f));
    Position /= Position.w;

    vec3 Ray = normalize(*(vec3*)&Position - Camera.Position);

    vec3 Normal = vec3(0.0f, 1.0f, 0.0f);
    float D = -dot(Normal, vec3(0.0f, WaterLevel, 0.0f));

    float NdotR = -dot(Normal, Ray);

    if(NdotR != 0.0f)
    {
        float Distance = (dot(Normal, Camera.Position) + D) / NdotR;

        if(Distance > 0.0)
        {
            vec3 Position = Ray * Distance + Camera.Position;

            float WMSD2 = WMS / 2.0f, MWMSD2 = -WMSD2;

            if(Position.x >= MWMSD2 && Position.x <= WMSD2 && Position.z >= MWMSD2 && Position.z <= WMSD2)
            {
                Waves[Wave].Position.x = Position.x;
                Waves[Wave].Position.y = Position.z;

                Waves[Wave].StartTime = (float)GetTickCount() * 0.001f;

                Waves[Wave].MaxY = MAXY;

                SetWaveInShaders(Wave++);

                Wave %= MAX_WAVES;
            }
        }
    }
}

void COpenGLRenderer::SetWaveInShaders(int Wave)
{
    glUseProgram(WaterBumpMapProgram);
    glUniform2fv(WaterBumpMapProgram.UniformLocations[1 + Wave * 5 + 0], 1, &Waves[Wave].Position);
    glUniform1f(WaterBumpMapProgram.UniformLocations[1 + Wave * 5 + 1], Waves[Wave].StartTime);
    glUniform1f(WaterBumpMapProgram.UniformLocations[1 + Wave * 5 + 2], Waves[Wave].Speed);
    glUniform1f(WaterBumpMapProgram.UniformLocations[1 + Wave * 5 + 3], Waves[Wave].MaxY);
    glUniform1f(WaterBumpMapProgram.UniformLocations[1 + Wave * 5 + 4], Waves[Wave].FrequencyMPIM2);
    glUseProgram(0);
}

void COpenGLRenderer::IncreaseWaterLevel(float Increment)
{
    WaterLevel += Increment;

    if(WaterLevel < 0.25f) WaterLevel = 0.25f;
    if(WaterLevel > 2.75f) WaterLevel = 2.75f;
}

void COpenGLRenderer::InitWaterVertexBuffer()
{
    WaterVerticesCount = WMR * WMR * 4;

    vec2 *WaterVertices = new vec2[WaterVerticesCount];

    float WMSD2 = WMS / 2.0f, MWMSD2 = -WMSD2, WMSDWMR = WMS / (float)WMR;

    int i = 0;

    for(int y = 0; y < WMR; y++)
    {
        for(int x = 0; x < WMR; x++)
        {
            WaterVertices[i].x = MWMSD2 + x * WMSDWMR;
            WaterVertices[i++].y = WMSD2 - y * WMSDWMR;

            WaterVertices[i].x = MWMSD2 + (x + 1) * WMSDWMR;
            WaterVertices[i++].y = WMSD2 - y * WMSDWMR;

            WaterVertices[i].x = MWMSD2 + (x + 1) * WMSDWMR;
            WaterVertices[i++].y = WMSD2 - (y + 1) * WMSDWMR;

            WaterVertices[i].x = MWMSD2 + x * WMSDWMR;
            WaterVertices[i++].y = WMSD2 - (y + 1) * WMSDWMR;
        }
    }

    glBindBuffer(GL_ARRAY_BUFFER, WaterVerticesVBO);
    glBufferData(GL_ARRAY_BUFFER, WaterVerticesCount * 2 * 4, WaterVertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    delete [] WaterVertices;
}

void COpenGLRenderer::InitSceneVertexBuffers()
{
    vec2 *TexCoords = new vec2[256];
    vec3 *Normals = new vec3[256];
    vec3 *Vertices = new vec3[256];

    ...

    glBindBuffer(GL_ARRAY_BUFFER, TexCoordsVBO);
    glBufferData(GL_ARRAY_BUFFER, 256 * 2 * 4, TexCoords, GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, NormalsVBO);
    glBufferData(GL_ARRAY_BUFFER, 256 * 3 * 4, Normals, GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, VerticesVBO);
    glBufferData(GL_ARRAY_BUFFER, 256 * 3 * 4, Vertices, GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    delete [] TexCoords;
    delete [] Normals;
    delete [] Vertices;
}

...

void CWnd::OnKeyDown(UINT Key)
{
    switch(Key)
    {
        case VK_F1:
            OpenGLRenderer.WireFrame = !OpenGLRenderer.WireFrame;
            break;

        case VK_F2:
            OpenGLRenderer.Water = !OpenGLRenderer.Water;
            break;

        case VK_SPACE:
            OpenGLRenderer.Pause = !OpenGLRenderer.Pause;
            break;

        case VK_ADD:
            OpenGLRenderer.IncreaseWaterLevel(0.05f);
            break;

        case VK_SUBTRACT:
            OpenGLRenderer.IncreaseWaterLevel(-0.05f);
            break;
    }
}

void CWnd::OnLButtonDown(int cx, int cy)
{
    OpenGLRenderer.AddWaveByMouseClick(cx, cy);
}

...
waterbumpmap.vs
#version 120

uniform float WMS, WMSD2;

varying vec2 Position;

void main()
{
    Position = vec2(gl_Vertex.x * WMS - WMSD2, WMSD2 - gl_Vertex.y * WMS);
    gl_Position = gl_Vertex * 2.0 - 1.0;
}
waterbumpmap.fs
#version 120

#define MAX_WAVES 32

struct CWave
{
    float StartTime, Speed, MaxY, FrequencyMPIM2;
    vec2 Position;
};

uniform float Time;
uniform CWave Waves[MAX_WAVES];

varying vec2 Position;

void main()
{
    float y = 0.0;

    for(int i = 0; i < MAX_WAVES; i++)
    {
        float d = distance(Waves[i].Position, Position);
        float t = Time - Waves[i].StartTime - d / Waves[i].Speed;

        if(t > 0.0)
        {
            float maxy = Waves[i].MaxY - Waves[i].MaxY * t;

            if(maxy > 0.0)
            {
                y -= sin(t * Waves[i].FrequencyMPIM2) * maxy / (1.0 + d);
            }
        }
    }

    gl_FragColor = vec4(0.0, 1.0, 0.0, y);
}
waternormalmap.vs
#version 120

void main()
{
    gl_TexCoord[0] = gl_Vertex;
    gl_Position = gl_Vertex * 2.0 - 1.0;
}
waternormalmap.fs
#version 120

uniform sampler2D WaterNormalBumpMapTexture;

uniform float ODWNBMTR, WMSDWNBMTRM2;

void main()
{
    float y[5];

    y[0] = texture2D(WaterNormalBumpMapTexture, vec2(gl_TexCoord[0].s + ODWNBMTR, gl_TexCoord[0].t)).a;
    y[1] = texture2D(WaterNormalBumpMapTexture, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t + ODWNBMTR)).a;
    y[2] = texture2D(WaterNormalBumpMapTexture, vec2(gl_TexCoord[0].s - ODWNBMTR, gl_TexCoord[0].t)).a;
    y[3] = texture2D(WaterNormalBumpMapTexture, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t - ODWNBMTR)).a;

    y[4] = texture2D(WaterNormalBumpMapTexture, gl_TexCoord[0].st).a;

    vec3 Normal = normalize(vec3(y[2] - y[0], WMSDWNBMTRM2, y[1] - y[3]));

    gl_FragColor = vec4(Normal, y[4]);
}
scene.vs
#version 120

uniform mat3x3 NormalMatrix;
uniform mat4x4 ModelMatrix;

varying vec3 Position, Normal;

void main()
{
    Position = (ModelMatrix * gl_Vertex).xyz;
    Normal = NormalMatrix * gl_Normal;
    gl_FrontColor = gl_Color;
    gl_TexCoord[0] = gl_MultiTexCoord0;
    gl_Position = gl_ModelViewProjectionMatrix * vec4(Position, 1.0);
}
scene.fs
#version 120

uniform int ClipType, Texturing, Caustic;
uniform float WaterLevel, ODWMS;
uniform sampler2D Texture, WaterNormalBumpMapTexture;

varying vec3 Position, Normal;

void main()
{
    if(ClipType == 1) if(Position.y < WaterLevel) discard;
    if(ClipType == 2) if(Position.y > WaterLevel) discard;

    vec3 LightDirection = gl_LightSource[0].position.xyz - Position;

    float LightDistance2 = dot(LightDirection, LightDirection);
    float LightDistance = sqrt(LightDistance2);

    LightDirection /= LightDistance;

    float NdotLD = max(0.0, dot(Normal, LightDirection));

    float Attenuation = gl_LightSource[0].constantAttenuation;
    Attenuation += gl_LightSource[0].linearAttenuation * LightDistance;
    Attenuation += gl_LightSource[0].quadraticAttenuation * LightDistance2;

    vec3 Light = (gl_LightSource[0].ambient.rgb + gl_LightSource[0].diffuse.rgb * NdotLD) / Attenuation;

    if(Caustic == 1 && NdotLD > 0.0 && gl_LightSource[0].position.y != WaterLevel)
    {
        vec2 TexCoord = vec2(Position.x * ODWMS + 0.5, 0.5 - Position.z * ODWMS);
        float waterlevel = WaterLevel + texture2D(WaterNormalBumpMapTexture, TexCoord).a;

        if((gl_LightSource[0].position.y > WaterLevel && Position.y > waterlevel) || (gl_LightSource[0].position.y < WaterLevel && Position.y < waterlevel))
        {
            LightDirection.xz = gl_LightSource[0].position.xz - Position.xz;
            LightDirection.y = WaterLevel * 2.0 - gl_LightSource[0].position.y - Position.y;

            LightDistance2 = dot(LightDirection, LightDirection);
            LightDistance = sqrt(LightDistance2);

            LightDirection /= LightDistance;

            NdotLD = max(0.0, dot(Normal, LightDirection));

            Attenuation = gl_LightSource[0].constantAttenuation;
            Attenuation += gl_LightSource[0].linearAttenuation * LightDistance;
            Attenuation += gl_LightSource[0].quadraticAttenuation * LightDistance2;
        }

        float Distance = (WaterLevel - Position.y) / LightDirection.y;

        TexCoord = LightDirection.xz * Distance + Position.xz;

        TexCoord.s = TexCoord.s * ODWMS + 0.5;
        TexCoord.t = 0.5 - TexCoord.t * ODWMS;

        vec3 WaterNormal = texture2D(WaterNormalBumpMapTexture, TexCoord).rgb;

        float WNdotLD = abs(dot(WaterNormal, LightDirection));
        float Caustic = NdotLD * pow(WNdotLD, 4.0) / Attenuation;
        Light += gl_LightSource[0].specular.rgb * Caustic;
    }

    gl_FragColor.rgb = gl_Color.rgb;
    if(Texturing == 1) gl_FragColor.rgb *= texture2D(Texture, gl_TexCoord[0].st).rgb;
    gl_FragColor.rgb *= Light;
}
water.vs
#version 120

uniform sampler2D WaterNormalBumpMapTexture;

uniform float WaterLevel, ODWMS;

varying vec3 Position;

void main()
{
    Position.xz = gl_Vertex.xy;

    gl_TexCoord[0].st = vec2(gl_Vertex.x, -gl_Vertex.y) * ODWMS + 0.5;

    Position.y = WaterLevel + texture2D(WaterNormalBumpMapTexture, gl_TexCoord[0].st).a;

    gl_TexCoord[1] = gl_ModelViewProjectionMatrix * vec4(Position, 1.0);

    gl_Position = gl_TexCoord[1];
}
water.fs
#version 120

uniform sampler2D WaterNormalBumpMapTexture, ReflectionTexture, RefractionTexture;

uniform float WaterLevel;
uniform vec3 CameraPosition;
uniform mat3x3 NormalMatrix;

varying vec3 Position;

void main()
{
    vec2 TexCoord = gl_TexCoord[1].st / gl_TexCoord[1].w * 0.5 + 0.5;

    vec3 Normal = NormalMatrix * normalize(texture2D(WaterNormalBumpMapTexture, gl_TexCoord[0].st).rgb);

    vec2 Offset = Normal.xz * vec2(0.05, -0.05);

    if(CameraPosition.y > WaterLevel)
    {
        vec3 Reflection = texture2D(ReflectionTexture, TexCoord + Offset).rgb;

        vec3 Refraction;

        Refraction.r = texture2D(RefractionTexture, TexCoord - Offset * 0.8).r;
        Refraction.g = texture2D(RefractionTexture, TexCoord - Offset * 0.9).g;
        Refraction.b = texture2D(RefractionTexture, TexCoord - Offset * 1.0).b;

        vec3 LightDirection = normalize(Position - gl_LightSource[0].position.xyz);
        vec3 LightDirectionReflected = reflect(LightDirection, Normal);
        vec3 CameraDirection = normalize(CameraPosition - Position);
        float CDdotLDR = max(dot(CameraDirection, LightDirectionReflected), 0.0);
        vec3 Specular = gl_LightSource[0].specular.rgb * pow(CDdotLDR, 128.0);

        float NdotCD = max(dot(Normal, CameraDirection), 0.0);

        gl_FragColor.rgb = mix(Reflection, Refraction, NdotCD) + Specular;
    }
    else
    {
        gl_FragColor.r = texture2D(RefractionTexture, TexCoord - Offset * 0.8).r;
        gl_FragColor.g = texture2D(RefractionTexture, TexCoord - Offset * 0.9).g;
        gl_FragColor.b = texture2D(RefractionTexture, TexCoord - Offset * 1.0).b;
    }
}
Download
water.zip (Visual Studio 2005 Professional, without normal-bump-map texture and caustic)
interactive_water_surface.zip (Visual Studio 2005 Professional)

© 2010 - 2016 Bc. Michal Belanec, michalbelanec (at) centrum (dot) sk
Last update June 25, 2016
OpenGL® is a registered trademark of Silicon Graphics Inc.