4.1 Layered Rendering (Cubemap)
A Geometry Shader is needed to render into different layers of a texture.
In case of a cubemap, there are 6 different layers available to render into.
The built-in variable "int gl_Layer" determines the layer in which the primitive gets rendered into.
This example has 2 program object: 1 to render into the cubemap, that contains also a geometry shader, and 1 to just show a textured cube.
A cubemap consists of 6 faces, each of them with the same size. Before rendering into these faces, the viewport has to resized to the texture's size by calling:
glViewport(0, 0, texture_size.x, texture_size.y);
When finished rendering into the cubemap texture, we have to resore the viewport's original size (which is the Default Framebuffer's size: here 800 x 600):
glViewport(0, 0, 800, 600);
To distinguish the cubemap's "background color" from the rest of the screen, this example uses 2 different "clear color" values, grey (for cubemap background) and black (as screen background).
Complete Source Code:
Main.cpp
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/transform.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtx/rotate_vector.hpp>
#include <iostream>
#include <vector>
#include <string>
#include "Shader.h"
using namespace std;
using namespace glm;
// main functions
bool Initialize();
void Render();
void CleanUp();
void CheckForGLError();
void CheckFramebuffer(GLuint framebuffer, GLenum target);
// variables
GLuint program_show = 0;
GLuint vertexshader_show = 0;
GLuint fragmentshader_show = 0;
GLuint program_render = 0;
GLuint vertexshader_render = 0;
GLuint geometryshader_render = 0;
GLuint fragmentshader_render = 0;
GLuint vertexarray = 0;
GLuint vertexbuffer = 0;
GLuint framebuffer = 0;
GLuint texture = 0;
uvec2 texture_size{1024, 1024};
struct Vertex
{
vec3 Position;
};
int main(int argc, char* argv[])
{
/* Initialize GLFW */
if (!glfwInit())
return 1;
/* Create a windowed mode window and its OpenGL context */
GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL", NULL, NULL);
if (!window)
{
glfwTerminate();
return 1;
}
/* Make the window's context current */
glfwMakeContextCurrent(window);
/* Initialize GLEW */
if (glewInit() != GLEW_OK)
{
glfwTerminate();
return 2;
}
/* Initialize resources for this application */
if (!Initialize())
{
CleanUp();
return 3;
}
/* 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();
}
/* Release resources */
CleanUp();
glfwTerminate();
return 0;
}
bool Initialize()
{
/* settings */
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
/* create all objects */
program_show = glCreateProgram();
vertexshader_show = glCreateShader(GL_VERTEX_SHADER);
fragmentshader_show = glCreateShader(GL_FRAGMENT_SHADER);
program_render = glCreateProgram();
vertexshader_render = glCreateShader(GL_VERTEX_SHADER);
geometryshader_render = glCreateShader(GL_GEOMETRY_SHADER);
fragmentshader_render = glCreateShader(GL_FRAGMENT_SHADER);
glGenVertexArrays(1, &vertexarray);
glGenBuffers(1, &vertexbuffer);
glGenFramebuffers(1, &framebuffer);
glGenTextures(1, &texture);
/* shader source code: for program that shows the cubemap */
string vertexshader_show_source = {
"#version 450 core\n"
"layout (location = 0) in vec3 in_position;"
"layout (location = 0) uniform mat4 MVP = mat4(1);"
"out VS_FS { smooth vec3 cubemap_texcoord; } vs_out;"
"void main() {"
"gl_Position = MVP * vec4(in_position, 1);"
"vs_out.cubemap_texcoord = in_position;"
"}"
};
string fragmentshader_show_source = {
"#version 450 core\n"
"in VS_FS { smooth vec3 cubemap_texcoord; } fs_in;"
"layout (binding = 2) uniform samplerCube tex1;"
"layout (location = 0) out vec4 out_color;"
"void main() {"
"out_color = texture(tex1, fs_in.cubemap_texcoord);"
"}"
};
/* setup program to show cubemap */
CompileShader(vertexshader_show, vertexshader_show_source);
CompileShader(fragmentshader_show, fragmentshader_show_source);
LinkProgram(program_show, { vertexshader_show, fragmentshader_show });
/* shader source code: for program that renders to the cubemap */
string vertexshader_render_source = {
"#version 450 core\n"
"layout (location = 0) in vec3 in_position;"
"out VS_GS { vec3 position; } vs_out;"
"void main() {"
"vs_out.position = in_position;"
"}"
};
string geometryshader_render_source = {
"#version 450 core\n"
"layout (triangles) in;"
"layout (triangle_strip, max_vertices = 3 * 6) out;"
"in VS_GS { vec3 position; } gs_in[];"
"out GS_FS { vec4 color; } gs_out;"
/* example: give each face a different color */
"const vec4 mycolorarray[6] = {"
"vec4(1, 0, 0, 1), vec4(0, 1, 1, 1),"
"vec4(0, 1, 0, 1), vec4(1, 0, 1, 1),"
"vec4(0, 0, 1, 1), vec4(1, 1, 0, 1) };"
"void main() {"
"for (int face = 0; face < 6; face++) {"
/* set layer that gets this primitive: */
" gl_Layer = face;"
/* pass the triangle trough to each layer with different colors */
" for (int vertex = 0; vertex < 3; vertex++) {"
" gl_Position = vec4(gs_in[vertex].position, 1);"
" gs_out.color = mycolorarray[face];"
" EmitVertex();"
"} EndPrimitive(); } }"
};
string fragmentshader_render_source = {
"#version 450 core\n"
"in GS_FS { vec4 color; } fs_in;"
"layout (location = 0) out vec4 out_color;"
"void main() {"
"out_color = fs_in.color;"
"}"
};
/* setup program to render to cubemap */
CompileShader(vertexshader_render, vertexshader_render_source);
CompileShader(geometryshader_render, geometryshader_render_source);
CompileShader(fragmentshader_render, fragmentshader_render_source);
LinkProgram(program_render, { vertexshader_render, geometryshader_render, fragmentshader_render });
/* setup vertexarray */
glBindVertexArray(vertexarray);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(Vertex), reinterpret_cast<GLvoid*>(offsetof(Vertex, Position)));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
/* setup vertexbuffer (cube from outside) */
vector<Vertex> vertices = {
{ { +1, -1, -1 } }, { { +1, +1, -1 } }, { { +1, +1, +1 } }, { { +1, -1, +1 } }, // +x
{ { -1, +1, -1 } }, { { -1, -1, -1 } }, { { -1, -1, +1 } }, { { -1, +1, +1 } }, // -x
{ { +1, +1, -1 } }, { { -1, +1, -1 } }, { { -1, +1, +1 } }, { { +1, +1, +1 } }, // +y
{ { -1, -1, -1 } }, { { +1, -1, -1 } }, { { +1, -1, +1 } }, { { -1, -1, +1 } }, // -y
{ { -1, -1, +1 } }, { { +1, -1, +1 } }, { { +1, +1, +1 } }, { { -1, +1, +1 } }, // +z
{ { +1, -1, -1 } }, { { -1, -1, -1 } }, { { -1, +1, -1 } }, { { +1, +1, -1 } }, // -z
/* triangle */
{ { 0, 0, 0 } }, { { 1, 0, 0 } }, { { 0, 1, 0 } },
};
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * vertices.size(), vertices.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
/* setup texture (cubemap) */
glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
glTexStorage2D(GL_TEXTURE_CUBE_MAP, 1, GL_RGBA8, texture_size.x, texture_size.y);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
/* bind texture to unit 2 (the same unit used in the fragmentshader to show the cubemap) */
glBindTextureUnit(2, texture);
/* setup framebuffer with cubemap attached */
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
CheckFramebuffer(framebuffer, GL_FRAMEBUFFER);
return true;
}
void Render()
{
// set camera
static float angle = 0;
angle += 0.016f;
mat4 MVP =
perspective(radians(45.0f), 1.33f, 0.1f, 100.0f) *
lookAt(vec3(5 * cos(angle), 2 * sin(angle * 0.5f), 5 * sin(angle)), vec3(0), vec3(0, 1, 0));
glProgramUniformMatrix4fv(program_show, 0, 1, false, value_ptr(MVP));
/* first draw 1 triangle into the cubemap layers */
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glViewport(0, 0, texture_size.x, texture_size.y);
glClearColor(0.5, 0.5, 0.5, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(program_render);
glBindVertexArray(vertexarray);
glDrawArrays(GL_TRIANGLES, 24, 3); /* triangle begins after the cube with 24 vertices */
glBindVertexArray(0);
glUseProgram(0);
/* then show the cubemap */
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, 800, 600);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(program_show);
glBindVertexArray(vertexarray);
glDrawArrays(GL_QUADS, 0, 24);
glBindVertexArray(0);
glUseProgram(0);
CheckForGLError();
}
void CleanUp()
{
// destroy all objects
glDeleteProgram(program_show);
glDeleteShader(vertexshader_show);
glDeleteShader(fragmentshader_show);
glDeleteProgram(program_render);
glDeleteShader(vertexshader_render);
glDeleteShader(geometryshader_render);
glDeleteShader(fragmentshader_render);
glDeleteVertexArrays(1, &vertexarray);
glDeleteBuffers(1, &vertexbuffer);
glDeleteFramebuffers(1, &framebuffer);
glDeleteTextures(1, &texture);
}
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 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();
}
}
Shader.h
#ifndef SHADER_H
#define SHADER_H
#include <GL/glew.h>
#include <string>
#include <list>
std::string LoadTextFile(const std::string& filepath);
std::string ShaderTypeName(GLuint shader);
bool CompileShader(GLuint shader, const std::string& sourcecode);
std::string ShaderInfoLog(GLuint shader);
bool LinkProgram(GLuint program, const std::list<GLuint>& shaderlist);
std::string ProgramInfoLog(GLuint program);
#endif // SHADER_H
Shader.cpp
#include "Shader.h"
#include <iostream>
#include <fstream>
std::string LoadTextFile(const std::string & filepath)
{
std::string result(""), line;
std::fstream f(filepath.c_str(), std::ios::in);
while (f.good())
{
std::getline(f, line);
result += line + '\n';
}
return result;
}
std::string ShaderTypeName(GLuint shader)
{
if (glIsShader(shader))
{
GLint type = 0;
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(GLuint 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;
}
const char* sourcearray[] = { sourcecode.c_str() };
glShaderSource(shader, 1, sourcearray, NULL);
glCompileShader(shader);
// check compile status
GLint status = 0;
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(GLuint shader)
{
if (glIsShader(shader))
{
GLint logsize = 0;
GLchar infolog[1024] = { 0 };
glGetShaderInfoLog(shader, 1024, &logsize, infolog);
return std::string(infolog);
}
return "invalid shader";
}
bool LinkProgram(GLuint program, const std::list<GLuint>& 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 (std::list<GLuint>::const_iterator it = shaderlist.begin(); it != shaderlist.end(); it++)
{
if (glIsShader(*it))
glAttachShader(program, *it);
}
// link program
glLinkProgram(program);
// detach all shaders again
for (std::list<GLuint>::const_iterator it = shaderlist.begin(); it != shaderlist.end(); it++)
{
if (glIsShader(*it))
glDetachShader(program, *it);
}
GLint status = 0;
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(GLuint program)
{
if (glIsProgram(program))
{
GLint logsize = 0;
GLchar infolog[1024] = { 0 };
glGetProgramInfoLog(program, 1024, &logsize, infolog);
return std::string(infolog);
}
return "invalid program";
}