I’m having a pretty tough problem with the floating point precision in my shader. What I want to do is, I have a framebuffer object which has a GL_UNSIGNED_SHORT texture for the color attachment. I want to do some scene processing and then, in my shader, use the fbo as an accumulator for the data that I’m processing. So, every time my condition (emulated by the “TestMap” shader below) is true, my plan is to return a result of .0001 for that particular texel, which is later aggregated with a lot of other .0001 results into my result buffer.
The problem is, as this test shows, if you change the line at the bottom to DrawStuff(0.5f) it works fine, 0.1f works fine, 0.01f works fine, .005f works fine, but below about .0023 or so it starts generating pure black textures, which tells me that for some reason something somewhere is rounding my floating points that are too small down to 0, probably because of a precision problem. That’s not cool because with an unsigned short buffer I should be getting at least 1/2^16 precision, or about .00015 and since I need to do many thousands of exposures, using a value of .001 won’t work for me.
I did this before with a floating point buffer and just returning 1 instead of .0001, but I’d rather get around the rather recent hardware requirements of floating point buffers. If there were some way (without using a more modern opengl) to access the short values of my buffer directly instead of working with just floats, I would love to do that. Or if there were some other way to do this while keeping the hardware requirements lowish, I’m all ears.
(There may be some messy bits, because I had to tear it out of my project to make it simple enough to post here.)
#include <GL/glew.h>
#include <GL/freeglut.h>
const char* GetVSTestMap()
{
return
"varying vec3 vecSurfaceNormal;"
"void main()"
"{"
" vecSurfaceNormal = normalize(gl_NormalMatrix * gl_Normal);"
" gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;"
" gl_FrontColor = gl_Color;"
"}";
}
const char* GetFSTestMap()
{
return
"uniform float flMaxValue;"
"varying vec3 vecSurfaceNormal;"
"void main()"
"{"
" float flShadow = flMaxValue;"
" if (dot(vecSurfaceNormal, vec3(0.0, 0.0, 1.0)) < 0.0)"
" flShadow = 0.0;"
" gl_FragColor = vec4(flShadow, flShadow, flShadow, flMaxValue);"
"}";
}
const char* GetVSResultMap()
{
return
"varying vec2 vecUV;"
"void main()"
"{"
" gl_Position = ftransform();"
" gl_FrontColor = gl_Color;"
" vecUV = gl_MultiTexCoord0.st;"
"}";
}
const char* GetFSResultMap()
{
return
"uniform sampler2D iResultMap;"
"varying vec2 vecUV;"
"void main()"
"{"
" vec4 vecColor = texture2D(iResultMap, vecUV);"
" if (vecColor.r == 0.0)"
" gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);"
" else"
" gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);"
"}";
}
void DrawTexture(GLuint iTexture, float flScale = 1.0f)
{
glClear(GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_TEXTURE);
glPushMatrix();
glLoadIdentity();
glPushAttrib(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_ENABLE_BIT|GL_TEXTURE_BIT);
glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
glShadeModel(GL_SMOOTH);
glBindTexture(GL_TEXTURE_2D, iTexture);
glColor3f(1.0f, 1.0f, 1.0f);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f);
glVertex2f(-flScale, -flScale);
glTexCoord2f(0.0f, 1.0f);
glVertex2f(-flScale, flScale);
glTexCoord2f(1.0f, 1.0f);
glVertex2f(flScale, flScale);
glTexCoord2f(1.0f, 0.0f);
glVertex2f(flScale, -flScale);
glEnd();
glPopAttrib();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_TEXTURE);
glPopMatrix();
}
GLuint iUVMap;
GLuint iTestProgram;
GLuint iUVFB;
GLuint iSceneList;
GLuint iResultProgram;
void DrawStuff(float flMaxValue)
{
glBindFramebufferEXT(GL_FRAMEBUFFER, iUVFB);
glPushAttrib(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_ENABLE_BIT|GL_TEXTURE_BIT);
glUseProgram(iTestProgram);
GLuint iResultMapMaxShadow = glGetUniformLocation(iTestProgram, "flMaxValue");
glUniform1f(iResultMapMaxShadow, flMaxValue);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(
5, 5, 10,
0, 0, 0,
0, 1, 0);
glDisable(GL_DEPTH_TEST);
glCallList(iSceneList);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glUseProgram(0);
glPopAttrib();
glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, iUVMap);
glDrawBuffer(GL_FRONT);
glReadBuffer(GL_FRONT);
glUseProgram(iResultProgram);
GLuint iResultMapUniform = glGetUniformLocation(iResultProgram, "iResultMap");
glUniform1i(iResultMapUniform, 0);
DrawTexture(iUVMap);
glUseProgram(0);
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_ALPHA | GLUT_MULTISAMPLE);
glutInitWindowPosition(0, 0);
glutInitWindowSize((int)1024, (int)1024);
// The easy way to get a "windowless" context.
glutCreateWindow("Precision test");
glewInit();
glutMainLoopEvent();
// Tuck away our current stack so we can return to it later.
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
// Create a list with the required polys so it draws quicker.
iSceneList = glGenLists(1);
glNewList(iSceneList, GL_COMPILE);
glutSolidTeapot(2.5);
glEndList();
glPushAttrib(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_ENABLE_BIT|GL_TEXTURE_BIT);
// Clear red so that we can pick out later what we want when we're reading pixels.
glClearColor(1, 0, 0, 1);
glDisable(GL_CULL_FACE);
GLsizei iShadowMapSize = 1024;
iUVMap;
glGenTextures(1, &iUVMap);
glBindTexture(GL_TEXTURE_2D, iUVMap);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)512, (GLsizei)512, 0, GL_RGBA, GL_UNSIGNED_SHORT, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
GLuint iUVRB;
glGenRenderbuffersEXT(1, &iUVRB);
glBindRenderbufferEXT( GL_RENDERBUFFER, iUVRB );
glRenderbufferStorageEXT( GL_RENDERBUFFER, GL_DEPTH_COMPONENT, (GLsizei)512, (GLsizei)512 );
glBindRenderbufferEXT( GL_RENDERBUFFER, 0 );
// A frame buffer for holding the UV layout once it is rendered flat with the shadow
iUVFB;
glGenFramebuffersEXT(1, &iUVFB);
glBindFramebufferEXT(GL_FRAMEBUFFER, iUVFB);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, iUVMap, 0);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, iUVRB); // Unused
glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
GLuint iTestVertexShader = glCreateShader(GL_VERTEX_SHADER);
const char* pszShaderSource = GetVSTestMap();
glShaderSource(iTestVertexShader, 1, &pszShaderSource, NULL);
glCompileShader(iTestVertexShader);
#ifdef _DEBUG
int iLogLength = 0;
char szLog[1024];
glGetShaderInfoLog(iTestVertexShader, 1024, &iLogLength, szLog);
#endif
GLuint iTestFragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
pszShaderSource = GetFSTestMap();
glShaderSource(iTestFragmentShader, 1, &pszShaderSource, NULL);
glCompileShader(iTestFragmentShader);
#ifdef _DEBUG
glGetShaderInfoLog(iTestFragmentShader, 1024, &iLogLength, szLog);
#endif
iTestProgram = glCreateProgram();
glAttachShader(iTestProgram, iTestVertexShader);
glAttachShader(iTestProgram, iTestFragmentShader);
glLinkProgram(iTestProgram);
#ifdef _DEBUG
glGetProgramInfoLog(iTestProgram, 1024, &iLogLength, szLog);
#endif
GLuint iResultVertexShader = glCreateShader(GL_VERTEX_SHADER);
pszShaderSource = GetVSResultMap();
glShaderSource(iResultVertexShader, 1, &pszShaderSource, NULL);
glCompileShader(iResultVertexShader);
#ifdef _DEBUG
glGetShaderInfoLog(iResultVertexShader, 1024, &iLogLength, szLog);
#endif
GLuint iResultFragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
pszShaderSource = GetFSResultMap();
glShaderSource(iResultFragmentShader, 1, &pszShaderSource, NULL);
glCompileShader(iResultFragmentShader);
#ifdef _DEBUG
glGetShaderInfoLog(iResultFragmentShader, 1024, &iLogLength, szLog);
#endif
iResultProgram = glCreateProgram();
glAttachShader(iResultProgram, iResultVertexShader);
glAttachShader(iResultProgram, iResultFragmentShader);
glLinkProgram(iResultProgram);
#ifdef _DEBUG
glGetProgramInfoLog(iResultProgram, 1024, &iLogLength, szLog);
#endif
float flSize = 10; // Length of the box's diagonal
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-flSize/2, flSize/2, -flSize/2, flSize/2, 1, flSize*2);
while (true)
{
glutMainLoopEvent();
glViewport(0, 0, 1024, 1024);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, 512, 512);
DrawStuff(0.01f);
glFinish();
glFinish(); // So I can set a breakpoint
}
}