好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

OpenGL光栅化作业:【bresenham算法】GL_POINTS为基础画线段

首先来看一下题目要求:

2.2 Draw a Line

Implement your line rasterization algorithm in OpenGL. You can only use integer arithmetic in your code.

Input: 2 2D points, that makes 4 integers, as a start point and an end point Output: A straight line in a window You can only use the  GL_POINTS  as primitives. Other (i.e.  GL_LINES  or  GL_LINE_STRIP ) primitives is not allowed to use.

Language: Only C/C++ is accepted in this homework.

Libraries: OpenGL and freeGLUT/GLFW

IDE: Any IDE can be used.

这里我的的开发环境是mac OS Sierra 10.2.3,使用Sublime做C++语言编写,直接在terminal使用g++编译。使用的library是GLFW,由于编译时需要链接的库文件还是蛮多的,这里也可以用Xcode配置好环境再编写,或者直接用makefile。这个环境配置的问题就下次再说了(其实也不知道下次会拖到什么时候)。

 

首先来审题,要求只用GL_POINTS做primitives,那么从几何数学的角度来看就是用连续的无数个点来集成一条线段。而且考察的是bresenham的光栅化算法,也就是把连续图形转换为计算机可以显示离散的像素点的一种做法,那么题意应该是要将我们理想中的一条【连续】的线段,【光栅化】处理后变成一系列有限的【离散】的点的集合,这些点的数量足够大,相隔间隙足够小,使得在计算机屏幕显示时在视觉上形成一条【看似连续】的线段。

 

然后来谈谈在审完题之后思考的一些问题。

第一,要求我们输入的是两个整数对(代表线段两个端点的坐标),但OpenGL的默认显示坐标范围是[-1,1]区间内的浮点数,而bresenham算法所做的是将连续的线段变为离散的点的坐标(坐标值也为整数对)。所以输入和输出到底要作些什么转化?

输入的是两个整数对,我们在数学上可以通过计算出直线的表达式,知道直线上的无数个点的X、Y坐标的范围,以及它们所遵循的一种关系。光栅化所做的就是对这些点进行一个采样量化的操作,在这连续的无数多个点中适当地采样出足够的点的值(比如在横坐标上进行整数采样),再对另一项坐标值进行量化(比如Y坐标量化得到的值也都是整数)。然后再将我们光栅化后得到的有限的一系列的点(坐标值为整数),将它们的坐标值规格化到[-1,1]。得到合适的浮点数,用OpenGL进行绘制。


规格化这方面就写了个normalize()函数,输入-500到500之间的整数,返回-1到1之间的浮点数。

 

想好了步骤就分部地研究这些问题。

 

首先确定好输入的范围,不确定好输入范围的话用户也不知道你画出的线段是不是他需要的大小比例。由于我们要使屏幕上展示的点的数量足够多以在视觉上形成连续的线段的效果,所以设定的整数范围应该要比较大,我这里默认设置的输入范围是[-500,500]之间的整数,当然如果要实现更加逼近视觉上连续的效果的话,可以设置更大的输入范围,依次类推。  此外,有朋友跟我交流的一种想法是以用户输入整数中的最大绝对值为输入边界,我想了一下,这样线段的形状当然是用户想要的形状,大小的话线段肯定有一端处在窗口的边界上,这么做是不是要涉及到什么动态的设置我就不清楚了,因为我写的不是这个想法(……)。

 

然后不管其他,先只考虑位于线段所在直线经过第一象限与x轴夹角小于45度,也就是0<m<1的情况。bresenham算法推导过程不再赘述,直接上结论。因为m=0或者1时画出来的线段也是差不多的,就直接算作0<=m<=1的情况了。

插播一下一下在OpenGL中怎么实现画多个点连成线。我的想法是用分别用两个长度相等的浮点数数组fxarray[]和fyarray[]存储规格化后的xi和yi的值,(其实对就是在光栅化计算出的两个整数数组做一下规格化运算得到的),然后在绘制点的时候循环调用。

  for ( int  i= 0 ;i<length;i++ ) {
                glVertex2f(xarray[i],yarray[i]);
            } 

 

因为在这种情况下,我们选择在x方向上采样,并在y方向上量化。x0到x1之间每个xi的值是固定的,xi=x0+i,而bresenham算法是根据以上过程递推出yi的值。所以我用一个整数数组array[]存储y0到y1之间每个yi的值,array[0]和array[n-1]分别最初赋值y0和y1的值,n为从x0到x1之间的距离。输入dx,dy,和p,array[]的地址及长度到一个递推函数。最初传入的p为p0=2dy-dx,递推结束后数组array就存储了计算出的y0到yn之间的值。

 void  bresenham( int  array[],  int  p,  int  i,  int  length,  int  dx,  int   dy) {
      if (i==length- 1  )
          return  ;
      int   pnext;
      if (p<= 0  ) {
        array[i + 1 ]= array[i];
        pnext =p+ 2 * dy;
    }
      else   {
        array[i + 1 ]=array[i]+ 1  ;
        pnext =p+ 2 *dy- 2 * dx;
    }
    bresenham(array,pnext,i + 1  ,length,dx, dy);
} 

 

然后就是考虑其他几种情况的问题了。

(1)m不存在(线段与y轴平行或重合)

(2)-1<=m<0

(3)m>1

(4)m<-1。

情况(1)比较简单,xarray[]中的值一直不变,存储y的数列直接从最低点一直递增最高点。(这里默认y0<=y1,同样在|m|<1的情况下默认x0<x1,|m|>1的情况下默认y0<y1,方便分析。控制这个在前面写个条件判断的交换函数也很容易。)

     if (x0== x1) {
          if (y0> y1) {
              int  tempy= y0;
            y0 = y1;
            y1 = tempy;
        }
        length =y1-y0+ 1  ;
          for ( int  i= 0 ;i<length;i++ ) {
            xarray[i] = x0;
            yarray[i] =y0+ i;
        } 

 

因为数学差(……)不想做其他情况的bresenham算法推导,所以直接采用了对称变换的思想。直接改变传入bresenham()中的参数并作一些相应的变换来求结果。没有去网上参考其他人是什么样的做法,也不知道这种对称变换+数组记录的方式会不会更复杂了吧(……)。

情况(2)其实和最开始讨论的0<=m<=1完全关于y轴对称,只需要先把既定的y0和y1的值关于y轴做一下轴对称变换(也就是*-1),用bresenham计算出来的yarray[]一定与我们要求的yarray[]关于y轴对称,再*-1即可求得。当然,由于y的值做了变换,所以传入bresenham算法中的与y有关的参数也有了相应的改变。以下是|m|<=1时的两种情况的计算方式。

  if (fabs(m)<= 1  ) {
            length =x1-x0+ 1  ;
              for ( int  i= 0 ;i<length;i++ )
                xarray[i] =x0+ i;
              //  0<m<=1 
             if (dy>= 0  ) {
                yarray[  0 ]= y0;
                yarray[length - 1 ]= y1;
                  int  p0= 2 *dy- dx;
                bresenham(yarray,p0,   0  , length,dx,dy);
            }   else   {
                  //  -1<=m<0 
                yarray[ 0 ]=- y0;
                yarray[length - 1 ]=- y1;
                  int  p0= 2 *(-dy)- dx;
                bresenham(yarray,p0,  0 ,length,dx,- dy);
                  for ( int  i= 0 ;i<length;i++ )
                    yarray[i] =- yarray[i];
            } 

 然后到了情况(3)和(4)。先考虑情况(3)。情况(4)与上同理,只需要做一个关于y轴的轴对称变换。

由于|m|>1,y轴上的变化尺度要比x轴上的大,所以应该在y轴上取样,在x轴上量化。这里相当于是把水平直角坐标系逆时针方向旋转了90度来思考问题,m>1的情况与y轴正方向的夹角为0~45度,就相当于最初讨论的最基本0<=m<=1的情况。length改为从y0到y1的距离,向bresenham算法中传入的数组也变为了xarray[],传入参数变成了p0=2*dx-dy。这个旋转坐标系的想法吧说难也不难,不过刚开始想的时候翻车了挺多次的(……),想明白了就好。底下是|m|>1的情况(3)(4)。

 }  else   {
            length =y1-y0+ 1  ;
              for ( int  i= 0 ;i<length;i++ )
                yarray[i] =y0+ i;
              if (dx>= 0  ) {
                  //  m>1 
                xarray[ 0 ]= x0;
                xarray[length - 1 ]= x1;
                  int  p0= 2 *dx- dy;
                bresenham(xarray,p0,  0  ,length,dy,dx);
            }   else   {
                  //  m<-1 
                xarray[ 0 ]=- x0;
                xarray[length - 1 ]=- x1;
                  int  p0 =  2 *(-dx)- dy;
                bresenham(xarray,p0,  0 ,length,dy,- dx);
                  for ( int  i= 0 ;i<length;i++ )
                    xarray[i] =- xarray[i];
            } 

然后在进行对输入(x0,y0), (y0, y1)的处理就是在进行bresenham算法处理之前,保证当|m|<=1时,x0<=x1,|m|>1时,y0<=y1,|m|不存在时,y0<=y1。这里就不详细解释了。直接看完全代码。

   1  #include <iostream>
   2  #include <math.h>
   3  #include <GLFW/glfw3.h>
   4   using   namespace   std;
    5   //  g++ -o line line.cpp -lglfw3 -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo 
   6   void  show( float  xarray[], float  yarray[], int   length) {
    7       bool  init =  true  ;
    8       if (! glfwInit()) {
    9          cout <<  "  Initialization failed  "  <<  endl;
   10          init =  false  ;
   11           //  return? 
  12       }
   13      
  14       //  create a window with its OpenGL context 
  15      GLFWwindow* window1 = glfwCreateWindow( 640 ,  640 ,  "  line  "  , NULL, NULL);
   16      
  17       if  (! window1) {
   18          cout <<  "  Window or OpenGL context creation failed  "  <<  endl;
   19           glfwTerminate();
   20           //  return? 
  21       }
   22       if (init&& window1) {
   23           //   Make the window's context current */ 
  24           glfwMakeContextCurrent(window1);
   25          
  26           while  (! glfwWindowShouldClose(window1)) {
   27               //   Keep running 
  28               /*   Draw a triangle   */ 
  29               glBegin(GL_POINTS);
   30              
  31              glColor3f( 1 ,  0.52 ,  0.0 );     //   Orange 
  32               for ( int  i= 0 ;i<length;i++ ) {
   33                   glVertex2f(xarray[i],yarray[i]);
   34               }
   35              
  36               glEnd();
   37              
  38               //  When the frame is rendered, swap the buffer with one another 
  39               glfwSwapBuffers(window1);
   40              
  41               /*   Poll for and process events   */ 
  42               glfwPollEvents();
   43           }
   44          
  45           if  (glfwWindowShouldClose(window1))
   46               glfwDestroyWindow(window1);
   47          
  48           //  done before exit 
  49           glfwTerminate();
   50       }
   51   }
   52  
  53  
  54   float  normalize( int   input) {
   55       return   float (input)/ 500  ;
   56   }
   57   void  bresenham( int  array[],  int  p,  int  i,  int  length,  int  dx,  int   dy) {
   58       if (i==length- 1  )
   59           return  ;
   60       int   pnext;
   61       if (p<= 0  ) {
   62          array[i+ 1 ]= array[i];
   63          pnext=p+ 2 * dy;
   64       }
   65       else   {
   66          array[i+ 1 ]=array[i]+ 1  ;
   67          pnext=p+ 2 *dy- 2 * dx;
   68       }
   69      bresenham(array,pnext,i+ 1  ,length,dx, dy);
   70   }
   71  
  72   int   main() {
   73       int   x0, x1, y0, y1;
   74      cout <<  "  Please gives the x and y value of two points  "  <<  endl;
   75      cout <<  "  with one space between them (like: 'x y')  "  <<  endl;
   76      cout <<  "  Notice: the x and y value should be integers between -500 and 500  "  << endl <<  endl;
   77      
  78      cout <<  "  the x and y value of the first point?  "  <<  endl;
   79      cin >> x0 >>  y0;
   80      cout <<  "  the coordinate of the first point: (  "  << x0 <<  "  ,   "  << y0 << "  ).  "  <<  endl;
   81      
  82      cout <<  "  the x and y value of the second point?  "  <<  endl;
   83      cin >> x1 >>  y1;
   84      cout <<  "  the coordinate of the second point: (  "  << x1 <<  "  ,   "  << y1 << "  ).  "  <<  endl;
   85      
  86       int   length;
   87       int  xarray[ 1001 ], yarray[ 1001  ];
   88       int   dx;
   89       int   dy;
   90      
  91       if (x0== x1) {
   92           if (y0> y1) {
   93               int  tempy= y0;
   94              y0= y1;
   95              y1= tempy;
   96           }
   97          length=y1-y0+ 1  ;
   98           for ( int  i= 0 ;i<length;i++ ) {
   99              xarray[i]= x0;
  100              yarray[i]=y0+ i;
  101           }
  102      }  else   {
  103           //  |m|<=1,let point 1 be on the right of point 0 
 104           float  m= float (y1-y0)/ float (x1- x0);
  105           if (fabs(m)<= 1 &&x0> x1) {
  106               int  tempx= x0;
  107               int  tempy= y0;
  108              x0= x1;
  109              y0= y1;
  110              x1= tempx;
  111              y1= tempy;
  112           }
  113           //  |m|>1, let point 1 be on the top of point 0 
 114           if (fabs(m)> 1 &&y0> y1) {
  115               int  tempy= y0;
  116               int  tempx= x0;
  117              y0= y1;
  118              x0= x1;
  119              y1= tempy;
  120              x1= tempx;
  121           }
  122          dx=x1- x0;
  123          dy=y1- y0;
  124          m= float (dy)/ float  (dx);
  125          
 126           if (fabs(m)<= 1  ) {
  127              length=x1-x0+ 1  ;
  128               for ( int  i= 0 ;i<length;i++ )
  129                  xarray[i]=x0+ i;
  130               //  0<m<=1 
 131               if (dy>= 0  ) {
  132                  yarray[ 0 ]= y0;
  133                  yarray[length- 1 ]= y1;
  134                   int  p0= 2 *dy- dx;
  135                  bresenham(yarray,p0,  0  , length,dx,dy);
  136              }  else   {
  137                   //  -1<=m<0 
 138                  yarray[ 0 ]=- y0;
  139                  yarray[length- 1 ]=- y1;
  140                   int  p0= 2 *(-dy)- dx;
  141                  bresenham(yarray,p0, 0 ,length,dx,- dy);
  142                   for ( int  i= 0 ;i<length;i++ )
  143                      yarray[i]=- yarray[i];
  144               }
  145          }  else   {
  146              length=y1-y0+ 1  ;
  147               for ( int  i= 0 ;i<length;i++ )
  148                  yarray[i]=y0+ i;
  149               if (dx>= 0  ) {
  150                   //  m>1 
 151                  xarray[ 0 ]= x0;
  152                  xarray[length- 1 ]= x1;
  153                   int  p0= 2 *dx- dy;
  154                  bresenham(xarray,p0, 0  ,length,dy,dx);
  155              }  else   {
  156                   //  m<-1 
 157                  xarray[ 0 ]=- x0;
  158                  xarray[length- 1 ]=- x1;
  159                   int  p0 =  2 *(-dx)- dy;
  160                  bresenham(xarray,p0, 0 ,length,dy,- dx);
  161                   for ( int  i= 0 ;i<length;i++ )
  162                      xarray[i]=- xarray[i];
  163               }
  164           }
  165       }
  166      
 167       float  fxarray[ 1001  ];
  168       float  fyarray[ 1001  ];
  169       for ( int  i= 0 ;i<length;i++ ) {
  170          fxarray[i]= normalize(xarray[i]);
  171          fyarray[i]= normalize(yarray[i]);
  172       }
  173      
 174       show(fxarray,fyarray,length);
  175      
 176      
 177      
 178       return   0  ;
  179  }

 

结果展示

m不存在的情况

 

 

m=0的情况

 

0<m<=1

 

-1<=m<0

 

m>1

 

m<-1

查看更多关于OpenGL光栅化作业:【bresenham算法】GL_POINTS为基础画线段的详细内容...

  阅读:40次