抛砖引玉:简谈2d游戏中利用法线贴图实现伪3d光影效果

不太会写文章,就简单谈谈要点和问题,希望各位大佬也能不吝赐教.

分两部分写,一是sprite上的明暗,二是sprite在场景里投影。

gw7sm-ezfx0
(sprite上的明暗效果,影子效果在后面的视频里)

  1. sprite上的明暗

光照模型参考一般的模型即可。常见的计算方法包括使用光线追踪算法,根据光源和表面法线之间的夹角来计算光照强度。具体可以参考大佬的文章基础光照模型笔记! - Creator 2.x - Cocos中文社区。这里的主要不同是:

a. 我们需要一个法线贴图来给出输入的normal向量值。ChatGPT说可以使用专业的图像编辑软件,如Photoshop或GIMP,(如果不是这样的请美术大佬指出)来绘制法线贴图。我这里是用blender直接渲染得到的,具体请参考这个链接 [https://www.reddit.com/r/blender/comments/10kq5gq/render_2d_png_of_3d_object_with_normal_map_colors/]。


有了法线贴图,我的实现方式是在sprite下面再挂一个不激活的子节点带有sprite组件,然后代码里就可以读入uvmap了,比如:

_mat.setProperty("uvmap",node.getComponent(Sprite).spriteFrame.texture)

b. 在2d游戏里的世界坐标和在法线贴图里的3d坐标之间可能需要相互转化。比如,对于2.5d等距视图,在cocos世界坐标系下y方向来的光线,实际上是法线贴图坐标系上x和y方向混合的向量。对于某些情况这个转换会相当必要。还是对于2.5d等距视图,地面的法线向量的方向总是(0,0,1),这个时候如果没有坐标变换,从Cocos拿来的2d光源方向总是在xy平面里,那么这两者之间的dotproduct就总是0,那么地面就永远不会被照亮了。参考线性代数,两者之间的转换矩阵可以用一个矩阵里的基向量们组成。

  1. sprite在场景中的投影

我试了两种实现方式,都有各自的问题。

  • 第一种是给每个子节点下面挂影子子节点,如果有n个光源,那一个物体就有n个影子,对应就有n个影子子节点。每个影子子节点是一个足以覆盖全屏的纯黑sprite,根据光源位置和物体在场景地面的形状,可以由射线确定阴影的多边形行政,在shader里擦除多边形外的部分即可。这个实现比较简单,而且能够表现正确的出遮挡关系:如果物体a在物体b之前,那物体a的影子也应该可以覆盖物体b。但这个实现的问题也很明显:光源数量多或者节点数量多的时候,会产生大量的阴影子节点,会影响效率。

  • 第二种方法是把所有的影子放在一个节点里绘制。这里,一个全局的,足以覆盖全屏的纯黑sprite用来绘制所有的影子。同样的方法读入所有阴影区域的多边形们之后,在全局的阴影节点上把所有多边形抠出来就可以了。我开始的时候觉得这个实现会比第一种快很多,但实测并不明显,可能是shader里的大量条件判断拖累了效率,另外,这个实现方法就不能正确表达遮挡关系了。

最后放一个目前的实现效果[https://www.bilibili.com/video/BV1ix4y197R7]

2赞

感谢分享!3D模型我知道怎么生成法线,那么2D的图片怎么绘制法线呢

感觉就只能手绘了?按照xyz方向对应rgb值画吧,不知道有没有什么别的更方便的方法了

这里有个收费的软件,不知道能否在3.x里直接用
https://www.codeandweb.com/spriteilluminator

1赞

厉害,先MARK后看

先mark一下!

mark一下