Add noise to blur to remove color banding.

This commit is contained in:
John Preston 2021-06-08 13:14:48 +04:00
parent 3d76e6de55
commit dd79b3c0d5
3 changed files with 234 additions and 26 deletions

View File

@ -24,8 +24,12 @@ using namespace Ui::GL;
constexpr auto kScaleForBlurTextureIndex = 3;
constexpr auto kFirstBlurPassTextureIndex = 4;
constexpr auto kBlurTextureSizeFactor = 1.7;
constexpr auto kBlurOpacity = 0.7;
constexpr auto kNoiseTextureSize = 256;
// The more the scale - more blurred the image.
constexpr auto kBlurTextureSizeFactor = 4.;
constexpr auto kBlurOpacity = 0.65;
constexpr auto kDitherNoiseAmount = 0.002;
constexpr auto kMinCameraVisiblePart = 0.75;
constexpr auto kQuads = 7;
@ -66,15 +70,128 @@ const int diameter = 2 * radius + 1;
};
}
[[nodiscard]] ShaderPart FragmentGenerateNoise() {
const auto size = QString::number(kNoiseTextureSize);
return {
.header = R"(
const float permTexUnit = 1.0 / )" + size + R"(.0;
const float permTexUnitHalf = 0.5 / )" + size + R"(.0;
const float grainsize = 1.3;
const float noiseCoordRotation = 1.425;
const vec2 dimensions = vec2()" + size + ", " + size + R"();
vec4 rnm(vec2 tc) {
float noise = sin(dot(tc, vec2(12.9898, 78.233))) * 43758.5453;
return vec4(
fract(noise),
fract(noise * 1.2154),
fract(noise * 1.3453),
fract(noise * 1.3647)
) * 2.0 - 1.0;
}
float fade(float t) {
return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
}
float pnoise3D(vec3 p) {
vec3 pi = permTexUnit * floor(p) + permTexUnitHalf;
vec3 pf = fract(p);
float perm = rnm(pi.xy).a;
float n000 = dot(rnm(vec2(perm, pi.z)).rgb * 4.0 - 1.0, pf);
float n001 = dot(
rnm(vec2(perm, pi.z + permTexUnit)).rgb * 4.0 - 1.0,
pf - vec3(0.0, 0.0, 1.0));
perm = rnm(pi.xy + vec2(0.0, permTexUnit)).a;
float n010 = dot(
rnm(vec2(perm, pi.z)).rgb * 4.0 - 1.0,
pf - vec3(0.0, 1.0, 0.0));
float n011 = dot(
rnm(vec2(perm, pi.z + permTexUnit)).rgb * 4.0 - 1.0,
pf - vec3(0.0, 1.0, 1.0));
perm = rnm(pi.xy + vec2(permTexUnit, 0.0)).a;
float n100 = dot(
rnm(vec2(perm, pi.z)).rgb * 4.0 - 1.0,
pf - vec3(1.0, 0.0, 0.0));
float n101 = dot(
rnm(vec2(perm, pi.z + permTexUnit)).rgb * 4.0 - 1.0,
pf - vec3(1.0, 0.0, 1.0));
perm = rnm(pi.xy + vec2(permTexUnit, permTexUnit)).a;
float n110 = dot(
rnm(vec2(perm, pi.z)).rgb * 4.0 - 1.0,
pf - vec3(1.0, 1.0, 0.0));
float n111 = dot(
rnm(vec2(perm, pi.z + permTexUnit)).rgb * 4.0 - 1.0,
pf - vec3(1.0, 1.0, 1.0));
vec4 n_x = mix(
vec4(n000, n001, n010, n011),
vec4(n100, n101, n110, n111),
fade(pf.x));
vec2 n_xy = mix(n_x.xy, n_x.zw, fade(pf.y));
return mix(n_xy.x, n_xy.y, fade(pf.z));
}
vec2 rotateTexCoords(in lowp vec2 tc, in lowp float angle) {
float cosa = cos(angle);
float sina = sin(angle);
return vec2(
((tc.x * 2.0 - 1.0) * cosa - (tc.y * 2.0 - 1.0) * sina) * 0.5 + 0.5,
((tc.y * 2.0 - 1.0) * cosa + (tc.x * 2.0 - 1.0) * sina) * 0.5 + 0.5);
}
)",
.body = R"(
vec2 rotatedCoords = rotateTexCoords(
gl_FragCoord.xy / dimensions.xy,
noiseCoordRotation);
float intensity = pnoise3D(vec3(
rotatedCoords.x * dimensions.x / grainsize,
rotatedCoords.y * dimensions.y / grainsize,
0.0));
// Looks like intensity is almost always in [-2, 2] range.
float clamped = clamp((intensity + 2.) * 0.25, 0., 1.);
result = vec4(clamped, 0., 0., 1.);
)",
};
}
[[nodiscard]] ShaderPart FragmentDitherNoise() {
const auto size = QString::number(kNoiseTextureSize);
return {
.header = R"(
uniform sampler2D n_texture;
)",
.body = R"(
vec2 noiseTextureCoord = gl_FragCoord.xy / )" + size + R"(.;
float noiseClamped = texture2D(n_texture, noiseTextureCoord).r;
float noiseIntensity = (noiseClamped * 4.) - 2.;
vec3 lumcoeff = vec3(0.299, 0.587, 0.114);
float luminance = dot(result.rgb, lumcoeff);
float lum = smoothstep(0.2, 0.0, luminance) + luminance;
vec3 noiseColor = mix(vec3(noiseIntensity), vec3(0.0), pow(lum, 4.0));
result.rgb = result.rgb + noiseColor * noiseGrain;
)",
};
}
// Depends on FragmentSampleTexture().
[[nodiscard]] ShaderPart FragmentFrameColor() {
const auto round = FragmentRoundCorners();
const auto blur = FragmentBlurTexture(true, 'b');
const auto noise = FragmentDitherNoise();
return {
.header = R"(
uniform vec4 frameBg;
uniform vec3 shadow; // fullHeight, shown, maxOpacity
uniform float paused; // 0. <-> 1.
)" + blur.header + round.header + noise.header + R"(
const float backgroundOpacity = )" + QString::number(kBlurOpacity) + R"(;
const float noiseGrain = )" + QString::number(kDitherNoiseAmount) + R"(;
float insideTexture() {
vec2 textureHalf = vec2(0.5, 0.5);
vec2 fromTextureCenter = abs(v_texcoord - textureHalf);
@ -82,10 +199,12 @@ float insideTexture() {
float outsideCheck = dot(fromTextureEdge, fromTextureEdge);
return step(outsideCheck, 0);
}
)" + blur.header + R"(
vec4 background() {
vec4 result;
)" + blur.body + R"(
)" + blur.body + noise.body + R"(
return result;
}
)",
@ -99,7 +218,7 @@ vec4 background() {
float shadowValue = max(1. - (shadowCoord / shadow.x), 0.);
float shadowShown = max(shadowValue * shadow.y, paused) * shadow.z;
result = vec4(result.rgb * (1. - shadowShown), result.a);
)",
)" + round.body,
};
}
@ -119,10 +238,10 @@ vec4 background() {
[[nodiscard]] QSize CountBlurredSize(
QSize unscaled,
QSize viewport,
QSize outer,
float factor) {
factor *= kBlurTextureSizeFactor; // The more the scale - more blurred the image.
const auto area = viewport / int(std::round(factor * cScale() / 100));
factor *= kBlurTextureSizeFactor;
const auto area = outer / int(std::round(factor * cScale() / 100));
const auto scaled = unscaled.scaled(area, Qt::KeepAspectRatio);
return (scaled.width() > unscaled.width()
|| scaled.height() > unscaled.height())
@ -208,6 +327,9 @@ void Viewport::RendererGL::init(
_frameBuffer->bind();
_frameBuffer->allocate(kValues * sizeof(GLfloat));
_downscaleProgram.yuv420.emplace();
const auto downscaleVertexSource = VertexShader({
VertexPassTextureCoord(),
});
_downscaleVertexShader = LinkProgram(
&*_downscaleProgram.yuv420,
VertexShader({
@ -216,6 +338,9 @@ void Viewport::RendererGL::init(
FragmentShader({
FragmentSampleYUV420Texture(),
})).vertex;
if (!_downscaleProgram.yuv420->isLinked()) {
//...
}
_blurProgram.emplace();
LinkProgram(
&*_blurProgram,
@ -234,7 +359,6 @@ void Viewport::RendererGL::init(
FragmentShader({
FragmentSampleYUV420Texture(),
FragmentFrameColor(),
FragmentRoundCorners(),
})).vertex;
_imageProgram.emplace();
@ -248,6 +372,8 @@ void Viewport::RendererGL::init(
FragmentSampleARGB32Texture(),
}));
validateNoiseTexture(f, 0);
_background.init(f);
}
@ -287,6 +413,8 @@ void Viewport::RendererGL::deinit(
_blurProgram = std::nullopt;
_frameProgram.argb32 = std::nullopt;
_frameProgram.yuv420 = std::nullopt;
_noiseTexture.destroy(f);
_noiseFramebuffer.destroy(f);
for (auto &data : _tileData) {
data.textures.destroy(f);
}
@ -314,9 +442,10 @@ void Viewport::RendererGL::paint(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f) {
_factor = widget->devicePixelRatio();
const auto defaultFramebufferObject = widget->defaultFramebufferObject();
validateDatas();
fillBackground(f);
const auto defaultFramebufferObject = widget->defaultFramebufferObject();
auto index = 0;
for (const auto &tile : _owner->_tiles) {
if (!tile->shown()) {
@ -584,7 +713,10 @@ void Viewport::RendererGL::paintTile(
_frameBuffer->bind();
_frameBuffer->write(0, coords, sizeof(coords));
const auto blurSize = CountBlurredSize(unscaled, _viewport, _factor);
const auto blurSize = CountBlurredSize(
unscaled,
geometry.size(),
_factor);
prepareObjects(f, tileData, blurSize);
f.glViewport(0, 0, blurSize.width(), blurSize.height());
@ -625,10 +757,11 @@ void Viewport::RendererGL::paintTile(
program->setUniformValue("paused", GLfloat(paused));
f.glActiveTexture(_rgbaFrame ? GL_TEXTURE1 : GL_TEXTURE3);
tileData.textures.bind(
f,
tileData.textureIndex * 5 + kFirstBlurPassTextureIndex);
tileData.textures.bind(f, kFirstBlurPassTextureIndex);
program->setUniformValue("b_texture", GLint(_rgbaFrame ? 1 : 3));
f.glActiveTexture(_rgbaFrame ? GL_TEXTURE2 : GL_TEXTURE5);
_noiseTexture.bind(f, 0);
program->setUniformValue("n_texture", GLint(_rgbaFrame ? 2 : 5));
program->setUniformValue(
"texelOffset",
GLfloat(1.f / blurSize.height()));
@ -692,7 +825,15 @@ void Viewport::RendererGL::prepareObjects(
QOpenGLFunctions &f,
TileData &tileData,
QSize blurSize) {
tileData.textures.ensureCreated(f);
if (!tileData.textures.created()) {
tileData.textures.ensureCreated(f); // All are GL_LINEAR, except..
tileData.textures.bind(f, kScaleForBlurTextureIndex);
// kScaleForBlurTextureIndex is attached to framebuffer 0,
// and is used to draw to framebuffer 1 of the same size.
f.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
f.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
tileData.framebuffers.ensureCreated(f);
if (tileData.textureBlurSize == blurSize) {
@ -701,8 +842,6 @@ void Viewport::RendererGL::prepareObjects(
tileData.textureBlurSize = blurSize;
const auto create = [&](int framebufferIndex, int index) {
index += tileData.textureIndex * 5;
tileData.textures.bind(f, index);
f.glTexImage2D(
GL_TEXTURE_2D,
@ -762,7 +901,7 @@ void Viewport::RendererGL::bindFrame(
ensureARGB32Program();
f.glUseProgram(program.argb32->programId());
f.glActiveTexture(GL_TEXTURE0);
tileData.textures.bind(f, tileData.textureIndex * 5 + 0);
tileData.textures.bind(f, 0);
if (upload) {
const auto &image = _userpicFrame
? tileData.userpicFrame
@ -785,7 +924,7 @@ void Viewport::RendererGL::bindFrame(
const auto yuv = data.yuv420;
f.glUseProgram(program.yuv420->programId());
f.glActiveTexture(GL_TEXTURE0);
tileData.textures.bind(f, tileData.textureIndex * 5 + 0);
tileData.textures.bind(f, 0);
if (upload) {
f.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
uploadTexture(
@ -800,7 +939,7 @@ void Viewport::RendererGL::bindFrame(
tileData.rgbaSize = QSize();
}
f.glActiveTexture(GL_TEXTURE1);
tileData.textures.bind(f, tileData.textureIndex * 5 + 1);
tileData.textures.bind(f, 1);
if (upload) {
uploadTexture(
f,
@ -812,7 +951,7 @@ void Viewport::RendererGL::bindFrame(
yuv->u.data);
}
f.glActiveTexture(GL_TEXTURE2);
tileData.textures.bind(f, tileData.textureIndex * 5 + 2);
tileData.textures.bind(f, 2);
if (upload) {
uploadTexture(
f,
@ -886,9 +1025,7 @@ void Viewport::RendererGL::drawFirstBlurPass(
f.glUseProgram(_blurProgram->programId());
f.glActiveTexture(GL_TEXTURE0);
tileData.textures.bind(
f,
tileData.textureIndex * 5 + kScaleForBlurTextureIndex);
tileData.textures.bind(f, kScaleForBlurTextureIndex);
_blurProgram->setUniformValue("b_texture", GLint(0));
_blurProgram->setUniformValue(
@ -1145,6 +1282,73 @@ void Viewport::RendererGL::validateDatas() {
_names.setImage(std::move(paintToImage));
}
void Viewport::RendererGL::validateNoiseTexture(
QOpenGLFunctions &f,
GLuint defaultFramebufferObject) {
if (_noiseTexture.created()) {
return;
}
_noiseTexture.ensureCreated(f, GL_NEAREST, GL_REPEAT);
_noiseTexture.bind(f, 0);
f.glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RED,
kNoiseTextureSize,
kNoiseTextureSize,
0,
GL_RED,
GL_UNSIGNED_BYTE,
nullptr);
_noiseFramebuffer.ensureCreated(f);
_noiseFramebuffer.bind(f, 0);
f.glFramebufferTexture2D(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
_noiseTexture.id(0),
0);
f.glViewport(0, 0, kNoiseTextureSize, kNoiseTextureSize);
const GLfloat coords[] = {
-1, -1,
-1, 1,
1, 1,
1, -1,
};
auto buffer = QOpenGLBuffer();
buffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
buffer.create();
buffer.bind();
buffer.allocate(coords, sizeof(coords));
auto program = QOpenGLShaderProgram();
LinkProgram(
&program,
VertexShader({}),
FragmentShader({ FragmentGenerateNoise() }));
f.glUseProgram(program.programId());
GLint position = program.attributeLocation("position");
f.glVertexAttribPointer(
position,
2,
GL_FLOAT,
GL_FALSE,
2 * sizeof(GLfloat),
nullptr);
f.glEnableVertexAttribArray(position);
f.glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
f.glDisableVertexAttribArray(position);
f.glUseProgram(0);
}
void Viewport::RendererGL::validateOutlineAnimation(
not_null<VideoTile*> tile,
TileData &data) {

View File

@ -57,7 +57,6 @@ private:
QImage userpicFrame;
QRect nameRect;
int nameVersion = 0;
mutable int textureIndex = 0;
mutable int trackIndex = -1;
mutable QSize rgbaSize;
mutable QSize textureSize;
@ -102,6 +101,9 @@ private:
TileData &tileData,
QSize blurSize);
void validateDatas();
void validateNoiseTexture(
QOpenGLFunctions &f,
GLuint defaultFramebufferObject);
void validateOutlineAnimation(
not_null<VideoTile*> tile,
TileData &data);
@ -142,6 +144,8 @@ private:
std::optional<QOpenGLShaderProgram> _blurProgram;
Program _frameProgram;
std::optional<QOpenGLShaderProgram> _imageProgram;
Ui::GL::Textures<1> _noiseTexture;
Ui::GL::Framebuffers<1> _noiseFramebuffer;
QOpenGLShader *_downscaleVertexShader = nullptr;
QOpenGLShader *_frameVertexShader = nullptr;

@ -1 +1 @@
Subproject commit 35236988b7bc489b7683019db89636d4030db1ce
Subproject commit 4ae6d319634be7aa3530ad3e934a187f630bd6fd