wu反走樣(Anti-aliased)直線

可能有圖片或者下載的鏈接不對,請到原始地址查看.http://dgame.yeah.net

========================================================

普通的Breshenham算法畫線很快,但並不是很精細.通常的整數畫線因爲只能在整數座標上繪圖,所以產生難看的鋸
齒.我在Michael Abrash的一本書力看到一個很好的反走樣直線畫法,並決定用非整數座標改進它.

一個wu直線不僅僅是看上去比一個普通直線好,它也產生更好的動畫.一個普通的直線從一個位置簡單的跳到下一個
位置.然而,一條wu直線非常悠閒的漂到下一個位置.
它是如何工作的?

讓我們想一下一個反走樣直線究竟意味這什麼.一條恰當的直線看上去應該是什麼樣的?直線有多粗?尾端應該是怎
麼樣的?直線是一個一維物體,無限長.它沒有什麼粗細.線段的尾端看上去什麼都不象,它就是尾端而已.這樣一個直
線不能被畫到一個基於像素的顯示設備上.

在計算機圖形中,通常假定線段爲一個像素粗.這意味這它可以被畫下來.這也意味着他足夠細使得不必考慮它的末
端應該是什麼樣子,因爲它比一個像素還小,不可以畫.

在我們設計一個新的wu直線的時候我們必須制定類似的假定.因爲這個新的算法可以處理非整數座標的端點,對於一
個小於一像素長的線能做什麼呢?

假定
.假定像素在它們座標的中心
.直線是一個像素粗
.直線的末端的形狀不重要
它可以更好,如果:
.兩個平行線段,端點相連接,不能分辨出其中一個,一個長線段
.直線的亮度/透明的可以被定義

最終的算法是怎樣的?

最終的算法相當容易實現,但是在實際應用中顯得太慢.但也不是真正的必要,因爲它的效果和另一個更快速的方法
幾乎相同.不管怎樣,我將闡明一些基本的東西.

想像一下,你可以可以放大我們在上面畫線的屏幕的像素.理想的畫線算法將計算每條線覆蓋的精確的區域,從而增
加那些像素的亮度.
這樣一個算法很容易寫,在一個更高的分辨率重畫屏幕的相關部分,然後把線畫到上面,計算出覆蓋的像素或者
calculating it presicely.

然而這需要一些精確度.我們來看下一種方法.

一個更明智的算法



這個算法跨騎着直線繪製一對像素.一個主循環將沿着直線的長度畫一對像素,端點的像素對將被分別計算.

像素對:
再想像一次,屏幕的一個特寫.畫一條几乎水平的線.
這個幾乎水平的線穿過垂直的像素列.每次跨過像素,x座標是整數,但是y座標是非整數.
看的更近些,一個單個的跨越點:

                           

在每個跨越點,我們將考慮跨騎直線的像素對.兩個像素的亮度相應應該是1,中心點的亮度就是理想直線的亮度.

直線上的像素的亮度必須於(1-a)成比例,線下面的像素的亮度必須與(1-b)成比例.也就是說越靠近直線的像素應該
越亮.

沿着直線長度畫這樣的像素對,那你基本上得到了一個反走樣直線.

繪製端點

最後的事情就是畫端點.這不好處理,我仍然沒有完全正確的處理它們.這裏是我現在用的一種方法.他不很理想,但
是非常近似.你將注意到在直線從幾乎垂直變得幾乎水平的時候它有小心的失靈,反過程也是.你將在直線緩慢的移
動過45度的時候注意到這點.

真正近距離的看一下端點:

可以看到直線的一個端點,用紅色的點來表示.藍色的點表示像素的中心點.你可以看到,頂點不在像素的中心點上.

計算直線端點的兩個像素的正確亮度:
.把直線延長(向後或向前)到最近的整數x座標(p).向計算直線上普通的像素對的亮度一樣計算這個像素對.然後,因
爲直線只是覆蓋這個像素的很小一部分(i),直線將降低它的亮度.所以亮度應該乘上i.因此,當整條直線向右移動,i
將變小,使得這個像素對平滑的變暗.

特殊的情況

一個長度小於一像素的直線將是怎樣的呢?因爲它的長度太小以至不能精確表現,所以你可以隨便按它應該的模樣畫
.這是我自己的實現方法,我把線拉伸到一個像素常,然後降低它們的亮度.所以一個非常短的直線看上去很暗,當它
變長,它就會變亮,當他是一個像素常,它在所在點上將被正常的畫出.

最後,一些僞代碼

wu直線的定點數計算需要的一些函數:
    function trunc(x)
return integer part of x
    end of function

    function frac(x)
return fractional part of x
    end of function

    function invfrac(x)
return 1 - (fractional part of x)
    end of function
wu直線程序:
    procedure WuLine(fixpt x1, fixpt y1, fixpt x2, fixpt y2)

        variable declerations:
            fixpt variables:
		grad, xd, yd, length,xm,ym
		xgap, ygap, xend, yend, xf, yf
		brigheness1, brigheness2

	    integer variables:
		x, y, ix1, ix2, iy1, iy2

	    byte variables:
		c1,c2

	code starts here:

	    Width and Height of the line
	    xd = (x2-x1)
	    yd = (y2-y1)
	
	   
	    if abs(xd) > abs(yd) then			check line gradient
	        horizontal(ish) lines


		if x1 > x2 then				if line is back to front
		    swap x1 and x2			then swap it round
		    swap y1 and y2
	            xd = (x2-x1)				and recalc xd & yd
	            yd = (y2-y1)
		end if

	 	grad = yd/xd                             gradient of the line
		

		End Point 1
		-----------

		xend = trunc(x1+.5)                      find nearest integer X-coordinate
		yend = y1 + grad*(xend-x1)               and corresponding Y value
		
		xgap = invfrac(x1+.5)                    distance i
		
		ix1  = int(xend)                         calc screen coordinates
		iy1  = int(yend)
	
		brightness1 = invfrac(yend) * xgap       calc the intensity of the other 
		brightness2 =    frac(yend) * xgap       end point pixel pair.
		
		c1 = byte(brightness1 * MaxPixelValue)	 calc pixel values
		c2 = byte(brightness2 * MaxPixelValue)	

		DrawPixel(ix1,iy1), c1			 draw the pair of pixels
		DrawPixel(ix1,iy1+1), c2

		yf = yend+grad                           calc first Y-intersection for
                                                         main loop

		End Point 2
		-----------

		xend = trunc(x2+.5)                      find nearest integer X-coordinate
		yend = y2 + grad*(xend-x2)               and corresponding Y value
		
		xgap = invfrac(x2-.5)                    distance i
		
		ix2  = int(xend)                         calc screen coordinates
		iy2  = int(yend)
	
		brightness1 = invfrac(yend) * xgap       calc the intensity of the first 
		brightness2 =    frac(yend) * xgap       end point pixel pair.
		
		c1 = byte(brightness1 * MaxPixelValue)	calc pixel values
		c2 = byte(brightness2 * MaxPixelValue)	

		DrawPixel(ix2,iy2), c1			draw the pair of pixels
		DrawPixel(ix2,iy2+1), c2



		MAIN LOOP
		---------

		Loop x from (ix1+1) to (ix2-1)			main loop

		    brightness1 = invfrac(yf)		        calc pixel brightnesses
		    brightness2 =    frac(yf)

  		    c1 = byte(brightness1 * MaxPixelValue)	calc pixel values
		    c2 = byte(brightness2 * MaxPixelValue)	

		    DrawPixel(x,int(yf)), c1			draw the pair of pixels
 		    DrawPixel(x,int(yf)+1), c2

	            yf = yf + grad				update the y-coordinate

		end of x loop					end of loop

	    else
		vertical(ish) lines

		handle the vertical(ish) lines in the
		same way as the horizontal(ish) ones
		but swap the roles of X and Y
	    end if
    end of procedure


這裏是上面所說的算法的更多的具體描述.爲了方便和速度,我在這裏使用了定點數.在這種情況下,它顯然很方便.
首先,我定義了一些函數.

最後

它被證明是真的可以工作的,而且看上去很奇妙,這裏有一個DEMO.


這個程序在兩種分辨率下畫了一個Newton's Cradle,來演示wu直線可以畫很小的東西,並且仍然看上去ok.你可以看到它們移動的多平滑.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章