該原創文章首發於微信公眾號:位元組流動
什麼是 Transform Feedback
Transform Feedback(變換反饋)是在 OpenGLES3.0 渲染管線中,頂點處理階段結束之後,圖元裝配和光柵化之前的一個步驟。 Transform Feedback 可以重新捕獲即將裝配為圖元(點,線段,三角形)的頂點,然後你將它們的部分或者全部屬性傳遞到快取物件。
Transform Feedback 的主要作用是可以將頂點著色器的處理結果輸出,並且可以有多個輸出,這樣可以將大量的向量或矩陣運算交給 GPU 並行處理,這是 OpenGLES 3.0 的新特性。
每個頂點在傳遞到圖元裝配階段時,將所有需要捕獲的屬性資料記錄到一個或者多個快取物件中,程式可以通過這些快取讀出這些資料,可以將他們用於後續的渲染操作。
Transform Feedback 物件
Transform Feedback 所有狀態通過一個 Transform Feedback 物件管理,主要包括以下狀態:
- 用於記錄頂點資料的快取物件;
- 用於標識快取物件的計數器;
- 用於標識 Transform Feedback 當前是否啟用的狀態量。
Transform Feedback 物件的建立繫結過程和一般的 OpenGLES 物件類似,如 VAO 。
生成和繫結 Transform Feedback 物件:
glGenTransformFeedbacks(1, &m_TransFeedbackObjId);
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_TransFeedbackObjId);
複製程式碼
Transform Feedback 快取
Transform Feedback 主要用來管理將頂點捕捉到快取物件的相關狀態。這個狀態中包含當前連線到的 Transform Feedback 快取繫結點的快取物件。可以同時給 Transform Feedback 繫結多個快取,也可以繫結快取物件的多個子塊,甚至可以將同一個快取物件不用子塊繫結到不同的 Transform Feedback 快取繫結點上。
建立 Transform Feedback 快取類似於建立 VBO 。
glGenBuffers(1, &m_TransFeedbackBufId);
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_TransFeedbackBufId);
// 設定快取的大小,輸出是一個 3 維向量和一個 2 維向量,一共 6 個頂點,大小為 (3 + 2) * 6 * sizeof(GLfloat)
glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, (3 + 2) * 6 * sizeof(GLfloat), NULL, GL_STATIC_READ);
複製程式碼
介面 glBindBufferBase
將快取繫結到當前 Transform Feedback 物件。
void glBindBufferBase(GLenum target, GLuint index, Gluint buffer);
複製程式碼
其中:
- target 引數須設定為 GL_TRANSFORM_FEEDBACK_BUFFER;
- index 必須是當前繫結的 transform feedback 物件的快取繫結點索引;
- buffer 表示被繫結的快取物件的 ID 。
為 Transform Feedback 物件繫結緩衝區物件。
glGenTransformFeedbacks(1, &m_TransFeedbackObjId);
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_TransFeedbackObjId);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_TransFeedbackBufId); // Specify the index of the binding point within the array specified by target.
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0);
複製程式碼
Transform Feedback 變數
glTransformFeedbackVaryings
用於指定變換反饋的變數,也就是頂點著色器需要輸出的變數。
宣告瞭 2 個變換反饋變數的頂點著色器:
#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texCoord;
out vec2 v_texCoord;
out vec3 outPos;
out vec2 outTex;
void main()
{
gl_Position = a_position;
v_texCoord = a_texCoord;
outPos = vec3(a_position)*3.0; //將位置向量做一個簡單運算後輸出
outTex = a_texCoord * 3.0; //將紋理座標向量做一個簡單運算後輸出
}
複製程式碼
設定變換反饋變數,需要注意的是 glTransformFeedbackVaryings
需要在 glLinkProgram
之前呼叫。
glAttachShader(program, vertexShaderHandle);
glAttachShader(program, fragShaderHandle);
GLchar const * varyings[] = {"outPos", "outTex"};
glTransformFeedbackVaryings(m_ProgramObj, sizeof(varyings)/ sizeof(varyings[0]), varyings, GL_INTERLEAVED_ATTRIBS);
glLinkProgram(program);
複製程式碼
Transform Feedback 捕獲啟動和停止
Transform Feedback 可以隨時啟動、暫停和停止。
glBeginTransformFeedback
用於開始 Transform Feedback ,它的引數是用來設定將要記錄的圖元型別,如:GL_POINTS、GL_LINES 和 GL_TRIANGLES 。
glPuaseTransformFeedback
暫停 Transform Feedback 對變數的記錄,但 Transform Feedback 還是處於啟動狀態。如果 Transform Feedback 沒有啟動則 OpenGLES 產生錯誤。
glResumeTransformFeedback
重新開啟一個之前通過 glPuaseTransformFeedback
暫停的變換反饋過程,如果 Transform Feedback 沒有啟動,或者沒有被處於活動狀態,則產生OpenGL錯誤。
glEndTransformFeedback
用來結束 Transform Feedback 過程。
Transform Feedback 緩衝區讀取
Transform Feedback 過程結束後,通過 glMapBufferRange
讀取緩衝區資料。
//繫結要讀取的緩衝區物件
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_TransFeedbackBufId);
//讀取緩衝區資料
void* rawData = glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, (3 + 2) * 6 * sizeof(GLfloat), GL_MAP_READ_BIT);
float *p = (float*)rawData;
for(int i= 0; i< 6; i++)
{
LOGCATE("TransformFeedbackSample::Draw() read feedback buffer outPos[%d] = [%f, %f, %f], outTex[%d] = [%f, %f]", i, p[i * 5], p[i * 5 + 1], p[i * 5 + 2], i, p[i * 5 + 3], p[i * 5 + 4]);
}
//解綁
glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0);
複製程式碼
Transform Feedback 的使用
Transform Feedback 的一般使用流程:
- 設定變換反饋變數;
- 建立 Transform Feedback 緩衝區;
- 建立 Transform Feedback 物件,並繫結緩衝區;
- 啟動變換反饋,在繪製結束後停止變換反饋;
- 讀取 Transform Feedback 緩衝區資料。
總體實現程式碼:
//1. 設定變換反饋變數;
glAttachShader(program, vertexShaderHandle);
glAttachShader(program, fragShaderHandle);
GLchar const * varyings[] = {"outPos", "outTex"};
glTransformFeedbackVaryings(m_ProgramObj, sizeof(varyings)/ sizeof(varyings[0]), varyings, GL_INTERLEAVED_ATTRIBS);
glLinkProgram(program);
//2. 建立 Transform Feedback 緩衝區;
glGenBuffers(1, &m_TransFeedbackBufId);
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_TransFeedbackBufId);
glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, (3 + 2) * 6 * sizeof(GLfloat), NULL, GL_STATIC_READ);
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0);
//3. 建立 Transform Feedback 物件,並繫結緩衝區;
glGenTransformFeedbacks(1, &m_TransFeedbackObjId);
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_TransFeedbackObjId);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_TransFeedbackBufId);
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0);
//4. 啟動變換反饋,在繪製結束後停止變換反饋;
glViewport(0, 0, screenW, screenH);
glUseProgram(m_ProgramObj);
glBindVertexArray(m_VaoId);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_ImageTextureId);
glUniform1i(m_SamplerLoc, 0);
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_TransFeedbackObjId);
glBeginTransformFeedback(GL_TRIANGLES);
glDrawArrays(GL_TRIANGLES, 0, 6);
glEndTransformFeedback();
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
glBindTexture(GL_TEXTURE_2D, GL_NONE);
glBindVertexArray(GL_NONE);
//5. 讀取 Transform Feedback 緩衝區資料。
// Read feedback buffer
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_TransFeedbackBufId);
void* rawData = glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, (3 + 2) * 6 * sizeof(GLfloat), GL_MAP_READ_BIT);
float *p = (float*)rawData;
for(int i= 0; i< 6; i++)
{
LOGCATE("TransformFeedbackSample::Draw() read feedback buffer outPos[%d] = [%f, %f, %f], outTex[%d] = [%f, %f]", i, p[i * 5], p[i * 5 + 1], p[i * 5 + 2], i, p[i * 5 + 3], p[i * 5 + 4]);
}
glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0);
複製程式碼
程式碼執行後讀取 Transform Feedback 緩衝區的資料:
E/ByteFlow: TransformFeedbackSample::Draw() read feedback buffer outPos[0] = [-3.000000, -1.500000, 0.000000], outTex[0] = [0.000000, 3.000000]
E/ByteFlow: TransformFeedbackSample::Draw() read feedback buffer outPos[1] = [3.000000, -1.500000, 0.000000], outTex[1] = [3.000000, 3.000000]
E/ByteFlow: TransformFeedbackSample::Draw() read feedback buffer outPos[2] = [-3.000000, 1.500000, 0.000000], outTex[2] = [0.000000, 0.000000]
E/ByteFlow: TransformFeedbackSample::Draw() read feedback buffer outPos[3] = [3.000000, -1.500000, 0.000000], outTex[3] = [3.000000, 3.000000]
E/ByteFlow: TransformFeedbackSample::Draw() read feedback buffer outPos[4] = [3.000000, 1.500000, 0.000000], outTex[4] = [3.000000, 0.000000]
E/ByteFlow: TransformFeedbackSample::Draw() read feedback buffer outPos[5] = [-3.000000, 1.500000, 0.000000], outTex[5] = [0.000000, 0.000000]
複製程式碼
Transform Feedback 實現程式碼路徑: github.com/githubhaoha…