最近我一直在尝试清理我的 openGl 渲染。我已经拥有这些文物一段时间了,但从来没有真正在意过它。这是一个屏幕截图:
经过一番研究,我无法弄清楚它有什么问题。我在 OSX 上使用 OpenGl,但我已经在其他系统上尝试过,并且出现了相同的工件。
最近我一直在尝试清理我的 openGl 渲染。我已经拥有这些文物一段时间了,但从来没有真正在意过它。这是一个屏幕截图:
经过一番研究,我无法弄清楚它有什么问题。我在 OSX 上使用 OpenGl,但我已经在其他系统上尝试过,并且出现了相同的工件。
您所体验的是每通道色彩空间 8 位的有限动态范围。一个简单的灰度梯度,即每通道 8 位帧缓冲区上的 R=B=G 只能有 2^8 = 256 个不同的值。如果您在大面积上在相似值(如图片中)之间转换,则结果是低动态范围条带。
克服这个问题的唯一方法是计算具有更多位数的梯度。为了在低动态范围屏幕上显示图像,您可以使用抖动。
正如datenwolf 所解释的,问题在于将子像素值四舍五入到 8 位范围,并且GL_DITHER
在大多数 OpenGL 实现中都执行无操作。为了缓解这种情况,您可以将抖动作为后处理步骤进行(另一种选择是直接在每个相关图元的片段着色器中进行)。但请注意,要实现这一点,对 OpenGL 实现有一些要求:
支持高精度纹理内部格式:您必须能够获得额外精度的结果颜色。精度越高,可以平滑渲染的颜色变化就越精细。选项包括:
浮点纹理(需要GL_ARB_texture_float
或 OpenGL 3.0+)类似RGBA32F
,RGBA16F
或R11F_G11F_B10F
比 8 位整数格式更宽,例如RGB10_A2
或RGBA16
FBO ( GL_ARB_framebuffer_object
):你确实想做后处理。
NPOT 纹理:大多数屏幕具有非二维的幂;这对于窗口模式更为重要。
GLSL:您确实想使用着色器。另一种选择是GL_ARB_fragment_program
。
正确支持高精度帧缓冲配置。如果您选择像 一样的整体纹理格式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;
}