OGL(教程47)——Shadow Mapping with Directional Lights

http://ogldev.atspace.co.uk/www/tutorial47/tutorial47.html

the shadow mapping algorithm that we explored in tutorial 23 and tutorial 24 used a spot light as the light source.
the algorithm itself is based on the idea of rendering into a shadow map from the light point of view. this is simple with spot lights because they behave in the same way as our standard camera.

the spot light has a location and a direction vector and the area covered by the light grows as we move further away from its source:
the fact that the spotlight behaves like a frustum makes it easier to implement shadow mapping because we can ues the same perspective projection matrix as the camera in order to render into the shadow map. implementing Shadow mapping with point lights was a bit of a challenge but we were able to overcome it by rendering into a cubemap. projection, though, was still perpective.

now let us think about directional lights. a directional light has a direction but not a position.
it is usually used to mimic the behavior of the sun which due to its size and distance seems to cast parallel lights rays:

in this case, we can no longer use perspective projection. enter orthographic projection.
the idea here is that of convering all light rays in one spot(the camera), the light rays remain parallel so no 3D effect is created.

the left box looks real, just as u would expect it to be and delivers the correct sense of depth.
the right one does not look real since the front and back rectangles are exactly the same.

we know that their dimensions are the same but when looking at a picture we expect the front one to look larger.
so how does orthographic projection helps us with directional lights?
well, remember that perspective projection takes something that looks like a frustum and maps it to a normalized cube
(a cube that goes from [-1,-1,-1] to [1,1,1]).

afther mapping, the xyz coordinates are used to find the location in the texture (in our case the shadow map) and the z is the value which is written there.
an orthographic projection takes a general box and maps it to the normalized cube (l,r,b,t,n,f stands for left, right, bottom, top, near, far, respectively):

now think about the rays of the directional light as if they are originating from the front face of the box and going parallel to each other until they hit the back face.
if we do the mapping between the general box and the normalized box (remember – we cal this ndc space) properly the reset of the generation of the shadow map remains the same.

let’s see how this mapping is done. we have three ranges along the xyz axes that we need to map to (-1,1). this is simple linear mapping without divide-by-zero after that (since it is orthographic and not perspective). the general for of an equation that maps range (a,b) to (c,d) is:
在这里插入图片描述

compare this matrix with the one we created for perspective projection in tutorial 12.
an important difference is that in location [3,2] (count starts at zero) we have 0 instead of 1. for perspective projection the 1 was requried in order to copy the Z into the W location of the result.
this allows the gpu to perform perspective divide when everything is divided automatically by W (and u can not disable this).
in the case of orthographic projection the W will remain as 1, effectively disableing this operation.

when working on shadow mapping with directional lights u need to be careful about how u define the dimensions of orthographic projection. with perspective projection life is a bit simpler. the field-of-view defines how wide the camera is and due to the nature of the frustum we capture more and more as we move further away from the viewer (same as how our eye functions).

we also need to define a near and far plane to control clipping based on distance.
in many cases the same values of field-of-view, near and far plane will work just fine.
but in the case of orthographic projection we have a box rather than a frustum and if we are not careful we may ‘miss’ the objects and not render anything.

let’s see an example. in the scene below left and bottom are -10, right and top are 10, the near plane is -10, and the far plane is 100:

the problem is that the objects are placed at distance of 30 from each other so the projection was not wide enough in order to capture everything (remember that the light direction is orthogonal to the viewer so the objects are scattered on a wide field relative to the light). now let us multiply left/right/bottom/top by 10 (near/far planes unchanged).

now all the objects have a shadow. however, we have a new problem. the shadows do not look as good as when only one object had a shadow. this problem is called perspective alising and the reason is that many pixels in view space (when rendering from the camera point of view) are mapped to the same pixel in the shadow map.

this makes the shadows look kind of blocky. when we increased the dimensions of the orthographic box we increased that ratio beacause the shadow map remins the same but a larger part of the world is now rendered to it.
perspective aliasing can be mitigated somewhat by increasing the size of the shadow map but u can not go too far with that as there is a negative impart on memory footprint.
in future tutorials we will explore advanced techniques to handle this problem.

the main difference between shadow mapping with directional and spot lights is the orthographic vs. perspective projection.
this is why i am only going to review the changes required for shadows with directional light.
make sure u are highly familiar with tutorial 23 and tutorial 24 before proceeding because most of the code is the same.
if u have working version of shadows with spot lights u will only to make a few minor changes to get directional lights shadows workding.

(matrix_3d.cpp:165)

void Matrix4f::InitOrthoProjTransform(const OrthoProjInfo& p)
{
    float l = p.l;
    float r = p.r;
    float b = p.b;
    float t = p.t;
    float n = p.n;
    float f = p.f;

    m[0][0] = 2.0f/(r - l); m[0][1] = 0.0f;         m[0][2] = 0.0f;         m[0][3] = -(r + l)/(r - l);
    m[1][0] = 0.0f;         m[1][1] = 2.0f/(t - b); m[1][2] = 0.0f;         m[1][3] = -(t + b)/(t - b);
    m[2][0] = 0.0f;         m[2][1] = 0.0f;         m[2][2] = 2.0f/(f - n); m[2][3] = -(f + n)/(f - n);
    m[3][0] = 0.0f;         m[3][1] = 0.0f;         m[3][2] = 0.0f;         m[3][3] = 1.0; 
}

i have added the above function to the Maxtrix4f class in order to initialize the orthographic projection matrix. this function is called from Pipeline::GetWVOrthoPTrans();

(tutorial47.cpp:163)

void ShadowMapPass()
{ 
    m_shadowMapFBO.BindForWriting();
    glClear(GL_DEPTH_BUFFER_BIT);

    m_ShadowMapEffect.Enable();

    Pipeline p;
    p.SetCamera(Vector3f(0.0f, 0.0f, 0.0f), m_dirLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));
    p.SetOrthographicProj(m_shadowOrthoProjInfo); 

    for (int i = 0; i < NUM_MESHES ; i++) {
        p.Orient(m_meshOrientation[i]);
        m_ShadowMapEffect.SetWVP(p.GetWVOrthoPTrans());
        m_mesh.Render();
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}


void RenderPass()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_LightingTech.Enable();

    m_LightingTech.SetEyeWorldPos(m_pGameCamera->GetPos());

    m_shadowMapFBO.BindForReading(SHADOW_TEXTURE_UNIT);

    Pipeline p; 
    p.SetOrthographicProj(m_shadowOrthoProjInfo); 
    p.Orient(m_quad.GetOrientation());
    p.SetCamera(Vector3f(0.0f, 0.0f, 0.0f), m_dirLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));
    m_LightingTech.SetLightWVP(p.GetWVOrthoPTrans()); 
    p.SetPerspectiveProj(m_persProjInfo); 
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    m_LightingTech.SetWVP(p.GetWVPTrans());
    m_LightingTech.SetWorldMatrix(p.GetWorldTrans()); 
    m_pGroundTex->Bind(COLOR_TEXTURE_UNIT);
    m_quad.Render();

    for (int i = 0; i < NUM_MESHES ; i++) {
        p.Orient(m_meshOrientation[i]);
        m_LightingTech.SetWVP(p.GetWVPTrans());
        m_LightingTech.SetWorldMatrix(p.GetWorldTrans());
        m_mesh.Render();
    }
}

these are the complete shadow and render passes and they are pratically the same as for spot lights so we do not have to review them fully. just a couple of differences that must be noted here.

first is that i have added a member called m_shadowOrthoProjInfo in order to keep the orthographic projection variables spearate from the existing perspective projection variables that are used for rendering.
m_shadowOrthoProjInfo is used to configure the WVP for the light point of view and it is initialized with the values of -100,+100,-100,+100,-10,+100 for left, right, bottom, top, near, far, respectively.

the second change is that when we configure the camera for that light WVP matrix we use the origin as the location of the light. since a directional light only has a directional and no position we do not care about that variable in the view matrix.
we just need to rotate the world so that light points toward the positive Z-axis.

positive Z-axis.

(lighting.fs:96)

vec4 CalcDirectionalLight(vec3 Normal, vec4 LightSpacePos)
{ 
    float ShadowFactor = CalcShadowFactor(LightSpacePos);
    return CalcLightInternal(gDirectionalLight.Base, gDirectionalLight.Direction, Normal, ShadowFactor); 
} 

void main() 
{ 
...
  vec4 TotalLight = CalcDirectionalLight(Normal, LightSpacePos); 
...
}

the shaders are almost exactly the same-- we just need to calcualte a shadow factor for the directional light as well.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值