[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属性值设置为ClockWiseCounterClockWise,取决于顶点顺序,可以两个都试一下看哪个有效果。规律是如果顶点按逆时针排列则设置ClockWise,反之CounterClockWise,正好跟顶点顺序反过来。这个设置控制了遮挡形状是从内部还是外部投射阴影。

如果编辑器中Sprite被遮挡,可以调整Sprite2DLightOccluder2D在场景树中的顺序,实际运行是没有区别的。

这样就做到了物体不被自身阴影遮挡,会被其他物体和墙体的阴影遮挡。

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 MaskVisibility 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 = 渲染

注意物体是否渲染还会受到其父物体的影响,如果父物体不渲染,子物体也不会渲染,但可以勾选CanvasItemTop 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

阴影有一些不同,它有三个属性参与控制,其实也很好理解,因为有投射出阴影的物体和接收阴影的物体,加上光源就是三个。

对于投射阴影的物体,LightOccluder2DOccluder 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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容