投影紋理映射(Projective Texture Mapping) 【轉】


摘抄“GPU Programming And Cg Language Primer 1rd Edition” 中文名“GPU編程與CG語言之陽春白雪下里巴人” 

 

 

投影紋理映射( Projective Texture Mapping )最初由 Segal 在文章 “Fast shadows and lighting effects using texture maaping” 中提出,用於映射一個紋理到物體上,就像將幻燈片投影到牆上一樣。該方法不需要在應用程序中指定頂點紋理坐標,實際上,投影紋理映射中使用的紋理坐標是在 頂點着色程序中通過視點矩陣和投影矩陣計算得到的,通常也被稱作投影紋理坐標 (coordinates in projective space) 。而我們常用的紋理坐標是在建模軟件中通過手工調整紋理和 3D 模型的對應關系而產生的。

投影紋理映射的目的是將紋理和三維空間頂點進行對應,這種對應的方法好比 “ 將紋理當作一張幻燈片,投影到牆上一樣 ” 。如 圖 35 投影紋理映射 所示。

本章我們針對投影紋理映射的原理和實現方法進行詳細的闡述。這一章的地位很高,在一些陰影算法以及體繪制算法中都需要用到投影紋理映射技術。嚴格的說,只要涉及到 “ 紋理實時和空間頂點對應 ” ,通常都會用到投影紋理映射技術。


12.1 投影紋理映射的優點

投影紋理映射有兩大優點:其一,將紋理與空間頂點進行實時對應,不需要預先在建模軟件中生成紋理坐標;其二,使用投影紋理映射,可以有效的避免紋理扭曲現象。

為了說明第一個優點,先舉一個簡要的例子:很多情況下,我們需要將場景渲染兩遍,第一遍是為了獲取場景信息,得到的場景信息通常保存為一張紋理(例 如深度圖);然后基於“存放場景信息”的紋理進行第二次渲染;第二次渲染結果才是最終顯示到屏幕上的效果。為了在第二次渲染中使用到“存放場景信息”的紋 理(無預先設置的紋理坐標),需要時時進行紋理計算,這時就可以使用投影紋理映射技術。實際上,這也是投影紋理映射技術的最廣泛的應用了。

  可能大家對於上一段文字還不能理解得很清楚,不過在第 13 章的陰影貼圖算法以及第 15 章的體繪制光線投射算法中,大家會明白其含義。一個算法只有理論加實踐,才可能真正的被理解,只會照本宣科的朗誦術語,基本上都是魯迅先生所說的 “ 泥塘 ” 似的人。

投影紋理映射的第二個優點是:可以有效的避免紋理扭曲現象。如 圖 36 所示,將一張紋理投影到兩個三角面片上,它們的頂點紋理坐標相同,但是由於三角面片形狀不同,插值出來的內部點的紋理坐標也會產生不同的梯度( gradient ),最后紋理顏色在兩個三角面片上的分布也是不一樣的。


圖 37 右邊所示的是將一張紋理貼到一個正方形上,左邊所示的是將同樣的紋理貼到一個梯形上,正方形和梯形的頂點紋理坐標相同,但兩者的貼圖效果是不同的。梯形上的紋理會出現明顯的扭曲現象。這是因為幾何體的變換,導致插值出來的內部紋理坐標分布不均衡。


12.2 齊次紋理坐標( Homogeneous Texture Coordinates )

齊次紋理坐標( homogeneous texture coordinates )的概念對大多數人來說比較陌生,紋理坐標一般是二維的,如果是體紋理,其紋理坐標也只是三維的。齊次紋理坐標的出現是為了和三維頂點的齊次坐標相對應, 因為本質上,投影紋理坐標是通過三維頂點的齊次坐標計算得到的。

齊次紋理坐標通常表示為(s,t,r,q ), 以區別於物體位置齊次坐標(x, y, z, w) 。一維紋理常用s 坐標表示,二維紋理常用(s, t) 坐標表示,目前忽略r 坐標,q 坐標的作用與齊次坐標點中的w 坐標非常類似。值一般為1 。

12.3 原理與實現流程

對投影紋理映射,很多教程上都是這么解釋的:紋理好比一張幻燈片,燈光好比投影機,然后將紋理投影到一個物體上,類似於投影機將幻燈片投影到牆上。這個比喻沒有太大的問題,也找不到更加形象的比喻了。問題是:這個解釋剛好顛倒了算法的實現流程。

投影紋理映射真正的流程是 “ 根據投影機(視點相機)的位置、投影角度,物體的坐標,求出每個頂點所對應的紋理坐標,然后依據紋理坐標去查詢紋理值 ” ,也就是說,不是將紋理投影到牆上,而是把牆投影到紋理上。投影紋理坐標的求得,也與紋理本身沒有關系,而是由投影機的位置、角度,以及 3D 模型的頂點坐標所決定。所以,我一直覺得 “ 投影紋理映射 ” 這個術語具有很強的誤導性,總讓人覺得是把紋理投射出去。

根據頂點坐標獲得紋理坐標的本質是將頂點坐標投影到 NDC 平面上,此時投影點的平面坐標即為紋理坐標。如果你將當前視點作為投影機,那么在頂點着色程序中通過 POSTION 語義詞輸出的頂點投影坐標,就是當前視點下的投影紋理坐標沒有被歸一化的表達形式。

“Projective texture mapping” 文章中有一幅比較著名的圖片,說明計算紋理投影坐標的過程,如 圖 38 所示。


圖 38 左邊是正常的頂點坐標空間轉換流程,無非是頂點從模型坐標空間轉換到世界坐標空間,然后從世界坐標空間轉換到視點空間,再從視點空間轉換到裁剪空間,然后 投影到視錐近平面,經過這些步驟,一個頂點就確定了在屏幕上的位置。圖的右邊是將視點當作投影機,根據模型空間的頂點坐標,求得投影紋理坐標的流程。通過 比較,可以發現這兩個流程基本一樣,唯一的區別在於求取頂點投影坐標后的歸一化不一樣:計算投影紋理坐標需要將投影頂點坐標歸一化到【0 ,1 】空間中,實現這一步,可以在需要左乘矩陣normalMatrix , 也可以在着色程序中對頂點投影坐標的每個分量先乘以1/2 然后再加上1/2 。

                         

所以求取投影坐標矩陣的公式為:

求得紋理投影矩陣后,便可以使用該矩陣將頂點坐標轉換為紋理投影坐標。

     

使用投影紋理坐標之前,別忘了將投影紋理坐標除以最后一個分量 q 。到此,你就可以使用所求得的投影紋理坐標的前兩個分量去檢索紋理圖片,從中提取顏色值。還記得Cg 標准函數庫中有的紋理映射函數的表達形式為:

tex2DProj(sampler2D tex, float4 szq)

tex2DProj 函數與 tex2D 函數的區別就在於:前者會對齊次紋理坐標除以最后一個分量 q ,然后再進行紋理檢索!

注意:上面常被提到的“投影機”只是一種形象化的比喻,本質是視點相機,很多教程上都說“將燈光當作投影機”,這是一種 錯誤的表達(並非是這些教程的作者不懂,而是語言組織上的錯誤),他們真正的意思是“在當前燈光所在的位置放置一個相機,相機的觀察方向和光線投射方向一 致”,這個相機就作為投影機使用。在一些陰影算法中,根據光源信息設置投影機,並從投影機的角度渲染出場景信息紋理(如,陰影紋理),然后把這個紋理放到 正常的場景渲染相機中使用,這時就需要投影機的矩陣信息來建立投影紋理矩陣了。

附:投影紋理矩陣的計算通常不需要開發人員自己動手,常用的圖形API 中都給出了獲取各種矩陣(視點矩陣、投影矩陣等)的函數,不過偏移矩陣需要自己設置。在應用程序中獲取這些矩陣信息后,再傳遞到着色程序中使用。

頂點着色程序和片段着色程序如下所示:

代碼 16 投影紋理映射頂點着色程序

 

void main_v(

       float4 position                          : POSITION,

       float4 normal                            : NORMAL,

 

       out float4 outPos                            : POSITION,

       out float4 outShadowUV        : TEXCOORD0,

 

       uniform float4x4 worldMatrix,

       uniform float4x4 worldViewProj,

       uniform float4x4 texViewProj // 投影紋理矩陣

       )

{

       outPos = mul(worldViewProj, position);

      

// 計算投影紋理坐標

       float4 worldPos = mul(worldMatrix, position);

       outShadowUV = mul(texViewProj, worldPos);

}

代碼 17 投影紋理映射片段着色程序

 

void main_f(

                     float4 shadowUV                    : TEXCOORD0,

                     out float4 result                        : COLOR,

                     uniform sampler2D projectiveMap  // 用於投影的紋理

)

{

       shadowUV = shadowUV / shadowUV.w;

      

       float4 mapColor ;

      

       // 歸一化到 0-1 空間

       shadowUV.x = (shadowUV.x +float(1.0))/float(2.0);

       shadowUV.y = (shadowUV.y +float(1.0))/float(2.0);

       mapColor = tex2D(projectiveMap, shadowUV.xy);  

      

       result = mapColor; 

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM