Godot 2D光照 - 物体自身阴影遮挡设置
-
记录一些在Godot中使用2D光照时遇到的问题和解决方法,本篇笔记包含以下内容:
- 控制物体是否受自身阴影影响的不同设置方法
- 理解光照相关的图层和各种Mask的作用
在Unity中,用来投射阴影的Shadow Caster 2D组件有一个
Self Shadows
属性用来控制阴影是否影响物体自身。Godot的光照遮挡器
LightOccluder2D
节点和TileSet
里都没有类似的选项,默认都是影响自身。例如下图中方块物体和黑色墙体都被自身投射出的阴影遮挡了。关于这个问题官方文档有这样一段说明:
LightOccluder2D 遵循常规的 2D 绘图顺序。这对于 2D 灯光而言非常重要,因为可以用来控制遮挡器是否应该遮挡精灵本身。
如果 LightOccluder2D 节点是精灵的同级节点,并且场景树中的遮挡器被放在精灵的下方,会遮挡住精灵本身。
如果 LightOccluder2D 节点是一个精灵的子节点,如果在 LightOccluder2D 节点中禁用了 Show Behind Parent(显示在父级之后)这个遮挡器将遮挡住精灵本身(该选项默认禁用)。
真的有用吗?以下是在4.4版本中测试的情况,分别是遮挡器位于精灵下方、上方、作为精灵的子节点并启用
Show Behind Parent
。不难发现完全没有效果,就算有用这个方法也没法用于
TileSet
。所以这里总结一些比较普遍的解决方法,顺便介绍2D光照中各种Mask的作用与对应关系。前两种方法来自Catlike Coding的教程,不同的方法各有优缺点,在效果细节上也有差别。
方法一 设置Cull Mode
可以实现“物体不被自身阴影遮挡,可被其他物体的阴影遮挡”的效果。
将
LightOccluder2D
节点的Cull Mode
属性值设置为ClockWise
或CounterClockWise
,取决于顶点顺序,可以两个都试一下看哪个有效果。规律是如果顶点按逆时针排列则设置ClockWise
,反之CounterClockWise
,正好跟顶点顺序反过来。这个设置控制了遮挡形状是从内部还是外部投射阴影。如果编辑器中Sprite被遮挡,可以调整
Sprite2D
和LightOccluder2D
在场景树中的顺序,实际运行是没有区别的。这样就做到了物体不被自身阴影遮挡,会被其他物体和墙体的阴影遮挡。
TileSet
同样可以这样设置,选择TileMapLayer
节点并打开编辑器底部的TileSet
面板,按图中步骤操作即可。可以发现TileSet
使用了类似的逻辑实现。设置后墙体会变得和物体一样,不受自身投射出阴影的影响,会被其他墙体和物体的阴影遮挡。
但效果并不理想,视觉上墙体应该是一个整体,而实际上每一块墙体都是单独的遮挡器,互相之间会遮挡,导致看起来很奇怪,而且数量较多的情况下对性能也会有影响。
教程里的目标效果是墙体不需要被照亮,即默认被自身阴影遮挡,但如果墙体需要照亮,这样设置满足不了要求,必须将墙体的遮挡器合并,类似这样:
很可惜目前Godot没有提供直接合并遮挡器的功能,上面是手动创建的,不适合大型复杂场景或者需要动态修改的情况,在这个提案里有提到可以使用2D导航/碰撞烘焙来实现,之后尝试如果可行再来补充。
方法二 使用两个光源
可以实现“物体不被自身阴影遮挡,不被其他物体的阴影遮挡,会被墙体的阴影遮挡,墙体被自身阴影遮挡”的效果。
回退方法一中的所有修改,回到初始状态。把场景中的光源复制一份。
将光源2的
Range Item Cull Mask
属性改为2,同时把Shadow Item Cull Mask
属性也改为2。这一步意味着这个光源专门用来照亮
Light Mask
属性为2的物体,同时只会对Occluder Light Mask
属性为2的物体投射出阴影。这一堆Mask看着有些头痛,总之先按步骤操作,之后会详细介绍每个Mask的作用和它们之间的关系,为什么这样设置就能有效果。
然后将物体Sprite的
Light Mask
属性设置为2,LightOccluder2D
节点保持原样不需要调整。这一步让物体的Sprite将只会受到光源2的影响,呈现出的效果是物体不再被自身阴影遮挡,也不会被其他物体的阴影遮挡。
可以发现此时物体也不会被墙体投射出的阴影遮挡,如果需要则再做一项设置,在墙体
TileMapLayer
使用的TileSet
中,将Rendering
->Occlusion Layers
下墙体Tile使用的遮挡图层的Light Mask
改为1与2同时点亮。这一步让墙体在接受到光源1、光源2的光照时都投射出阴影,通过光源2投射出的阴影将会覆盖在物体上。
这种方法缺点很明显,需要双倍数量的光源,对性能有影响,适合低分辨率的像素游戏和光源较少的情况。
如果只需要控制物体阴影自身遮挡,对物体和墙体之间的阴影遮挡没有要求,又不想使用两个光源,这种情况可以通过设置Mask来实现。
理解各种Mask的作用
官方文档中有时将Mask翻译为“遮罩”,有时不翻译。个人觉得“掩码”更贴切一些,类似计算机网络里的“子网掩码(Subnet Mask)”,都是用来做位运算。
2D节点和UI控件节点都继承自
CanvasItem
节点,在CanvasItem
中有Light Mask
和Visibility Layer
两个属性。它们对应项目设置里2D Render的图层,共有20个,对应Mask中的20位。
是否渲染:Visibility Layer 与 Canvas Cull Mask
Visibility Layer
决定了物体位于哪个/哪些渲染图层,在视口Viewport
中有一个与之对应的属性Canvas Cull Mask
,用来控制这个视口渲染哪些图层,默认全部点亮,即渲染所有图层。在运行时我们的场景会被放在一个名为root的窗口
Window
节点下,而Window
正是继承自Viewport
,也就是说默认有一个渲染所有图层的视口。例如物体A的
Visibility Layer
点亮图层1,物体B的Visibility Layer
点亮图层2、3、5,视口的Canvas Cull Mask
点亮图层3、6。用视口的
Canvas Cull Mask
值分别与物体的Visibility Layer
做与运算,结果不为0时表示需要渲染:物体A是否渲染 = 0000 0000 0000 0000 0001 & 0000 0000 0000 0010 0100 = 0000 0000 0000 0000 0000 = 不渲染
物体B是否渲染 = 0000 0000 0000 0001 0110 & 0000 0000 0000 0010 0100 = 0000 0000 0000 0000 0100 = 渲染
注意物体是否渲染还会受到其父物体的影响,如果父物体不渲染,子物体也不会渲染,但可以勾选
CanvasItem
的Top Level
属性来取消这个限制。Godot原生支持多窗口,有基于这个特性开发独特玩法的游戏,例如《Windowkill》,这点是Unity做不到的。
是否计算光照:Light Mask 与 Range Item Cull Mask
类似地,
CanvasItem
中的Light Mask
属性用来设置物体属于哪个/哪些光照图层,在2D光源Light2D
中的Range
组下有一个Item Cull Mask
与之对应,用来控制这个光源在哪些图层上计算光照。例如物体A、B都在光源范围内,物体A的
Light Mask
点亮图层1,物体B的Light Mask
点亮图层2、3、5,光源的Range Item Cull Mask
点亮图层3、6,那么只有物体B会被照亮。是否计算阴影:Occluder Light Mask 与 Light Mask 与 Shadow Item Cull Mask
阴影有一些不同,它有三个属性参与控制,其实也很好理解,因为有投射出阴影的物体和接收阴影的物体,加上光源就是三个。
对于投射阴影的物体,
LightOccluder2D
的Occluder Light Mask
属性用来设置遮挡器属于哪个/哪些阴影图层;TileSet则是Rendering
->Occlusion Layers
里图层项的Light Mask
属性。对于接收阴影的物体,还是
CanvasItem
中的Light Mask
属性,也就是说它既决定了物体的光照图层,也决定了阴影图层。在2D光源
Light2D
中的Shadow
组下的Item Cull Mask
与它们对应,用来控制这个光源在哪些图层上计算阴影。例如物体A的
Light Mask
为1,物体B的Light Mask
为2,遮挡器的Occluder Light Mask
为3,光源的Range Item Cull Mask
为1、2,Shadow Item Cull Mask
为1、3,那么物体A、B会被照亮,遮挡器投射出的阴影会影响物体A,不影响物体B。方法三 划分图层
可以实现“单独控制某个图层的物体是否被同图层物体的阴影遮挡”的效果,但对于不同图层物体之间的阴影遮挡不好控制。
将场景中的物体和遮挡器划分到不同的图层,例如地板、墙体、墙体遮挡器、物体、物体遮挡器,可以在项目设置中给图层命名。
接着将各个物体的
Light Mask
属性都设置到对应的图层,遮挡器的Occluder Light Mask
也一样。假设要实现物体和墙体都不被自身阴影遮挡,那么地板、墙体、物体图层都可以被照亮,即光源的
Range Item Cull Mask
点亮1、2、4图层;地板需要接收阴影,墙体、物体不需要接收阴影,墙体遮挡器、物体遮挡器需要投射阴影,即光源的Shadow Item Cull Mask
点亮1、3、5图层。在此基础上,如果要做到方法二的最终效果,物体被墙体的阴影遮挡,这种情况还是得使用两个光源。
将光源复制一份,光源1不照亮物体物体,阴影和之前一样;光源2只需要照亮物体,墙体遮挡器投射阴影,物体接收阴影。
参考与素材来源:True Top-Down 2D by Catlike Coding