概述
空间的透视是可以在二维平面上参数化计算和模拟的。本篇基于CanvasItem
绘制函数draw_colored_polygon()
自带的UV坐标和贴图功能,实现基础的平行透视效果。
或者可以叫做一点透视,由一个消失点决定物体的透视效果。
测试代码
extends Node2D
var 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 = val
queue_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.0
queue_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 * rect
bottom_rect = Transform2D().scaled(Vector2.ONE * scale_rate).translated(dir * thickness) * bottom_rect
bottom_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:PackedVector2Array
var pos = rect.position; var end = rect.end; var size = rect.size
pots = [ pos,pos + Vector2(size.x,0), end,end - Vector2(size.x,0)]
return pots
绘制效果:
改进版本
extends Node2D
var 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 = val
queue_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.0
queue_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 * rect
bottom_rect = Transform2D().scaled(Vector2.ONE * scale_rate) * bottom_rect
bottom_rect = bottom_rect * move
bottom_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:int
var ang = c.direction_to(p).angle()
if ang> -PI and ang <= - PI * 0.5: # 在第一象限
quadrant = 0
if ang> - PI * 0.5 and ang<= 0: # 在第二象限
quadrant = 1
if ang> 0 and ang<= PI * 0.5: # 在第三象限
quadrant = 2
if ang> PI * 0.5 and ang<= PI: # 在第四象限
quadrant = 3
return 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
以下是改进版本的测试效果:
改进版本更符合我最初的设想。