uniform is a global Shader variable declared with the “uniform” storage qualifier. These act as parameters that the user of a shader program can pass to that program. Their values are stored in a program object.

Uniforms are so named because they do not change from one shader invocation to the next within a particular rendering call thus their value is uniform among all invocations. This makes them unlike shader stage inputs and outputs, which are often different for each invocation of a shader stage.

https://www.khronos.org/opengl/wiki/Uniform_(GLSL)#:~:text=A%20uniform%20is%20a%20global,stored%20in%20a%20program%20object.
#version 330 core                                                                               
layout (location = 0) in vec2 aPos;             
uniform float time; // we define an uniform 
                                                                                                
void main() {                       
   // we use uniform for manipulating vertex position          
   gl_Position = vec4(aPos.x * cos(time), aPos.y * sin(time/1), 0.0, 1.0);
}        
int location = glGetUniformLocation(shaderID, "time");
glUniform1f(location, 5.0f);
  • glGetUniformLocation returns an integer that represents the location of a specific uniform variable within a program object.
  • glUniform modifies the value of a uniform variable or a uniform variable array.

Shader source

basic.shader

#shader vertex
#version 330 core                                                                               
layout (location = 0) in vec2 aPos;             
uniform float time;
                                                                                                
void main() {                                   
   gl_Position = vec4(aPos.x * cos(time), aPos.y * sin(time/1), 0.0, 1.0);
}                                               

#shader fragment
#version 330 core                                                                                   
out vec4 FragColor;                                                                                 
                                                  
void main() {                                     
   FragColor = vec4(0.9f, 0.2f, 0.2f, 1.0f);      
}                                                 

Shader Class

We collect vertex and fragment code to a file “basic.shader”. After that, we create a parser for separate vertex and fragment code. Why do we do that? Instead of reading two files we want to read only one file. Of course, you can read vertex and fragment shader code from different files.

Also, we add functions for getting uniform location and setting uniform. We use unordered-map for caching. (

#pragma once
#include <string>
#include <unordered_map>

struct ShaderProgramSource {
	std::string VertexSource;
	std::string FragmentSource;
};

class Shader
{
	unsigned int ID{}; 
	std::unordered_map<std::string, unsigned int> m_UniformLocationCache;

	unsigned int CompileShader(std::string source, unsigned int shader_type);
	void CompileProgram(unsigned int vs, unsigned int fs);

	ShaderProgramSource ParseShader(const std::string& filepath);
	int GetUniformLocation(const std::string& name);

public:
	void Compile();
	void Bind();
	void Unbind();

	void SetUniform1f(const std::string& name, float value);

};
#include "Shader.h"
#include "glad/glad.h"

#include <iostream>
#include <fstream>
#include <sstream>

void Shader::Compile(){
    auto source = ParseShader("basic.shader");
    std::cout << "Vertex shader:\n" << std::endl;
    std::cout << source.VertexSource << std::endl << std::endl;
    std::cout << "Fragment shader:\n" << std::endl;
    std::cout << source.FragmentSource << std::endl;

    unsigned int vs = CompileShader(source.VertexSource, GL_VERTEX_SHADER);
    unsigned int fs = CompileShader(source.FragmentSource, GL_FRAGMENT_SHADER);
    CompileProgram(vs, fs);
}

unsigned int Shader::CompileShader(std::string source, unsigned int shader_type)
{
    const char* source_code = source.c_str();
    unsigned int shaderID = glCreateShader(shader_type);

    glShaderSource(shaderID, 1, &source_code, NULL);
    glCompileShader(shaderID);

    // check for shader compile errors
    int success;
    char infoLog[512];
    glGetShaderiv(shaderID, GL_COMPILE_STATUS, &success);
    if (!success) {
        glGetShaderInfoLog(shaderID, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::" << (shader_type == GL_VERTEX_SHADER ? "VERTEX" : "FRAGMENT") << "::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    return shaderID;
}

void Shader::CompileProgram(unsigned int vs, unsigned int fs){
    // link shaders to a program 
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vs);
    glAttachShader(shaderProgram, fs);
    glLinkProgram(shaderProgram);

    // check for linking errors
    int success;
    char infoLog[512];
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }

    // delete shader after linking program because we dont need them 
    glDeleteShader(vs);
    glDeleteShader(fs);

    ID = shaderProgram;

}

ShaderProgramSource Shader::ParseShader(const std::string& filepath){
    std::ifstream stream(filepath);

    enum class ShaderType {
        NONE = -1, VERTEX = 0, FRAGMENT = 1
    };

    std::string line;
    std::stringstream ss[2];
    ShaderType type = ShaderType::NONE;

    while (getline(stream, line))
    {
        if (line.find("#shader") != std::string::npos) {

            if (line.find("vertex") != std::string::npos)
                type = ShaderType::VERTEX;//set mode to vertex
            else if (line.find("fragment") != std::string::npos)
                type = ShaderType::FRAGMENT;//set mode to fragment
        }
        else {
            ss[(int)type] << line << "\n";
        }
    }

    return { ss[0].str(),ss[1].str() };
}

int Shader::GetUniformLocation(const std::string& name)
{
    if (m_UniformLocationCache.find(name) != m_UniformLocationCache.end())
        return m_UniformLocationCache[name];


    int location = glGetUniformLocation(ID, name.c_str());
    if (location == -1) 
        std::cout << "Warning: uniform " << name << " doesn't exist!" << std::endl;
    else 
        m_UniformLocationCache[name] = location;

    return location;
}

void Shader::Bind(){
    glUseProgram(ID);
}

void Shader::Unbind(){
    glUseProgram(0);
}

void Shader::SetUniform1f(const std::string& name, float value)
{
    glUniform1f(GetUniformLocation(name), value);
}

Render Loop

We only add one line of code to pass the time to the shader program.

    // |>----------<>----------<>----------<>----------<>----------<|
    // |>                       RENDER LOOP                        <|
    // |>----------<>----------<>----------<>----------<>----------<|

    va.Bind();

    while (!glfwWindowShouldClose(window)) {
        shader.SetUniform1f("time", glfwGetTime()); // this line

        glClearColor(0.2f, 0.2f, 0.2f, 1.0f); 
        glClear(GL_COLOR_BUFFER_BIT); 

        glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

  • https://www.khronos.org/opengl/wiki/Uniform_(GLSL)#:~:text=A%20uniform%20is%20a%20global,stored%20in%20a%20program%20object.
  • https://registry.khronos.org/OpenGL-Refpages/gl4/html/glGetUniformLocation.xhtml
  • https://registry.khronos.org/OpenGL-Refpages/gl4/html/glUniform.xhtml


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *