日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

光栅图形学(一)——直线段的扫描转换算法

發布時間:2023/12/16 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 光栅图形学(一)——直线段的扫描转换算法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 數值微分法

??已知過端點P0(x0,y0)P_0(x_0,y_0)P0?(x0?,y0?)P1(x1,y1)P_1(x_1,y_1)P1?(x1?,y1?)的直線段L(P0,P1)L(P_0,P_1)L(P0?,P1?);直線斜率k=y1?y0x1?x0k = \frac{y_1 - y_0}{x_1 - x_0}k=x1??x0?y1??y0??。畫線的過程為:從xxx的左端點x0x_0x0?開始,向右端點步進,步長為1(像素),按y=kx+by = kx +by=kx+b計算相應的yyy坐標,并取像素點(x,round(y))(x,round(y))(x,round(y))作為當前坐標。但這樣做,計算每一個點需要做一個乘法、一個加法。設步長為Δx\Delta xΔx,有xi+1=xi+Δxx_{i+1} = x_i + \Delta xxi+1?=xi?+Δx,于是
(1.1)yi+1=kxi+1+b=kxi+kΔx+b=yi+kΔxy_{i+1} = kx_{i+1} + b = kx_i + k \Delta x + b = y_i + k\Delta x \tag{1.1} yi+1?=kxi+1?+b=kxi?+kΔx+b=yi?+kΔx(1.1)
??當Δx=1\Delta x = 1Δx=1時,有yi+1=yi+ky_{i+1} = y_i + kyi+1?=yi?+k。即xxx每遞增1,yyy遞增kkk(即直線的斜率)。這樣,計算就由一個乘法和加法減少為一個加法。

  • 算法程序:DDA畫線算法程序
void DDALine(int x0, int y0, int x1, int y1, int color) {int x;float dx, dy, y, k;dx = x1 - x0, dy = y1 - y0;k = dy/dx, y = y0;for(x = x0; x < x1; x++,y+=k){drawpiexl(x, int(y + 0.5), color);} }

注意: 用int(y + 0.5)取整的目的是為取離真正交單近的像素網格點作為光柵化的點。
??應當注意的是,上述算法僅適用于k≤1k \leq 1k1的情形。在這種情況下,xxx每增加1,yyy最多增加1。當∣k∣≥1|k| \ge 1k1時,必須把x,yx,yx,y地位互換,yyy每增加1,xxx相應增加1/k1/k1/k。本節的其它算法也是如此。在這個算法中,yyykkk必須用浮點數表示,而且每一步都要對yyy進行四舍五入后取整,這使得該算法不利于硬件的實現。

2.中點畫線法


??通過觀察可以發現,畫直線段的過程中,當前像素點(xp,yp)(x_p,y_p)(xp?,yp?),下一個像素點有兩種選擇點P1(xp+1,yp)P_1(x_p+1,y_p)P1?(xp?+1,yp?)P1(xp+1,yp+1)P_1(x_p+1,y_p+1)P1?(xp?+1,yp?+1)。若M=(xp+1,yp+0.5)M = (x_p+1,y_p+0.5)M=(xp?+1,yp?+0.5)P1P_1P1?P2P_2P2?的中點,QQQ為理想直線與x=xp+1x = x_p +1x=xp?+1垂線的交點。當MMMQQQ的下方時,P2P_2P2?應為下一個像素點;當MMMQQQ的上方時,應取P1P_1P1?為下一點。這就是中點畫線法的基本原理(可見下圖)。對直線段L(p0(x0,y0),p1(x1,y1)))L(p_0(x_0,y_0),p_1(x_1,y_1)))L(p0?(x0?,y0?),p1?(x1?,y1?))),采用方程F(x,y)=ax+bx+c=0F(x,y) = ax + bx +c = 0F(x,y)=ax+bx+c=0表示,其中a=y0?y1,b=x1?x0,c=x0y1?x1y0a = y_0 - y_1,b = x_1 - x_0,c = x_0y_1 - x_1y_0a=y0??y1?,b=x1??x0?,c=x0?y1??x1?y0?。于是有下述點與LLL的關系:
{線上:F(x,y)=0上方:F(x,y)&gt;0下方:F(x,y)&lt;0\begin{cases} \text{線上}:F(x,y) = 0 \\ \text{上方}:F(x,y) &gt; 0 \\ \text{下方}:F(x,y) &lt; 0 \\ \end{cases} ??????線上F(x,y)=0上方F(x,y)>0下方F(x,y)<0?
??因此,欲判斷MMMQQQ上方還是下方,只要把MMM代入F(x,y)F(x,y)F(x,y),并判斷它的符號即可。構造如下判別式:
(1.2)d=F(M)=F(xp+1,yp+0.5)=a(xp+1)+b(yp+0.5)+cd = F(M) = F(x_p+1,y_p+0.5) = a(x_p+1) + b(y_p+0.5)+c \tag{1.2} d=F(M)=F(xp?+1,yp?+0.5)=a(xp?+1)+b(yp?+0.5)+c(1.2)
??當d&lt;0d &lt; 0d<0MMMLLL(QQQ點)下方,取P2P_2P2?為下一像素;當d&gt;0d &gt; 0d>0MMMLLL(QQQ點)上方,取P1P_1P1?為下一像素;當d=0d = 0d=0,選P1P_1P1?P2P_2P2?均可,約定取P1P_1P1?為下一個像素。
??用式(1.2)計算符號時,需要4個加法和2個乘法。事實上,dddxp,ypx_p,y_pxp?,yp?的線性函數,因此可采用增量計算,提高運算效率。方法如下

  • (1)若d≥0d \ge 0d0,則取正右方像素P1(xp+1,yp)P_1(x_p +1,y_p)P1?(xp?+1,yp?)。判斷下一個像素的位置時,應計算d1=F(xp+2,yp+0.5)=a(xp+2)+b(yp+0.5)+c=d+ad_1 = F(x_p+2,y_p+0.5)= a(x_p+2)+b(y_p+0.5)+c = d + ad1?=F(xp?+2,yp?+0.5)=a(xp?+2)+b(yp?+0.5)+c=d+a,增量為aaa
  • (2)若d&lt;0d &lt; 0d<0,則取正右方像素P2(xp+1,yp+1)P_2(x_p +1,y_p+1)P2?(xp?+1,yp?+1)。判斷下一個像素的位置時,應計算d1=F(xp+2,yp+1.5)=a(xp+2)+b(yp+1.5)+c=d+a+bd_1 = F(x_p+2,y_p+1.5)= a(x_p+2)+b(y_p+1.5)+c = d + a + bd1?=F(xp?+2,yp?+1.5)=a(xp?+2)+b(yp?+1.5)+c=d+a+b,增量為a+ba+ba+b
    ??設從點(x0,y0)(x_0,y_0)(x0?,y0?)開始畫線,ddd的初值d0=F(xp+1,yp+0.5)=F(x0,y0)+a+0.5bd_0 = F(x_p+1,y_p+0.5) = F(x_0,y_0)+a+0.5bd0?=F(xp?+1,yp?+0.5)=F(x0?,y0?)+a+0.5b。因F(x0,y0)=0F(x_0,y_0)=0F(x0?,y0?)=0,則d0=a+0.5bd_0 = a + 0.5bd0?=a+0.5b。由于使用的只是ddd的符號,而ddd的增量都是整數,只是初始值包含小數。因此可以用2d2d2d代替ddd來擺脫點運算,寫出僅包含整數運算的算法。
  • 算法程序:中點畫線算法程序
void MidpointLine(int x0, int y0, int x1, int y1, int color) {int a, b, d1, d2, d, x, y;a = y0 - y1, b = x1 - x0, d = 2*a + b;d1 = 2*a, d2 = 2*(a + b);x = x0, y = y0;drawpixel(x,y,color);while(x < x1){if(d < 0)x++, y++, d+=d2;elsex++, d+=d2;drawpixel(x,y,color)} }

3.Bresenham算法

??Bresenham算法是在計算機圖形學領域內使用最廣泛的直線掃描轉換算法。該方法類似于中點法,由誤差項符號決定下一個像素取右邊點還是右上角。
??算法原理如下:過各行各列像素中心構造一組虛擬網絡線,按直線從起點到終點的順序計算直線與各垂直網絡的交點,然后確定該列像素中與此交點最近的像素。該算法的巧妙在于采用增量計算,使得每一列,只要檢查一個誤差項的符號,就可以確定該列所求的像素。
??如圖所示,設直線方程為y=kx+by=kx +by=kx+b,則有yi+1=yi+k(xi+1?xi)=yi+ky_{i+1} = y_i +k(x_{i+1}-x_i) = y_i +kyi+1?=yi?+k(xi+1??xi?)=yi?+k,其中kkk為直線的斜率。假設xxx列的像素坐標已經確定為xix_ixi?,其行坐標yiy_iyi?,那么下一個像素的列坐標為xi+1x_i + 1xi?+1,而行坐標要么為yiy_iyi?要么為yi+1y_i +1yi?+1。是否增1取決于圖所示的誤差項ddd的值。因為直線的起始點在像素中心,所以誤差項d0=0d_0 = 0d0?=0xxx下標每增加1,ddd的值相應遞增直線斜率值kkk,即d=d+kd = d+kd=d+k。當d≥0.5d \ge 0.5d0.5時,直線與xi+1x_i +1xi?+1列垂直網絡的交點最接近于當前像素(xi,yi)(x_i,y_i)(xi?,yi?)的右上方像素(xi+1,yi+1)(x_i+1,y_i+1)(xi?+1,yi?+1),該像素在yyy方向增加1,同時作為下一次計算的新基點,因此ddd值相應減1;而當d&lt;0.5d &lt; 0.5d<0.5,更接近于右方像素(xi+1,yi)(x_i+1,y_i)(xi?+1,yi?)。為了方便計算,令e=d?0.5e = d - 0.5e=d?0.5eee的初始值為-0.5,增量為k。當e≥0e \ge 0e0,取當前像素(xi,yi)(x_i,y_i)(xi?,yi?)的右上方像素(xi+1,yi+1)(x_i+1,y_i+1)(xi?+1,yi?+1)eee減小1;而當e&lt;0e &lt; 0e<0時,更接近于右方的像素(xi+1,yi)(x_i+1,y_i)(xi?+1,yi?)

  • Bresenham畫線算法程序
void BresenhamLine(int x0, int y0, int x1, int y1, int color) {int x, y, dx, dy;float k, e;dx = x1 - x0, dy = y1 - y0, k = dy/dx;e = -0.5, x = x0, y = y0;for(int i = 0; i < dx; i++){drawpixel(x, y, color);x = x + 1, e = e + k;if(e >= 0)y++, e = e-1;} }

上述Bresenham算法在計算直線斜率與誤差項時用到了小數與除法,可以改用整數以避免除法。由于算法中只用到了誤差項的符號,因此可以做如下替換e′=2?e?dxe&#x27; = 2*e *dxe=2?e?dx

  • 改進的Bresenham畫線算法程序
void BresenhamLine(int x0, int y0, int x1, int y1, int color) {int x, y, dx, dy, e;dx = x1 - x0, dy = y1 - y0, e = -dx;x = x0, y = y0;for(int i = 0; i < dx; i++){drawpixel(x, y, color);x = x + 1, e = e + 2*dy;if(e >= 0)y++, e = e-2*dx;} }

4. 代碼實現(VS2017 + OpenGL)

#include "pch.h" #include <iostream> #include <gl\glut.h> #include <gl\GL.h> #include <gl\GLU.h> using namespace std;void init(void) {glClearColor(1.0, 1.0, 1.0, 0.0); // Set display-window color to white.glMatrixMode(GL_PROJECTION); // Set projection parameters.gluOrtho2D(0.0, 200.0, 0.0, 150.0); }//窗口大小改變時調用的登記函數 void ChangeSize(GLsizei w, GLsizei h) {if (h == 0)h = 1;glViewport(0, 0, w, h);glMatrixMode(GL_PROJECTION);glLoadIdentity();if (w <= h)glOrtho(0.0f, 250.0f, 0.0f, 250.0f*h / w, 1.0, -1.0);elseglOrtho(0.0f, 250.0f*w / h, 0.0f, 250.0f, 1.0, -1.0); }//DDA算法函數 void DDALine(int x0, int y0, int x1, int y1) {glColor3f(1.0, 0.0, 0.0); //設置像素點顏色為紅色glPointSize(2.0f); //設置像素點大小int x;float dx, dy, y, k;dx = x1 - x0, dy = y1 - y0;k = dy / dx, y = y0;for (x = x0; x < x1; x++, y += k){glVertex2i(x, int(y + 0.5));} } //中點畫線算法 void MidpointLine(int x0, int y0, int x1, int y1) {glColor3f(0.0, 1.0, 0.0); //設置像素點顏色為綠色glPointSize(2.0f); //設置像素點大小int a, b, d1, d2, d, x, y;a = y0 - y1, b = x1 - x0, d = 2 * a + b;d1 = 2 * a, d2 = 2 * (a + b);x = x0, y = y0;glVertex2f(x, y);while (x < x1){if (d < 0) {x++, y++, d += d2;}else {x++, d += d1;}glVertex2f(x, y);} } //Bresenham算法畫線 void BresenhamLine(int x0, int y0, int x1, int y1) {glColor3f(0.0, 0.0, 1.0); //設置像素點顏色為藍色glPointSize(2.0f); //設置像素點大小int x, y, dx, dy, e;dx = x1 - x0, dy = y1 - y0, e = -dx;x = x0, y = y0;for (int i = 0; i < dx; i++){glVertex2f(x, y);x = x + 1, e = e + 2 * dy;if (e >= 0)y++, e = e - 2 * dx;} } void display() {int x1_1 = 20, y1_1 = 20, x2_1 = 160, y2_1 = 80;int x1_2 = 20, y1_2 = 40, x2_2 = 160, y2_2 = 100;int x1_3 = 20, y1_3 = 60, x2_3 = 160, y2_3 = 120;glClear(GL_COLOR_BUFFER_BIT);glBegin(GL_POINTS);DDALine(x1_1, y1_1, x2_1, y2_1);glEnd();glBegin(GL_POINTS);MidpointLine(x1_2, y1_2, x2_2, y2_2);glEnd();glBegin(GL_POINTS);BresenhamLine(x1_3, y1_3, x2_3, y2_3);glEnd();glFlush(); }int main(int argc, char **argv) {glutInit(&argc, argv); //GLUT初始化glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); //指出顯示窗口使用單個緩存且使用由紅綠藍三種顏色模型glutInitWindowPosition(500, 500); //設置顯示窗口位置glutInitWindowSize(400, 400); //設置顯示窗口大小glutCreateWindow("Line Algorithm"); //設置顯示窗口的標題glutDisplayFunc(display); //將圖賦值給顯示窗口glutReshapeFunc(ChangeSize);init();glutMainLoop(); //必須是程序的最后一個return 0; }
  • 最終結果

總結

以上是生活随笔為你收集整理的光栅图形学(一)——直线段的扫描转换算法的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。