Renderer Overview
I like to keep all the rendering logic together in one place. For this I have the renderer struct which is
in charge of setting up the shader programs and then use them for rendering a scene.
Renderer
#ifndef RENDERER_H_
#define RENDERER_H_
struct uniform_locators {
GLuint ModelUniformLoc;
GLuint ViewUniformLoc;
GLuint ProjectionUniformLoc;
};
struct shader_program {
GLuint ID;
uniform_locators Uniforms;
};
struct renderer {
shader_program ShaderProgram;
};
renderer Renderer_Create(int ScreenWidth, int ScreenHeight);
void Renderer_Destroy(renderer &Renderer);
shader_program Renderer_CreateShaderProgram(const char *VertexFile,
const char *FragmentFile);
void Renderer_ClearBackground(float R, float G, float B, float Alpha);
#endif
Here I have defined the renderer struct that will hold all the shader programs that we will need. It can also be used for drawing anything from the scene and setting up the lights of the scene, but I will add those things later when we need them.
In the main.cpp now I can use this to create an instance of the renderer.
#include "renderer.h"
int main() {
...
renderer Renderer = Renderer_Create(Context.ScreenWidth, Context.ScreenHeight);
while (!glfwWindowShouldClose(Context.Window)) {
...
Renderer_ClearBackground(0.1f, 0.1f, 0.1f, 1.0f);
// Draw Calls
}
}
Here is the implementation of the renderer methods:
renderer Renderer_Create(int ScreenWidth, int ScreenHeight) {
renderer Renderer;
Renderer.ShaderProgram = Renderer_CreateShaderProgram(
"./resources/shaders/default.vert", "./resources/shaders/default.frag");
return Renderer;
}
void Renderer_Destroy(renderer &Renderer) {
glDeleteProgram(Renderer.ShaderProgram.ID);
}
shader_program Renderer_CreateShaderProgram(const char *VertexFile,
const char *FragmentFile) {
shader_program ShaderProgram;
std::string vertex_code = GetFileContents(VertexFile);
std::string fragment_code = GetFileContents(FragmentFile);
const char *VertexShaderSource = vertex_code.c_str();
const char *FragmentShaderSource = fragment_code.c_str();
// build and compile our shader program
// ------------------------------------
// vertex shader
GLuint VertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(VertexShader, 1, &VertexShaderSource, NULL);
glCompileShader(VertexShader);
// check for shader compile errors
int Success;
char InfoLog[512];
glGetShaderiv(VertexShader, GL_COMPILE_STATUS, &Success);
if (!Success) {
glGetShaderInfoLog(VertexShader, 512, NULL, InfoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n"
<< InfoLog << std::endl;
}
// fragment shader
GLuint FragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(FragmentShader, 1, &FragmentShaderSource, NULL);
glCompileShader(FragmentShader);
// check for shader compile errors
glGetShaderiv(FragmentShader, GL_COMPILE_STATUS, &Success);
if (!Success) {
glGetShaderInfoLog(FragmentShader, 512, NULL, InfoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n"
<< InfoLog << std::endl;
}
// link shaders
ShaderProgram.ID = glCreateProgram();
glAttachShader(ShaderProgram.ID, VertexShader);
glAttachShader(ShaderProgram.ID, FragmentShader);
glLinkProgram(ShaderProgram.ID);
// check for linking errors
glGetProgramiv(ShaderProgram.ID, GL_LINK_STATUS, &Success);
if (!Success) {
glGetProgramInfoLog(ShaderProgram.ID, 512, NULL, InfoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n"
<< InfoLog << std::endl;
}
glDeleteShader(VertexShader);
glDeleteShader(FragmentShader);
return ShaderProgram;
}
void Renderer_ClearBackground(float R, float G, float B, float Alpha) {
glClearColor(R, G, B, Alpha);
glClear(GL_COLOR_BUFFER_BIT);
}
std::string GetFileContents(const char *Filename) {
std::ifstream in(Filename, std::ios::binary);
if (in) {
std::string Contents;
in.seekg(0, std::ios::end);
Contents.resize(in.tellg());
in.seekg(0, std::ios::beg);
in.read(&Contents[0], Contents.size());
in.close();
return Contents;
}
throw errno;
}
The Renderer_Create function is the initial function that sets all the shader program info.
Then the Renderer_CreateShaderProgram creates the default shader program of the renderer. For this function
I need to provide a vertex and a fragment shader which are the minimum requirements from OpenGL to have
a complete rendering pipeline.
The Renderer_ClearBackground function, clears the entire color buffer which is the buffer that
gets renderer at the end of the frame.
The Shaders
Shaders are small programs that run on the GPU in different stages of the graphics pipeline. With this programs I can convert vertices into fully colored shapes and geometry. I can also add lights to the objects and other cool effects that I will cover later on this series.
For more information on how shaders work, I recommend reading this chapter of the LearnOpenGL online book.
Here is the code of the shaders. First the vertex shader:
#version 330 core
layout (location = 0) in vec3 a_pos;
layout (location = 1) in vec3 a_normal;
layout (location = 2) in vec2 a_tex_coords;
out vec3 Normal;
out vec3 FragPos;
out vec2 TexCoords;
uniform mat4 u_model;
uniform mat4 u_view;
uniform mat4 u_projection;
void main()
{
FragPos = vec3(u_model * vec4(a_pos, 1.0));
Normal = a_normal;
TexCoords = a_tex_coords;
gl_Position = u_projection * u_view * u_model * vec4(a_pos, 1.0);
}
And the fragment shader:
#version 330 core
out vec4 FragColor;
in vec3 Normal;
in vec3 FragPos;
in vec2 TexCoords;
void main() {
FragColor = vec4(1.0);
}
I will be expanding this ones in the next sections.