http://www.matrix44.net/cms/notes/opengl-3d-graphics/228-2
OpenGL Shading Language Debugging
If you’re doing GLSL Development you know the misere: Because you can’t debug the Shader directly, it’s sometimes hard to fix subtle bugs in your code.
I faced myself with the same problem and because i always was a fan of “printf”-style debugging i decided to do something more or less similar – you may now ask “how can i output something from my shader to the console”? And the short answer is: you can’t. Instead you can output informations encoded in the pixels you generate.
General & Fragment Shaders
Take a look at the picture below which obviously shows a three colored cube:
But the colors in this example have a meaning – they represent the (interpolated) vertex normals of the geometry. I simply pushed the vec4 which contains the normal information through a encoder and outputted the result as the color for the pixel. In my main application code i implemented a method for reading the pixel information of a given location (the one i click with my mouse cursor at) and decoding it back to their original values.
To be more precise, you have to squeeze the information you want to display in a vec4 with a component range of 0 to 1 -because you have to set each pixel’s color as a 4 component vector which represents Red, Green, Blue and Alpha in a Range of 0 to 1. You also have to take care of negative values. In my encoding method, i decided to squeeze positive values into the range 0 to 0.5 and negative values from 0.5 to 1:
vec4 SqueezeZeroOne( vec4 input )
{
vec4 v;
float base = 0.5 / fDebugSqueezeFactor;
// X
if( input.x < 0 )
v.x = 0.5 + ( base * abs( input.x ));
else
v.x = base * input.x;
// Y
if( input.y < 0 )
v.y = 0.5 + ( base * abs( input.y ));
else
v.y = base * input.y;
// Z
if( input.z < 0 )
v.z = 0.5 + ( base * abs( input.z ));
else
v.z = base * input.z;
// W
if( input.w < 0 )
v.w = 0.5 + ( base * abs( input.w ));
else
v.w = base * input.w;
return v;
}
The variable “fDebugSqueezeFactor” is a uniform which is passed in from the main application and denotes the highest value you want to encode. This is necessary because you loose accuracy when squeezing the values into a smaller range – and we want to keep accuracy as high as possible. Just play around with the values to suite your need – for example when debugging vertex normals it is safe to use a squeeze factor of 2 (because vertex normals should be normalized anyway, so x/y/z should not be bigger than 1).
To decode the information you need a way to retrieve the associated value for each pixel drawn – this can be accomplished using the glReadPixels function:
void DumpPixel( int x, int y )
{
int winWidth, winHeight;
glfwGetWindowSize( &winWidth, &winHeight );
int capturePositionX = x;
int capturePositionY = winHeight - y;
GLfloat *data = new GLfloat[4];
glReadPixels( capturePositionX, capturePositionY, 1, 1, GL_RGBA, GL_FLOAT, data );
GLfloat base = g_DebugSqueezeFactor / 0.5f;
GLfloat vx = data[0], vy = data[1], vz = data[2], vw = data[3];
// X
if( vx > 0.5f )
vx = (( vx * (-1)) + 0.5f ) * base;
else
vx *= base;
// Y
if( vy > 0.5f )
vy = (( vy * (-1)) + 0.5f ) * base;
else
vy *= base;
// Z
if( vz > 0.5f )
vz = (( vz * (-1)) + 0.5f ) * base;
else
vz *= base;
// W
if( vw > 0.5f )
vw = (( vw * (-1)) + 0.5f ) * base;
else
vw *= base;
cout << "DEBUG: Capturing at [" << capturePositionX << "," << capturePositionY << "] with squeeze factor " << g_DebugSqueezeFactor << endl;
cout << " RAW : X = " << data[0] << " Y = " << data[1] << " Z = " << data[2] << " W = " << data[3] << endl;
cout << " INTERPRETED: X = " << vx << " Y = " << vy << " Z = " << vz << " W = " << vw << endl;
delete[] data;
}
This function reads a pixel from the Framebuffer, decodes them and outputs the result to the console:
With this method you can successively debug your shaders – even if it’s not the perfect solution, it’s better than nothing.
Vertex Shaders
If you want to debug values from your vertex shader, you have to pass it on the the fragment shader – but please note that values are interpolated then. For example, if you pass the vertex position from the vertex shader to the fragment shader, it’s value is interpolated across the surface:
The black marked points show vertices and their position. If you pass them to the fragment shader, the positions are interpolated as shown by the yellow marks (the interpolated values change slightly for every pixel in the fragment shader).
Example code
If you want to try a working example, just download the entire project from below – Windows binaries are included: