Search code examples
c++openglglslsdlfragment-shader

A Simple Gradient Effect


I need to code the fragment shader so that the triangle has a simple gradient effect. That is, so that its transparency decreases from left to right. I tried this but it fails:

#version 120
uniform float startX = gl_FragCoord.x;
void main(void) {
    gl_FragColor[0] = 0.0;
    gl_FragColor[1] = 0.0;
    gl_FragColor[2] = 1.0;
    gl_FragColor[3] = startX / gl_FragCoord.x;
}

The full code:

#include <cstdlib>
#include <iostream>
using namespace std;

#include <GL/glew.h>
#include <SDL.h>

GLuint program;
GLint attribute_coord2d;

bool init_resources(void)
{
    GLint compile_ok, link_ok = GL_FALSE;

    GLuint vs = glCreateShader(GL_VERTEX_SHADER);
    const char* vs_source = R"(
        #version 120
        attribute vec2 coord2d;
        void main(void) {
            gl_Position = vec4(coord2d, 0.0, 1.0);
        }
    )";
    glShaderSource(vs, 1, &vs_source, NULL);
    glCompileShader(vs);
    glGetShaderiv(vs, GL_COMPILE_STATUS, &compile_ok);
    if (!compile_ok) {
        cerr << "Error in vertex shader" << endl;
        return false;
    }

    GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
    const char* fs_source = R"(
        #version 120
        uniform float startX = gl_FragCoord.x;
        void main(void) {
            gl_FragColor[0] = 0.0;
            gl_FragColor[1] = 0.0;
            gl_FragColor[2] = 1.0;
            gl_FragColor[3] = startX / gl_FragCoord.x;
        }
    )";
    glShaderSource(fs, 1, &fs_source, NULL);
    glCompileShader(fs);
    glGetShaderiv(fs, GL_COMPILE_STATUS, &compile_ok);
    if (!compile_ok) {
        cerr << "Error in fragment shader" << endl;
        return false;
    }

    program = glCreateProgram();
    glAttachShader(program, vs);
    glAttachShader(program, fs);
    glLinkProgram(program);
    glGetProgramiv(program, GL_LINK_STATUS, &link_ok);
    if (!link_ok) {
        cerr << "Error in glLinkProgram" << endl;
        return false;
    }

    const char* attribute_name = "coord2d";
    attribute_coord2d = glGetAttribLocation(program, attribute_name);
    if (attribute_coord2d == -1) {
        cerr << "Could not bind attribute " << attribute_name << endl;
        return false;
    }

    return true;
}

void render(SDL_Window* window)
{
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    glUseProgram(program);

    glEnableVertexAttribArray(attribute_coord2d);
    GLfloat triangle_vertices[] = {
        0.0,  0.8,
       -0.8, -0.8,
        0.8, -0.8,
    };

    glVertexAttribPointer(attribute_coord2d, 2, GL_FLOAT, GL_FALSE, 0, triangle_vertices);

    glDrawArrays(GL_TRIANGLES, 0, 3);

    glDisableVertexAttribArray(attribute_coord2d);

    SDL_GL_SwapWindow(window);
}

void free_resources()
{
    glDeleteProgram(program);
}

void mainLoop(SDL_Window* window)
{
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    while (true)
    {
        SDL_Event ev;
        while (SDL_PollEvent(&ev))
        {
            if (ev.type == SDL_QUIT)
                return;
        }
        render(window);
    }
}

int main(int argc, char* argv[])
{
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window* window = SDL_CreateWindow("My First Triangle", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL);
    SDL_GL_CreateContext(window);

    GLenum glew_status = glewInit();
    if (glew_status != GLEW_OK) {
        cerr << "Error: glewInit: " << glewGetErrorString(glew_status) << endl;
        return EXIT_FAILURE;
    }

    if (!init_resources())
        return EXIT_FAILURE;

    mainLoop(window);

    free_resources();
    return EXIT_SUCCESS;
}

How to do it right?


Solution

  • vYou can not initialize a uniform with gl_FragCoord.x. A uniform initialization is determined at link time.

    uniform float startX = gl_FragCoord.x;

    uniform float startX;
    

    You have to set the unform with glUniform1f.


    gl_FragCoord.xy are not the vertex coordinates. gl_FragCoord.xy are the window coordinate in pixels. You have to divide gl_FragCoord.xy by the size of the viewport:

    #version 120
    
    void main(void) {
        gl_FragColor = vec4(0.0, 0.0, 0.0, gl_FragCoord.x / 640.0);
    }
    

    Or passing coord2d to the fragment shader:

    #version 120
            
    attribute vec2 coord2d;
    varying vec2 coord;
            
    void main(void) {
        coord = coord2d;         
        gl_Position = vec4(coord2d, 0.0, 1.0);
    }
    
    #version 120
    
    varying vec2 coord;
    
    void main(void) {
        float alpha = 2.0 / (1.0 - coord.y);
        gl_FragColor = vec4(0.0, 0.0, 1.0, alpha);
    }
    

    Or use a color attribute:

    #version 120
    
    attribute vec2 coord2d;
    attribute vec4 attrColor;
    varying vec4 color;
    
    void main(void) {
        color = attrColor;
        gl_Position = vec4(coord2d, 0.0, 1.0);
    }
    
    #version 120
    
    varying vec4 color;
           
    void main(void) {
        gl_FragColor = color;
    }
    
    attribute_color = glGetAttribLocation(program, "attrColor");
    
    GLfloat triangle_colors[] = {
        0.0f, 0.0f, 1.0f, 0.5f,
        0.0f, 0.0f, 1.0f, 0.0f,
        0.0f, 0.0f, 1.0f, 1.0f
    };
    
    glEnableVertexAttribArray(attribute_color);
    glVertexAttribPointer(attribute_color, 4, GL_FLOAT, GL_FALSE, 0, triangle_colors);