概述
在绘图函数的使用过冲中,已经总结出了CanvasShape
这样的图形类,并通过将其资源化,可以方便的用于自定义节点。
初期的CanvasShape
也加入了多边形阴影的绘制选项,不过是简单的复制+偏移形式。本篇在简单复制+偏移形式阴影的基础上,讨论了多边形的平行光阴影、点光源阴影等模拟原理和在Godot4.3中的实际求解。最后给出一个初步的既适用于凸多边形也适用于凹多边形的平行光阴影求解函数。
一些阴影类型与求解原理
偏移阴影
通过复制和偏移多边形一定距离,可以获得一个简单的阴影,让物体看起来悬浮了起来。
平行光阴影
模拟平行光照射的阴影,可以在复制和偏移多边形的基础上,进一步求原图形和偏移阴影两者的并集(一个凹多边形),再求这个凹多边形的凸多边形,就可以获得平行光阴影的多边形。
远距离时无法求并集的修正
但是这种方式在阴影偏移距离过大,原图形和阴影两者没有交集时,无法获得正确的并集。也就无法得到正确的平行光阴影。
修正的方式也很简单,在原图形和偏移阴影之间,创建一个连接两者轴对齐包围盒(Rect2)中心点的窄矩形。然后求三者的并集,再求凸多边形,这样无论多远的偏移距离都可以实现求解。
凹多边形求阴影的修正
在原始图形为凹多边形时,用上面的方法会求解出多余的部分。
修正思路也很简单,就是先将凹多边形拆解为多个凸多边形(或者获取三角化的三角形集合),然后分别求阴影图形,最后再求并集。
点光源阴影
在平行光求阴影的原理基础上,实际上就是依据点光源与形状的距离,设定偏移阴影进行一定的缩放。其余求解思路几乎没有变化。
顶点着色用于创建渐变透明
真实的阴影会随着距离逐渐变淡。在计算机里就是逐渐变透明。利用绘图函数的顶点着色功能,可以创建逐渐渐变的透明阴影效果。
测试
以下是一些测试阶段的截图,初期基于简单的矩形进行试验。
求多边形的平行光阴影函数
为了简化调用和尽可能的复用,我将求多边形平行光阴影封装为了一个函数。凹多边、凸多边形阴影的求解用这个函数都可以求解。
只需要传入多边形顶点数组,设定阴影偏移的角度和距离,便可以直接获得阴影多边形的顶点数据。
# 求多边形的平行光阴影
# 2024年9月7日01:18:06
# 作者:巽星石
func polygon_dir_light_shadow(
shape:PackedVector2Array, # 要求阴影的图形
angle:float = 45.0, # 阴影方向(与X轴正方向夹角)
length:float = 10.0, # 阴影长度
) -> PackedVector2Array:
var result:PackedVector2Array # 最终阴影结果
var polygons = Geometry2D.decompose_polygon_in_convex(shape) # 分解为多个凸多边形
if polygons.size() > 1: # shape为凹多边形
var shadows:Array[PackedVector2Array] = []
# 逐个求阴影
for polygon in polygons:
shadows.append(polygon_dir_light_shadow(polygon,angle,length))
# 逐个求并集
result = shadows[0]
for i in range(1,shadows.size()):
result = Geometry2D.merge_polygons(result,shadows[i])[0]
elif polygons.size() == 1: # shape为凸多边形
var dir = Vector2.RIGHT.rotated(deg_to_rad(angle)) # 阴影的偏转方向
var offset = dir * length # 阴影相对原图形的偏移
# 1.获取偏移的阴影
var shadow:PackedVector2Array = Transform2D().translated(offset) * shape.duplicate() # 应用偏移后的阴影多边形
var shape_rect:Rect2 = get_rect(shape)
var shadow_rect:Rect2 = get_rect(shadow)
# 2.获取连接阴影和原图形之间的矩形
var link_rect = CanvasRect.new()
link_rect.height = 3
link_rect.width = shadow_length
var link_rect_points = Transform2D().rotated(deg_to_rad(angle)).translated(offset/2.0 + shape_rect.get_center()) * link_rect.points
# 3.求原始图形和连接矩形之间的并集
var union1 = Geometry2D.merge_polygons(shape,link_rect_points)[0]
# 4.求并集1和偏移阴影之间的并集
var union = Geometry2D.merge_polygons(union1,shadow)[0]
# 5.求凸多边形
result = Geometry2D.convex_hull(union)
return result
# 获取图形的矩形
func get_rect(shape:PackedVector2Array) -> Rect2:
# 拆分出X坐标和Y坐标数组
var x_arr = []
var y_arr = []
for p in shape:
x_arr.append(p.x)
y_arr.append(p.y)
# 最小值构成Rect2的offset
var pos = Vector2(x_arr.min(),y_arr.min())
# 最大值 - pos = Rect2 的 size
var siz = Vector2(x_arr.max(),y_arr.max()) - pos
return Rect2(pos,siz)
凹多边形阴影实例
开启show_debug后,会绘制被拆分的每个凸多边形,及其偏移阴影,连接矩形等。
凸多边形阴影实例
后续
- 目前在测试场景中编写,后续会直接放入
CanvasShape
基类中,覆盖原有的阴影选项和绘制方式。 - 所有
CanvasShape
及其子类型都可以直接使用。