点光源阴影

点光源阴影(Omnidirectional Shadow Maps)

摊牌

  • 由点光源产生的阴影,过去的名字是万向阴影贴图(Omnidirectional Shadow Maps)
  • 点光源可以照射的方向是空间中的各个方向,因此他产生的阴影也是在空间中的各个方向

原理

  • 先回顾一下阴影贴图中提到的绘制阴影的原理:首先在光源的视角下渲染场景,得到一张深度贴图,然后在摄像机的视角下再次渲染场景,并将这时候的顶点变换到光源空间中,先计算顶点在光源空间中的实际深度,再对深度贴图采样,通过实际深度值与最小深度值比较,判断片段是否在阴影中
  • 点光源阴影与阴影贴图的原理相似,不同的地方是生成深度贴图的维度
  • 因为点光源产生的阴影是在空间中的各个方向,所以我们可以用一张立方体的深度贴图来包含各个方向上的深度值

实现

  • 生成立方体深度贴图
  1. 创建立方体贴图的纹理glGenTextures(1, &cubeDepthMap)
  2. 生成立方体贴图的每个面,并将每个面都设置为2D深度贴图,不要忘记设置环绕方式、过滤方式
for (int i=0; i<6; ++i)
{
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
}
  • 需要注意的是:在正常情况下,一次只能将立方体贴图的一个面附加到帧缓冲的附件上,所以需要渲染场景6次,每次把帧缓冲的深度附件改成不同的立方体面,最终完成一个立方体纹理
  • 但是如果我们使用几何着色器,则可以把所有的立方体面在一次渲染中完成,这样我们就需要把立方体贴直接附加到帧缓冲的深度附件上
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, cubeDepthMap, 0);
  • 还有记得调用glDrawBuffer(GL_NONE)和glReadBuffer(GL_NONE)来显示的告诉OpenGL这个帧缓冲对象不会渲染到颜色缓冲
  • 光源空间的变换
  1. 对于投影矩阵来说,我们使用透视投影;因为点光源代表一个空间中的点,使用透视投影更有实际意义
  • 对于不同立方体面使用同一个投影矩阵
  • 需要注意的是:透视投影的视角需要设置成90度,这样才能保证视野足够大到可以合适地填满立方体贴图的一个面,立方体贴图的所有面都能与其他面在边缘对齐
  1. 对于观察矩阵,需要为立方体贴图的6个面设置不同的观察矩阵

std::vector<glm::mat4> shadowTransforms;
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3(1.0,0.0,0.0), glm::vec3(0.0,-1.0,0.0)); 
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3(-1.0,0.0,0.0), glm::vec3(0.0,-1.0,0.0)); 
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3(0.0,1.0,0.0), glm::vec3(0.0,0.0,1.0)); 
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3(0.0,-1.0,0.0), glm::vec3(0.0,0.0,-1.0)); 
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3(0.0,0.0,1.0), glm::vec3(0.0,-1.0,0.0)); 
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3(0.0,0.0,-1.0), glm::vec3(0.0,-1.0,0.0));
//观察矩阵的up参数,为什么要朝向那个方向?
  1. 顶点着色器1
  • 由于我们打算使用几何着色器来实现立方体深度贴图,所以先设想一下几何着色器要做的事情,这样就顺带确定了顶点着色器要做的事情。几何着色器要做的就是将世界空间中的顶点变换到6个不同的光空间中,因此顶点着色器只需要将顶点变换到世界空间即可
gl_Position = modelMat * vec4(position, 1.0);
  1. 几何着色器1
  • 几何着色器将一次接收到三角面的三个顶点,和一个光空间的变换矩阵数据
  • 几何着色器的内置变量gl_Layer,指定发射出的基本图形将发送到立方体的哪个面上
  • 这样,有了顶点、变换矩阵和gl_Layer就可以将同一个三角面变换后发送到不同的面上,以至最后构成立方体贴图
#version 330 core 
layout (triangles) in; 
layout (triangle_strip, max_vertices=18) out; 
uniform mat4 shadowMatrices[6]; 
out vec4 FragPos;
for (int face = 0; face < 6; ++face)
{
    gl_Layer = face;
    for (int i = 0; i < 3; ++i)
    {
        FragPos = gl_in[i].gl_Position;
        gl_Position = shadowMatrices[face] * FragPos;
        EmitVertex();
    }
    EndPrimitive();
}
  • 几何着色器的最后还要将世界空间的顶点输出到片段着色器,因为要在片段着色器中计算深度值
  1. 片段着色器1
  • 片段着色器的目的就是计算深度值,这个深度值是每个片段位置和光源之间的线形距离,然后再变换到[0.0, 1.0]的范围内
  • 经过上面7步,可以得到一个立方体深度贴图,下面开始渲染阴影
  1. 顶点着色器2
  • 由于片段着色器2中将使用顶点与光源之间的方向对立方体贴图采样,所以在顶点着色器2中将世界空间中的顶点传给片段着色器2即可
  1. 片段着色器2
  • 片段着色器2主要有三部分工作组成:一是计算光照,这里用Blinn-Phong光照模型;二是判断片段是否在阴影中;三是计算片段最终的颜色
  • 判断片段是否在阴影中,需要两个值的比较,一个是当前片段与光源的实际距离,另一个是从立方体贴图中采样到的最小距离,如果实际距离大于最小距离,就说明片段在阴影中
  • 对立方体贴图采样用的采样器类型是samplerCube
vec3 fragToLight = fragPos - lightPos;
currentDepth = length(fragToLight); //实际距离
float closestDepth = texture(depthMap, fragToLight).r; 
closestDepth *= far_plane; //最近距离
float bias = 0.05;
float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;

显示立方体深度贴图

  • 直接对立方体贴图采样,并把采样结果传给gl_FrogColor显示即可

副作用

锯齿

  • 由于立方体深度贴图基于传统阴影映射的原理,所以他也继承了由解析度产生的非真实感,因此也会有锯齿的阴影边缘
  • 解决办法:减少采样次数的PFC算法
vec3 sampleOffsetDirections[20] = vec3[]
(
    vec3( 1, 1, 1), vec3( 1, -1, 1), vec3(-1, -1, 1), vec3(-1, 1, 1), 
    vec3( 1, 1, -1), vec3( 1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1),
    vec3( 1, 1, 0), vec3( 1, -1, 0), vec3(-1, -1, 0), vec3(-1, 1, 0),
    vec3( 1, 0, 1), vec3(-1, 0, 1), vec3( 1, 0, -1), vec3(-1, 0, -1),
    vec3( 0, 1, 1), vec3( 0, -1, 1), vec3( 0, -1, -1), vec3( 0, 1, -1)
 );
 
float shadow = 0.0;
float bias = 0.15;
int samples = 20;
float viewDistance = length(viewPos - fragPos);
float diskRadius = 0.05;
for (int i = 0; i < samples; ++i)
{
    float closestDepth = texture(depthMap, fragToLight + sampleOffsetDirections[i] * diskRadius).r;
    closestDepth *= far_plane; // Undo mapping [0;1]
    if (currentDepth - bias > closestDepth)
        shadow += 1.0;
}
shadow /= float(samples);

在这里插入图片描述

  • 需要注意的是:使用几何着色器来生成深度贴图不会一定比渲染场景6次更快。使用几何着色器有他自己的性能局限,在第一个阶段使用几何着色器可能获得更好的性能表现,但这也取决于环境的类型,以及特定的显卡驱动等因素
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值