Add noise to blur to remove color banding.
This commit is contained in:
parent
3d76e6de55
commit
dd79b3c0d5
|
@ -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) {
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue