接上文:OpenGL基础52:阴影映射(上)

五、阴影失真

按照上文的计算的结果,一个很明显的问题是:对于参与计算深度贴图的物体,其表面可以看到这样的栅格状的阴影,这种常见的错误表现也叫做阴影失真(Shadow Acne)

出现这种请情况的原因是:在计算深度贴图的时候,分辨率不够高,毕竟这张深度贴图是对于整个场景而言的,想要一一对应到每个模型的每个片段就是个不可能的事,这样对于实际的模型的多个片段,就都会从深度贴图中的同一个坐标去采样,如果这些相邻片段的实际深度值一致那其实不会有什么问题,但由于光照角度的问题,这些相邻片段的深度值就会是不一样的,因此之前的比较方法在这里就会将部分片段给算在了阴影之中

解决方法也比较容易想到:多个相邻片段它们就算深度不同,但是它们的深度肯定是非常接近的,因此在计算当前片段是否被遮挡时,可以允许一定的误差

例子使用的是平行光,在平行光刚好垂直于表面时,相邻片段的深度值就是一样的,而光照倾斜角度越大,计算得到深度值差也就越大,所以可以在光照角度越接近于法向量时,使用更小的误差值

代码修改如下(就加了个bias的计算):

float bias = max(0.05 * (1.0 - dot(normalIn, lightDir)), 0.005);
float shadow = currentDepth - bias > closestDepth ? 0.75 : 0.0;

如果对产生的原因没有怎么明白,可以参考上面的图片:上图光照对于平面的倾斜角大约是45°,那么图中的 bias 的值就可以理解为当前实际片段与对应深度贴图采样片段的深度差,如果这个差是负数,那么当前的片段就会被归于被遮挡的片段

悬浮:

使用了上面的误差参与计算,也就相当于是对物体的实际深度进行了整体平移,如果这个误差值定义的过大了,就会有得到种物体“悬浮”的错觉:阴影有一点对不上实际的物体,因此误差值在保证解决了阴影失真的情况下要尽可能的小

当然还有个非常tricks的方法一样可以解决大部分情况下的阴影失真问题,还不会产生上面悬浮的效果:那就是在渲染深度贴图的时候是开启正面剔除,而非背面剔除,也就是永远使用物体的背面来计算深度

当然了,这种方法只对封闭的物体有效,并且接近阴影的物体仍然可能会出现不正确的效果,所以需要注意使用场景,不过大部分情况下并没有人会去关心理应不可能被看到的物体内部

上图为解决了阴影失真/悬浮的渲染结果

六、采样错误

这算是一个注意事项:

因为使用的是正交投影,所以要注意投影的范围一定尽可能的把场景中的所有物体包住,否则没有包住的部分就无法从深度贴图中采样,当然了,为了避免范围外的物体呈现完全的阴影,那么可以在设置深度贴图环绕方式为GL_CLAMP_TO_BORDER,也就是让所有超出深度贴图的坐标的深度范围是1.0,这样超出的坐标将永远不在阴影之中

除此之外,着色器部分也可以进行对应的修改,如果投影向量的z坐标大于1.0,就直接把shadow的值强制设为0.0,因为这部分的纹理已经超出了正交投影的远平面

float ShadowCalculation(vec4 fragPosLightSpace, vec3 lightDir)
{//……float bias = max(0.05 * (1.0 - dot(normalIn, lightDir)), 0.005);float shadow = currentDepth - bias > closestDepth ? 0.75 : 0.0;if(projCoords.z > 1.0)shadow = 0.0;return shadow;
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
GLfloat borderColor[] = { 1.0, 1.0, 1.0, 1.0 };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

七、百分比渐近过滤(PCF)

仔细看这个阴影,可以发现它的边缘非常的“锋利”,并且还呈现明显的锯齿状,而在现实世界里,阴影往往都是柔和的,越是离物体表面越远则边缘越“软”

产生锯齿的原因和前面的阴影失真原因一样:深度贴图有一个固定的分辨率,从而在计算阴影时多个相邻片段就会只对应到一个纹理像素,理论上把深度贴图的分辨率调高就能有效缓解锯齿的现象

另一个简单的解决方案叫做百分比渐近过滤(PCF),它是一个比较的常见的进行阴影边缘反走的技术,原理是从深度贴图中多次采样,并且每一次采样的纹理坐标都稍有不同,这样每个独立的样本可能在也可能不再阴影中,所有的次生结果接着结合在一起进行平均化,就得到了相对柔和的阴影

一个最简单的PCF例子如下:

float ShadowCalculation(vec4 fragPosLightSpace, vec3 lightDir)
{// 执行透视除法vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;// 变换到[0,1]的范围projCoords = projCoords * 0.5 + 0.5;// 取得当前片段在光源视角下的深度float currentDepth = projCoords.z;// 检查当前片段是否在阴影中float bias = max(0.05 * (1.0 - dot(normalIn, lightDir)), 0.005);float shadow = 0.0;vec2 texelSize = 1.0 / textureSize(shadowMap, 0);for(int x = -1; x <= 1; ++x){for(int y = -1; y <= 1; ++y){float closestDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r; shadow += currentDepth - bias > closestDepth ? 0.75 : 0.0;    }    }shadow /= 9.0;if(projCoords.z > 1.0)shadow = 0.0;return shadow;
}

可以说和抗锯齿的方式有点相似

当然这只是最简单的PCF实现,对于下图中的效果,如果从一个较远的距离去观察阴影,就可以发现它没有那么生硬了,看起来要自然的多

八、扩展

关于阴影的实现,可以扩展的就可太多了,哪怕是已经介绍的方案与解决方法,仍然有可优化的空间,不过这两章至少是实现了最简单的阴影。总之,在此之后还是要注重方法而不是openGL本身,如果是换一个图形接口,又或者是用游戏引擎来实现一样的效果,也要能够很轻松的实现

光源烘培:

如果打过ACM,又或者解决过一些项目中的数据读取问题,都知道有一个很经典的方法叫做离线打表,也就是先运行一次打表代码,运算出一些重要的数据记录到本地,然后等到真正需要这些数据来解决问题的时候,直接读取之前打出来的数据即可,这个方法可以说比较tricks,之前一个很经典的题目:2017百度之星资格赛:1005. 寻找母串(卡特兰数+分块打表)有兴趣的话可以去看下

为什么要提到这个呢?因为在渲染领域,有一个很经典的方法叫做光源烘培,是和离线打表一样的原理

思考一下:是不是对于游戏的场景,极大多数的模型(墙壁、地面、路灯)等它们属性和位置是永远不会改变的?这也意味着,除非玩家从旁边走过,又或者游戏中有天气环境变化等,它们的渲染效果也是永远不会变的(其实哪怕是环境有改变,一个属性不变的光源对附近一个属性不变的静态物体产生的光效,也是不变的)

所以很多情况下,可以离线渲染出深度贴图,甚至是离线计算出模型每一个片段带光照的实际颜色,然后直接以贴图文件的形式存入本地,这样等真正运行游戏时,就可以直接使用这张贴图作为最终颜色,不但不需要专门的帧缓冲去重新生成深度贴图,也不需要在着色器中计算对应物体的漫反射光照和镜面光照了

好处很明显,其一是省去了实时渲染的复杂度,这是一个很好的优化,其二就是离线渲染没有复杂度要求,你甚至可以直接模拟每一条光线或者使用非常复杂的算法,只要能最后够烘培出很好的效果就都不是问题

坏处的话的也很明显:

  • 无法渲染出镜面光照,因为这和视角有关
  • 接上,静态物体无法折射出动态的游戏对象与坏境,也无法产生对应的阴影
  • 光照结果是死的,没有生机感
  • ……

关于光源烘培对于动态游戏对象的效果,有个妥协方案叫做光照探针,有兴趣的话可以去了解下

一句话总结:如果是静态物体+属性不变的区域光,可以直接现烘培出光照结果,得到对应的光照贴图/深度贴图,而不再需要在每帧中单独去计算

OpenGL基础53:阴影映射(下)相关推荐

  1. OpenGL基础54:点光源阴影

    前置: OpenGL基础53:阴影映射(下) 一.万象阴影贴图 之前成功实现了平行光阴影,生成阴影贴图时使用的矩阵是正交矩阵,若是想要实现点光源的阴影效果,那么理论上只需要修改投影矩阵为透视矩阵就好了 ...

  2. OpenGL基础52:阴影映射(上)

    参考于:https://learnopengl.com/#!Advanced-Lighting/Shadows/Shadow-Mapping 一.游戏中的阴影 阴影是光线被阻挡的结果,当一个光源的光线 ...

  3. [转载] [OpenGL] shadow mapping(实时阴影映射)

    参考链接: Java中静态函数的阴影(方法隐藏) 转载原创:ZJU_fish1996   http://blog.csdn.net/zju_fish1996/article/details/51932 ...

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

    source:原文地址 code:点击可以直接下载源代码 1978年,Lance Williams在其发表的论文<Casting curved shadows on curved surface ...

  5. OpenGL学习(九)阴影映射(shadowMapping)

    目录 写在前面 阴影映射原理简介 封装 Camera 类 帧缓冲 阴影映射 准备工作 创建帧缓冲与深度纹理附件 从光源方向进行渲染 正常地渲染场景 如何查找bug(⚠重要) 多纹理传送 查看深度纹理数 ...

  6. OpenGL基础49:高度贴图(下)

    接上文:OpenGL基础48:高度贴图(上) 四.陡峭视差映射 上文计算纹理偏移的方法是最简单的,但问题也比较大:当你斜视贴图的时候可以看到非常明显的错误 这里有另一种方法,这种方法类似于找连续函数在 ...

  7. OpenGL基础45:光照矫正(下)之Gamma校正

    接上文:OpenGL基础44:光照矫正(上) 四.Gamma矫正 4.1.人的视觉特性 和很多错视图一样,对于下面这张灰阶图,如果1表示纯白,0表示纯黑,那么这张图片的哪个位置代表的是0.5,也就是自 ...

  8. openGL实现阴影映射(Shadow Mapping)

    openGL系列文章目录 文章目录 openGL系列文章目录 前言 阴影映射 阴影映射原理 二.使用步骤 显示效果 源码下载 参考 前言 阴影是光线被阻挡的结果:当一个光源的光线由于其他物体的阻挡不能 ...

  9. OpenGL基础35:帧缓冲(下)之简单图像处理

    在之前的章节,所有的物体都是中规中矩的显示的,只考虑了光照对物体的影响,那假设想要显示特殊的效果该怎么操作呢?例如马赛克风.将所有的物体都显示为黑白色,就像上世纪80年代的灰白电视一样,又或者说将整个 ...

最新文章

  1. 有感于“政府傍大款”----谈中小企业融资问题
  2. 我们从2021谷歌I/O大会给的谜题中发现了隐藏信息
  3. 阿里1582.73亿营收背后的持续交付如何玩?
  4. VTK:直线用法实战
  5. python中函数和类的区别_Python中函数和方法的区别
  6. 如何在Office 2007中查看关于对话框和版本信息
  7. 下列哪一项不是计算机网络的典型应用,09级计算机信息网络试卷A
  8. 阿里云Https部署网站
  9. OJ1008: 美元和人民币
  10. 【备忘录】创建自己的消息映射
  11. Algorithm Set:floyd判环法
  12. html烟火源码,HTML5:烟火
  13. 十行代码实现高仿Promise
  14. AngularJs + angular-ui-router + bootstrap 实现blog基础导航功能
  15. Nashorn Multithreading and MT-safety
  16. mybatisplus修改单个属性_SolidWorks工程图比例:整体修改与单视图修改
  17. Oracle数据库基本操作(windows 本地环境)
  18. winxp升级win7教程_PR CC 2015下载和安装教程
  19. LeetCode_初级算法_数组
  20. Delphi中使用TThread类实现多线程

热门文章

  1. 1000行代码入门python-Python基础知识和工作环境
  2. python必背入门代码-python必背内容有哪些
  3. python自动化办公都能做什么-盘点使用Python进行自动化办公所需要的知识点
  4. python和c 的区别-Python和C区别该如何理解?如何适应这种区别?
  5. p语言是python吗-Python 这语言真是混乱和原始
  6. 云知声原创技术再获肯定:多篇论文被国际语音顶会 INTERSPEECH 2020 收录
  7. 顶尖的语音识别软件――Nuance Recognizer_语音识别_CTI论坛
  8. python请求库_如何使用Python请求库发出post请求?
  9. pdf转chm_PDF转Excel的小妙招!
  10. 前端用户忘记密码,手机验证码修改密码功能