OpenGL tutorial (23) Shadow Mapping (1)

Source: Internet
Author: User

Original post address: http://ogldev.atspace.co.uk/www/tutorial23/tutorial23.html

When light is projected onto an object, a shadow is generated on an object such as the ground or wall. In computer graphics, there are many technologies that can produce shadows. In this tutorial, we will learn the most common shadow mapping technology.

The problem of shadow in OpenGL can be summarized as: how to determine whether a pixel is in the shadow area. Simply put, we can connect the position of a pixel to the position of the light source (as shown in). If the link is connected to an object (assuming that the object is not transparent), the pixel may be in the shadow, otherwise, it is not in the shadow. For example, if a pixel and the object are handed over to a point C, it should be in the shadow, and B pixel and the object have no intersection, so they are not in the shadow. We can place the camera in the position of the light source, and the point depth test in the shadow area will fail, but not in the shadow area. Shadow ing is based on this idea.

We have concluded that the deep test can help us determine whether a pixel is in the shadow area, but the premise is that the light source and camera are in the same position. In most cases, the light source and camera are not in the same position. What should we do now?

The solution is to render the scene twice: when we make a 1st rendering, we place the camera in the position of the light source. At this time, we do not write the color buffer, but only output the depth buffer, usually output to a Texture buffer. During the second rendering, the camera is in its original position. In the second rendered sliced object shader, we will read the depth buffer (usually a texture) of the First rendering ), for each pixel, we will read the depth value from its corresponding position from the last rendered depth buffer and compare it with the current depth value of the pixel. If the two depths are the same, it indicates that the pixel is not in the shadow, and we can enter its normal color. If the depth buffer is different, it indicates that when the light source is located, other pixels cover it, the pixel should be within the shadow. At this time, we can enter a shadow color, such as gray, as the output color of the pixel.

Our scenario consists of two objects, cube and ground. The light source is located in the upper left corner and points to cube. In the first rendering, the camera is in the position of the light source, and B is rendered. Its depth value enters the depth buffer, while point A and point C are on the same line. At this time, the depth of point C is smaller, therefore, the depth of C is written into the depth buffer. In the second rendering, the camera is in its original position. For point B, the depth of the camera is the same as that of the first rendering (note that it may not be completely consistent, there is a floating point Precision problem), so it is not in the shadow, and for a point, the current depth value is different from the depth of the first rendering, so a is in the shadow.

The depth graph generated during the first rendering is called shadow map. For the shadow map-based shadow algorithm, we will study it in two tutorials. In this tutorial, we only learn how to generate a shadow map, that is, output the first rendered depth map to a texture through the texture ing technology. Finally, we will display the generated shadow map on the screen, this is also a good debugging method. We can see whether the shadow map is correct. Sometimes the shadow map is incorrect because the shadow map is incorrect.

The source code contains a simple quadrilateral, which is used to display the shadow map. The quadrilateral consists of two triangles. The texture coordinates are set to (0, 0), (1, 0), (0, 1) (1, 1), and so that it overwrites the entire texture space.

Main source code:

Shadow_map_fbo.h

class ShadowMapFBO
{
    public:
        ShadowMapFBO();
        ~ShadowMapFBO();
        bool Init(unsigned int WindowWidth, unsigned int WindowHeight);
        void BindForWriting();
        void BindForReading(GLenum TextureUnit);
    private:
        GLuint m_fbo;
        GLuint m_shadowMap;
};

In openggl, the final output buffer of a 3D pipeline is called a framebuffer object or FBO. The concept of FBO involves color buffering, depth buffering, and other buffering, such as template buffering. When the function gluinitdisplaymode () is called, a default framebuffer object is created. This framebuffer object is managed by the window system. OpenGL cannot delete it except the default framebuffer, each application can also create its own FBO, which is controlled by the application and can be used to implement some special effects.

The shadowmapfbo class in this tutorial provides a convenient method for managing FBO. This class contains two OpenGL handles. The handle m_fbo represents the real FBO (the FBO output to the screen ), the handle m_shadowmap indicates the depth buffer. Note: Only the default framebuffer can be displayed on the screen. the FBO created by the application can only be used for offline rendering, for example, saving the FBO in a file.

Framebuffer itself is just a handle. We need to associate it with the texture. The data in the texture is the real content in framebuffer.

The following are some settings associated with FBO in OpenGL:

  1. Color_attachmenti: the output image of the multipart shader is placed in the texture. Suffix I means that there may be multiple textures associated with the color buffer. In the segment object shader, we can render multiple color buffers at the same time.
  2. Depth_attachment-The texture is associated with the depth buffer.
  3. Stencil_attachment-The texture is associated with the template buffer.
  4. Depth_stencil_attachment-The texture is associated with the depth template buffer.

In the Shadow Mapping shadow implementation, we only need depth buffering. m_shadowmap is the texture handle associated with the depth buffering. The shadowmapfbo class also provides some functions called in main CPP. For example, you must call bindforwriting () before rendering the shadow map and call bindforreading () before the second rendering ().

Shadow_map_fbo.cpp

glGenFramebuffers(1, &m_fbo);

We started to create FBO. The method for creating FBO is similar to the texture. First, we specify the address and size of a gluints array, which is the FBO handle in the array.

Glgentextures (1, & m_shadowmap );

glBindTexture(GL_TEXTURE_2D, m_shadowMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, WindowWidth, WindowHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

Next, we create a shadow map texture, which is a standard 2D texture.

  1. The internal format of the texture is gl_depth_component, which is different from the general texture settings. The general texture is gl_rgb, and gl_depth_component indicates that the texture data is in a single floating point format, which indicates the normalized depth value.
  2. The last parameter glteximage2d is null, which means that we do not provide any data to initialize this buffer.
  3. Gl_clamp limits texture coordinates to [0, 1.

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);

The above code associates the texture object with FBO. Gl_draw_framebuffer indicates writing framebuffer, while gl_read_framebuffer indicates reading framebuffer. In this case, glreadpixels can be used to obtain the content of framebuffer.

glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowMap, 0);

We associate the shadow map texture with the depth FBO. The last parameter is the mipmap layer. Because we didn't use the mipmap layer, it is 0 here, and the fourth parameter is the texture handle, if the value is 0, the deep FBO texture Association is canceled.

glDrawBuffer(GL_NONE);

Since the first rendering does not output color buffering, we use the gl_none parameter. By default, the color buffer target is gl_color_attachment0. Valid parameters include gl_none and gl_color_attachment0 to gl_color_attachmentm. Here, M is gl_max_color_attachments-1. Note that these parameters are only valid for FBO.

GLenum Status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (Status != GL_FRAMEBUFFER_COMPLETE) {
    printf("FB error, status: 0x%x\n", Status);
    return false;
}

After the FBO configuration is complete, we need to verify whether it is valid to ensure that the program will not go wrong.

void ShadowMapFBO::BindForWriting()
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
}

void ShadowMapFBO::BindForReading(GLenum TextureUnit)
{
    glActiveTexture(TextureUnit);
    glBindTexture(GL_TEXTURE_2D, m_shadowMap);
}

We will switch between the shadow map and the default framebuffer. The first output is shadow map and the second output is framebuffer. The above two functions are used to execute this function.

The following is the code for Vs and FS (PS) during shadow map rendering during the first rendering:

Shadowmap.

# Version 400

Layout (location = 0) in vec3 position;
Layout (location = 1) in vec2 texcoord;
Layout (location = 2) in vec3 normal;

Uniform mat4 gwvp;

Out vec2 texcoordout;

Void main ()
{
Gl_position = gwvp * vec4 (location, 1.0 );
Texcoordout = texcoord;
}

The two rendered shader files are the same. In the first rendering, because the color buffer is not allowed to be written, the multipart shader has no actual output.

Shadowmap. Ps

#version 400
in vec2 TexCoordOut;
uniform sampler2D gShadowMap;
out vec4 FragColor;
void main()
{
    float Depth = texture(gShadowMap, TexCoordOut).x;
    Depth = 1.0 - (1.0 - Depth) * 25.0;
    FragColor = vec4(Depth);
}

In the second rendering, we will execute the multipart shader and output the shadow map texture. Since gl_depth_componentu is used as the format when shadow map is created, we use the X component of the texture coordinate to sample the texture value.

Tutorial23.cpp

virtual void RenderSceneCB()
{
    m_pGameCamera->OnRender();
    m_scale += 0.05f;
    ShadowMapPass();
    RenderPass();
    glutSwapBuffers();
}

The rendering of the main function is very simple. It calls two rendering functions, rendering the shadow map first, and the second rendering outputs the shadow map to the screen.

virtual void ShadowMapPass()
{
    m_shadowMapFBO.BindForWriting();
    glClear(GL_DEPTH_BUFFER_BIT);
    Pipeline p;
    p.Scale(0.1f, 0.1f, 0.1f);
    p.Rotate(0.0f, m_scale, 0.0f);
    p.WorldPos(0.0f, 0.0f, 5.0f);
    p.SetCamera(m_spotLight.Position, m_spotLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));
    p.SetPerspectiveProj(20.0f, WINDOW_WIDTH, WINDOW_HEIGHT, 1.0f, 50.0f);
    m_pShadowMapTech->SetWVP(p.GetWVPTrans());
    m_pMesh->Render();
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

In the first rendering, the camera is placed in the position of the light source to obtain the shadow map.

virtual void RenderPass()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    m_pShadowMapTech->SetTextureUnit(0);
    m_shadowMapFBO.BindForReading(GL_TEXTURE0);
    Pipeline p;
    p.Scale(5.0f, 5.0f, 5.0f);
    p.WorldPos(0.0f, 0.0f, 10.0f);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(30.0f, WINDOW_WIDTH, WINDOW_HEIGHT, 1.0f, 50.0f);
    m_pShadowMapTech->SetWVP(p.GetWVPTrans());
    m_pQuad->Render();
}

In the second rendering, the input texture shadow map is rendered in a quad. Note: You must load the quad model quad. obj.

After the program is executed, the effect is as follows:

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.