blur: add noise in perceptual space

The previous implementation added noise in linear space, which resulted in
the effect becoming more pronounced on black backgrounds. This patch
changes the process to be applied in perceptual space, by making the noise
addition pass a separate draw call and disabling GL_FRAMEBUFFER_SRGB during
that.

After this change, noise will look much more suppressed and almost never
grainy. This change also changes the range of the noise from
[-strength..strength) to [0..strength), as blending can only be either
additive or subtractive. As a result, users might need to ramp up their
noise parameter after this change.

v2: Add more explanation around the draw call.
v3: Fix noise not fading out with the fade out effect.
v4: Restore an accidentally removed comment.
v5: Add CCBUG.
v6: Rebase.
v7: Fix a formatting issue.

CCBUG: 409620
This commit is contained in:
Tatsuyuki Ishi 2021-08-21 15:34:28 +09:00 committed by David Edmundson
parent 0a2c511489
commit c193efc962
3 changed files with 45 additions and 35 deletions

View file

@ -623,7 +623,7 @@ void BlurEffect::generateNoiseTexture()
uint8_t *noiseImageLine = (uint8_t *) noiseImage.scanLine(y);
for (int x = 0; x < noiseImage.width(); x++) {
noiseImageLine[x] = qrand() % m_noiseStrength + (128 - m_noiseStrength / 2);
noiseImageLine[x] = qrand() % m_noiseStrength;
}
}
@ -715,34 +715,58 @@ void BlurEffect::doBlur(const QRegion& shape, const QRect& screen, const float o
glDisable(GL_BLEND);
}
if (m_noiseStrength > 0) {
// Apply an additive noise onto the blurred image.
// The noise is useful to mask banding artifacts, which often happens due to the smooth color transitions in the
// blurred image.
// The noise is applied in perceptual space (i.e. after glDisable(GL_FRAMEBUFFER_SRGB)). This practice is also
// seen in other application of noise synthesis (films, image codecs), and makes the noise less visible overall
// (reduces graininess).
glEnable(GL_BLEND);
if (opacity < 1.0) {
// We need to modulate the opacity of the noise as well; otherwise a thin layer would appear when applying
// effects like fade out.
// glBlendColor should have been set above.
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE);
} else {
// Add the shader's output directly to the pixels in framebuffer.
glBlendFunc(GL_ONE, GL_ONE);
}
applyNoise(vbo, blurRectCount * (m_downSampleIterations + 1), shape.rectCount() * 6, screenProjection, windowRect.topLeft());
glDisable(GL_BLEND);
}
vbo->unbindArrays();
}
void BlurEffect::upscaleRenderToScreen(GLVertexBuffer *vbo, int vboStart, int blurRectCount, QMatrix4x4 screenProjection, QPoint windowPosition)
{
glActiveTexture(GL_TEXTURE0);
m_renderTextures[1].bind();
if (m_noiseStrength > 0) {
m_shader->bind(BlurShader::NoiseSampleType);
m_shader->setTargetTextureSize(m_renderTextures[0].size() * GLRenderTarget::virtualScreenScale());
m_shader->setNoiseTextureSize(m_noiseTexture->size() * GLRenderTarget::virtualScreenScale());
m_shader->setTexturePosition(windowPosition * GLRenderTarget::virtualScreenScale());
glActiveTexture(GL_TEXTURE1);
m_noiseTexture->bind();
} else {
m_shader->bind(BlurShader::UpSampleType);
m_shader->setTargetTextureSize(m_renderTextures[0].size() * GLRenderTarget::virtualScreenScale());
}
m_shader->bind(BlurShader::UpSampleType);
m_shader->setTargetTextureSize(m_renderTextures[0].size() * GLRenderTarget::virtualScreenScale());
m_shader->setOffset(m_offset);
m_shader->setModelViewProjectionMatrix(screenProjection);
//Render to the screen
vbo->draw(GL_TRIANGLES, vboStart, blurRectCount);
m_shader->unbind();
}
glActiveTexture(GL_TEXTURE0);
void BlurEffect::applyNoise(GLVertexBuffer *vbo, int vboStart, int blurRectCount, QMatrix4x4 screenProjection, QPoint windowPosition)
{
m_shader->bind(BlurShader::NoiseSampleType);
m_shader->setTargetTextureSize(m_renderTextures[0].size() * GLRenderTarget::virtualScreenScale());
m_shader->setNoiseTextureSize(m_noiseTexture->size() * GLRenderTarget::virtualScreenScale());
m_shader->setTexturePosition(windowPosition * GLRenderTarget::virtualScreenScale());
m_noiseTexture->bind();
m_shader->setOffset(m_offset);
m_shader->setModelViewProjectionMatrix(screenProjection);
vbo->draw(GL_TRIANGLES, vboStart, blurRectCount);
m_shader->unbind();
}

View file

@ -76,6 +76,7 @@ private:
void generateNoiseTexture();
void upscaleRenderToScreen(GLVertexBuffer *vbo, int vboStart, int blurRectCount, QMatrix4x4 screenProjection, QPoint windowPosition);
void applyNoise(GLVertexBuffer *vbo, int vboStart, int blurRectCount, QMatrix4x4 screenProjection, QPoint windowPosition);
void downSampleTexture(GLVertexBuffer *vbo, int blurRectCount);
void upSampleTexture(GLVertexBuffer *vbo, int blurRectCount);
void copyScreenSampleTexture(GLVertexBuffer *vbo, int blurRectCount, QRegion blurShape, QMatrix4x4 screenProjection);

View file

@ -133,31 +133,19 @@ BlurShader::BlurShader(QObject *parent)
streamFragCopy.flush();
// Fragment shader - Noise texture
// Fragment shader - Noise tiling
QTextStream streamFragNoise(&fragmentNoiseSource);
streamFragNoise << glHeaderString << glUniformString;
streamFragNoise << "uniform sampler2D noiseTexUnit;\n";
streamFragNoise << "uniform vec2 noiseTextureSize;\n";
streamFragNoise << "uniform vec2 texStartPos;\n";
// Upsampling + Noise
streamFragNoise << "void main(void)\n";
streamFragNoise << "{\n";
streamFragNoise << " vec2 uv = vec2(gl_FragCoord.xy / renderTextureSize);\n";
streamFragNoise << " vec2 uvNoise = vec2((texStartPos.xy + gl_FragCoord.xy) / noiseTextureSize);\n";
streamFragNoise << " \n";
streamFragNoise << " vec4 sum = " << texture2D << "(texUnit, uv + vec2(-halfpixel.x * 2.0, 0.0) * offset);\n";
streamFragNoise << " sum += " << texture2D << "(texUnit, uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0;\n";
streamFragNoise << " sum += " << texture2D << "(texUnit, uv + vec2(0.0, halfpixel.y * 2.0) * offset);\n";
streamFragNoise << " sum += " << texture2D << "(texUnit, uv + vec2(halfpixel.x, halfpixel.y) * offset) * 2.0;\n";
streamFragNoise << " sum += " << texture2D << "(texUnit, uv + vec2(halfpixel.x * 2.0, 0.0) * offset);\n";
streamFragNoise << " sum += " << texture2D << "(texUnit, uv + vec2(halfpixel.x, -halfpixel.y) * offset) * 2.0;\n";
streamFragNoise << " sum += " << texture2D << "(texUnit, uv + vec2(0.0, -halfpixel.y * 2.0) * offset);\n";
streamFragNoise << " sum += " << texture2D << "(texUnit, uv + vec2(-halfpixel.x, -halfpixel.y) * offset) * 2.0;\n";
streamFragNoise << " \n";
streamFragNoise << " " << fragColor << " = sum / 12.0 - (vec4(0.5, 0.5, 0.5, 0) - vec4(" << texture2D << "(noiseTexUnit, uvNoise).rrr, 0));\n";
streamFragNoise << " " << fragColor << " = vec4(" << texture2D << "(texUnit, uvNoise).rrr, 0);\n";
streamFragNoise << "}\n";
streamFragNoise.flush();
@ -168,9 +156,9 @@ BlurShader::BlurShader(QObject *parent)
m_shaderNoisesample.reset(ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentNoiseSource));
m_valid = m_shaderDownsample->isValid() &&
m_shaderUpsample->isValid() &&
m_shaderCopysample->isValid() &&
m_shaderNoisesample->isValid();
m_shaderUpsample->isValid() &&
m_shaderCopysample->isValid() &&
m_shaderNoisesample->isValid();
if (m_valid) {
m_mvpMatrixLocationDownsample = m_shaderDownsample->uniformLocation("modelViewProjectionMatrix");
@ -227,9 +215,6 @@ BlurShader::BlurShader(QObject *parent)
m_shaderNoisesample->setUniform(m_texStartPosLocationNoisesample, QVector2D(1.0, 1.0));
m_shaderNoisesample->setUniform(m_halfpixelLocationNoisesample, QVector2D(1.0, 1.0));
glUniform1i(m_shaderNoisesample->uniformLocation("texUnit"), 0);
glUniform1i(m_shaderNoisesample->uniformLocation("noiseTexUnit"), 1);
ShaderManager::instance()->popShader();
}
}