文章目錄
- 5.1 觀察視圖
- 5.1.1 視圖模型—相機模型
- OpenGL的整個處理過程中所用到的坐標系統:
- 視錐體
- 視錐體的剪切
- 5.1.2 視圖模型--正交視圖模型
- 5.2 用戶變換
- 5.2.1 矩陣乘法的回顧
- 5.2.2 齊次坐標
- 5.2.3 線性變換與矩陣
- SRT
- 透視投影
- 正交投影
- 5.2.4 法線變換
- 逐像素計算法向量
- 5.2.5 OpenGL矩陣
- uniform mat4
- 給頂點屬性設置矩陣(使用buffer)
- ogl中矩陣行與列
- 5.3 OpenGL變換
- z坐標的變化范圍
- 視口
- 多視口
- z的精度/深度沖突z-fighting
- 5.3.1 高級技巧:用戶裁減和剪切
- gl_ClipDistance的簡單用法
- 5.3.2 OpenGL變換的控制(視口變換)
- 5.4 transform feedback
- 5.4.1 transform feedback對象
- 5.4.2 transform feedback緩存
- transform feedback緩存初始化過程
- 5.4.3 配置transform feedback的變量
- 通過ogl api配置xfb變量
- 通過著色器配置xfb變量
- 5.4.4 transform feedback的啟動和停止
- 5.4.5 transform feedback的示例:粒子系統
5.1 觀察視圖
- 本章我們將學習如何將模型的三維坐標投影到固定的平面的二維幕坐標上。
- 將三維空間的模型投影到二維的關鍵方法,就是齊次坐標(homogeneous coordinate的應用、矩陣乘法的線性變換方法,以及視口映射。
5.1.1 視圖模型—相機模型
- 常見的視圖模型操作可以類比為使用照相機拍攝照片的過程:
- 將相機移動到準備拍攝的位置,將它對準某個方向。
- 將準備拍攝的對象移動到場景中必要的位置上(模型變換,model transform)。
- 可以認為上面的第1步和第2步做的是同一件事情,只不過方向相反而已。
- 主要目的都是構建一個獨立的、統一的空間系統,將場景中所有的物體都變換到視圖空間,或者人眼空間(eye space)當中。
- 設置相機的焦距,或者調整縮放比例(投影變換,projection transform)。
- 拍攝照片(應用變換結果)。
- 對結果圖像進行拉伸或者擠壓,將它變換到需要的圖片大小(視口變換,viewport transform)。
這一步同樣需要對深度信息進行拉伸或者擠壓(深度范圍的縮放)。
第三步是選擇捕捉場景的范圍大小,而這一步是對結果的拉伸或擠壓。
- 在OpenGL中,可以直接在著色器中完成第一步和第三步,也就是說傳遞給opengl的坐標應該是已經完成模型視圖變換和投影變換的。
- 我們可以告訴opengl如何完成第五步,也可以讓固定渲染管線自動完成這個變換過程(詳見5.3節)
OpenGL的整個處理過程中所用到的坐標系統:
- 其實就是一般都存在模型、視圖、投影變換:
- 模型變換:局部到世界
- 相機/視圖變換:相機位置為原點,再通過at和up獲得正確的坐標系(例如相機向上對齊y軸,看向z軸或-z軸,x軸由at和up的叉乘獲得)
此時物體的坐標會被重新計算為相對于相機的位置,從而方便后續的投影變換(如透視投影或正交投影)。
- 投影變換:壓縮到標準化設備坐標,進而壓縮到2D并存放進幀緩沖區。
- 典型的透視投影矩陣形式為:是先將平截錐體壓縮成矩形,然后再進行一次正交投影獲得
非線性深度值:- 正交投影矩陣:
因為w為1,所以不需要透視除法。- 更多深度值參考
- 計算流程:
視錐體
- 設置的焦距或者縮放的數值,其實就是在相機拍攝場景時,設置取景用的矩形錐體的寬度或者窄度。同時,也是計算用于透視投影的“近大遠小”效果的相關參數,具體在除以w的透視除法中。
- 同時還設置近平面和遠平面來去除過近(無限大的)和過遠(深度值將涵蓋一個非常大的范圍)的物體。
視錐體的剪切
- 如果某個圖元落在組成視錐體的四個平面之外,那么它將不會被繪制(它將被裁減,cull)。
- 如果有一個圖元正好穿過這里的某個平面,OpenGL將會對此圖元進行剪切(clip)。
- 它會負責計算幾何體與平面的交集,然后將落入視錐體范圍內的形狀進行計算后會生成新的幾何體。
- 著色器中也可以使用用戶自定義的平面來進行剪切,詳見后文。
5.1.2 視圖模型–正交視圖模型
- 它的主要作用是在投影之后依然保持物體的真實大小以及相互之間的角度。
- 我們可以簡單地通過忽略x、y、z三個坐標軸中的一個來實現這一效果,也就是用其余兩個構成二維坐標。
5.2 用戶變換
- 在光柵化之前的各個階段都是可編程來定制的,這對于坐標的處理方式以及變換方式才有這巨大的靈活性。
- 但是,最終還是要將輸出傳遞給后繼的幾個固定階段(不可編程),所以最后必需要生成可以用于透視除法的齊次坐標(也稱作剪切坐標,clip coordinate)。
- 空間變換都是線性變換方式,可以通過齊次坐標的矩陣乘法來完成。這是opengl變換的關鍵所在。
- 在著色器中當中,使用矩陣對一個頂點進行變換的過程如下所示:
#version 330 core
uniform mat4 Transform;//對于各個頂點都是一致的(圖元級別的粒度)
in vec4 Vertex;//每次傳遞的逐頂點數據
void main()
{gl_Positon = Transform * Vertex;
}
- 線性變換是可以級聯的,因此可以將任意數量的線性變換過程合并為一次矩陣乘法。
- 需要注意矩陣乘法沒有交換律。
5.2.1 矩陣乘法的回顧
詳見此處
- 新向量的每個分量都是所有舊向量分量的一個線性函數,因為是通過矩陣進行線性變換,也意味著逆矩陣可以還原操作。
- 向量(0,0,0,0)與矩陣相乘的結果仍然是(0,0,0,0),這也是3x3的矩陣與三維向量相乘時無法表達平移操作的原因。
5.2.2 齊次坐標
- 準備進行變換的幾何體本身就是三維形式,之所以將三維的笛卡爾坐標轉換為四維的齊次坐標,主要是為了可以進一步完成透視變換和使用線性變換來實現模型的平移。
- 更具體是因為想將能進行平移的仿射變換變成統一的線性變換。
- 因為我們規定w=1為point,而w=0為向量,即它表示一個“無限遠的點”。
- 因此如果w越大,表示坐標位于更遠的位置,那么透視除法除以w后,前三分量都會越小,繪制比例就越小,那么就能實現透視效果。
5.2.3 線性變換與矩陣
詳見此處
SRT
- 平移
Tmat4<T> translate(T x, T y, T z);
- 縮放
Tmat4<T> scale(T x, T y, T z);
如果想進行非同型變換(單獨分量縮放),最好在視圖變換完成之后再進行,過早進行會導致物體在旋轉的時候變形。
- 旋轉
// 分別代表繞各個軸旋轉的度數
Tmat4<T> rotate(T angle, T x, T y, T z);
透視投影
- 觀察點總是原點,并看向正z。
- 透視除法是ogl內部完成的。
// 視圖
Tmat4<T> lookat(const vecN<T,3>& eye, const vecN<T,3>& center, const vecN<T,3>& up);// 透視
mat4 frustum(float left, float right, float bottom, float top, float n, float f);// 透視
mat4 perspective(float fovy, float aspect, float n, float f);
- 投影變換:壓縮到標準化設備坐標,進而壓縮到2D并存放進幀緩沖區。
- 典型的透視投影矩陣形式為:是先將平截錐體壓縮成矩形,然后再進行一次正交投影獲得
非線性深度值:
正交投影
mat4 ortho(float left, float right, float bottom, float top, float n, float f);
- 正交投影矩陣:
因為w為1,所以不需要透視除法。
5.2.4 法線變換
- 除了頂點的變換之外,還需要對表面法線進行變換,也就是從某個點出發,方向與物體表面垂直的一個向量。
- 法線是需要進行歸一化的,也就是它的長度必須為1.0,主要是出于光照計算的目的。
- 法線向量通常是只有三個分量的向量,沒有使用齊次坐標,因為物體表面的平移不會影響到法線的值。
- 但是我們并不能直接使用頂點的變換矩陣,而是先令M為3X3的矩陣,再令其為世界和視圖矩陣的3X3形式,不包含投影矩陣,然后取M的逆矩陣的轉置來當作法線變換矩陣。
- 并且如果M變換的內容(世界與視圖變換)只涉及旋轉和等軸縮放,那么M可以只包含旋轉信息,不考慮縮放(因為法線會進行歸一化處理,也是是單位化)。
逐像素計算法向量
原文
- dFdx(v) = 該像素點右邊的v值 - 該像素點的v值 // v 可以是任意值
- dFdy(v) = 該像素點下面的v值 - 該像素點的v值
- fwidth(v) = abs( dFdx(v) + dFdy(v))
- dFdx和dFdy都是計算dFdy計算一個變量在 x 方向或y方向的梯度,可用于計算法向量或光照效果。
- fwidth計算的是在x和y方向上的變化的絕對值的和。
- 都只能在片元著色器中使用。
- 且都是接收什么類型的參數,返回值就是什么類型的。
- 逐像素計算法向量
varying vec3 pos;vec3 normal = normalize( cross(dFdx(pos), dFdy(pos)) );
- 平滑顏色
// 從紋理中獲取顏色vec4 color = texture(texture1, TexCoords);// 計算顏色在x方向的變化率float colorChangeX = ddx(color.r); // 計算紅色通道在x方向的變化// 使用變化率對顏色進行調整FragColor = vec4(color.rgb + vec3(colorChangeX), color.a);
5.2.5 OpenGL矩陣
- 可以將矩陣通過uniform或逐頂點屬性位置設置給著色器即可。
uniform mat4
// 著色器中定義
uniform mat4 model_matrix;// 程序中定義
vmath::mat4 model_matrix;
glUseProgram(shaderProgram);
GLint render_model_matrix_loc = glGetUniformLocation(shaderProgram, "model_matrix");
model_matrix = vmath::translate(-3.0f, 0.0f, -5.0f);
// GL_FALSE是指以列為主序讀取,按列進行傳遞,1代表uniform不是數組
glUniformMatrix4fv(render_model_matrix_loc, 1, GL_FALSE, model_matrix);
給頂點屬性設置矩陣(使用buffer)
// 著色器中定義
// Instanced vertex attributes
"// model_matrix will be used as a per-instance transformation matrix\n"
"// Note that a mat4 consumes 4 consecutive locations, so\n"
"// this will actually sit in locations, 3, 4, 5, and 6.\n"
layout (location = 3) in mat4 model_matrix;// 程序中定義
int instanceNum = 4;//定義4個mat4的vertex attributes
glGenBuffers(1, &model_matrix_buffer);
glBindBuffer(GL_ARRAY_BUFFER, model_matrix_buffer);
glBufferData(GL_ARRAY_BUFFER, instanceNum * sizeof(vmath::mat4), NULL, GL_DYNAMIC_DRAW);
int matrix_loc = glGetAttribLocation(shaderProgram, "model_matrix");// Loop over each column of the matrix...在矩陣的每一列上循環for (int i = 0; i < 4; i++){// Set up the vertex attributeglVertexAttribPointer(matrix_loc + i, // 第i個屬性4,// 每個屬性都是4分量GL_FLOAT, // 數據類型GL_FALSE, //不需要歸一化//一個頂點的stride,即更新的時候的跨步(按頂點更新或者instance更新)!sizeof(vmath::mat4),(void*)(sizeof(vmath::vec4) * i)); //在buffer中取當前這種屬性的開頭位置// Enable itglEnableVertexAttribArray(matrix_loc + i); // 啟用這個屬性索引// Make it instancedglVertexAttribDivisor(matrix_loc + i, 1);//每 1個實例都會分配一個新的屬性值(需要更新屬性值的屬性索引為matrix_loc + i).}// Set model matrices for each instancevmath::mat4* matrices = (vmath::mat4*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);for (int n = 0; n < instanceNum; n++){matrices[n] = vmath::translate((float)n + (-4.0f), 0.0f, -5.0f);}glUnmapBuffer(GL_ARRAY_BUFFER);
ogl中矩陣行與列
- 只有一種情形下我們需要考慮矩陣的列主序或者行主序關系,那就是將GLSL矩陣放人自定義的內存塊時。
為了通知GLSL從內存中正確加載矩陣數據,需要使用布局限定符row_major和 column_major。
layout(shared,row_major)uniform{...};
5.3 OpenGL變換
z坐標的變化范圍
- 如果我們需要通知OpenGL在何處放置近平面和遠平面,即定義視口變換過程中z坐標的變化范圍,可以使用:
//默認情況下它們分別是0.0和1.0,這個函數的參數設置范圍必須是[0,1]之間的數值。
void glDepthRange( GLdouble nearVal,GLdouble farVal);void glDepthRangef( GLfloat nearVal,GLfloat farVal);
視口
void glViewport(GLint x,GLint y,GLsizei width,GLsizei height);
在程序窗口中定義一個矩形的像素區域,并且將最終渲染的圖像映射到其中。這里的x和y參數設置了視口(viewport)的左下角坐標,而width和height 設置了視口矩形的像素大小。默認情況下視口初始值設置為(0,0,winWidth,winHeight),其中winWidth和 winHeight 為窗口的像素尺寸。
多視口
- 可以使用gIViewport()來選擇一個更小的繪制區域;例如,我們可以通過分割窗口來模擬同一個窗口中多個視圖分裂的效果。
- OpcnGL提供了相應的命令支持,并且可以在幾何著色階段選擇具體要進行繪制的視口,例如多視口,詳見10.6
z的精度/深度沖突z-fighting
- 計算過程中,硬件的浮點數精度支持是有限的,從而導致在數學上深度坐標應該是不同的,但是硬件中最終記錄的浮點數z值可能是相同的(甚至與實際結果相反),因而導致相互距離較為接近的物體會發生閃爍交疊的情形。
- 經過透視變換之后,z的精度問題可能會惡化,無論對于深度坐標還是其他類型的坐標值都是如此:此時如果深度坐標遠離近剪切平面,那么它的位置精度將越來越低
這是因為透視投影深度值是非線性
- 就算沒有經過透視變換,浮點數的精度也是有限的,這樣在深度值較大的時候會帶來更多的問題。
- 這個問題的根源是我們在一個過小的z值區域內繪制了過多的數據。如果要避免這個問題,我們需要盡量將遠平面與近平面靠近,并且盡可能不要在一個很小的區域內繪制過多的z值。
5.3.1 高級技巧:用戶裁減和剪切
- OpenGL會自動根據視口和近平面與遠平面的設置來裁減(篩選的意思)和剪切幾何體。
用戶裁減和剪切的意思就是再添加一些任意方向的平面,與幾何數據相交,例如只允許幾何體在平面的一側可見,而另一側不可見。
- OpenGL的用戶裁減和剪切操作需要特殊的內置頂點著色器數組gl_CullDistance[]和gl_ClipDistance[]聯合產生作用。
這兩個變量允許我們控制裁減和剪切平面與頂點的關系。它們的值經過插值之后設置給頂點之間的各個片元。
- 定義平面(A,B,C,D)
- gl_CullDistance[]和gl_ClipDistance[]數組的每個元素都對應于一個平面(其實是頂點和定義的平面的點乘結果)。
- 平面的數量是有限的,通常為8個或者更多,并且是兩者共用的。這個總數可以通過gl_MaxCombinedClipAndCullDistance來查詢,還可以通過gl_MaxClipDistance和gl_MaxCullDistance來查詢。
- gl_CullDistance[]和gl_ClipDistance[]變量在聲明時并沒有指定大小,而我們用到的平面數量(數組元素)是在著色器中設置的。因此需要重新聲明它的大小。
- gl_CullDistance[]和gl_ClipDistance[]必須包含所有已經通過OpenGLAPI啟用的剪切平面;如果它沒有包括所有啟用的剪切平面,那么得到的結果可能是不確定的。啟用:
glEnable[GL_CLIP_PLANE0+i]
著色器中必須寫入所有啟用的平面距離值,否則可能會得到奇怪的剪切結果。
- gl_CullDistance[]和gl_ClipDistance[]在片元著色器中也是可用的,沒有被剪切的片元可以讀取每個剪切平面的距離插值結果.
gl_ClipDistance的簡單用法
#version 450 coreuniform vec4 plane;
in vec4 vertex;
float gl_ClipDistance[1];//使用一個剪切平面
void main()
{// 計算平面方程gl_ClipDistance[0] = dot(vertex,plane);}
- 這個變量的含義是,距離為0表示頂點落在平面之上,正數值表示頂點在剪切平面的內側(或說保留這個頂點),負數值表示頂點在剪切平面的外側(或裁減這個頂點)。
- 在圖元中剪切距離是線性插值的。OpenGL會負責將完全落在某個裁減平面之外的圖元剔除。(如果圖元與所有的裁減平面相交且有一部分落在它們的內側,則認為它應當被保留。)
- 此外,OpenGL會直接拋棄所有距離值小于0的片元。
5.3.2 OpenGL變換的控制(視口變換)
- 默認情況下,OpenGL會映射剪切空間的坐標(0,0)到窗口空間的中心,x坐標軸正向指向右側,y坐標軸正向指向上方。因此(-1,-1)將位于窗口的左下方,(1,1)將位于窗口的右上方。
- 有些圖形系統會將深度取值范圍映射到[-1.0,1.0],也有些是將剪切空間中-z的坐標值表示觀察者身后的位置,因此可見的深度范圍在剪切空間中會被映射到0.0到1.0。
- OpenGL允許你重新配置這兩種映射方式
// 設置剪切坐標到窗口坐標的映射方式。
// origin設置的是窗口坐標x和y的原點
// 而depth 設置的是剪切空間深度值映射到glDepthRange()所設置的數值的方式。
// origin Must be one of GL_LOWER_LEFT or GL_UPPER_LEFT.
// GL_LOWER_LEFT :xy坐標(-1.0,-1.0)對應窗口坐標的左下角.
// GL_UPPER_LEFT:xy坐標(-1.0,-1.0)對應窗口坐標的左上角.
// depth Must be one of GL_NEGATIVE_ONE_TO_ONE or GL_ZERO_TO_ONE.
// GL_NEGATIVE_ONE_TO_ONE :那么窗口空間中的深度對應于剪切空間的[-1.0,1.0].
// GL_ZERO_TO_ONE:那么剪切空間的[0.0,1.0]范圍將被映射到窗口空間的深度值,此時0.0表示近平面,1.0表示遠平面。
//剪切空間的z負值變換后將處于近平面的后方,但是觀察者眼前(近處)的數據精度值會變得更高。
void glClipControl( GLenum origin,GLenum depth);
5.4 transform feedback
- 是OpenGL管線中,頂點處理階段結束之后,圖元裝配和光柵化之前的一個步驟。
更準確地說,transfomfeedback是與圖元裝配過程緊密結合的,這是因為整個圖元數據都會被捕獲到級存對象中。而這里我們可以認為是緩存的空間不夠,因此必須丟棄一部分圖元。為了確保這一過程,需要在transform feedback階段給出當前的圖元類型信息。
- 可以重新捕獲即將裝配為圖元(點、線段、三角形)的頂點然后將它們的部分或者全部屬性傳遞到緩存對象中。
5.4.1 transform feedback對象
- transform feedback對象主要用于管理將頂點捕捉到緩存對象的相關狀態,包括所有用于記錄頂點數據的緩存對象、用于標識緩存對象的充滿程度的計數器、以及用于標識 transform feedback當前是否啟用的狀態量。
- 系統會內置一個默認的對象。這個默認 transform feedback對象的id為0.
- 分配一個transform feedback對象的名稱:
// 會包含一個默認的transformfeedback狀態,并在需要的時候綁定到環境
void glCreateTransformFeedbacks(GLsizei n,GLuint *ids);
- 如果要將一個transform feedback對象綁定到當前環境,需要使用:
// target must be GL_TRANSFORM_FEEDBACK.
void glBindTransformFeedback(GLenum target,GLuint id);
- 判斷某個值是否是一個transform feedback對象的名稱:
GLboolean glIsTransformFeedback(GLuint id);
- 刪除
// 如果ids不是transform feedback對象名稱或者為0則會忽略。
void glDeleteTransformFeedbacks(GLsizei n,const GLuint *ids);
刪除對象的操作會延遲到所有相關的操作結束之后才進行。也就是說,如果當前transform feedback對象處于啟用狀態,而我們調用 glDeleteTransformFeedbacks(),那么只有本次 transform feedback結束之后,才會刪除對象。
5.4.2 transform feedback緩存
-
transformfeedback緩存綁定點的總數是一個與具體設備實現相關的常量,可以通過GL_MAX_TRANSFORM_FEEDBACK_BUFFERS的值來查詢,至少64個。
-
可以同時給transformfeedback對象綁定多個緩存,也可以綁定緩存對象的多個子塊。
-
我們甚至可以將同一個緩存對象的不同子塊同時綁定到不同的transform feedback 緩存綁定點。
-
將整個緩存對象綁定到某個 transform feedback緩存綁定點:
// 將名為buffer的緩存對象綁定到名為xfb的transformfeedback對象上,其緩存綁定點索引通過index設置。
void glTransformFeedbackBufferBase( GLuint xfb,GLuint index,GLuint buffer);glBindBufferBase
- 將一個緩存對象的一部分綁定到某個 transform feedback緩存綁定點:
void glTransformFeedbackBufferRange(GLuint xfb,GLuint index,GLuint buffer,GLintptr offset,GLsizei size);
這個函數可以用來將同一個緩存對象的不同區域綁定到不同的transform feedback綁定點,我們需要保證這些區域是互不交疊的
transform feedback緩存初始化過程
GLunit buffer;
glCreateBuffers(1,&buffer);
glNamedBufferStorage(buffer,1024*1024,NULL,0);glTransformFeedbackBufferRange(xfb,0,buffer,0,512*1024);
glTransformFeedbackBufferRange(xfb,1,buffer,512*1024,512*1024);
// 也可以使用glBindBuffersRange來替代調用兩次glTransformFeedbackBufferRange
glNamedBufferStorage中flags參數設置為0,ogl會假設該緩存對象的用途:它不會被映射,也不會在 CPU端改變內容。
5.4.3 配置transform feedback的變量
通過ogl api配置xfb變量
- 設置 transform feedback 過程中要記錄哪些變量:
//count設置 varyings 數組中所包含的字符串的數量。
//設置使用 varyings 來記錄transformfeedback的信息,
//varyings是一個字符串數組,其中記錄所有輸出到片元(或者幾何)著色器中的,同時需要通過transformfeedback獲取的變化量。
// bufferMode must be GL_INTERLEAVED_ATTRIBS or GL_SEPARATE_ATTRIBS.它標識transform feedback中捕獲的變量是如何分配的。
// GL_INTERLEAVED_ATTRIBS :所有的變量是一個接著一個記錄在綁定到當前transformfeedback對象的第一個綁定點的緩存對象里的。
//GL_SEPARATE_ATTRIBS:那么每個變量都會記錄到一個單獨的緩存對象中。
void glTransformFeedbackVaryings( GLuint program,GLsizei count,const char **varyings,GLenum bufferMode);//
static const char*const vars[] =
{
"foo","bar","baz"
};
glTransformFeedbackVaryings(prog,sizeof(vars)/sizeof(vars[0]),vars,GL_INTERLEAVED_ATTRIBS);glLinkProgram(prog);
- glTransformFeedbackVaryings中所選擇的變量只有程序對象再一次被鏈接的時候才會起作用。
- 現在只要執行prog就會將寫人到VERTEX 2foo、bar和baz的數據記錄到當前transformfeedback對象所綁定的緩存當中
- GL_INTERLEAVED_ATTRIBS
- GL_SEPARATE_ATTRIBS
- 兩種mode都是緊密排列的,但是有時候還是需要使用不同的對齊方式,用于在緩存中留一些空隙不寫入數據。例如gl_SkipComponents和gl_NextBuffer,并且只有mode為GL_INTERLEAVED_ATTRIBS才可使用。
- 遇到內置變量gl_SkipComponents,就會在 transform feedback 緩存中留出一個指定數量(1、2、3、4)的空隙(例如gl_SkipComponents1).
static const char*const vars[] =
{
"foo","gl_SkipComponents2","baz"
};
glTransformFeedbackVaryings(prog,sizeof(vars)/sizeof(vars[0]),vars,GL_INTERLEAVED_ATTRIBS);glLinkProgram(prog);
- 遇到內置變量gl_NextBuffer,那么它會將變量傳遞到當前綁定的下一個 transform feedback緩存中。
- 遇到兩個或者多個gl_NextBuffer 的示例,那么它將會直接跳過當前的綁定點,并且在當前綁定的緩存中不會記錄任何的數據。
```cpp
static const char*const vars[] =
{
"foo","gl_SkipComponents1","baz","gl_SkipComponents2",
"gl_NextBuffer ",
"gl_SkipComponents4","baz","gl_SkipComponents2",
"gl_NextBuffer ",
"gl_NextBuffer ",
"iron","gl_SkipComponents3","copper"
};
glTransformFeedbackVaryings(prog,sizeof(vars)/sizeof(vars[0]),vars,GL_INTERLEAVED_ATTRIBS);glLinkProgram(prog);
通過著色器配置xfb變量
- 如果使用著色器配置,就盡量不要使用glTransformFeedbackVaryings。
- 需要使用以下的著色器layout限定符:
- xfb_buffer 設置變量對應的緩存。
- xfb_offset設置變量在緩存中的位置,決定了是否捕獲該變量。
- xfb_stride 設置數據從一個頂點到下一個的排列方式,用來實現使用api時可設置的空隙。
- 跨幅和偏移量的設置值必須是4的倍數,除非其中包含了雙精度(double)類型的數據,此時必須設置為8的倍數。
- 我們可以針對一個緩存的情況設置默認的跨幅,此時不需要指定變量:
layout(xfb_buffer=l,xfb_stride=40) out;
,之后對這個緩存的數據再使用xfb offset的時候,會直接采用之前的默認跨幅值。
5.4.4 transform feedback的啟動和停止
- transform feedback可以隨時啟動或者停止,甚至暫停。
- 如果transform feedback正處于暫停的狀態,那么再次啟動它將會從之前暫停的位置開始記錄。
- 啟用:
//設置 transform feedback準備記錄的圖元類型.
//
void glBeginTransformFeedback(GLenum primitiveMode);
- 限制條件
- 在這之后的繪制命令中的圖元類型必須與這里的primitiveMode相符,或者幾何著色器(如果存在的話)的輸出類型必須與primitiveMode相符。
- 當前綁定的 transform feedback 對象不可改變。
- 不允許將其他的緩存綁定到GLTRANSFORMFEEDBACKBUFFER 的綁定點。
- 當前的程序對象不可改變。
- 暫停:當transform feedback暫停之后,它依然是啟用狀態,但是暫時不會向 transform feedback緩存中記錄任何數據。
// 如果當前的 transform feedback沒有啟用,或者已經處于暫停狀態,glPauseTransformFeedback將產生一個錯誤。
void glPauseTransformFeedback(void);
- 重啟:
//重新啟用一個之前通過glPauseTransformFeedback暫停的transform feedback過程。
// 如果transformfeedback沒有啟用或者已經啟用但是沒有處于暫停狀態,glResumeTransformFeedback會產生一個錯誤。
void glResumeTransformFeedback(void);
- 結束:
//如果已經完成了所有transform feedback圖元的渲染,我們可以使用該函數重新切換到正常的渲染模式
void glEndTransformFeedback(void);
5.4.5 transform feedback的示例:粒子系統
- 這個程序通過兩個步驟使用 transform feedback實現了一個粒子系統。
- 第一步當中,使用transformfeedback獲取/截取OpenGL管線中的幾何數據,同時進行粒子的繪制。 - 在第二步同時使用捕獲的幾何數據和另一個transformfeedback的實例一起實現一個粒子系統,其中使用頂點著色器來實現粒子和之前渲染的幾何體的碰撞檢測,并更新粒子位置(所以粒子的截取需要使用到兩個buffer)。
粒子碰撞shader參考
#include<iostream>
#include<glad/glad.h>
#include<GLFW/glfw3.h>
#include <GL/gl.h>
#include "vmath.h"#include "shader.h"// Vertex Shader source code
const char* vertexShaderSource = "#version 450 core\n"
"layout (location = 0) in vec4 position;\n"
"layout (location = 1) in vec4 color;\n"
"out vec4 vs_fs_color;\n"
"uniform mat4 model_matrix;\n"
"uniform mat4 projection_matrix;\n"
"out vec4 world_space_position;\n"
"out vec3 vs_fs_normal;\n"
"void main()\n"
"{\n"
" vec4 pos = (model_matrix * (position * vec4(1.0, 1.0, 1.0, 1.0)));\n"
" world_space_position = pos;\n"
" gl_Position = pos;\n"
" //vs_fs_normal = normalize(vec3(0,0,-1));\n"
" vec3 normal = vec3(0,0,-1);\n"
" vs_fs_normal = normalize((model_matrix * vec4(normal, 0.0)).xyz);\n"
" vs_fs_color = color;\n"
"}\0";
//Fragment Shader source code
const char* fragmentShaderSource = "#version 450 core\n"
"in vec4 vs_fs_color;\n"
"layout (location = 0) out vec4 color;\n"
"void main()\n"
"{\n"
" color = vs_fs_color;\n"
"}\n\0";const GLfloat vertices[] =
{-0.5f, -0.5f, 0.0f,1.0f,0.5f, -0.5f, 0.0f,1.0f,0.0f, 0.5f, 0.0f,1.0f,};//const GLfloat verticesOutline[] =
//{
// -0.55f, -0.55f, -0.01f,1.0f,
// 0.55f, -0.55f, -0.01f,1.0f,
// 0.0f, 0.55f, -0.01f , 1.0f,
//
//};const GLfloat verticesOutline[] =
{-0.55f, -0.55f, 0.0f,1.0f,0.55f, -0.55f, 0.0f,1.0f,0.0f, 0.55f, 0.0f , 1.0f,};const GLfloat colors[] =
{1.0f, 0.0f, 0.0f,1.0f,1.0f, 0.0f, 0.0f,1.0f ,1.0f, 0.0f, 0.0f,1.0f ,};const GLubyte colorsNeedNormal[] =
{255, 255, 255,255,255, 255, 0,255 ,255, 0, 255,255 ,};// 編譯鏈接著色器的輔助函數
unsigned int shaderProgram(const char* vertexSrc, const char* fragmentSrc) {// 創建并編譯頂點著色器unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexSrc, NULL);glCompileShader(vertexShader);int result;glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &result);if (result == GL_FALSE){int length;glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &length);char* message = (char*)alloca(length * sizeof(char));glGetShaderInfoLog(vertexShader, length, &length, message);std::cout << message << std::endl;glDeleteShader(vertexShader);return -1;}// 創建并編譯片段著色器unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentSrc, NULL);glCompileShader(fragmentShader);glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &result);if (result == GL_FALSE){int length;glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &length);char* message = (char*)alloca(length * sizeof(char));glGetShaderInfoLog(fragmentShader, length, &length, message);std::cout << message << std::endl;glDeleteShader(fragmentShader);return -1;}// 創建著色器程序并鏈接unsigned int program = glCreateProgram();glAttachShader(program, vertexShader);glAttachShader(program, fragmentShader);glLinkProgram(program);// 刪除臨時著色器對象glDeleteShader(vertexShader);glDeleteShader(fragmentShader);return program;
}GLint model_matrix_loc;
GLint projection_matrix_loc;
GLint triangle_count_loc;
GLint time_step_loc;GLint render_model_matrix_loc;
GLint render_projection_matrix_loc;const int point_count = 5000;GLuint geometry_tex;GLuint vao[2];
GLuint vbo[2];
GLuint xfb;GLuint geometry_vbo;
GLuint render_vao;
//unsigned int render_prog;
unsigned int update_prog;
unsigned int redShader;
int i, j;static unsigned int seed = 0x13371337;
static inline float random_float()
{float res;unsigned int tmp;seed *= 16807;tmp = seed ^ (seed >> 4) ^ (seed << 15);*((unsigned int*)&res) = (tmp >> 9) | 0x3F800000;return (res - 1.0f);
}static vmath::vec3 random_vector(float minmag = 0.0f, float maxmag = 1.0f)
{vmath::vec3 randomvec(random_float() * 2.0f - 1.0f, random_float() * 2.0f - 1.0f, random_float() * 2.0f - 1.0f);randomvec = normalize(randomvec);randomvec *= (random_float() * (maxmag - minmag) + minmag);return randomvec;
}static inline int min(int a, int b)
{return a < b ? a : b;
}void Initialize()
{update_prog = shaderProgram(update_vs_source, white_fs);static const char* varyings[] ={"position_out", "velocity_out"};glTransformFeedbackVaryings(update_prog, 2, varyings, GL_INTERLEAVED_ATTRIBS);glLinkProgram(update_prog);glUseProgram(update_prog);model_matrix_loc = glGetUniformLocation(update_prog, "model_matrix");projection_matrix_loc = glGetUniformLocation(update_prog, "projection_matrix");triangle_count_loc = glGetUniformLocation(update_prog, "triangle_count");time_step_loc = glGetUniformLocation(update_prog, "time_step");// static const char* varyings2[] ={"world_space_position"};glTransformFeedbackVaryings(redShader, 1, varyings2, GL_INTERLEAVED_ATTRIBS);glLinkProgram(redShader);glUseProgram(redShader);render_model_matrix_loc = glGetUniformLocation(redShader, "model_matrix");render_projection_matrix_loc = glGetUniformLocation(redShader, "projection_matrix");// 輸入和輸出粒子bufferglGenVertexArrays(2, vao);glGenBuffers(2, vbo);for (i = 0; i < 2; i++){glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, vbo[i]);glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, point_count * (sizeof(vmath::vec4) + sizeof(vmath::vec3)), NULL, GL_DYNAMIC_COPY);if (i == 0){struct buffer_t {vmath::vec4 position;vmath::vec3 velocity;} *buffer = (buffer_t*)glMapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, GL_WRITE_ONLY);for (j = 0; j < point_count; j++){buffer[j].velocity = random_vector();buffer[j].position = vmath::vec4(buffer[j].velocity + vmath::vec3(-0.5f, 40.0f, 0.0f), 1.0f);buffer[j].velocity = vmath::vec3(buffer[j].velocity[0], buffer[j].velocity[1] * 0.3f, buffer[j].velocity[2] * 0.3f);}glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);}glBindVertexArray(vao[i]);glBindBuffer(GL_ARRAY_BUFFER, vbo[i]);glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(vmath::vec4) + sizeof(vmath::vec3), NULL);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(vmath::vec4) + sizeof(vmath::vec3), (GLvoid*)sizeof(vmath::vec4));glEnableVertexAttribArray(0);glEnableVertexAttribArray(1);}//glGenBuffers(1, &geometry_vbo);glGenTextures(1, &geometry_tex);glBindBuffer(GL_TEXTURE_BUFFER, geometry_vbo);glBufferData(GL_TEXTURE_BUFFER, 800 * 600 * sizeof(vmath::vec4), NULL, GL_DYNAMIC_COPY);glBindTexture(GL_TEXTURE_BUFFER, geometry_tex);glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, geometry_vbo);glGenVertexArrays(1, &render_vao);glBindVertexArray(render_vao);glBindBuffer(GL_ARRAY_BUFFER, geometry_vbo);glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, NULL);glEnableVertexAttribArray(0);glClearColor(0.0f, 0.0f, 0.0f, 1.0f);glClearDepth(1.0f);
}float t = 0;
float q = 0.0f;
void Display()
{static int frame_count = 0;static const vmath::vec3 X(1.0f, 0.0f, 0.0f);static const vmath::vec3 Y(0.0f, 1.0f, 0.0f);static const vmath::vec3 Z(0.0f, 0.0f, 1.0f);if (t > 1.0)t = 0;vmath::mat4 projection_matrix(vmath::frustum(-1.0f, 1.0f, -800.0/600.0, 800.0/600.0, 1.0f, 5000.0f) /** vmath::translate(0.0f, 0.0f, -100.0f)*/);vmath::mat4 model_matrix(vmath::rotate(t * 360.0f, 0.0f, 1.0f, 0.0f) *vmath::rotate(t * 360.0f * 3.0f, 0.0f, 0.0f, 1.0f));t += 0.01;glUseProgram(redShader);glUniformMatrix4fv(render_model_matrix_loc, 1, GL_FALSE, model_matrix);glUniformMatrix4fv(render_projection_matrix_loc, 1, GL_FALSE, projection_matrix);
}int main() {// 初始化GLFWif (!glfwInit()) {std::cerr << "Failed to initialize GLFW" << std::endl;return -1;}// 創建窗口GLFWwindow* window = glfwCreateWindow(800, 600, "Stencil Border with Depth", NULL, NULL);if (!window) {std::cerr << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}glfwMakeContextCurrent(window);gladLoadGL();// 創建著色器程序redShader = shaderProgram(vertexShaderSource, fragmentShaderSource);Initialize();// 配置頂點數據unsigned int VAOs[2], VBOs[2];glGenVertexArrays(2, VAOs);glGenBuffers(2, VBOs);// 設置原始三角形VAOglBindVertexArray(VAOs[0]);glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) + sizeof(colorsNeedNormal), NULL, GL_STATIC_DRAW);glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices), sizeof(colorsNeedNormal), colorsNeedNormal);glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, NULL);glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, (const GLvoid*)sizeof(vertices));glEnableVertexAttribArray(0);glEnableVertexAttribArray(1);// 設置邊框三角形VAOglBindVertexArray(VAOs[1]);glBindBuffer(GL_ARRAY_BUFFER, VBOs[1]);glBufferData(GL_ARRAY_BUFFER, sizeof(verticesOutline) + sizeof(colors), NULL, GL_STATIC_DRAW);glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verticesOutline), verticesOutline);glBufferSubData(GL_ARRAY_BUFFER, sizeof(verticesOutline), sizeof(colors), colors);glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, NULL);glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)sizeof(vertices));glEnableVertexAttribArray(0);glEnableVertexAttribArray(1);// 主渲染循環while (!glfwWindowShouldClose(window)) {static const float black[] = { 0.0f,0.0f,0.0f,0.0f };glClearBufferfv(GL_COLOR, 0, black);glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);// 啟用模板測試glEnable(GL_STENCIL_TEST);glEnable(GL_DEPTH_TEST);glDepthFunc(GL_LEQUAL);//glEnable(GL_CULL_FACE);// --- 第1步:繪制紅色三角形 ---glStencilFunc(GL_ALWAYS, 1, 0xFF); // 總是通過模板測試,將三角形對應像素模板值寫入1glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); // 通過時替換為1//glUseProgram(redShader);glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, geometry_vbo);static int frame_count = 0;static const vmath::vec3 X(1.0f, 0.0f, 0.0f);static const vmath::vec3 Y(0.0f, 1.0f, 0.0f);static const vmath::vec3 Z(0.0f, 0.0f, 1.0f);/* if (t > 1.0)t = 0;*/vmath::mat4 projection_matrix(vmath::frustum(-1.0f, 1.0f, -800.0 / 600.0, 800.0 / 600.0, 1.0f, 5000.0f) /** vmath::translate(0.0f, 0.0f, -100.0f)*/);vmath::mat4 model_matrix(/*vmath::translate(0.0f,0.0f,-t)**/vmath::rotate(t * 360.0f, 0.0f, 1.0f, 0.0f) *vmath::rotate(t * 360.0f * 3.0f, 0.0f, 0.0f, 1.0f));t += 0.00001;glUseProgram(redShader);glUniformMatrix4fv(render_model_matrix_loc, 1, GL_FALSE, model_matrix);glUniformMatrix4fv(render_projection_matrix_loc, 1, GL_FALSE, projection_matrix);glBindVertexArray(VAOs[0]);glBeginTransformFeedback(GL_TRIANGLES);glDrawArrays(GL_TRIANGLES, 0, 3);glEndTransformFeedback();// --- 第2步:繪制白色邊框 ---glStencilFunc(GL_NOTEQUAL, 1, 0xFF); // 僅模板值≠1的區域繪制glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // 不修改模板緩沖glBindVertexArray(VAOs[1]);glDrawArrays(GL_TRIANGLES, 0, 3);// 禁用模板測試glDisable(GL_STENCIL_TEST);// 粒子glBindTexture(GL_TEXTURE_BUFFER, geometry_tex);glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, geometry_vbo);glUseProgram(update_prog);model_matrix = vmath::mat4::identity();glUniformMatrix4fv(model_matrix_loc, 1, GL_FALSE, model_matrix);glUniform1i(triangle_count_loc, 1);if (t > q){glUniform1f(time_step_loc, (t - q) * 2000.0f);}q = t;if ((frame_count & 1) != 0){glBindVertexArray(vao[1]);glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, vbo[0]);}else{glBindVertexArray(vao[0]);glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, vbo[1]);}glBeginTransformFeedback(GL_POINTS);glDrawArrays(GL_POINTS, 0, min(point_count, (frame_count >> 3)));glEndTransformFeedback();glBindVertexArray(0);frame_count++;// testfor (i = 0; i < 2; i++){glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, vbo[i]);if (i == 0){struct buffer_t {vmath::vec4 position;vmath::vec3 velocity;} *buffer = (buffer_t*)glMapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, GL_READ_ONLY);for (j = 0; j < point_count; j++){vmath::vec4 position = buffer[j].position;vmath::vec3 velocity = buffer[j].velocity;}glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);}}// 交換緩沖區和處理事件glfwSwapBuffers(window);glfwPollEvents();//++t;}// 清理資源//glDeleteVertexArrays(2, VAOs);//glDeleteBuffers(2, VBOs);glDeleteProgram(redShader);glfwTerminate();return 0;
}