1. 程式人生 > >從零開始寫光柵化渲染器2:直線繪製光柵化演算法

從零開始寫光柵化渲染器2:直線繪製光柵化演算法

直線繪製光柵化演算法

1.數值微分DDA(Digital Differential Analyzer)演算法

1.1原理

引入增量思想,以dx≥dy(斜率[0,1])為例,考慮直線y=kx+b,當x步進為1時,y步進為k,即yi+1=yi+k,根據四捨五入法即可繪製直線

1.2虛擬碼

y = y1;
for(x:[x1,x2])
{
    drawPixel(x,y);
    y += k;
}

1.3實現程式碼

/************************************************************************/
/* 數值微分DDA(Digital Differential Analyzer)演算法 */ /************************************************************************/ void drawline(int x1,int y1,int x2,int y2,acolor color) { int dx = abs(x2 - x1); int dy = abs(y2 - y1); if (dx>=dy) //以dx=1為步進,否則會出現斷點 { if
(x1>x2) { swap(x1,x2); swap(y1,y2); } float k = static_cast<float>(y2-y1)/(x2-x1); float y = y1; for (int x=x1;x<=x2;++x) { drawpixel(x,y,color); y += k; } } else { if
(y1>y2) { swap(x1,x2); swap(y1,y2); } float k = static_cast<float>(x2-x1)/(y2-y1); float x = x1; for (int y=y1;y<=y2;++y) { drawpixel(x,y,color); x += k; } } }

DDA演算法原理簡單明瞭,但是由於存在浮點數的加減運算,所以效率還需提高。

2.中點畫線演算法

2.1原理

通過一般式Ax+By+C=0來繪製直線,同樣考慮斜率[0,1]情況,對於任意一點P(x,y)我們可以通過B(Ax+By+C)的正負來判斷點P相對於直線的位置,>0時點在直線的上方,=0則在直線上,<0則在直線下方。根據這個原理,我們可以通過判斷點xi+1,yi+0.5相對於直線的位置來進行直線取點。
我們把中點代入f(x,y)
  d0=f(xi+1,yi+0.5)=Axi+Byi+C+A+0.5B=A+0.5B
若取上點,則
  d1=f(xi+2,yi+1.5)=d0+A+B
若取下點,則
  d1=f(xi+2,yi+0.5)=d0+A
這裡我們可以將d乘以2以消除浮點數,避免浮點數運算的開銷,提高運算效率

2.2虛擬碼

d = 2A+B;
y = y1;
for(x:[x1,x2])
{
    drawPixel(x,y);
    if(d<0) //中點在直線下方,取上點
    {
        d += 2(A+B);
        ++y;
    }
    else
    {
        d += 2A;
    }
}

2.3實現程式碼

/************************************************************************/
/* 繪製直線(中點法)
 * 隱式方程f(x,y)=(y1-y1)x+(x2-x1)y+x1y2-x2y1=0
 * B*f(x,y)>0為上,<0為下,每次用中點(x+1,y±0.5)帶入進行比較
 * 此處取B>0,即可直接判斷
 */                                                                     
/************************************************************************/
void drawLine(int x1,int y1,int x2,int y2,AColor color)
{
    int dx = abs(x2 - x1);
    int dy = abs(y2 - y1);
    if (dx>=dy)                                     //以dx=1為步進,否則會出現斷點
    {
        if (x1>x2)
        {
            swap(x1,x2);
            swap(y1,y2);
        }
        int A = y1-y2;
        int B = x2-x1;
        if(y2>=y1)                                  //斜率[0,1]
        {
            int d = (A<<1) + B;                     //f(x+1,y+0.5)*2以消除浮點數運算
            int upIncrement = (A+B)<<1;             //取上點時d的增量
            int downTncrement = A<<1;               //取下點時d的增量
            for (int x=x1,y=y1;x<=x2;++x)
            {
                drawPixel(x,y,color);
                if (d<0){                           //中點在直線下,取上點
                    d += upIncrement;
                    ++y;
                }
                else
                {
                    d += downTncrement;
                }
            }
        }
        else                                        //斜率[-1,0)
        {
            int d = (A<<1) - B;                 
            int upIncrement = A<<1;         
            int downTncrement = (A-B)<<1;                   
            for (int x=x1,y=y1;x<=x2;++x)
            {
                drawPixel(x,y,color);
                if (d<0){                               
                    d += upIncrement;
                }
                else
                {
                    d += downTncrement;
                    --y;
                }
            }
        }   
    }
    else
    {
        if (y1>y2)
        {
            swap(x1,x2);
            swap(y1,y2);
        }
        int A = x1-x2;
        int B = y2-y1;
        if (x2>=x1)
        {
            int d = (A<<1) + B;                 //f(x+0.5,y+1)*2以消除浮點數運算,此處Ay+Bx+C=0
            int upIncrement = (A+B)<<1;         //取上點時d的增量
            int downTncrement = A<<1;           //取下點時d的增量
            for (int x=x1,y=y1;y<=y2;++y)
            {
                drawPixel(x,y,color);
                if (d<0){                       //中點在直線下,取上點
                    d += upIncrement;
                    ++x;
                }
                else
                {
                    d += downTncrement;
                }
            }
        }
        else
        {
            int d = (A<<1) - B;                 
            int upIncrement = A<<1;         
            int downTncrement = (A-B)<<1;           
            for (int x=x1,y=y1;y<=y2;++y)
            {
                drawPixel(x,y,color);
                if (d<0){                       
                    d += upIncrement;
                }
                else
                {
                    d += downTncrement;
                    --x;
                }
            }
        }
    }   
}

3.Bresenham演算法

3.1原理

同樣從簡單的開始考慮(斜率[0,1]),當x步進1時,y提高k,則我們用d記錄提高量,一旦d>0.5,我們便取上點,並對d進行減1操作。同樣,這樣也會出現浮點數加減法運算,首先我們可以通過e=d-0.5的正負來進行判斷,因為對d-0.5的正乘法不會影響其符號,所以我們可以進行如下改變:
第一步:(d-0.5)*2 = 2d-1,消除0.5這個浮點數。
由於d=dy/dx(dy=y2-y1,dx=x2-x1)同樣會產生浮點數,因為斜率為[0,1],所以我們可以保證dx>0
第二步:(2d-1)*dx = 2dy-dx,此時浮點數完全被消除。

3.2虛擬碼

k = 2dy;
e = -dx;
for(x:[x1,x2])
{
    drawPixel(x,y);
    e += k;
    if(e>0)
    {
        ++y;
        e -= 2dx;
    }
}

3.3實現程式碼

/************************************************************************/
/* Bresenham演算法   
 * 主要通過e=d±0.5判斷符號
*/
/************************************************************************/
void drawLine(int x1,int y1,int x2,int y2,AColor color)
{
    int dx = abs(x2 - x1);
    int dy = abs(y2 - y1);
    if (dx>=dy)                                     //以dx=1為步進,否則會出現斷點
    {
        if (x1>x2)
        {
            swap(x1,x2);
            swap(y1,y2);
        }
        int flag = y2>=y1?1:-1;                     //斜率[-1,1]
        int k = flag*(dy<<1);
        int e = -dx*flag;
        for (int x=x1,y=y1;x<=x2;++x) 
        {
            drawPixel(x,y,color);
            e += k;
            if (flag*e>0)
            {
                y += flag;
                e -= 2*dx*flag;
            }
        }
    }
    else
    {
        if (y1>y2)
        {
            swap(x1,x2);
            swap(y1,y2);
        }
        int flag = x2>x1?1:-1;
        int k = flag*(dx<<1);
        int e = -dy*flag;
        for (int x=x1,y=y1;y<=y2;++y) 
        {
            drawPixel(x,y,color);
            e += k;
            if (flag*e>0)
            {
                x += flag;
                e -= 2*dy*flag;
            }
        }
    }
}

4.效果圖

繪製一個圓中的一些半徑,圓心(350,350)

for (int i=0;i<580;i+=5)
    {
        for (int j=0;j<580;j+=5)
        {
            double v = sqrt(pow(i-350,2)+pow(j-350,2))-200;
            if ( v>=0&&v < 2)
            {      
                drawline(350,350,i,j,AColor(0,(1+rand()%255)/255.0f,(1+rand()%255)/255.0f,(1+rand()%255)/255.0f));            
            }
        }
    }

這裡寫圖片描述

專案完整地址:

相關推薦

開始光柵渲染2直線繪製光柵演算法

直線繪製光柵化演算法 1.數值微分DDA(Digital Differential Analyzer)演算法 1.1原理 引入增量思想,以dx≥dy(斜率[0,1])為例,考慮直線y=kx+b,當x步進為1時,y步進為k,即yi+1=yi+k,根據

[Golang] 開始Socket Server(3) 對長、短連接的處理策略(模擬心跳)

microsoft ted 每次 range 點擊 關閉 ade 而在 href 通過前兩章,我們成功是寫出了一套湊合能用的Server和Client,並在二者之間實現了通過協議交流。這麽一來,一個簡易的socket通訊框架已經初具雛形了,那麽我們接下來做的

[Golang] 開始Socket Server(5)Server的解耦—通過Router+Controller實現邏輯分發

       在實際的系統專案工程中中,我們在寫程式碼的時候要儘量避免不必要的耦合,否則你以後在更新和維護程式碼的時候會發現如同深陷泥潭,隨便改點東西整個系統都要變動的酸爽會讓你深切後悔自己當初為什麼非要把東西都寫到一塊去(我不會說我剛實習的時候就是這麼幹

[Golang] 開始Socket Server(4)將執行引數放入配置檔案(XML/YAML)

    為了將我們寫好的Server釋出到伺服器上,就要將我們的程式碼進行build打包,這樣如果以後想要修改一些程式碼的話,需要重新給程式碼進行編譯打包並上傳到伺服器上。     顯然,這麼做過於繁瑣。。。因此常見的做法都是將Server執行中

[Golang] 開始Socket Server(1) Socket-Client框架

第一次跑到網際網路公司實習 。。感覺自己進步飛快啊~第一週剛寫了個HTTP伺服器用於微信公共號的點餐系統~ 第二週就直接開始一邊自學GO語言一邊寫用於Socket的伺服器了。。。 因為發現Golang這一塊資料挺少的,接下來我會在Blog裡把整個Server的Coding,還有遇到的坑都記錄

[Golang] 開始Socket Server(3) 對長、短連線的處理策略(模擬心跳)

    通過前兩章,我們成功是寫出了一套湊合能用的Server和Client,並在二者之間實現了通過協議交流。這麼一來,一個簡易的socket通訊框架已經初具雛形了,那麼我們接下來做的,就是想辦法讓這

開始光柵渲染3三角形光柵

1.平底三角形光柵化 我們已經知道如何繪製一條2D直線,接下來我們開始繪製實心三角形。其實原理比較簡單,如下圖是一個平底三角形,V2.y==V3.y,所以我們只要沿著三角形的兩條邊不斷畫直線,就可以將三角形填滿。首先我們必須確定每條直線的左右兩個點,即VL和V

開始渲染引擎

學圖形學也有一段時間了,對圖形驅動OpenGL也有了基本的理解,但也僅限於API層的瞭解,圖形管線的具體實現對於我來說還是十分抽象的概念,於是就想著自己實現個軟體渲染引擎來加深對圖形學的理解,但又無從下手。網上也下了不少的開源軟渲染引擎,看起來也是相當痛苦。找了很久,居然一

開始JavaWeb框架(第二章節)

oca ext span logs http ioe 請求方法 servlet 類型 這一章太多了。。。好累,不想寫那麽細了,就做一點總結吧。 package org.smart4j.chapter2.controller; import java.io.IOExcep

Elasticsearch入門之開始安裝ik分詞

gpo article terms n) rm -rf 從零開始 系列 pack 默認 起因 需要在ES中使用聚合進行統計分析,但是聚合字段值為中文,ES的默認分詞器對於中文支持非常不友好:會把完整的中文詞語拆分為一系列獨立的漢字進行聚合,顯然這並不是我的初衷。我們來看個

開始STL-容器-雙端隊列

這一 偏移 nis log index end ref 分配 locate 從零開始寫STL-容器-雙端隊列 什麽是雙端隊列?在介紹vector源碼,我們發現在vector前端插入元素往往會引起大量元素的重新分配,雙端隊列(deque)就是為了解決這一問題,雙端隊列中在首

開始STL—functional

binder 保存 函數調用 mark 獲取 AR ref 返回 log function C++11 將任意類型的可調用(Callable)對象與函數調用的特征封裝到一起。 這裏的類是對函數策略的封裝,將函數的性質抽象成組件,便於和algorithm庫配合使用 基本運

開始造一個Markdown編輯(一)

實時 需要 自己實現 自己 背景 學習正則表達式 tex ID img 背景 最近學習正則表達式,於是要挑一個練手項目,恰好對markdown編輯器十分感興趣,於是就進行了一些常識。做了一個簡單的markdown解析器和編輯器。 網頁端的地址(不支持文件的操作): http

開始C# MVC框架之--- 配置log4日誌

寫入 出錯 fill 文件 幫助 fontsize att 日誌處理 引用 在框架中配置日誌分2步,一個是在幫助項目Zy.Utilities--Zy.Utility.Core中新建log類,封裝寫入日誌方法,還需要在Zy.Utility.Core添加 log4net 的引用

一起學習造輪子(三)開始一個React-Redux

導致 href dispatch 判斷 som render connect mis 回調 本文是一起學習造輪子系列的第三篇,本篇我們將從零開始寫一個React-Redux,本系列文章將會選取一些前端比較經典的輪子進行源碼分析,並且從零開始逐步實現,本系列將會學習Prom

開始bootloader

inf boot bsp 開始 src 技術 text tex gif 從零開始寫bootloader

[Golang] 開始Socket Server(6)【完結】日誌模組的設計與定時任務模組模組

好久沒寫文章啦。。。今天把golang挖的這個坑給補完吧~ 作為一個Server,日誌(Log)功能是必不可少的,一個設計良好的日誌模組,不論是開發Server時的除錯,還是執行時候的維護,都是非常有幫助的。 因為這裡寫的是一個比較簡化的Server框架,因此我選擇對Golang本

[Golang] 開始Socket Server(2 自定義通訊協議

        在上一章我們做出來一個最基礎的demo後,已經可以初步實現Server和Client之間的資訊交流了~ 這一章我會介紹一下怎麼在Server和Client之間實現一個簡單的通訊協議,從而增強整個資訊交流過程的穩定性。  

Python開始爬蟲(二)BeautifulSoup庫使用

Beautiful Soup 是一個可以從HTML或XML檔案中提取資料的Python庫, BeautifulSoup在解析的時候是依賴於解析器的,它除了支援Python標準庫中的HTML解析器,還支援一些第三方的解析器比如lxml等。可以從其官網得到更詳細的資訊:http://beau

Python開始爬蟲(一)requests庫使用

requests是一個強大的網路請求庫,簡單易用-讓 HTTP 服務人類。可以參考這個網站的介紹:http://cn.python-requests.org/zh_CN/latest/index.html 直接使用pip install requests安裝此模組之後,開始吧。