如何在 C++ 中为 GLSL 片段着色器实现 iGlobalTime?

Posted

技术标签:

【中文标题】如何在 C++ 中为 GLSL 片段着色器实现 iGlobalTime?【英文标题】:How to implement iGlobalTime for a GLSL fragment shader in C++? 【发布时间】:2016-09-28 23:15:31 【问题描述】:

目前我有一个小型 C++ 应用程序,它通过这两个着色器显示一个三角形:

顶点着色器:

static const char *vertex_shader_glsl = \
"#version 430\n"

"layout (location=0)in vec2 inVer;"
"out vec2 p;"
"out gl_PerVertex"
""
"vec4 gl_Position;"
";"

"void main()"
""
"gl_Position=vec4(inVer,0.0,1.0);"
"p=inVer;"
"";

片段着色器:

#version 430


out vec4 fragColor;
in vec2 fragCoord;
vec2 iResolution;
//uniform float iGlobalTime;
float a = -2;

void main()

    a = sin(-2);
    //Colors
    vec3 fgColor = vec3(0.741, 0.635, 0.471);
    vec3 bgColor = vec3(0.192, 0.329, 0.439);

    //Triangle barycentric coordinates defined on screen space


    vec2 t0 = vec2(0.25, 0);
    vec2 t1 = vec2(0.75, 0.25);
    vec2 t2 = vec2(0.50, 0.85);
    vec2 tCentroid  = (t0 + t1 + t2)/3.0;    
    //Compute UV coordinates
    vec2 uv = fragCoord.xy;

    vec2 v0 = t2 - t0;
    vec2 v1 = t1 - t0;
    vec2 v2 = uv - t0;

    //Compute barycentric coordinates 
    float dot00 = dot(v0, v0);
    float dot01 = dot(v0, v1);
    float dot02 = dot(v0, v2);
    float dot11 = dot(v1, v1);
    float dot12 = dot(v1, v2);

    float invDenom = 1.0/(dot00 * dot11 - dot01 * dot01);
    float baryX = (dot11 * dot02 - dot01 * dot12) * invDenom;
    float baryY = (dot00 * dot12 - dot01 * dot02) * invDenom;

    if((baryX >= 0.0) && (baryY >= 0.0) && (baryX + baryY <= 1.0)) 
        fragColor = vec4(fgColor, 1.0);
     else 
        fragColor = vec4(bgColor, 1.0);
    

这两个着色器显示一个简单的三角形:

但是我想旋转这个三角形,所以我需要一些计时器实现 (iGlobalTime)。

我的意思是,我知道我需要这样的东西: https://www.shadertoy.com/view/Xtt3WX

但是我无法从上面的链接复制粘贴代码,因为我无法在我的 C++ 项目中使用 iGlobalTime。如何更新上面的着色器以便能够根据系统时间旋转三角形?如何在一个非常基础的 C++ 应用程序中实现 iGlobalTime?

============================

已编辑

我已经更新了:

ext.h:

...
    #ifdef DEBUG
    #define NUMFUNCTIONS 14 // <<== HERE
    #else
    #define NUMFUNCTIONS 12 // <<== HERE
    #endif

    extern void *myglfunc[NUMFUNCTIONS];

    #define glCreateShaderProgramv         ((PFNGLCREATESHADERPROGRAMVPROC)myglfunc[0])
    #define glGenProgramPipelines          ((PFNGLGENPROGRAMPIPELINESPROC)myglfunc[1])
    #define glBindProgramPipeline          ((PFNGLBINDPROGRAMPIPELINEPROC)myglfunc[2])
    #define glUseProgramStages             ((PFNGLUSEPROGRAMSTAGESPROC)myglfunc[3])
    #define glProgramUniform4fv            ((PFNGLPROGRAMUNIFORM4FVPROC)myglfunc[4])
    #define glGenFramebuffers              ((PFNGLGENFRAMEBUFFERSPROC)myglfunc[5])
    #define glBindFramebuffer              ((PFNGLBINDFRAMEBUFFERPROC)myglfunc[6])
    #define glTexStorage2D                 ((PFNGLTEXSTORAGE2DPROC)myglfunc[7])
    #define glDrawBuffers                  ((PFNGLDRAWBUFFERSPROC)myglfunc[8])
    #define glFramebufferTexture           ((PFNGLFRAMEBUFFERTEXTUREPROC)myglfunc[9])
    #define glProgramUniform1f             ((PFNGLPROGRAMUNIFORM1FPROC)myglfunc[10]) // <<== ADDED NEW LINE HERE
    #define glGetUniformLocation           ((PFNGLGETUNIFORMLOCATIONPROC)myglfunc[11]) // <<== ADDED NEW LINE HERE

    #ifdef DEBUG
    #define glGetProgramiv          ((PFNGLGETPROGRAMIVPROC)myglfunc[12])
    #define glGetProgramInfoLog     ((PFNGLGETPROGRAMINFOLOGPROC)myglfunc[13])
    #endif
...

更新了 ext.cpp:

...
static char *strs[] = 
    "glCreateShaderProgramv",
    "glGenProgramPipelines",
    "glBindProgramPipeline",
    "glUseProgramStages",
    "glProgramUniform4fv",
    "glProgramUniform1fv",
    "glGenFramebuffers",
    "glBindFramebuffer",
    "glTexStorage2D",
    "glDrawBuffers",
    "glFramebufferTexture",
    "glProgramUniform1f",        // <<== ADDED NEW LINE HERE
    "glGetUniformLocation",      // <<== ADDED NEW LINE HERE

    #ifdef DEBUG
    "glGetProgramiv",
    "glGetProgramInfoLog",
    #endif
    ;
...

更新了 intro.cpp:

...
    void intro_do(long time)
    
    ...
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glBindProgramPipeline(renderingPipeline);
        // Set fparams to give input to shaders

        // Render
        glProgramUniform4fv(fragmentShader, 0, 4, fparams);
        glProgramUniform1f(fragmentShader, glGetUniformLocation(fragmentShader, "iGlobalTime"), time*1000); // <<== ADDED NEW LINE HERE
        glRects(-1, -1, 1, 1); // Deprecated. Still seems to work though.

    ...
    

应用程序现在运行,但立即崩溃说:“4kIntro.exe 中 0x7542CB49 处的未处理异常:0xC0000005:访问冲突读取位置 0x00000000。”

【问题讨论】:

和其他制服一样吗? 我已经试过了:写“uniform float iGlobalTime;”导致 iGlobalTime=0 不断。似乎没有什么增加它。 是的,OpenGL 不知道 iGlobalTime 应该是时间,你必须自己设置它(就像任何其他制服一样)。 这是有道理的,但是如何设置呢?这是这里的问题。 【参考方案1】:

你可以这样做(你需要一个兼容 c++11 的编译器)

#include <chrono>

int main()

    // init stuff

    // game loop
    using Clock = std::chronohigh_resolution_clock;
    using TimePoint = std::chrono::time_point<Clock>;

    TimePoint t0, t1;
    float global_time = 0;
    t0 = Clock::now();
    while (run_game)
    
        // dt is the time since last frame
        // global_time is the total time since the start of the game loop
        t1 = Clock::now();
        float dt = std::chrono::duration_cast<std::chrono::duration<float, std::chrono::seconds::period>>(t1 - t0);
        global_time += dt;

        // Send your uniform and kickoff rendering
    

    return 0;

希望这有帮助(代码中可能有一些错字我没有检查它)

编辑: 看起来你误解了一些东西,所以我会试着解释一下事情是如何发生的。

着色器

根据the OpenGL wiki,着色器是“一种用户定义的程序,旨在运行在图形处理器的某个阶段,其目的是执行渲染管线的可编程阶段之一。”

这意味着,当您要求 GPU 绘制内容时(通过 draw call,即glDrawArraysglDrawElements),它会调用,对于每个顶点,顶点着色器,然后,对于每个片段,片段着色器。 (这个非常简化,关于整个流水线更完整的解释可以参考this OpenGL wiki)。

CPU 和 GPU 之间的通信

在调用 glDrawArraysglDrawElements 之前,您必须首先向 GPU 发送您希望它绘制的顶点。

你在你的 c++ 代码中做了一些事情,比如(这可能不是完全相同的代码,不用担心)

GLfloat data[] =  ...  // The data you want to send
GLuint vao, vbo;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof data, data, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0);

在这里,您将一些顶点数组发送到 GPU,并且在它的每个元素上,您的 顶点着色器 将在您每次发出 drawcall 时执行。 在其中你输出一个gl_Position,然后它会被 GPU 光栅化并输入到 fragment(或像素)shader。在这里,对于每个片段/像素,您的着色器都将被执行。

“这很好,但是如何将全局数据发送到 GPU ?” 这就是uniforms 发挥作用的地方。您可以使用它们将每个着色器数据发送到 GPU。这是在您的 c++ 代码中完成的,类似于

GLuint location = glGetUniformLocation(program_id, "uniform_name");
glUniform_(location, uniform_data);

由于 OpenGL API 是用 C 编写的,所有glUniform 调用都以您要发送的数据类型为后缀(​​所有调用请参见the docs)。然后,发送数据后,您就可以在着色器中使用它了。

小心,在着色器中声明统一是不够的,您还必须将数据发送到 GPU。这与 C++ 中的任何变量相同,只要您不影响它,它就不会具有可靠的值。

在你的情况下,因为你想发送一个浮动制服,称为iGlobalTime,你会有类似的东西

GLuint time_loc = glUniformLocation(program_id, "iGlobalTime");
glUniform1f(time_loc, global_time)

考虑到这一点的更新后的 c++ 代码是

#include <chrono>
int main()

    // init stuff
    // Compile your shader_program and store its id in program_id
    // Set the vertex data on the GPU

    // game loop
    using Clock = std::chronohigh_resolution_clock;
    using TimePoint = std::chrono::time_point<Clock>;

    TimePoint t0, t1;
    float global_time = 0;
    t0 = Clock::now();

    while (!quit)
    
        // dt is the time since last frame
        // global_time is the total time since the start of the game loop
        t1 = Clock::now();
        float dt = std::chrono::duration_cast<std::chrono::duration<float, std::chrono::seconds::period>>(t1 - t0);
        global_time += dt;

        // Activate program
        glUseProgram(program_id);
        // Send global_time uniform
        glUniform1f(glGetUniformLocation(program_id, "iGlobalTime"), global_time);

        // Issue draw call
        glBindVertexArray(vao);
        glDrawArrays(GL_TRIANGLES, 0, 4);
        glBindVertexArray(0);
    

    return 0;

【讨论】:

不,它应该在你的 c++ 代码中,一旦我可以访问计算机,我会让它更清晰 我更新了答案,如果还有什么不清楚的告诉我。 其实,我并没有指出你的着色器保持不变,我展示的所有代码都在 c++ 端 我猜在 intro_do 中 你得到一个很长的值,所以我猜这是毫秒或纳秒,所以如果你想要秒,你可能需要一些转换,但是你可以使用这个值

以上是关于如何在 C++ 中为 GLSL 片段着色器实现 iGlobalTime?的主要内容,如果未能解决你的问题,请参考以下文章

GLSL:使用片段着色器进行对象翻译

片段着色器中设置的颜色未显示 GLSL 1.30

片段着色器不会在 OpenGL GLSL 中创建像光一样的渐变

GLSL-片段着色器不同部分的精度不同

GLSL/C++:自定义着色器的默认行为

GLSL片段着色器 - 绘制简单的粗曲线