$\require{color}$
glCopyTexImage2D
copies framebuffer data to texture objects.
internalFormat
refers to either the colorbuffer (GL_RGBA
) or the depth buffer (GL_DEPTH_COMPONENT
).
glCopyTexImage2D(GL_TEXTURE_2D, 0, internalFormat, 0, 0, viewportWidth, viewportHeight, 0);
Limitations:
Texture internal formats:
Rendering a 3D scene multiple times to collect different intermediate image data, and then combining the images to synthesise final frame.
Limitation:
Allows creation of non-displayable framebuffers. OpenGL can redirect output to FBO, each of which contains a collection of rendering destinations.
Many color attachment points allow multiple render targets (MRT) that allow color to be rendered to multiple destinations at once.
// Generate and bind framebuffer
glGenFramebuffers(1, &fboHandle);
glBindFramebuffer(GL_FRAMEBUFFER, fboHandle);
// Create first texture object
GLuint renderTexA;
glGenTextures(1, &renderTexA);
glActiveTexture(GL_TEXTURE0);
// Use the texture unit 0
glBindTexture(GL_TEXTURE_2D, renderTexA);
// Bind the texture to 2D mode
glTexImage2D(...);
// define texture images that can be used by the shader
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, renderTexA, 0);
// Create second texture object
// ...
glBindTexture(GL_TEXTURE_2D, renderTexB);
// ...
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_2D, renderTexB, 0);
// Generate and bind framebuffer, same as above
// Create depth renderbuffer
GLUint depthBuf;
glGenRenderbuffers(1, &depthBuf);
glBindRenderbuffer(GL_RENDERBUFFER, depthBuf);
glRenderbufferStorage(GL_RENDERBUFFER, GL__DEPTH_COMPONENT, fboWidth, fboHeight);
// Bind the depth buffer to FBO
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBuf);
// Set the target for the fragment shader outputs
GLenum drawBufs[] = { GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 };
glDrawBuffers(2, drawBufs);
// Default framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
Note that we have to set the targets in drawBufs
for fragment shaders’ outputs to have the
layouts here in the Fragment Shader to work.
glBindFramebuffer(GL_FRAMEBUFFER, fboHandle);
glViewport(0, 0, fboWidth, fboHeight);
glClear(GL_DEPTH_BUFFER_BIT);
// clear the color buffers
const GLfloat lightGreen[4] = {0.5f, 1.0f, 0.5f, 1.0f};
const GLfloat lightRed[4] = {1.0f, 0.5f, 0.5f, 1.0f};
glClearBufferfv(GL_COLOR, 0, lightGreen);
glClearBufferfv(GL_COLOR, 1, lightRed);
// Setup the projection matrix and view matrix, then render texture
renderTextureScene(); // 1st pass
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, winWidth, winHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Projection View matrix
renderScene();
One for rendering to textures, the other for rendering to the scene.
// Fragment shader for render to textures
layout (location = 0) out vec4 FragColorA; // Attachment point 2
layout (location = 1) out vec4 FragColorB; // Attachment point 3
void main() { FragColorA = ..., FragColorB = ... }
// Fragment shader for final render
uniform sampler2D texMapA; // Texture Unit 0
uniform sampler2D texMapB; // Texture Unit 1
How to do it is rasterization? (Without raytracing/radiosity): No direct method in polygon based graphics renderers.
Only generates the shadow shapes, but the color of the shadows itself is highly inaccurate.
Two main methods: Shadow volume and shadow mapping,
An occluder casts a shadow volume.
A ray enters (+1) and exits (-1) the shadow volume. If any point on the ray is $> 0$, then it is in one or more shadow volumes.
Find silhouette edges
Render scene as if completely in shadow (i.e. ambient lighting only).
Disable writing to depth and color buffers
There are front facing and back facing shadow volume polygons,
On depth pass i.e. occlusion of polygons between viewpoint and shadow applied
Set depth comparison mode to GL_LEQUAL
(Depth pass method) If viewpoint is in shadow volume or too close to shadow volume, the near plane clips out the front facing polygons. Results in negative values in the depth buffer, undefined behavior and may result in “holes” in the image.
Fixed with depth fail method, which traces from the reverse direction instead (polygon to viewpoint).
Advantages of depth pass:
Disadvantages of depth pass:
Advantages of shadow volume:
Disadvantages of shadow volume:
Uses texture mapping.
First remember all surfaces visible from light source (equates to all lit surfaces).
Save depth buffer from light source (no shadows, all depth buffer channels are the closest value).
Then on rendering from another view point, compare the depth buffer channel to the saved LightSourceDepthBuffer at position A.
Then map the 3D location of the fragment to the original lightsource view. If the depth values are the same, then the fragment is visible from the lightsource.
Brighter in shadow map corresponds to deeper (greater) depth value.
First pass: Saving the shadow map
GLfloat border[] = {1.0f, 0.0f, 0.0f, 0.0f};
// Usual setup of textures
glActiveTexture(GL_TEXTURE0);
GLuint depthTex;
glGenTextures(1, &depthTex);
glBindTextures(GL_TEXTURE_2D, depthTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, shadowMapWidth,
shadowMapHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(...);
/* set Mag and Min filter to GL_NEAREST and not interpolated.
* set Wrap mode for S and T coordinates to Clamp to Border
* set border color to border. This is to uniformly make the depth
* value 1.0 outside of the view frustum (FOV of light source).
*/
glTexParameteri(GL_TEXTURE2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glTexParameteri(GL_TEXTURE2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
Advantages:
Disadvantages:
...
out vec3 ecPosition;
out vec3 ecNormal;
out vec4 ShadowCoord;
uniform mat4 ModelViewMatrix;
uniform mat4 NormalMatrix;
uniform mat4 MVP;
uniform mat4 ShadowMatrix; // B * Pl * Vl
void main() {
...
ShadowCoord = ShadowMatrix * vPosition;
...
}
in vec3 ecPosition;
in vec3 ecNormal;
in vec4 ShadowCoord;
uniform sampler2DShadow ShadowMap;
uniform int RenderPass;
layout (location = 0) out vec4 FragColor;
void recordDepth() {
// No need to do anything! Depth buffer automatically saved by OpenGL.
}
void shadeWithShadow() {
vec3 ambient = ...;
vec3 diffuseSpec = ...;
/* Lookup shadow-map. textureProj does perspective division.
* i.e. textureProject(texture, vec4(x, y, z, w)) converts to
* (x/w, y/w, z/w, 1) = (s, t, p, 1) which is used to lookup texture.
*/
}
void main() {
if (RenderPass == 0) { // shadow mapping pass
recordDepth();
} else { // actual render pass
shadeWithShadow();
}
}
Self shadowing values at unshadowed surfaces causes shadow acne.
ShadowCoord.z
Blockiness (Aliasing) as depth buffers only store discrete samples of the scene’s depth,
and textureProj
only returns a binary value.
Even more noticeable when duelling frusta: Camera is close to an object (more fragments to compare) but light is far from it (less samples).
Average depth comparison in a neighbourhood of the shadow map can smoothen out edges.
/* everything same as above until TexParameters.
* Instead of using GL_NEAREST as the texture filtering option, use GL_LINEAR.
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
void shadeWithShadow() {
// ...
float sum = 0.0f;
sum += textureProjOffset(ShadowMap, ShadowCoord, ivec2(-1, -1));
sum += textureProjOffset(ShadowMap, ShadowCoord, ivec2(-1, 1));
sum += textureProjOffset(ShadowMap, ShadowCoord, ivec2(1, 1));
sum += textureProjOffset(ShadowMap, ShadowCoord, ivec2(1, -1));
float shadow = sum * 0.25; // averaging
// ...
}