Shaders
GLSL¶
Egy tipikus shader szignatúra:
#version version_number
in type in_variable_name;
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;
void main() {
// process input(s) and do some graphics stuff
...
// output processed stuff to output variable
out_variable_name = processed_data;
}
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
Types¶
A GLSL rendelkezik a C nyelvben is ismert legtöbb típussal: int, float, double, uint és bool. Továbbá a GLSL tartalmaz két beépített container osztályt is: vector és matrix.
Vectors¶
A vektort a GLSL-ben 1,2,3 vagy 4 komponensű container-ként definiálhatjuk, ahol a komponensek az említett C típusok lehetnek. A következő vektor típusokat használhatjuk (n a komponensek száma):
- vecn: alapértelmezett vektor n db float értékkel
- bvecn: bool vektor
- ivecn: integer vektor
- uvecn: unsigned integer vektor
- dvecn: double vektor
Egy vektor komponenseit a következő képpen lehet elérni: vec.x, ahol az x a vektor első komponense. Hasonlóan érhetjük el az y, z és a w komponenseket. A GLSL lehetővé teszi az rgba használatát a color információhoz, továbbá a stpq-t a textúra koordináták eléréséhez.
A vektor típus lehetővé teszi az egyes komponensek rugalmas kezelését és elérését:
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);
A vektorokat használhatjuk az egyes shader-ek input és output változóikként. A mátrix osztály használatáról a későbbi tutorialok során lesz szó.
In és Out¶
A másik különbség, hogy a fragment shader esetén definiálni kell egy vec4 típusú color output változót, mert a fragment shader célja, hogy meghatározza a végső színt. Ha nem definiáljuk ezt az output color-t, akkor az OpenGL fekete vagy fehér színnel fogja megjeleníteni az objektumokat.
Az előző tutorial kódját fogjuk módosítani úgy, hogy a vertex shader határozza meg a színtm amit továbbít a fragment shader-nek.
Vertex shader
#version 400 core
layout (location = 0) in vec3 aPos; // the position variable has attribute position 0
out vec4 vertexColor; // specify a color output to the fragment shader
void main() {
gl_Position = vec4(aPos, 1.0); // see how we directly give a vec3 to vec4's constructor
vertexColor = vec4(0.0, 0.0, 1.0, 1.0); // set the output variable to blue color
}
Fragment shader
out vec4 FragColor;
in vec4 vertexColor; // the input variable from the vertex shader (same name and same type)
void main() {
FragColor = vertexColor;
}
Uniforms¶
#version 400 core
out vec4 FragColor;
uniform vec4 ourColor; // we set this variable in the OpenGL code.
void main() {
FragColor = ourColor;
}
A uniform változót most a main függvényben fogjuk definiálni. Először meg kell határoznunk a shader-ben használt uniform változó indexét/lokációját, majd utána frissíthetjük az értékét. Ahelyett, hogy egy konstans színt állítanánk be, folyamatosan frissítjük azt az egyes iterációk alatt:
float timeValue = glfwGetTime();
float blueValue = (sin(timeValue) / 2.0f) + 0.5f;
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUseProgram(shaderProgram);
glUniform4f(vertexColorLocation, 0.0f, 0.0f, blueValue, 1.0f);
- f: float típus
- i: int típus
- ui: unsigned int típus
- 3f: 3 db float típus
- fv: float vektor
A mi esetünkben 4 db float értéket kell beállítanunk a uniform változónak ezért a glUniform4f verziót használjuk. A render loop-ban minden egyes iterációban kiszámoljuk a háromszög aktuális színét és frissítjük a uniform változón keresztül:
while(!glfwWindowShouldClose(window)) {
// input
processInput(window);
// render
// clear the colorbuffer
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// be sure to activate the shader
glUseProgram(shaderProgram);
// update the uniform color
float timeValue = glfwGetTime();
float blueValue = sin(timeValue) / 2.0f + 0.5f;
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUniform4f(vertexColorLocation, 0.0f, 0.0f, blueValue, 1.0f);
// now render the triangle
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
// swap buffers and poll IO events
glfwSwapBuffers(window);
glfwPollEvents();
}
Szín minden vertexhez¶
float vertices[] = {
// positions // colors
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // top
};
Mivel most több adatot fogunk küldeni, ezért ennek megfelelően fel kell venni egy input color attribútumot a vertex shader-ben, hogy fogadja a szín adatokat is. A color attribútum lokációját a layout kulcsszóval 1-re állítjuk:
#version 400 core
layout (location = 0) in vec3 aPos; // the position variable has attribute position 0
layout (location = 1) in vec3 aColor; // the color variable has attribute position 1
out vec3 ourColor; // output a color to the fragment shader
void main() {
gl_Position = vec4(aPos, 1.0);
ourColor = aColor; // set ourColor to the input color we got from the vertex data
}
Mivel a uniform fragment color változó helyett, most a vertex shader-ből küldjük a szín információt a fragment shader-nek ezért azt is módosítanunk kell:
#version 400 core
out vec4 FragColor;
in vec3 ourColor;
void main() {
FragColor = vec4(ourColor, 1.0);
}
Mivel egy új vertex attribútumot hoztunk létre, ezért újra kell konfigurálnunk a vertex attribútum pointer-t, ami a következő képpen néz ki:
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// color attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float)));
glEnableVertexAttribArray(1);
Az első két kódsor a 0 lokációhoz tartozó pozíció attribútumot, míg a második két kódsor az 1 lokációhoz tartozó szín attribútumot konfigurálja fel. Ahogy a fenti képen is látszik a stride 6 sizeof(float) érték mind a két változó esetén. A pozíciónál az offset érték 0, míg a színnél 3sizeof(float).
A kód a következő képet rajzolja:
Saját shader osztály¶
Megírni, fordítani, linkelni és kezelni az egyes shader-eket bonyolult és hosszadalmas, főleg, ha sok shader-t használ az applikációnk. Ezért létrehozunk egy shader osztályt, ami sokkal kényelmesebbé teszi a kezelésüket: fájlból shader olvasás, fordítás és linkelés és közben hibakezelés:
#ifndef SHADER_H
#define SHADER_H
#include <glad/glad.h> // include glad to get all the required OpenGL headers
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
class Shader {
public:
// the program ID
unsigned int ID;
// constructor reads and builds the shader
Shader(const GLchar* vertexPath, const GLchar* fragmentPath);
// use/activate the shader
void use();
// utility uniform functions
void setBool(const std::string &name, bool value) const;
void setInt(const std::string &name, int value) const;
void setFloat(const std::string &name, float value) const;
};
#endif
A shader osztály használatához, először létre hozunk egy shader objektumot, majd a segédfüggvényeken keresztül tudjuk használni:
Shader ourShader("path/to/shaders/shader.vs", "path/to/shaders/shader.fs");
...
while(...) {
ourShader.use();
ourShader.setFloat("someUniform", 1.0f);
DrawStuff();
}