Now we will see how to use dynamic cube mapping to render a real-time scene to a cubemap render target. This allows us to create reflective surfaces. In modern OpenGL, offscreen rendering (also called render-to-texture) functionality is exposed through FBOs.
In this recipe, we will render a box with encircling particles. The code is contained in the Chapter3/DynamicCubemap
directory.
Let us get started with the recipe as follows:
glGenTextures(1, &dynamicCubeMapID); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_CUBE_MAP, dynamicCubeMapID); glTexParameterf(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); for (int face = 0; face < 6; face++) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGBA,CUBEMAP_SIZE, CUBEMAP_SIZE, 0, GL_RGBA, GL_FLOAT, NULL); }
glGenFramebuffers(1, &fboID); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboID); glGenRenderbuffers(1, &rboID); glBindRenderbuffer(GL_RENDERBUFFER, rboID); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, CUBEMAP_SIZE, CUBEMAP_SIZE); glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fboID); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X, dynamicCubeMapID, 0); GLenum status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); if(status != GL_FRAMEBUFFER_COMPLETE) { cerr<<"Frame buffer object setup error."<<endl; exit(EXIT_FAILURE); } else { cerr<<"FBO setup successfully."<<endl; }
glViewport(0,0,CUBEMAP_SIZE,CUBEMAP_SIZE); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboID); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,GL_TEXTURE_CUBE_MAP_POSITIVE_X, dynamicCubeMapID, 0); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glm::mat4 MV1 = glm::lookAt(glm::vec3(0),glm::vec3(1,0,0),glm::vec3(0,-1,0)); DrawScene( MV1*T, Pcubemap); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, dynamicCubeMapID, 0); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glm::mat4 MV2 = glm::lookAt(glm::vec3(0),glm::vec3(-1,0,0), glm::vec3(0,-1,0)); DrawScene( MV2*T, Pcubemap); ...//similar for rest of the faces glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glViewport(0,0,WIDTH,HEIGHT); DrawScene(MV, P);
glBindVertexArray(sphereVAOID); cubemapShader.Use(); T = glm::translate(glm::mat4(1), p); glUniformMatrix4fv(cubemapShader("MVP"), 1, GL_FALSE, glm::value_ptr(P*(MV*T))); glUniform3fv(cubemapShader("eyePosition"), 1, glm::value_ptr(eyePos)); glDrawElements(GL_TRIANGLES,indices.size(),GL_UNSIGNED_SHORT,0); cubemapShader.UnUse();
Dynamic cube mapping renders the scene six times from the reflective object using six cameras at the reflective object's position. For rendering to the cubemap texture, an FBO is used with a cubemap texture attachment. The cubemap texture's GL_TEXTURE_CUBE_MAP_POSITIVE_X
target is bound to the GL_COLOR_ATTACHMENT0
color attachment of the FBO. The last parameter of glTexImage2D
is NULL
since this call just allocates the memory for offscreen rendering and the real data will be populated when the FBO is set as the render target.
The scene is then rendered to the cubemap texture without the reflective object by placing six cameras at the reflective object's position in the six directions. The cubemap projection matrix (Pcubemap
) is given a 90 degree fov.
Pcubemap = glm::perspective(90.0f,1.0f,0.1f,1000.0f);
This renders the scene into the cubemap texture. For each side, a new MVP
matrix is obtained by multiplying the new MV
matrix (obtained by using glm::lookAt
function). This is repeated for all six sides of the cube. Next, the scene is rendered normally and the reflective object is finally rendered using the generated cubemap to render the reflective environment. Rendering each frame six times into an offscreen target hinders performance, especially if there are complex objects in the world. Therefore this technique should be used with caution.
The cubemap vertex shader outputs the object space vertex positions and normals.
#version 330 core layout(location=0) in vec3 vVertex; layout(location=1) in vec3 vNormal; uniform mat4 MVP; smooth out vec3 position; smooth out vec3 normal; void main() { position = vVertex; normal = vNormal; gl_Position = MVP*vec4(vVertex,1); }
The cubemap fragment shader uses the object space vertex positions to determine the view vector. The reflection vector is then obtained by reflecting the view vector at the object space normal.
#version 330 core layout(location=0) out vec4 vFragColor; uniform samplerCube cubeMap; smooth in vec3 position; smooth in vec3 normal; uniform vec3 eyePosition; void main() { vec3 N = normalize(normal); vec3 V = normalize(position-eyePosition); vFragColor = texture(cubeMap, reflect(V,N)); }
The demo application implementing this recipe renders a reflective sphere with eight cubes pulsating around it, as shown in the following figure:
In this recipe, we could also use layered rendering by using the geometry shader to output to a different Framebuffer
object layer. This can be achieved by outputting to the appropriate gl_Layer
attribute from the geometry shader and setting the appropriate viewing transformation. This is left as an exercise for the reader.
3.135.207.129