1. 主页
  2. 文档
  3. 从零开始的计算机图形_程序员的3D渲染介绍教程
  4. 从零开始的计算机图形_程序员的3D渲染介绍 线
  5. 画线

画线

我们现在有了一种方法来获得我们感兴趣的每个x的值的y的值。这就给了我们一对满足直线方程的(x,y)。

我们现在可以写一个函数的第一近似值,从P0到P1画一条线段。让x0和y0分别是P0的x和y坐标,x1和y1是P1的坐标。假设x0 < x1,我们可以从x0到x1,计算每个x值的y值,并在这些坐标处画一个像素。

DrawLine(P0, P1, color) {
    a = (y1 - y0) / (x1 - x0)
    b = y0 - a * x0
    }
}

请注意,除法运算符/被期望执行实数除法,而不是整数除法。尽管x和y在这里是整数,因为它们代表画布上像素的坐标。

还要注意的是,我们认为for循环包括范围内的最后一个值。在C、C++、Java和JavaScript等语言中,这将被写成for(x = x0; x <= x1; ++x)。我们将在本书中使用这一约定。

这个函数是上述方程的一个直接、简单的实现。它是有效的,但我们能让它更快吗?

相反,我们只在x的整数增量上计算它们,而且是按顺序计算的。在计算完y(x)之后,我们再计算y(x+1)。

y(x) = ax + b

y(x + 1) = a · (x + 1) + b

我们可以对第二个表达式做一些处理:

y(x + 1) = ax + a + b

y(x + 1) = (ax + b) + a

y(x + 1) = y(x) + a

这并不令人惊讶;毕竟,斜率a是衡量x增加1时y变化的程度,这正是我们在这里做的。

这意味着我们可以通过获取y的前一个值并加上斜率来计算y的下一个值;不需要每个像素的乘法,这使得该函数更快。在开始时,没有 “y的上一个值”,所以我们从(x0,y0)开始。然后我们不断地在x上加1,在y上加a,直到我们到达x1。

再次假设x0 < x1,我们可以将该函数重写如下。

DrawLine(P0, P1, color) {
    a = (y1 - y0) / (x1 - x0)
    y = y0
    for x = x0 to x1 {
        canvas.PutPixel(x, y, color)
        y = y + a
    }
}

到目前为止,我们一直假设x0 < x1。有一个简单的变通方法来支持不成立的线条:由于我们绘制像素的顺序并不重要,如果我们得到一条从右到左的线条,我们只需交换P0和P1,将其转化为同一线条的从左到右的版本,然后像以前一样绘制它。

DrawLine(P0, P1, color) {
    // Make sure x0 < x1
    if x0 > x1 {
        swap(P0, P1)
    }
    a = (y1 - y0) / (x1 - x0)
    y = y0
    for x = x0 to x1 {
        canvas.PutPixel(x, y, color)
        y = y + a
    }
}

让我们用我们的函数来画几条线。图6-1显示了线段(-200,-100)-(240,120),图6-2显示了线段的特写。

图6-1:一条直线

图6-2:放大直线

线条出现锯齿状是因为我们只能在整数坐标上绘制像素,而数学线条的宽度实际上为零;我们所绘制的是一条从(-200,-100)-(240,120)的理想线条的量化近似。有一些方法可以画出更漂亮的线条近似值(你可能想研究一下MSAA、FXAA、SSAA和TAA,作为进入一组有趣的兔子洞的可能入口)。我们不会去那里,有两个原因。(1)它比较慢,(2)我们的目标不是要画出漂亮的线条,而是要开发一些渲染3D场景的基本算法。

让我们试试另一条线,(-50,-200)-(60,240)。图6-3显示了结果,图6-4显示了相应的特写。

图6-3:另一条斜率较高的直线

图6-4:放大第二条直线

哎呀。发生了什么?
算法完全按照我们的要求做了;它从左到右,为每个x的值计算一个y的值,并画出相应的像素。问题是,它为每个x的值计算一个y的值,而在这种情况下,我们实际上需要为某些x的值计算几个y的值。

发生这种情况是因为我们选择了y=f(x)的表述;事实上,这也是我们不能画垂直线的原因–在这种极端情况下,所有的y值都对应于x的相同值。

这篇文章对您有用吗? 1