Niedziałający vertex shader w programie OpenGL

0

Napisałem dla OpenGL'a klasy Display, Shader i Buffer, które mają pozwolić na obiektową obsługę aplikacji.
Udało mi się z tymi 3 klasami napisać prosty program wyświetlający trójkąt.
Jednakże z zapewne jakiegoś prostego błędu, vertex shader w ogóle nie działa na geometrię trójkąta.
Jest to coś dziwnego, ponieważ fragment shader jest w stanie pokolorować trójkąt na czerwono zgodnie z zapisem.
Prosiłbym o pomoc, bo jestem początkującym w OpenGL'u i nie mogę znaleźć żadnego błędu... Przydały by mi się też wskazówki dotyczące poprawek w moich klasach.

main.cpp

#include <iostream>
#include "Shader.h"
#include "Buffer.h"
#include "Display.h"

int main()
{
    Display display;
    display.setAttributes("GAME", 1280, 720, false, 0, GLFW_DONT_CARE);
    if(!display.create())return -1;

    glClearColor(0.0f, 1.0f, 1.0f, 1.0f);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    GLuint VertexArrayID;
    glGenVertexArrays(1, &VertexArrayID);
    glBindVertexArray(VertexArrayID);

    float vertex_buffer_data[9] = {0.0f, 0.5f, 0.0f, -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f};
    Buffer VBO(GL_ARRAY_BUFFER);
    VBO.data(vertex_buffer_data, sizeof(vertex_buffer_data), GL_STATIC_DRAW);

    Shader shader("shader.vsh", "shader.fsh");
    shader.addAttribute(0, "vertex");
    //shader.addUniform(1,"imAnInt");

    while(!display.isShouldClose()) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        shader.bind();
        VBO.bind(0, 3, GL_FLOAT, 0, 0);

        glDrawArrays(GL_TRIANGLES, 0, 3);

        VBO.unbind();
        shader.unbind();

        display.update();
    }
    display.destroy();
    return 0;
}

Shader.h

#ifndef SHADER_H
#define SHADER_H

#define GLEW_STATIC
#include <GL/glew.h>
#include <glm/vec3.hpp>
#include <glm/mat4x4.hpp>
#include <iostream>
#include <fstream>
#include <map>

class Shader {
private:
    GLuint programID;
    GLuint vertexShaderID;
	GLuint fragmentShaderID;

	GLuint loadShaderFromFile(std::string filepath, GLuint type) {
        std::ifstream file(filepath);
        std::string code((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>()));

        GLuint shaderID = glCreateShader(type);
        char const* source = code.c_str();
        glShaderSource(shaderID, 1, &source, nullptr);
        glCompileShader(shaderID);
        GLint status = GL_FALSE;
        glGetShaderiv(shaderID, GL_COMPILE_STATUS, &status);
        if(status == GL_FALSE){
            GLint logSize;
            glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &logSize);
            GLchar description[logSize];
            glGetShaderInfoLog(shaderID, logSize, nullptr, &description[0]);
            std::cout<<"\n"<<description;
            std::cout<<"\nCould not compile shader!";
        }
        glDetachShader(programID, vertexShaderID);
        glDetachShader(programID, fragmentShaderID);
        glDeleteShader(vertexShaderID);
        glDeleteShader(fragmentShaderID);
        return shaderID;
    }
public:
    Shader(std::string vertexShaderFilePath,std::string fragmentShaderFilePath) {
        std::cout<<"\nCompiling vertex shader...";
		vertexShaderID = loadShaderFromFile(vertexShaderFilePath, GL_VERTEX_SHADER);
		std::cout<<"\nCompiling fragment shader...";
		fragmentShaderID = loadShaderFromFile(fragmentShaderFilePath, GL_FRAGMENT_SHADER);
		programID = glCreateProgram();
		glAttachShader(programID, vertexShaderID);
		glAttachShader(programID, fragmentShaderID);
		glLinkProgram(programID);
		GLint status = GL_FALSE;
		glGetProgramiv(programID, GL_LINK_STATUS, &status);
        if(status == GL_FALSE){
            GLint logSize;
            glGetShaderiv(programID, GL_INFO_LOG_LENGTH, &logSize);
            GLchar description[logSize];
            glGetProgramInfoLog(programID, logSize, nullptr, &description[0]);
            std::cout<<"\n"<<description;
            std::cout<<"\nCould not link shader program!";
        }
		glValidateProgram(programID);
	}

    void addAttribute(GLint index, std::string name) {
        glBindAttribLocation(programID, index, name.c_str());
    }

    void addUniform(int value, std::string name) {
        glUniform1i(glGetUniformLocation(programID, name.c_str()), value);
    }
    void addUniform(float value, std::string name) {
        glUniform1f(glGetUniformLocation(programID, name.c_str()), value);
    }
    void addUniform(glm::vec3 value, std::string name) {
        glUniform3f(glGetUniformLocation(programID, name.c_str()), value.x, value.y, value.z);
    }
    void addUniform(glm::mat4 value, std::string name) {
        glUniformMatrix4fv(glGetUniformLocation(programID, name.c_str()), 1, GL_FALSE, &value[0][0]);
    }

    void bind() {
		glUseProgram(programID);
	}

	void unbind() {
		glUseProgram(0);
	}

    ~Shader(){
        glDeleteProgram(programID);
    }
};

#endif

Buffer.h

#ifndef BUFFER_H
#define BUFFER_H

#define GLEW_STATIC
#include <GL/glew.h>
#include <iostream>

class Buffer {
private:
    GLuint bufferID;
    GLuint index;
    GLenum target;
    bool active;
public:
    Buffer(GLenum target) {
        this->target = target;
        this->active = false;
        glGenBuffers(1, &bufferID);
    }

    void data(const void *data, int dataSize, GLenum mode) {
        glBindBuffer(target, bufferID);
        glBufferData(target, dataSize, data, mode);
        glBindBuffer(target, 0);
    }

    void bind(GLuint index, GLint unitSize, GLenum dataType, GLsizei stride, int pointer) {
        active = true;
        index = index;
        glEnableVertexAttribArray(index);
        glBindBuffer(target, bufferID);
        glVertexAttribPointer(index, unitSize, dataType, GL_FALSE, stride, (void*)pointer);
    }

    void unbind() {
        active = false;
        glBindBuffer(target, 0);
        glDisableVertexAttribArray(index);
    }

    GLuint getID() {
        return bufferID;
    }

    ~Buffer() {
        if(active)
            unbind();
        glDeleteBuffers(1, &bufferID);
    }
};

#endif

Display.h

#ifndef DISPLAY_H
#define DISPLAY_H

#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <string>

static void error_callback(int error, const char* description) {
    fprintf(stderr, "GLFW Error: %s\n", description);
}

class Display {
private:
    GLFWwindow* window;
    bool shouldClose;
    std::string title;
    int width, height;
    bool fullscreen;
    int msaa_samples;
    int fps_cap;
public:
    Display() {
        shouldClose = true;
        setAttributes();
    }

    void setAttributes(std::string title="OpenGL", int width=640, int height=640, bool fullscreen=false, int msaa_samples=0, int fps_cap=60) {
        this->title = title;
        this->width = width;
        this->height = height;
        this->fullscreen = fullscreen;
        this->msaa_samples = msaa_samples;
        this->fps_cap = fps_cap;
    }

    bool create() {
        glfwSetErrorCallback(error_callback);
        if(!glfwInit())
            return false;
        glfwWindowHint(GLFW_REFRESH_RATE, fps_cap);
        glfwWindowHint(GLFW_SAMPLES, msaa_samples);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
        window = glfwCreateWindow(width, height, title.c_str(), fullscreen?glfwGetPrimaryMonitor():nullptr, nullptr);
        if(!window) {
            glfwTerminate();
            return false;
        }
        glfwMakeContextCurrent(window);
        glewExperimental = true;
        if(glewInit() != GLEW_OK) {
            fprintf(stderr, "Failed to initialize GLEW!\n");
            glfwTerminate();
            return false;
        }
        glViewport(0, 0, width, height);
        shouldClose = false;
        return true;
    }

    void update() {
        shouldClose = glfwWindowShouldClose(window);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    void destroy() {
        glfwDestroyWindow(window);
        glfwTerminate();
    }

    bool reload() {
        glfwDestroyWindow(window);
        return create();
    }

    bool isShouldClose() {
        return shouldClose;
    }
};

#endif

SHADERY

#version 330 core

in vec3 vertex;

void main()
{
    gl_Position = vec4(vertex+vec3(0.1),1.0);
}
//////////////////////////////////////////////////////////
#version 330 core

out vec4 OUT_COLOR;

void main()
{
    OUT_COLOR = vec4(1.0,0.0,0.0,1.0);
}

W kodzie mogą jeszcze być niepotrzebne #include.

0

Problem masz w metodzie loadShaderFromFile.
Wywołanie Detach/DeleteShader powinno być dopiero po linkowaniu do programu.
Przenieś te 4 linijki poniżej glLinkProgram(programID);

0

Dzięki za zwięzłą odpowiedź! Vertex shader już działa, ale niepokoi mnie to, że po wykasowaniu shader.addAttribute(0, "vertex");, shader nie wywala błędu i wszystko dalej działa... :/
Nie działa też addUniform(1.0f, "imFloat")(ustawione poniżej shader.addAttribute(0, "vertex");), po przekazaniu liczby 1.0f do programu i ustawieniu jej w OUT_COLOR = vec4(1.0,imFloat,0.0,1.0);, kolor figury także się nie zmienia.

Nie chcę tworzyć o tym nowego wątku więc piszę tutaj.

EDIT: Przed dodawaniem uniformów, musiałem zbindować shader. Teraz działają.
Nadal nie wiem, dlaczego vertex shader nie wywala błędu po zmianie nazwy atrybutu z "vertex" na "vertx" w shader.addAttribute();...

0

Po prostu in vec3 vertex ma przydzielony z automatu jakiś indeks jeśli tego nie zrobisz ręcznie. Teoretycznie wtedy należy pobrać ten indeks za pomocą glGetAttribLocation, ale widocznie masz szczęście i jest nim 0.

PS.

       shader.bind();
       VBO.bind(0, 3, GL_FLOAT, 0, 0);
       glDrawArrays(GL_TRIANGLES, 0, 3);
       VBO.unbind();
       shader.unbind();

jeśli nie zmieniasz shadera ani aktywnego VBO to nie musisz w kółko bindować i odbindowywać.

0

Aha, ale jeżeli wywołam addAttribute() z poprawną nazwą, a następnie link() (nowa metoda w class Shader), to nie muszę podawać numeru lokacji atrybutu w shaderze?

Wprowadziłem kilka poprawek w kodzie, więc tamten jest nieaktualny. Ten bind() i unbind() ustawiłem, żeby mieć kod "gotowy" pod więcej niż 1 model, teraz dodałem bind() i unbind() do VAO.

while(!display.isShouldClose()) {
        glm::mat4 model = glm::rotate(glm::mat4(1.0f), glm::radians((float)glfwGetTime()*100.0f/6.0f), glm::vec3(0.0f,0.0f,1.0f));

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glBindVertexArray(VAO);
        shader.bind();
        shader.addUniform(model, "modelMatrix");
        VBO.bind(0, 3, GL_FLOAT, 0, 0);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        VBO.unbind();
        shader.unbind();
        glBindVertexArray(0);

        display.update();
    }
1

Aha, ale jeżeli wywołam addAttribute() z poprawną nazwą, a następnie link() (nowa metoda w class Shader), to nie muszę podawać numeru lokacji atrybutu w shaderze?

Wolałbym dyskutować w kategoriach funkcji OpenGL, nie twoich wrapperów.

Są trzy sposoby (welcome to GL) na lokacje atrybutów:
a) po glLinkProgram użyć glGetAttribLocation by pobrać przydzieloną automatycznie lokację atrybutu (wymaga OpenGL 2.0)
b) przed glLinkProgram użyć glBindAttribLocation by narzucić lokację atrybutu (wymaga OpenGL 2.0)
c) wewnątrz shadera użyć layout (location = N) by narzucić lokację atrybutu (wymaga OpenGL 3.3)

1 użytkowników online, w tym zalogowanych: 0, gości: 1