【Godot4.2】像素直线画法及点求取函数

概述

基于CanvasItem提供的绘图函数进行线段绘制只需要直接调用draw_line函数就可以了。

但是对于可以保存和赋值节点直接使用的纹理图片,却需要依靠Image类。而Image类没有直接提供基于像素的绘图函数。只能依靠set_pixelset_pixelv进行逐个像素的填色。

所以问题就变成了获取线段上两点之间所有经过的点的位置的问题。

基于向量插值的尝试

作为一个数学学渣,首先想到的是基于Vector2进行插值(lerp)或者使用move_toward()来获取每个点坐标。

因此,我首先搭建了如下的测试场景:
在这里插入图片描述
然后为TextureRect节点添加如下代码:

# 基于向量插值求线段所有经过的点
extends TextureRect

var img:Image
# 起点和终点
var start:Vector2 = Vector2()
var end:Vector2


func _ready() -> void:
	# 创建画布
	img = Image.create(500,500,false,Image.FORMAT_RGBA8)

# 鼠标移动,绘制线段
func _gui_input(event: InputEvent) -> void:
	if event is InputEventMouseMotion:
		end = event.position
		print(end)
		draw_line_in_image(img,start,end)
	
# 在Image上逐个像素绘制线段
func draw_line_in_image(image:Image,p1:Vector2,p2:Vector2) -> void:
	image.fill(Color.WHITE)
	var d = p2 - p1
	var steps = max(abs(d.x),abs(d.y))  # 步幅
	# 用插值绘制
	for i in range(steps):
		var p = p1.lerp(p2,i/steps)
		image.set_pixelv(p,Color.BLACK)
	update_texture()

# 更新显示图片
func update_texture():
	texture = ImageTexture.create_from_image(img)

想法虽好,然而结果却不太完美,在一些情况下会丢失点或位置不正确从而导致线段不连续

在这里插入图片描述
想进行四舍五入方面的控制,但是尝试半天也没有多大改进,于是不使用数学公式的方法就此作罢。

使用增量法

也就是所谓的“Bresenham算法”。知道这个算法是在去年,但是一直没有搞出成功的代码。经过昨晚复习直线基础的定义和概念,以及复习B站关于此算法的视频,再通过自己的一番画图理解,终于有所开窍,成功实现了GDScript版本的算法代码。

感谢互联网,感谢分享基础数学知识的人们!让我这个学渣可以随时方便的补习到数学知识。

言归正传,这里我画了一张图:

  • 平面上任意两点A(x1,y1)B(x2,y2),经过这两点可以定义一条直线 y = k x + b y=kx+b y=kx+b,其中k是该直线的斜率,也就是AB向量与X轴正方向夹角α的正切值 t a n α tanα tanα
  • k = t a n α = d y / d x = ( y 2 − y 1 ) / ( x 2 − x 1 ) k = tanα = dy/dx = (y_2-y_1)/(x_2-x_1) k=tanα=dy/dx=(y2y1)/(x2x1)
    image.png

增量法的核心是首先确定在像素网格中绘制一条线段AB,需要绘制多少个点。而通过比较dydx大小,可以分为3种情况:

  • dx>dy时,我们需要在像素网格上绘制dx个点
  • dx=dy时,我们需要在像素网格上绘制dx(或dy)个点
  • dx<dy时,我们需要在像素网格上绘制dy个点

image.png

确定需要绘制的点的个数,接下来就确定坐标计算的规则。以上图中两个线段为例:

  • 图左线段:
    • dx=14,dy=6dx>dy,也就是斜率k<1,所以我们需要从(x1,y1)开始,绘制14个点,x每增加1y增加k
  • 图右线段:
    • dx=9,dy=14dy>dx,也就是斜率k>1,所以我们需要从(x1,y1)开始,绘制14个点,y每增加1x增加1/k

还有一点就是根据增量是否>0.5,可以对计算获得的非整数xy坐标进行四舍五入,从而确定一个唯一的像素位置(可以理解为Vector2i)。

你可以手动控制这里的四舍五入规则,或者也可以直接享受Image类型set_pixelset_pixelv提供的从自动四舍五入(因为你不可能在一个非整数像素坐标上设置颜色)。

以上就是增量法的核心原理。但是上面只考虑了第一象限,在一个平面直角坐标系中,直线的斜率还要考虑在其他象限的情况
直线在平面坐标系中斜率示意图
好在,根据绘图和分析,直线斜率k的变化可以认为是Y轴对称的:
所以就有了3种情况:

  • -1≤k≤1,此时dx≥dysteps = dx。也就是我们需要计算和绘制dx个点的坐标:
    • 遍历0dx,单个点的坐标就是:(x1 + i,y1 + k * i),也就是,x每增加1y增加k
var min_p = p1 if p1.x< p2.x else p2  # 左侧点(x比较小的点)
for x in range(steps):
	var p = Vector2(min_p.x + x,min_p.y + k * x)
  • k>1k<-1,此时dy>dxsteps = dy。此时,y每增加1x增加1/k。单个点的坐标就成了:(x1 + 1/k * y,y1 + y)
var min_p = p1 if p1.y< p2.y else p2  # 下侧点(y比较小的点)
for y in range(steps):
    var p = Vector2(min_p.x + y/k,min_p.y + y)
  • dx = 0k不存在,因为还是dy>dx,所以steps = dy,此时y每增加1x增加0:
var min_p = p1 if p1.y< p2.y else p2  # 下侧点
for y in range(steps):
    var p = Vector2(min_p.x + 0,min_p.y + y)

像素线段点求取函数

有了上面的分析,则可以编写出一个如下的函数:

# 返回两点之间绘制线段所需要着色的点的集合
func get_line_points(p1:Vector2,p2:Vector2) -> PackedVector2Array:
	var arr:PackedVector2Array
	var d = p2 - p1  # 端点坐标相减
	var k = d.y/d.x if d.x != 0 else null # 斜率
	var steps = max(abs(d.x),abs(d.y))    # 步幅 - 需要添加的点的数目,dx或dy中比较大的那个的绝对值
	
	if k == null: # 斜率不存在
		var min_p = p1 if p1.y< p2.y else p2  # 下侧点
		for y in range(steps):
			var p = Vector2(min_p.x + 0,min_p.y + y)
			arr.append(p)
	else:
		if k<=1 and k >= -1:  # 斜率在[-1,1]
			var min_p = p1 if p1.x< p2.x else p2  # 左侧点
			for x in range(steps):
				var p = Vector2(min_p.x + x,min_p.y + k * x)
				arr.append(p)
		else:     # 斜率在 [-,-1)[1,+)
			var min_p = p1 if p1.y< p2.y else p2  # 下侧点
			for y in range(steps):
				var p = Vector2(min_p.x + y/k,min_p.y + y)
				arr.append(p)
	return arr

我们只需要传入线段两个端点的坐标,就可以返回所有需要着色的点。有了这些点,我们就可以用Image的方法进行图片像素的着色,绘制出线段。

测试

搭建如下测试场景:

image.png
我们用一个500×500pxTextureRect作为绘制像素直线的的画布。为其添加如下代码:

extends TextureRect

var img:Image
# 线段起止点
var start:Vector2 = Vector2(250,250)
var end:Vector2

@export var bg_color:Color = Color.WHITE    # 画布背景色
@export var line_color:Color = Color.BLACK  # 线条颜色

func _ready() -> void:
	# 创建画布
	img = Image.create(500,500,false,Image.FORMAT_RGBA8)
	draw_my_line(start,end,line_color)

# 鼠标移动时绘制起点到终点之间的线段
func _gui_input(event: InputEvent) -> void:
	if event is InputEventMouseMotion:
		end = event.position
		draw_my_line(start,end,line_color)

# 在指定的Image上进行线段的绘制
func draw_my_line(p1:Vector2,p2:Vector2,color:Color = Color.BLACK) -> void:
	img.fill(bg_color) # 填充背景色
	var points = get_line_points(p1,p2)   # 获取线段点集合
	for p in points:
		img.set_pixelv(p,color)
	texture = ImageTexture.create_from_image(img)# 更新显示图片
  • 我们创建与TextureRect大小一致的Image,并在其上填充白色背景,用黑色绘制线段之间的所有点。
  • 我们将线段的起始点放在(250,250)也就是TextureRect中心点,终点则由鼠标的局部位置决定。

运行后的效果:
绘制线段3.gif
可以看到线段被正常显示。

总结

搞定绘制线段后,就可以基于多个点连线绘制多边形,也就可以绘制出其他常见的几何图形。

基于Image搞自己的绘图函数,其目的不言而喻,就是为了实现像素画绘制工具,以及实现基于像素的程序化纹理生成

搞定第一步,很开心。

参考

  • 17
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
CSDN IT狂飙上传的代码均可运行,功能ok的情况下才上传的,直接替换数据即可使用,小白也能轻松上手 【资源说明】 基于MATLAB实现的有限差分法实验报告用MATLAB中的有限差分法计算槽内电位;对比解析法和数值法的异同点;选取一点,绘制收敛曲线;总的三维电位图+使用说明文档 1、代码压缩包内容 主函数:main.m; 调用函数:其他m文件;无需运行 运行结果效果图; 2、代码运行版本 Matlab 2020b;若运行有误,根据提示GPT修改;若不会,私信博主(问题描述要详细); 3、运行操作步骤 步骤一:将所有文件放到Matlab的当前文件夹中; 步骤二:双击打开main.m文件; 步骤三:点击运行,等程序运行完得到结果; 4、仿真咨询 如需其他服务,可后台私信博主; 4.1 期刊或参考文献复现 4.2 Matlab程序定制 4.3 科研合作 功率谱估计: 故障诊断分析: 雷达通信:雷达LFM、MIMO、成像、定位、干扰、检测、信号分析、脉冲压缩 滤波估计:SOC估计 目标定位:WSN定位、滤波跟踪、目标定位 生物电信号:肌电信号EMG、脑电信号EEG、心电信号ECG 通信系统:DOA估计、编码译码、变分模态分解、管道泄漏、滤波器、数字信号处理+传输+分析+去噪、数字信号调制、误码率、信号估计、DTMF、信号检测识别融合、LEACH协议、信号检测、水声通信 5、欢迎下载,沟通交流,互相学习,共同进步!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巽星石

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值