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";

}