<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Godot 2D光照 - 物体自身阴影遮挡设置]]></title><description><![CDATA[<p dir="auto">记录一些在Godot中使用2D光照时遇到的问题和解决方法，本篇笔记包含以下内容：</p>
<ul>
<li>控制物体是否受自身阴影影响的不同设置方法</li>
<li>理解光照相关的图层和各种Mask的作用</li>
</ul>
<p dir="auto">在Unity中，用来投射阴影的<a href="https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@14.0/manual/2DShadows.html" rel="nofollow ugc">Shadow Caster 2D组件</a>有一个<code>Self Shadows</code>属性用来控制阴影是否影响物体自身。</p>
<p dir="auto"><img src="/assets/uploads/files/1748572975839-pasted-image-20250504183337.png" alt="Pasted image 20250504183337.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">Godot的光照遮挡器<code>LightOccluder2D</code>节点和<code>TileSet</code>里都没有类似的选项，默认都是影响自身。例如下图中方块物体和黑色墙体都被自身投射出的阴影遮挡了。</p>
<p dir="auto"><img src="/assets/uploads/files/1748572985466-pasted-image-20250504220112.png" alt="Pasted image 20250504220112.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">关于这个问题<a href="https://docs.godotengine.org/en/stable/tutorials/2d/2d_lights_and_shadows.html#occluder-draw-order" rel="nofollow ugc">官方文档</a>有这样一段说明：</p>
<blockquote>
<p dir="auto"><strong>LightOccluder2D 遵循常规的 2D 绘图顺序</strong>。这对于 2D 灯光而言非常重要，因为可以用来控制遮挡器是否应该遮挡精灵本身。</p>
<p dir="auto">如果 LightOccluder2D 节点是精灵的<em>同级节点</em>，并且场景树中的遮挡器被放在精灵的下方，会遮挡住精灵本身。</p>
<p dir="auto">如果 LightOccluder2D 节点是一个精灵的子节点，如果在 LightOccluder2D 节点中禁用了 <strong>Show Behind Parent</strong>（显示在父级之后）这个遮挡器将遮挡住精灵本身（该选项默认禁用）。</p>
</blockquote>
<p dir="auto">真的有用吗？以下是在4.4版本中测试的情况，分别是遮挡器位于精灵下方、上方、作为精灵的子节点并启用<code>Show Behind Parent</code>。</p>
<p dir="auto"><img src="/assets/uploads/files/1748572993958-pasted-image-20250520085233.png" alt="Pasted image 20250520085233.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">不难发现完全没有效果，就算有用这个方法也没法用于<code>TileSet</code>。</p>
<p dir="auto">所以这里总结一些比较普遍的解决方法，顺便介绍2D光照中各种Mask的作用与对应关系。前两种方法来自Catlike Coding的<a href="https://catlikecoding.com/godot/true-top-down-2d/4-light-and-shadow/" rel="nofollow ugc">教程</a>，不同的方法各有优缺点，在效果细节上也有差别。</p>
<h1>方法一 设置Cull Mode</h1>
<p dir="auto">可以实现“物体不被自身阴影遮挡，可被其他物体的阴影遮挡”的效果。</p>
<p dir="auto">将<code>LightOccluder2D</code>节点的<code>Cull Mode</code>属性值设置为<code>ClockWise</code>或<code>CounterClockWise</code>，取决于顶点顺序，可以两个都试一下看哪个有效果。规律是如果顶点按逆时针排列则设置<code>ClockWise</code>，反之<code>CounterClockWise</code>，正好跟顶点顺序反过来。这个设置控制了遮挡形状是从内部还是外部投射阴影。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573003603-pasted-image-20250504180335.png" alt="Pasted image 20250504180335.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">如果编辑器中Sprite被遮挡，可以调整<code>Sprite2D</code>和<code>LightOccluder2D</code>在场景树中的顺序，实际运行是没有区别的。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573009448-pasted-image-20250504175706.png" alt="Pasted image 20250504175706.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">这样就做到了物体不被自身阴影遮挡，会被其他物体和墙体的阴影遮挡。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573015330-pasted-image-20250504180238.png" alt="Pasted image 20250504180238.png" class=" img-responsive img-markdown" /></p>
<p dir="auto"><code>TileSet</code>同样可以这样设置，选择<code>TileMapLayer</code>节点并打开编辑器底部的<code>TileSet</code>面板，按图中步骤操作即可。可以发现<code>TileSet</code>使用了类似的逻辑实现。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573024789-pasted-image-20250504181841.png" alt="Pasted image 20250504181841.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">设置后墙体会变得和物体一样，不受自身投射出阴影的影响，会被其他墙体和物体的阴影遮挡。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573030556-pasted-image-20250504182412.png" alt="Pasted image 20250504182412.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">但效果并不理想，视觉上墙体应该是一个整体，而实际上每一块墙体都是单独的遮挡器，互相之间会遮挡，导致看起来很奇怪，而且数量较多的情况下对性能也会有影响。</p>
<p dir="auto">教程里的目标效果是墙体不需要被照亮，即默认被自身阴影遮挡，但如果墙体需要照亮，这样设置满足不了要求，必须将墙体的遮挡器合并，类似这样：</p>
<p dir="auto"><img src="/assets/uploads/files/1748573035075-pasted-image-20250520113037.png" alt="Pasted image 20250520113037.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">很可惜目前Godot没有提供直接合并遮挡器的功能，上面是手动创建的，不适合大型复杂场景或者需要动态修改的情况，在<a href="https://github.com/godotengine/godot-proposals/discussions/7934" rel="nofollow ugc">这个提案</a>里有提到可以使用2D导航/碰撞烘焙来实现，之后尝试如果可行再来补充。</p>
<h1>方法二 使用两个光源</h1>
<p dir="auto">可以实现“物体不被自身阴影遮挡，不被其他物体的阴影遮挡，会被墙体的阴影遮挡，墙体被自身阴影遮挡”的效果。</p>
<p dir="auto">回退方法一中的所有修改，回到初始状态。把场景中的光源复制一份。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573040967-pasted-image-20250504221607.png" alt="Pasted image 20250504221607.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">将光源2的<code>Range Item Cull Mask</code>属性改为2，同时把<code>Shadow Item Cull Mask</code>属性也改为2。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573045903-pasted-image-20250504222054.png" alt="Pasted image 20250504222054.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">这一步意味着这个光源专门用来照亮<code>Light Mask</code>属性为2的物体，同时只会对<code>Occluder Light Mask</code>属性为2的物体投射出阴影。</p>
<p dir="auto">这一堆Mask看着有些头痛，总之先按步骤操作，之后会详细介绍每个Mask的作用和它们之间的关系，为什么这样设置就能有效果。</p>
<p dir="auto">然后将物体Sprite的<code>Light Mask</code>属性设置为2，<code>LightOccluder2D</code>节点保持原样不需要调整。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573051748-pasted-image-20250504220330.png" alt="Pasted image 20250504220330.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">这一步让物体的Sprite将只会受到光源2的影响，呈现出的效果是物体不再被自身阴影遮挡，也不会被其他物体的阴影遮挡。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573056217-pasted-image-20250504223727.png" alt="Pasted image 20250504223727.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">可以发现此时物体也不会被墙体投射出的阴影遮挡，如果需要则再做一项设置，在墙体<code>TileMapLayer</code>使用的<code>TileSet</code>中，将<code>Rendering</code> -&gt; <code>Occlusion Layers</code>下墙体Tile使用的遮挡图层的<code>Light Mask</code>改为1与2同时点亮。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573079060-pasted-image-20250504224117.png" alt="Pasted image 20250504224117.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">这一步让墙体在接受到光源1、光源2的光照时都投射出阴影，通过光源2投射出的阴影将会覆盖在物体上。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573084260-pasted-image-20250504225120.png" alt="Pasted image 20250504225120.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">这种方法缺点很明显，需要双倍数量的光源，对性能有影响，适合低分辨率的像素游戏和光源较少的情况。</p>
<p dir="auto">如果只需要控制物体阴影自身遮挡，对物体和墙体之间的阴影遮挡没有要求，又不想使用两个光源，这种情况可以通过设置Mask来实现。</p>
<h1>理解各种Mask的作用</h1>
<p dir="auto">官方文档中有时将Mask翻译为“遮罩”，有时不翻译。个人觉得“掩码”更贴切一些，类似计算机网络里的“子网掩码（Subnet Mask）”，都是用来做位运算。</p>
<p dir="auto">2D节点和UI控件节点都继承自<code>CanvasItem</code>节点，在<code>CanvasItem</code>中有<code>Light Mask</code>和<code>Visibility Layer</code>两个属性。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573089778-pasted-image-20250513104628.png" alt="Pasted image 20250513104628.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">它们对应项目设置里2D Render的图层，共有20个，对应Mask中的20位。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573101770-pasted-image-20250513104945.png" alt="Pasted image 20250513104945.png" class=" img-responsive img-markdown" /></p>
<h2>是否渲染：Visibility Layer 与 Canvas Cull Mask</h2>
<p dir="auto"><code>Visibility Layer</code>决定了物体位于哪个/哪些渲染图层，在视口<code>Viewport</code>中有一个与之对应的属性<code>Canvas Cull Mask</code>，用来控制这个视口渲染哪些图层，默认全部点亮，即渲染所有图层。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573107853-pasted-image-20250513105709.png" alt="Pasted image 20250513105709.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">在运行时我们的场景会被放在一个名为root的窗口<code>Window</code>节点下，而<code>Window</code>正是继承自<code>Viewport</code>，也就是说默认有一个渲染所有图层的视口。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573113378-pasted-image-20250513110220.png" alt="Pasted image 20250513110220.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">例如物体A的<code>Visibility Layer</code>点亮图层1，物体B的<code>Visibility Layer</code>点亮图层2、3、5，视口的<code>Canvas Cull Mask</code>点亮图层3、6。</p>
<p dir="auto">用视口的<code>Canvas Cull Mask</code>值分别与物体的<code>Visibility Layer</code>做与运算，结果不为0时表示需要渲染：</p>
<p dir="auto">物体A是否渲染 = 0000 0000 0000 0000 0001 &amp; 0000 0000 0000 0010 0100 = 0000 0000 0000 0000 0000 = 不渲染</p>
<p dir="auto">物体B是否渲染 = 0000 0000 0000 0001 0110 &amp; 0000 0000 0000 0010 0100 = 0000 0000 0000 0000 0100 = 渲染</p>
<blockquote>
<p dir="auto">注意物体是否渲染还会受到其父物体的影响，如果父物体不渲染，子物体也不会渲染，但可以勾选<code>CanvasItem</code>的<code>Top Level</code>属性来取消这个限制。</p>
</blockquote>
<blockquote>
<p dir="auto">Godot原生支持多窗口，有基于这个特性开发独特玩法的游戏，例如《Windowkill》，这点是Unity做不到的。</p>
</blockquote>
<h2>是否计算光照：Light Mask 与 Range Item Cull Mask</h2>
<p dir="auto">类似地，<code>CanvasItem</code>中的<code>Light Mask</code>属性用来设置物体属于哪个/哪些光照图层，在2D光源<code>Light2D</code>中的<code>Range</code>组下有一个<code>Item Cull Mask</code>与之对应，用来控制这个光源在哪些图层上计算光照。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573119745-pasted-image-20250513154420.png" alt="Pasted image 20250513154420.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">例如物体A、B都在光源范围内，物体A的<code>Light Mask</code>点亮图层1，物体B的<code>Light Mask</code>点亮图层2、3、5，光源的<code>Range Item Cull Mask</code>点亮图层3、6，那么只有物体B会被照亮。</p>
<h2>是否计算阴影：Occluder Light Mask 与 Light Mask 与 Shadow Item Cull Mask</h2>
<p dir="auto">阴影有一些不同，它有三个属性参与控制，其实也很好理解，因为有投射出阴影的物体和接收阴影的物体，加上光源就是三个。</p>
<p dir="auto">对于投射阴影的物体，<code>LightOccluder2D</code>的<code>Occluder Light Mask</code>属性用来设置遮挡器属于哪个/哪些阴影图层；TileSet则是<code>Rendering</code> -&gt; <code>Occlusion Layers</code>里图层项的<code>Light Mask</code>属性。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573126168-pasted-image-20250513155829.png" alt="Pasted image 20250513155829.png" class=" img-responsive img-markdown" /></p>
<p dir="auto"><img src="/assets/uploads/files/1748573131099-pasted-image-20250513160009.png" alt="Pasted image 20250513160009.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">对于接收阴影的物体，还是<code>CanvasItem</code>中的<code>Light Mask</code>属性，也就是说它既决定了物体的光照图层，也决定了阴影图层。</p>
<p dir="auto">在2D光源<code>Light2D</code>中的<code>Shadow</code>组下的<code>Item Cull Mask</code>与它们对应，用来控制这个光源在哪些图层上计算阴影。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573136059-pasted-image-20250513160656.png" alt="Pasted image 20250513160656.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">例如物体A的<code>Light Mask</code>为1，物体B的<code>Light Mask</code>为2，遮挡器的<code>Occluder Light Mask</code>为3，光源的<code>Range Item Cull Mask</code>为1、2，<code>Shadow Item Cull Mask</code>为1、3，那么物体A、B会被照亮，遮挡器投射出的阴影会影响物体A，不影响物体B。</p>
<h1>方法三 划分图层</h1>
<p dir="auto">可以实现“单独控制某个图层的物体是否被同图层物体的阴影遮挡”的效果，但对于不同图层物体之间的阴影遮挡不好控制。</p>
<p dir="auto">将场景中的物体和遮挡器划分到不同的图层，例如地板、墙体、墙体遮挡器、物体、物体遮挡器，可以在项目设置中给图层命名。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573145801-pasted-image-20250513161405.png" alt="Pasted image 20250513161405.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">接着将各个物体的<code>Light Mask</code>属性都设置到对应的图层，遮挡器的<code>Occluder Light Mask</code>也一样。</p>
<p dir="auto">假设要实现物体和墙体都不被自身阴影遮挡，那么地板、墙体、物体图层都可以被照亮，即光源的<code>Range Item Cull Mask</code>点亮1、2、4图层；地板需要接收阴影，墙体、物体不需要接收阴影，墙体遮挡器、物体遮挡器需要投射阴影，即光源的<code>Shadow Item Cull Mask</code>点亮1、3、5图层。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573151582-pasted-image-20250513162023.png" alt="Pasted image 20250513162023.png" class=" img-responsive img-markdown" /></p>
<p dir="auto"><img src="/assets/uploads/files/1748573159018-pasted-image-20250513164014.png" alt="Pasted image 20250513164014.png" class=" img-responsive img-markdown" /></p>
<p dir="auto">在此基础上，如果要做到方法二的最终效果，物体被墙体的阴影遮挡，这种情况还是得使用两个光源。</p>
<p dir="auto">将光源复制一份，光源1不照亮物体物体，阴影和之前一样；光源2只需要照亮物体，墙体遮挡器投射阴影，物体接收阴影。</p>
<p dir="auto"><img src="/assets/uploads/files/1748573164264-pasted-image-20250513165735.png" alt="Pasted image 20250513165735.png" class=" img-responsive img-markdown" /></p>
<p dir="auto"><img src="/assets/uploads/files/1748573167890-pasted-image-20250513165910.png" alt="Pasted image 20250513165910.png" class=" img-responsive img-markdown" /></p>
<blockquote>
<p dir="auto">参考与素材来源：<a href="https://catlikecoding.com/godot/true-top-down-2d/" rel="nofollow ugc">True Top-Down 2D</a> by <a href="%5Bcatlikecoding.com%5D(https://catlikecoding.com/)">Catlike Coding</a></p>
</blockquote>
]]></description><link>http://designhub.top/topic/76/godot-2d光照-物体自身阴影遮挡设置</link><generator>RSS for Node</generator><lastBuildDate>Tue, 08 Sep 2026 06:04:12 GMT</lastBuildDate><atom:link href="http://designhub.top/topic/76.rss" rel="self" type="application/rss+xml"/><pubDate>Fri, 30 May 2025 02:46:14 GMT</pubDate><ttl>60</ttl></channel></rss>