紋理坐標三角形內插值問題,我搞不定了。?

經過各位高手解答,我這個故事有更新。更新在第二個分割線後面。

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

我想問問,當給定三角形三頂點的紋理坐標,如何插值獲得三角形內部所有點的紋理坐標?

我從網上找了一種「重心坐標法」,現在可以根據三角形三頂點顏色值,插值出內部點的顏色值。顯示出來看到效果也挺好。但當我想套用該法插值紋理坐標時,發現效果不對。

我找的「重心坐標法」簡單說是這樣的:

設三角形三點p1,p2,p3;它們對內部任一點p的「貢獻」為c1,c2,c3,則對於任何「可插值」量來說,有

p=p1*c1+p2*c2+p3*(1-c1-c2)。

其中pn為頂點的給定值,p為插值。

由於點坐標是可插值的(三角形是平面所以可以線性插值),所以將xy分別代入上面公式後,得到了有c1,c2兩個未知數的兩個線性方程(組)。

我簡單變換了一下公式,放到程序里,來一個三角形就可求得三個頂點的貢獻值。然後我用顏色和法線測試,貌似顯示出來的效果都挺好。

最後我搞了一個直角三角形,給定三個頂點的紋理坐標為(0,0), (0,1), (1,1)。你一定可以想像出,我是希望沿著對角線把 左-上 半張紋理貼到直角三角形上。效果看上去應該沒有扭曲,是裁開的樣子。

但是實際上我發現插值出來的紋理坐標,在三角形內一「橫行」之內,x坐標還好,y坐標隨著x增大而略微增大,使得貼圖看上去扭曲了。這樣一來,自然當顯示一整個mesh時,貼圖都不對。

抱歉這會兒在地鐵上,明天爭取貼截屏。

我想問問:

1,紋理坐標能用「重心坐標法」線性插值嗎?

2,如果能,為何我得到這樣的結果?錯在哪了?

3,如果不能,那麼顯卡是怎麼做的?換作你會怎麼做?(其實我覺得能,理由是:一般我們會把法線、紋理坐標,世界坐標等等一併從vertex shader輸出,然後又在fragment/pixel shader里一併接收它們。顯卡並不知道它們的差別,所以只能用相同方法插值。)

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

首先說一下故事的背景。最近單位比較閑,那天我心血來潮想用純軟體編程實現一下OpenGL+顯卡的功能,看看我能實現多少它們的功能。順便也能透徹理解一下底層原理。

然後,回答里有人說不明白我說的「將xy分別代入上面公式」是什麼意思?意思就是下面公式所示:

其中P,P1,P2,P3為三角形三個頂點和一個待插值的三角形內點,它們是已知量,C1,C2以及(1-C1-C2)為三頂點貢獻值。解方程組可得三個貢獻值。

下圖是我發此問題時出錯的圖,害我很絕望,事後發現確實是敲程序時的手誤引起。

改了手誤後,發現正對著投影面是對的,側個身就錯了。如下圖。

上圖的紋理坐標插值公式如下。(此時C1和C2是已知量)

幸虧回答的高手們告訴我,還有「透視校正」一說。改吧。暫時懶得看校正公式的推導過程了,先拿來試一試!

首先用三個頂點的NDC的 z 插值獲得待插值內點的 z。

您可能已經注意到了,上面公式是直接用 z 而不是其倒數計算的,我心想這能有多大差別?(因為懶得看公式推導的原理了。民科的典型心態。)然後用 z 校正紋理坐標插值如下。(v 類似)

然後獲得的效果如下。

雖然依然是很寒磣,但是明顯看到,logo大體上不扭曲了。只是還是有點……

好吧,我心裡知道高手給的博客鏈接里,明明說 z 的倒數是可以線性插值的。人家沒說 z 本身也可以線插。所以就不圖省事了,將插值公式改為:

這下效果終於對了。

太不容易了!

謝謝各位高手指點。最後附上一張有正確插值的,有diffuse和specular光照的圖。好開心。

接下來打算試一試bump mapping、shadow map、shadow volume,和deferred rendering。然後再用多核模擬一下多條管線,可惜相比之下cpu的核真的很少。


重心坐標是對的,如果結果不對,就說明代碼寫錯。實際顯卡上,用的是掃描線的方法,本質上仍然是重心坐標,但變成遞進計算,而不是每個點計算。

至於你說的情況,還有一個可能是,你是否考慮了z?沒有透視矯正的話,如果有z的變化,結果肯定不對。


題主問的是二維三角形,所以:

由 V1,V2 插值得到 V4,

由 V1,V3 插值得到 V5,

由 V4,V5 插值得到 V6三維三角形情況稍微複雜些,可以看:

透視校正插值(Perspective-Correct Interpolation)


因為在投影的時候,每個像素的深度(z坐標)一般都是不一樣的,所以導致三維空間中線性分布的頂點屬性在投影之後就不是線性分布了。國際慣例(dx,gl)等用的投影矩陣都是讓z非線性分布的,就是轉換到homogeneous clipping space(齊次裁剪空間)之後是1/z線性分布,而不是單純的z均勻分布。這有個好處就是光柵化之後,每個像素深度都不用透視矯正插值了,可以直接在屏幕空間線性插值,因為國際慣例的齊次空間z坐標都不是均勻分布了。

最近自己在寫簡單的軟體渲染,並沒有採用國際慣例的非線性z分布,而是直接吧view space的深度z坐標線性映射到[0,1]區間里。

如果設近平面的深度是n,遠平面是f,則國際慣例中的齊次空間深度坐標Z`和未進行投影的view space的z坐標關係是:

Z=f/(f-n) - (nf/(f-n)z)

你看,z在分母那

而我寫的是

Z = (z-n)/(f-n)

z在分子那= =

估計題主是像我這種了線性的齊次空間z了。然後深度值,頂點屬性(例如顏色,紋理坐標)的插值都是和1/z成線性關係。

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

新開的項目 c++控制台 + 軟渲 剛剛搞定了頂點光照和z-test,還有透視校正插值

把字體調大一點就是下面那樣(字元可以稍微的看清一點)

圖片裡面顏色量化得不太對呢,是因為我還沒完善colorRGB到字元的轉換= =如果想用控制台畫點字元畫,只能自己用(字元色+字元+背景色)自己去模擬一些顏色來擴充顏色空間,因為控制台講道理只支持三原色和三原色的直接疊加,然後可以加強顏色,所以一共只有

14種顏色,但是遲點我打算利用前後景色的混合進一步地擴充顏色空間。


按照頂點紋理坐標,可以把原始的三角形(稱之為A)投影到紋理空間,投影后還是個三角形(稱之為B),但是形狀上不一定跟原來的一樣。對於三角形B,重心法求各點紋理坐標應該是沒問題的。但是如果是按三角形A來應用重心法,紋理坐標就可能是錯的。


看到這帖子,想起來五年前,快六年了,做畢業設計的時候,也是自己實現了一個軟體光柵化渲染器,也只有基本的空間坐標變換、紋理映射,簡單光照,場景管理(八叉樹)。當時還想做個簡單的shader編譯器,後來時間太緊就放棄了。也是渲染了一個地球。多核要用simd吧,什麼時候有時間想再用opencl搞一個.... 懷念那個時候


你的演算法

紋理插值-&>頂點變換

計算機目前的做法

頂點變換-&>紋理變換

計算機算紋理變換應該是在光柵化的階段


謝邀O.o

@Xi Yang 在樓上已經提到了,三角形插值用面積比較妥當。你提到的每個頂點的「貢獻度」就是指該頂點相對那一塊小三角形的面積

如A的貢獻度是藍色部分Ca,B的貢獻度為紅色的Cb。這個演算法應該是很明晰的。。

計算面積也如同他提到的一樣用向量叉乘即可。。現在無論你用什麼東西應該都有封裝的數學庫可用,沒有的話自己實現一個也不難..

我不太明白你的「代入xy求得c1和c2」是怎麼算出來的。。但我覺得你的錯應該就是出在解方程這裡。穩妥的做法應該是利用ABC三點的坐標,求出相應的向量,再用叉乘計算面積。。

我建議你先用這種做法試試,再回頭修改你自己的方法。你的思路應該都是沒錯的,問題應該出在實現細節上。


做線性插值可以像上面這樣,先用p2p3插出D,再用p1和D插出o。

和計算p1op2,p2op3,p3op1的面積進行插值是等價的。

V_O = frac{|OD|}{|P_1D|}V_1+frac{|P_1O|}{|P_1D|}V_D
=frac{|OD|}{|P_1D|}V_1+frac{|P_1O|}{|P_1D|}(frac{|P_2D|}{|P_2P_3|}V_3+frac{|P_3D|}{|P_2P_3|}V_2)

其中

frac{|OD|}{|P_1D|}=Area({P_2OP_3})/Area(P_1P_2P_3)

frac{|P_1O|}{|P_1D|}=Area(P_1OP_2)/Area(P_1P_2D)=Area(P_1OP_3)/Area(P_1P_3D)

frac{|P_2D|}{|P_2P_3|}=Area(P_1P_2D)/Area(P_1P_2P_3)

frac{|P_3D|}{|P_2P_3|}=Area(P_1P_3D)/Area(P_1P_2P_3)

做掃描線填充的時候,比如一次填充一橫條,就先插出左右兩端,這樣插值不用每次都計算三角形面積。

所謂線性插值,是指3個頂點構成一個平面,

所謂二次插值,是指6個點(一般是頂點加邊的中點)構成一個二次曲面。

這樣理解的話,你的公式為p = c1(p1-p3)+c2(p2-p3)+p3=c1p1+c2p2+(1-c1-c2)p3應該是對的。如果有問題的話可能是你算錯了。

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

有人質疑掃描線填充時不能這麼干,

如上面這個圖所示

P1,P2是兩端,P是要插值的點,要正確插值出P,需要求出3D空間中|P1P|和|P2P|的比例

frac{|P_1P|}{|P_2P|}=frac{|P_1P_1

也就是說如果知道兩個端點的深度,那麼對應的3D長度比例也就可以得到了。

那麼如何得到深度呢?

直接拿這個公式插值即可

z=frac{L_1z_1}{L_1z_1+L_2z_2}z_2+frac{L_2z_2}{L_1z_1+L_2z_2}z_1=frac{(L_1+L_2)z_1z_2}{L_1z_1+L_2z_2}

把分子分母換一下,就得到下面這個都比較熟悉的公式了

frac{1}{z}=frac{L_1z_1}{(L_1+L_2)z_1z_2}+frac{L_2z_2}{(L_1+L_2)z_1z_2}=frac{L_1}{L_1+L_2}cdotfrac{1}{z_2}+frac{L_2}{L_1+L_2}cdotfrac{1}{z_1}

接下來我們依然做掃描線填充,每次掃描一行的時候,先插值出左右兩端的深度以及其他屬性值,然後利用深度加權之後進一步填充中間像素的深度及其他屬性值。


三角形插值是使用面積坐標,而不是先插兩點再與第三點插。三個頂點的插值全重,使用對邊與待求點構成的三角形的面積,除以整個三角型的面積。比如三個頂點ABC,目標點P,那麼A的權重就是PBC的面積除以ABC的面積。

面積可以用向量叉乘得到。


透視校正插值,它的軟實現你可以看3d遊戲編程大師技巧。


剛想說為什麼不寫個shader,看到後面發現是心血來潮代碼實現顯卡功能。。。

其他人已經答得很好了。

另外計算這些向量和浮點數的時候,,,顯卡比cpu在性能上有數量級的差距。

所以記得買台好電腦。


諮詢一下,透視矯正過程中對Z的倒數進行線性插值時候,z1,z2,z3都為0時該怎麼處理,因為分母為零了


我目前做的項目也需要這樣插值,同樣的方法,將幾何模型和網格模型對應的。我就是先判斷距離,然後在xy方向投影,找出該點落在哪個三角形上,如果沒有,再在xz,yz方向投影,距離選得合理的話一定能投影到某個三角形上。目前效果還可以(因為我是把整個模型放到大型場景上,不要求精細),這是其中一個炮筒最後計算出來的結果,有星星點點的地方效果不太好。持續關注這個話題,歡迎大牛們來指點啊


推薦閱讀:

為什麼一些採用 Source 引擎的遊戲,會有「看畫風知引擎」的感覺?
Google開發的RAISR演算法利用機器學習壓縮圖片,提高解析度,實際應用怎麼樣?
虛擬現實(vr)將會是下一個熱點嗎?
Hololens 的使用場景有哪些局限?
浙大周昆教授的gpu是誰教的?

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