透視裁剪到底發生在哪個階段?

我看real time rendering以及遊戲引擎架構上,說的都是經過透視投影后,Frustum對應的觀察體轉換成了CVV(拿opengl來說,其坐標空間是 -1,-1-1—— 1,1,1),然後在這個空間做裁剪。但是知乎上看到很多人都說裁剪髮生在透視除法之前,這是咋回事?透視除法之前,觀察體不是歸一化的啊。

-------------------------------------------------

下面是新增的:

現在突然想明白了,拿openGL來說,點p(a,b,c,1)透視投影后得到的齊次坐標(x,y,z,w)中,w的值就是-c,如果c=0,那麼透視除法除以w(w=0) 就是除零錯誤。所以在透視除法之前必然要把這樣的點裁剪掉,也就是透視除法之前做裁剪。

那麼問題又來了,為什麼經典的RTR跟遊戲引擎架構,都說裁剪是在標準觀察體CVV中進行,坐標變換到CVV時,必然已經做了透視除法了。這難道不會遇到前面說的除0問題嗎?

396頁,「在裁剪空間中,標準的觀察體積是一個長方體,其x軸和y軸的範圍都是-1~+1.在z軸方向,觀察體不是從-1延伸至+1(openGL),就是0~1(DirectX)。我們稱此坐標系統為裁剪空間,因為觀察體積的平面是和坐標系統里的軸對齊的,使按觀察體積裁剪三角形變得方便"

上面是《遊戲引擎架構》內的原話,這難道不是說裁剪是在CVV中進行嗎,還是我理解有問題?


不容易啊,這年頭還有人在糾結這些很基礎的原理。

事情說起來很簡單,w 平面裁剪是一個特殊過程,必須在 /w 之前的四維空間下進行裁剪,保證所有頂點的 w &> 0,後面歸一化 / w 時也才不會除零錯誤。

後面各個具體 CVV 平面,一樣可以在 /w 之前進行四維裁剪,簡單點也可以 /w 以後用三維坐標進行 [-1, 1] 的裁剪,不是難點。

三角形被 w = 0 平面裁剪後會變成 0-2 個新的三角形,並且保證所有新三角形的頂點 w 都 &> 0, 然後送給其他平面裁剪,最終裁剪完加起來會得到 0-n 個新的三角形,完全處在 cvv 內,以便送給下一級流水線具體渲染了。

所以關鍵點在於如何實現這個四維空間的三角形裁剪?很簡單,常見軟體實現里,裁剪一個三角形就是從裁剪一條線段開始的,靠一個 「四維線段裁剪」 的函數來實現的:

int clip_line_w_zero(const vertex *p1, const vertex *p2, vertex *p3);

p1 是起點,p2是終點,p3 是四維空間里線段 P1-&>P2 和 w = 0 這個平面相交的點,結果有幾種:P1 和 P2 都在 w &> 0 一側,不用裁剪;P1 + P2 都在 w &< 0 一側,直接丟棄;P1, P2 位於 w=0 平面的兩側,求解線段和平面 w=0 的交點 P3 出來,就是我們的裁剪點。

一般是將 P1-&>P2 這條直線用 t 來表示:

P = P1 + (P2 - P1) * t

展開成:

x = x1 + (x2 - x1) * t
y = y1 + (y2 - y1) * t
z = z1 + (z2 - z1) * t
w = w1 + (w2 - w1) * t

接下來就好辦了,和 w = 0相交就是把 w = 0 帶入到第四個式子里,計算出 P3 的 t值來,然後 P3 的其他幾個分量就帶入算出來了。

這裡有個坑,裁剪後 p3 恰巧落在了 w = 0 平面了,做除法,這不就除 0 了么?我們要的是 w &> 0 而不是 w = 0啊,怎麼裁剪出剛好 w &> 0 呢?好辦,工程上到處是妥協的辦法。

比如 TinyGL 的做法是,把 w=0 平面往前推進那麼一點點,變成 w=E,E是浮點數里一個很小的近似0的值 ,比如可以取 +1E-20 之類的,將上面的 w=0 平面裁剪改為 w = E 帶入上面的方程組:

t = (E - w1) / (w2 - w1)

帶入上面直線方程,把 P3的 [x, y, z, w] 全部求解出來(實際還需要繼續插值求出P3 的其他各項欄位,如紋理坐標,法向量等 attribute),就完成了線段的裁剪,而前面說的三角形裁剪就是基於線段裁剪實現的。

對三角形進行了 w &> 0 以後做什麼呢?你可以繼續在四維空間里用上述方式把整個 cvv 的包圍平面( z &> 0, z &< w, x &> -w, x &< w ...) 給裁剪完,或者簡單點,可以先歸一化 / w了,然後用 [-1, 1] 裁剪。

最後一個坑,浮點數誤差,P3 這個點的值 w = E,近似零的值了,要取的比較小,但又不能太小,你要搞到接近單精度浮點的 1.4E-45 之類的單精度極限值的話,歸一化 /w 時,P3其他幾個值就都他媽失真,接近無窮大了。

---

所以這裡就有一個優化,歸一化前把近平面裁剪(z = 0)這個特殊的步驟提前做了,先對 z = 0 進行裁剪,再到 w &> 0 裁剪,然後歸一化/w,再用 [-1, 1] 的範圍進行其他平面裁剪。

由於一般乘完 mvp 到歸一化前,z和w的關係基本上是 z &< w,而同時 z, w 是線性關係,在前面裁剪 w &> 0 平面之前,先裁剪 z = 0,方法同上。再用上面的傳統步驟裁剪 w &> 0,這樣歸一化前,保證 w &> 0 的同時,把近平面裁剪也提前做了。如果時標準的 mvp 投影矩陣,基本滿足 z = 0 的時候,w 也必然 &> 0了,後面的 w &> 0 裁剪運算基本不會發生,只是兜底要再過一遍。

這樣 P3 得到的 w 值就會比較大一些,不會是那個很小的 E,後面做除法,精度會高很多,這算一個優化吧。

---

裁剪整套實現下來,至少也幾百上千行,而且大量的運算,所以早年的 3D引擎基本就不做裁剪,特別是近平面的裁剪,就是判斷下如果三角形任何一個定點裡 z &< 0,或者 w &<= 0,那麼直接剔除整個三角形,這樣做最省事,所以你看有些早年遊戲鏡頭不斷接近某些物體時,你會看到這些物體的某個面突然一下就整塊消失了,就是這個原因。為了緩解這種突然消失帶來的突兀,常見的做法是直接在模型空間中,把離鏡頭近的大三角形拆成好幾塊小的,這樣當整個三角形因為一個定點越界而被整塊剔除時,感覺不會那麼突兀。


首先你理解肯定有問題,原文可能也有一些模糊(沒有上下文不好判斷,只能臆測一下)。

業界通用的裁剪肯定是發生在裁剪空間(也就是透視除法前)的,這個我很久以前就在另一個問題中做過詳細的回答:

徐行:GPU在進行vertex shading之後,rasterization之前,是怎麼剪裁的?

下面來說說你的理解問題:

可能你沒好好看標點符號。

原文說,「……在裁剪空間中,標準的觀察體積是一個長方體……。我們稱此坐標系統為裁剪空間……」。

注意它中間的句號,前一句是在描述標準觀察體(canonical view volume, CVV),後一句是說之前介紹的某一種坐標系統稱為「裁剪空間」。

你似乎錯誤的認為後一句是在描述前一句的,然後就錯誤的認為 CVV 是裁剪空間了。

臆測一下原文的問題:

實際上,裁減空間是4維的(全稱是齊次裁剪空間),頂點做完投影變換後就在裁剪空間,這個空間中的形體不能用慣常的幾何思維來理解,所以原文里 「在裁減空間中,標準的觀察體積是一個長方體」 的說法是模糊的,容易引起誤解的。


  1. 關於昨天的討論

知乎用戶:點經過透視矩陣變化後得到齊次坐標,進行裁剪時w分量的含義?

昨天在這個回答下面跟你討論了硬體如何在裁剪空間剔除顯然完全不可見三角形,以及如何在光柵化剔除三角形不可見部分。同時也討論了一下從硬體實現角度為何不做裁剪。

那個回答裡面的圖來自於下面這篇的論文。Triangle scan conversion using 2D homogeneous coordinates

2. 關於cvv做裁剪的局限性

你的提到的w = 0的情況有問題,接著1中提到的圖,再進行一些解釋吧。

齊次坐標系(x, y, z, w)可以理解為一個四維坐標,透視除法是將整個空間映射到w = 1的超平面。

A trip down the graphics pipeline: the homogeneous perspective transform

之前例子寫錯了,現在重新從《計算機圖形學的演算法基礎(原書第二版)》這本書找了個例子p185

P1[0, 1, 6, -1/5] P2[0, -1, -6, 11/5]

注意到點P1P2變換後的齊次坐標符號相反,這說明直線經由無窮處卷回。即當投影到平面h = 1時,直線叢P1離開P2向無窮遠處伸展然後再卷回P2.除以齊次坐標分量得到的通常坐標為:

P1(0, -5, -30)和P2(0, -5/11, -30/11)

此時如果使用cvv裁剪,俺么這兩個點一定無法體現出從無窮處迴轉。

其實齊次坐標系在圖形學的應用,除了mvp矩陣計算簡單意外,還因為w項可以表示無窮。

關於這一點的論文一時找不到了。。。


@Milo Yip 誰能邀請大大來解惑一下吧,需要一個比較權威的說法。


推薦閱讀:

海島奇兵中海邊的浪花是如何實現的,使用shader還是序列幀動畫?
手遊程序員想學習圖形學知識,圖形學、GL/DX、Shader這些有什麼合適的學習順序嗎?
主流的遊戲引擎都是如何解決Alpha Blending問題的呢?
計算機視覺和圖像處理方向前景如何?
計算機圖形學的裁剪演算法,實現任意多邊形與直線、圓的裁剪?

TAG:計算機圖形學 | 遊戲編程 |