跳轉到內容

GLSL 程式設計/Blender/輪廓增強

來自 Wikibooks,開放世界中的開放書籍
一隻半透明的水母。注意輪廓處的透明度增加了。

本教程涵蓋了表面法線向量的變換。它假設你熟悉透明度教程中討論的alpha混合以及檢視空間著色教程中討論的uniform。

本教程的目標是實現左側圖片中可見的效果:半透明物體的輪廓往往比物體其他部分更不透明。這即使沒有光照,也能增強三維形狀的印象。事實證明,變換後的法線對於獲得這種效果至關重要。

表面法線向量(簡稱法線)在一個表面塊上。

平滑表面的輪廓

[編輯 | 編輯原始碼]

在平滑表面的情況下,輪廓上表面的點以平行於觀察平面的法線向量為特徵,因此與觀察者的方向正交。在左側的圖中,圖頂部輪廓處的藍色法線向量平行於觀察平面,而其他法線向量更指向觀察者(或相機)的方向。透過計算觀察者的方向和法線向量並測試它們是否(幾乎)相互正交,我們可以因此測試一個點是否(幾乎)在輪廓上。

更準確地說,如果V是歸一化(即長度為 1)的觀察者方向,而N是歸一化的表面法線向量,則這兩個向量正交當點積為 0 時:V·N = 0。在實踐中,這種情況很少出現。但是,如果點積V·N接近 0,我們可以假設該點接近輪廓。

增加輪廓處的透明度

[編輯 | 編輯原始碼]

對於我們的效果,因此我們應該增加透明度如果點積V·N接近 0。有各種方法可以增加觀察者方向和法線向量之間點積較小時的透明度。這裡有一個方法(它實際上有一個物理模型作為基礎,在這篇出版物的第 5.1 節中描述)來從材料的常規透明度計算增加的透明度

檢查像這樣的方程式的極端情況總是很有意義。考慮接近輪廓的點的案例:V·N ≈ 0。在這種情況下,常規透明度將除以一個小的正數。(注意,GLSL 保證優雅地處理除以零的情況;因此,我們不必擔心它。)因此,無論是什麼,和一個小正數的比率將更大。這函式將確保生成的透明度永遠不會大於 1。

另一方面,對於遠離輪廓的點,我們有V·N ≈ 1。在這種情況下,α' ≈ min(1, α) ≈ α;也就是說,這些點的透明度不會發生太大變化。這正是我們想要的。因此,我們剛剛驗證了該方程至少是合理的。

在著色器中實現方程

[編輯 | 編輯原始碼]

為了在著色器中實現類似於 的方程式,第一個問題應該是:它應該在頂點著色器還是片段著色器中實現?在某些情況下,答案很明確,因為實現需要紋理對映,而紋理對映通常只在片段著色器中可用。然而,在許多情況下,沒有普遍的答案。在頂點著色器中實現往往更快(因為頂點通常比片段少),但影像質量較低(因為法線向量和其他頂點屬性在頂點之間可能會突然變化)。因此,如果您最關心效能,那麼在頂點著色器中實現可能是一個更好的選擇。另一方面,如果您最關心影像質量,那麼在畫素著色器中實現可能是一個更好的選擇。在 每頂點光照(即 Gouraud 著色)和 每片段光照(即 Phong 著色)之間也存在同樣的權衡。

下一個問題是:在哪個座標系中應該實現該方程式?(有關標準座標系的描述,請參見 “頂點變換”。)同樣,也沒有普遍的答案。但是,在 Blender 中,在檢視座標中實現通常是一個不錯的選擇,因為許多統一變數是在檢視座標中指定的。(在其他環境中,在世界座標中實現更為常見。)

在實現方程式之前,最後一個問題是:我們從哪裡獲取方程式的引數?常規的不透明度 由遊戲屬性指定(在 RGBA 顏色中)(請參見 關於在檢視空間中著色的教程)。法線向量 gl_Normal 是標準的頂點屬性(請參見 關於著色器除錯的教程)。朝向觀察者的方向可以在頂點著色器中計算為從檢視空間中的頂點位置到檢視空間中的攝像機位置的向量,而攝像機位置始終是原點,即 ,如 “頂點變換” 中所述。換句話說,我們應該計算 減去檢視空間中的位置,這僅僅是檢視空間中的負位置(除了第四個座標,對於方向,它應該為 0)。

因此,我們只需要在實現方程式之前將頂點位置和法線向量變換到檢視空間。從物件空間到檢視空間的變換矩陣 gl_ModelViewMatrix 以及它的逆矩陣 gl_ModelViewMatrixInverse 和逆矩陣的轉置 gl_ModelViewMatrixInverseTranspose 都由 Blender 提供,如 關於在檢視空間中著色的教程 中所述。將變換矩陣應用於點和法線向量的過程在 “應用矩陣變換” 中進行了詳細討論。基本結果是點和方向只是透過將它們乘以變換矩陣來變換,例如

vec4 positionInViewSpace = gl_ModelViewMatrix * gl_Vertex;
vec3 viewDirection = -vec3(positionInViewSpace); // == vec3(0.0, 0.0, 0.0) - vec3(positionInViewSpace)

另一方面,法線向量透過將它們乘以轉置的逆變換矩陣來變換。由於 Blender 為我們提供了轉置的逆變換矩陣 gl_ModelViewMatrixInverseTranspose,因此可以用來變換法線。另一種方法是從左邊將法線向量乘以逆矩陣 gl_ModelViewMatrixInverse,這等效於將其從右邊乘以轉置的逆矩陣,如 “應用矩陣變換” 中所述。但是,最好的方法是使用 3×3 矩陣 gl_NormalMatrix,它是模型檢視矩陣的轉置的逆 3×3 矩陣

vec3 normalInViewSpace = gl_NormalMatrix * gl_Normal;

現在我們已經擁有了編寫著色器所需的所有元件。

著色器程式碼

[edit | edit source]

片段著色器使用 alpha 混合,如 關於透明度的教程 中所述

import bge

cont = bge.logic.getCurrentController()

VertexShader = """
         varying vec3 varyingNormalDirection; 
            // normalized surface normal vector
         varying vec3 varyingViewDirection; 
            // normalized view direction 
                  
         void main()
         {				
            varyingNormalDirection = 
               normalize(gl_NormalMatrix * gl_Normal);
            varyingViewDirection = 
               -normalize(vec3(gl_ModelViewMatrix * gl_Vertex));

            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
"""

FragmentShader = """
         varying vec3 varyingNormalDirection; 
            // normalized surface normal vector
         varying vec3 varyingViewDirection; 
            // normalized view direction 
                  
         const vec4 color = vec4(1.0, 1.0, 1.0, 0.3);

         void main()
         {
         
            vec3 normalDirection = normalize(varyingNormalDirection);
            vec3 viewDirection = normalize(varyingViewDirection);
            
            float newOpacity = min(1.0, 
               color.a / abs(dot(viewDirection, normalDirection)));
            gl_FragColor = vec4(vec3(color), newOpacity);
         }
"""

mesh = cont.owner.meshes[0]
for mat in mesh.materials:
    shader = mat.getShader()
    if shader != None:
        if not shader.isValid():
            shader.setSource(VertexShader, FragmentShader, 1)
            mat.setBlending(bge.logic.BL_SRC_ALPHA, 
                            bge.logic.BL_ONE_MINUS_SRC_ALPHA)

newOpacity 的賦值是對方程式的幾乎逐字翻譯

請注意,我們在頂點著色器中對 varyings varyingNormalDirectionvaryingViewDirection 進行了歸一化(因為我們希望在方向之間進行插值,而不會對任何方向賦予更多或更少的權重),並在片段著色器的開頭進行歸一化(因為插值可能會在一定程度上扭曲我們的歸一化)。但是,在許多情況下,頂點著色器中 varyingNormalDirection 的歸一化是不必要的。類似地,在大多數情況下,片段著色器中 varyingViewDirection 的歸一化也是不必要的。

更多藝術控制

[edit | edit source]

片段著色器使用一個常量 color 來指定表面的 RGBA 顏色。如果將該常量替換為四個遊戲屬性(請參見 關於在檢視空間中著色的教程),那麼 CG 藝術家更容易修改該顏色。

此外,這種輪廓增強缺乏藝術控制;即,CG 藝術家不能輕易地建立比物理模型暗示的更薄或更厚的輪廓。為了允許更多的藝術控制,您可以引入另一個(正的)浮點型數字屬性,並在使用上面的方程式之前,將點積 |V·N| 提高到該數字的冪。這將允許 CG 藝術家獨立於基本顏色的不透明度來建立更薄或更厚的輪廓。

總結

[edit | edit source]

恭喜,您已完成本教程。我們已經討論了

  • 如何找到光滑表面的輪廓(使用法線向量和觀察方向的點積)。
  • 如何在這些輪廓處增強不透明度。
  • 如何在著色器中實現方程式。
  • 如何將點和法線向量從物件空間變換到檢視空間。
  • 如何計算觀察方向(作為從攝像機位置到頂點位置的差)。
  • 如何插值歸一化方向(即進行兩次歸一化:在頂點著色器和片段著色器中)。
  • 如何為輪廓的厚度提供更多藝術控制。

進一步閱讀

[edit | edit source]

如果您想了解更多資訊

  • 關於物件空間和世界空間,請閱讀對 “頂點變換” 的描述。
  • 關於如何將變換矩陣應用於點、方向和法線向量,請閱讀 “應用矩陣變換”
  • 有關渲染透明物體的基礎知識,您應該閱讀透明度教程
  • 有關Blender提供的統一變數和遊戲屬性,您應該閱讀檢視空間著色教程
  • 有關輪廓增強數學原理,您可以閱讀Martin Kraus發表在2005年IEEE視覺化大會上的論文“Scale-Invariant Volume Rendering”的第5.1節,該論文可線上獲取


< GLSL Programming/Blender

除非另有說明,本頁面上的所有示例原始碼均歸屬公有領域。
華夏公益教科書