Search code examples
c++openglglslfragment-shaderfading

OPENGL: How do I implement uniform variable to achieve fading effect?


current state:

img

What I am trying to achieve is to create a fading effect (periodically black to full color) on the second cube/model matrix, without changing any of the global vertices. What I have gathered so far is the need to declare a uniform variable in the fragment shader and play with the float values. I have since added to the fragment shader:

uniform float uAlpha;

void main()
{
    // set output color
    fColor = vec4(vColor, uAlpha);
}

I don't know what to do next with my source code. Add something along the lines of this?

GLuint g_uAlpha = glGetUniformLocation(g_shaderProgramID, "uAlpha");
vec4 color = vec4(1.0, 1.0, 1.0, 1.0);
GLfloat alpha = color.a;
glUniform1fv(g_uAlpha, 1, &alpha);

It really doesn't do anything I know. I'm really clueless about how to implement this and I'm hoping someone can shed some light, thanks.

Source code:

#include <cstdio>
#include <iostream>
#include <cstddef>
#include <Windows.h>
#include <time.h>
using namespace std;

#define GLEW_STATIC
#include <GLEW/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>

#include "shader.h"
#include "Camera.h"

struct Vertex
{
    GLfloat position[3];
    GLfloat color[3];
};

Vertex g_vertices[] = {
// vertex 1
-0.2f, 0.2f, 0.2f,
1.0f, 0.0f, 1.0f,

// vertex 2
-0.2f, -0.2f, 0.2f,
1.0f, 0.0f, 0.0f,

// vertex 3
0.2f, 0.2f, 0.2f,
1.0f, 1.0f, 1.0f,

// vertex 4
0.2f, -0.2f, 0.2f,
1.0f, 1.0f, 0.0f,

// vertex 5
-0.2f, 0.2f, -0.2f,
0.0f, 0.0f, 1.0f,

// vertex 6
-0.2f, -0.2f, -0.2f,
0.0f, 0.0f, 0.0f,

// vertex 7
0.2f, 0.2f, -0.2f,
0.0f, 1.0f, 1.0f,

// vertex 8
0.2f, -0.2f, -0.2f,
0.0f, 1.0f, 0.0f,
};

GLuint g_indices[] = {
    0, 1, 2,
    2, 1, 3,
    4, 5, 0,
    0, 5, 1,
    2, 3, 6,
    6, 3, 7,
    4, 0, 6,
    6, 0, 2,
    1, 5, 3,
    3, 5, 7,
    5, 4, 7,
    7, 4, 6,
};

GLuint g_IBO[1];
GLuint g_VBO[1];
GLuint g_VAO[1];
GLuint g_shaderProgramID = 0;
GLuint g_MVP_Index = 0;
mat4 g_modelMatrix[2];
mat4 g_viewMatrix;
mat4 g_projectionMatrix;
Camera g_camera;

static void init(GLFWwindow* window)
{
    srand(time(NULL));

    glClearColor(0.0, 0.0, 0.0, 1.0);
    glEnable(GL_DEPTH_TEST);
    g_shaderProgramID = loadShaders("Vertex_Shader.vert", "Fragment_Shader.frag");
    glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);

    // find the location of shader variables
    GLuint positionIndex = glGetAttribLocation(g_shaderProgramID, "aPosition");
    GLuint colorIndex = glGetAttribLocation(g_shaderProgramID, "aColor");
    g_MVP_Index = glGetUniformLocation(g_shaderProgramID, "uModelViewProjectionMatrix");

    // initialise model matrix to the identity matrix
    g_modelMatrix[0] = mat4(1.0f);
    g_modelMatrix[1] = mat4(1.0f);

    // set camera's view matrix
    g_camera.setViewMatrix(vec3(0, 1, 5), vec3(0, 0, 2), vec3(0, 1, 0));

    // get the framebuffer width and height in order to calculate the aspect ratio
    int width, height;
    glfwGetFramebufferSize(window, &width, &height);
    float aspectRatio = static_cast<float>(width) / height;

    // initialise the projection matrix
    g_camera.setProjectionMatrix(perspective(45.0f, aspectRatio, 0.1f, 100.0f));

    glGenBuffers(1, g_VBO);
    glGenVertexArrays(1, g_VAO);
    glGenBuffers(1, g_IBO);

    // draw cubes
    glBindBuffer(GL_ARRAY_BUFFER, g_VBO[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertices), g_vertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_IBO[0]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(g_indices), g_indices, GL_STATIC_DRAW);

    glBindVertexArray(g_VAO[0]);
    glBindBuffer(GL_ARRAY_BUFFER, g_VBO[0]);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_IBO[0]);
    glVertexAttribPointer(positionIndex, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, position)));
    glVertexAttribPointer(colorIndex, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, color)));

    glEnableVertexAttribArray(positionIndex);
    glEnableVertexAttribArray(colorIndex);
}

static void render_scene()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glUseProgram(g_shaderProgramID);

    glBindVertexArray(g_VAO[0]);
    mat4 MVP = g_camera.getProjectionMatrix() * g_camera.getViewMatrix() * g_modelMatrix[0];
    glUniformMatrix4fv(g_MVP_Index, 1, GL_FALSE, &MVP[0][0]);
    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

    mat4 MVP1 = g_camera.getProjectionMatrix() * g_camera.getViewMatrix() * g_modelMatrix[1];
    glUniformMatrix4fv(g_MVP_Index, 1, GL_FALSE, &MVP1[0][0]);
    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

    glFlush();
}

static void update_scene(GLFWwindow* window)
{
    g_modelMatrix[1] = glm::translate(glm::vec3(1.0f, 0.0f, 0.0f));
}

static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
    {
        glfwSetWindowShouldClose(window, GL_TRUE);
        return;
    }
}

int main(void)
{
    GLFWwindow* window = NULL;

    if (!glfwInit())
    {
        exit(EXIT_FAILURE);
    }

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);

    window = glfwCreateWindow(1028, 768, "Test", NULL, NULL);

    if (window == NULL)
    {
        glfwTerminate();
        exit(EXIT_FAILURE);
    }

    glfwMakeContextCurrent(window);
    glfwSwapInterval(1);

    if (glewInit() != GLEW_OK)
    {
        cerr << "GLEW initialisation failed" << endl;
        exit(EXIT_FAILURE);
    }

    glfwSetKeyCallback(window, key_callback);
    glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);
    init(window);

    float lastUpdateTime = glfwGetTime();
    float currentTime = lastUpdateTime;

    // rendering loop
    while (!glfwWindowShouldClose(window))
    {
        currentTime = glfwGetTime();

        if (currentTime - lastUpdateTime > 0.02)
        {
            g_camera.update(window);
            update_scene(window);
            render_scene();

            glfwSwapBuffers(window);
            glfwPollEvents();
            lastUpdateTime = currentTime;
        }
    }

    // clean up
    glDeleteProgram(g_shaderProgramID);
    glDeleteBuffers(1, g_IBO);
    glDeleteBuffers(1, g_VBO);
    glDeleteVertexArrays(1, g_VAO);

    // close the window and terminate GLFW
    glfwDestroyWindow(window);
    glfwTerminate();

    exit(EXIT_SUCCESS);
}

Camera.h

#ifndef __CAMERA_H
#define __CAMERA_H

#include <GLFW/glfw3.h> // include GLFW (which includes the OpenGL header)
#include <glm/glm.hpp>  // include GLM (ideally should only use the GLM headers that are actually used)
#include <glm/gtx/transform.hpp>
#include <glm/gtx/rotate_vector.hpp>
using namespace glm;    // to avoid having to use glm::

#define MOVEMENT_SENSITIVITY 0.05f      // camera movement sensitivity
#define ROTATION_SENSITIVITY 0.05f      // camera rotation sensitivity

class Camera {
public:
    Camera();
    ~Camera();

    void update(GLFWwindow* window);
    void updateYaw(float yaw);
    void updatePitch(float pitch);
    void setViewMatrix(glm::vec3 position, glm::vec3 lookAt, glm::vec3 up);
    void setProjectionMatrix(glm::mat4& matrix);
    glm::mat4 getViewMatrix();
    glm::mat4 getProjectionMatrix();

private:
    float mYaw;
    float mPitch;
    glm::vec3 mPosition;
    glm::vec3 mLookAt;
    glm::vec3 mUp;
    glm::mat4 mViewMatrix;
    glm::mat4 mProjectionMatrix;
};

#endif

Camera.cpp

#include "Camera.h"

Camera::Camera()
{
    // initialise camera member variables
    mPosition = glm::vec3(0.0f, 0.0f, 1.0f);
    mLookAt = glm::vec3(0.0f, 0.0f, 0.0f);
    mUp = glm::vec3(0.0f, 1.0f, 0.0f);

    mYaw = 0.0f;
    mPitch = 0.0f;

    mViewMatrix = glm::lookAt(mPosition, mLookAt, mUp);
    mProjectionMatrix = glm::perspective(45.0f, 1.0f, 0.1f, 100.0f);
}

Camera::~Camera()
{}

void Camera::update(GLFWwindow* window)
{
    // variables to store forward/back and strafe movement
    float moveForward = 0;
    float strafeRight = 0;

    // update variables based on keyboard input
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        moveForward += MOVEMENT_SENSITIVITY;
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        moveForward -= MOVEMENT_SENSITIVITY;
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        strafeRight -= MOVEMENT_SENSITIVITY;
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        strafeRight += MOVEMENT_SENSITIVITY;

    // rotate the respective unit vectors about the y-axis
    glm::vec3 rotatedForwardVec = glm::rotateY(glm::vec3(0.0f, 0.0f, -1.0f), mYaw);
    glm::vec3 rotatedRightVec = glm::rotateY(glm::vec3(1.0f, 0.0f, 0.0f), mYaw);
    // rotate the rotated forward vector about the rotated right vector
    rotatedForwardVec = glm::vec3(glm::rotate(mPitch, rotatedRightVec)*glm::vec4(rotatedForwardVec, 0.0f));

    // update position, look-at and up vectors
    mPosition += rotatedForwardVec * moveForward + rotatedRightVec * strafeRight;
    mLookAt = mPosition + rotatedForwardVec;
    mUp = glm::cross(rotatedRightVec, rotatedForwardVec);   // cross product

    // compute the new view matrix
    mViewMatrix = glm::lookAt(mPosition, mLookAt, mUp);
}

void Camera::updateYaw(float yaw)
{
    mYaw -= yaw * ROTATION_SENSITIVITY;
}

void Camera::updatePitch(float pitch)
{
    mPitch -= pitch * ROTATION_SENSITIVITY;
}

void Camera::setViewMatrix(glm::vec3 position, glm::vec3 lookAt, glm::vec3 up)
{
    mPosition = position;
    mLookAt = lookAt;
    mUp = up;

    mViewMatrix = glm::lookAt(mPosition, mLookAt, mUp);
}

void Camera::setProjectionMatrix(glm::mat4& matrix)
{
    mProjectionMatrix = matrix;
}

glm::mat4 Camera::getViewMatrix()
{
    return mViewMatrix;
}

glm::mat4 Camera::getProjectionMatrix()
{
    return mProjectionMatrix;
}

Vertex Shader

#version 330 core

// input data (different for all executions of this shader)
in vec3 aPosition;
in vec3 aColor;

// ModelViewProjection matrix
uniform mat4 uModelViewProjectionMatrix;

// output data (will be interpolated for each fragment)
out vec3 vColor;

void main()
{
    // set vertex position
    gl_Position = uModelViewProjectionMatrix * vec4(aPosition, 1.0);

    // the color of each vertex will be interpolated
    // to produce the color of each fragment

    vColor = aColor;
}

Fragment Shader

#version 330 core

// interpolated values from the vertex shaders
in vec3 vColor;

// output data
out vec4 fColor;

uniform float uAlpha;

void main()
{
    // set output color
    fColor = vec4(vColor, uAlpha);
}

Solution

  • You need to control the fade in the Host Code, and pass the state along to the shader[s] at runtime. Since you're using GLFW as your window manager, that's relatively simple:

    while(!glfwWindowShouldClose(window)) {
        glfwPollEvents();
        constexpr float factor = 30; //Higher == faster fade, lower == slower fade
        float alpha = (float(std::sin(glfwGetTime() * factor) + 1) / 2; //Generates a Sine Wave in range [0, 1].
        //float alpha = float(glfwGetTime() * factor - std::floor(glfwGetTime() * factor); //Sawtooth fade
        glUniform1f(glGetUniformLocation(g_shaderProgramID, "uAlpha"), alpha);
        update_scene(window);
        render_scene();
        /*Whatever else needs to happen*/
    }
    

    The other thing I'm going to recommend is that you do the blending manually in the [fragment] shader, not automatically using OpenGL blending.

    #version 330 core
    
    // interpolated values from the vertex shaders
    in vec3 vColor;
    
    // output data
    out vec4 fColor;
    
    uniform float uAlpha;
    
    void main()
    {
        // set output color
        vec4 fade_color = vec4(0,0,0,1); //Black fade
        fColor = mix(vec4(vcolor, 1), fade_color, uAlpha);
    }
    

    The GLSL function mix will blend two vectors together using a float value to choose how much of either color to use. Using this function, you can set the "fade" color to be whatever you want.

    #version 330 core
    
    // interpolated values from the vertex shaders
    in vec3 vColor;
    
    // output data
    out vec4 fColor;
    
    uniform float uAlpha;
    uniform vec4 fade_color;
    
    void main()
    {
        // set output color
        fColor = mix(vec4(vcolor, 1), fade_color, uAlpha);
    }
    

    If you actually intend the object to be transparent when fading (which is how the alpha parameter is usually used), then you can use the same host code I provided, with an additional function call in the setup code:

    glEnable(GL_BLEND);
    

    And then you can use your original Fragment Shader code. The only restriction is that if you do this (like any rendering involving transparency) the ordering of draw calls becomes extremely important. I would advise you look around for tutorials on how to do transparency using alpha-blending, since getting into the dirt of that is beyond the scope of this question.