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 ...
随机推荐
- java练习---9
//程序员:罗元昊 2017.10.22 package cn.lyh; import com.rupeng.game.GameCore; public class L implements Runn ...
- 用ECharts绘制Prometheus图表,实现类似Grafana的自定义Dashboard
大家一般都是用Grafana自定义Dashboard来监控Prometheus数据的,作者这次尝试用ECharts来绘制Prometheus数据图表,一方面可以减少依赖,另一方面可以将监控界面灵活 ...
- Git 的常用的命令
之前一直在使用SVN作为版本管理工具,现在项目要求使用Git,下面简单记录一下一些常用的命令.关于原理和使用方式的详细说明,具体教程参考的廖雪峰的git教程. 1. github 账号的申请. 2. ...
- 利用dockerfile 安装一个nginx-1.14.1
FROM docker.io/centos MAINTAINER jim 107420988@qq.com ENV TZ "Asia/Shanghai" #ENV TERM xte ...
- LVS + Keepalived + Nginx基于DR模式构建高可用方案
在大型网站中一般服务端会做集群,同时利用负载均衡器做负载均衡.这样有利于将大量的请求分散到各个服务器上,提升网站的响应速度.当然为了解决单点故障的问题,还会做热备份方案.这里演示利用LVS做负载均衡器 ...
- 七分钟理解什么是 KMP 算法
本文是介绍 什么是 BF算法.KMP算法.BM算法 三部曲之一. KMP算法 内部涉及到的数学原理与知识太多,本文只会对 KMP算法 的运行过程. 部分匹配表 .next数组 进行介绍,如果理解了这三 ...
- Mybatis整合Spring 使用
1.继承通用的Mapper<T>,必须指定泛型<T> 例如下面的例子: public interface UserInfoMapper extends Mapper<Us ...
- 【python-django后端开发】Redis缓存配置使用详细教程!!!
官方查阅资料:https://django-redis-chs.readthedocs.io/zh_CN/latest/ 1. 安装django-redis扩展包 1.安装django-redis扩展 ...
- 用 PYQT5 和 QT Dseingner 写的串口助手
最近公司做项目需要写串口助手,于是从网上找教程着手写了一下,基本的功能可以实现了,但是想要一个表盘的功能一直没有找到教程,有些遗憾.大神们会的话给指导指导 谢谢啦 ! 下边有源码的连接,欢迎大家下载 ...
- JavaScript数据结构——栈的实现与应用
在计算机编程中,栈是一种很常见的数据结构,它遵从后进先出(LIFO——Last In First Out)原则,新添加或待删除的元素保存在栈的同一端,称作栈顶,另一端称作栈底.在栈中,新元素总是靠近栈 ...