OPenGL--Transform feedback示例解析

簡述

英文地址https://open.gl/feedback

到目前爲止,我們總是將頂點數據發送到圖形處理器,並且只在幀緩存中生成繪製的像素。如果我們想要在經過頂點着色器或幾何着色器之後捕獲這些頂點呢?在這一章中,我們將探討一種方法,即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和光柵化來更新頂點並在同一時間繪製它們!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章