For those interested, I've solved my banding issue by adding some random dithering. The picture shows the extreme case of black to white. In my game I'm using much more subtler gradients; however the banding was still visible, but now looks good.
The method was trivial:
1. Make a 2048x2048 sf::Image and fill with random noise
2. In the gradient shader, use the random texture to perturb the y value of the lookup coord
setup ..
sf::Image randomImage;
randomImage.Create(2048,2048);
for(int x=0;x<2048;x++){
for(int y=0;y<2048;y++){
randomImage.SetPixel(x,y,sf::Color(random(255),random(255),random(255)));
}
}
mRandomnessTexture = make_shared<sf::Texture>();
mRandomnessTexture->LoadFromImage(randomImage);
mRandomnessTexture->SetRepeated(false); // clamp to border pixels
// load mSkyGradientShader
rendering ..
if (mAllowShaders){
// Shader version
mSkyGradientShader->SetParameter("windowSize",TARGET_WIDTH,TARGET_HEIGHT);
mSkyGradientShader->SetParameter("horizonColour",horizonColour);
mSkyGradientShader->SetParameter("zenithColour",zenithColour);
mSkyGradientShader->SetParameter("randomTexture",*mRandomnessTexture);
const sf::RectangleShape bigRect(sf::Vector2f(TARGET_WIDTH,TARGET_HEIGHT));
target->Draw(bigRect,sf::RenderStates(mSkyGradientShader.get()));
}
shader ..
#version 120
uniform vec2 windowSize;
uniform vec4 horizonColour; // nB: colours must be vec4's for sfml's api
uniform vec4 zenithColour;
uniform sampler2D randomTexture;
void main(void)
{
const float MAX_PIXEL_MIX = 4; // how many pixels away can we mix
vec2 normalisedFragCoord = gl_FragCoord.xy/windowSize;
float inverseHeight = 1.0/windowSize.y;
// perturb the coordinate in y direction by a random amount
float randomValue = texture2D(randomTexture, normalisedFragCoord).r;
float perturbedFragCoordY = normalisedFragCoord.y + randomValue*inverseHeight*MAX_PIXEL_MIX;
gl_FragColor = mix(horizonColour,zenithColour,perturbedFragCoordY);
}
Also, I'd like to say that you've seemed to make some nice choices in the graphics API laurent, it's really growing on me.