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

概述

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

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

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

基于向量插值的尝试

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

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

# 基于向量插值求线段所有经过的点
extends TextureRectvar img:Image
# 起点和终点
var start:Vector2 = Vector2()
var end:Vector2func _ready() -> void:# 创建画布img = Image.create(500,500,false,Image.FORMAT_RGBA8)# 鼠标移动,绘制线段
func _gui_input(event: InputEvent) -> void:if event is InputEventMouseMotion:end = event.positionprint(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 - p1var 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:PackedVector2Arrayvar 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 TextureRectvar 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.positiondraw_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搞自己的绘图函数,其目的不言而喻,就是为了实现像素画绘制工具,以及实现基于像素的程序化纹理生成

搞定第一步,很开心。

参考

  • Bilibili - 编程挑战:画一条直线

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/284774.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数字乡村发展策略:科技引领农村实现跨越式发展

随着信息技术的迅猛发展和数字经济的崛起&#xff0c;数字乡村发展策略已经成为引领农村实现跨越式发展的重要手段。科技的力量正在深刻改变着传统农业的生产方式、农村的社会结构以及农民的生活方式&#xff0c;为农村经济发展注入了新的活力和动力。本文将从数字乡村的内涵、…

java每日一题——买啤酒(递归经典问题)

前言&#xff1a; 非常喜欢的一道题&#xff0c;经典中的经典。打好基础&#xff0c;daydayup!!!啤酒问题&#xff1a;一瓶啤酒2元&#xff0c;4个盖子可以换一瓶&#xff0c;2个空瓶可以换一瓶&#xff0c;请问10元可以喝几瓶 题目如下&#xff1a; 啤酒问题&#xff1a;一瓶…

基于图的在线社区假新闻检测建模

论文原文&#xff1a;Graph-based Modeling of Online Communities for Fake News Detection 论文代码&#xff1a;GitHub - shaanchandra/SAFER: Repository containing the official code for the paper Graph-based Modeling of Online Communities for Fake News Detectio…

KIMI爆了!对比文心一言和通义千问它到底有多强?

原文:赵侠客 前言 最近国产大模型KIMI爆了大部分人应该都知道了&#xff0c;从我个人的感受来看这次KIMI爆了我不是从技术领域接触到的&#xff0c;而是从各种金融领域接触到的。目前国内大模型可以说是百模大战&#xff0c;前几年新能源大战&#xff0c;今年资本割完韭菜后留…

java面向对象编程基础

对象&#xff1a; java程序中的对象&#xff1a; 本质上是一种特殊的数据结构 对象是由类new出来的&#xff0c;有了类就可以创建对象 对象在计算机的执行原理&#xff1a; student s1new student();每次new student(),就是在堆内存中开辟一块内存区域代表一个学生对象s1变…

Matlab DDPG

文章目录 1 rlSimulinkEnv1.1 说明1.2 例子1.2.1 使用工作空间Agent创建Simulink环境1.2.2 为Simulink模型创建强化学习环境1.2.3 创建Simulink多Agents环境2 创建Simulink环境和训练Agent2.1 创建环境接口2.2 创建DDPG Agent2.3 训练Agent2.4 验证已训练的Agent3 创建Simulink…

创建linux虚拟机系统:(安装Ubuntu镜像文件,包含语言设置、中文输入法、时间设置)

我下载的是清华大写开源软件镜像站中的ubuntu-20.04.6-desktop-amd64.iso这个镜像文件&#xff0c; 这个文件我下载完成之后没有解压&#xff0c;直接在创建虚拟机的时候选择的压缩包。 地址为&#xff1a;Index of /ubuntu-releases/20.04/ | 清华大学开源软件镜像站 | Tsin…

Git——IDEA中的使用详解

目录 Git1、IDEA中配置Git2、将本地项目推送到远程仓库2.1、创建项目远程仓库2.2、初始化本地仓库2.3、连接远程仓库2.4、提交到本地仓库2.5、推送到远程仓库 3、克隆远程仓库到本地4、基本操作4.1、代码提交到暂存区4.2、暂存区代码提交到本地库4.3、推送到远程仓库4.4、撤销本…

网络: 网络层

IP地址: 分为网络号和主机号. 用来标识主机 IP协议 IP协议报文 4位版本号(version): 指定IP协议的版本, 对于IPv4来说, 就是4.4位头部长度(header length): IP头部的长度是多少个32bit, 也就是 length * 4 的字节数. 4bit表示最大的数字是15, 因此IP头部最大长度是60字节. 8…

HarmonyOS NEXT应用开发案例集

概述 随着应用代码的复杂度提升&#xff0c;为了使应用有更好的可维护性和可扩展性&#xff0c;良好的应用架构设计变得尤为重要。本篇文章将介绍一个应用通用架构的设计思路&#xff0c;以减少模块间的耦合、提升团队开发效率&#xff0c;为开发者呈现一个清晰且结构化的开发…

YOLOv8:Roboflow公开数据集训练模型

Roboflow公开数据集 Roboflow是一个提供计算机视觉数据集管理和处理工具的平台。虽然Roboflow本身并不创建或策划公开数据集&#xff0c;但它提供了一系列功能&#xff0c;帮助用户组织、预处理、增强和导出计算机视觉数据集。 官方网站&#xff1a;https://universe.roboflow…

FOCUS-AND-DETECT: A SMALL OBJECTDETECTION FRAMEWORK FOR AERIAL IMAGES

摘要 为了解决小对象检测问题&#xff0c;提出了一个叫做 Focus-and Detect 的检测框架&#xff0c;它是一个两阶段的框架。 第 一阶段包括由高斯混合模型监督的对象检测器网络&#xff0c;生成构成聚焦区域的对象簇 。 第二阶段 也是一个物体探测器网络&#xff0c;预测聚焦…

Web框架开发-Ajax

一、 Ajax准备知识:json 1、json(Javascript Obiect Notation,JS对象标记)是一种轻量级的数据交换格式 1 2 它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。…

谧林涓露门禁

原神武器升级材料谧林涓露和门禁好像聂。 difference(){union(){cylinder(2, 10,10, $fn365);hull(){translate([15,0,0])cylinder(1,2,2,$fn365);cylinder(1,10,10,$fn365);}}translate([15,0,-1])cylinder(4,1,1,$fn365); }

swagger3快速使用

目录 &#x1f37f;1.导入依赖 &#x1f32d;2.添加配置文件 &#x1f9c2;3.添加注解 &#x1f96f;4.访问客户端 1.导入依赖 引入swagger3的依赖包 <dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artif…

智能驾驶域控制器行业介绍

汽车智能驾驶功能持续高速渗透&#xff0c;带来智能驾驶域控制器市场空间快速增 长。智驾域控制器是智能驾驶决策环节的重要零部件&#xff0c;主要功能为处理感知 信息、进行规划决策等。其核心部件主要为计算芯片&#xff0c;英伟达、地平线等芯 片厂商市场地位突出。随着消费…

【c语言】详解操作符(上)

1. 操作符的分类 2. 原码、反码、补码 整数的2进制表示方法有三种&#xff0c;即原码、反码、补码 有符号整数的三种表示方法均有符号位和数值位两部分&#xff0c;2进制序列中&#xff0c;最高位的1位是被当做符号位其余都是数值位。 符号位都是用0表示“正”&#xff0c;用…

二分算法(查找)

问题&#xff1a;在数组中查找某一个数字x4的下标 例&#xff1a;arr:1 3 4 6 10 20 21 22 显然&#xff0c;数字4的下标为3。 1、线性查找&#xff0c;一个个地去遍历&#xff0c;时间复杂度为O(n) 2、二分查找&#xff0…

国务院办公厅发布:政府类网站网页设计规范(试行)

国务院办公厅于2019年12月发布了《政府类网站网页设计规范&#xff08;试行&#xff09;》。该规范的发布旨在统一政府类网站的设计风格和标准&#xff0c;提升政府网站的用户体验和可访问性&#xff0c;推动政府信息公开和服务的提升。 该规范涵盖了政府类网站的各个方面&…

【Esp32-Cam模型训练相关问题解决方案】

Esp32-Cam模型训练相关问题解决方案 一、AttributeError: module everywhereml.data.ImageDataset has no attribute from_nested_folders现象解决方案 二、Connection to 192.168.x.xxx timed out(connect timeoutNone)三、卡在0%| |0/100 [00:00<?, ?it/s] 抛砖引玉&…