Unity3D 基于ShadowMap的平滑硬阴影
前言
传统的ShadowMap在明暗边缘处都会有很难看的锯齿,因此一般得到的结果会比较难看,常规的解决办法都会在使用ShadowMap渲染阴影的时候通过背面剔除把这种缺陷隐藏掉,最后剩下一个影子。但是这样一来,自阴影就会丢失,因而传统的做法又会通过局部光照来重新为这个物体添加上部分自阴影,也就是咱们常见的Phone光照模型、Blinn-Phone光照模型。而本文决定通过文献[1]的一个平滑方法把ShadowMap在明暗边缘处的锯齿消除,并和光照模型求并,最后得到了一个包含丰富平滑自阴影效果。
本文读者默认为有图形学基础、编写Shader基础和ShadowMap基本原理,若没有请先去把这些基础学习一下,再来阅读本文,否则可能会有阅读障碍。
一、ShadowMap和局部光照模型的缺陷
(1) 传统ShadowMap能产生丰富的自阴影,但在明暗边缘处都会有很难看的锯齿和走样问题,如图1.1。

图1.1 ShadowMap的缺陷
(2) 局部光照模型在在明暗边缘处能产生非常平滑的阴影,但只能产生有限的自阴影,缺陷表现为丢失部分自阴影和投射阴影,如图1.2。

图1.2 局部光照模型的缺陷
二、ShadowMap缺陷分析
ShadowMap产生这种锯齿缺陷的原因是,光照摄像机的方向和模型中边缘处的三角面接近平行,导致这些三角面没有映射到任何像素点。如下图2.1、图2.2

图2.1 以光照摄像机的正交投影视角观察需产生阴影的物体

图2.2 以自由的正交投影视角观察需产生阴影的物体
图2.1渲染出来的图像就是ShadowMap,图2.2是以不同的角度观察ShadowMap渲染的物体。
两张图都是是同一个渲染方法,只是观察角度不同。即:把光照方向和三角面法向量的点乘结果大于0的三角面渲染出来的结果,也就是隐藏对于光源摄像机不可见部分。这样大致上能模拟得出构成ShadowMap的所有必须的三角面。
从人的视觉上看图2.1很完美,理论上也就认为能产生完美的阴影,但实际操作的时候就会发现结果和自己想的不一样。结合两张图得出,造成ShadowMap明暗边缘锯齿的罪魁祸首是模型的三角化导致的。因此不管怎么增加ShadowMap的分辨率都是没用的。

图2.3 ShadowMap的缺陷指示图
目前市面上的模型基本都是三角面构成的,不可能因为这个问题就废弃掉。虽然学术上有一种把三角面模型体素化的方法把模型转换成控件中的一个个有体积微小正方体,但貌似并不常用。因此问题怎么消除这些锯齿是本文的重点。
三、ShadowMap和局部光照模型的并集
那么仔细观察两种模型产生的阴影缺陷后,把两者求并集后是否就能即拥有局部光照般的边缘平滑度,又有ShadowMap丰富的阴影呢?立马动手实现,如下图3.1

图3.1 ShadowMap和局部光照模型求并
如果这样的效果能接受的话,那么到此就结束了。本人在翻了一番国内学术后发现,也是到这一步就结束了,后续貌似没人再做更多的工作。但其实还可以进一步把平滑做得更完美。
四、ShadowMap明暗边界的平滑
4.1 构造明暗边界线
ShadowMap的锯齿原因是由于在明暗边界的地方三角面不完整,导致深度呈锯齿状起伏,因此只要把明暗交接的地方的深度值(像素值)用同一个深度值覆盖就能获取到非常平滑的明暗边界。
在文献[1]~文献[3]中都阐述到了同一种,方向向量与模型网格(Mesh)在边缘处求边缘线的方法。由于本人未对其做深入研究,仅知道通过其提供的公式即可求出边缘线,进而可构造出比较完美的明暗边界边,理论就不多说了免得班门弄斧,建议直接去看原文,不看那就直接抄本人写的代码。效果如下图4.1~图4.3:

图4.1 ShadowMap+明暗交界线(红色)

图4.2 局部光照+明暗交界线(红色)

图4.2 复杂模型+局部光照+明暗交界线(红色)
此边缘线基本上就是局部光照模型的明暗交接的比较完美的逼近了,甚至还比局部光照还能更平滑,这都是得益于数学上的赫米特(Hiemite)插值法。
4.2 平滑ShadowMap明暗边界的深度值
实际上通过文献[1]~文献[3]求出来的是一个一个轮廓三角形上的一条线段,最后把所有这些线段合并起来就得到了明暗边界线,那么我们就可以通过这些点构造出一条针对于光照摄像机的可控粗细的线条。如下图4.2.1、图4.2.2

图 4.2.1 其他视角

图 4.2.2 光照摄像机视角
具体实现步骤如下:
1.通过明暗边界线的2个点的位置及其单位法向量(注:这2个数据都可以通过文献[1]~文献[3]计算得到)构造出2个各自沿着单位法向量负方向位移一段距离的点,以及2个各自沿着单位法向量正方向位移一段距离的点。
2.通过步骤1得到的4个点构造出2个三角面,进而构造出1个四角面。
这样我们就得到了一个针对于光照摄像机的可控粗细的明暗边界线,接下来就是如何进行正确的覆盖ShadowMap中锯齿状起伏的深度值。
4.3 覆盖ShadowMap中锯齿状起伏的深度值
关于这一块本人目前没有想到太好的办法,目前的做法是把这些明暗边界线往光照方向的负方向位移一段距离来覆盖锯齿状起伏的深度值,效果看起来还不错。

图4.3.1 ShadowMap+平滑明暗边界的深度值

图4.3.2 ShadowMap+平滑明暗边界的深度值+局部光照

图4.3.3 ShadowMap+平滑明暗边界的深度值+局部光照+复杂模型

图4.3.4 平滑明暗边界的ShadowMap
可以看到仅仅使用ShadowMap就非常接近局部光照阴影的平滑程度了,并且还拥有丰富的全局阴影。但由于只是简单的把这些明暗边界线往光照方向的负方向位移一段距离来覆盖锯齿状起伏的深度值,因此还是有一点点的小缺陷。如果到这里已经满足了的话,我建议再把局部光照加上去,因为局部光照算法非常简单,1次点乘+1次step即可得到结果,再与本文方法求并,就能得到效果很不错的阴影了,如图4.3.2和图4.3.3。
五、实现以及用途
说了这么多,真正动手去实现的时候会发现,并没有增加多少复杂度,仅仅在传统的ShadowMap的基础上,在渲染ShadowMap的时候增加几何着色器即可。代码部分不过多说明,后面会给出基于Unity3D的源码。
那么这种硬阴影有什么用呢,甚至不惜增加一定的复杂度?\本文认为这种阴影在卡通渲染上是十分有用的,因为卡通的颜色并不需要过多的渐变,一般只需要明暗2种颜色即可,而卡通渲染又需要丰富阴影,因此将其运用到卡通渲染上是用途之一,如下图5.1。

图5.1 本文算法+明暗贴图+复杂模型
六、结束语
虽然是这么说,但实际上二次元精美的插画都有一定程度的渐变,这是人为主观意识来添加的。在计算机上要实时实现这中渐变,并且任何角度观察都能达到插画级的精美程度是很困难的,这是因为插画的绘画人自己也说不出这个数学模型,在计算机里没有数学模型就不存在合理性,没有合理性就很难模拟,因此一般都只能用大量人力一帧一帧地把画面画出来。
目前顶尖水平的卡通渲染是以GuiltyGearXrd为首的渲染方法,使用局部光照阴影,并通过大量人力物力对某个物体在各个角度做类似法线贴图的贴图。这是无法复制的,一次创作需要消耗大量人力物力。这种方式产生的阴影在物体形变大的时候会产生凌乱的阴影。因此制作人一般都会想办法遮掩这部分缺陷,比如减少物体的形变动作,或者把镜头放到你看不到这些缺陷的地方。如果哪天实时全局光照烂大街了,那么插画级卡通渲染或许就会来临吧。
感谢学术界大佬们的精彩文章,本文到此结束,谢谢。
附:源码
参考文献
Silhouette Smoothing for Real-time Rendering of Mesh Surfaces
基于GPU的网格模型平滑阴影的实时绘制
三角网格模型平滑阴影的实时绘制
Unity3D 基于ShadowMap的平滑硬阴影的更多相关文章
- Unity3d 基于物理渲染Physically-Based Rendering之最终篇
前情提要: 讲求基本算法 Unity3d 基于物理渲染Physically-Based Rendering之specular BRDF plus篇 Unity3d 基于物理渲染Physically-B ...
- Unity3D 基于预设(Prefab)的泛型对象池实现
背景 在研究Inventory Pro插件的时候,发现老外实现的一个泛型对象池,觉得设计的小巧实用,不敢私藏,特此共享出来. 以前也看过很多博友关于对象池的总结分享,但是世界这么大,这么复杂到底什么样 ...
- 【转】Unity3D 关于贝赛尔曲线,平滑曲线,平滑路径,动态曲线
http://tieba.baidu.com/p/2460036481 很多时候我们需要的并不是直线和折线,而是平滑的曲线,比如寻路系统,某些物体的曲线运动,都需要平滑曲线来保证效果,今天试了一下,通 ...
- unity3d 基于物理渲染的问题解决
最近1个月做了unity 次世代开发的一些程序方面的支持工作,当然也是基于物理渲染相关的,主要还是skyshop marmoset的使用吧,他算是unity4.x版本 PBR的优秀方案之一了但在使用以 ...
- Unity3d 基于物理渲染Physically-Based Rendering之specular BRDF
在实时渲染中Physically-Based Rendering(PBR)中文为基于物理的渲染它能为渲染的物体带来更真实的效果,而且能量守恒 稍微解释一下字母的意思,为对后文的理解有帮助,从右到左L为 ...
- Unity3d 基于物理渲染Physically-Based Rendering之实现
根据前文的例子http://blog.csdn.net/wolf96/article/details/44172243(不弄超链接了审核太慢)弄一下真正的基于物理的渲染逃了节课= =,弄了一下.公式和 ...
- GJM: Unity3D基于Socket通讯例子 [转载]
首先创建一个C# 控制台应用程序, 直接服务器端代码丢进去,然后再到Unity 里面建立一个工程,把客户端代码挂到相机上,运行服务端,再运行客户端. 高手勿喷!~! 完全源码已经奉上,大家开始研究吧! ...
- Unity3d基于Socket通讯例子(转)
按语:按照下文,服务端利用网络测试工具,把下面客户端代码放到U3D中摄像机上,运行结果正确. http://www.manew.com/thread-102109-1-1.html 在一个网站上看到有 ...
- unity3d 制造自己的水体water effect(二)
前篇:unity3d 制造自己的水体water effect(一) 曲面细分:Unity3d 使用DX11的曲面细分 PBR: 讲求基本算法 Unity3d 基于物理渲染Physically-Base ...
随机推荐
- jQuery表单校验
主要特性: 表单提交前对所有数据进行校验,不符合不让提交(validate) 如果表单校验不通过,自动focus到第一个错误的域 自动在控件后面显示错误提示内容(error message) 支持根据 ...
- 第一章jQuery基础
一.jQuert简介 1.什么是jQuery jQuery是javaScript的程序库之一,它是javaScript对象和实用函数的封装. jQuery是继Prototype之后又一个优秀的java ...
- 05-k8s调度器、预选策略、优选函数
目录 k8s调度器.预选策略.优选函数 节点选择过程 调度器 预选策略 优选函数 高级调度设置机制 node选择器/node亲和调度 pod亲和性 污点调度 Taints 与 Tolerations ...
- Presto Event Listener开发
简介 同Hive Hook一样,Presto也支持自定义实现Event Listener,用于侦听Presto引擎执行查询时发生的事件,并作出相应的处理.我们可以利用该功能实现诸如自定义日志记录.调试 ...
- isMemberOfClass、isKindOfClass原理分析
isMemberOfClass - 调用者必须是传入的类的实例对象才返回YES- 判断调用者是否是传入对象的实例,别弄反了,如 [s1 isMemberOfClass:p1] ,意思是s1是否是p1的 ...
- mule发布调用webservice
mule发布webservice 使用mule esb消息总线发布和调用webservice都非常精简,mule包装了所有操作,你只需要拖控件配置就可以,下面讲解mule发布: 1.下面是flow,h ...
- Linux内核实战(二)- 操作系统概览
不知道你有没有产生过这些疑问: 桌面上的图标到底是啥?凭啥我在鼠标上一双击,就会出来一些不可描述的画面?都是从哪里跑出来的? 凭什么我在键盘上噼里啪啦地敲,某个位置就会显示我想要的那些字符? 电脑怎么 ...
- 一文了解:Redis事务
Redis事务 事务提供了一种"将多个命令打包,一次性提交并按顺序执行"的机制,提交后在事务执行中不会中断.只有在执行完所有命令后才会继续执行来自其他客户的消息. Redis中的使 ...
- 【原创】TextCNN原理详解(一)
最近一直在研究textCNN算法,准备写一个系列,每周更新一篇,大致包括以下内容: TextCNN基本原理和优劣势 TextCNN代码详解(附Github链接) TextCNN模型实践迭代经验总结 ...
- 逆向破解之160个CrackMe —— 002-003
CrackMe —— 002 160 CrackMe 是比较适合新手学习逆向破解的CrackMe的一个集合一共160个待逆向破解的程序 CrackMe:它们都是一些公开给别人尝试破解的小程序,制作 c ...