Rendertarget: "GL_COLOR_ATTACHMENT0"
Rendertarget: "GL_COLOR_ATTACHMENT1"
Rendertarget: "GL_COLOR_ATTACHMENT2"
Press key "0", "1", "2" to switch the rendertarget.
To render into multiple rendertargets at once, we have to modify our framebuffer and our fragmentshader:
1. Our framebuffer needs multiple attachments:
// attach renderbuffers to framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, renderbuffer1);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_RENDERBUFFER, renderbuffer2);
2. We have to define in which attachments we want to draw:
// specify the attachments in which to draw:
vector<GLenum> drawbuffers = { // array index || fragmentshader output
//-------------------------------------------------------------------------------------------------------------
GL_COLOR_ATTACHMENT0, // array index = 0: || "out layout (location = 0) ..."
GL_COLOR_ATTACHMENT1, // array index = 1: || "out layout (location = 1) ..."
GL_COLOR_ATTACHMENT2, // array index = 2: || "out layout (location = 2) ..."
};
glDrawBuffers(drawbuffers.size(), drawbuffers.data());
3. Finally the fragmentshader needs multiple output variables:
#version 450 core
in vec4 color;
out layout (location = 0) vec4 out_color0;
out layout (location = 1) vec4 out_color1;
out layout (location = 2) vec4 out_color2;
void main()
{
out_color0 = color;
out_color1 = vec4(1) - color;
out_color2 = vec4(1, 1, 0, 1);
}
"out_color0" will be written into attachment at index 0 in array "vector<GLenum> drawbuffers", which is GL_COLOR_ATTACHMENT0.
"out_color1" will be written into attachment at index 1 in array "vector<GLenum> drawbuffers", which is GL_COLOR_ATTACHMENT1.
"out_color2" will be written into attachment at index 2 in array "vector<GLenum> drawbuffers", which is GL_COLOR_ATTACHMENT2.
Complete Source Code:
Main.cpp
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <iostream>
#include <vector>
#include "Shader.h"
using namespace std;
using namespace glm;
// main functions
void Initialize();
void Render();
void CleanUp();
void CheckFramebuffer(GLuint framebuffer, GLenum target);
void CheckForGLError();
// callbacks
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
// variables
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
GLFWwindow* window = nullptr;
GLuint program = 0;
GLuint vertexshader = 0;
GLuint fragmentshader = 0;
GLuint vertexarray = 0;
GLuint vertexbuffer = 0;
GLuint framebuffer = 0;
GLuint renderbuffer0 = 0;
GLuint renderbuffer1 = 0;
GLuint renderbuffer2 = 0;
unsigned int framebufferwidth = 50;
unsigned int framebufferheight = 50;
GLenum currennt_readbuffer = GL_COLOR_ATTACHMENT0;
// structures
struct Vertex {
vec3 Position;
vec4 Color;
};
int main(void)
{
/* Initialize the library */
if (!glfwInit())
return -1;
/* Create a windowed mode window and its OpenGL context */
window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Hello World", NULL, NULL);
if (!window)
{
glfwTerminate();
return -1;
}
/* Make the window's context current */
glfwMakeContextCurrent(window);
/* Set callback functions */
glfwSetKeyCallback(window, key_callback);
/* Initialize GLEW */
if (glewInit() != GLEW_OK)
{
glfwTerminate();
return -1;
}
Initialize();
/* Loop until the user closes the window */
while (!glfwWindowShouldClose(window))
{
/* Render here */
Render();
/* Swap front and back buffers */
glfwSwapBuffers(window);
/* Poll for and process events */
glfwPollEvents();
}
CleanUp();
glfwTerminate();
return 0;
}
void Initialize()
{
// settings:
glClearColor(0.3f, 0.4f, 0.8f, 0); // change background to skyblue
glViewport(0, 0, framebufferwidth, framebufferheight); // define the draw area
// create all objects
program = glCreateProgram();
vertexshader = glCreateShader(GL_VERTEX_SHADER);
fragmentshader = glCreateShader(GL_FRAGMENT_SHADER);
glGenVertexArrays(1, &vertexarray);
glGenBuffers(1, &vertexbuffer);
glGenFramebuffers(1, &framebuffer);
glGenRenderbuffers(1, &renderbuffer0);
glGenRenderbuffers(1, &renderbuffer1);
glGenRenderbuffers(1, &renderbuffer2);
// shader source code
string vertexshader_source = {
"#version 450 core\n"
"in layout (location = 0) vec3 in_position;"
"in layout (location = 1) vec4 in_color;"
"out vec4 color;"
"void main() {"
"gl_Position = vec4(in_position, 1);"
"color = in_color;"
"}"
};
string fragmentshader_source = {
"#version 450 core\n"
"in vec4 color;"
"out layout (location = 0) vec4 out_color0;"
"out layout (location = 1) vec4 out_color1;"
"out layout (location = 2) vec4 out_color2;"
"void main() {"
"out_color0 = color;" // color
"out_color1 = vec4(1) - color;" // inverse color
"out_color2 = vec4(1, 1, 0, 1);" // yellow
"}"
};
// compile shaders and link program
CompileShader(vertexshader, vertexshader_source);
CompileShader(fragmentshader, fragmentshader_source);
LinkProgram(program, { vertexshader, fragmentshader });
// setup vertexarray
glBindVertexArray(vertexarray);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(Vertex), (void*)(0)); // in_position
glVertexAttribPointer(1, 4, GL_FLOAT, false, sizeof(Vertex), (void*)(sizeof(vec3))); // in_color
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindVertexArray(0);
// setup vertexbuffer (triangle)
vector<Vertex> vertices = {
{ { 0, 0, 0 },{ 1, 0, 0, 1 } },
{ { 1, 0, 0 },{ 0, 1, 0, 1 } },
{ { 0, 1, 0 },{ 0, 0, 1, 1 } },
};
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * vertices.size(), vertices.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// setup framebuffer
// allocate memory for renderbuffer 0, 1, and 2
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer0);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, framebufferwidth, framebufferheight);
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer1);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, framebufferwidth, framebufferheight);
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer2);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, framebufferwidth, framebufferheight);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
// attach renderbuffers to framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, renderbuffer1);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_RENDERBUFFER, renderbuffer2);
// specify the attachments in which to draw:
vector<GLenum> drawbuffers = { // array index || fragmentshader output
//-------------------------------------------------------------------------------------------------------------
GL_COLOR_ATTACHMENT0, // array index = 0: || "out layout (location = 0) ..."
GL_COLOR_ATTACHMENT1, // array index = 1: || "out layout (location = 1) ..."
GL_COLOR_ATTACHMENT2, // array index = 2: || "out layout (location = 2) ..."
};
glDrawBuffers(drawbuffers.size(), drawbuffers.data());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
CheckFramebuffer(framebuffer, GL_FRAMEBUFFER);
}
void Render()
{
// bind our framebuffer to render everything into it
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
// draw triangle
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(program);
glBindVertexArray(vertexarray);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glUseProgram(0);
// copy off-screen framebuffer content into the screen framebuffer (also called "default framebuffer")
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // write into default framebuffer
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer); // read from off-screen framebuffer
// specify the attachment from which to read:
glReadBuffer(currennt_readbuffer);
// copy:
glBlitFramebuffer(
0, 0, framebufferwidth, framebufferheight, // source area: we rendered into 50 x 50
0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, // destination area: full window size
GL_COLOR_BUFFER_BIT, // buffer bitfield: copy the color only (from location "GL_COLOR_ATTACHMENT0")
GL_NEAREST); // filtering parameter: see Tutorial 9.2 Texture Parameters
CheckForGLError();
}
void CleanUp()
{
// destroy all objects
glDeleteProgram(program);
glDeleteShader(vertexshader);
glDeleteShader(fragmentshader);
glDeleteVertexArrays(1, &vertexarray);
glDeleteBuffers(1, &vertexbuffer);
glDeleteFramebuffers(1, &framebuffer);
glDeleteRenderbuffers(1, &renderbuffer0);
glDeleteRenderbuffers(1, &renderbuffer1);
glDeleteRenderbuffers(1, &renderbuffer2);
}
void CheckFramebuffer(GLuint framebuffer, GLenum target)
{
GLenum status = glCheckNamedFramebufferStatus(framebuffer, target);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
cout << "OpenGL Framebuffer Error: \t";
if (status == GL_FRAMEBUFFER_UNDEFINED)
cout << "undefined framebuffer";
if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)
cout << "a necessary attachment is uninitialized";
if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)
cout << "no attachments";
if (status == GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER)
cout << "incomplete draw buffer";
if (status == GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER)
cout << "incomplete read buffer";
if (status == GL_FRAMEBUFFER_UNSUPPORTED)
cout << "combination of attachments is not supported";
if (status == GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE)
cout << "number if samples for all attachments does not match";
cout << (char)7 << endl; /*play sound*/
cin.get();
}
}
void CheckForGLError()
{
for (GLenum error; (error = glGetError()) != GL_NO_ERROR;)
{
cout << "OpenGL Error: \t";
if (error == GL_INVALID_ENUM)
cout << "GL_INVALID_ENUM";
if (error == GL_INVALID_VALUE)
cout << "GL_INVALID_VALUE";
if (error == GL_INVALID_OPERATION)
cout << "GL_INVALID_OPERATION";
if (error == GL_STACK_OVERFLOW)
cout << "GL_STACK_OVERFLOW";
if (error == GL_STACK_UNDERFLOW)
cout << "GL_STACK_UNDERFLOW";
if (error == GL_OUT_OF_MEMORY)
cout << "GL_OUT_OF_MEMORY";
if (error == GL_INVALID_FRAMEBUFFER_OPERATION)
cout << "GL_INVALID_FRAMEBUFFER_OPERATION";
if (error == GL_CONTEXT_LOST)
cout << "GL_CONTEXT_LOST";
cout << (char)7 << endl; /*play sound*/
cin.get();
}
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (action == GLFW_PRESS)
{
if (key == GLFW_KEY_0)
currennt_readbuffer = GL_COLOR_ATTACHMENT0;
if (key == GLFW_KEY_1)
currennt_readbuffer = GL_COLOR_ATTACHMENT1;
if (key == GLFW_KEY_2)
currennt_readbuffer = GL_COLOR_ATTACHMENT2;
}
}
Shader.h
#pragma once
#include <string>
#include <list>
// shader functions
std::string LoadTextFile(const std::string& filepath);
std::string ShaderTypeName(unsigned int shader);
bool CompileShader(unsigned int shader, const std::string& sourcecode);
std::string ShaderInfoLog(unsigned int shader);
bool LinkProgram(unsigned int program, const std::list<unsigned int>& shaderlist);
std::string ProgramInfoLog(unsigned int program);
Shader.cpp
#include "Shader.h"
#include <iostream>
#include <fstream>
#include <GL/glew.h>
std::string LoadTextFile(const std::string & filepath)
{
std::string result(""), line;
std::fstream f(filepath, std::ios::in);
while (f.good())
{
std::getline(f, line);
result += line + '\n';
}
return result;
}
std::string ShaderTypeName(unsigned int shader)
{
if (glIsShader(shader))
{
int type;
glGetShaderiv(shader, GL_SHADER_TYPE, &type);
if (type == GL_VERTEX_SHADER)
return "Vertex Shader";
if (type == GL_TESS_CONTROL_SHADER)
return "Tessellation Control Shader";
if (type == GL_TESS_EVALUATION_SHADER)
return "Tessellation Evaluation Shader";
if (type == GL_GEOMETRY_SHADER)
return "Geometry Shader";
if (type == GL_FRAGMENT_SHADER)
return "Fragment Shader";
if (type == GL_COMPUTE_SHADER)
return "Compute Shader";
}
return "invalid shader";
}
bool CompileShader(unsigned int shader, const std::string& sourcecode)
{
if (!glIsShader(shader))
{
std::cout << "ERROR: shader compilation failed, no valid shader specified" << std::endl;
return false;
}
if (sourcecode.empty())
{
std::cout << "ERROR: shader compilation failed, no source code specified (" << ShaderTypeName(shader) << ")" << std::endl;
return false;
}
// array of source code components
const char* sourcearray[] = { sourcecode.c_str() };
// set source code
glShaderSource(shader, 1, sourcearray, NULL);
// compile shaders
glCompileShader(shader);
// check compile status
int status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
// successfully compiled shader
if (status == GL_TRUE)
return true;
// show compile errors
std::cout << "ERROR: shader compilation failed (" << ShaderTypeName(shader) << ")" << std::endl << ShaderInfoLog(shader) << std::endl;
return false;
}
std::string ShaderInfoLog(unsigned int shader)
{
if (glIsShader(shader))
{
int logsize;
char infolog[1024] = { 0 };
glGetShaderInfoLog(shader, 1024, &logsize, infolog);
return std::string(infolog);
}
return "invalid shader";
}
bool LinkProgram(unsigned int program, const std::list<unsigned int>& shaderlist)
{
if (!glIsProgram(program))
{
std::cout << "ERROR: shader linking failed, no valid program specified" << std::endl;
return false;
}
// attach all shaders to the program
for (auto& shader : shaderlist)
{
if (glIsShader(shader))
glAttachShader(program, shader);
}
// link program
glLinkProgram(program);
// detach all shaders again
for (auto& shader : shaderlist)
{
if (glIsShader(shader))
glDetachShader(program, shader);
}
int status;
glGetProgramiv(program, GL_LINK_STATUS, &status);
// successfully linked program
if (status == GL_TRUE)
return true;
// show link errors
std::cout << "ERROR: shader linking failed" << std::endl << ProgramInfoLog(program) << std::endl;
return false;
}
std::string ProgramInfoLog(unsigned int program)
{
if (glIsProgram(program))
{
int logsize;
char infolog[1024] = { 0 };
glGetProgramInfoLog(program, 1024, &logsize, infolog);
return std::string(infolog);
}
return "invalid program";
}