参考
https://docs.cocos.com/creator/3.3/manual/zh/concepts/scene/shadow.html
在 3D 世界中,光与影一直都是极其重要的组成部分,它们能够丰富整个环境,质量好的阴影可以达到以假乱真的效果,并且使得整个世界具有立体感。
Creator 3.0 目前支持 Planar 和 ShadowMap 两种阴影类型。
关于ShadowMap理论部分,可以参考图形学笔记九 阴影 光追一 求交
一、Planar简介
参考
使用顶点投射的方法制作实时阴影
【Unity Shader】平面阴影(Planar Shadow)
1.概述
阴影是计算机图形学中一个很重要的部分,阴影的加入使得物体更加具有立体感,也有助于我们理解物体间的相互位置关系和大小。
实时阴影的实现方法有很多种,shadowMap适用性最好,但性能开销也大,有时候我们的项目其实并不需要那么通用的阴影,我们只需要一个“适用某些特定场合”的一个“看起来正确”的实时阴影。
本文所说的,就是一种利用顶点投射的方法实现的实时阴影技术,在一些阴影质量要求不高,地面平整的项目是一个非常合适的方案,现在很火的手游《王者荣耀》就用了类似的技术。
(经提醒,这个技术叫平面投影阴影(Planar Projected Shadows)技术,由Jim Blinn 1988年提出。http://www.twinklingstar.cn/2015/1717/tech-of-shadows/#21_Blinns)
2.推导
说到求投影,我当时首先想到的就是用投影矩阵,后来经群友提醒,其实可以更进一步简化为求相似三角形,这样理解起来似乎还更简单些,以下是推导过程,为了简化计算,我们在二维空间内进行推导。
根据已知条件,我们可以得到一个这样的题目:已知平面坐标系内一个单位向量L(Lx,Ly),坐标系内一点M(Mx,My),求点M沿着L方向 在y = h上的投影位置P,如下图所示:
3.优缺点
这种平面阴影的优点是性能消耗小,阴影品质较高,简单好实现,非常适合MOBA类、俯视视角类型的游戏(角色和相机有一定距离)。
但是此方法的局限性也很大,最明显的缺点是阴影只投影在特定平面上,会插到墙体等障碍物中。另外它模拟的并不是真实的物理阴影和实际差别很大,阴影边缘很硬,真实的边缘会有虚化,如果镜头距离较近的话缺陷就会比较明显。
二、Shadow Map简介
参考
Shadow Map阴影贴图小讲
作为目前绝大多数电子游戏使用的阴影技术,我们来看一下shadow mapping的原理,优缺点都有哪些。这篇文章主要讲解Directional Light平行光产生的阴影, Omnidirectional light(也就是点光源,具体分为point light和spot light)产生的阴影放在下一篇中。
我们先讲原理,拿OpenGL举例,不谈具体的代码,聊聊大概的框架和偏原理的数学,以及实现过程中出现的问题,问题产生的原因和相对应的优化方案。
1.Shadow Mapping的原理
对于比较了解实时渲染的同学们应该知道 实时渲染中z-buffer的概念。这里简单的讲一下z-buffer是什么。z,取于3D场景中xyz坐标中的z。在将一个3D场景空间的三角形转换在2D图形空间的过程中,保留每个像素点的深度值储存起来,这就是z-buffer,这个过程在vertex shader中完成。总结来说,zbuffer就是每个像素点在场景空间的深度,通过比较每个像素点的深度,可以确定哪些像素点被遮挡而不需要被渲染,可以很大的提高渲染的效率。
介绍完z-buffer,现在开始讲讲Shadow Mapping的原理。原理其实非常简单,在确定遮挡关系的时候, z-buffer的深度值是相对于摄像机镜头,也就是眼睛所见来确定物体是否被遮挡,这个很容易理解。眼睛能看不见的地方,当然被挡住了。那我们换个角度想想,阴影是如何产生的?
阴影其实就是光线看不见的地方。因此,如果我们把摄像机放到光源,那么光能看见的地方,亮度就会高;而光看不见的地方,就是阴影。所以,通过把摄像机放到光源出获得的每个像素点的深度值z-buffer保存起来得到一张类似于map的缓冲,就是Shadow Map。
当我们有了这一张Shadow Map以后,我们回到摄像机空间中,把每个点在光照空间的坐标求出来,获得每个点的深度值z,在fragment shader中通过blinn-phong光照模型来渲染场景。这就是Shadow Map的大概原理。
图片来自http://LearnOpenGL.com
这张图可以很好地帮助理解。我们通过矩阵T来获得每个点在光照空间的坐标。如左图,P点是摄像机可以看到的点,因此需要通过T(P)转化到光照空间中,从而获得深度值0.9。而C点摄像机捕捉不到,但是可以直接在光照空间中获得深度值0.4。通过比较两个点深度值,我们判断出P点处在阴影当中。
以下参考
7.2 Shadow Map
Shadow Map实现原理:首先将光源想像成一个相机对场景做第一次渲染,将光源所能看到的所有场景物体的深度写入z-buffer中,如果光源为定向光,如太阳,光源将对观察者所看到的视锥体内的所有物体投射阴影, 光源使用正交投影,投影的视图的宽(x),高(y)要设置得足够大来包含所有可视物体。如果光源为局部(Local)光源,也需要做类似调整,如果局部光源离投影阴影的物体足够远,一个视锥体就可以包含这些物体, 特别地,如果 局部光源是一盏聚光灯(spotlight),它本身与有一个视锥关联,视锥外的一切物体都认为是不会受光照影响。
并不是场景中的所有物体都要渲染到shadow map中,只有投影阴影的物体才需要被渲染。例如,如果已知地面只接收阴影而不投影阴影,那么就不需要将其渲染到shadow map。
三、cocos中开启阴影
1.在 层级管理器 中选中 Scene,然后在 属性检查器 的 shadows 组件中勾选 Enabled 属性。
2.在 层级管理器 中选中需要显示阴影的 3D 节点,然后在 属性检查器 的 MeshRenderer 组件中将 ShadowCastingMode 属性设置为 ON。
若阴影类型是 ShadowMap,还需要将 MeshRenderer 组件上的 ReceiveShadow 属性设置为 ON。注意:Planar 类型的阴影只有投射在平面上才能正常显示,不会投射在物体上,也就是说 MeshRenderer 组件中的 ReceiveShadow 属性是无效的。
注意:如果阴影无法正常显示,需要调整一下方向光的照射方向。
3.Planar shadow
Planar 阴影类型一般用于较为简单的场景。
- Enabled 是否开启阴影效果
- Type 阴影类型
- Saturation 调节阴影饱和度
- ShadowColor 设置阴影颜色
- Normal 垂直于阴影的法线,用于调整阴影的倾斜度
- Distance 阴影在法线的方向上与坐标原点的距离
4.ShadowMap
ShadowMap 是以光源为视点来渲染场景的。从光源位置出发,场景中看不到的地方就是阴影产生的地方。
- Enabled 是否开启阴影效果
- Type 设置阴影类型
- Saturation 调节阴影饱和度
- Pcf 设置阴影边缘反走样等级,目前支持 HARD、SOFT、SOFT_2X,详情可参考下文 PCF 软阴影 部分的介绍。
- MaxReceived 最多支持产生阴影的光源数量,默认为 4 个,可根据需要自行调整
- Bias 设置阴影偏移值,防止 z-fiting
- NormalBias 设置法线偏移值,防止曲面出现锯齿状
- ShadowMapSize 设置阴影纹理大小,目前支持 Low_256x256、Medium_512x512、High_1024x1024、Ultra_2048x2048 四种精度的纹理
- InvisibleOcclusionRange 设置 Camera 可见范围外的物体产生的阴影是否投射到可见范围内,若需要则调大该值即可
- ShadowDistance 设置 Camera 可见范围内显示阴影效果的范围,阴影质量与该值的大小成反比
- FixedArea 设置是否通过手动设置下列属性来控制 Camera 可见范围内显示阴影效果的范围,详情可参考下文 FixedArea 模式 部分的介绍
- Near 设置主光源相机的近裁剪面
- Far 设置主光源相机的远裁剪面
- OrthoSize 设置主光源相机的正交视口大小
注意:从 v3.3 开始,属性检查器 中阴影的 Linear、Packing 项被移除,Creator 将自动判断硬件能力,并选用最优方式进行阴影渲染。
ShadowMap 在开启了物体 MeshRenderer 组件上的 ReceiveShadow 后,就会接收并显示其它物体产生的阴影效果。
ShadowMap 一般用于要求光影效果比较真实,且较为复杂的场景。但不足之处在于如果不移动光源,那么之前生成的 Shadow Map 就可以重复使用,而一旦移动了光源,那么就需要重新计算新的 ShadowMap。
5.PCF 软阴影
百分比渐近过滤(PCF)是一个简单、常见的用于实现阴影边缘反走样的技术,通过对阴影边缘进行平滑处理来消除阴影贴图的锯齿现象。原理是在当前像素(也叫做片段)周围进行采样,然后计算样本跟片段相比更接近光源的比例,使用这个比例对散射光和镜面光成分进行缩放,然后再对片段着色,以达到模糊阴影边缘的效果。
目前 Cocos Creator 支持 硬采样、4 倍采样(SOFT 模式)、9 倍采样(SOFT_2X 模式),倍数越大,采样区域越大,阴影边缘也就越柔和。
6.FixedArea 模式
FixedArea 模式用于设置是否手动控制 Camera 可见范围内显示阴影效果的范围:
- 若不勾选该项(默认),则引擎会使用和 CSM(级联阴影算法)相同的裁切流程和相机计算,根据 Camera 的方向和位置来计算阴影产生的范围。
- 若勾选该项,则根据手动设置的
Near
、Far
、OrthoSize
属性来控制阴影产生的范围。
四、官方示例test-case-3d中的shadow-map
五、问题
1.关于3.0 ShadowMap的疑问
离灯很近的地方不产生阴影
2.一个bug,Creator3.0的地形Terrain 不能接收阴影,角色无法在地形上显示阴影
Planar只能在XOZ面上投,如果的地形的高度(Y轴坐标)超过了0,就看不到了。
3.请问游戏中如何关闭阴影
目前强行关闭了
let planarShadows = director.getRunningScene()._renderScene.planarShadows
planarShadows.enabled = false
planarShadows.onGlobalPipelineStateChanged()
planarShadows[’_pendingModels’].length = 0;