在 OpenGL 中绘制单色二维数组

Drawing a monochrome 2D array in OpenGL

我需要将 OpenGL 用于非常特定的目的。我有一个一维浮点数数组,大小为 [SIZE][SIZE](它总是正方形),代表二维图像。绘图在这里只是额外的,因为我一直使用第三方程序通过将数组输出到文本文件来进行绘图,但我想提供在程序本身中进行绘图的选项。

这个数组在循环中不断更新,因为它应该表示模拟字段的值,其细节无关紧要,但重要的是它们中的每一个的值都将是一个在 -1 和 1 之间浮动。现在,我只想将这个数组绘制为二维图像(实时),主循环的每 N 步。我尝试使用 X11 的像素绘图工具(我在 Linux 上这样做),并通过循环遍历并在 SIZE X SIZE window 上逐个像素地绘制数组,但是这非常慢,并且比模拟本身花费的时间要多得多。我一直在研究 OpenGL,从我读到的内容来看,理想的解决方案是将我的数组重新解释为 2D 纹理,然后将其打印在四边形上。显然要使用裸 OpenGL,我将不得不重新调整我的代码以与 OpenGL 绘图的主循环一起工作,这有点不切实际,所以如果可以在 GLFW 中完成同样的操作,我很高兴。

要绘制的图像始终是方形的,它的方向完全无关紧要,无论是镜像绘制、倒置绘制、转置绘制等都没有关系,因为它应该是完全各向同性的。

程序的主要backbone遵循下一个方案

#include <iostream>
#include <GLFW/glfw3.h>
using namespace std;


int main(int argc, char** argv)
{
    if (GFX) //GFX is a bool, only draw stuff if it's 1 (its value doesnt change)
    {
        //Initialize GLFW
    }


    float field[2*SIZE][SIZE] = {0}; //This is the array to print (only the first SIZE * SIZE components)

    for (int i = 0; i < totalTime; i++)
    {
        for (int x=0; x < SIZE; x++)
        {
            for (int y=0; y < SIZE; y++)
            {
                //Each position of the array is then updates here
            }
        }
        if (GFX)
        {
            //The drawing should be done here
        }
    }
    return 0;
}

我已经尝试了一些代码片段并修改了我发现的一些其他示例,但无法使其正常工作,要么他们必须调用 glLoop 来破坏我自己的模拟循环,要么它只是在中心打印一个像素。

所以我的主要问题是如何用 field 的前 SIZE X SIZE 分量制作纹理,然后在 QUAD 上绘制它。

谢谢!

对于新手来说最简单的就是使用没有着色器的旧API。为了完成这项工作,您只需将数据编码为 <0.0,1.0> 范围内的浮点数的一维线性数组,这可以从 <-1,+1> 非常快地在 CPU 侧使用单个 for 循环完成,如下所示:

for (i=0;i<size*size;i++) data[i]=0.5*(data[i]+1.0);

我既不使用 GLUT 也不为你的平台编写代码,所以我坚持只渲染:

//---------------------------------------------------------------------------
const int size=512;         // data  resolution
const int size2=size*size;
float data[size2];          // your float size*size data
GLuint txrid=-1;            // GL texture ID
//---------------------------------------------------------------------------
void init() // this must be called once (after GL is initialized)
    {
    int i;
    // generate float data
    Randomize();
    for (i=0;i<size2;i++) data[i]=Random();
    // create texture
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D,txrid);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_MODULATE);
    glDisable(GL_TEXTURE_2D);
    }
//---------------------------------------------------------------------------
void exit() // this must be called once (before GL is unitialized)
    {
    // release texture
    glDeleteTextures(1,&txrid);
    }
//---------------------------------------------------------------------------
void gl_draw()
    {
    glClear(GL_COLOR_BUFFER_BIT);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // bind texture
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D,txrid);
    // copy your actual data into it
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE32F_ARB, size, size, 0, GL_LUMINANCE, GL_FLOAT, data);

    // render single textured QUAD
    glColor3f(1.0,1.0,1.0);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0,0.0); glVertex2f(-1.0,-1.0);
    glTexCoord2f(1.0,0.0); glVertex2f(+1.0,-1.0);
    glTexCoord2f(1.0,1.0); glVertex2f(+1.0,+1.0);
    glTexCoord2f(0.0,1.0); glVertex2f(-1.0,+1.0);
    glEnd();

    // unbind texture (so it does not mess with othre rendering)
    glBindTexture(GL_TEXTURE_2D,0);
    glDisable(GL_TEXTURE_2D);

    glFlush();
    SwapBuffers(hdc);   // ignore this GLUT should make it on its own
    }
//---------------------------------------------------------------------------

此处预览:

为了完成这项工作,您需要在 GLUT 创建 GL 上下文后在应用程序启动时调用 init(),并在 GLUT 关闭 GL 上下文之前在应用程序结束时调用 exit()gl_draw() 将呈现您的数据,因此必须在 GLUT 的绘图事件中调用它。

如果您不想在 CPU 侧将范围转换为 <0,1>,您可以将其移动到着色器(非常简单的顶点和片段着色器),但我感觉你' re rookie 和 shaders 对你来说太过分了。如果你真的想走那条路,请看:

它还涵盖了没有 GLUT 但 Windows ...

的 GL 初始化

现在对上面的程序做一些说明:

  1. 我使用了GL_LUMINANCE32F_ARB纹理格式扩展

    它的 32 位浮点纹理格式未固定,因此您的数据保持原样。它应该出现在所有现在的 gfx HW 上。我这样做是为了简化向着色器的过渡,后者可以直接在原始数据上进行操作...

  2. size

    在原始 GL 规范中,纹理大小应该是 2 的幂,所以 16,32,64,128,256,512,... 如果不是,您需要使用矩形纹理扩展,但它在 gfx HW 中原生存在多年,所以不需要改变任何东西。但是在 linux 和 MAC 上,GL 实现可能会出现问题,所以如果某些东西不起作用,请尝试使用 2 大小的幂(以防万一)...

    也不要对尺寸过于狂热,因为 gfx 卡有限制,通常 2048 是低端东西的安全限制。如果你需要更多,那就做更多的马赛克 QUADS/textures

  3. GL_CLAMP_TO_EDGE

    这也是扩展(现在是 HW 原生的)所以你的纹理坐标从 01 而不是从 0+pixel/21-pixel/2 ...

然而,所有这些都不是 GL 1.0 的东西,因此您需要向您的应用程序添加扩展(如果 GLUT 或您使用的任何东西还没有)。所有这些都只是 tokens/constants 没有函数调用所以万一编译器抱怨它应该足够了:

#include <gl\glext.h>

包含 gl.h 后或直接添加定义:

#define GL_CLAMP_TO_EDGE                  0x812F
#define GL_LUMINANCE32F_ARB               0x8818

顺便说一句。你的代码看起来不像 GLUT 应用程序(但我可能错了,因为我不使用它)例如看这个:

你的 header 建议 GLFW3GLUT 完全不同(除非它源自 GLUT)所以也许你应该编辑标签和 OP 以匹配您的真实情况 have/use.

现在着色器:

如果您在 <-1,+1> 范围内生成数据:

for (i=0;i<size2;i++) data[i]=(2.0*Random())-1.0;

并使用这些着色器:

顶点:

// Vertex
#version 400 core
layout(location = 0) in vec2 pos;   // position
layout(location = 8) in vec2 tex;   // texture

out vec2 vpos;
out vec2 vtex;

void main()
    {
    vpos=pos;
    vtex=tex;
    gl_Position=vec4(pos,0.0,1.0);
    }

片段:

// Fragment
#version 400 core

uniform sampler2D txr;

in vec2 vpos;   // position
in vec2 vtex;   // texture

out vec4 col;

void main()
    {
    vec4 c;
    c=texture(txr,vtex);
    c=(c+1.0)*0.5;
    col=c;
    }

那么结果是一样的(GPU端转换速度更快)。但是,您需要将 GL_QUADS 转换为 VAO/VBOunless nVidia card is used 但即便如此,您肯定应该使用 VBO/VAO)。