簡述
到目前爲止,我們總是將頂點數據發送到圖形處理器,並且只在幀緩存中生成繪製的像素。如果我們想要在經過頂點着色器或幾何着色器之後捕獲這些頂點呢?在這一章中,我們將探討一種方法,即transform feedback。
到目前爲止,我們已經使用了VBO(頂點緩衝區對象)來存儲用於繪製操作的頂點。
Transform feedback 允許着色器把頂點寫回這些。
例如,您可以構建一個頂點着色器,它模擬重力並將更新後的頂點位置寫回緩衝區。這樣,您就不必將這些數據從圖形內存傳輸到主內存。最重要的是,你可以從如今GPU的巨大的並行處理能力中獲益。
基本Transform feedback
我們將從頭開始,以便最終程序能夠清楚地演示Transform feedback是多麼簡單。
不幸的是,這次沒有預覽,因爲我們不會在這一章裏畫任何東西!儘管這個特性可以用來簡化像粒子模擬這樣的效果,但是解釋這些內容超出了這些文章的範圍。在您瞭解了轉換反饋的基礎之後,您將能夠發現並理解關於這些主題的大量文章。
讓我們從一個簡單的頂點着色器開始。
const GLchar* vertexShaderSrc = R"glsl(
in float inValue;
out float outValue;
void main()
{
outValue = sqrt(inValue);
}
)glsl";
這個頂點着色器似乎沒有多大意義。它沒有設置一個gl_position,它只接受一個任意的浮點數作爲輸入。幸運的是,我們可以使用Transform feedback來捕獲結果,我們馬上就會看到。
GLuint shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(shader, 1, &vertexShaderSrc, nullptr);
glCompileShader(shader);
GLuint program = glCreateProgram();
glAttachShader(program, shader);
編譯着色器,創建一個程序並附加着色器,但是不要調用glLinkProgram()!在鏈接程序之前,我們必須告訴OpenGL,我們想要捕獲到一個緩衝區中的輸出屬性。
const GLchar* feedbackVaryings[] = { "outValue" };
glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);
第一個參數是不言而喻的,是着色器程序。第二個參數和第三個參數指定了輸出名稱數組和數組本身的長度,最後一個參數指定了應該如何寫入數據。
以下兩種格式可供選擇:
--GL_INTERLEAVED_ATTRIBS:將所有屬性寫入一個緩衝區對象。。
--GL_SEPARATE_ATTRIBS: 將屬性寫入多個緩衝區對象,或將不同的偏移量寫入緩衝區。
有時對於每個屬性都有單獨的緩衝區是很有用的,但是爲了讓我們對這個演示保持簡單。現在您已經指定了輸出變量,您可以鏈接並激活該程序。這是因爲鏈接過程依賴於關於輸出的設置。
glLinkProgram(program);
glUseProgram(program);
之後,創建和綁定VAO:
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
現在,創建的頂點着色器的輸入數據緩衝區:
GLfloat data[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
數據中的數字是我們想要的數字,用來計算平方根,Transform feedback將幫助我們得到結果。
GLint inputAttrib = glGetAttribLocation(program, "inValue");
glEnableVertexAttribArray(inputAttrib);
glVertexAttribPointer(inputAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0);
Transform feedback將返回outValue的值,但是首先我們需要創建一個VBO來保存這些值,就像輸入的頂點一樣:
GLuint tbo;
glGenBuffers(1, &tbo);
glBindBuffer(GL_ARRAY_BUFFER, tbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(data), nullptr, GL_STATIC_READ);
注意,我們現在傳遞了一個nullptr,以創建一個足夠大的緩衝區,以便容納所有生成的浮點數,但是沒有指定任何初始數據。設置一個適當的使用類型GL_STATIC_READ,它表示我們打算將OpenGL寫入到這個緩衝區中,我們的應用程序可以從中讀取。(請參閱使用類型的參考資料)
現在我們已經爲渲染計算過程做了所有的準備。由於我們不打算畫任何東西,光柵化器應該被禁用:
glEnable(GL_RASTERIZER_DISCARD);
爲了實際綁定我們在上面創建的緩衝區作爲轉換反饋緩衝區,我們必須使用一個名爲glBindBufferBase的新函數。
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);
第一個參數是目前需gl_transform_feedback_buffer讓未來的擴展。第二個參數是輸出 變量的指數,這是簡單的零因爲我們只有一個。最後一個參數 指定綁定的緩衝區對象。
做畫的電話之前,你必須輸入變換反饋模式:
目前,第一個參數是GL_TRANSFORM_FEEDBACK_BUFFER,以支持將來的擴展。第二個參數是輸出變量的索引,它是0,因爲我們只有一個。最後一個參數指定要綁定的緩衝區對象。
在進行繪製調用之前,您必須設置transform_feedback模式:
glBeginTransformFeedback(GL_POINTS);
它肯定會讓人想起昔日的“美好時光”!就像上一章的幾何着色一樣,原始模式的可能值也有一些限制。
--GL_POINTS — GL_POINTS
--GL_LINES — GL_LINES, GL_LINE_LOOP, GL_LINE_STRIP, GL_LINES_ADJACENCY, GL_LINE_STRIP_ADJACENCY
--GL_TRIANGLES — GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES_ADJACENCY,
--GL_TRIANGLE_STRIP_ADJACENCY
如果你只有一個頂點着色器,就像我們現在所做的那樣,普通模式必須與被繪製的那個元素匹配:
glDrawArrays(GL_POINTS, 0, 5);
即使我們現在使用的是數據,單個數字仍然可以看作是單獨的“點”,所以我們使用普通模式。
結束轉換反饋模式:
glEndTransformFeedback();
通常,在繪圖操作結束時,我們將交換緩衝區以在屏幕上顯示結果。我們仍然希望在嘗試訪問結果之前確保渲染操作已經完成,所以我們刷新OpenGL的命令緩衝區:
glFlush();
將結果返回現在就像將緩衝區數據複製回數組一樣簡單:
GLfloat feedback[5];
glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);
如果您現在在數組中打印值,那麼您應該在終端中看到輸入的平方根:
printf("%f %f %f %f %f\n", feedback[0], feedback[1], feedback[2], feedback[3], feedback[4]);
打印結果
恭喜你,你現在知道如何讓你的GPU執行帶有頂點着色器的通用任務!當然,像OpenCL這樣的真正的GPGPU框架通常更好,但是Feedback transform的優勢在於,您可以直接在繪圖操作中重新使用數據,
例如將Feedback transform緩衝區作爲數組緩衝區進行綁定,並執行正常的繪圖調用。
如果你有顯卡和驅動程序支持它,你也可以在OpenGL4.3中使用計算着色器,它實際上是爲與繪圖相關的任務設計的。
以下是完整的代碼
// Link statically with GLEW
#define GLEW_STATIC
// Headers
#include <GL/glew.h>
#include <SFML/Window.hpp>
// Vertex shader
const GLchar* vertexShaderSrc = R"glsl(
#version 150 core
in float inValue;
out float outValue;
void main()
{
outValue = sqrt(inValue);
}
)glsl";
int main()
{
sf::ContextSettings settings;
settings.depthBits = 24;
settings.stencilBits = 8;
sf::Window window(sf::VideoMode(800, 600, 32), "Transform Feedback", sf::Style::Titlebar | sf::Style::Close, settings);
// Initialize GLEW
glewExperimental = GL_TRUE;
glewInit();
// Compile shader
GLuint shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(shader, 1, &vertexShaderSrc, nullptr);
glCompileShader(shader);
// Create program and specify transform feedback variables
GLuint program = glCreateProgram();
glAttachShader(program, shader);
const GLchar* feedbackVaryings[] = { "outValue" };
glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);
glLinkProgram(program);
glUseProgram(program);
// Create VAO
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
// Create input VBO and vertex format
GLfloat data[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
GLint inputAttrib = glGetAttribLocation(program, "inValue");
glEnableVertexAttribArray(inputAttrib);
glVertexAttribPointer(inputAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0);
// Create transform feedback buffer
GLuint tbo;
glGenBuffers(1, &tbo);
glBindBuffer(GL_ARRAY_BUFFER, tbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(data), nullptr, GL_STATIC_READ);
// Perform feedback transform
glEnable(GL_RASTERIZER_DISCARD);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);
glBeginTransformFeedback(GL_POINTS);
glDrawArrays(GL_POINTS, 0, 5);
glEndTransformFeedback();
glDisable(GL_RASTERIZER_DISCARD);
glFlush();
// Fetch and print results
GLfloat feedback[5];
glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);
printf("%f %f %f %f %f\n", feedback[0], feedback[1], feedback[2], feedback[3], feedback[4]);
glDeleteProgram(program);
glDeleteShader(shader);
glDeleteBuffers(1, &tbo);
glDeleteBuffers(1, &vbo);
glDeleteVertexArrays(1, &vao);
window.close();
return 0;
}
Feedback transform和幾何着色器
當你有幾何着色器,變換反饋操作將 捕捉幾何着色器的輸出而不是頂點着色器。對於 例子:
// 頂點着色器
const GLchar* vertexShaderSrc = R"glsl(
in float inValue;
out float geoValue;
void main()
{
geoValue = sqrt(inValue);
}
)glsl";
//幾何着色器
const GLchar* geoShaderSrc = R"glsl(
layout(points) in;
layout(triangle_strip, max_vertices = 3) out;
in float[] geoValue;
out float outValue;
void main()
{
for (int i = 0; i < 3; i++) {
outValue = geoValue[0] + i;
EmitVertex();
}
EndPrimitive();
}
)glsl";
幾何着色器接受頂點着色器處理的一個點,生成一個三角形,每個點都有一個更高的值。
GLuint geoShader = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geoShader, 1, &geoShaderSrc, nullptr);
glCompileShader(geoShader);
...
glAttachShader(program, geoShader);
編譯並將幾何着色器附加到程序中以開始使用它。
const GLchar* feedbackVaryings[] = { "outValue" };
glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);
雖然現在的輸出現在來自於幾何着色器,但我們沒有改變名稱,所以這段代碼保持不變。
因爲每個輸入頂點將生成3個頂點作爲輸出,所以Transform feedback 緩衝區現在需要3倍於輸入緩衝區的大小:
glBufferData(GL_ARRAY_BUFFER, sizeof(data) * 3, nullptr, GL_STATIC_READ);
當使用幾何着色器時,指定爲glBeginTransformFeedback的類型必須與幾何着色器的輸出類型匹配:
glBeginTransformFeedback(GL_TRIANGLES);
檢索輸出仍然是相同的:
//獲取並打印結果
GLfloat feedback[15];
glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);
for (int i = 0; i < 15; i++) {
printf("%f\n", feedback[i]);
}
打印結果
雖然你要注意反饋的數據類型和你的緩衝區的大小 ,但添加幾何着色器的方程並沒有太大變化, 除了負責輸出着色器。
以下是完整的代碼
// Link statically with GLEW
#define GLEW_STATIC
// Headers
#include <GL/glew.h>
#include <SFML/Window.hpp>
// Vertex shader
const GLchar* vertexShaderSrc = R"glsl(
#version 150 core
in float inValue;
out float geoValue;
void main()
{
geoValue = sqrt(inValue);
}
)glsl";
// Geometry shader
const GLchar* geoShaderSrc = R"glsl(
#version 150 core
layout(points) in;
layout(triangle_strip, max_vertices = 3) out;
in float[] geoValue;
out float outValue;
void main()
{
for (int i = 0; i < 3; i++) {
outValue = geoValue[0] + i;
EmitVertex();
}
EndPrimitive();
}
)glsl";
int main()
{
sf::ContextSettings settings;
settings.depthBits = 24;
settings.stencilBits = 8;
sf::Window window(sf::VideoMode(800, 600, 32), "Transform Feedback", sf::Style::Titlebar | sf::Style::Close, settings);
// Initialize GLEW
glewExperimental = GL_TRUE;
glewInit();
// Compile shaders
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSrc, nullptr);
glCompileShader(vertexShader);
GLuint geoShader = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geoShader, 1, &geoShaderSrc, nullptr);
glCompileShader(geoShader);
// Create program and specify transform feedback variables
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, geoShader);
const GLchar* feedbackVaryings[] = { "outValue" };
glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);
glLinkProgram(program);
glUseProgram(program);
// Create VAO
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
// Create input VBO and vertex format
GLfloat data[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
GLint inputAttrib = glGetAttribLocation(program, "inValue");
glEnableVertexAttribArray(inputAttrib);
glVertexAttribPointer(inputAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0);
// Create transform feedback buffer
GLuint tbo;
glGenBuffers(1, &tbo);
glBindBuffer(GL_ARRAY_BUFFER, tbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(data) * 3, nullptr, GL_STATIC_READ);
// Perform feedback transform
glEnable(GL_RASTERIZER_DISCARD);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);
glBeginTransformFeedback(GL_TRIANGLES);
glDrawArrays(GL_POINTS, 0, 5);
glEndTransformFeedback();
glDisable(GL_RASTERIZER_DISCARD);
glFlush();
// Fetch and print results
GLfloat feedback[15];
glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);
for (int i = 0; i < 15; i++) {
printf("%f\n", feedback[i]);
}
glDeleteProgram(program);
glDeleteShader(geoShader);
glDeleteShader(vertexShader);
glDeleteBuffers(1, &tbo);
glDeleteBuffers(1, &vbo);
glDeleteVertexArrays(1, &vao);
window.close();
return 0;
}
Transform feedback變量反饋
正如我們在前一章中看到的,幾何着色器有惟一的屬性來生成一個可變數量的數據。幸運的是,有一些方法可以通過"查詢對象"獲得輸出圖元的個數。
就像OpenGL中的所有其他對象一樣,你必須先創建一個:
GLuint query;
glGenQueries(1, &query);
然後在調用glBeginTransformFeedback之前,你必須告訴OpenGL跟蹤圖元的數量:
glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query);
調用glendtransformfeedback後,你可以停止“記錄”:
glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
檢索結果如下:
GLuint primitives;
glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitives);
然後,您可以將該值與其他數據一起打印出來:
printf("%u primitives written!\n\n", primitives);
打印結果
以下是完整的代碼
// Link statically with GLEW
#define GLEW_STATIC
// Headers
#include <GL/glew.h>
#include <SFML/Window.hpp>
// Vertex shader
const GLchar* vertexShaderSrc = R"glsl(
#version 150 core
in float inValue;
out float geoValue;
void main()
{
geoValue = sqrt(inValue);
}
)glsl";
// Geometry shader
const GLchar* geoShaderSrc = R"glsl(
#version 150 core
layout(points) in;
layout(triangle_strip, max_vertices = 3) out;
in float[] geoValue;
out float outValue;
void main()
{
for (int i = 0; i < 3; i++) {
outValue = geoValue[0] + i;
EmitVertex();
}
EndPrimitive();
}
)glsl";
int main()
{
sf::ContextSettings settings;
settings.depthBits = 24;
settings.stencilBits = 8;
sf::Window window(sf::VideoMode(800, 600, 32), "Transform Feedback", sf::Style::Titlebar | sf::Style::Close, settings);
// Initialize GLEW
glewExperimental = GL_TRUE;
glewInit();
// Compile shaders
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSrc, nullptr);
glCompileShader(vertexShader);
GLuint geoShader = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geoShader, 1, &geoShaderSrc, nullptr);
glCompileShader(geoShader);
// Create program and specify transform feedback variables
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, geoShader);
const GLchar* feedbackVaryings[] = { "outValue" };
glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);
glLinkProgram(program);
glUseProgram(program);
// Create VAO
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
// Create input VBO and vertex format
GLfloat data[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
GLint inputAttrib = glGetAttribLocation(program, "inValue");
glEnableVertexAttribArray(inputAttrib);
glVertexAttribPointer(inputAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0);
// Create transform feedback buffer
GLuint tbo;
glGenBuffers(1, &tbo);
glBindBuffer(GL_ARRAY_BUFFER, tbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(data) * 3, nullptr, GL_STATIC_READ);
// Create query object to collect info
GLuint query;
glGenQueries(1, &query);
// Perform feedback transform
glEnable(GL_RASTERIZER_DISCARD);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);
glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query);
glBeginTransformFeedback(GL_TRIANGLES);
glDrawArrays(GL_POINTS, 0, 5);
glEndTransformFeedback();
glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
glDisable(GL_RASTERIZER_DISCARD);
glFlush();
// Fetch and print results
GLuint primitives;
glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitives);
GLfloat feedback[15];
glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);
printf("%u primitives written!\n\n", primitives);
for (int i = 0; i < 15; i++) {
printf("%f\n", feedback[i]);
}
glDeleteQueries(1, &query);
glDeleteProgram(program);
glDeleteShader(geoShader);
glDeleteShader(vertexShader);
glDeleteBuffers(1, &tbo);
glDeleteBuffers(1, &vbo);
glDeleteVertexArrays(1, &vao);
window.close();
return 0;
}
注意,它返回的是圖元的數量,而不是頂點的數量。因爲我們有15個頂點,每個三角形都有3個,我們有5個圖元。
查詢對象也可以用來記錄一些東西,比如在處理幾何着色器和gltime用時,用來測量在服務器上花費的時間(圖形卡)工作的時間。如果你被困在某個地方,
結論
你現在已經對幾何學的陰影和Transform feedback有了足夠的瞭解,讓你的圖形卡做一些非常有趣的工作,而不僅僅是繪畫!您甚至可以組合Variable feedback和光柵化來更新頂點並在同一時間繪製它們!