Tutorial - Deferred Rendering Shadow Mapping 转

http://www.codinglabs.net/tutorial_opengl_deferred_rendering_shadow_mapping.aspx

Tutorial - Deferred Rendering Shadow Mapping



In this tutorial I will present the shadow mapping technique implemented in a deferred renderer. This tutorial will lean on a previous one, Simple Deferred Rendering in OpenGL; I strongly reccomend you to read it before proceeding with this tutorial as most of the code is shared and I will not present those bits that have already been covered in the other tutorial.

Shadow Mapping is the most widely used technique in real time rendering engines today. Almost every game uses some sort of shadow mapping technique to render its shadows, even if often the actual implementation relies on extensions of the basic shadow mapping that we will see in this tutorial. Some more advanced implementations are Cascade Shadow Mapping, Soft Shadow Mapping, Parallel Split Shadow Mapping, etc etc. For this tutorial we will simply try to understand the basics ideas that all these techniques use, which is using a texture (map) to encode useful information to render shadows.

   

Determining whether a pixel is in shadow or if it‘s lit by a given light is a visibility problem. What we want to test is if that specific point in the world is visible or not from the light point of view. We apply this idea to every pixel that we are rendering on screen thus what we want to do is to calculate what position each pixel is mapped to in world space. Once we have the position we can cast a ray back to the light. If the ray can reach the light without being interrupted, the pixel is in light, otherwise is in shadow. Watch the picture below (Figure 1). You can see how the two point are both visible from the camera, but just one is visible from the light.

Figure 1: Points in shadow and in light

So now we know the principle and we can proceed to the practice. How can we determine whether a pixel (remapped in world) is visible or not from the light position. There are many many ways, but the one we are going to implement takes advantage of the ability of the GPU to render the depth of a scene. So if we set an imaginary camera in the light position and we render the depth of the scene as seen from the light what we get is a texture that contains a bunch of distances. Every pixel of this shadow map can be remapped in world space and will tell you what pixel the light see and how far that point is from the light itself. Watch Figure 2 below. The first point, P1, is visible from both the camera point of view and the light point of view. So if when we render it we also calculate its world position and then calculate the distance from the light we can compare it with the distance saved in the shadow map. When we compare P1‘s distance we will find that the value we calculate is the same value contained in the map (minus some error due to the way we store depth in the map). This because the pixel is visible frome the light point of view. 
If we try to do the same with P2 we‘ll find a substantial difference. Say we calculate P2 in world space and then we calculate the distance between P2 in world space and the light as we did for P1. Now, if we compare the calculated distance with the distance saved in the shadow map we‘ll find that the one in the map is way smaller than the one we have just calculated. This because in the map the light will have the distance between P3 and the light itself. In fact, if you see from the light point of view P2 and P3 are overlapped, and since P3 is the closest one it "overrides" P2 which is then not visible. This tells us that P2 is in shadow.

Figure 2: Points in shadow and in light seen from light and camera

This is all you need to implement shadow mapping. Let‘s see the sample code and let‘s try to make it as clear as possible.


1


I‘ll try to present the code not from the first to the last line but instead picking various funcionality and explaining what is doing what. So, let‘s start from the the rendering function.

/**
*	Render the scene
*/
void GLApplication::render()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glClearColor(0.2f, 0.3f, 0.8f, 1.0f);

    // We move the near plane just a bit to make the depth texture a bit more visible.
    // It also increases the precision.
    glMatrixMode(GL_PROJECTION);

    glPushMatrix();
    glLoadIdentity();
    gluPerspective(20.0f, 1, 40.0f, 70.0f);

    // Set the light position
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glRotatef(55, 1, 0, 0);
    glRotatef(-45, 0, 1, 0);
    glTranslatef(-25.0f, -50.0f, -25.0f);
    glRotatef(m_lightRotation, 0, 1, 0);
    glFrontFace(GL_CW);

To start we want to acquire the shadow map, therefore we position the camera in the light‘s position and we orient it so that it looks down to the scene. We also flip from the standard CCW to CW when rendering the shadow map. This is a nice trick that works as far as every object is "closed". Flipping to CW renders the object "inside out" which means that we will use the back faces for comparing the distances reducing the shadow acne that typically shows up with shadow mapping. This is not mandatory, it just helps a bit in most of the cases (but make it worst in others).

    // Render the shadow map
    m_deferredRendering->startRenderToShadowMap();
    for(int i=0; i<c_modelsCount; ++i)
        m_models[i]->render();
    m_deferredRendering->stopRenderToShadowMap();

We then render all the models into the shadow map. We‘ll check how the shadow map is created in a moment, for now all we need to know is that here we render all the models‘ depth into a texture.
Once we have finished with the shadow map we save the matrices and reset everything.

    // We then save out the matrices and send them to the deferred rendering, so when it comes to do the deferred pass
    // it can project the pixel it‘s rendering to the light and see if it‘s in shadows
    float worldToLightViewMatrix[16];
    float lightViewToProjectionMatrix[16];
    glGetFloatv(GL_MODELVIEW_MATRIX, worldToLightViewMatrix);
    glGetFloatv(GL_PROJECTION_MATRIX, lightViewToProjectionMatrix);

    // Re-set the projection to the default one we have pushed on the stack
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();

    // Set the camera position
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glRotatef(20, 1, 0, 0);
    glTranslatef(0.0f,-6.5f,-11.0f);
    glFrontFace(GL_CCW);

    float worldToCameraViewMatrix[16];
    glGetFloatv(GL_MODELVIEW_MATRIX, worldToCameraViewMatrix);

We store the matrices in some local variables because we need to provide them to the shader to reproject the pixels back in world and into the light clip/projection space. Notice that we also restore the renderer back to render faces CCW.

    // Render our geometry into the FBO
    m_deferredRendering->startRenderToFBO();
    for(int i=0; i<c_modelsCount; ++i)
        m_models[i]->render();
    m_deferredRendering->stopRenderToFBO();

    // Render to the screen
    if(m_state == 0)
    {
        // Render to screen using the deferred rendering shader
        m_deferredRendering->setLightMatrices(worldToLightViewMatrix, lightViewToProjectionMatrix, worldToCameraViewMatrix);
        m_deferredRendering->render();
    }
    else if(m_state == 1)
    {
        m_deferredRendering->showTexture( 0, (float)m_windowWidth, (float)m_windowHeight, 512, 384, 0);
        m_deferredRendering->showTexture( 1, (float)m_windowWidth, (float)m_windowHeight, 512, 384, 512);
        m_deferredRendering->showTexture( 2, (float)m_windowWidth, (float)m_windowHeight, 512, 384, 0, 384);
        m_deferredRendering->showShadowMap(  (float)m_windowWidth, (float)m_windowHeight, 384, 384, 512, 384);
    }

    SwapBuffers(m_hdc);
}

This is the core of the rendering procedure. Summarizing we acquire the shadow map, save the matrices, send the matrices to the shader and then render. So we now have to see in detail two more things: how we create and use the shadow map and, the most important one, the shader that composes and renders the final frame.


2


Let‘s see how do we capture the shadow map. Since all we need to store in the shadow map is the depth, we don‘t have to create a colour render target. All we need is a render target that can receive depth. To isolate this feature I‘ve created a class called DepthRenderTexture which provides some methods to render the depth into the render target and some debug functionality to show the render target as a texture. The constructor of this class tells OpenGL that we only want to render depth into an FBO:

// Generate the OGL resources for what we need
glGenFramebuffersEXT(1, &m_fbo);
glGenRenderbuffersEXT(1, &m_depthBufferRT);

// Bind the FBO so that the next operations will be bound to it
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_fbo);

// Bind the depth buffer
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, m_depthBufferRT);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, m_width, m_height);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, m_depthBufferRT);

// Generate and bind the OGL texture for diffuse
glGenTextures(1, &m_depthTexture);
glBindTexture(GL_TEXTURE_2D, m_depthTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, m_width, m_height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, 0);
   
// Attach the texture to the FBO
glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_depthTexture, 0);

// Check if all worked fine and unbind the FBO
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if( status != GL_FRAMEBUFFER_COMPLETE_EXT)
    throw new std::exception("Can‘t initialize an FBO render texture. FBO initialization failed.");

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

Once the OpenGL resource is created we can acquire the texture as we would do with a normal FBO. This is wrapped by the start and stop methods.
Now that we have the depth map we can finally proceed to render our shadow in the main scene.


3


We are now to the last step of this tutorial. How to combine everything and define whether a pixel is in shadow or not. All the magic happens in the shader, so we will now analyze it bit by bit.

uniform sampler2D tDiffuse;
uniform sampler2D tPosition;
uniform sampler2D tNormals;
uniform sampler2D tShadowMap;
uniform vec3 cameraPosition;
uniform mat4 worldToLightViewMatrix;
uniform mat4 lightViewToProjectionMatrix;
uniform mat4 worldToCameraViewMatrix;

float readShadowMap(vec3 eyeDir)
{...}

void main( void )
{
    // Read the data from the textures
    vec4 image = texture2D( tDiffuse, gl_TexCoord[0].xy );
    vec4 position = texture2D( tPosition, gl_TexCoord[0].xy );
    vec4 normal = texture2D( tNormals, gl_TexCoord[0].xy );

    mat4 lightViewToWolrdMatrix = inverse(worldToLightViewMatrix);
    vec3 light = lightViewToWolrdMatrix[3].xyz;
    vec3 lightDir = light - position.xyz;

    normal = normalize(normal);
    lightDir = normalize(lightDir);

    vec3 eyeDir = position.xyz - cameraPosition;
    vec3 reflectedEyeVector = normalize(reflect(eyeDir, normal));

    float shadow = readShadowMap(eyeDir);
    float diffuseLight = max(dot(normal,lightDir),0) * shadow;
    float ambientLight = 0.1;

    gl_FragColor = (diffuseLight + ambientLight ) * image + pow(max(dot(lightDir,reflectedEyeVector),0.0), 100) * 1.5 * shadow;
}; 

So what‘s different from the previous tutorial? For a start we now read the light position from the light matrix (since we have it),   but the important bit is obviously the line that calls readShadowMap(eyeDir). At that line we call the funcion that given the camera to pixel vector decides if the pixel we see is in shadow or not. We will see the function in a moment. The shadow value ranges between 0.0 to 1.0. We then use this value to make the diffuse light black and to kill the specular reflection.
The function that does all the complex math is readShadowMap. This function takes the vector camera to pixel‘s world position and find where this point is in the shadow map.

float readShadowMap(vec3 eyeDir)
{
    mat4 cameraViewToWorldMatrix = inverse(worldToCameraViewMatrix);
    mat4 cameraViewToProjectedLightSpace = lightViewToProjectionMatrix * worldToLightViewMatrix * cameraViewToWorldMatrix;vec4 projectedEyeDir = cameraViewToProjectedLightSpace * vec4(eyeDir,1);
    projectedEyeDir = projectedEyeDir/projectedEyeDir.w;

    vec2 textureCoordinates = projectedEyeDir.xy * vec2(0.5,0.5) + vec2(0.5,0.5);

    const float bias = 0.0001;
    float depthValue = texture2D( tShadowMap, textureCoordinates ) - bias;
    return projectedEyeDir.z * 0.5 + 0.5 < depthValue;
}

The eyeDir that comes in input is in View Space. To find the pixel in the shadow map we need to take that point and covert it into the light‘s clip space, which means going from Camera View Space into World Space, then into Light View Space, than into Light Projection Space/Clip space. All these transformations are done using matrices; if you are not familiar with space changes you may want to read my article about spaces and transformations.

Once we are in the right space we calculate the texture coordinates and we are finally ready to read from the shadow map. Bias is a small offset that we apply to the values in the map to avoid that because of rounding errors a point ends up shading itself! So we shift all the map back a bit so that all the values in the map are slightly smaller than they should.

Finally we check if the distance between the point and the light (projectedEyeDir) is smaller than the value stored in the depth map. Notice how we need to scale back projectedEyeDir from the -1 -> 1 range to the 0->1 range as the shadow map is in this latter range.

That‘s it, not too difficult if you graps the idea of chaning space and mapping pixels into a map of dephts! Feel free to grab the code and have a play with it, as usual, trying yourself is way more useful than any explanation.

时间: 2025-01-15 01:22:24

Tutorial - Deferred Rendering Shadow Mapping 转的相关文章

opengl 教程(23) shadow mapping (2)

原帖地址:http://ogldev.atspace.co.uk/www/tutorial24/tutorial24.html Background In the previous tutorial we learned the basic principle behind the shadow mapping technique and saw how to render the depth into a texture and later display it on the screen b

OpenGL阴影,Shadow Mapping(附源程序)

实验平台:Win7,VS2010 先上结果截图(文章最后下载程序,解压后直接运行BIN文件夹下的EXE程序): 本文描述图形学的两个最常用的阴影技术之一,Shadow Mapping方法(另一种是Shadow Volumes方法).在讲解Shadow Mapping基本原理及其基本算法的OpenGL实现之后,将继续深入分析解决几个实际问题,包括如何处理全方向点光源.多个光源.平行光.最近还有可能写一篇Shadow Volumes的博文(目前已经将基本理论弄清楚了),在那里,将对Shadow Ma

shadow mapping实现动态shadow实现记录

前段时间一直在弄一个室内场景,首先完成了render,效果还可以.然后给其加上shadow,使其更逼真.这里主要记录下在做的过程中遇到的问题. 1.是在导入场景的时候,由于场景比较大(200M)左右,所以在ios上加载这么大的场景会频繁的memorywarning,然后就会被系统kill掉.这个问题的解决方法是通过改变数据类型来达到压缩的目的.顶点的坐标double是没法改变的,如果改变会严重影响场景的准确度.这里主要是改变normal和 uv的类型,其实在正常的精度范围内,normal和 uv

OpenGL 阴影之Shadow Mapping和Shadow Volumes

先说下开发环境.VS2013,C++空项目,引用glut,glew.glut包含基本窗口操作,免去我们自己新建win32窗口一些操作.glew使我们能使用最新opengl的API,因winodw本身只包含opengl 1.1版本的API,根本是不能用的. 其中矩阵计算采用gitHub项目openvr中的三份文件, Vectors.h ,Matrices.h, Matrices.cpp,分别是矢量与点类,矩阵类,我们需要的一些操作,矢量的叉乘和点乘,矩阵转置,矩阵的逆,矩阵与矢量相剩等. 这里主要

Deferred Rendering(三)反锯齿和半透明问题

Deferred 框架下的AA 前面说过Deferred 框架下无法使用硬件AA,这句话不严谨: Deferred Shading在G-Buffer之后,物体几何信息全被抛弃了,导致后续每个像素都独立计算,所以不能使用硬件AA: 但是:Deferred Lighting,在Shading Pass阶段,物体会被再次渲染一遍,此时打开硬件MSAA,肯定是能用的(尽管光照部分取自lighting Pass阶段得到的texture,没能享受到AA,但对最终结果影响很小). 所以,总结来看,Deferr

shadow mapping实现动态shadow实现记录 【转】

http://blog.csdn.net/iaccepted/article/details/45826539 前段时间一直在弄一个室内场景,首先完成了render,效果还可以.然后给其加上shadow,使其更逼真.这里主要记录下在做的过程中遇到的问题. 1.是在导入场景的时候,由于场景比较大(200M)左右,所以在iOS上加载这么大的场景会频繁的memorywarning,然后就会被系统kill掉.这个问题的解决方法是通过改变数据类型来达到压缩的目的.顶点的坐标double是没法改变的,如果改

[OpenGL] shadow mapping(实时阴影映射)

source:原文地址 code:点击可以直接下载源代码 1978年,Lance Williams在其发表的论文<Casting curved shadows on curved surfaces>中提出了Shadow mapping算法,从那以后,该算法在离线渲染和实时渲染两个领域都得到了广泛的应用.皮尔斯动画工作室的Renderman渲染器.以及一些知名电影如<玩具总动员>都使用了shadow mapping技术. 在众多图形应用的阴影技术中,shadow mapping只是产

Forward Render VS Deferred Rendering

Forward Render 传统的渲染方式,你提供给显卡形状Mesh,它将其打散成一堆节点,接着经过一系列的变换和分割成为Fregment或者像素,在呈现在屏幕之前已经完成了所有的渲染处理. 这是相当线性的,每一个形状都会在生成完整图像之前经过流水线的每一个阶段. Deferred Rendering 中文称为延迟渲染,渲染的工作被放在最后,直到所有的形状到都完成了前面的工作,一旦所有需要的缓冲建好,就直接被读进一种着色算法中,合并在一起从而得出最后的结果. 这样,着色一个场景所需的计算和内存

【学习笔记】3D图形核心基础精炼版-12:stage3D实战-动态阴影 shadow mapping 和范例工程4

目的: 物体投影在另一个物体身上,而另一个物体可能是平的,但大多数都是不平的多边形物体,这里考虑的是后者,这样可以适用于大多数场合的投影. 预览效果: 原理: 这里使用的是shadow mapping方式,其原理如下: 1.将场景的深度值预先渲染到 以光源位置为原点.光线发射方向为观察方向的投影坐标系中,形成深度纹理. 2.再次渲染场景的过程中,将每个片断(像素)变换到前述眼坐标系中,并缩放到[0,1]的范围内以便查询纹理. 3.以较暗的光照绘制场景 4.以当前片断在眼坐标中的S.T坐标查询深度