[未完结]数字微分分析法的直线绘制(DDA)

注意!
本文被第1次更新,可能存在后续更新

直线画法

直线的斜截式方程

在二维空间下,一条直线的方程可以被描述为若干种形式,其中比较常见的一种是斜截式方程:

[y=kx+b ]

其中(k)称为直线的斜率,反映直线的倾斜情况,假定直线与水平面成角( heta)(右侧角),则(k)( heta)存在如下关系:

[k= an heta ]

(b)称为截距,反映该直线在二维空间中的位置

数字微分分析(DDA)

可对直线(y=kx+b)进行微分处理得到:

[frac{mathrm{d}y}{mathrm{d}x}=k= an heta ]

对于缓直线(|k|le1))的时候,可考虑将直线考虑为X型直线(水平走向),这个时候可取x方向的单位点作为取样点计算y的值:

[y=kx+b(xinmathbb{Z}) ]

对于给定的线段((x_1,y_1) ightarrow(x_2,y_2)),这里考虑取样值的走向(delta x)为:

[delta x=mathrm{sgn} (x_2-x_1) ]

这确定了,若曲线走向从右向左的时候,这条直线也是以这个走向被绘制出来而不至于南辕北辙。

缓直线之所以考虑成X走向,是因为对于每一个(x+delta x),在(delta x =1)的时候,其得到的(delta y)总小于1,而对应地,对于陡直线(|k|ge1))则相应地令(delta y=1),则对应于(y+delta y)得到的(delta x)总小于1,这样也就把陡曲线考虑为Y向曲线得到的误差是在可接受范围之内的(当差值(delta>1)的时候,轴向上相邻两点将得到两个不邻接的像素点亮,这将打破直线的连续性并使得直线观感变差)。

若结果为小数,则遵循四舍五入原则(加0.5后再取整)

也可考虑银行家舍入法(Banker 's Rounding)进行舍入

极端情形

完全水平(k=0)

这种情况下,线段认为X型这种情况下y只能得到b

完全垂直(k=∞)

这种情况下直线不存在斜截式方程、斜率式方程(因为斜率和截距都不存在),斜率被认为无穷大(正无穷或负无穷),这种情况下显然被认为Y型曲线

方向通用情形

一般地,如果绘制一条线段,我们更多的会考虑这种绘制方式:

给定两点A和B,然后通过两点可以确定一条直线以及这条线段。

其中,给定两点A和B即给定这两点的坐标,这两种坐标可以被表示为:

[P_A(x_a,y_a),P_B(x_b,y_b) ]

这里没有体现出一个至关重要的信息:斜率,但这一信息可以通过这两个坐标推算得出,由于给定两点有先后顺序(这个顺序将决定绘制的走向),因此这两点可认为构成了向量

[vec{v}=P_B-P_A=(x_b-x_a,y_b-y_a)=(Delta x,Delta y) ]

如果考虑把这个向量投射到复平面上,可以得到:

[z=Delta x+iDelta y ]

而斜率(k)的反正切即为(z)辐角

[arg z=arctan frac{Delta y}{Delta x}=arctan k ]

但需注意,这里(Delta x)可能为0,同样地,(Delta y)也有类似的可能,当仅有(Delta y=0)的时候,有(k=0),然而,其他的情况要复杂得多,若仅有(Delta x=0),则直线是铅直的,也就是斜率为∞,而两个同时为0的时候则上式直接变为未定式(此时两点重合,实际上得到的是(vec{0}),而这个向量是任意方向的)。
但所幸,一般的程序数学库中提供了函数atan2可以很好的解决这个问题,atan2的提出主要是出于两种目的:

  1. 解决斜率为∞的情形
  2. 降低当x或y中任意一个值过大而导致的运算误差(这实际上无法规避)

atan2取得两个参数xy作为输入并返回这个向量正确的方位角( heta)(当然,实际上是弧度形式)。尽管它并不得到斜率(因为其本质还是反正切,正好把( heta)从正切中取出),但是直接通过方位角,我们仍然可以确定一些事情:

[|k|>1Rightarrow pm frac{pi}{4} < heta < pm frac{3pi}{4} ]

[|k|<1Rightarrow -frac{pi}{4} < heta < frac{pi}{4} vee frac{3pi}{4} < heta < frac{5pi}{4}left(-frac{3pi}{4} ight) ]

这里还需考虑,当xy都为0的时候,这可能导致atan2无法正常工作,因此这个情况需要预先代替atan2进行处理。

光栅通用情形(注意这里着重讨论端点不落在整数位上)

由于在屏幕上是离散空间,因此绘制需要光栅化,因此,当结果存在小数时,则应当进行适当的舍入操作,当然,上文提及一种策略是四舍五入策略。

但是这仅仅是对于中间确定点亮像素的情形,这里仍然需要考虑:如果(P_A)(P_B)的坐标就很不老实该怎么办??

如果此时提前对(P_A)(P_B)做光栅化(坐标取整)则是否会存在潜在的问题??

这里将这两点的坐标做如下考虑:

[x_a=I_{xa}+F_{xa} ]

[x_b=I_{xb}+F_{xb} ]

[cdots ]

[Iinmathbb{Z},|F|<1 ]

以人话的方式说,这里面(I)表示整数部分,(F)表示小数部分。
这里只考虑有斜率的情形,斜率可以表述为:

[k=frac{(I_{yb}+F_{yb})-(I_{ya}+F_{ya})}{(I_{xb}+F_{xb})-(I_{xa}+F_{xa})} ]

假设(F_{yb}>0.5,F_{ya}<0.5,F_{xb}<0.5,F_{xa}>0.5),则

[k'=frac{(I_{yb}+1)-I_{ya}}{I_{xb}-(I_{xa}+1)} ]

此时造成误差:

[Delta k=k-k'=frac{-2af+ag+ah-a+2be-bg-bh-b-ce+cf+c-de+df+d-e-f+g+h}{e^2-eg-eh-e-f^2+fg+fh-f+g+h} ]

[a=I_{yb},b=F_{yb}dots k=frac{a+b-c-d}{e+f-g-h} ]

上式计算过于复杂,这里定性分析一下,考虑到小数部分都很小,因此两个F相乘的项忽略不计,I乘以F的项统一认为是同一阶,最后剩下的部分是:

[kapproxfrac{ag-a-b-ce+c+d-e-f+g+h}{e^2-eg-e-f+g+h} ]

其中认为所有I乘以I也是同阶的。所有的I和所有的F分别内部彼此同阶,则上式再次化简得到:

[kapproxfrac{Delta I-Delta F}{0} ]

好吧我承认这是一个非常敷衍了事的证明方法(如果你看着不爽,你完全可以认为这™就是一团废话然后自己证明一番,但我会认为你十有八九会面对一堆abcd无从下手,当然也许我方向就有问题,你完全可以认为这个分析过程是错的),因为这里把很多东西混淆掉了并且忽略了众多的情况,这是一个很粗糙的分析,但是这个提示我们一个信号:分子阶次高于分母。

这提示了一个问题,当两个点离得非常远的时候如果提前把两个点光栅化的话,斜率可能会发生较为显著的变化(当然,真正拿图画出来的时候可能更直观,但老实说这个误差从某种程度上来说差角确实不算大,但斜率确实是有明显的变化),当斜率发生变化的时候最主要导致的问题就是

我™又得重新确定点了!!因为线偏了!!

当然,更糟糕的是,当某一点沿着线段移动到另一点的时候,你将看到这个线段会鬼畜地抖动(因为每次运动一段距离之后可能变成整数点也可能不会变但误差形式可能又会发生变化然后线的偏离程度可能也会发生变化),当然这个幅度并不会很大,但是问题在于如果线段越长这种效应恐怕是越明显的,最重要的是,无论幅度大小与否这根本就不应当发生,但是,我们仍然有方法去解决这个问题,其实很简单,在对端点进行光栅化之前,先把斜率求出来。

对线影响最强烈的莫过于斜率!!!
线越长,影响越明显

方向对于一条直线来说太重要了,考虑一下当你正拿着一把楞次(会爆炸的弓箭)瞄准一个正在蹲坑的Grineer,然后在发射的时候你的手抖那么1度,命中了在隔壁扫描结合目标的队友,他在目标的10米以外的位置……
确定了方向,就意味着这条直线不会走向一个很奇怪的位置,在这个算法中才不至于产生这种效应。

但如果仅仅是这样的话,这条线存在可能比预期低那么1个像素的可能,当然这也是非常不明显的,而且实际上也只有几个点会发生这种变化,这个实际上也不会造成什么抖动的效应,不过本着力求完美的原则,而且这个过程并不会浪费太多的时间,这里还是考虑把点多少修正一下,这个做法也同样很简单,那就是并非直接对端点进行舍入,而是顺着端点推到最近的整数位点计算它对应的y值。

To be continued...

原文地址:https://www.cnblogs.com/oberon-zjt0806/p/10680196.html