这个学期报了学校开设的计算机图形学课程,由于前一个月老师讲的都太抽象完全不知道在说啥……于是我的入门现在才刚刚开始。最近的一节课教授了基本图元的生成算法,留的作业是使用OpenGL或者DirectX实现DDA算法画直线、方程法画圆以及Bresenham画直线和圆。

ps:本次作业使用的是OpenGL中的<GL/glut.h>

文章目录

  • 1. 对于窗口的配置
  • 2. DDA算法绘制直线
  • 3. Bresenham算法绘制直线
  • 4. 使用方程绘制圆形
  • 5. 使用Bresenham算法绘制圆形
  • 6. 关于使用类来综合几个display函数

1. 对于窗口的配置

原本默认的窗口是左下角坐标是(-1, -1),右上角坐标为(1, 1),窗口的中心坐标为(0, 0)。在此绘制的是1000*600的窗口,左下角坐标为(0, 0),右上角坐标为(1000, 600)。且变换窗口大小图形的大小和位置不会改变。这一操作中起到关键作用的函数是gluOrtho2DglViewport,具体的理解可以参考这里:gluOrtho2D、glViewport、glutInitWindowSize区别与关系

大致的窗口配置如下:

void Init()
{glClearColor(1.0, 1.0, 1.0, 1.0);glMatrixMode(GL_PROJECTION);glLoadIdentity();gluOrtho2D(0, 1000, 0, 600);
}int main(int argc, char** argv)
{glutInit(&argc, argv);glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);glutInitWindowPosition(500, 250);glutInitWindowSize(1000, 600);glutCreateWindow("MyDraw");Init();glutDisplayFunc(display);glutMainloop();
}

2. DDA算法绘制直线

DDA算法的原理很简单,即求出用户输入直线两端的坐标 ( x 1 , y 1 ) 、 ( x 2 , y 2 ) (x1, y1)、(x2,y2) (x1,y1)、(x2,y2)后求出要绘制直线的斜率 k k k。然后先画出更靠左的点,接着依次绘制 ( x i + 1 , r o u n d ( y i + k ) ) (x_{i}+1,round(y_{i}+k)) (xi​+1,round(yi​+k)),直到最右边的点即可。

在算法的实现过程中容易注意不到的错误有三个:

  • 在斜率绝对值大于1时,再以x为步长每次自增1,会导致直线变成由离散的点组成的虚线。此时应该交换x和y的地位再进行绘图。

  • 忽略 x1=x2 的情况,计算斜率时分母为零(但是画出的结果依然是正确的……)。

  • 由于画直线默认是从左向右画,但是假如先输入的坐标更靠右,即x1>x2,需要调换两点坐标,否则无法画出直线。

实现的display函数如下:

void MyDraw::DDA_Draw()
{glClear(GL_COLOR_BUFFER_BIT);glViewport(0, 0, 1000, 600);glColor3f(0, 0, 0);if(x1 == x2) {  // 对于横坐标相等的点的特殊处理if(y1 > y2) swap(y1, y2);glBegin(GL_POINTS);for(double y = y1; y<=y2; y++)glVertex2f(x1, y);glEnd();glFlush();return;}else if(x1 > x2) {  // 保证先绘制的是更靠左的点swap(x1, x2);swap(y1, y2);}double k = (y2-y1)/(x2-x1);   // 计算出斜率kglBegin(GL_POINTS);if(fabs(k) <=1) {for(double x=x1, y=y1; x<=x2; x++, y+=k)glVertex2f(x, round(y));}else {  // 当斜率绝对值大于1时,交换原本x和y的地位进行计算double xStep = 1/k;if(k > 0)for(double x=x1, y=y1; y<=y2; y++, x+=xStep)glVertex2f(round(x), y);elsefor(double x=x1, y=y1; y>=y2; y--, x-=xStep)glVertex2f(round(x), y);}glEnd();glFlush();
}

DDA算法容易实现,非常直观,但是缺点也很明显,因为涉及了太多次浮点运算,所以不利于硬件实现 ,并不实用。

3. Bresenham算法绘制直线

以下关于算法的叙述适用于斜率 k k k位于0和1之间的情况下。

在绘出点 ( x i , y i ) 后 (x_{i},y_{i})后 (xi​,yi​)后,要确定 ( x I + 1 , y i + 1 ) (x_{I+1}, y_{i+1}) (xI+1​,yi+1​)的位置,实际上就是确定 ( x i + 1 , y i + 1 ) (x_{i+1},y_{i+1}) (xi+1​,yi+1​)是在下一个网格线段中点的上方还是下方,如图:

对于判断直线点在直线上还是在直线下,利用直线方程可以很容易地做到,即对于直线 y = k x + b y=kx+b y=kx+b,可以令函数 F ( x , y ) = y − k x − b F(x,y)=y-kx-b F(x,y)=y−kx−b,对于点 ( x , y ) (x,y) (x,y):

  • 对于直线上的点, F ( x , y ) = 0 F(x,y)=0 F(x,y)=0;
  • 对于直线上方的点, F ( x , y ) > 0 F(x,y)>0 F(x,y)>0;
  • 对于直线下方的点, F ( x , y ) < 0 F(x,y)<0 F(x,y)<0

选定一个判别变量d,有 d i = F ( x i + 1 , y i + 0.5 ) = y i + 0.5 − k ( x i + 1 ) − b d_{i}=F(x_{i}+1,y_{i}+0.5)=y_{i}+0.5-k(x_{i}+1)-b di​=F(xi​+1,yi​+0.5)=yi​+0.5−k(xi​+1)−b

则有
y i + 1 = { y i + 1 ( d i < 0 ) y i ( d i ≥ 0 ) y_{i+1}=\begin{cases}y_{i}+1\quad\quad(d_{i}<0) \\\\ y_{i}\quad\quad\quad(d_{i}\geq0)\end{cases} yi+1​=⎩⎪⎨⎪⎧​yi​+1(di​<0)yi​(di​≥0)​
当 d i ≥ 0 d_{i}\geq0 di​≥0时, d i + 1 = F ( x i + 2 , y i + 0.5 ) = d i − k d_{i+1}=F(x_{i}+2,y_{i}+0.5)=d_{i}-k di+1​=F(xi​+2,yi​+0.5)=di​−k

当 d i < 0 d_{i}<0 di​<0时, d i + 1 = F ( x i + 2 , y i + 1.5 ) = d i + 1 − k d_{i+1}=F(x_{i}+2,y_{i+1.5})=d_{i}+1-k di+1​=F(xi​+2,yi+1.5​)=di​+1−k

且 d 0 = F ( x 0 + 1 , y 0 + 0.5 ) = 0.5 − k d_{0}=F(x_{0}+1,y_{0}+0.5)=0.5-k d0​=F(x0​+1,y0​+0.5)=0.5−k

由上可知绘制过程即为绘制像素点和继续对下一个d的值的判断。当时上述的算法并没有摆脱浮点运算,所以改进版使用 2 d Δ x 2d\Delta x 2dΔx代替 d d d,如下:

  1. 输入直线的两端点 P 0 ( x 0 , y 0 ) P_{0}(x_{0},y_{0}) P0​(x0​,y0​)和 P 1 ( x 1 , y 1 ) P_{1}(x_{1},y_{1}) P1​(x1​,y1​)。
  2. 计算初始值 Δ x 、 Δ y 、 d = Δ x − 2 Δ y 、 x = x 0 、 y = y 0 。 \Delta x、\Delta y、d=\Delta x-2\Delta y、x=x_{0}、y=y_{0}。 Δx、Δy、d=Δx−2Δy、x=x0​、y=y0​。
  3. 绘制点 ( x , y ) (x,y) (x,y)。判断 d d d的符号。若 d < 0 d<0 d<0,则 ( x , y ) (x,y) (x,y)更新为 ( x + 1 , y + 1 ) (x+1,y+1) (x+1,y+1), d d d更新为 d + 2 Δ x − 2 Δ y d+2\Delta x-2\Delta y d+2Δx−2Δy;否则 ( x , y ) (x,y) (x,y)更新为 ( x + 1 , y ) (x+1,y) (x+1,y), d d d更新为 d − 2 Δ y d-2\Delta y d−2Δy。
  4. 当直线没有画完时,重复步骤3。否则结束。

改进后的算法很巧妙地避免了进行浮点数运算。编写代码时需要注意的点和DDA算法中一致。

display函数如下

void MyDraw::Bresenham_Draw()
{glClear(GL_COLOR_BUFFER_BIT);glViewport(0, 0, 1000, 600);glColor3f(0, 0, 0);if(x1 == x2 && y1 == y2) {glBegin(GL_POINTS);glVertex2f(x1, y1);glEnd();glFlush();return ;}if(x1 > x2) {   // 保证先绘制的是更靠左的点swap(x1, x2);swap(y1, y2);}int dx = abs(x2 - x1),dy = abs(y2 - y1), d, mark;bool flag;  // flag用于标记斜率绝对值是否大于1if (dx >= dy) { // k的绝对值在0和1之间时flag = false;mark = dx;}else {  // k的绝对值大于1时,交换dx,dyswap(x1, y1);swap(x2, y2);swap(dx, dy);flag = true;mark = dx;}d = dx - 2 * dy;int x = x1, xStep = x2 > x1 ? 1:-1,y = y1, yStep = y2 > y1 ? 1:-1;glBegin(GL_POINTS);for (int i = 1; i <= mark; i++) { // 以步长大的为基准画线if(!flag) glVertex2f(x, y);else glVertex2f(y, x);x += xStep;if (d >= 0) {d -= 2 * dy;}else {y += yStep;d += 2 * (dx - dy);}}glEnd();glFlush();
}

4. 使用方程绘制圆形

使用八分法绘制圆形,利用圆很好的对称特性,只需要画出 ( π 2 , π ) (\frac{\pi}{2},\pi) (2π​,π)区间上的八分之一圆弧,即可绘制出整个圆形。

利用圆形的方程 x 2 + y 2 = R 2 x^2+y^2=R^2 x2+y2=R2可以得到绘制圆形的递推公式:
x i + 1 = x i + 1 x ⊂ [ 0 , R / 2 ] y i + 1 = r o u n d ( R 2 − x i + 1 2 ) x_{i+1}=x_{i}+1 \quad x\subset[0,R/\sqrt{2}] \\\\ y_{i+1}=round(\sqrt{R^2-x_{i+1}^2}) xi+1​=xi​+1x⊂[0,R/2 ​]yi+1​=round(R2−xi+12​ ​)
display函数如下

void MyDraw::DiscreteDrawCircle()
{glClear(GL_COLOR_BUFFER_BIT);glViewport(0, 0, 1000, 600);glColor3f(0, 0, 0);int x, y;double mark =r/sqrt(2);   // 提前计算好判断时使用频繁的变量// glPointSize(10);glBegin(GL_POINTS);// 利用轴对称的特性,使用八分法作出圆形for(x = x1, y = y1 + r; x <= x1 + mark; x++) {  // 以x的变化为步长glVertex2f(x ,y);glVertex2f(x, 2*y1 - y);    // 关于y轴对称glVertex2f(2*x1 - x, y);    // 关于x轴对称glVertex2f(2*x1 - x, 2*y1 - y); // 关于原点对称int xx = x1 + y - y1, yy = y1 + x - x1; // (x, y)关于与坐标轴夹角为45°的直线的对称点glVertex2f(xx ,yy);glVertex2f(xx, 2*y1 - yy);    // 关于y轴对称glVertex2f(2*x1 - xx, yy);    // 关于x轴对称glVertex2f(2*x1 - xx, 2*y1 - yy); // 关于原点对称y = y1 + round(sqrt(r*r-(x-x1)*(x-x1)));}glEnd();glFlush();
}

这种算法的特点和DDA算法接近,都很简单直观易实现,但是运算量有点劝退。

ps:也可以使用极坐标方程实现方程法绘制圆形~

5. 使用Bresenham算法绘制圆形

该算法的思想和Bresenham算法画直线一样,只是方程改成了圆形方程 F ( x , y ) = x 2 + y 2 − R 2 F(x,y)=x^2+y^2-R^2 F(x,y)=x2+y2−R2

使用 d − 0.25 d-0.25 d−0.25代替 d d d优化之后同样摆脱了浮点运算:

  1. 输入圆的半径R
  2. 计算初始值 d = 1 − R 、 x = 0 、 y = R d=1-R、x=0、y=R d=1−R、x=0、y=R
  3. 绘制点 ( x , y ) (x,y) (x,y)及其在八分圆中的另外七个对称点
  4. 判断 d d d的符号。若 d ≤ 0 d≤0 d≤0,则先将 d d d更新为 d + 2 x + 3 d+2x+3 d+2x+3,再将 ( x , y ) (x,y) (x,y)更新为 ( x + 1 , y ) (x+1,y) (x+1,y);否则先将 d d d更新为 d + 2 ( x − y ) + 5 d+2(x-y)+5 d+2(x−y)+5,再将 ( x , y ) (x,y) (x,y)更新为 ( x + 1 , y − 1 ) (x+1,y-1) (x+1,y−1)
  5. 当 x < y x<y x<y时,重复步骤3和4。否则结束

绘图函数如下:

void MyDraw::BresenhamDrawCircle()
{glClear(GL_COLOR_BUFFER_BIT);glViewport(0, 0, 1000, 600);glColor3f(0, 0, 0);int d = 1 - r;glBegin(GL_POINTS);for(int x= x1, y = y1 + r; x-x1 < y-y1; x++) {glVertex2f(x, y);glVertex2f(x, 2*y1 - y);    // 关于y轴对称glVertex2f(2*x1 - x, y);    // 关于x轴对称glVertex2f(2*x1 - x, 2*y1 - y); // 关于原点对称int xx = x1 + y - y1, yy = y1 + x - x1; // (x, y)关于与坐标轴夹角为45°的直线的对称点glVertex2f(xx ,yy);glVertex2f(xx, 2*y1 - yy);    // 关于y轴对称glVertex2f(2*x1 - xx, yy);    // 关于x轴对称glVertex2f(2*x1 - xx, 2*y1 - yy); // 关于原点对称if(d <= 0) {d += 2*(x-x1) +3;}else {d += 2*((x-x1)-(y-y1)) + 5;y--;}}glEnd();glFlush();
}

成果的展示就是一个圈圈……

6. 关于使用类来综合几个display函数

glutDisplayFunc函数的参数是一个没有参数的函数指针,也就是说,起到绘制图形作用的display函数是不允许有参数的。但是这项作业还要求输入坐标绘制图形,感觉使用全局变量不是一个很好的行为(而且全局变量还不允许定义y1……),本着面(shi)对(li)对(cai)象(ji)的思想,我感觉用class来综合这些东西比较合适。让输入的坐标值都作为class中的私有变量,既可以避免使用全局变量,也让整个程序会变得好看很多。

但是这个OpenGL是一个C API,然后在glutDisplayFunc中调用类中的函数时,因为无法识别类中的this指针,所以会报一个这样的错误:

error: invalid use of non-static member function ‘void MyDraw::DDA_Draw()’
glutDisplayFunc(p.DDA_Draw);

上网查了一下资料(比如这里和这里),发现我的原来的想法有点过于美好了。然后改进的方式是把绘图函数和表示坐标的数字改成static的。C++中的static变量和函数和Java里好像差别不小的样子?反正我的初始化最后变成了这个样子:

class MyDraw {
public:MyDraw();void InputInformationOfLine();void InputInformationOfCircle();static void DDA_Draw();static void Bresenham_Draw();static void DiscreteDrawCircle();static void BresenhamDrawCircle();
private:static double x1, y1, x2, y2, r;
};MyDraw::MyDraw() {}double MyDraw::x1 = 0, MyDraw::y1 = 0, MyDraw::x2 = 0, MyDraw::y2 = 0, MyDraw::r = 0;

勉强能用。

ps:虽 然 变 得 更 丑 了

Glut绘制直线和圆相关推荐

  1. Visual C++ MFC编程 绘制直线、圆、自行车

    1.建立工程 右方空白处输入工程名称,然后点击确定 选中基本对话框,点击完成 将右边控件中的图像控件拖到这上面去,然后点击右键,改成位图类型 2.界面设计 布局各自随意,这里用到了静态文本,编辑框,组 ...

  2. java用中点画圆法_OpenGL通过中点法绘制直线和圆

    #include #include static int i=1; void Initial(void) { glClearColor(1.0f, 1.0f, 1.0f, 1.0f); //设置窗口背 ...

  3. 在OpenCV里绘制直线、圆、中文等图形

    前面学习过寻找图像的边缘,需要在图像标记出来,这时就需要在图像上画一些图形,那么这里就来学习cv.line(), cv.circle() , cv.rectangle(), cv.ellipse()来 ...

  4. C++实现glut绘制点、直线、多边形、圆

    C++实现glut绘制点.直线.多边形.圆 必备环境 glut.h 头文件 glut32.lib 对象文件库 glut32.dll 动态连接库 程序说明 C++实现了用glut画点.画直线.画多边形和 ...

  5. OpenGL—直线与圆的绘制

    实验二 直线与圆的绘制 #define GLUT_DISABLE_ATEXIT_HACK #include <glut.h> template<typename T>inlin ...

  6. 结构建模设计——Solidworks软件之草图绘制基础图形工具总结(绘制直线、矩形、圆、槽、圆弧、圆角等)

    [系列专栏]:博主结合工作实践输出的,解决实际问题的专栏,朋友们看过来! <QT开发实战> <嵌入式通用开发实战> <从0到1学习嵌入式Linux开发> <A ...

  7. 运用C#在VS2017的PictureBox控件中绘制简易二自由度机械臂,并且让机械臂实现画直线、圆、人物轮廓及写字的功能。

    运用C#在VS2017的PictureBox控件中绘制简易二自由度机械臂,并且让机械臂实现画直线.圆.人物轮廓及写字的功能. 给大家看看效果吧 演示写字视频在下: VID 首先放置了诸多控件 在给控件 ...

  8. Qt绘制直线、矩形、圆

    Qt绘制直线.矩形.圆 新建一个Qt Widgets Application工程 添加头文件 #include <QPainter> 添加paintEvent函数,代码如下: void M ...

  9. 一般是指用计算机绘制的画面,()一般指用计算机绘制的画面,如直线、圆、圆弧、矩形、任意曲线和图表等。A、图形B、图像C、动画D、图...

    ()一般指用计算机绘制的画面,如直线.圆.圆弧.矩形.任意曲线和图表等.A.图形B.图像C.动画D.图 更多相关问题 minf(X)=x12+3x22一3x1x2+4x1-12x2 电子束焊设备应装置 ...

最新文章

  1. 光电耦合NEC2051 的输入输出特性
  2. PostgreSQL在何处处理 sql查询之九
  3. [Objective-c 基础 - 2.10] description方法
  4. 加号(+)运算符重载
  5. 缺失的第一个正数—leetcode41
  6. 员工培训与开发实训心得体会_公司新员工培训心得体会800字范文
  7. canvas换图时候会闪烁_Canvas实现图片上标注、缩放、移动和保存历史状态,纯干货(附CSS 3变化公式)...
  8. java script中定义的var变量怎么在body中使用,深入分析JavaScriptvar中的预解析与副作用...
  9. SVN-功能介绍之切换
  10. Javascript in one picture
  11. TIOBE 5 月编程语言排行榜:暴涨的 C,逆袭的 Scala
  12. 你要的Chrome插件都在这里了
  13. Airbnb房源信息爬取(二)——获取房源信息
  14. 自己组装电脑需要买哪些配件
  15. 大数据应用型产品设计方法及行业案例介绍(附110页PPT)
  16. NameError: name ‘XXX‘ is not defined
  17. matlab 1向量组,matlab-线性代数 rank 向量组的秩
  18. flutter在导航栏处实现对两个列表的点击事件
  19. VUE实现下载文件,避免浏览器默认直接打开文件
  20. 算法竞赛:几道比较复杂的模拟题

热门文章

  1. java枚举类型原理_Java枚举类接口实例原理解析
  2. STM32F1与STM32CubeIDE编程实例-315M超再生无线遥控模块驱动
  3. 前端基础——一篇文章带你了解HTML语法
  4. 2021年中国钢铁产业发展现状及龙头企业对比分析:宝钢股份优势明显[图]
  5. React UI 组件库 Chakra UI - 04全局组件样式配置
  6. M10淮海西路施工 沪公交138路26路绕道
  7. 关于时间管理的一些技巧
  8. iOS 5.0.1 Redsn0w红雪越狱破解教程[图]
  9. 驱动人生2014 v6.0.9.70 绿色版
  10. Dundas Chart for .NET