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用最少循环求两个数组的交集、差集、并集
import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List ...
- FormLayout and FormData
FormLayout通过为小窗口部件创建四边的Form附加值(attachment)来进行工作,并且把这些Form附加值存储在布局数据中.一个附加值让一个小窗口部件指定的一边粘贴(attach)到父C ...
- html的一些基本语法学习与实战
其实在学校前端开始之前,问过自己为什么要学,因为自己学的比较杂,直到现在刚刚毕业出来工作了,才明确了方向了,要往嵌入式方向走,但是随着时代的发展,在编程和智能硬件结合的越来越紧密,特别是物联网这一块, ...
- iOS的录屏功能
iOS的录屏功能其实没什么好说的,因为网上的教程很多,但是网上的Demo无一例外几乎都有一个bug,那就是iPad上会出现闪退,这也体现了国内的教程文档的一个特点,就是抄袭,教程几乎千篇一律,bug也 ...
- American daily English notes (enlarged edition): A review
Life English is the most pragmatic kind of English when one wants to associate with foreigner friend ...
- 0x03 前缀和与差分
前缀和 [例题]BZOJ1218 激光炸弹 计算二位前缀和,再利用容斥原理计算出答案即可. #include <iostream> #include <cstdio> #inc ...
- gRPC【RPC自定义http2.0协议传输】
gRPC 简介 gRPC是由Google公司开源的高性能RPC框架. gRPC支持多语言 gRPC原生使用C.Java.Go进行了三种实现,而C语言实现的版本进行封装后又支持C++.C#.Node.O ...
- 2月9日 《Java 8实战》读后感
第一部分 基础知识 第3章 Lambda表达式 使用函数式接口 Predicate Consumer Function 第二部分 函数式数据处理 第4章 引入流 第5章 使用流 第6章 用流收集数据 ...
- HDU 4635 (完全图 和 有向图缩点)
题目链接:HDU 4635 题目大意: 给你一个有向图,加有向边,使得这个图是简单有向图.问你最多加多少条有向边. 简单有向图: 1.不存在有向重边. 2.不存在图循环.(注意是不存在 “图” 循环 ...
- 洛谷 P5367 【模板】康托展开(数论,树状数组)
题目链接 https://www.luogu.org/problem/P5367 什么是康托展开 百度百科上是这样说的: “康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩. ...