diff --git a/resources/shaders/common.glsl b/resources/shaders/common.glsl index 80894b7..58f3dc5 100644 --- a/resources/shaders/common.glsl +++ b/resources/shaders/common.glsl @@ -124,6 +124,17 @@ vec4 alphaOver(vec4 under, vec4 over) { return vec4((over.rgb * over.a + under.rgb * under.a * (1.0 - over.a)) / alpha, alpha); } +// ------------------------------------------------------------------------- color helpers + +// Maps the given value from [0..1] to the given colors. +vec3 tritone(float val, vec3 shadows, vec3 midtones, vec3 highlights) { + if (val < 0.5) { + return mix(shadows, midtones, smoothstep(0, 1, val * 2.0)); + } + + return mix(midtones, highlights, smoothstep(0, 1, val * 2.0 - 1.0)); +} + // ---------------------------------------------------------------------- easing functions // Here are some basic easing function. More can be added if required! diff --git a/resources/shaders/incinerate.frag b/resources/shaders/incinerate.frag index e1b551f..daf190e 100644 --- a/resources/shaders/incinerate.frag +++ b/resources/shaders/incinerate.frag @@ -13,124 +13,93 @@ // The content from common.glsl is automatically prepended to each shader effect. -uniform sampler2D uAshTexture; uniform vec2 uSeed; // uniform vec3 uColor; uniform float uScale; // uniform float uWidth; uniform float uTurbulence; -const float BURN_TIME = 0.5; -const float FIRE_WIDTH = 0.03; -const float ASH_WIDTH = 0.5; -const float ASH_LAYERS = 4; +const float SCORCH_WIDTH = 0.1; +const float SMOKE_WIDTH = 0.6; -vec4 getFireColor(float v) { - const float steps[4] = float[](0.0, 0.33, 0.66, 1.0); - vec4 colors[4] = - vec4[](vec4(1, 0, 0, 0), vec4(1, 0, 0, 0.5), vec4(1, 1, 0, 1), vec4(1, 0, 0, 0)); +vec4 getFireColor(float val) { + return vec4(tritone(val, vec3(0.0), vec3(1.0, 0.8, 0.6), vec3(1.0)), val); +} - if (v < steps[0]) { - return colors[0]; - } +float getMask(vec2 uv) { + float noise = simplex2DFractal(uv * 0.005 + uSeed); + float gradient = rotate(cogl_tex_coord_in[0].st - 0.5, uSeed.x * 2.0 * 3.141).x + 0.5; - for (int i = 0; i < 3; ++i) { - if (v <= steps[i + 1]) { - float blend = smoothstep(0, 1, (v - steps[i]) / (steps[i + 1] - steps[i])); - return mix(colors[i], colors[i + 1], vec4(blend)); - } - } - - return colors[3]; + return mix(gradient, noise, 0.15); } void main() { - // Shell.GLSLEffect uses straight alpha. So we have to convert from premultiplied. - vec4 windowColor = texture2D(uTexture, cogl_tex_coord_in[0].st); - if (windowColor.a > 0) { - windowColor.rgb /= windowColor.a; + + float hideThreshold = mix(-SCORCH_WIDTH, 1.0 + SMOKE_WIDTH, uProgress); + vec2 scorchRange = uForOpening ? vec2(hideThreshold - SCORCH_WIDTH, hideThreshold) + : vec2(hideThreshold, hideThreshold + SCORCH_WIDTH); + vec2 smokeRange = vec2(hideThreshold - SMOKE_WIDTH, hideThreshold); + + vec2 uv = cogl_tex_coord_in[0].st / uScale * uSize; + float mask = getMask(uv); + + vec2 distort = vec2(0.0); + + if (!uForOpening && (smokeRange.x < mask && mask < smokeRange.y)) { + distort.y += pow(1.0 - (mask - smokeRange.x) / SMOKE_WIDTH, 2.0) * 0.1; } - cogl_color_out = windowColor; + if ((uForOpening && mask > scorchRange.x) || (!uForOpening && mask <= scorchRange.y)) { + distort += + 0.5 * vec2(dFdx(mask), dFdy(mask)) * (scorchRange.y - mask) / SCORCH_WIDTH * 5.0; + } - vec2 uv = cogl_tex_coord_in[0].st * uSize / uScale; + vec4 oColor = getInputColor(cogl_tex_coord_in[0].st + distort); - float lfNoise = simplex2D(uv / max(uSize.x, uSize.y) + uSeed); - float hfNoise = simplex2DFractal(uv * 0.01 + uSeed); + float xNoise = simplex2D(uv * 0.05 + uSeed * 10.0 + 0.0) - 0.5; + float yNoise = simplex2D(uv * 0.05 + uSeed * 10.0 + uDuration * uProgress) - 0.5; + float maskDistorted = getMask(uv + vec2(xNoise, yNoise) * 50.0); - float noise = (lfNoise + hfNoise * uTurbulence * 0.5) / (1.0 + uTurbulence * 0.5); + float a = mask > hideThreshold ? 1.0 : 0.0; + float b = maskDistorted > hideThreshold ? 1.0 : 0.0; + float fireEdge = clamp(a - b, 0, 1) * oColor.a; - float burnProgress = - (uForOpening ? 1.0 - uProgress / BURN_TIME : uProgress / BURN_TIME); + if (uForOpening && mask > smokeRange.y) { + oColor = vec4(0.0); + } - vec2 fireRange = vec2(burnProgress - FIRE_WIDTH, burnProgress + FIRE_WIDTH); + if (!uForOpening && mask < smokeRange.y) { + oColor = vec4(0.0); + } - if (uForOpening) { + float smoke = + smoothstep(0, 1, (mask - smokeRange.x) / SMOKE_WIDTH) * getRelativeEdgeMask(0.1); + smoke *= + simplex2DFractal(uv * 0.01 + uSeed - smoke * smoke * vec2(0.0, 0.1 * uDuration)); + float emberNoise = + simplex2DFractal(uv * 0.075 + uSeed - smoke * vec2(0.0, 0.5 * uDuration)); + fireEdge = fireEdge * pow(emberNoise + 0.4, 4.0); - // Hide window. - if (noise < fireRange.x) { - cogl_color_out.a = 0.0; + if (smokeRange.x < mask && mask < smokeRange.y) { + + oColor = alphaOver(oColor, vec4(0.5 * vec3(smoke), smoke)); + float embers = clamp(pow(emberNoise + 0.32, 100.0), 0.0, 2.0) * smoke; + embers += clamp(pow(emberNoise + 0.32, 10.0), 0.0, 2.0) * smoke; + oColor = alphaOver(oColor, getFireColor(embers)); + } + + // Add scorch effect. + if (scorchRange.x < mask && mask < scorchRange.y) { + float scorch = smoothstep(1, 0, (mask - scorchRange.x) / SCORCH_WIDTH); + + if (uForOpening) { + scorch = 1.0 - scorch; } - } else { - - // Draw ash particles if the window is closed. - vec2 ashRange = vec2(burnProgress - ASH_WIDTH, burnProgress); - - float ash = clamp(1.0 - (noise - ashRange.x) / (ashRange.y - ashRange.x), 0, 1); - // vec4 ashColor = vec4(0, 0, 0, 0); - // cogl_color_out = mix(cogl_color_out, ashColor, ash); - - // for (float i = 0; i < ASH_LAYERS; ++i) { - - // float factor = ASH_LAYERS == 1 ? 0 : i / (ASH_LAYERS - 1); - - // Now check wether there is actually something in the current ash layer at - // the coords position. - vec2 ashMap = texture2D(uAshTexture, uv * 0.005).rg; - // float ashGroup = floor(ashMap.g * ASH_LAYERS * 0.999); - - // if (ashGroup == i) { - - // Get the window color. - // vec4 windowColor = texture2D(uTexture, coords + 0.5); - - // // Shell.GLSLEffect uses straight alpha. So we have to convert from - // premultiplied. if (windowColor.a > 0) { - // windowColor.rgb /= windowColor.a; - // } - - // Dissolve and blend the layers. - if (ashMap.x - ash < 0) { - cogl_color_out.a = 0; - } - // } - // } + oColor.rgb = mix(oColor.rgb, mix(oColor.rgb, vec3(0.1, 0.05, 0.02), 0.4), scorch); } - // Add fire. - if (fireRange.x < noise && noise < fireRange.y) { - float fire = 1.0 - (noise - fireRange.x) / (fireRange.y - fireRange.x); - fire *= cogl_color_out.a; - cogl_color_out = alphaOver(cogl_color_out, getFireColor(fire)); - } + oColor = alphaOver(oColor, getFireColor(fireEdge)); - // Add smoke. - vec2 smokeRange = uForOpening ? vec2(fireRange.y, 1.0) : vec2(0.0, fireRange.x); - if (smokeRange.x < noise && noise < smokeRange.y) { - float smoke = (noise - smokeRange.x) / (smokeRange.y - smokeRange.x); - - if (!uForOpening) { - smoke = 1 - smoke; - } - - vec2 smokeShift = vec2(0, smoke * noise * uTime); - smoke *= simplex2DFractal(uv * 0.01 + uSeed + smokeShift); - smoke *= getRelativeEdgeMask(0.3); - - float fadeOutProgress = clamp((uProgress - BURN_TIME) / (1.0 - BURN_TIME), 0, 1); - smoke *= 1 - fadeOutProgress; - - cogl_color_out = alphaOver(cogl_color_out, vec4(vec3(0.2), smoke)); - } + setOutputColor(oColor); } \ No newline at end of file diff --git a/src/Incinerate.js b/src/Incinerate.js index 12e2899..7d7b5c7 100644 --- a/src/Incinerate.js +++ b/src/Incinerate.js @@ -39,18 +39,9 @@ var Incinerate = class { this.shaderFactory = new ShaderFactory(this.getNick(), (shader) => { // We import these modules in this function as they are not available in the // preferences process. This callback is only called within GNOME Shell's process. - const {Clutter, GdkPixbuf, Cogl} = imports.gi; - - // Create the texture in the first call. - if (!this._ashTexture) { - const ashData = GdkPixbuf.Pixbuf.new_from_resource('/img/dust.png'); - this._ashTexture = new Clutter.Image(); - this._ashTexture.set_data(ashData.get_pixels(), Cogl.PixelFormat.RGB_888, - ashData.width, ashData.height, ashData.rowstride); - } + const {Clutter} = imports.gi; // Store uniform locations of newly created shaders. - shader._uAshTexture = shader.get_uniform_location('uAshTexture'); shader._uSeed = shader.get_uniform_location('uSeed'); shader._uColor = shader.get_uniform_location('uColor'); shader._uWidth = shader.get_uniform_location('uWidth'); @@ -73,23 +64,6 @@ var Incinerate = class { shader.set_uniform_float(shader._uTurbulence, 1, [settings.get_double('incinerate-turbulence')]); // clang-format on }); - - // This is required to bind the ash texture for drawing. Sadly, this seems to be - // impossible under GNOME 3.3x as this.get_pipeline() is not available. It was - // called get_target() back then but this is not wrapped in GJS. - // https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598 - shader.connect('update-animation', (shader) => { - const pipeline = shader.get_pipeline(); - - // Use linear filtering for the window texture. - pipeline.set_layer_filters(0, Cogl.PipelineFilter.LINEAR, - Cogl.PipelineFilter.LINEAR); - - // Bind the ash texture. - pipeline.set_layer_texture(1, this._ashTexture.get_texture()); - pipeline.set_layer_wrap_mode(1, Cogl.PipelineWrapMode.REPEAT); - pipeline.set_uniform_1i(shader._uAshTexture, 1); - }); }); } @@ -97,7 +71,7 @@ var Incinerate = class { // This effect is only available on GNOME Shell 40+. getMinShellVersion() { - return [40, 0]; + return [3, 36]; } // This will be called in various places where a unique identifier for this effect is