Mesh模型的Laplace Deformation(網格形變 - 拉普拉斯形變) - C++程式碼實現

Bob__yuan發表於2018-08-29

github: https://github.com/GaoYuanBob/LaplaceDeformation     

https://blog.csdn.net/Bob__yuan/article/details/81778875,這裡是我的第一篇Laplace形變的學習記錄,在這第二篇學習記錄中,想將做出的幾個結果放上來看一下(紅色是固定區域,綠色是移動區域,由於上一篇文章說了,Laplace Deformation是可以三個軸分著計算的,所以只是將綠色區域在Y方向上進行拖動,所有點的座標也只更新Y座標):

    我目前是先實現的最簡單的Laplace Deformation,就是點的Laplace座標就是單純的用周圍一圈點的座標平均(同樣的權重)然後和該點座標做減法。從結果上看還是比較對的,為了檢查在記憶體有洞的情況下是否會影響,加的大圖的測試(紅色為移動錨點,進行旋轉,藍色未固定錨點,固定不動)。

    具體到程式碼實現流程如下:

    1、先選取錨點

          對一個要進行形變的模型,我們先選擇出錨點,包括在形變過程中不動的錨點,我稱之為固定錨點,記錄這些點的索引到fixed_anchor_idx(vector)中;然後選取我們確定形變之後在哪裡的點,我稱之為移動錨點,記錄這些點的索引到move_anchor_idx(vector)中。

    2、計算點的連線關係

          這個功能在VCG庫裡其實是有的,匯入模型之後自動就有這種屬性,但是為了實現流程,還是自己寫了一下,大致思想就是遍歷所有的面(必須是三角形),由於是三角形面片,所以三角形上的三個點肯定是相連線的,對於所有面片,將每個點的連線點vector中,新增上這個三角形面片的另外兩個點,遍歷完成,所有點的連線關係就確定完成了。

    3、計算Laplace矩陣 - L

         處理的模型有 N 個點,Laplace矩陣L就是 N * N 的方陣(稀疏矩陣,大多數位置值為0),對角線上代表,第i個點有幾個鄰接點,也就是用到了我們上邊計算出來的連線關係。然後 i,j 位置是 -1 則表示第 j 個點和第 i 個點連線,如果是0,則沒有相連線。如果覺得自己的矩陣可能有問題,可以輸出出來,看看每一行的 -1 的個數加起來和這一行對角線上的值是不是一樣的,不一樣一定是問題的!

          具體到程式碼部分,我用到的是VCG中的Eigen庫,標頭檔案方面需要如下幾個。

          首先是建立Laplace矩陣L以及之後需要擴充的矩陣Ls,L的大小是N * N,Ls的大小是 (M + N) * N(M是錨點總個數)。然後根據上述方式進行賦初值。這個過程過後,Laplace矩陣L就計算完成了!L用來和形變前模型的所有點座標組成的矩陣相乘,得到等式右邊的Laplace座標,(忘了是啥可以看一下第一篇文章)。Ls還沒有計算完成,還需要新增錨點資訊

           比如說對於只有19的圓盤來說,Laplace矩陣L就如下圖所示。

          接下來新增固定錨點和移動錨點資訊,新增方式相同,就是每一個錨點新增一行,這一行中除了這個點的索引處為1,其餘位置全為0;同樣是上邊的模型,Ls矩陣就是多了兩行的L,如下所示。(可以看出,只有兩個錨點,分別是第0號,和第16號點)。

 

         輸出矩陣資訊的時候,Eigen庫過載了cout,所以直接 cout << L << endl; 即可輸出整個矩陣,並且是對齊的。但是矩陣很小時輸出能夠看出一些資訊,矩陣大了肉眼就看不出什麼資訊了。

    4、計算等式左邊 - LsTLs

         上圖公式左邊,和x相乘的就是Ls矩陣,計算了Ls之後,就是計算等式 (L_{s}^{T} \cdot L_s) \cdot \tilde{x} = L_{s}^{T} \cdot b 左邊了。也就是求出Ls矩陣的轉置,然後左乘上Ls本身,我稱之為 LsTLs 矩陣。所有的矩陣庫都肯定有矩陣求轉置的方法,Eigen庫中直接 LsT = Ls.transpose();  LsTLs = LsT * Ls; 就可以求出我們需要的 LsTLs 矩陣了。

    5、計算等式右邊 - LsTb

         等式右邊的矩陣稱為 b,上邊的部分就是用我們之前算過的Laplace矩陣左乘上所有點的座標的矩陣,得到的所有點的Laplace座標矩陣,因為xyz三個軸可以分別計算,所以每個軸其實是一個很長的向量。b 矩陣下邊部分是Laplace Deformation的重點!它包括兩個部分,固定錨點和移動錨點,b裡的每一行,對應著Ls中的每一行。這些表示錨點資訊的行,是決定形變方式以及效果的資料。一行和一行之間的對應一定要對!

          每一行的賦值,對於固定錨點和移動錨點,賦值的原理是一樣的,每一行除了點的索引位置上有數,其餘位置都是0,索引位置上的數,是這錨點的形變後坐標!!

          但是,對於固定錨點,因為固定,所有其實形變後坐標也是形變前座標,固定錨點的每一行就是在上述數學公式中的含義就是,這個點,在形變過程中,座標沒有變。 

          對於移動錨點,因為移動了,所有不再是形變前座標,這時就需要用到我們指定的這些點形變後的座標,要自己指定

         從程式碼也可以看出,就是先用Laplace矩陣L計算所有點的Laplace座標,也就是 b 矩陣的上半部分,然後通過Eigen庫中的 conservativeResize 方法將 b 矩陣擴充套件為 N + M 行,然後用上述方法指定錨點資訊,我這裡只對Y座標進行改動,對所有移動錨點,將其Y座標減去5,看看形變效果。(從這裡就可以明顯的看出,如果所有點只是在Y軸方向上移動,那麼模型上所有點的X和Z座標形變前後是都不會變的!)。

    6、計算形變後的座標 

         得到形變後的座標 x' 的方式就是解 LsTLs * x' = LsTb 這個線性方程組,方程組左右我們都知道了,並且用矩陣的形式表示,我們使用Eigen庫中提供的Cholesky分解加速計算。vy_new = LsTLs.llt().solve(LsTby);  一行即可計算出 vy_new 這個 Eigen::VectorXf 型別向量,向量的大小是自動計算出來的,不需要指定。

     7、對模型進行形變

           得到的 vy_new 向量包括了所有點的 y 座標(x和z同樣方式計算),但不是所有點的 y 座標都需要進行更新,因為我們有錨點,所以更新模型的方式就是,遍歷vy_new向量:

         (1)如果這個索引上的點不是錨點,那麼這個點的新座標就變為 vy_new 對應位置的值

         (2)如果這個索引上的點是固定錨點,那麼這個點的座標不用調整

         (3)如果這個索引上的點是移動錨點,那麼這個點的座標調整為指定的形變後坐標

          也就是說,只有不是錨點的點的座標是需要計算的,錨點的形變後坐標的我們在形變前就已經知道了,如下圖所示賦值。

    這樣,模型的所有點的座標也已經更新了,完整的Laplace形變就完成了。 

    如果有更多要求限制,比如旋轉不變性,保體積性,等等,也是在這個流程的基礎上新增約束資訊,中心思想是一樣的。

相關文章