Eevee: TAA Reprojection: Initial implementation
authorClément Foucault <foucault.clem@gmail.com>
Fri, 20 Apr 2018 16:24:14 +0000 (18:24 +0200)
committerClément Foucault <foucault.clem@gmail.com>
Fri, 20 Apr 2018 16:29:33 +0000 (18:29 +0200)
This "improve" the viewport experience by reducing the noise from random
sampling effects (SSAO, Contact Shadows, SSR) when moving the viewport or
during playback.

This does not do Anti Aliasing because this would conflict with the outline
pass. We could enable AA jittering in "only render" mode though.

There are many things to improve but this is a solid basis to build upon.

source/blender/draw/engines/eevee/eevee_effects.c
source/blender/draw/engines/eevee/eevee_engine.c
source/blender/draw/engines/eevee/eevee_private.h
source/blender/draw/engines/eevee/eevee_temporal_sampling.c
source/blender/draw/engines/eevee/shaders/bsdf_common_lib.glsl
source/blender/draw/engines/eevee/shaders/effect_temporal_aa.glsl

index 14924345d3ea262ca44db4b0fbff67a772f88cef..ccb61d3e3280ea264dd3dbf19f9eb1bcb1e48a3d 100644 (file)
@@ -250,6 +250,22 @@ void EEVEE_effects_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata, Object
                effects->velocity_tx = NULL;
        }
 
+       /**
+        * Setup depth double buffer.
+        */
+       if ((effects->enabled_effects & EFFECT_DEPTH_DOUBLE_BUFFER) != 0) {
+               DRW_texture_ensure_fullscreen_2D(&txl->depth_double_buffer, DRW_TEX_DEPTH_24_STENCIL_8, 0);
+
+               GPU_framebuffer_ensure_config(&fbl->double_buffer_depth_fb, {
+                       GPU_ATTACHMENT_TEXTURE(txl->depth_double_buffer)
+               });
+       }
+       else {
+               /* Cleanup to release memory */
+               DRW_TEXTURE_FREE_SAFE(txl->depth_double_buffer);
+               GPU_FRAMEBUFFER_FREE_SAFE(fbl->double_buffer_depth_fb);
+       }
+
        /**
         * Setup double buffer so we can access last frame as it was before post processes.
         */
index be9a9fa157a09c93e23f62b6991292da2789675a..2c459f5ae3ba01c4946f40fdbb991f627595e314 100644 (file)
@@ -204,12 +204,18 @@ static void eevee_draw_background(void *vedata)
                double offset[3] = {0.0, 0.0, 0.0};
                double r[3];
 
+               bool taa_use_reprojection = (stl->effects->enabled_effects & EFFECT_TAA_REPROJECT) != 0;
+
                if (DRW_state_is_image_render() ||
+                       taa_use_reprojection ||
                    ((stl->effects->enabled_effects & EFFECT_TAA) != 0))
                {
-                       BLI_halton_3D(primes, offset, stl->effects->taa_current_sample, r);
+                       int samp = taa_use_reprojection
+                                   ? stl->effects->taa_reproject_sample + 1
+                                   : stl->effects->taa_current_sample;
+                       BLI_halton_3D(primes, offset, samp, r);
                        EEVEE_update_noise(psl, fbl, r);
-                       EEVEE_volumes_set_jitter(sldata, stl->effects->taa_current_sample - 1);
+                       EEVEE_volumes_set_jitter(sldata, samp - 1);
                        EEVEE_materials_init(sldata, stl, fbl);
                }
                /* Copy previous persmat to UBO data */
@@ -217,7 +223,8 @@ static void eevee_draw_background(void *vedata)
 
                if (((stl->effects->enabled_effects & EFFECT_TAA) != 0) &&
                    (stl->effects->taa_current_sample > 1) &&
-                   !DRW_state_is_image_render())
+                   !DRW_state_is_image_render() &&
+                   !taa_use_reprojection)
                {
                        DRW_viewport_matrix_override_set(stl->effects->overide_persmat, DRW_MAT_PERS);
                        DRW_viewport_matrix_override_set(stl->effects->overide_persinv, DRW_MAT_PERSINV);
index 07624100ea741fbb4eb74c475228beeffcf712a0..9c4d7d65ccdfc88fbd546859ac3e20d0326758d4 100644 (file)
@@ -483,6 +483,8 @@ typedef enum EEVEE_EffectsFlag {
        EFFECT_NORMAL_BUFFER       = (1 << 10), /* Not really an effect but a feature */
        EFFECT_SSS                 = (1 << 11),
        EFFECT_VELOCITY_BUFFER     = (1 << 12), /* Not really an effect but a feature */
+       EFFECT_TAA_REPROJECT       = (1 << 13), /* should be mutually exclusive with EFFECT_TAA */
+       EFFECT_DEPTH_DOUBLE_BUFFER = (1 << 14), /* Not really an effect but a feature */
 } EEVEE_EffectsFlag;
 
 typedef struct EEVEE_EffectsInfo {
@@ -506,6 +508,7 @@ typedef struct EEVEE_EffectsInfo {
        struct GPUTexture *ssr_hit_output;
        struct GPUTexture *ssr_pdf_output;
        /* Temporal Anti Aliasing */
+       int taa_reproject_sample;
        int taa_current_sample;
        int taa_render_sample;
        int taa_total_sample;
index acc1bff63312a15a7b2980e70f42957c4df2e73d..06f6293240d3ee8d4a853620489544897438920b 100644 (file)
 
 #include "DRW_render.h"
 
+#include "ED_screen.h"
+
 #include "BLI_rand.h"
+#include "BLI_string_utils.h"
 
 #include "eevee_private.h"
 #include "GPU_texture.h"
 static struct {
        /* Temporal Anti Aliasing */
        struct GPUShader *taa_resolve_sh;
+       struct GPUShader *taa_resolve_reproject_sh;
 
        /* Pixel filter table: Only blackman-harris for now. */
        float inverted_cdf[FILTER_CDF_TABLE_SIZE];
 } e_data = {NULL}; /* Engine data */
 
+extern char datatoc_common_uniforms_lib_glsl[];
+extern char datatoc_common_view_lib_glsl[];
+extern char datatoc_bsdf_common_lib_glsl[];
 extern char datatoc_effect_temporal_aa_glsl[];
 
 static void eevee_create_shader_temporal_sampling(void)
 {
-       e_data.taa_resolve_sh = DRW_shader_create_fullscreen(datatoc_effect_temporal_aa_glsl, NULL);
+       char *frag_str = BLI_string_joinN(
+               datatoc_common_uniforms_lib_glsl,
+               datatoc_common_view_lib_glsl,
+               datatoc_bsdf_common_lib_glsl,
+               datatoc_effect_temporal_aa_glsl);
+
+       e_data.taa_resolve_sh = DRW_shader_create_fullscreen(frag_str, NULL);
+       e_data.taa_resolve_reproject_sh = DRW_shader_create_fullscreen(frag_str, "#define USE_REPROJECTION\n");
+
+       MEM_freeN(frag_str);
 }
 
 static float UNUSED_FUNCTION(filter_box)(float UNUSED(x))
@@ -163,6 +179,11 @@ int EEVEE_temporal_sampling_init(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_Data
        EEVEE_TextureList *txl = vedata->txl;
        EEVEE_EffectsInfo *effects = stl->effects;
 
+       if (!e_data.taa_resolve_sh) {
+               eevee_create_shader_temporal_sampling();
+               eevee_create_cdf_table_temporal_sampling();
+       }
+
        /* Reset for each "redraw". When rendering using ogl render,
         * we accumulate the redraw inside the drawing loop in eevee_draw_background().
         * But we do NOT accumulate between "redraw" (as in full draw manager drawloop)
@@ -173,6 +194,14 @@ int EEVEE_temporal_sampling_init(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_Data
        ViewLayer *view_layer = draw_ctx->view_layer;
        IDProperty *props = BKE_view_layer_engine_evaluated_get(view_layer, COLLECTION_MODE_NONE, RE_engine_id_BLENDER_EEVEE);
 
+       int repro_flag = 0;
+       if (!DRW_state_is_image_render() &&
+               BKE_collection_engine_property_value_get_bool(props, "taa_reprojection"))
+       {
+               repro_flag = EFFECT_TAA_REPROJECT | EFFECT_VELOCITY_BUFFER | EFFECT_DEPTH_DOUBLE_BUFFER | EFFECT_DOUBLE_BUFFER | EFFECT_POST_BUFFER;
+               effects->taa_reproject_sample = ((effects->taa_reproject_sample + 1) % 16);
+       }
+
        if ((BKE_collection_engine_property_value_get_int(props, "taa_samples") != 1 &&
            /* FIXME the motion blur camera evaluation is tagging view_updated
             * thus making the TAA always reset and never stopping rendering. */
@@ -181,17 +210,17 @@ int EEVEE_temporal_sampling_init(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_Data
        {
                float persmat[4][4], viewmat[4][4];
 
-               if (!e_data.taa_resolve_sh) {
-                       eevee_create_shader_temporal_sampling();
-                       eevee_create_cdf_table_temporal_sampling();
-               }
-
                /* Until we support reprojection, we need to make sure
                 * that the history buffer contains correct information. */
                bool view_is_valid = stl->g_data->valid_double_buffer;
 
                view_is_valid = view_is_valid && (stl->g_data->view_updated == false);
 
+               if (draw_ctx->evil_C != NULL) {
+                       struct wmWindowManager *wm = CTX_wm_manager(draw_ctx->evil_C);
+                       view_is_valid = view_is_valid && (ED_screen_animation_no_scrub(wm) == NULL);
+               }
+
                effects->taa_total_sample = BKE_collection_engine_property_value_get_int(props, "taa_samples");
                MAX2(effects->taa_total_sample, 0);
 
@@ -215,6 +244,7 @@ int EEVEE_temporal_sampling_init(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_Data
                                /* OGL render already jitter the camera. */
                                if (!DRW_state_is_image_render()) {
                                        effects->taa_current_sample += 1;
+                                       repro_flag = 0;
 
                                        double ht_point[2];
                                        double ht_offset[2] = {0.0, 0.0};
@@ -238,42 +268,54 @@ int EEVEE_temporal_sampling_init(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_Data
                        effects->taa_current_sample = 1;
                }
 
-               DRW_texture_ensure_fullscreen_2D(&txl->depth_double_buffer, DRW_TEX_DEPTH_24_STENCIL_8, 0);
-
-               GPU_framebuffer_ensure_config(&fbl->double_buffer_depth_fb, {
-                       GPU_ATTACHMENT_TEXTURE(txl->depth_double_buffer)
-               });
-
-               return EFFECT_TAA | EFFECT_DOUBLE_BUFFER | EFFECT_POST_BUFFER;
+               return repro_flag | EFFECT_TAA | EFFECT_DOUBLE_BUFFER | EFFECT_DEPTH_DOUBLE_BUFFER | EFFECT_POST_BUFFER;
        }
 
        effects->taa_current_sample = 1;
 
-       /* Cleanup to release memory */
-       DRW_TEXTURE_FREE_SAFE(txl->depth_double_buffer);
-       GPU_FRAMEBUFFER_FREE_SAFE(fbl->double_buffer_depth_fb);
-
-       return 0;
+       return repro_flag;
 }
 
-void EEVEE_temporal_sampling_cache_init(EEVEE_ViewLayerData *UNUSED(sldata), EEVEE_Data *vedata)
+void EEVEE_temporal_sampling_cache_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata)
 {
        EEVEE_PassList *psl = vedata->psl;
        EEVEE_StorageList *stl = vedata->stl;
        EEVEE_TextureList *txl = vedata->txl;
        EEVEE_EffectsInfo *effects = stl->effects;
 
-       if ((effects->enabled_effects & EFFECT_TAA) != 0) {
+       if ((effects->enabled_effects & (EFFECT_TAA | EFFECT_TAA_REPROJECT)) != 0) {
+               struct GPUShader *sh = (effects->enabled_effects & EFFECT_TAA_REPROJECT)
+                                       ? e_data.taa_resolve_reproject_sh
+                                       : e_data.taa_resolve_sh;
+
                psl->taa_resolve = DRW_pass_create("Temporal AA Resolve", DRW_STATE_WRITE_COLOR);
-               DRWShadingGroup *grp = DRW_shgroup_create(e_data.taa_resolve_sh, psl->taa_resolve);
+               DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->taa_resolve);
 
-               DRW_shgroup_uniform_texture_ref(grp, "historyBuffer", &txl->color_double_buffer);
+               DRW_shgroup_uniform_texture_ref(grp, "colorHistoryBuffer", &txl->color_double_buffer);
                DRW_shgroup_uniform_texture_ref(grp, "colorBuffer", &txl->color);
-               DRW_shgroup_uniform_float(grp, "alpha", &effects->taa_alpha, 1);
+
+               if (effects->enabled_effects & EFFECT_TAA_REPROJECT) {
+                       DefaultTextureList *dtxl = DRW_viewport_texture_list_get();
+                       DRW_shgroup_uniform_texture_ref(grp, "velocityBuffer", &effects->velocity_tx);
+                       DRW_shgroup_uniform_block(grp, "common_block", sldata->common_ubo);
+               }
+               else {
+                       DRW_shgroup_uniform_float(grp, "alpha", &effects->taa_alpha, 1);
+               }
                DRW_shgroup_call_add(grp, DRW_cache_fullscreen_quad_get(), NULL);
        }
 }
 
+/* Special Swap */
+#define SWAP_BUFFER_TAA() do { \
+       SWAP(struct GPUFrameBuffer *, fbl->effect_fb, fbl->double_buffer_fb); \
+       SWAP(struct GPUFrameBuffer *, fbl->effect_color_fb, fbl->double_buffer_color_fb); \
+       SWAP(GPUTexture *, txl->color_post, txl->color_double_buffer); \
+       effects->swap_double_buffer = false; \
+       effects->source_buffer = txl->color_double_buffer; \
+       effects->target_buffer = fbl->main_color_fb; \
+} while (0);
+
 void EEVEE_temporal_sampling_draw(EEVEE_Data *vedata)
 {
        EEVEE_PassList *psl = vedata->psl;
@@ -282,8 +324,8 @@ void EEVEE_temporal_sampling_draw(EEVEE_Data *vedata)
        EEVEE_StorageList *stl = vedata->stl;
        EEVEE_EffectsInfo *effects = stl->effects;
 
-       if ((effects->enabled_effects & EFFECT_TAA) != 0) {
-               if (effects->taa_current_sample != 1) {
+       if ((effects->enabled_effects & (EFFECT_TAA | EFFECT_TAA_REPROJECT)) != 0) {
+               if ((effects->enabled_effects & EFFECT_TAA) != 0 && effects->taa_current_sample != 1) {
                        if (DRW_state_is_image_render()) {
                                /* See EEVEE_temporal_sampling_init() for more details. */
                                effects->taa_alpha = 1.0f / (float)(effects->taa_render_sample);
@@ -300,19 +342,24 @@ void EEVEE_temporal_sampling_draw(EEVEE_Data *vedata)
                                GPU_framebuffer_blit(fbl->double_buffer_depth_fb, 0, fbl->main_fb, 0, GPU_DEPTH_BIT);
                        }
 
-                       /* Special Swap */
-                       SWAP(struct GPUFrameBuffer *, fbl->effect_fb, fbl->double_buffer_fb);
-                       SWAP(struct GPUFrameBuffer *, fbl->effect_color_fb, fbl->double_buffer_color_fb);
-                       SWAP(GPUTexture *, txl->color_post, txl->color_double_buffer);
-                       effects->swap_double_buffer = false;
-                       effects->source_buffer = txl->color_double_buffer;
-                       effects->target_buffer = fbl->main_color_fb;
+                       SWAP_BUFFER_TAA();
                }
                else {
-                       /* Save the depth buffer for the next frame.
-                        * This saves us from doing anything special
-                        * in the other mode engines. */
                        if (!DRW_state_is_image_render()) {
+                               /* Do reprojection for noise reduction */
+                               /* TODO : do AA jitter if in only render view. */
+                               if ((effects->enabled_effects & EFFECT_TAA_REPROJECT) != 0 &&
+                                   stl->g_data->valid_double_buffer)
+                               {
+                                       GPU_framebuffer_bind(fbl->effect_color_fb);
+                                       DRW_draw_pass(psl->taa_resolve);
+
+                                       SWAP_BUFFER_TAA();
+                               }
+
+                               /* Save the depth buffer for the next frame.
+                                * This saves us from doing anything special
+                                * in the other mode engines. */
                                GPU_framebuffer_blit(fbl->main_fb, 0, fbl->double_buffer_depth_fb, 0, GPU_DEPTH_BIT);
                        }
                }
@@ -330,10 +377,10 @@ void EEVEE_temporal_sampling_draw(EEVEE_Data *vedata)
                        }
                }
        }
-
 }
 
 void EEVEE_temporal_sampling_free(void)
 {
        DRW_SHADER_FREE_SAFE(e_data.taa_resolve_sh);
+       DRW_SHADER_FREE_SAFE(e_data.taa_resolve_reproject_sh);
 }
index b5e54c8747a14fbfe8f865d2b73d6466f34fb450..9f4f508d6fdd197aad45c2a1f9f1339f7ea59fcb 100644 (file)
@@ -95,6 +95,30 @@ vec3 project_point(mat4 m, vec3 v) {
        return tmp.xyz / tmp.w;
 }
 
+#define min3(a, b, c)                   min(a, min(b, c))
+#define min4(a, b, c, d)                min(a, min3(b, c, d))
+#define min5(a, b, c, d, e)             min(a, min4(b, c, d, e))
+#define min6(a, b, c, d, e, f)          min(a, min5(b, c, d, e, f))
+#define min7(a, b, c, d, e, f, g)       min(a, min6(b, c, d, e, f, g))
+#define min8(a, b, c, d, e, f, g, h)    min(a, min7(b, c, d, e, f, g, h))
+#define min9(a, b, c, d, e, f, g, h, i) min(a, min8(b, c, d, e, f, g, h, i))
+
+#define max3(a, b, c)                   max(a, max(b, c))
+#define max4(a, b, c, d)                max(a, max3(b, c, d))
+#define max5(a, b, c, d, e)             max(a, max4(b, c, d, e))
+#define max6(a, b, c, d, e, f)          max(a, max5(b, c, d, e, f))
+#define max7(a, b, c, d, e, f, g)       max(a, max6(b, c, d, e, f, g))
+#define max8(a, b, c, d, e, f, g, h)    max(a, max7(b, c, d, e, f, g, h))
+#define max9(a, b, c, d, e, f, g, h, i) max(a, max8(b, c, d, e, f, g, h, i))
+
+#define avg3(a, b, c)                   (a + b + c) * (1.0 / 3.0)
+#define avg4(a, b, c, d)                (a + b + c + d) * (1.0 / 4.0)
+#define avg5(a, b, c, d, e)             (a + b + c + d + e) * (1.0 / 5.0)
+#define avg6(a, b, c, d, e, f)          (a + b + c + d + e + f) * (1.0 / 6.0)
+#define avg7(a, b, c, d, e, f, g)       (a + b + c + d + e + f + g) * (1.0 / 7.0)
+#define avg8(a, b, c, d, e, f, g, h)    (a + b + c + d + e + f + g + h) * (1.0 / 8.0)
+#define avg9(a, b, c, d, e, f, g, h, i) (a + b + c + d + e + f + g + h + i) * (1.0 / 9.0)
+
 float min_v2(vec2 v) { return min(v.x, v.y); }
 float min_v3(vec3 v) { return min(v.x, min(v.y, v.z)); }
 float max_v2(vec2 v) { return max(v.x, v.y); }
@@ -252,6 +276,7 @@ void make_orthonormal_basis(vec3 N, out vec3 T, out vec3 B)
 }
 
 /* ---- Opengl Depth conversion ---- */
+
 float linear_depth(bool is_persp, float z, float zf, float zn)
 {
        if (is_persp) {
index f3df1864317fd6fb6dfad8a74235f6e9612a1912..b3b94b0a3916765021c9f1a37baca2ea0eae9062 100644 (file)
 
-uniform sampler2D colorBuffer;
-uniform sampler2D historyBuffer;
-uniform float alpha;
+uniform sampler2D colorHistoryBuffer;
+uniform sampler2D velocityBuffer;
 
 out vec4 FragColor;
 
+#ifdef USE_REPROJECTION
+
+/**
+ * Adapted from https://casual-effects.com/g3d/G3D10/data-files/shader/Film/Film_temporalAA.pix
+ * which is adapted from https://github.com/gokselgoktas/temporal-anti-aliasing/blob/master/Assets/Resources/Shaders/TemporalAntiAliasing.cginc
+ * which is adapted from https://github.com/playdeadgames/temporal
+ * Optimization by Stubbesaurus and epsilon adjustment to avoid division by zero.
+ *
+ * This can cause 3x3 blocks of color when there is a thin edge of a similar color that
+ * is varying in intensity.
+ */
+vec3 clip_to_aabb(vec3 color, vec3 minimum, vec3 maximum, vec3 average)
+{
+       /* note: only clips towards aabb center (but fast!) */
+       vec3 center  = 0.5 * (maximum + minimum);
+       vec3 extents = 0.5 * (maximum - minimum);
+       vec3 dist = color - center;
+       vec3 ts = abs(extents) / max(abs(dist), vec3(0.0001));
+       float t = saturate(min_v3(ts));
+       return center + dist * t;
+}
+
+/**
+ * Vastly based on https://github.com/playdeadgames/temporal
+ */
+void main()
+{
+       ivec2 texel = ivec2(gl_FragCoord.xy);
+       float depth = texelFetch(depthBuffer, texel, 0).r;
+       vec2 motion = texelFetch(velocityBuffer, texel, 0).rg;
+
+       /* Compute pixel position in previous frame. */
+       vec2 screen_res = vec2(textureSize(colorBuffer, 0).xy);
+       vec2 uv = gl_FragCoord.xy / screen_res;
+       vec2 uv_history = uv - motion;
+
+       ivec2 texel_history = ivec2(uv_history * screen_res);
+       vec4 color_history = textureLod(colorHistoryBuffer, uv_history, 0.0);
+
+       /* Color bounding box clamping. 3x3 neighborhood. */
+       vec4 c02 = texelFetchOffset(colorBuffer, texel, 0, ivec2(-1,  1));
+       vec4 c12 = texelFetchOffset(colorBuffer, texel, 0, ivec2( 0,  1));
+       vec4 c22 = texelFetchOffset(colorBuffer, texel, 0, ivec2( 1,  1));
+       vec4 c01 = texelFetchOffset(colorBuffer, texel, 0, ivec2(-1,  0));
+       vec4 c11 = texelFetchOffset(colorBuffer, texel, 0, ivec2( 0,  0));
+       vec4 c21 = texelFetchOffset(colorBuffer, texel, 0, ivec2( 1,  0));
+       vec4 c00 = texelFetchOffset(colorBuffer, texel, 0, ivec2(-1, -1));
+       vec4 c10 = texelFetchOffset(colorBuffer, texel, 0, ivec2( 0, -1));
+       vec4 c20 = texelFetchOffset(colorBuffer, texel, 0, ivec2( 1, -1));
+
+       vec4 color = c11;
+
+       /* AABB minmax */
+       vec4 min_col = min9(c02, c12, c22, c01, c11, c21, c00, c10, c20);
+       vec4 max_col = max9(c02, c12, c22, c01, c11, c21, c00, c10, c20);
+       vec4 avg_col = avg9(c02, c12, c22, c01, c11, c21, c00, c10, c20);
+
+       /* bias the color aabb toward the center (rounding the shape) */
+       vec4 min_center = min5(c12, c01, c11, c21, c10);
+       vec4 max_center = max5(c12, c01, c11, c21, c10);
+       vec4 avg_center = avg5(c12, c01, c11, c21, c10);
+       min_col = (min_col + min_center) * 0.5;
+       max_col = (max_col + max_center) * 0.5;
+       avg_col = (avg_col + avg_center) * 0.5;
+
+       /* Clip color toward the center of the neighborhood colors AABB box. */
+       color_history.rgb = clip_to_aabb(color_history.rgb, min_col.rgb, max_col.rgb, avg_col.rgb);
+
+       /* Luminance weighting. */
+       /* TODO correct luminance */
+       float lum0 = dot(color.rgb, vec3(0.333));
+       float lum1 = dot(color_history.rgb, vec3(0.333));
+       float diff = abs(lum0 - lum1) / max(lum0, max(lum1, 0.2));
+       float weight = 1.0 - diff;
+       float alpha = mix(0.04, 0.12, weight * weight);
+
+       color_history = mix(color_history, color, alpha);
+
+       bool out_of_view = any(greaterThanEqual(abs(uv_history - 0.5), vec2(0.5)));
+       color_history = (out_of_view) ? color : color_history;
+
+       FragColor = color_history;
+}
+
+#else
+
+uniform float alpha;
+
 void main()
 {
-       /* TODO History buffer Reprojection */
-       vec4 history = texelFetch(historyBuffer, ivec2(gl_FragCoord.xy), 0).rgba;
-       vec4 color = texelFetch(colorBuffer, ivec2(gl_FragCoord.xy), 0).rgba;
-       FragColor = mix(history, color, alpha);
-}
\ No newline at end of file
+       ivec2 texel = ivec2(gl_FragCoord.xy);
+       vec4 color = texelFetch(colorBuffer, texel, 0);
+       vec4 color_history = texelFetch(colorHistoryBuffer, texel, 0);
+       FragColor = mix(color_history, color, alpha);
+}
+#endif
\ No newline at end of file