概述
空间的透视是可以在二维平面上参数化计算和模拟的。本篇基于CanvasItem
绘制函数draw_colored_polygon()
自带的UV坐标和贴图功能,实现基础的平行透视效果。
或者可以叫做一点透视,由一个消失点决定物体的透视效果。
测试代码
extends Node2Dvar rect = Rect2(-50,-50,60,60) # 透视图形
var dis_p # 消失点var thickness:float = 60.0 # 厚度
var scale_rate:float # 缩放比例
var uvs = Points2D.Vec2Arr("0,0 0,1 1,1 1,0")@export var texture:Texture2D:set(val):texture = valqueue_redraw()func _ready() -> void:dis_p = get_viewport_rect().get_center() # 消失点func _process(delta: float) -> void:# 矩形跟随鼠标移动rect.position = get_global_mouse_position() - rect.size/2.0queue_redraw()func _draw() -> void:var c = rect.get_center() # 矩形中心var dir = c.direction_to(dis_p) # 由矩形中心向消失点的方向var dis = c.distance_to(dis_p) scale_rate = (dis - thickness)/dis# 求偏移矩形var move = Transform2D().translated(-c)var bottom_rect = move * rectbottom_rect = Transform2D().scaled(Vector2.ONE * scale_rate).translated(dir * thickness) * bottom_rectbottom_rect = bottom_rect * move# 求侧边多边形var rect_points = get_rect_points(rect) var bottom_rect_points = get_rect_points(bottom_rect)var side_top = PackedVector2Array([rect_points[0],bottom_rect_points[0],bottom_rect_points[1],rect_points[1]])var side_left = PackedVector2Array([rect_points[0],bottom_rect_points[0],bottom_rect_points[3],rect_points[3]])var side_right = PackedVector2Array([rect_points[1],bottom_rect_points[1],bottom_rect_points[2],rect_points[2]])var side_bottom = PackedVector2Array([rect_points[2],bottom_rect_points[2],bottom_rect_points[3],rect_points[3]])var rect_4:Array[Rect2] = ShapeTests.get_rect_4_parts(get_viewport_rect()) # 四象限划分draw_line(c,dis_p,Color.ORANGE_RED,1)draw_colored_polygon(bottom_rect_points,Color.AQUAMARINE,uvs,texture) # 底面var sides:Array[PackedVector2Array]if rect_4[0].has_point(c): # 在第一象限sides.append_array([side_top,side_left,side_right,side_bottom])if rect_4[1].has_point(c): # 在第二象限sides.append_array([side_top,side_right,side_left,side_bottom])if rect_4[2].has_point(c): # 在第三象限sides.append_array([side_right,side_bottom,side_top,side_left])if rect_4[3].has_point(c): # 在第四象限sides.append_array([side_left,side_bottom,side_top,side_right])for rec in sides:draw_colored_polygon(rec,Color.CADET_BLUE,uvs,texture)draw_colored_polygon(rect_points,Color.AQUAMARINE,uvs,texture) # 原始矩形#draw_rect(rect,Color.AQUAMARINE,false,1)#draw_rect(bottom_rect,Color.AQUAMARINE,false,1)# 返回矩形四个角点
func get_rect_points(rect:Rect2) -> PackedVector2Array:var pots:PackedVector2Arrayvar pos = rect.position; var end = rect.end; var size = rect.sizepots = [ pos,pos + Vector2(size.x,0), end,end - Vector2(size.x,0)]return pots
绘制效果:
改进版本
extends Node2Dvar rect = Rect2(-50,-50,60,60) # 透视图形
var dis_p # 消失点var thickness_rate:float = 0.2 # 厚度比例
var scale_rate:float = 0.8 # 缩放比例
var uvs = Points2D.Vec2Arr("0,0 0,1 1,1 1,0") # 顶点UV坐标## 纹理
@export var texture:Texture2D:set(val):texture = valqueue_redraw()func _ready() -> void:dis_p = get_viewport_rect().get_center() # 消失点func _process(delta: float) -> void:# 矩形跟随鼠标移动rect.position = get_global_mouse_position() - rect.size/2.0queue_redraw()func _draw() -> void:var c = rect.get_center() # 矩形中心var dir = c.direction_to(dis_p) # 由矩形中心向消失点的方向var dis = c.distance_to(dis_p)# 求偏移矩形var move = Transform2D().translated(-c)var bottom_rect = move * rectbottom_rect = Transform2D().scaled(Vector2.ONE * scale_rate) * bottom_rectbottom_rect = bottom_rect * movebottom_rect.position = c.lerp(dis_p,thickness_rate) - bottom_rect.size/2.0# 求矩形的点集var r1_pots = ShapeTests.get_rect_points(rect) var r2_pots = ShapeTests.get_rect_points(bottom_rect)# 绘制底面draw_colored_polygon(r2_pots,Color.AQUAMARINE,uvs,texture)var sides = ShapeTests.get_rect_rect_sides(rect,bottom_rect) # 获取测边多边形var order = ShapeTests.get_sides_order(c,dis_p) # 获取绘制顺序# 按顺序绘制侧边面for i in order:draw_colored_polygon(sides[i],Color.CADET_BLUE,uvs,texture)# 绘制原始矩形draw_colored_polygon(r1_pots,Color.AQUAMARINE,uvs,texture)
部分代码已经总结为函数,放入ShapeTests
函数库,总结的函数如下:
# 求两个矩形相连的侧边多边形
func get_rect_rect_sides(rect1:Rect2,rect2:Rect2) -> Array[PackedVector2Array]:var sides:Array[PackedVector2Array]var r1_pots = get_rect_points(rect1) var r2_pots = get_rect_points(rect2)var side_top = PackedVector2Array([r1_pots[0],r2_pots[0],r2_pots[1],r1_pots[1]])var side_left = PackedVector2Array([r1_pots[0],r2_pots[0],r2_pots[3],r1_pots[3]])var side_right = PackedVector2Array([r1_pots[1],r2_pots[1],r2_pots[2],r1_pots[2]])var side_bottom = PackedVector2Array([r1_pots[2],r2_pots[2],r2_pots[3],r1_pots[3]])sides.append_array([side_top,side_right,side_bottom,side_left])return sides# 返回一个点p在另一个点c所划分的四象限的哪个象限
func get_pos_quadrant(p:Vector2,c:Vector2) -> int:var quadrant:intvar ang = c.direction_to(p).angle()if ang> -PI and ang <= - PI * 0.5: # 在第一象限quadrant = 0if ang> - PI * 0.5 and ang<= 0: # 在第二象限quadrant = 1if ang> 0 and ang<= PI * 0.5: # 在第三象限quadrant = 2if ang> PI * 0.5 and ang<= PI: # 在第四象限quadrant = 3return quadrant# 返回各个象限绘制的顺序
func get_sides_order(c:Vector2,dis_p:Vector2) -> Array[int]:var sides_order:Array[int]match get_pos_quadrant(c,dis_p):0:sides_order.append_array([0,3,1,2])1:sides_order.append_array([0,1,2,3])2:sides_order.append_array([1,2,3,0])3:sides_order.append_array([2,3,0,1])return sides_order
以下是改进版本的测试效果:
改进版本更符合我最初的设想。