在3.1節中的程式中,我們在RendererApplication::OnInitialize()中看到有下面一段代碼,這段代碼創建了一個轉換到攝像機空間的矩陣和轉換到投影空間的矩陣,并將他們傳遞給了渲染器,到目前為止,我們還沒有創建攝像機類,因為對渲染器來講,攝像機物件最終的主要作用就是將這兩個矩陣資料傳遞給著色器,因此在還沒涉及到復雜的場景管理之前,我們先介紹這兩個矩陣的原理,
1 ... 2 Matrix4x4f vMat, proMat; 3 vMat.BuildCameraLookAtMatrix(Vector3f(0, 0, 0), Vector3f(0, 0, -1), Vector3f(0, 1, 0)); 4 proMat.BuildProjectionMatrixPerspectiveFovRH(PI / 6.f, 1.0f * GetWindowHeight() / GetWindowWidth(), 1.0f, 1000.f); 5 Renderer->SetGlobalUniform("mvpMat", (proMat * vMat).m, sizeof(Matrix4x4f)); 6 ...
上面代碼第三行創建了由世界坐標空間轉換到攝像機空間的矩陣,這個矩陣的計算添加到了Matrix4x4這個類中,其代碼如下:
1 template <typename T> 2 Matrix4x4<T> &Matrix4x4<T>::BuildCameraLookAtMatrix(const Vector3<T> &position, const Vector3<T> &dir, const Vector3<T> &upVector) 3 { 4 Vector3<T> zaxis = -dir; 5 zaxis.Normalize(); 6 7 Vector3<T> xaxis = upVector.CrossProduct(zaxis); 8 xaxis.Normalize(); 9 10 Vector3<T> yaxis = zaxis.CrossProduct(xaxis); 11 yaxis.Normalize(); 12 13 m00 = (T)xaxis.x; 14 m01 = (T)xaxis.y; 15 m02 = (T)xaxis.z; 16 m03 = (T)-xaxis.DotProduct(position); 17 18 m10 = (T)yaxis.x; 19 m11 = (T)yaxis.y; 20 m12 = (T)yaxis.z; 21 m13 = (T)-yaxis.DotProduct(position); 22 23 m20 = (T)zaxis.x; 24 m21 = (T)zaxis.y; 25 m22 = (T)zaxis.z; 26 m23 = (T)-zaxis.DotProduct(position); 27 28 m30 = 0; 29 m31 = 0; 30 m32 = 0; 31 m33 = 1; 32 33 return *this; 34 }
在2.2矩陣章節我們已經知道,3D空間中的點表示為基向量空間的線性組合,于是點要由世界空間轉換到攝像機空間,首先要計算攝像機空間坐標系的三個基向量,通常我們通過攝像機的位置、方向和向上向量就可以計算這個矩陣了,我們定義攝像機的方向為z軸的負方向(使用右手坐標系),那么負的攝像機的方向就是攝像機空間坐標系的z軸方向,通過向上方向和z軸的叉乘,我們可以計算出x軸方向,再次進行z軸和x軸的叉乘,最后得到了y軸的方向(上面代碼4~11行),我們將計算到的基向量賦值給矩陣的每一行,這就是得到了由世界到攝像機空間的旋轉矩陣,但攝像機還有位置資訊,這個位置就是攝像機坐標系在世界坐標系中旋轉后進行的位移,而位移值就是位置在攝像機坐標系各軸上的投影大小,因此在計算最終攝相機空間的坐標時,需要減去這個位移值,在構造矩陣中代碼為上面的第16、21、26行,下圖為2D空間中的例子,由xOy先旋轉到x'Oy',再位移到x''O''y'',p為空間中的一點,

在空間中的點變換到相機空間后,就可以進行投影操作,攝像機的投影分為正交投影和透視投影,我們以透視投影為例,如果螢屏的寬度/高度為aspect,那么我們在這個步驟的目的是將相機空間中的xyz坐標映射到[-1, 1](OpenGL使用的z映射值為[-1,1],DX為[0, 1],這里以[-1,1]說明),只有落到這些范圍內的坐標點才會進行最終的渲染計算, 先看代碼,然后我們來講解矩陣中的這些值是怎么計算來的:
1 template <typename T> 2 Matrix4x4<T> &Matrix4x4<T>::BuildProjectionMatrixPerspectiveFovRH(T fieldOfViewRadians, T aspectRatio, T zNear, T zFar) 3 { 4 const T h = static_cast<T>(1.0 / tan(fieldOfViewRadians * 0.5f)); 5 assert(aspectRatio != 0.f); 6 const T w = static_cast<T>(h / aspectRatio); 7 assert(zNear != zFar); 8 9 m[0] = w; 10 m[1] = 0; 11 m[2] = 0; 12 m[3] = 0; 13 14 m[4] = 0; 15 m[5] = h; 16 m[6] = 0; 17 m[7] = 0; 18 19 m[8] = 0; 20 m[9] = 0; 21 m[10] = ((zNear + zFar) / (zNear - zFar)); 22 m[11] = (T)(2.0f * zNear * zFar / (zNear - zFar)); 23 24 m[12] = 0; 25 m[13] = 0; 26 m[14] = (T)-1; 27 m[15] = 0; 28 29 return *this; 30 }
上面函式是計算透視投影矩陣的代碼,函式傳入的引數為攝像機的fov、螢屏的寬高比、近裁剪面和遠裁剪面的距離,如下圖,攝像機的fov為θ,點P在垂直方向投影到近裁剪面上一點Q,近裁剪面為我們的目的投影面,因此利用三角函式可以計算出投影平面上的點

為:


而這個值要映射到[-1,1] ,由于寬高比的存在,寬度方向需要除以aspect,最終P'x為

正常來講,圖形計算出了x和y的裁剪坐標,就可以轉到螢屏空間坐標進行繪制了,但是在光柵化時,我們還需要用到z值,這里z值主要有兩個作用:
1. 深度測驗,光柵化階段會對每個像素位置進行深度測驗,以保證最前面的圖形被正確繪制,
2. 計算頂點屬性的插值,
第二點需要特殊說明,頂點屬性除了位置之外還有顏色、紋理坐標等資訊,光柵化階段,需要對三角形內的這些資訊進行插值計算出每個片元像素的頂點屬性,下圖為一條線段向平面投影的示意圖,在投影平面上均勻采樣,但實際線段上采樣點的間隔卻是隨著位置離投影平面的距離增大而增大,這表明投影后頂點屬性的插值計算不是線性的,

上圖中的線段我們可用方程表示為:

由相似三角形知道:

解關于x的方程,上式代入直線方程得到:

將直線方程寫成1/z的形式:

對于投影平面上的一點p3,令:

則有:

由上式知道,三角形面投影后z值的倒數是線性插值,對于一個頂點屬性b,光柵化時需要根據b1和b2計算插值屬于b3,這兩個頂點的z值為z1和z2,則有:

而z3的值為:

最終可以解得:

由上面的公式,我們就可以在光柵化階段計算出正確的屬性插值結果,這個程序叫做透視校正,
通過上面我們知道,透視校正計算插值時需要乃至1/z的值,因此最終需要求得的z'值可以表示為下面的線性關系:

其中右手坐標系中z的范圍為(-n,-f),z'的范圍為(-1,1),代入上式有


解上面方程得到:


于是有投影后的z'值為:

這里點P進行投影后的點值都進行了除以z值的操作,因此投影后的Q點的齊次坐標為:

用一個矩陣來表示這個計算為:

到目前為止,我們計算出了ViewMatrix和ProjectMatrix,這是后面用到攝像機的核心內容,此外還有正交投影矩陣計算,其計算程序與上述類似,正交投影的代碼如下:
1 template <typename T> 2 Matrix4x4<T> &Matrix4x4<T>::BuildProjectionMatrixOrthoRH(T widthOfViewVolume, T heightOfViewVolume, T zNear, T zFar) 3 { 4 assert(widthOfViewVolume != 0.f); 5 assert(heightOfViewVolume != 0.f); 6 assert(zNear != zFar); 7 8 m[0] = (T)(2 / widthOfViewVolume); 9 m[1] = 0; 10 m[2] = 0; 11 m[3] = 0; 12 13 m[4] = 0; 14 m[5] = (T)(2 / heightOfViewVolume); 15 m[6] = 0; 16 m[7] = 0; 17 18 m[8] = 0; 19 m[9] = 0; 20 m[10] = (T)(2 / (zNear - zFar)); 21 m[11] = (T)((zNear + zFar) / (zNear - zFar)); 22 23 m[12] = 0; 24 m[13] = 0; 25 m[14] = 0; 26 m[15] = 1; 27 28 return *this; 29 }
通過矩陣計算獲取了裁剪空間的坐標之后,需要將坐標轉換到螢屏空間才能進行光柵化操作,以Windows視窗為例左上角為螢屏空間的(0,0)點,向中為x正方向,向下為y正方向,則對每個頂點位置進行轉換的計算程序為:


到此,我們已經為光柵化所需要的資料做好了準備,下一步就是進行光柵化操作了,
本文來自博客園,作者:毅安,轉載請注明原文鏈接:https://www.cnblogs.com/primarycode/p/16629517.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/502925.html
標籤:其他
