Unity实现类Terraria/Starbound的2D光影效果技术问询
Unity 2D 实现 Terraria/Starbound 风格光影的解决方案
我完全懂你现在的困境——给一堆Sprite挨个加漫反射材质、堆一堆光源,结果要么是光影效果不对(比如火把光没被方块挡住、室内光照分层做不出来),要么是帧率直接崩了。这类像素沙盒游戏的光影逻辑和Unity原生的2D光照思路不太一样,得换个路子来搞,下面给你几个实用的方案,兼顾效果和性能:
方案一:优化原生Light2D 快速入门
这是最容易上手的方案,基于Unity自带的2D光照系统改造,不用写太多代码:
- 统一材质,减少Draw Call:别给每个Sprite单独加漫反射材质了,全换成Unity内置的
Sprites/Litshader,把所有Sprite的材质批量替换成这个。这个Shader支持批量接收光照,能大幅降低渲染开销。 - 光源设置适配需求:
- 火把这类点光源用
Point Light 2D,开启Soft Shadows,调整Shadow Angle和Shadow Distance,让光线碰到方块时快速衰减、形成清晰的遮挡边界;把光源的Culling Mask只勾选地面/墙壁层,避免浪费性能在不需要光照的物体上。 - 顶层定向光(比如日光):单独开一个
Directional Light 2D,把Culling Mask排除室内层(你可以给室内物体单独建个Layer),这样室外能被日光照亮,室内自动屏蔽日光,配合火把光就能做出分层的室内光影。
- 火把这类点光源用
- 性能兜底优化:把同类型的Sprite打包成
Sprite Atlas,合并Draw Call;开启Light2D的Per-Pixel Lighting只在玩家附近区域,远处用性能更低的Vertex Lighting;用对象池复用火把光源,远处的火把自动降低强度或者禁用光源。
方案二:光线投射实现硬阴影(还原Terraria质感)
如果想要更精准的“光线遇方块就被完全挡住”的硬阴影效果,得用实时射线检测来生成光影遮罩:
- 核心逻辑:每个光源发射多条射线,检测场景中的碰撞体,把未被遮挡的区域连成多边形,作为光照的可视范围。
- 具体实现步骤:
- 给光源创建一个圆形的Sprite(或者动态生成Mesh),作为光照的基底。
- 用
Physics2D.Raycast从光源中心向32-64个方向发射射线(数量越多阴影越平滑,32条足够),记录每个射线的碰撞点(没碰到物体就取最大距离的点)。 - 把这些点按顺序连接成多边形,裁剪掉被方块挡住的部分,然后更新光照Sprite的形状或者Mesh的顶点。
- 给这个光照区域用半透明的发光材质,设置成
Overlay渲染模式,叠加在场景上。
- 性能优化:只在光源移动、场景方块变化时重新计算射线,平时缓存结果;用对象池管理光照的Mesh/Sprite组件;远处的光源减少射线数量(比如16条)或者直接禁用。
方案三:静态烘焙+动态光源混合(大型地图最优解)
如果你的地图很大,全用实时光源肯定扛不住,那就结合静态光照烘焙和动态光源:
- 静态场景烘焙:把墙壁、地面这类不会动的静态物体设置为
Static,开启Unity的2D Lightmapping,把日光、环境光这类静态光源烘焙到光照贴图里。这样静态物体的光照不需要实时计算,性能开销直接降下来。 - 动态光源处理:火把、玩家手持光源这类动态物体,用方案二的光线投射方案来做实时光影,只处理动态部分的光照,和烘焙的静态贴图叠加,效果完全不输全实时,性能却能扛住大地图。
避坑提醒
- 给Tilemap加碰撞时,一定要用
Composite Collider 2D+Rigidbody 2D(设置为Static),否则射线检测会漏判方块。 - 别贪多开光源,哪怕优化过,超过20个实时光源也会有明显性能压力,尽量用对象池复用,远处的光源做降级处理。
- 发光材质尽量用简单的Unlit Shader,别加复杂的漫反射计算,光影的质感靠形状和透明度来体现,不是靠复杂着色器。
内容的提问来源于stack exchange,提问作者Derk Jan Speelman
相关产品推荐
相关产品推荐

