2013-04-15 19 views
8

In letzter Zeit habe ich versucht, meine OpenGl Render zu bereinigen. Ich hatte diese Artefakte jetzt schon eine Weile, habe aber nie wirklich darauf geachtet. Hier ist ein Screenshot: enter image description hereOpenGL Farbverlauf "Banding" Artefakte

enter image description here Ich habe nicht in der Lage gewesen, was ist falsch, um herauszufinden, mit ihm nach einem wenig Forschung. Ich benutze OpenGl auf OSX, aber ich habe es auf anderen Systemen versucht und die gleichen Artefakte auftreten.

+0

Ich bezweifle, dass Sie viel Hilfe auf der Grundlage dieses Bild allein bekommen ... – dtech

+0

möglich Duplikat [OpenGL Gradienten Banding auf Android] (http://stackoverflow.com/questions/8669765/opengl-gradient- banding-on-android) – mbeckish

+0

@mbeckish Es gibt keine Konfiguration unter OSX, die die Lösung dieses Themas verwendet. – BlueSpud

Antwort

6

Was Sie erleben, ist der begrenzte dynamische Bereich von 8 Bit pro Kanal Farbraum. Ein einfacher Graustufengradient, d. H. R = B = G auf einem Framebuffer mit 8 Bit pro Kanal, kann nur 2^8 = 256 verschiedene Werte haben. Wenn Sie zwischen ähnlichen Werten (wie in Ihrem Bild) über einen großen Bereich wechseln, entsteht ein Bandbreitenbereich mit geringer Dynamik.

Der einzige Weg, dies zu überwinden, ist die Berechnung des Gradienten mit einer größeren Anzahl von Bits. Zum Anzeigen des Bildes auf einem Bildschirm mit niedriger Dynamik können Sie Dithering verwenden.

+0

Ich dachte, in OpenGL Dithering war standardmäßig aktiviert – BlueSpud

+0

Auch, Das Farbverlaufsbild kommt von der Beleuchtung, nicht als ein Gefälle gezeichnet. – BlueSpud

+2

@BlueSpud: Es spielt keine Rolle, was den Farbverlauf, die Beleuchtung oder einen expliziten Farbverlauf erzeugt. Wenn Sie nur 8 Bit pro Kanal haben, können Sie nur 256 Graustufen auflösen. Die zu verwendende Dithering-Methode wird nicht von der OpenGL-Spezifikation angegeben und kann nur lokalisierte Effekte erzeugen. Auch ein Null-Dithering-Algorithmus (d. H. Keine Dither-Unterstützung durch die Implementierung) ist vollständig gültig. – datenwolf

2

Wie explained by datenwolf, das Problem ist Rundung von Subpixel-Werten auf 8-Bit-Bereich und No-Op-Implementierung von GL_DITHER auf die meisten OpenGL-Implementierungen. Um dies zu erleichtern, können Sie Dithering als Postprocessing-Schritt durchführen (eine weitere Möglichkeit besteht darin, dies direkt im Fragment-Shader für jedes relevante Primitiv zu tun). Beachten Sie aber, dass es einige Anforderungen an die OpenGL-Implementierung sind dies möglich sein:

  1. Unterstützung hoher Präzision Textur interne Formate: Sie Zugriff auf zusätzliche Präzision der resultierenden Farben haben müssen. Je mehr Extra-Präzision, desto feiner die Variation der Farben, die Sie glatt rendern können. Die Optionen sind:

    • Gleitkommazahlen Texturen (erfordert GL_ARB_texture_float oder OpenGL 3.0+) llike RGBA32F, RGBA16F oder R11F_G11F_B10F

    • breiter als 8-Bit-Integral-Formate wie RGB10_A2 oder RGBA16

  2. FBO (GL_ARB_framebuffer_object): Sie möchten nachbearbeiten.

  3. NPOT-Texturen: Die meisten Bildschirme haben nicht-Power-of-zwei Dimensionen; Es ist noch wichtiger für den Fenstermodus.

  4. GLSL: Sie möchten einen Shader verwenden. Eine andere Option ist GL_ARB_fragment_program10.

  5. Korrekte Unterstützung für hochpräzise Framebufferkonfigurationen. Wenn Sie das integrale Texturformat wie RGBA16 wählen, kann es im Stillen mit z.B. RGBA8 (siehe Spezifikation TexImage2D mögliche interne Formate).

Mit all dies gesagt, hier ist eine Demo, die oben genannten Merkmale (und Floating-Point-Texturen des Ergebnisses sicher sein) unter Verwendung erlaubt ditherless beleuchtet graue Würfel mit Dither zu vergleichen. Beachten Sie, dass es standardmäßig für Monitore, die selbst Dithering durchführen, schlechte Ergebnisse liefert, da sie tatsächlich 6bpp statt 8bpp sind. Für 6bpp definieren Monitore MONITOR_6_BPP beim Kompilieren dieses Codes.

// 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; 
} 
+1

"* Sie müssen Zugriff auf die resultierenden Farben mit voller Genauigkeit haben. *" Unsinn. Sie brauchen einfach * mehr * Präzision als 8-bpp. Sie könnten ein RGB10A2-Bild oder sogar RGBA16 rendern. Beide sind vorzeichenlose normalisierte Formate, bieten aber 10 und 16 Bits Genauigkeit. Und beide sind viel weniger teuer als ein voller 32-Bit-Float. Und RGBA16 bietet mehr nützliche Präzision als RGBA16F. –

+0

@ NicolBolas guter Punkt. Ich habe nicht einmal an diese Formate gedacht, für mich war es entweder 8-Bit oder float32. Muss dies in bearbeiten. – Ruslan

+0

"* Sie benötigen ordnungsgemäße Unterstützung für solche Framebuffer-Konfigurationen von Grafikkarte. *" Nein, Sie müssen nur folgen, was OpenGL benötigt. OpenGL (3.0+) hat eine spezifische Liste von Formaten, die für die Verwendung durch FBOs erforderlich sind *. Keines der 3-Kanal-Formate ist auf dieser Liste (außer für R11FG11FB10F), aber die 4-Kanal-Formate sind. –