OpenGL渐变“条纹”伪影问题

9
最近我一直在努力清理我的openGl渲染。我已经有一段时间看到这些伪影了,但从未真正关注过它。下面是一个截图:
enter image description here
enter image description here
经过一番研究,我仍然无法找出问题所在。我在OSX上使用OpenGl,但我已经在其他系统上试过,结果都出现了同样的伪影。

仅凭这张图片,我怀疑你不会得到太多帮助... - dtech
@mbeckish,OSX上没有使用该主题解决方案的配置。 - BlueSpud
1
我曾经遇到类似的问题。通过使用浮点渲染目标进行修复。 - Michael IV
2个回答

10
您所体验到的是每通道8位色域有限的动态范围。 在每通道8位的帧缓冲区上,简单的灰度渐变,即R = B = G,只能具有256个不同的值。 如果您在大面积过渡相似的值(如您的图片)之间进行转换,则结果是低动态范围条纹状。
克服这种现象的唯一方法是使用更多位数计算梯度。 为了在低动态范围屏幕上显示图像,可以使用抖动技术。

我认为在OpenGL中抖动默认是启用的。 - BlueSpud
3
@BlueSpud:无论是光照还是显式渐变,都不重要。如果每个通道只有8位,你只能分辨256级灰度。OpenGL规范没有指定要使用的抖动方法,可能只产生局部效果。而且,一个无抖动算法(即实现不支持抖动)也完全有效。 - datenwolf
2
@BlueSpud:启用GL_DITHER只是启用正在使用的OpenGL实现内置的抖动算法。这很可能是空抖动(即没有抖动)或局部抖动。OpenGL不提供更改由GL_DITHER开关控制的阶段使用的抖动算法的方法。但是,完全可以将渲染到具有高动态范围颜色附件的FBO中(需要12位来覆盖人类视觉灰度动态范围)。然后在后处理中,您可以使用片段着色器从HDR图像生成LDR输出来实现抖动。 - datenwolf
好的,我理解了抖动算法部分,但是其他部分对我来说毫无意义。我对GLSL还很陌生,不知道如何制作所需的着色器或将其应用到哪里。 - BlueSpud
2
@BlueSpud:我建议您开另一个问题,具体询问“如何使用OpenGL实现后处理抖动?”这将使问题更加专注于特定主题,并使它们更易于搜索。在StackOverflow上已经多次介绍了使用FBOs渲染到纹理的用法。 - datenwolf
显示剩余2条评论

4

正如 datenwolf所解释的那样,问题在于将亚像素值四舍五入为8位范围,以及大多数OpenGL实现中GL_DITHER的无操作实现。为了缓解这个问题,您可以将抖动作为后处理步骤(另一种选择是直接在每个相关基元的片段着色器中进行)。但请注意,这需要OpenGL实现满足一些要求才能实现:

  1. 支持高精度纹理内部格式:您必须能够访问所得颜色的额外精度。额外精度越多,您可以平稳地呈现更细微的颜色变化。选项包括:

    • 浮点纹理(需要GL_ARB_texture_float或OpenGL 3.0+),如RGBA32FRGBA16FR11F_G11F_B10F

    • 比8位整数格式更宽的格式,如RGB10_A2RGBA16

  2. FBO(GL_ARB_framebuffer_object):您确实希望进行后处理。

  3. NPOT纹理:大多数屏幕具有非二次幂尺寸;对于窗口模式来说更加重要。

  4. GLSL:您确实希望使用着色器。另一个选项是GL_ARB_fragment_program

  5. 针对高精度帧缓冲配置的适当支持。如果您选择像RGBA16这样的整数纹理格式,则可能会被静默替换为例如RGBA8(请参阅TexImage2D可能的内部格式规范)。

在这里,使用上述功能(和浮点纹理以确保结果)的演示允许比较不加抖动的灰色立方体和加抖动的灰色立方体。请注意,默认情况下对于执行自己的抖动的显示器会产生不良结果,因为它实际上是6bpp而不是8bpp。对于6bpp显示器,在编译此代码时定义MONITOR_6_BPP
// Dithering shader implementation inspired by (and completely reworked):
// http://www.anisopteragames.com/how-to-fix-color-banding-with-dithering/
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <GL/glew.h>
#include <GL/glut.h>

GLboolean animating=GL_TRUE, dithering=GL_FALSE;

GLuint ditherProgram=0;
GLuint ditherShader=0;
GLuint frameTexture=0,bayerMatrixTexture=0;
GLuint frameFramebuffer=0, depthRenderBuffer=0;

void initDitheringShader()
{
    ditherProgram=glCreateProgram();
    ditherShader=glCreateShader(GL_FRAGMENT_SHADER);
    const char* src=
        "uniform sampler2D frame, bayerMatrix;\n"
        "void main()\n"
        "{\n"
        "    vec4 color=texture2D(frame,gl_TexCoord[0].xy);\n"
        "    float bayer=texture2D(bayerMatrix,gl_FragCoord.xy/8.).r*(255./64.); // scaled to [0..1]\n"
#ifdef MONITOR_6_BPP // use this for 6 bit per subpixel monitors
        "    const float rgbByteMax=63.;\n"
#else
        "    const float rgbByteMax=255.;\n"
#endif
        "    vec4 rgba=rgbByteMax*color;\n"
        "    vec4 head=floor(rgba);\n"
        "    vec4 tail=rgba-head;\n"
        "    color=head+step(bayer,tail);\n"
        "    gl_FragColor=color/rgbByteMax;\n"
        "}\n"

        ;
    const GLint length=strlen(src);
    glShaderSource(ditherShader,1,&src,&length);
    glCompileShader(ditherShader);
    GLint status;
    glGetShaderiv(ditherShader,GL_COMPILE_STATUS,&status);
    if(!status)
    {
        fprintf(stderr,"Failed to compile shader\n");
        exit(2);
    }
    glAttachShader(ditherProgram,ditherShader);
    glLinkProgram(ditherProgram);
    glGetProgramiv(ditherProgram,GL_LINK_STATUS,&status);
    if(!status)
    {
        fprintf(stderr,"Failed to link shading program\n");
        exit(3);
    }
    static const char bayerPattern[] = {
        0,  32,  8, 40,  2, 34, 10, 42,  /* 8x8 Bayer ordered dithering  */
        48, 16, 56, 24, 50, 18, 58, 26,  /* pattern.  Each input pixel   */
        12, 44,  4, 36, 14, 46,  6, 38,  /* is scaled to the 0..63 range */
        60, 28, 52, 20, 62, 30, 54, 22,  /* before looking in this table */
        3,  35, 11, 43,  1, 33,  9, 41,  /* to determine the action.     */
        51, 19, 59, 27, 49, 17, 57, 25,
        15, 47,  7, 39, 13, 45,  5, 37,
        63, 31, 55, 23, 61, 29, 53, 21,};
    glGenTextures(1,&bayerMatrixTexture);
    glBindTexture(GL_TEXTURE_2D,bayerMatrixTexture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 8,8, 0,GL_LUMINANCE, GL_UNSIGNED_BYTE, bayerPattern);
}

void initLightAndMaterial()
{
    const GLfloat ambient[4]={0.5,0.5,0.5,1};
    glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, ambient);
    const GLfloat emission[4]={0.2,0.2,0.2,1};
    glMaterialfv(GL_FRONT, GL_EMISSION, emission);
    const GLfloat diffuseColor[4]={1,1,1,1};
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseColor);
    const GLfloat position[4]={2,0,-2,1};
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_LIGHT0);
    const GLfloat globalAmbient[4]={0,0,0,1};
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT,globalAmbient);
}

void checkRequirements()
{
    if(!GL_ARB_texture_float)
    {
        fputs("Float textures are not supported, this demo relies on them\n",stderr);
        exit(1);
    }
    if(!GL_ARB_framebuffer_object)
    {
        fputs("FBO is not supported, no good way to do postprocessing\n",stderr);
        exit(1);
    }
    /* We need OpenGL 2.0+ for GLSL and NPOT textures.
     * Extension interface fot GL_ARB_shader_objects is too
     * different from core so not trying to use it.
     */
    if(!GLEW_VERSION_2_0)
    {
        fprintf(stderr,"Need OpenGL>=2.0 for GLSL and NPOT textures\n");
        exit(1);
    }
}

GLboolean init()
{
    checkRequirements();
    initLightAndMaterial();
    initDitheringShader();
    return 1;
}

unsigned getTime()
{
    struct timeval tv;
    gettimeofday(&tv,NULL);
    return tv.tv_usec/1000+tv.tv_sec*1000;
}

void renderScene()
{
    glClearColor(0,0,0,1);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    static unsigned oldTime;
    if(!oldTime) oldTime=getTime();
    const unsigned curTime=getTime();
    if(animating)
    {
        static double angle=0;
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        angle += (curTime-oldTime)%13500 * 360 / 13500.;
        glRotatef(angle,1,0,0);
        glRotatef(angle,0,1,0);
    }
    oldTime=curTime;

    typedef struct CubeVertex
    {
        GLfloat x,  y,  z;
        GLfloat nx, ny, nz;
        GLfloat u, v;
    } CubeVertex;
    static const CubeVertex vertices[] =
    {
    //   x  y  z  nx ny nz   u v
       { 1,-1,-1,  0, 0,-1,  0,1},
       { 1, 1,-1,  0, 0,-1,  0,0},
       {-1, 1,-1,  0, 0,-1,  1,0},
       {-1,-1,-1,  0, 0,-1,  1,1},

       {-1,-1,-1, -1, 0, 0,  0,1},
       {-1, 1,-1, -1, 0, 0,  0,0},
       {-1, 1, 1, -1, 0, 0,  1,0},
       {-1,-1, 1, -1, 0, 0,  1,1},

       {-1,-1, 1,  0, 0, 1,  0,1},
       {-1, 1, 1,  0, 0, 1,  0,0},
       { 1, 1, 1,  0, 0, 1,  1,0},
       { 1,-1, 1,  0, 0, 1,  1,1},

       { 1,-1, 1,  1, 0, 0,  0,1},
       { 1, 1, 1,  1, 0, 0,  0,0},
       { 1, 1,-1,  1, 0, 0,  1,0},
       { 1,-1,-1,  1, 0, 0,  1,1},

       { 1,-1,-1,  0,-1, 0,  0,1},
       {-1,-1,-1,  0,-1, 0,  0,0},
       {-1,-1, 1,  0,-1, 0,  1,0},
       { 1,-1, 1,  0,-1, 0,  1,1},

       { 1, 1, 1,  0, 1, 0,  0,1},
       {-1, 1, 1,  0, 1, 0,  0,0},
       {-1, 1,-1,  0, 1, 0,  1,0},
       { 1, 1,-1,  0, 1, 0,  1,1},
    };
    const GLushort indices[]=
    {
         0, 1, 2,   2, 3, 0,
         4, 5, 6,   6, 7, 4,
         8, 9,10,  10,11, 8,
        12,13,14,  14,15,12,
        16,17,18,  18,19,16,
        20,21,22,  22,23,20,
    };

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glFrontFace(GL_CW);
    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHTING);

    glVertexPointer(3, GL_FLOAT, sizeof(CubeVertex), vertices);
    glNormalPointer(GL_FLOAT, sizeof(CubeVertex), (char*)vertices+3*sizeof(GLfloat));
    glDrawElements(GL_TRIANGLES, sizeof indices/sizeof*indices, GL_UNSIGNED_SHORT, indices);

    glDisable(GL_LIGHTING);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
    glFrontFace(GL_CW);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);
}

void blitFBToScreen()
{
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D,frameTexture);
    if(dithering)
    {
        const GLint frameLoc=glGetUniformLocation(ditherProgram,"frame");
        if(frameLoc==-1)
            fprintf(stderr,"Failed to get location of frame uniform\n");
        glUseProgram(ditherProgram);
        glUniform1i(frameLoc,0);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D,bayerMatrixTexture);
        const GLint bayerMatrixLoc=glGetUniformLocation(ditherProgram,"bayerMatrix");
        if(bayerMatrixLoc==-1)
            fprintf(stderr,"Failed to get location of Bayer matrix uniform\n");
        glUniform1i(bayerMatrixLoc,1);
    }
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
     glLoadIdentity();
     glMatrixMode(GL_MODELVIEW);
     glPushMatrix();
      glLoadIdentity();
      glOrtho(0,1,0,1,-1,1);
      glActiveTexture(GL_TEXTURE0);
      glBindTexture(GL_TEXTURE_2D,frameTexture);
      glEnable(GL_TEXTURE_2D);
      glBegin(GL_QUADS);
       glTexCoord2f(0,0);
       glVertex2f(0,0);
       glTexCoord2f(0,1);
       glVertex2f(0,1);
       glTexCoord2f(1,1);
       glVertex2f(1,1);
       glTexCoord2f(1,0);
       glVertex2f(1,0);
      glEnd();
      glDisable(GL_TEXTURE_2D);
      glBindTexture(GL_TEXTURE_2D,0);
     glMatrixMode(GL_MODELVIEW);
     glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glUseProgram(0);
}

void display()
{
    static GLboolean inited;
    if(!inited) inited=init();

    glBindFramebuffer(GL_FRAMEBUFFER,frameFramebuffer);
    renderScene();
    glBindFramebuffer(GL_FRAMEBUFFER,0);
    blitFBToScreen();

    glutSwapBuffers();
    glutPostRedisplay();
}

void reshape(int width, int height)
{
    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    const float aspect=(float)width/height;
    gluPerspective(180./4, aspect, 1, 100);
    if(aspect<1)
    {
        const GLfloat fixup[4*4]=
        {
            aspect, 0, 0, 0,
            0, aspect, 0, 0,
            0,    0,   1, 0,
            0,    0,   0, 1,
        };
        glMultMatrixf(fixup);
    }
    gluLookAt(0, 0,-5,
              0, 0, 0,
              0, 1, 0);

    // reinitialize FBO
    if(!frameTexture)
        glGenTextures(1,&frameTexture);
    glBindTexture(GL_TEXTURE_2D,frameTexture);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA32F,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,NULL);
    if(!frameFramebuffer)
        glGenFramebuffers(1,&frameFramebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER,frameFramebuffer);
    if(!depthRenderBuffer)
        glGenRenderbuffers(1,&depthRenderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER,depthRenderBuffer);
    glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH_COMPONENT32,width,height);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_RENDERBUFFER,depthRenderBuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,frameTexture,0);
    GLenum status=glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if(status!=GL_FRAMEBUFFER_COMPLETE)
    {
        fprintf(stderr,"Error: framebuffer is incomplete: status=%#x\n",status);
        exit(10);
    }
    glBindFramebuffer(GL_FRAMEBUFFER,0);
    glBindRenderbuffer(GL_RENDERBUFFER,0);
    glBindTexture(GL_TEXTURE_2D,0);
}

void keyboard(unsigned char key,int x,int y)
{
    char winTitle[1024];
    switch(key)
    {
    case ' ':
        animating=!animating;
        snprintf(winTitle,sizeof winTitle,"Animation %sabled",animating?"en":"dis");
        break;
    case 'd':
        dithering=!dithering;
        snprintf(winTitle,sizeof winTitle,"Dithering %sabled",dithering?"en":"dis");
        break;
    default:
        return;
    }
    glutSetWindowTitle(winTitle);
    glutPostRedisplay();
}

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode (GLUT_DOUBLE|GLUT_RGB);
    glutInitWindowSize (1200, 900);
    glutCreateWindow ("Dithering test");
    if(glewInit()!=GLEW_OK)
    {
        fputs("Failed to init GLEW\n",stderr);
        exit(1);
    }
    glutReshapeFunc(reshape);
    glutDisplayFunc(display);
    glutKeyboardFunc(keyboard);
    fputs("Press <SPACE> to toggle animation, 'd' to toggle dithering\n",stderr);
    glutMainLoop();
    return 0;
}

3
你必须拥有完整精度的颜色结果访问权限。” 这是无意义的。你只需要比8位深度更高的精度即可。你可以渲染成RGB10A2图像,甚至是RGBA16。这两种格式都是无符号标准化格式,但提供了10位和16位的精度。而且它们都比完整的32位浮点数要便宜得多。并且RGBA16比RGBA16F提供更有用的精度。 - Nicol Bolas
@NicolBolas 说得好。我甚至没有考虑到这些格式,对我来说只有8位或float32。必须进行编辑。 - Ruslan
“你需要从视频卡中获得适当的支持来配置帧缓冲。” 不,你只需要遵循OpenGL的要求。 OpenGL(3.0+)有一个特定的格式列表,FBOs必须使用这些格式。 该列表中没有任何3通道格式(除了R11FG11FB10F),但是4通道格式是必需的。 - Nicol Bolas
@NicolBolas 在OpenGL 3.0+中要求这样做,但ARB_framebuffer_object却不需要。使用宽整数内部格式,此后处理技术也适用于OpenGL 2.0硬件。 - Ruslan
有了广泛的整数内部格式,这种后处理技术也可以在OpenGL 2.0硬件上工作。当然,假设2.0硬件确实支持“宽整数内部格式”。强制执行某些内部格式的支持正是3.0+条款对FBO支持施加的要求。如果没有该条款,实现可以通过替换RGBA8来实现RGBA16。 - Nicol Bolas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接