使用 glVertex2i() 绘制图形显示使用 glScaled() 缩放时的人工制品

Plotting graphs with glVertex2i() shows artefacts when scaled with glScaled()

我正在使用 OpenGL 绘制 RGB 图像直方图。由于这是一个 8 位图像直方图,我的数据集包含从 0 到 255 的数据点。

如果我在不使用 glScaled() 的情况下绘制直方图,则图形会按预期绘制,但当然不会填充分配的区域(宽度可变,高度不变)。但是,当我使用 glScaled() 时,图表显示出奇怪的人工制品。

请查看以下图片以查看问题示例:

上图显示了使用 256 个数据点绘制的直方图,未使用 glScaled() 进行缩放。

上面的两张图片显示了使用 256 个数据点绘制并使用 glScaled() 缩放的直方图。奇怪的人工制品很明显(缺少数据?)。请注意,由于光照水平的变化,第三个直方图的形状略有不同。

这是我的 OpenGL 初始化代码的相关部分:

glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

glOrtho(0.0f, width, height, 0.0f, 0.0f, 1.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

// If this line is removed then the graph plots correctly
// m_scale_factor = width / 256
glScaled(m_scale_factor, 1.0, 1.0);

glClear(GL_COLOR_BUFFER_BIT);

这里是我的情节代码的相关部分:

glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);

glBegin(GL_LINE_STRIP);
for (int n = 0; n < m_histogram_X; n++)
{
    glColor4ub(255, 0, 0, 255);
    glVertex2i(n, m_Hist_Channel_R[n]);
    glVertex2i(n, GRAPH_HEIGHT);

    glColor4ub(0, 255, 0, 255);
    glVertex2i(n, m_Hist_Channel_G[n]);
    glVertex2i(n, GRAPH_HEIGHT);

    glColor4ub(0, 0, 255, 255);
    glVertex2i(n, m_Hist_Channel_B[n]);
    glVertex2i(n, GRAPH_HEIGHT);
}
glEnd()

...

在这个阶段我觉得我必须声明我是 OpenGL 的新手,所以我可能误解了很多 OpenGL 的东西...

我的问题是:是否可以在 OpenGL 中解决这个问题,或者我是否必须通过某种插值来增加数据点的数量,然后在不缩放的情况下进行绘图?

感谢您提供的任何帮助。

没有数据丢失。只是,您正在以不适合数据的方式使用绘图原语。 GL_LINE_STRIP 绘制一条连续的长线,在您传入时连接点,并在您设置的颜色之间进行插值。

基本上您要做的是,从最后一个蓝色容器到下一个红色容器高度画一条线 blue-to-red,然后从红色容器到绿色容器,再从那里到蓝色容器。然后你跳到下一个红色垃圾桶等等。所以本质上你是在用红色和蓝色之间的连接线绘制小 "spikes"。当然,如果要填充的宽度像素多于垃圾箱,就会有间隙。

我建议你拿一张(绘图)纸,自己动手执行绘图步骤,以了解这种结果是如何发生的。

实话实说:无论如何,这不是绘制直方图的最有效方法。一种更好的方法是将直方图数据加载到 1D 纹理中,绘制一个大四边形(或者更好的视口填充三角形,使用剪刀测试将视口剪切为矩形)并为每个片段(大致是一个像素,一些额外的东西)在片段着色器中使用X坐标从纹理中查找bin并从纹理中减去Y坐标并将结果传递给stepsmoothstep GLSL 函数确定像素的颜色。这对新手来说可能听起来很奇怪,但是绘制一个三角形并在片段着色器中完成其余部分 比提交一大片多边形更有效。它还提供了更好的质量!

更新 – 示例着色器

一个实际的应用程序可能会用这样的着色器实现(Shadertoy 语义):

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // read the histogram data from a sampler
    // on shadertoy there are no 1D textures, so we use a
    // single row of a 2D texture instead, and sweep up/down
    // over time to get a "dynamic" histogram.
    vec3 h = texture(iChannel0, vec2(uv.x, sin(0.01*iTime))).rgb;

    // discard fragments which are "outside" the histogram
    // also use this value later for the alpha channel
    float a = smoothstep( 0.000, 0.001, length(h));
    if( 0. == a ){ discard; }

    // Color the fragment. The smoothstep gives some antialiasing.
    // For perfect pixel coverage based antialiasing we'd have to
    // determine the slope of the histogram, to construct a tangent
    // and determine the distance to it, i.e. create a
    // Signed Distance Field
    fragColor = vec4(
        smoothstep(-0.001, 0.001, h.r - uv.y),
        smoothstep(-0.001, 0.001, h.g - uv.y),
        smoothstep(-0.001, 0.001, h.b - uv.y),
        a );

    // Instead of using the smoothstep and/or the SDF using a
    // multisampled buffer and performing a simple `y >= h` test
    // would yield probably a nicer result.
}

结果看起来像这样

如果将渲染分成 3 个通道,您应该能够使用三角形条带而不是线条带(应该填充间隙)。

glBegin(GL_TRIANGLE_STRIP);
glColor4ub(255, 0, 0, 255);
for (int n = 0; n < m_histogram_X; n++)
{
    glVertex2i(n, m_Hist_Channel_R[n]);
    glVertex2i(n, GRAPH_HEIGHT);
}
glEnd()

glBegin(GL_TRIANGLE_STRIP);
glColor4ub(0, 255, 0, 255);
for (int n = 0; n < m_histogram_X; n++)
{
    glVertex2i(n, m_Hist_Channel_G[n]);
    glVertex2i(n, GRAPH_HEIGHT);
}
glEnd()

glBegin(GL_TRIANGLE_STRIP);
glColor4ub(0, 0, 255, 255);
for (int n = 0; n < m_histogram_X; n++)
{
    glVertex2i(n, m_Hist_Channel_B[n]);
    glVertex2i(n, GRAPH_HEIGHT);
}
glEnd()