線性插值的問題
OpenGL在進行光柵化時,對於每個fragment shader的input attribute作插值,這個插值默認情況下perspective correct的。插值默認是screen space進行的,如果使用簡單的線性插值,對於texture mapping會得到錯誤的結果,來看下圖(圖片取自《Graphics Shaders 2nd》)。
可以明顯地看到採用線性插值的quad中兩個triangles的邊界。
Perspective Correct Interpolation的簡單推導
OpenGL 4.5 spec 14.6 p456中給出地插值公式如下
其中爲fragment對應的重心座標,爲頂點處地attribute,爲頂點齊次座標地最後一個分量。
爲了簡化討論,我們這裏在2D中進行討論,結論對3D同樣有效。
考慮上圖中的線段AB,投影后的線段爲A’B’,其中A’B’的中心。我們可以容易地發現,A’B’中心並不對應AB中心,所以直接使用對AB屬性進行線性插值會得到錯誤的結果。容易想到,如果我們通過screen space的插值係數,得到camera space的插值係數,那麼就可以得到perspective correct的interpolation。
假設screen space的點P的插值係數爲,A爲,B爲,near plane (不影響結果,爲1可以簡化討論),可知
計算與的交點,我們可以得到camera space的插值係數爲爲
若我們在A處屬性爲,B處屬性爲,那麼使用進行插值,結果爲
如果使用標準透視矩陣,那麼中的,我們可以看出公式與是一致的。
公式可以進一步表示爲線性插值
gl_Position
也許有人會有疑問,我們能不能在vertex shader中進行透視除呢?即對於齊次座標,手動進行透視除,得到並賦值給gl_Position。我們可以將代入,得到
顯然這退化成爲了screen space的線性插值,所以必然最後結果是不正確的。在Unity中作一個簡單的實驗就可以看到結果
上圖是未進行透視除的結果,下圖是進行透視除的結果。
測試代碼如下
Shader "Unlit/VertexColor"
{
Properties
{
_MainTex ("Main Texture", 2D) = "white" {}
[Toggle] _ManualPerspectiveDivision ("Manual Perspective Division", Float) = 0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
GLSLPROGRAM
#include "UnityCG.glslinc"
#ifdef VERTEX
uniform int _ManualPerspectiveDivision;
out vec2 vST;
void main()
{
vST = gl_MultiTexCoord0.st;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
if (_ManualPerspectiveDivision != 0) {
gl_Position /= gl_Position.w;
}
}
#endif
#ifdef FRAGMENT
in vec2 vST;
uniform sampler2D _MainTex;
void main()
{
gl_FragColor = texture(_MainTex, vST);
}
#endif
ENDGLSL
}
}
}
結論
- 要進行perspective correct interpolation,不能直接在screen space利用重心座標直接進行插值。
- 齊次座標的對於進行perspective correct interpolation至關重要,不能在vertex shader中手動進行透視除。