C++ 使用glVertex2i()绘制图形在使用GLSCALLED()缩放时显示人工制品

C++ 使用glVertex2i()绘制图形在使用GLSCALLED()缩放时显示人工制品,c++,opengl,opengl-compat,C++,Opengl,Opengl Compat,我正在使用OpenGL绘制RGB图像直方图。由于这是一个8位图像直方图,我的数据集包含从0到255的数据点 如果我在没有使用GLSCALE的情况下绘制直方图,那么图形将按预期绘制,但是,当然,不会填充宽度可变的分配区域,即高度常量。然而,当我使用GLSCALE时,图表显示了奇怪的人工制品 请参见以下图像以查看问题示例: 上图显示了使用256个数据点绘制的直方图,未使用GLSCALE进行缩放 上面的两幅图像显示了使用256个数据点绘制的直方图,并使用GLSCALE进行缩放。这些奇怪的文物显然是丢失

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

如果我在没有使用GLSCALE的情况下绘制直方图,那么图形将按预期绘制,但是,当然,不会填充宽度可变的分配区域,即高度常量。然而,当我使用GLSCALE时,图表显示了奇怪的人工制品

请参见以下图像以查看问题示例:

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

上面的两幅图像显示了使用256个数据点绘制的直方图,并使用GLSCALE进行缩放。这些奇怪的文物显然是丢失的数据?。请注意,第三个柱状图是一个稍微不同的形状,由于不断变化的光线水平

以下是我的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绘制一条长的连续线,在传入点时连接点,并在设置的颜色之间进行插值

基本上你要做的是,从最后一个蓝色箱子到下一个红色箱子高度,从红色箱子到绿色箱子,再从那里到蓝色箱子,画一条从蓝色到红色的线。然后你跳到下一个红色垃圾桶,依此类推。所以本质上你是在用红色和蓝色之间的连接线画小尖峰。当然,如果要填充的像素的宽度大于要填充的像素的宽度,则会有间隙

我建议你拿一张绘图纸,自己动手完成绘图步骤,以了解这个结果是如何发生的

说实话:无论如何,这不是绘制直方图的最有效方法。更好的方法是将直方图数据加载到1D纹理中,绘制一个大的四边形或更好的视口填充三角形,使用剪刀测试将视口剪切为矩形,对于每个片段,大致为一个像素,使用片段着色器中的一些额外内容,使用X坐标从纹理中查找bin,并从纹理中减去Y坐标,然后将结果传递到step或smoothstep 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.
}
结果是这样的


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

基本上你要做的是,从最后一个蓝色箱子到下一个红色箱子高度,从红色箱子到绿色箱子,再从那里到蓝色箱子,画一条从蓝色到红色的线。然后你跳到下一个红色垃圾桶,依此类推。所以本质上你是在用红色和蓝色之间的连接线画小尖峰。当然,如果要填充的像素的宽度大于要填充的像素的宽度,则会有间隙

我建议你拿一张绘图纸,自己动手完成绘图步骤,以了解这个结果是如何发生的

说实话:无论如何,这不是绘制直方图的最有效方法。更好的方法是将直方图数据加载到1D纹理中,绘制一个大的四边形或更好的视口填充三角形,使用剪刀测试将视口剪切为矩形,对于每个片段,大致为一个像素,使用片段着色器中的一些额外内容,使用X坐标从纹理中查找bin,并从纹理中减去Y坐标,然后将结果传递到step或smoothstep 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()

如果将渲染拆分为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()

简单地分别绘制这三种颜色,以避免相邻顶点之间的颜色变化,将大大有助于解决此问题。@datenwolf感谢您的宝贵意见。正如我所说的,我对OpenGL非常陌生,你的建议对于我的新手GL来说听起来比较复杂。我需要在学习OpenGL上投入更多的时间,不幸的是,时间才是关键。@Ben Voigt我尝试了你的建议,它确实让事情变得更好,但是,唉,仍然不完美。我认为,至少现在,我将插入额外的数据点,并在没有GLSCALE的情况下绘制。一旦我有时间,我可以学习更多关于OpenGL的知识,并实现一个类似于datenwolf@Amanda:我已经用一些着色器代码更新了我的答案。您仍然需要一些OpenGL宿主代码来将着色器和直方图加载到纹理中。“但是这应该会给你一个想法。”datenwolf对此表示非常感谢,非常感谢。我将试验你的想法。只需分别绘制三种颜色,以避免相邻顶点之间的颜色变化,将大大有助于解决问题。@datenwolf感谢你的帮助性评论。正如我所说的,我对OpenGL非常陌生,你的建议对于我的新手GL来说听起来比较复杂。我需要在学习OpenGL上投入更多的时间,不幸的是,时间才是关键。@Ben Voigt我尝试了你的建议,它确实让事情变得更好,但是,唉,仍然不完美。我认为,至少现在,我将插入额外的数据点,并在没有GLSCALE的情况下绘制。一旦我有时间,我可以学习更多关于OpenGL的知识,并实现一个类似于datenwolf@Amanda:我已经用一些着色器代码更新了我的答案。您仍然需要一些OpenGL宿主代码来将着色器和直方图加载到纹理中。“但是这应该会给你一个想法。”datenwolf对此表示非常感谢,非常感谢。我将尝试你的想法。如果你是OpenGL新手,我建议你学习现代OpenGL。您所学到的是10年以上不推荐使用的OpenGL。如果你是OpenGL新手,我建议你学习现代OpenGL。您所学到的是10年以上不推荐使用的OpenGL。请参阅,它解决了8位图像直方图和16位图像直方图的问题。谢谢。这就解决了8位图像直方图和16位图像直方图的问题。非常感谢。