OpenGL Programming/Advanced/Shadows
Drawing shadows in OpenGL can be a challenging task, but there are several techniques that can be used to achieve convincing results. Here are a few common techniques for drawing shadows in OpenGL:
- Shadow mapping: Shadow mapping is a popular technique for rendering shadows in real-time 3D graphics. It involves rendering the scene from the perspective of the light source and storing the depth values in a texture. The texture is then used to determine whether a pixel is in shadow or not when the scene is rendered from the camera's perspective.
- Shadow volumes: Shadow volumes are another technique for rendering shadows that works by creating a volume that surrounds an object and intersects with other objects in the scene. This volume is then used to determine whether a pixel is in shadow or not.
- Shadow masks: Shadow masks are a technique for rendering soft shadows in OpenGL. They work by rendering a black and white mask that represents the shape of the shadow, and then using this mask to modulate the brightness of the objects in the scene.
- Projected textures: Projected textures can be used to simulate the appearance of shadows by projecting a texture that represents the shadow onto the objects in the scene. This technique can be used to create soft shadows or to simulate the appearance of shadows cast by objects that are not in the scene.
Each of these techniques has its own strengths and weaknesses, and the best approach will depend on the specific requirements of your project. By experimenting with these techniques and adapting them to your needs, you can create convincing and visually appealing shadows in your OpenGL applications.
Here's an example of how to implement shadow mapping in OpenGL using shaders and framebuffers:
First, set up the framebuffers and depth texture:
GLuint fbo, depthTex;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glGenTextures(1, &depthTex);
glBindTexture(GL_TEXTURE_2D, depthTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTex, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
Next, create the shadow map shader program:
GLuint shadowProgram = createShaderProgram("shadow.vert", "shadow.frag");
GLint shadowViewLoc = glGetUniformLocation(shadowProgram, "view");
GLint shadowProjLoc = glGetUniformLocation(shadowProgram, "proj");
GLint shadowModelLoc = glGetUniformLocation(shadowProgram, "model");
Then, render the scene from the light's perspective to the depth texture:
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glViewport(0, 0, width, height);
glClear(GL_DEPTH_BUFFER_BIT);
glUseProgram(shadowProgram);
glUniformMatrix4fv(shadowViewLoc, 1, GL_FALSE, glm::value_ptr(lightView));
glUniformMatrix4fv(shadowProjLoc, 1, GL_FALSE, glm::value_ptr(lightProj));
for (auto& object : objects) {
glUniformMatrix4fv(shadowModelLoc, 1, GL_FALSE, glm::value_ptr(object.model));
object.mesh->render();
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
Finally, render the scene from the camera's perspective, using the depth texture to determine whether a pixel is in shadow:
glViewport(0, 0, width, height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(program);
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(proj));
glUniformMatrix4fv(lightViewLoc, 1, GL_FALSE, glm::value_ptr(lightView));
glUniformMatrix4fv(lightProjLoc, 1, GL_FALSE, glm::value_ptr(lightProj));
glUniform1i(shadowMapLoc, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, depthTex);
for (auto& object : objects) {
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(object.model));
object.mesh->render();
}
This is just a basic example, and there are many ways to optimize and improve this code. However, it should give you an idea of how to implement shadow mapping in OpenGL using shaders and framebuffers.