经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C 语言 » 查看文章
[TinyRenderer] Chapter1 p3 Line
来源:cnblogs  作者:qiyuewuyi2333  时间:2024/6/13 21:20:45  对本文有异议

(注:本小节不是对划线算法事无巨细的证明,如果你需要更加系统的学习,请跳转至文末的参考部分)
如果你是一名曾经学习过图形学基础的学生,那么你一定对画线算法稔熟于心,中点划线算法,Bresenham算法。其中,现代光栅化器中使用最多的就是Bresenham算法,它以去除了除法和浮点运算而著称。

但如果现在让你看下面的这段代码,你能否把它和Bresenham算法联系起来呢?

  1. void Segment::draw(const Point2i begin, const Point2i end, const RGBPixel& color, BMPImage& image)
  2. {
  3. int x0 = begin.x;
  4. int y0 = begin.y;
  5. int x1 = end.x;
  6. int y1 = end.y;
  7. int dx = abs(x1 - x0);
  8. int dy = abs(y1 - y0);
  9. int sx = (x0 < x1) ? 1 : -1;
  10. int sy = (y0 < y1) ? 1 : -1;
  11. int error = dx - dy;
  12. while (true)
  13. {
  14. image.set(x0, y0, color);
  15. if (x0 == x1 && y0 == y1)
  16. break;
  17. int e2 = 2 * error;
  18. if (e2 > -dy)
  19. {
  20. error -= dy;
  21. x0 += sx;
  22. }
  23. if (e2 < dx)
  24. {
  25. error += dx;
  26. y0 += sy;
  27. }
  28. }
  29. }

如果你可以顺利地说出每行代码的含义,那么恭喜你,你已经完全掌握了Bresenham算法,你可以跳过本小节,进行下一小节的学习。
但如果你还有所异或,那么相信我,看完本小节,你必定有所收获。

本节目标

在光栅化渲染器中加入画线功能

分析

首先,让我们考虑一种再简单不过的场景,你从一个初始点begin出发,从左向右,沿着斜率在0~1的直线到达end

这是一个Bresenham算法的基础场景,在这个场景中,我们可以通过中点划线算法知道存在一个判断依据,用于确定在每次遍历时y的值是否需要改变。

这个值的变化由直线方程给出,在这里我们仅使用beginend给出。(具体论证请翻阅参考)
还记得我们的假设场景么?在这个场景下:

  1. assert: (x0 < x1) and ((y1 y0) < (x1 x0))
  2. δx = x1 x0;
  3. δy = y1 y0;
  4. incrE = 2 * δy;
  5. incrNE = 2 * y - δx);
  6. d = 2 * δy δx;

保持x递增的同时,判断y是否改变。
d<0 -> d += incrE
else -> d += incrNE and ++y

现在我们得到了在斜率为0~1的时候的Bresenham算法,且begin.x<end.x
那么对于其他场景呢?

  1. begin.x > end.x
  2. slope < 0 or slope > 1
  3. 平行于坐标轴的方向

解决方案:

  1. 只需要将begin和end两个点做一下交换即可
  2. slope < 0 or slope > 1
    1. slope < 0只需要对begin和end加上符号,最后set的时候再变回来即可
    2. slope > 1这个更加简单,只需要交换xy即可
  3. 平行于轴体的方向只需要特判一下进行处理,而且这样效率更高
实现

实际上TinyRenderer的实现思路也是如此:

  1. void line(int x0, int y0, int x1, int y1, TGAImage &image, TGAColor color) {
  2. bool steep = false;
  3. if (std::abs(x0-x1)<std::abs(y0-y1)) {
  4. std::swap(x0, y0);
  5. std::swap(x1, y1);
  6. steep = true;
  7. }
  8. if (x0>x1) {
  9. std::swap(x0, x1);
  10. std::swap(y0, y1);
  11. }
  12. int dx = x1-x0;
  13. int dy = y1-y0;
  14. int derror2 = std::abs(dy)*2;
  15. int error2 = 0;
  16. int y = y0;
  17. for (int x=x0; x<=x1; x++) {
  18. if (steep) {
  19. image.set(y, x, color);
  20. } else {
  21. image.set(x, y, color);
  22. }
  23. error2 += derror2;
  24. if (error2 > dx) {
  25. y += (y1>y0?1:-1);
  26. error2 -= dx*2;
  27. }
  28. }
  29. }

而这种实现还可以转化成下面的这种写法,就是上面我们提到的方法,更加优雅,不需要特判条件,使用统一变量。

实现

  1. void Segment::draw(const Point2i begin, const Point2i end, const RGBPixel& color, BMPImage& image)
  2. {
  3. int x0 = begin.x;
  4. int y0 = begin.y;
  5. int x1 = end.x;
  6. int y1 = end.y;
  7. int dx = abs(x1 - x0);
  8. int dy = abs(y1 - y0);
  9. // 决定决策会落在坐标系四个方位中的哪一个
  10. int sx = (x0 < x1) ? 1 : -1;
  11. int sy = (y0 < y1) ? 1 : -1;
  12. // 维护error,用于决策下一个点的位置
  13. int error = dx - dy;
  14. while (true)
  15. {
  16. image.set(x0, y0, color);
  17. if (x0 == x1 && y0 == y1)
  18. break;
  19. int e2 = 2 * error;
  20. // 每次迭代会进行两次决策,共同决定下一个点是在一个角落中的哪一个
  21. // 如果在x轴上的误差较大
  22. if (e2 > -dy)
  23. {
  24. error -= dy;
  25. x0 += sx;
  26. }
  27. // 如果在y轴上的误差较大
  28. if (e2 < dx)
  29. {
  30. error += dx;
  31. y0 += sy;
  32. }
  33. }
  34. }

误差判别的依据,B,C点

最后记得,反转一下y轴,因为bmp图像中的y轴方向是向下的

结果

Reference

  1. # Lesson 1: Bresenham’s Line Drawing Algorithm
  2. Bresenham.pdf
  3. # wiki Bresenham's line algorithm

原文链接:https://www.cnblogs.com/qiyuewuyi/p/18246788

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号