Alper Şahıstan
Alper Şahıstan

Reputation: 11

Defining multiple structs and setting them as uniforms in Fragment Shader GLSL

Im trying to create a material struct and a light struct that I will use in GLSL fragment shader. I can render the scene I have correctly when I have only one struct defined (either material or light) but when I define both of them together the glGetUniformLocation(...) calls return -1 for Material structs (order of defnitions does not matter) as if they are not there or not being used. I need to use the structs for the future use please do not ask me to use PODs. I want to learn how to upload multiple structs as uniforms. Thanks.

Here is the vertex shader:

#version 450

layout (location = 0) in vec3 pos;
layout (location = 1) in vec3 norm;

layout (location = 2) out vec3 v_space_norm;
layout (location = 3) out vec3 v_space_pos;

layout(location = 0) uniform mat4 to_screen_space; // mvp
layout(location = 1) uniform mat4 to_view_space; //mv
layout(location = 2) uniform mat3 normals_to_view_space;
//layout(location = 4) float light_intensity;

void main() {
gl_Position = to_screen_space \* vec4(pos, 1.0);
v_space_norm = normals_to_view_space \* norm;
v_space_pos = (to_view_space \* vec4(pos, 1.0)).xyz;
}

and fragment shader:

#version 450
precision mediump float;
//------------ Structs ------------
// struct Light{
//      vec3 position;
//      float intensity;
// };

//------------ Variying ------------
layout (location = 2) in vec3 v_space_norm;
layout (location = 3) in vec3 v_space_pos;

//------------ Uniforms ------------
layout(location = 1) uniform mat4 to_view_space; //mv
//layout(location = 3) uniform vec3 light_position;

//layout(location = 4) uniform float light_intensity;
layout(location = 3) uniform struct{
     vec3 position;
     float intensity;
}light;

layout(location = 6) uniform struct{
     vec3 ka;
     vec3 kd;
     vec3 ks;
     float shininess;
} material;


out vec4 color;

void main() {
     vec3 v_space_norm = normalize(v_space_norm);
     vec3 l =  normalize( (to_view_space * vec4(light.position, 1)).xyz - v_space_pos);//normalize(l); //light vector
     vec3 h = normalize(l + vec3(0,0,1)); //half vector

     float cos_theta = dot(l, v_space_norm);
     if(cos_theta >= 0)
     {
          vec3 diffuse = material.kd * max(cos_theta,0);
          vec3 ambient = material.ka;
          vec3 specular= material.ks * pow(max(dot(h, v_space_norm),0), material.shininess);
          color = vec4(light.intensity * (specular + diffuse) + ambient, 1);
     }
     else
     {
          color = vec4(material.ka,1);
     }
}

And the way I set uniforms:

...
program->SetUniform("light.position", light.position);
program->SetUniform("light.intensity", light.intensity);
program->SetUniform("material.ka", material.ambient);
program->SetUniform("material.kd", material.diffuse);
program->SetUniform("material.ks", material.specular);
program->SetUniform("material.shininess", material.shininess);
...

The definition of SetUniform:

GLint location = glGetUniformLocation(glID, name);
if (location == -1)
{
    std::cout << "ERROR::SHADER::UNIFORM::" << name << "::NOT_FOUND"<<std::endl;
    return;
}
glUniform1f(location, value);//or what ever the type is there are definitions for all types!!!

I was unable to find anything online about my problem

I was hoping to see the teapot rendered and blinn shaded. And I can get that if I only define one struct and then upload the other properties as PODs.

So using the fragment shader below:

//------------ Uniforms ------------
layout(location = 1) uniform mat4 to_view_space; //mv
layout(location = 3) uniform vec3 light_position;

layout(location = 4) uniform float light_intensity;
// layout(location = 3) uniform struct{
//      vec3 position;
//      float intensity;
// }light;
...

//using light_position and light_intensity instead of light.position, light.intensity

Of course setUniform calls are also changed accordingly. enter image description here

But if I use both of the structs I get this: enter image description here

Edit: Instead of accessing via glGetUniformLocation(glID, name); I can manually set each variable via layout location number. Now the question becomes "Can I set the structs using the string variable names?"

Upvotes: 0

Views: 458

Answers (2)

Alper Şahıstan
Alper Şahıstan

Reputation: 11

Using named structs like:

struct Light{
      vec3 position;
      float intensity;
};

And then using them as uniforms seems to be the correct way to go according to OpenGL wiki (I could have sworn I tried that but oh well...).

layout(location = 3) uniform Light light;

So,

layout(location = 3) uniform struct{
     vec3 position;
     float intensity;
}light;

this is either not good or undefined behavior which is beyond my knowledge.

Upvotes: 1

Carl HR
Carl HR

Reputation: 820

I don't know what you're doing wrong on your code, as you didn't provided a minimal reproducible example of your problem, so I can't point what you're doing it wrong.

By writing my own application to:

  1. Load a GLFW window
  2. Load OpenGL Context
  3. Load a Shader based on your GLSL scripts (vertex and fragment)
  4. Load all uniforms
  5. Print all uniforms

That's the output of my application:

SHADER: Loading uniforms
(location = 0 ): light.intensity       <- NOTE: This looks weird because
(location = 1 ): light.position        i'm not searching the correct location
(location = 2 ): material.ka           id for each uniform name. I'm guessing
(location = 3 ): material.kd           them as if you've never set the
(location = 4 ): material.ks           locations manually. The print
(location = 5 ): material.shininess    occurs inside a for-loop. If you
(location = 6 ): normals_to_view_space take out all `layout(location=*)`
(location = 7 ): to_screen_space       from the GLSL scripts, the ids printed
(location = 8 ): to_view_space         here will look ok.

SHADER: Printing Uniforms
Uniform 'light.intensity' location is (4)
Uniform 'light.position' location is (3)
Uniform 'material.ka' location is (6)
Uniform 'material.kd' location is (7)
Uniform 'material.ks' location is (8)
Uniform 'material.shininess' location is (9)
Uniform 'normals_to_view_space' location is (2)
Uniform 'to_screen_space' location is (0)
Uniform 'to_view_space' location is (1)

So, in my test application, the uniforms locations were loaded successfully.

Here's the code I used. Note I also use the OpenGL version 4.50, with Core Profile. My code is an adaptation of the code provided from the learnopengl tutorials:

main.cpp:

#include "shader.hpp"

int main(int argc, char **argv) {
    GLFWwindow* window = NULL;
    Shader shader;
    GLuint vao;

    glewExperimental = true;
    if (!glfwInit()) {
        std::cout << "GLFW::FAILED" << std::endl;
        return false;
    } else {
        glfwWindowHint(GLFW_SAMPLES, 4);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // Our OpenGL version must be set 
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); // to 4.50 (same as the shader)
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

        window = glfwCreateWindow(800, 600, "GLSLloader.exe", NULL, NULL);

        if (window == NULL) {
            std::cout << "GLFW::FAILED::CREATE::WINDOW" << std::endl;
            return false;
        } else {
            glfwMakeContextCurrent(window);

            if (glewInit() != GLEW_OK) {
                std::cout << "GLEW::FAILED" << std::endl;
            } else {
                glGenVertexArrays(1, &vao);
                glBindVertexArray(vao);

                shader.LoadProgram();
                shader.PrintUniforms();
            }
        }
    }

    return 0;
}

shader.hpp:

#ifndef _MY_SHADER_LOADER_
#define _MY_SHADER_LOADER_

// You can get all g++ flags and links by using the following command:
// pkg-config glfw3 glm glew --cflags --libs
// 
// Compile with:
// g++ main.cpp shader.cpp -I./ -IC:/msys64/mingw64/bin/../include -LC:/msys64/mingw64/bin/../lib -lglfw3 -lglew32 -o GLSLloader.exe

#include <cstdio>
#include <cstdlib>
#include <cstring>

#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <istream>
#include <ostream>

#include <map>
#include <vector>
#include <list>

extern "C" {
    #include <GL/glew.h>
    #include <GLFW/glfw3.h>
}

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

using ShaderUniforms = std::map<std::string, GLuint>;
class Shader {
private:
    ShaderUniforms uniforms;
    GLuint program;

    void LoadUniforms();
    GLuint Attachment(std::string filename, GLenum stype);

public:
    Shader();
    ~Shader();

    bool LoadProgram();
    void ReleaseProgram();
    void SetUniform(std::string key, const float data);

    void PrintUniforms();
};

#endif

shader.cpp:

#include "shader.hpp"

// Code adaptated from: https://learnopengl.com/Getting-started/Shaders

Shader::Shader() {
    program = 0;
    uniforms = ShaderUniforms();
}

Shader::~Shader() {
    ReleaseProgram();
}

bool Shader::LoadProgram() {
    char infoLog[512];
    bool success;
    int result;

    GLuint vertex = Attachment("vertex.glsl", GL_VERTEX_SHADER);
    GLuint fragment = Attachment("fragment.glsl", GL_FRAGMENT_SHADER);

    ReleaseProgram();

    program = glCreateProgram();
    success = true;

    glAttachShader(program, vertex);
    glAttachShader(program, fragment);

    glLinkProgram(program);
    glGetProgramiv(program, GL_LINK_STATUS, &result);

    if (!result) {
        glGetProgramInfoLog(program, 512, NULL, infoLog);
        std::cout << "SHADER::FAILED::LINK-PROGRAM " << infoLog << std::endl;
        success = false;
    }

    if (vertex != 0) {
        glDeleteShader(vertex);
    }

    if (fragment != 0) {
        glDeleteShader(fragment);
    }

    if (success) {
        LoadUniforms();
        return true;
    } else {
        return false;
    }
}

void Shader::ReleaseProgram() {
    if (program != 0) {
        glDeleteProgram(program);
        program = 0;
    }

    uniforms.clear();
}

void Shader::SetUniform(std::string key, const float data) {
    ShaderUniforms::iterator it;
    
    it = uniforms.find(key);
    if (it != uniforms.end()) {
        glUniform1f(it->second, data);
    }
}

// Private
// Source: https://stackoverflow.com/a/442819/14956120
void Shader::LoadUniforms() {
    GLint n;
    GLint size;
    GLint bufSize;
    GLenum type;
    GLsizei length;
    std::string name;

    glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &n);
    glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &bufSize);

    std::vector<GLchar> uName = std::vector<GLchar>(bufSize+1, 0);

    std::cout << "SHADER: Loading uniforms" << std::endl;
    for (GLint i = 0; i < n; ++i) {
        std::fill(uName.begin(), uName.end(), 0);
        glGetActiveUniform(program, i, bufSize, &length, &size, &type, &(uName[0]));
        if (length > 0) {
            name = std::string(uName.begin(), uName.end());
            std::cout << "(location = " << i << " ): " << name << std::endl;
            uniforms.insert(std::pair<std::string, GLuint>(name, (GLuint) i));
        }
    }
}

// Private
GLuint Shader::Attachment(std::string filename, GLenum stype) {
    std::string code;
    std::ifstream file;
    std::stringstream stream;
    GLuint shader;
    char* ccode;
    char infoLog[512];
    int result;

    file.exceptions(std::ifstream::failbit | std::ifstream::badbit);

    try {
        file.open(filename);
        stream << file.rdbuf();
        file.close();
        code = stream.str();
    } catch (std::ifstream::failure& e) {
        std::cout << "SHADER::FAILED::READ::SOURCECODE (" << filename << ")" << std::endl;
        return 0;
    }

    shader = glCreateShader(stype);
    ccode = (char*) code.c_str();
    glShaderSource(shader, 1, (const GLchar**) &(ccode), NULL);
    glCompileShader(shader);

    glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
    if (!result) {
        memset(infoLog, '\0', sizeof(char) * 512);
        glGetShaderInfoLog(shader, 512, NULL, infoLog);
        std::cout << "SHADER::FAILED::COMPILE (" << filename << "): " << infoLog << std::endl;
    }

    return shader;
}

// Used for testing only
void Shader::PrintUniforms() {
    ShaderUniforms::iterator it;
    GLint location = -1;
    std::string name;

    std::cout << std::endl;
    std::cout << "SHADER: Printing Uniforms" << std::endl;
    for (it = uniforms.begin(); it != uniforms.end(); ++it) {
        name = it->first;

        location = glGetUniformLocation(program, name.c_str());
        if (location == -1) {
            std::cout << "ERROR::SHADER::UNIFORM::" << name << "::NOT_FOUND"<<std::endl;
            return;
        } else {
            std::cout << "Uniform '" << name << "' location is (" << location << ")" << std::endl;
        }
    }
}

vertex.glsl:

#version 450

layout (location = 0) in vec3 pos;
layout (location = 1) in vec3 norm;

layout (location = 2) out vec3 v_space_norm;
layout (location = 3) out vec3 v_space_pos;

layout(location = 0) uniform mat4 to_screen_space; // mvp
layout(location = 1) uniform mat4 to_view_space; //mv
layout(location = 2) uniform mat3 normals_to_view_space;
//layout(location = 4) float light_intensity;

void main() {
    // for some reason, you wrote \* instead of *. So I removed the
    // backslash character as the GLSL compiler complained about it
    gl_Position = to_screen_space * vec4(pos, 1.0);
    v_space_norm = normals_to_view_space * norm;
    v_space_pos = (to_view_space * vec4(pos, 1.0)).xyz;
}

fragment.glsl:

#version 450
precision mediump float;
//------------ Structs ------------
// struct Light{
//      vec3 position;
//      float intensity;
// };

//------------ Variying ------------
layout (location = 2) in vec3 v_space_norm;
layout (location = 3) in vec3 v_space_pos;

//------------ Uniforms ------------
layout(location = 1) uniform mat4 to_view_space; //mv
//layout(location = 3) uniform vec3 light_position;

//layout(location = 4) uniform float light_intensity;
layout(location = 3) uniform struct{
     vec3 position;
     float intensity;
}light;

layout(location = 6) uniform struct{
     vec3 ka;
     vec3 kd;
     vec3 ks;
     float shininess;
} material;


out vec4 color;

void main() {
     vec3 v_space_norm = normalize(v_space_norm);
     vec3 l =  normalize( (to_view_space * vec4(light.position, 1)).xyz - v_space_pos);//normalize(l); //light vector
     vec3 h = normalize(l + vec3(0,0,1)); //half vector

     float cos_theta = dot(l, v_space_norm);
     if(cos_theta >= 0)
     {
          vec3 diffuse = material.kd * max(cos_theta,0);
          vec3 ambient = material.ka;
          vec3 specular= material.ks * pow(max(dot(h, v_space_norm),0), material.shininess);
          color = vec4(light.intensity * (specular + diffuse) + ambient, 1);
     }
     else
     {
          color = vec4(material.ka,1);
     }
}

Here's my project structure:

.
├── main.cpp
├── shader.hpp
├── shader.cpp
├── vertex.glsl
└── fragment.glsl

I'm using MSYS2 on windows 10, to compile and run the application.

Upvotes: 0

Related Questions