2012-10-29 55 views
2

我是C++的新手,但語言對我來說似乎很好。作爲一個學習項目,我決定製作一個小型2D圖形引擎。這可能看起來像一個艱難的項目,但我有一個好主意如何繼續前進。從頭開始繪製大圈子

我還沒有真正開始,但當我遇到這個問題時,我正在形成我的頭腦: 在某些時候,我將不得不做一個在屏幕上畫圓的函數。我現在的做法是這樣的:

in a square with sides from (x-r) to (x+r) loop through x and y, 
if at each point, the current distance sqr(x^2+y^2) is less than or equal to r 
, then draw a pixel at that point. 

這將工作,如果不是,不要打擾告訴我,我會弄明白。如果屏幕上出現x + r & y + r,我只會畫出這個圓。

問題在於我有時需要畫大圈。例如,如果我需要繪製一個半徑爲5000的圓,(如果像素循環計算需要循環總計10000^2次)。所以用2Ghz的處理器,這個單一的圓只能夠達到2Ghz /(10000^2),這是〜22次/秒,佔用了整個核心(相信它只需要每個像素計算一次,這是無處真相)。

現在哪種方法是正確的?我想這與使用GFX進行這些簡單計算有關。如果是這樣,我可以使用OpenGL for C++嗎?我也想知道,以及:)

+3

[中點圓算法](http://en.wikipedia.org/wiki/Midpoint_circle_algorithm) – Andrey

回答

6

我的第一個C/C++項目實際上也是圖形庫。我當時沒有OpenGL或DirectX並且使用DOS。我從中學到了很多東西,因爲我經常發現新的更好(更快)的方式來畫畫。

現代操作系統的問題是,他們並不真的讓你做我當時所做的事情。你不能直接開始使用硬件。坦率地說,這些日子你不再想要了。

你仍然可以自己畫一切。如果您想放置自己的像素,請查看SDL。這是一個C庫,您可以將它們封裝到您自己的C++對象中。它適用於不同的平臺(包括Linux,Windows,Mac ......),並且在內部它將使用諸如DirectX或OpenGL之類的東西。

對於真實世界的圖形,人們不只是去繪製自己的像素。這不是有效的。或者至少不要在不能直接使用硬件的設備上......但是爲了您的目的,我認爲SDL絕對是您的選擇!祝你好運。

+0

+1很好的回答! –

+0

「一個人不會去繪製自己的像素,這是不高效的」< - 有時候「繪製自己的像素」比使GPU執行更有效率。因爲例如在OpenGL中,您需要創建一個需要大約400ms或更長時間的上下文。例如,我懷疑這個網站的自動生成的隨機頭像使用OpenGL或DirectX來呈現它們(這將是愚蠢的,爲此購買GFX卡)。我也不信任GFX卡的穩定性或兼容性。用CPU你知道它會工作。 – Rookie

+2

嗯,是的,但不是繪製半徑爲5000像素的實心圓,對不對?就是那個問題。考慮到這一點,比單獨繪製像素更有效。 – Koder

2

你不會通過手動繪製像素到屏幕上做圖形,這樣的瘋狂謊言。

你想使用的是DirectX或OpenGL。我建議你打開谷歌並閱讀,這裏有很多東西要讀。

一旦你下載了庫,有很多示例項目可以看一看,它們會幫助你開始。

這裏有兩種方法:計算向量的數學方法,描述具有非常多邊的形狀(即它看起來像一個圓)。或者有一種「欺騙」的方法,即用alpha通道在屏幕上繪製一個圓的紋理(即圖片),以使紋理的其餘部分透明。 (作弊方法更容易編碼,執行速度更快,產生更好的結果,儘管它不太靈活)。

如果你想做數學運算,那麼這兩個庫都允許你畫線,因此你需要從每一行的起點和終點開始,而不是單個像素。我,你想要矢量圖形。

我不能做繁重的數學權利,但該向量方法看起來有點像這樣(須藤代碼):

in-param: num_of_sides, length_of_side; 
float angle = 360/num_of_sides; 
float start_x = 0; 
float start_y = 0; 

x = startX; 
y = startX; 
for(int i(0); i < num_of_sides; ++i) 
{ 
    float endX, endY; 
    rotateOffsetByAngle(x, y, x + lenth_of_side, y, angle * i, endX, endY); 
    drawline(x, y, endX, endY); 
    x = endX; 
    y = endY; 
} 


drawline(float startX, startY, endX, endY) 
{ 
    //does code that draws line between the start and end coordinates; 
} 

rotateOffsetByAngle(float startX, startY, endX, endY, angle, &outX, &outY) 
{ 
    //the in-parameters startX, startY and endX, endY describe a line 
    //we treat this line as the offset from the starting point 

    //do code that rotates this line around the point startX, startY, by the angle. 
    //after this rotation is done endX and endY are now at the same 
    //distance from startX and startY that they were, but rotated. 

    outX = endX; 
    outY = endY; //pass these new coordinates back out by reference; 

} 

在上面的代碼中,我們繞了一圈的移動之外一個一個畫出外面的每條線。對於每條線我們都有一個起始點和一個偏移量,然後我們將偏移量旋轉一個角度(這個角度隨着我們在圓周上移動而增加)。然後我們畫出從起點到偏移點的直線。在我們開始下一次迭代之前,我們將起點移動到偏移點,以便下一行從最後一行開始。

我希望這是可以理解的。

+0

該op問是因爲他想將它編碼爲一個學習項目(據我瞭解他)。我認爲做一些基本的事情是沒有問題的,例如逐個像素地繪製一個圓來習慣編程...... –

+0

「手動繪製像素」並不是什麼壞事,因爲它使聲音聽起來很不錯。有時你不想要,或者你不能使用GL/DX。可能有多種原因。 – Rookie

+0

@philip是的,你是對的。我剛剛看到他的O(n2)方法,並將其稍微彎曲一下。我希望他從我的回答中得到的信息是,畫線是更好的方法。我已經添加了一些sudo代碼覆蓋(用相當差的我會承認)使用線繪製一個圓的方法,但我故意將實際繪圖位留空。 – Ian

1

這是一種繪製實心圓的方法。正如你所看到的,它會表現得很慢。

現代圖形是基於抽象出較低層次的東西,使其可以優化;開發人員編寫drawCircle(x,y,r),圖形庫文件+驅動程序可以一直傳遞到芯片,這可以填充適當的像素。

儘管您使用C++編寫,但除非使用圖形驅動程序,否則不會操縱離核心最近的數據。甚至在您的setPixelColour級別方法和通過線路傳遞的實際二進制值之間還有層次的子例程調用;幾乎每一層都有檢查和額外的計算和例程運行。然後,更快速圖形的祕訣就是減少你製作的這些電話的數量。如果您可以將指令drawCircle一路傳送至圖形芯片,請執行此操作。不要在單個像素上浪費任何時間,而只需繪製一個規則的形狀即可。

在一個現代操作系統中,圖形處理層有各種各樣的應用程序的請求,如窗口,合成和其他效果。所以你的'畫畫'的命令已經被幾個圖層中介了。您想要提供給CPU的是將計算卸載到圖形子系統所需的最少信息。

我想說如果你想要學習在屏幕上畫東西,玩帆布和js,因爲開發週期很簡單,而且比較無痛苦。如果您想學習C++,請嘗試使用Euler項目,或者使用現有的圖形庫來繪製東西。如果您想編寫2D圖形庫,請學習像DirectX和OpenGL這樣的底層圖形技術,因爲它們是圖形在現實中的完成方式。但是他們看起來很複雜,你說?那麼你需要先學習更多的C++。他們是出於某些非常好的原因,無論結果如何複雜。

+0

非常棒的閱讀,謝謝:) 我對OpenGL沒有任何問題,我很快就想學習它,但起初看起來對2D來說並不好。 –

1

正如第一個答案所說,你不應該爲了認真的工作而自己做這件事。但如果你只是想這樣做,作爲一個例子,則可能會做這樣的事情:首先定義函數,在屏幕上繪製線段:

void draw_line(int x1, int y1, int x2, int y2); 

這應該是比較容易實現:只要找到方向變化最快,並在使用整數邏輯來迭代該方向時找出另一維度應該改變的程度。即,如果x變化更快,則y = (x-x1)*(y2-y1)/(x2-x1)

然後使用該函數來實現的圓作爲分段線元素:

void draw_circle(int x, int y, int r) 
{ 
    double dtheta = 2*M_PI/8/r; 
    int x1 = x+r, x2, y1 = y, y2; 
    int n = 2*M_PI/dtheta; 
    for(int i = 1; i < n; i++) 
    { 
     double theta = i*dtheta; 
     x2 = int(x+r*cos(theta)); y2 = int(y+r*sin(theta)); 
     draw_line(x1, y1, x2, y2); 
     x1 = x2; y1 = y2; 
    } 
} 

這使用浮點邏輯和三角函數找出哪些線元件最好近似一個圓。這是一個有點粗糙的實現,但我認爲任何想要爲大型團隊提高效率的實現都必須這樣做。

如果只允許使用整數邏輯,一種方法可能是首先繪製一個低分辨率的整數圓,然後將每個選定的像素細分爲更小的像素,然後選擇所需的子像素,等等上。這將按N log N進行縮放,因此仍然比上述方法慢。但是你可以避免犯罪和罪惡。

+0

我必須說,我不喜歡你濫用for循環的方式。理論上它可能看起來不錯,但是你必須知道漂浮/雙打併不精確,而且除此之外,精度會更高。使用乘法,併爲循環使用整數。 – Rookie

+0

你是對的,這個實現是通過連續遞增theta作爲浮點運算來累積浮點錯誤。但是,在這種情況下,這個錯誤完全可以忽略不計。但無論如何我會重寫它。 – amaurea