线段的栅格化过程常见于显示器的矢量图显示算法,而随着图形学发展,线段栅格化的过程也不再仅限于二维的平面屏幕,这里基于二维的Bresenham 算法简单进行了三维拓展。
关于二维Bresenham 算法有很多优秀的blog做了详细的说明,比如(58条消息) 直线绘制算法-Bresenham算法_着风少年的博客-CSDN博客
等,大家可以自行查阅了解其基本原理,当然不了解其基本原理也可以无障碍阅读本文。
算法思想的简单概述
简单的说,当我们想要将一条线段映射到栅格中时,我们可以看作一边的端点向着线段的另一边“一步一步”的前进,每前进一步,都会经过新的栅格,我们需要的就是把这些经过的栅格全部找出来。但是有些栅格虽然经过了,但是是冗余的,比如图中浅色的部分,这些部分我们不需要。我们以上图的二维栅格为例,这一过程中会发现,除开斜率正好为1的情况(倾斜45°),总有一个方向是增长的比较快的(比如图中的横坐标方向),这意味着在增长比较快的方向前进多步之后,在另外较慢的方向上可能只前进了一步。
综上,我们可以给出Bresenham 算法的精髓“判断线段在最快的方向上每一步增长之后,在其他的方向上是否需要增长”。当然这个过程中有更多的细节要考虑,比如每一步的增长,坐标到底是增大还是减小,这取决于两端点之间的相对位置关系。
算法及代码
几点说明:
1.如何确定哪个方向增长最快,xyz坐标对应相减,绝对值最大的方向增长最快
2.如何确定增长方向,终点坐标减去起始点坐标,为正的正增长,为负的负增长
3.如何确定最快方向增长一步之后其他方向是否需要增长,事实上是看每一步之后其他反向与栅格交点的坐标是否多于相交栅格的一半,多则选取,少于则被认为是冗余栅格,不选取。判断是否多于一半的这一过程中会自然的涉及到0.5的浮点运算,这会影响算法精度,而且违背了栅格运算中数据结构基本都为整数的原则,因此这里需要换元,用乘以2来代替0.5。
以下算法以X为最快增长方向为例,便于理解,如果需要改造成任意方向通用,简单的使用其他变量对xyz进行代换即可,例如使用l1,l2,l3来代换xyz,l1>=max(l2 , l3),运算完之后再根据最开始做出的判断代换回去就可以得到真实坐标。
#include <iostream>
#include <cmath>
struct Point3D {
int x;
int y;
int z;
};
int sign(int number) {
return (number > 0) - (number < 0);
}
void drawLine3D(Point3D start, Point3D end) {
int dx = end.x - start.x;
int dy = end.y - start.y;
int dz = end.z - start.z;
int adx = abs(dx);
int ady = abs(dy);
int adz = abs(dz);
int sx = sign(dx);
int sy = sign(dy);
int sz = sign(dz);
int err1 = 2 * ady - adx;
int err2 = 2 * adz - adx;
int x = start.x;
int y = start.y;
int z = start.z;
for (int i = 0; i <= adx; i++) {
std::cout << "(" << x << ", " << y << ", " << z << ")" << std::endl; // 输出当前点
if (err1 > 0) {
y += sy;
err1 -= 2 * adx;
}
if (err2 > 0) {
z += sz;
err2 -= 2 * adx;
}
x += sx;
err1 += 2 * ady;
err2 += 2 * adz;
}
}
int main() {
Point3D start = {0, 0, 0};
Point3D end = {5, 3, 2};
drawLine3D(start, end);
return 0;
}