From db7913d91b3435c1b9b4c5ecdf9b130ca74bc32c Mon Sep 17 00:00:00 2001
From: JozsefA <jozsefaug@gmail.com>
Date: Mon, 12 Apr 2021 16:00:13 -0700
Subject: [PATCH] It all works, needs some cleanup

---
 .../render/ContraptionProgram.java            |   2 +-
 .../simibubi/create/events/ClientEvents.java  |   2 +
 .../foundation/mixin/RenderHooksMixin.java    |  10 +-
 .../mixin/StoreProjectionMatrixMixin.java     |  39 ++++-
 .../foundation/render/AllProgramSpecs.java    |  39 +++--
 .../foundation/render/KineticRenderer.java    |   2 +-
 .../foundation/render/backend/Backend.java    |   8 +-
 .../render/backend/FastRenderDispatcher.java  |   7 -
 .../render/backend/ShaderLoader.java          |  58 +++----
 .../backend/{gl => core}/BasicProgram.java    |   7 +-
 .../backend/effects/EffectsHandler.java       | 121 +++++++++++++-
 .../effects/PostProcessingProgram.java        |  76 +++++++++
 .../backend/effects/ScreenQuadAttributes.java |  38 +++++
 .../render/backend/gl/GlBuffer.java           |  35 +++--
 .../gl/shader/FogSensitiveProgram.java        |  59 +++++--
 .../render/backend/gl/shader/GlProgram.java   | 147 +++++++++---------
 .../render/backend/gl/shader/ProgramSpec.java |  64 +++-----
 .../backend/gl/shader/ShaderSpecLoader.java   |   7 +
 .../backend/gl/shader/SingleProgram.java      |  24 +++
 .../backend/instancing/InstancedModel.java    |   2 +-
 .../instancing/InstancedTileRenderer.java     |   2 +-
 .../backend/instancing/RenderMaterial.java    |   2 +-
 .../create/flywheel/shaders/chromatic.frag    |  59 ++++++-
 .../create/flywheel/shaders/screen_quad.vert  |  20 +++
 24 files changed, 611 insertions(+), 219 deletions(-)
 rename src/main/java/com/simibubi/create/foundation/render/backend/{gl => core}/BasicProgram.java (85%)
 create mode 100644 src/main/java/com/simibubi/create/foundation/render/backend/effects/PostProcessingProgram.java
 create mode 100644 src/main/java/com/simibubi/create/foundation/render/backend/effects/ScreenQuadAttributes.java
 create mode 100644 src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/ShaderSpecLoader.java
 create mode 100644 src/main/resources/assets/create/flywheel/shaders/screen_quad.vert

diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionProgram.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionProgram.java
index 977593f34..7e0d2750e 100644
--- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionProgram.java
+++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/render/ContraptionProgram.java
@@ -2,7 +2,7 @@ package com.simibubi.create.content.contraptions.components.structureMovement.re
 
 import org.lwjgl.opengl.GL20;
 
-import com.simibubi.create.foundation.render.backend.gl.BasicProgram;
+import com.simibubi.create.foundation.render.backend.core.BasicProgram;
 import com.simibubi.create.foundation.render.backend.gl.shader.ProgramFogMode;
 
 import net.minecraft.util.ResourceLocation;
diff --git a/src/main/java/com/simibubi/create/events/ClientEvents.java b/src/main/java/com/simibubi/create/events/ClientEvents.java
index 88d13aece..d33a3c3bb 100644
--- a/src/main/java/com/simibubi/create/events/ClientEvents.java
+++ b/src/main/java/com/simibubi/create/events/ClientEvents.java
@@ -186,6 +186,8 @@ public class ClientEvents {
 		ms.pop();
 
 		RenderWork.runAll();
+
+		//Backend.effects.render();
 	}
 
 	@SubscribeEvent
diff --git a/src/main/java/com/simibubi/create/foundation/mixin/RenderHooksMixin.java b/src/main/java/com/simibubi/create/foundation/mixin/RenderHooksMixin.java
index da41425e6..2ea27cea7 100644
--- a/src/main/java/com/simibubi/create/foundation/mixin/RenderHooksMixin.java
+++ b/src/main/java/com/simibubi/create/foundation/mixin/RenderHooksMixin.java
@@ -46,9 +46,9 @@ public class RenderHooksMixin {
 		if (!Backend.available())
 			return;
 
-		Matrix4f viewProjection = stack.peek()
-			.getModel()
-			.copy();
+		Matrix4f view = stack.peek()
+				.getModel();
+		Matrix4f viewProjection = view.copy();
 		viewProjection.multiplyBackward(Backend.projectionMatrix);
 
 		FastRenderDispatcher.renderLayer(type, viewProjection, camX, camY, camZ);
@@ -56,6 +56,10 @@ public class RenderHooksMixin {
 		ContraptionRenderDispatcher.renderLayer(type, viewProjection, camX, camY, camZ);
 
 		GL20.glUseProgram(0);
+
+		if (type == RenderType.getTranslucent()) {
+			Backend.effects.render(view);
+		}
 	}
 
 	@Inject(at = @At(value = "INVOKE", target = "net.minecraft.client.renderer.WorldRenderer.updateChunks(J)V"), method = "render")
diff --git a/src/main/java/com/simibubi/create/foundation/mixin/StoreProjectionMatrixMixin.java b/src/main/java/com/simibubi/create/foundation/mixin/StoreProjectionMatrixMixin.java
index 7b1d5c7cd..0fe562376 100644
--- a/src/main/java/com/simibubi/create/foundation/mixin/StoreProjectionMatrixMixin.java
+++ b/src/main/java/com/simibubi/create/foundation/mixin/StoreProjectionMatrixMixin.java
@@ -1,19 +1,41 @@
 package com.simibubi.create.foundation.mixin;
 
+import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
 import com.mojang.blaze3d.matrix.MatrixStack;
 import com.simibubi.create.foundation.render.backend.Backend;
+import com.simibubi.create.foundation.render.backend.effects.EffectsHandler;
 
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.ActiveRenderInfo;
 import net.minecraft.client.renderer.GameRenderer;
 import net.minecraft.util.math.vector.Matrix4f;
 
 @Mixin(GameRenderer.class)
-public class StoreProjectionMatrixMixin {
+public abstract class StoreProjectionMatrixMixin {
+
+	@Shadow
+	private float cameraZoom;
+	@Shadow
+	private float zoomX;
+	@Shadow
+	private float zoomY;
+
+	@Shadow
+	public abstract double getFOVModifier(ActiveRenderInfo p_215311_1_, float p_215311_2_, boolean p_215311_3_);
+
+	@Shadow
+	@Final
+	private Minecraft mc;
+	@Shadow
+	private float farPlaneDistance;
 
 	@Unique
 	private boolean shouldCopy = false;
@@ -34,4 +56,19 @@ public class StoreProjectionMatrixMixin {
 			shouldCopy = false;
 		}
 	}
+
+	@Inject(method = "getBasicProjectionMatrix",
+			at = @At("HEAD"),
+			cancellable = true)
+	private void overrideNearPlane(ActiveRenderInfo p_228382_1_, float p_228382_2_, boolean p_228382_3_, CallbackInfoReturnable<Matrix4f> cir) {
+		MatrixStack matrixstack = new MatrixStack();
+		matrixstack.peek().getModel().loadIdentity();
+		if (this.cameraZoom != 1.0F) {
+			matrixstack.translate((double) this.zoomX, (double) (-this.zoomY), 0.0D);
+			matrixstack.scale(this.cameraZoom, this.cameraZoom, 1.0F);
+		}
+
+		matrixstack.peek().getModel().multiply(Matrix4f.perspective(this.getFOVModifier(p_228382_1_, p_228382_2_, p_228382_3_), (float) this.mc.getWindow().getFramebufferWidth() / (float) this.mc.getWindow().getFramebufferHeight(), EffectsHandler.getNearPlane(), EffectsHandler.getFarPlane()));
+		cir.setReturnValue(matrixstack.peek().getModel());
+	}
 }
diff --git a/src/main/java/com/simibubi/create/foundation/render/AllProgramSpecs.java b/src/main/java/com/simibubi/create/foundation/render/AllProgramSpecs.java
index 840732670..8592dbeb4 100644
--- a/src/main/java/com/simibubi/create/foundation/render/AllProgramSpecs.java
+++ b/src/main/java/com/simibubi/create/foundation/render/AllProgramSpecs.java
@@ -11,12 +11,15 @@ import com.simibubi.create.content.contraptions.components.structureMovement.ren
 import com.simibubi.create.content.contraptions.relays.belt.BeltAttributes;
 import com.simibubi.create.content.logistics.block.FlapAttributes;
 import com.simibubi.create.foundation.render.backend.core.BasicAttributes;
+import com.simibubi.create.foundation.render.backend.core.BasicProgram;
 import com.simibubi.create.foundation.render.backend.core.ModelAttributes;
 import com.simibubi.create.foundation.render.backend.core.OrientedAttributes;
 import com.simibubi.create.foundation.render.backend.core.TransformAttributes;
-import com.simibubi.create.foundation.render.backend.gl.BasicProgram;
+import com.simibubi.create.foundation.render.backend.effects.PostProcessingProgram;
+import com.simibubi.create.foundation.render.backend.gl.shader.FogSensitiveProgram;
 import com.simibubi.create.foundation.render.backend.gl.shader.ProgramSpec;
 import com.simibubi.create.foundation.render.backend.gl.shader.ShaderConstants;
+import com.simibubi.create.foundation.render.backend.gl.shader.SingleProgram;
 
 import net.minecraft.util.ResourceLocation;
 
@@ -25,7 +28,15 @@ public class AllProgramSpecs {
 		// noop, make sure the static field are loaded.
 	}
 
-	public static final ProgramSpec<BasicProgram> MODEL = register(ProgramSpec.builder("model", BasicProgram::new)
+	public static final ProgramSpec<PostProcessingProgram> CHROMATIC = register(ProgramSpec.builder("chromatic", new SingleProgram.SpecLoader<>(PostProcessingProgram::new))
+			.addAttributes(ModelAttributes.class)
+			.addAttributes(BasicAttributes.class)
+			.addAttributes(TransformAttributes.class)
+			.setVert(Locations.SCREEN_QUAD)
+			.setFrag(Locations.CHROMATIC)
+			.createProgramSpec());
+
+	public static final ProgramSpec<BasicProgram> MODEL = register(ProgramSpec.builder("model", new FogSensitiveProgram.SpecLoader<>(BasicProgram::new))
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(BasicAttributes.class)
 			.addAttributes(TransformAttributes.class)
@@ -33,7 +44,7 @@ public class AllProgramSpecs {
 			.setFrag(Locations.MODEL_FRAG)
 			.createProgramSpec());
 
-	public static final ProgramSpec<BasicProgram> ORIENTED = register(ProgramSpec.builder("oriented", BasicProgram::new)
+	public static final ProgramSpec<BasicProgram> ORIENTED = register(ProgramSpec.builder("oriented", new FogSensitiveProgram.SpecLoader<>(BasicProgram::new))
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(BasicAttributes.class)
 			.addAttributes(OrientedAttributes.class)
@@ -41,7 +52,7 @@ public class AllProgramSpecs {
 			.setFrag(Locations.MODEL_FRAG)
 			.createProgramSpec());
 
-	public static final ProgramSpec<BasicProgram> ROTATING = register(ProgramSpec.builder("rotating", BasicProgram::new)
+	public static final ProgramSpec<BasicProgram> ROTATING = register(ProgramSpec.builder("rotating", new FogSensitiveProgram.SpecLoader<>(BasicProgram::new))
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(BasicAttributes.class)
 			.addAttributes(KineticAttributes.class)
@@ -50,7 +61,7 @@ public class AllProgramSpecs {
 			.setFrag(Locations.MODEL_FRAG)
 			.createProgramSpec());
 
-	public static final ProgramSpec<BasicProgram> BELT = register(ProgramSpec.builder("belt", BasicProgram::new)
+	public static final ProgramSpec<BasicProgram> BELT = register(ProgramSpec.builder("belt", new FogSensitiveProgram.SpecLoader<>(BasicProgram::new))
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(BasicAttributes.class)
 			.addAttributes(KineticAttributes.class)
@@ -59,18 +70,18 @@ public class AllProgramSpecs {
 			.setFrag(Locations.MODEL_FRAG)
 			.createProgramSpec());
 
-	public static final ProgramSpec<BasicProgram> FLAPS = register(ProgramSpec.builder("flap", BasicProgram::new)
+	public static final ProgramSpec<BasicProgram> FLAPS = register(ProgramSpec.builder("flap", new FogSensitiveProgram.SpecLoader<>(BasicProgram::new))
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(FlapAttributes.class)
 			.setVert(Locations.FLAP)
 			.setFrag(Locations.MODEL_FRAG)
 			.createProgramSpec());
-	public static final ProgramSpec<ContraptionProgram> C_STRUCTURE = register(ProgramSpec.builder("contraption_structure", ContraptionProgram::new)
+	public static final ProgramSpec<ContraptionProgram> C_STRUCTURE = register(ProgramSpec.builder("contraption_structure", new FogSensitiveProgram.SpecLoader<>(ContraptionProgram::new))
 			.addAttributes(ContraptionAttributes.class)
 			.setVert(Locations.CONTRAPTION_STRUCTURE)
 			.setFrag(Locations.CONTRAPTION)
 			.createProgramSpec());
-	public static final ProgramSpec<ContraptionProgram> C_MODEL = register(ProgramSpec.builder("contraption_model", ContraptionProgram::new)
+	public static final ProgramSpec<ContraptionProgram> C_MODEL = register(ProgramSpec.builder("contraption_model", new FogSensitiveProgram.SpecLoader<>(ContraptionProgram::new))
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(BasicAttributes.class)
 			.addAttributes(TransformAttributes.class)
@@ -78,7 +89,7 @@ public class AllProgramSpecs {
 			.setFrag(Locations.CONTRAPTION)
 			.setDefines(ShaderConstants.define("CONTRAPTION"))
 			.createProgramSpec());
-	public static final ProgramSpec<ContraptionProgram> C_ORIENTED = register(ProgramSpec.builder("contraption_oriented", ContraptionProgram::new)
+	public static final ProgramSpec<ContraptionProgram> C_ORIENTED = register(ProgramSpec.builder("contraption_oriented", new FogSensitiveProgram.SpecLoader<>(ContraptionProgram::new))
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(BasicAttributes.class)
 			.addAttributes(OrientedAttributes.class)
@@ -86,7 +97,7 @@ public class AllProgramSpecs {
 			.setFrag(Locations.CONTRAPTION)
 			.setDefines(ShaderConstants.define("CONTRAPTION"))
 			.createProgramSpec());
-	public static final ProgramSpec<ContraptionProgram> C_ROTATING = register(ProgramSpec.builder("contraption_rotating", ContraptionProgram::new)
+	public static final ProgramSpec<ContraptionProgram> C_ROTATING = register(ProgramSpec.builder("contraption_rotating", new FogSensitiveProgram.SpecLoader<>(ContraptionProgram::new))
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(BasicAttributes.class)
 			.addAttributes(KineticAttributes.class)
@@ -95,7 +106,7 @@ public class AllProgramSpecs {
 			.setFrag(Locations.CONTRAPTION)
 			.setDefines(ShaderConstants.define("CONTRAPTION"))
 			.createProgramSpec());
-	public static final ProgramSpec<ContraptionProgram> C_BELT = register(ProgramSpec.builder("contraption_belt", ContraptionProgram::new)
+	public static final ProgramSpec<ContraptionProgram> C_BELT = register(ProgramSpec.builder("contraption_belt", new FogSensitiveProgram.SpecLoader<>(ContraptionProgram::new))
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(BasicAttributes.class)
 			.addAttributes(KineticAttributes.class)
@@ -104,14 +115,14 @@ public class AllProgramSpecs {
 			.setFrag(Locations.CONTRAPTION)
 			.setDefines(ShaderConstants.define("CONTRAPTION"))
 			.createProgramSpec());
-	public static final ProgramSpec<ContraptionProgram> C_FLAPS = register(ProgramSpec.builder("contraption_flap", ContraptionProgram::new)
+	public static final ProgramSpec<ContraptionProgram> C_FLAPS = register(ProgramSpec.builder("contraption_flap", new FogSensitiveProgram.SpecLoader<>(ContraptionProgram::new))
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(FlapAttributes.class)
 			.setVert(Locations.FLAP)
 			.setFrag(Locations.CONTRAPTION)
 			.setDefines(ShaderConstants.define("CONTRAPTION"))
 			.createProgramSpec());
-	public static final ProgramSpec<ContraptionProgram> C_ACTOR = register(ProgramSpec.builder("contraption_actor", ContraptionProgram::new)
+	public static final ProgramSpec<ContraptionProgram> C_ACTOR = register(ProgramSpec.builder("contraption_actor", new FogSensitiveProgram.SpecLoader<>(ContraptionProgram::new))
 			.addAttributes(ModelAttributes.class)
 			.addAttributes(ActorVertexAttributes.class)
 			.setVert(Locations.CONTRAPTION_ACTOR)
@@ -120,6 +131,8 @@ public class AllProgramSpecs {
 
 
 	public static class Locations {
+		public static final ResourceLocation SCREEN_QUAD = loc("screen_quad.vert");
+		public static final ResourceLocation CHROMATIC = loc("chromatic.frag");
 		public static final ResourceLocation MODEL_FRAG = loc("model.frag");
 		public static final ResourceLocation MODEL_VERT = loc("model.vert");
 		public static final ResourceLocation ORIENTED = loc("oriented.vert");
diff --git a/src/main/java/com/simibubi/create/foundation/render/KineticRenderer.java b/src/main/java/com/simibubi/create/foundation/render/KineticRenderer.java
index 451122105..d6dd5bb80 100644
--- a/src/main/java/com/simibubi/create/foundation/render/KineticRenderer.java
+++ b/src/main/java/com/simibubi/create/foundation/render/KineticRenderer.java
@@ -7,9 +7,9 @@ import com.simibubi.create.content.contraptions.base.RotatingModel;
 import com.simibubi.create.content.contraptions.relays.belt.BeltInstancedModel;
 import com.simibubi.create.content.logistics.block.FlapModel;
 import com.simibubi.create.foundation.render.backend.MaterialTypes;
+import com.simibubi.create.foundation.render.backend.core.BasicProgram;
 import com.simibubi.create.foundation.render.backend.core.OrientedModel;
 import com.simibubi.create.foundation.render.backend.core.TransformedModel;
-import com.simibubi.create.foundation.render.backend.gl.BasicProgram;
 import com.simibubi.create.foundation.render.backend.gl.shader.ShaderCallback;
 import com.simibubi.create.foundation.render.backend.instancing.InstancedTileRenderer;
 import com.simibubi.create.foundation.render.backend.instancing.RenderMaterial;
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/Backend.java b/src/main/java/com/simibubi/create/foundation/render/backend/Backend.java
index 9c17503c9..095f96b84 100644
--- a/src/main/java/com/simibubi/create/foundation/render/backend/Backend.java
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/Backend.java
@@ -9,7 +9,7 @@ import org.lwjgl.opengl.GL;
 import org.lwjgl.opengl.GLCapabilities;
 
 import com.simibubi.create.foundation.config.AllConfigs;
-import com.simibubi.create.foundation.render.backend.gl.GlFog;
+import com.simibubi.create.foundation.render.backend.effects.EffectsHandler;
 import com.simibubi.create.foundation.render.backend.gl.shader.GlProgram;
 import com.simibubi.create.foundation.render.backend.gl.shader.IMultiProgram;
 import com.simibubi.create.foundation.render.backend.gl.shader.ProgramSpec;
@@ -28,6 +28,7 @@ public class Backend {
 	public static final Logger log = LogManager.getLogger(Backend.class);
 
 	public static final ShaderLoader shaderLoader = new ShaderLoader();
+	public static EffectsHandler effects;
 
 	public static Matrix4f projectionMatrix = new Matrix4f();
 
@@ -108,5 +109,10 @@ public class Backend {
 				compat.instancedArraysSupported();
 
 		enabled = AllConfigs.CLIENT.experimentalRendering.get() && !OptifineHandler.usingShaders();
+
+		if (enabled) {
+			if (effects != null) effects.delete();
+			effects = new EffectsHandler();
+		}
 	}
 }
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/FastRenderDispatcher.java b/src/main/java/com/simibubi/create/foundation/render/backend/FastRenderDispatcher.java
index f4deae1dc..fd516cdd8 100644
--- a/src/main/java/com/simibubi/create/foundation/render/backend/FastRenderDispatcher.java
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/FastRenderDispatcher.java
@@ -2,24 +2,17 @@ package com.simibubi.create.foundation.render.backend;
 
 import java.util.concurrent.ConcurrentHashMap;
 
-import com.mojang.blaze3d.matrix.MatrixStack;
 import com.simibubi.create.CreateClient;
 import com.simibubi.create.content.contraptions.KineticDebugger;
 import com.simibubi.create.foundation.render.KineticRenderer;
-import com.simibubi.create.foundation.utility.AnimationTickHolder;
 import com.simibubi.create.foundation.utility.WorldAttached;
 
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.entity.player.ClientPlayerEntity;
-import net.minecraft.client.renderer.GameRenderer;
 import net.minecraft.client.renderer.RenderType;
 import net.minecraft.client.world.ClientWorld;
 import net.minecraft.entity.Entity;
-import net.minecraft.potion.Effects;
 import net.minecraft.tileentity.TileEntity;
-import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.math.vector.Matrix4f;
-import net.minecraft.util.math.vector.Vector3f;
 import net.minecraft.world.World;
 
 public class FastRenderDispatcher {
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/ShaderLoader.java b/src/main/java/com/simibubi/create/foundation/render/backend/ShaderLoader.java
index 41c5dfb33..1683c63bf 100644
--- a/src/main/java/com/simibubi/create/foundation/render/backend/ShaderLoader.java
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/ShaderLoader.java
@@ -12,7 +12,6 @@ import java.nio.channels.FileChannel;
 import java.nio.channels.ReadableByteChannel;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -27,11 +26,9 @@ import org.lwjgl.system.MemoryUtil;
 
 import com.google.common.collect.Lists;
 import com.mojang.blaze3d.systems.RenderSystem;
-import com.simibubi.create.foundation.render.backend.gl.GlFogMode;
-import com.simibubi.create.foundation.render.backend.gl.shader.SingleProgram;
+import com.simibubi.create.foundation.render.backend.gl.attrib.IVertexAttrib;
 import com.simibubi.create.foundation.render.backend.gl.shader.GlProgram;
 import com.simibubi.create.foundation.render.backend.gl.shader.GlShader;
-import com.simibubi.create.foundation.render.backend.gl.shader.FogSensitiveProgram;
 import com.simibubi.create.foundation.render.backend.gl.shader.IMultiProgram;
 import com.simibubi.create.foundation.render.backend.gl.shader.ProgramSpec;
 import com.simibubi.create.foundation.render.backend.gl.shader.ShaderConstants;
@@ -96,43 +93,34 @@ public class ShaderLoader {
 
 	private <P extends GlProgram, S extends ProgramSpec<P>> void loadProgramFromSpec(S programSpec) {
 
-		if (programSpec.fogSensitive) {
-			Map<GlFogMode, P> programGroup = new EnumMap<>(GlFogMode.class);
-
-			for (GlFogMode fogMode : GlFogMode.values()) {
-				programGroup.put(fogMode, loadProgram(programSpec, fogMode));
-			}
-
-			Backend.programs.put(programSpec, new FogSensitiveProgram<>(programGroup));
-		} else {
-			P program = loadProgram(programSpec, GlFogMode.NONE);
-
-			Backend.programs.put(programSpec, new SingleProgram<>(program));
-		}
+		Backend.programs.put(programSpec, programSpec.finalizer.create(this, programSpec));
 
 		Backend.log.debug("Loaded program {}", programSpec.name);
 	}
 
-	private <P extends GlProgram, S extends ProgramSpec<P>> P loadProgram(S programSpec, GlFogMode fogMode) {
-		GlShader vert = null;
-		GlShader frag = null;
+	public GlProgram.Builder loadProgram(ProgramSpec<?> programSpec) {
+		return loadProgram(programSpec, programSpec.defines);
+	}
+
+	public GlProgram.Builder loadProgram(ProgramSpec<?> programSpec, ShaderConstants defines) {
+		return loadProgram(programSpec.name, programSpec.vert, programSpec.frag, programSpec.attributes, defines);
+	}
+
+	public GlProgram.Builder loadProgram(ResourceLocation name, ResourceLocation vert, ResourceLocation frag, Collection<IVertexAttrib> attribs, ShaderConstants defines) {
+		GlShader vsh = null;
+		GlShader fsh = null;
 		try {
-			ShaderConstants defines = new ShaderConstants(programSpec.defines);
-
-			defines.defineAll(fogMode.getDefines());
-
-			vert = loadShader(programSpec.getVert(), ShaderType.VERTEX, defines);
-			frag = loadShader(programSpec.getFrag(), ShaderType.FRAGMENT, defines);
-
-			GlProgram.Builder builder = GlProgram.builder(programSpec.name, fogMode).attachShader(vert).attachShader(frag);
-
-			programSpec.attributes.forEach(builder::addAttribute);
-
-			return builder.build(programSpec.factory);
+			vsh = loadShader(vert, ShaderType.VERTEX, defines);
+			fsh = loadShader(frag, ShaderType.FRAGMENT, defines);
 
+			return GlProgram.builder(name)
+					.attachShader(vsh)
+					.attachShader(fsh)
+					.addAttributes(attribs)
+					.link();
 		} finally {
-			if (vert != null) vert.delete();
-			if (frag != null) frag.delete();
+			if (vsh != null) vsh.delete();
+			if (fsh != null) fsh.delete();
 		}
 	}
 
@@ -166,7 +154,7 @@ public class ShaderLoader {
 		});
 	}
 
-	private GlShader loadShader(ResourceLocation name, ShaderType type, ShaderConstants defines) {
+	public GlShader loadShader(ResourceLocation name, ShaderType type, ShaderConstants defines) {
 		String source = shaderSource.get(name);
 
 		source = processIncludes(name, source);
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/gl/BasicProgram.java b/src/main/java/com/simibubi/create/foundation/render/backend/core/BasicProgram.java
similarity index 85%
rename from src/main/java/com/simibubi/create/foundation/render/backend/gl/BasicProgram.java
rename to src/main/java/com/simibubi/create/foundation/render/backend/core/BasicProgram.java
index 2087795b0..5f2e671d3 100644
--- a/src/main/java/com/simibubi/create/foundation/render/backend/gl/BasicProgram.java
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/core/BasicProgram.java
@@ -1,8 +1,7 @@
-package com.simibubi.create.foundation.render.backend.gl;
+package com.simibubi.create.foundation.render.backend.core;
 
 import org.lwjgl.opengl.GL20;
 
-import com.simibubi.create.foundation.render.backend.RenderUtil;
 import com.simibubi.create.foundation.render.backend.gl.shader.GlProgram;
 import com.simibubi.create.foundation.render.backend.gl.shader.ProgramFogMode;
 import com.simibubi.create.foundation.utility.AnimationTickHolder;
@@ -51,8 +50,4 @@ public class BasicProgram extends GlProgram {
 
         fogMode.bind();
     }
-
-    protected static void uploadMatrixUniform(int uniform, Matrix4f mat) {
-        GL20.glUniformMatrix4fv(uniform, false, RenderUtil.writeMatrix(mat));
-    }
 }
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/effects/EffectsHandler.java b/src/main/java/com/simibubi/create/foundation/render/backend/effects/EffectsHandler.java
index 509e5fb5b..e0bbdaadb 100644
--- a/src/main/java/com/simibubi/create/foundation/render/backend/effects/EffectsHandler.java
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/effects/EffectsHandler.java
@@ -1,24 +1,72 @@
 package com.simibubi.create.foundation.render.backend.effects;
 
+import org.lwjgl.opengl.GL15;
+import org.lwjgl.opengl.GL20;
+import org.lwjgl.opengl.GL30;
+
+import com.simibubi.create.foundation.render.AllProgramSpecs;
+import com.simibubi.create.foundation.render.backend.Backend;
+import com.simibubi.create.foundation.render.backend.gl.GlBuffer;
+import com.simibubi.create.foundation.render.backend.gl.GlPrimitiveType;
+import com.simibubi.create.foundation.render.backend.gl.GlVertexArray;
+import com.simibubi.create.foundation.utility.AnimationTickHolder;
+
 import net.minecraft.client.MainWindow;
 import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.GameRenderer;
 import net.minecraft.client.shader.Framebuffer;
+import net.minecraft.client.shader.FramebufferConstants;
+import net.minecraft.util.math.vector.Matrix4f;
+import net.minecraft.util.math.vector.Vector3d;
 
 public class EffectsHandler {
 
-	final Minecraft mc;
+	public static float getNearPlane() {
+		return 0.05f;
+	}
+
+	public static float getFarPlane() {
+		return Minecraft.getInstance().gameRenderer.getFarPlaneDistance();
+	}
+
+	public static final float[] vertices = {
+			// pos        // tex
+			-1.0f, -1.0f, 0.0f, 0.0f,
+			1.0f, 1.0f, 1.0f, 1.0f,
+			-1.0f, 1.0f, 0.0f, 1.0f,
+
+			-1.0f, -1.0f, 0.0f, 0.0f,
+			1.0f, -1.0f, 1.0f, 0.0f,
+			1.0f, 1.0f, 1.0f, 1.0f
+	};
+
+	private static final int bufferSize = vertices.length * 4;
 
 	private final Framebuffer framebuffer;
+	private final GlVertexArray vao = new GlVertexArray();
 
-	public EffectsHandler(Minecraft minecraft) {
-		this.mc = minecraft;
+	private final GlBuffer vbo = new GlBuffer(GL20.GL_ARRAY_BUFFER);
 
-		Framebuffer render = minecraft.getFramebuffer();
+	public EffectsHandler() {
+		Framebuffer render = Minecraft.getInstance().getFramebuffer();
 		framebuffer = new Framebuffer(render.framebufferWidth, render.framebufferHeight, false, Minecraft.IS_RUNNING_ON_MAC);
+
+		vbo.bind();
+		vbo.alloc(bufferSize, GL15.GL_STATIC_DRAW);
+		vbo.map(bufferSize, buf -> buf.asFloatBuffer().put(vertices));
+
+		vao.bind();
+
+		GL20.glEnableVertexAttribArray(0);
+
+		GL20.glVertexAttribPointer(0, 4, GlPrimitiveType.FLOAT.getGlConstant(), false, 4 * 4, 0);
+
+		vao.unbind();
+		vbo.unbind();
 	}
 
 	public void prepFramebufferSize() {
-		MainWindow window = mc.getWindow();
+		MainWindow window = Minecraft.getInstance().getWindow();
 		if (framebuffer.framebufferWidth != window.getFramebufferWidth()
 				|| framebuffer.framebufferHeight != window.getFramebufferHeight()) {
 			framebuffer.func_216491_a(window.getFramebufferWidth(), window.getFramebufferHeight(),
@@ -26,5 +74,68 @@ public class EffectsHandler {
 		}
 	}
 
+	public void render(Matrix4f view) {
+		GL20.glEnable(GL20.GL_DEPTH_TEST);
 
+		GL20.glDepthRange(getNearPlane(), getFarPlane());
+
+//		float[] floats = new float[2];
+//		GL20.glGetFloatv(GL20.GL_DEPTH_RANGE, floats);
+
+		prepFramebufferSize();
+
+		Framebuffer mainBuffer = Minecraft.getInstance().getFramebuffer();
+
+		GL30.glBindFramebuffer(FramebufferConstants.FRAME_BUFFER, framebuffer.framebufferObject);
+
+		PostProcessingProgram program = Backend.getProgram(AllProgramSpecs.CHROMATIC);
+		program.bind();
+
+		program.bindColorTexture(mainBuffer.getColorAttachment());
+		program.bindDepthTexture(mainBuffer.getDepthAttachment());
+
+		GameRenderer gameRenderer = Minecraft.getInstance().gameRenderer;
+		Matrix4f projection = gameRenderer.getBasicProjectionMatrix(gameRenderer.getActiveRenderInfo(), AnimationTickHolder.getPartialTicks(), true);
+		//Matrix4f projection = Backend.projectionMatrix.copy();
+		//projection.a23 = projection.a32 = 0;
+		projection.a33 = 1;
+		projection.invert();
+		program.bindInverseProjection(projection);
+
+		Matrix4f inverseView = view.copy();
+		inverseView.invert();
+//		Matrix4f inverseView = new Matrix4f();
+//		inverseView.loadIdentity();
+		program.bindInverseView(inverseView);
+
+		Vector3d pos = new Vector3d(286, 73, -149);
+		Vector3d cameraPos = gameRenderer.getActiveRenderInfo().getProjectedView();
+
+		Vector3d shaderPos = pos.subtract(cameraPos).scale(1 / getFarPlane());
+		program.setSphere(shaderPos, 20f / getFarPlane(), 0.01f);
+
+		program.setFarPlane(getFarPlane());
+		program.setNearPlane(getNearPlane());
+
+		vao.bind();
+		GL20.glDrawArrays(GL20.GL_TRIANGLES, 0, 6);
+		vao.unbind();
+
+		program.bindColorTexture(0);
+		program.bindDepthTexture(0);
+
+		program.unbind();
+
+		GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, framebuffer.framebufferObject);
+		GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, mainBuffer.framebufferObject);
+		GL30.glBlitFramebuffer(0, 0, mainBuffer.framebufferWidth, mainBuffer.framebufferHeight, 0, 0, mainBuffer.framebufferWidth, mainBuffer.framebufferHeight, GL30.GL_COLOR_BUFFER_BIT, GL20.GL_LINEAR);
+		GL30.glBindFramebuffer(FramebufferConstants.FRAME_BUFFER, mainBuffer.framebufferObject);
+	}
+
+	public void delete() {
+		framebuffer.deleteFramebuffer();
+
+		vao.delete();
+		vbo.delete();
+	}
 }
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/effects/PostProcessingProgram.java b/src/main/java/com/simibubi/create/foundation/render/backend/effects/PostProcessingProgram.java
new file mode 100644
index 000000000..19f239ad2
--- /dev/null
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/effects/PostProcessingProgram.java
@@ -0,0 +1,76 @@
+package com.simibubi.create.foundation.render.backend.effects;
+
+import org.lwjgl.opengl.GL20;
+
+import com.simibubi.create.foundation.render.backend.gl.shader.GlProgram;
+
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.math.vector.Matrix4f;
+import net.minecraft.util.math.vector.Vector3d;
+
+public class PostProcessingProgram extends GlProgram {
+
+	final int uDepth;
+	final int uColor;
+
+	final int uInverseProjection;
+	final int uInverseView;
+
+	final int uNearPlane;
+	final int uFarPlane;
+	final int uSphereCenter;
+	final int uSphereRadius;
+	final int uSphereFeather;
+
+
+	public PostProcessingProgram(ResourceLocation name, int handle) {
+		super(name, handle);
+
+		uInverseProjection = getUniformLocation("uInverseProjection");
+		uInverseView = getUniformLocation("uInverseView");
+		uNearPlane = getUniformLocation("uNearPlane");
+		uFarPlane = getUniformLocation("uFarPlane");
+		uSphereCenter = getUniformLocation("uSphereCenter");
+		uSphereRadius = getUniformLocation("uSphereRadius");
+		uSphereFeather = getUniformLocation("uSphereFeather");
+
+		bind();
+		uDepth = setSamplerBinding("uDepth", 8);
+		uColor = setSamplerBinding("uColor", 9);
+		unbind();
+	}
+
+	public void setNearPlane(float nearPlane) {
+		GL20.glUniform1f(uNearPlane, nearPlane);
+	}
+
+	public void setFarPlane(float farPlane) {
+		GL20.glUniform1f(uFarPlane, farPlane);
+	}
+
+	public void setSphere(Vector3d center, float radius, float feather) {
+		GL20.glUniform3f(uSphereCenter, (float) center.x, (float) center.y, (float) center.z);
+
+		GL20.glUniform1f(uSphereRadius, radius);
+		GL20.glUniform1f(uSphereFeather, feather);
+	}
+
+	public void bindInverseProjection(Matrix4f mat) {
+		uploadMatrixUniform(uInverseProjection, mat);
+	}
+
+	public void bindInverseView(Matrix4f mat) {
+		uploadMatrixUniform(uInverseView, mat);
+	}
+
+	public void bindDepthTexture(int textureObject) {
+		GL20.glActiveTexture(GL20.GL_TEXTURE8);
+		GL20.glBindTexture(GL20.GL_TEXTURE_2D, textureObject);
+	}
+
+	public void bindColorTexture(int textureObject) {
+		GL20.glActiveTexture(GL20.GL_TEXTURE9);
+		GL20.glBindTexture(GL20.GL_TEXTURE_2D, textureObject);
+	}
+
+}
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/effects/ScreenQuadAttributes.java b/src/main/java/com/simibubi/create/foundation/render/backend/effects/ScreenQuadAttributes.java
new file mode 100644
index 000000000..5b81e2383
--- /dev/null
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/effects/ScreenQuadAttributes.java
@@ -0,0 +1,38 @@
+package com.simibubi.create.foundation.render.backend.effects;
+
+import com.simibubi.create.foundation.render.backend.gl.attrib.CommonAttributes;
+import com.simibubi.create.foundation.render.backend.gl.attrib.IAttribSpec;
+import com.simibubi.create.foundation.render.backend.gl.attrib.IVertexAttrib;
+
+public enum ScreenQuadAttributes implements IVertexAttrib {
+	INSTANCE_POS("aVertex", CommonAttributes.VEC4),
+	;
+
+	private final String name;
+	private final IAttribSpec spec;
+
+	ScreenQuadAttributes(String name, IAttribSpec spec) {
+		this.name = name;
+		this.spec = spec;
+	}
+
+	@Override
+	public String attribName() {
+		return name;
+	}
+
+	@Override
+	public IAttribSpec attribSpec() {
+		return spec;
+	}
+
+	@Override
+	public int getDivisor() {
+		return 0;
+	}
+
+	@Override
+	public int getBufferIndex() {
+		return 0;
+	}
+}
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/gl/GlBuffer.java b/src/main/java/com/simibubi/create/foundation/render/backend/gl/GlBuffer.java
index f39913ccd..7cb688703 100644
--- a/src/main/java/com/simibubi/create/foundation/render/backend/gl/GlBuffer.java
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/gl/GlBuffer.java
@@ -3,6 +3,7 @@ package com.simibubi.create.foundation.render.backend.gl;
 import java.nio.ByteBuffer;
 import java.util.function.Consumer;
 
+import org.lwjgl.opengl.GL15;
 import org.lwjgl.opengl.GL20;
 
 import com.simibubi.create.foundation.render.backend.Backend;
@@ -18,25 +19,29 @@ public class GlBuffer extends GlObject {
 
     public int getBufferType() {
         return bufferType;
-    }
+	}
 
-    public void bind() {
-        GL20.glBindBuffer(bufferType, handle());
-    }
+	public void bind() {
+		GL20.glBindBuffer(bufferType, handle());
+	}
 
-    public void unbind() {
-        GL20.glBindBuffer(bufferType, 0);
-    }
+	public void unbind() {
+		GL20.glBindBuffer(bufferType, 0);
+	}
 
-    public void with(Consumer<GlBuffer> action) {
-        bind();
-        action.accept(this);
-        unbind();
-    }
+	public void alloc(int size, int usage) {
+		GL15.glBufferData(bufferType, size, usage);
+	}
 
-    public void map(int length, Consumer<ByteBuffer> upload) {
-        Backend.compat.mapBuffer(bufferType, 0, length, upload);
-    }
+	public void with(Consumer<GlBuffer> action) {
+		bind();
+		action.accept(this);
+		unbind();
+	}
+
+	public void map(int length, Consumer<ByteBuffer> upload) {
+		Backend.compat.mapBuffer(bufferType, 0, length, upload);
+	}
 
     public void map(int offset, int length, Consumer<ByteBuffer> upload) {
         Backend.compat.mapBuffer(bufferType, offset, length, upload);
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/FogSensitiveProgram.java b/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/FogSensitiveProgram.java
index 426dbc4ac..b308a6bd3 100644
--- a/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/FogSensitiveProgram.java
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/FogSensitiveProgram.java
@@ -1,26 +1,61 @@
 package com.simibubi.create.foundation.render.backend.gl.shader;
 
+import java.util.EnumMap;
 import java.util.Map;
 
+import com.simibubi.create.foundation.render.backend.ShaderLoader;
 import com.simibubi.create.foundation.render.backend.gl.GlFog;
 import com.simibubi.create.foundation.render.backend.gl.GlFogMode;
 
+import net.minecraft.util.ResourceLocation;
+
 public class FogSensitiveProgram<P extends GlProgram> implements IMultiProgram<P> {
 
-    private final Map<GlFogMode, P> programs;
+	private final Map<GlFogMode, P> programs;
 
-    public FogSensitiveProgram(Map<GlFogMode, P> programs) {
-        this.programs = programs;
-    }
+	public FogSensitiveProgram(Map<GlFogMode, P> programs) {
+		this.programs = programs;
+	}
 
-    @Override
-    public P get() {
-        return programs.get(GlFog.getFogMode());
-    }
+	@Override
+	public P get() {
+		return programs.get(GlFog.getFogMode());
+	}
 
-    @Override
-    public void delete() {
-        programs.values().forEach(GlProgram::delete);
-    }
+	@Override
+	public void delete() {
+		programs.values().forEach(GlProgram::delete);
+	}
 
+	public static class SpecLoader<P extends GlProgram> implements ShaderSpecLoader<P> {
+
+		private final FogProgramLoader<P> fogProgramLoader;
+
+		public SpecLoader(FogProgramLoader<P> fogProgramLoader) {
+			this.fogProgramLoader = fogProgramLoader;
+		}
+
+		@Override
+		public IMultiProgram<P> create(ShaderLoader loader, ProgramSpec<P> spec) {
+			Map<GlFogMode, P> programs = new EnumMap<>(GlFogMode.class);
+
+			for (GlFogMode fogMode : GlFogMode.values()) {
+				ShaderConstants defines = new ShaderConstants(spec.defines);
+
+				defines.defineAll(fogMode.getDefines());
+
+				GlProgram.Builder builder = loader.loadProgram(spec, defines);
+
+				programs.put(fogMode, fogProgramLoader.create(builder.name, builder.program, fogMode.getFogFactory()));
+			}
+
+			return new FogSensitiveProgram<>(programs);
+		}
+
+	}
+
+	public interface FogProgramLoader<P extends GlProgram> {
+
+		P create(ResourceLocation name, int handle, ProgramFogMode.Factory fogFactory);
+	}
 }
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/GlProgram.java b/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/GlProgram.java
index 56dce3fd8..40d2e62ae 100644
--- a/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/GlProgram.java
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/GlProgram.java
@@ -1,34 +1,37 @@
 package com.simibubi.create.foundation.render.backend.gl.shader;
 
+import java.util.Collection;
+
 import org.lwjgl.opengl.GL20;
 
 import com.simibubi.create.foundation.render.backend.Backend;
-import com.simibubi.create.foundation.render.backend.gl.GlFogMode;
+import com.simibubi.create.foundation.render.backend.RenderUtil;
 import com.simibubi.create.foundation.render.backend.gl.GlObject;
 import com.simibubi.create.foundation.render.backend.gl.attrib.IVertexAttrib;
 
 import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.math.vector.Matrix4f;
 
 public abstract class GlProgram extends GlObject {
 
-    public final ResourceLocation name;
+	public final ResourceLocation name;
 
-    protected GlProgram(ResourceLocation name, int handle) {
-        setHandle(handle);
-        this.name = name;
-    }
+	protected GlProgram(ResourceLocation name, int handle) {
+		setHandle(handle);
+		this.name = name;
+	}
 
-    public static Builder builder(ResourceLocation name, GlFogMode fogMode) {
-        return new Builder(name, fogMode);
-    }
+	public static Builder builder(ResourceLocation name) {
+		return new Builder(name);
+	}
 
-    public void bind() {
-        GL20.glUseProgram(handle());
-    }
+	public void bind() {
+		GL20.glUseProgram(handle());
+	}
 
-    public void unbind() {
-        GL20.glUseProgram(0);
-    }
+	public void unbind() {
+		GL20.glUseProgram(0);
+	}
 
     /**
      * Retrieves the index of the uniform with the given name.
@@ -52,76 +55,78 @@ public abstract class GlProgram extends GlObject {
      * @return The sampler uniform's index.
      * @throws NullPointerException If no uniform exists with the given name.
      */
-    public int setSamplerBinding(String name, int binding) {
-        int samplerUniform = getUniformLocation(name);
+	public int setSamplerBinding(String name, int binding) {
+		int samplerUniform = getUniformLocation(name);
 
-        if (samplerUniform >= 0) {
-            GL20.glUniform1i(samplerUniform, binding);
-        }
+		if (samplerUniform >= 0) {
+			GL20.glUniform1i(samplerUniform, binding);
+		}
 
-        return samplerUniform;
-    }
+		return samplerUniform;
+	}
 
-    @Override
-    protected void deleteInternal(int handle) {
-        GL20.glDeleteProgram(handle);
-    }
+	protected static void uploadMatrixUniform(int uniform, Matrix4f mat) {
+		GL20.glUniformMatrix4fv(uniform, false, RenderUtil.writeMatrix(mat));
+	}
 
-    public static class Builder {
-        private final ResourceLocation name;
-        private final int program;
-        private final GlFogMode fogMode;
+	@Override
+	protected void deleteInternal(int handle) {
+		GL20.glDeleteProgram(handle);
+	}
 
-        private int attributeIndex;
+	public static class Builder {
+		public final ResourceLocation name;
+		public final int program;
 
-        public Builder(ResourceLocation name, GlFogMode fogMode) {
-            this.name = name;
-            this.program = GL20.glCreateProgram();
-            this.fogMode = fogMode;
-        }
+		private int attributeIndex;
 
-        public Builder attachShader(GlShader shader) {
-            GL20.glAttachShader(this.program, shader.handle());
+		public Builder(ResourceLocation name) {
+			this.name = name;
+			this.program = GL20.glCreateProgram();
+		}
 
-            return this;
-        }
+		public Builder attachShader(GlShader shader) {
+			GL20.glAttachShader(this.program, shader.handle());
 
-        public <A extends IVertexAttrib> Builder addAttribute(A attrib) {
-            GL20.glBindAttribLocation(this.program, attributeIndex, attrib.attribName());
-            attributeIndex += attrib.attribSpec().getAttributeCount();
-            return this;
-        }
+			return this;
+		}
 
-        /**
-         * Links the attached shaders to this program and returns a user-defined container which wraps the shader
-         * program. This container can, for example, provide methods for updating the specific uniforms of that shader
-         * set.
-         *
-         * @param factory The factory which will create the shader program's container
-         * @param <P> The type which should be instantiated with the new program's handle
-         * @return An instantiated shader container as provided by the factory
-         */
-        public <P extends GlProgram> P build(ProgramFactory<P> factory) {
-            GL20.glLinkProgram(this.program);
+		public <A extends IVertexAttrib> Builder addAttributes(Collection<A> attributes) {
+			attributes.forEach(this::addAttribute);
+			return this;
+		}
 
-            String log = GL20.glGetProgramInfoLog(this.program);
+		public <A extends IVertexAttrib> Builder addAttribute(A attrib) {
+			GL20.glBindAttribLocation(this.program, attributeIndex, attrib.attribName());
+			attributeIndex += attrib.attribSpec().getAttributeCount();
+			return this;
+		}
 
-            if (!log.isEmpty()) {
-                Backend.log.debug("Program link log for " + this.name + ": " + log);
-            }
+		/**
+		 * Links the attached shaders to this program and returns a user-defined container which wraps the shader
+		 * program. This container can, for example, provide methods for updating the specific uniforms of that shader
+		 * set.
+		 *
+		 * @param factory The factory which will create the shader program's container
+		 * @param <P>     The type which should be instantiated with the new program's handle
+		 * @return An instantiated shader container as provided by the factory
+		 */
+		public Builder link() {
+			GL20.glLinkProgram(this.program);
 
-            int result = GL20.glGetProgrami(this.program, GL20.GL_LINK_STATUS);
+			String log = GL20.glGetProgramInfoLog(this.program);
 
-            if (result != GL20.GL_TRUE) {
-                throw new RuntimeException("Shader program linking failed, see log for details");
-            }
+			if (!log.isEmpty()) {
+				Backend.log.debug("Program link log for " + this.name + ": " + log);
+			}
 
-            return factory.create(this.name, this.program, this.fogMode.getFogFactory());
-        }
-    }
+			int result = GL20.glGetProgrami(this.program, GL20.GL_LINK_STATUS);
 
-    @FunctionalInterface
-    public interface ProgramFactory<P extends GlProgram> {
-        P create(ResourceLocation name, int handle, ProgramFogMode.Factory fogFactory);
-    }
+			if (result != GL20.GL_TRUE) {
+				throw new RuntimeException("Shader program linking failed, see log for details");
+			}
+
+			return this;
+		}
+	}
 }
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/ProgramSpec.java b/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/ProgramSpec.java
index 26c3b0eb2..33c891517 100644
--- a/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/ProgramSpec.java
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/ProgramSpec.java
@@ -16,54 +16,42 @@ public class ProgramSpec<P extends GlProgram> {
 
     public final ShaderConstants defines;
 
-    public final GlProgram.ProgramFactory<P> factory;
-
     public final ArrayList<IVertexAttrib> attributes;
 
-    public final boolean fogSensitive;
+    public final ShaderSpecLoader<P> finalizer;
 
-    public static <P extends GlProgram> Builder<P> builder(String name, GlProgram.ProgramFactory<P> factory) {
-        return builder(new ResourceLocation(Create.ID, name), factory);
-    }
-
-    public static <P extends GlProgram> Builder<P> builder(ResourceLocation name, GlProgram.ProgramFactory<P> factory) {
-        return new Builder<>(name, factory);
-    }
-
-    public ProgramSpec(ResourceLocation name, ResourceLocation vert, ResourceLocation frag, GlProgram.ProgramFactory<P> factory, ShaderConstants defines, ArrayList<IVertexAttrib> attributes, boolean fogSensitive) {
-        this.name = name;
-        this.vert = vert;
-        this.frag = frag;
-        this.defines = defines;
-
-        this.factory = factory;
-        this.attributes = attributes;
-		this.fogSensitive = fogSensitive;
+	public static <P extends GlProgram> Builder<P> builder(String name, ShaderSpecLoader<P> factory) {
+		return builder(new ResourceLocation(Create.ID, name), factory);
 	}
 
-    public ResourceLocation getVert() {
-        return vert;
-    }
+	public static <P extends GlProgram> Builder<P> builder(ResourceLocation name, ShaderSpecLoader<P> factory) {
+		return new Builder<>(name, factory);
+	}
 
-    public ResourceLocation getFrag() {
-        return frag;
-    }
+	public ProgramSpec(ResourceLocation name, ResourceLocation vert, ResourceLocation frag, ShaderConstants defines, ArrayList<IVertexAttrib> attributes, ShaderSpecLoader<P> finalizer) {
+		this.name = name;
+		this.vert = vert;
+		this.frag = frag;
+		this.defines = defines;
+
+		this.attributes = attributes;
+		this.finalizer = finalizer;
+	}
 
     public static class Builder<P extends GlProgram> {
         private ResourceLocation vert;
         private ResourceLocation frag;
-        private ShaderConstants defines = ShaderConstants.EMPTY;
-        private boolean fogSensitive = true;
+		private ShaderConstants defines = ShaderConstants.EMPTY;
+		private final ShaderSpecLoader<P> loader;
 
         private final ResourceLocation name;
-        private final GlProgram.ProgramFactory<P> factory;
         private final ArrayList<IVertexAttrib> attributes;
 
-        public Builder(ResourceLocation name, GlProgram.ProgramFactory<P> factory) {
-            this.name = name;
-            this.factory = factory;
-            attributes = new ArrayList<>();
-        }
+		public Builder(ResourceLocation name, ShaderSpecLoader<P> factory) {
+			this.name = name;
+			this.loader = factory;
+			attributes = new ArrayList<>();
+		}
 
         public Builder<P> setVert(ResourceLocation vert) {
             this.vert = vert;
@@ -80,18 +68,14 @@ public class ProgramSpec<P extends GlProgram> {
             return this;
         }
 
-		public Builder<P> setFogSensitive(boolean fogSensitive) {
-			this.fogSensitive = fogSensitive;
-			return this;
-		}
-
 		public <A extends Enum<A> & IVertexAttrib> Builder<P> addAttributes(Class<A> attributeEnum) {
             attributes.addAll(Arrays.asList(attributeEnum.getEnumConstants()));
             return this;
         }
 
         public ProgramSpec<P> createProgramSpec() {
-            return new ProgramSpec<>(name, vert, frag, factory, defines, attributes, fogSensitive);
+			return new ProgramSpec<>(name, vert, frag, defines, attributes, loader);
         }
     }
+
 }
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/ShaderSpecLoader.java b/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/ShaderSpecLoader.java
new file mode 100644
index 000000000..3747a572c
--- /dev/null
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/ShaderSpecLoader.java
@@ -0,0 +1,7 @@
+package com.simibubi.create.foundation.render.backend.gl.shader;
+
+import com.simibubi.create.foundation.render.backend.ShaderLoader;
+
+public interface ShaderSpecLoader<P extends GlProgram> {
+	IMultiProgram<P> create(ShaderLoader loader, ProgramSpec<P> spec);
+}
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/SingleProgram.java b/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/SingleProgram.java
index c3ee700ab..832e6d8e3 100644
--- a/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/SingleProgram.java
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/gl/shader/SingleProgram.java
@@ -1,5 +1,9 @@
 package com.simibubi.create.foundation.render.backend.gl.shader;
 
+import com.simibubi.create.foundation.render.backend.ShaderLoader;
+
+import net.minecraft.util.ResourceLocation;
+
 public class SingleProgram<P extends GlProgram> implements IMultiProgram<P> {
 	final P program;
 
@@ -16,4 +20,24 @@ public class SingleProgram<P extends GlProgram> implements IMultiProgram<P> {
 	public void delete() {
 		program.delete();
 	}
+
+	public static class SpecLoader<P extends GlProgram> implements ShaderSpecLoader<P> {
+		final ProgramFactory<P> factory;
+
+		public SpecLoader(ProgramFactory<P> factory) {
+			this.factory = factory;
+		}
+
+		@Override
+		public IMultiProgram<P> create(ShaderLoader loader, ProgramSpec<P> spec) {
+			GlProgram.Builder builder = loader.loadProgram(spec);
+
+			return new SingleProgram<>(factory.create(builder.name, builder.program));
+		}
+	}
+
+	@FunctionalInterface
+	public interface ProgramFactory<P extends GlProgram> {
+		P create(ResourceLocation name, int handle);
+	}
 }
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/instancing/InstancedModel.java b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/InstancedModel.java
index fc6be1b40..10f182d46 100644
--- a/src/main/java/com/simibubi/create/foundation/render/backend/instancing/InstancedModel.java
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/InstancedModel.java
@@ -182,7 +182,7 @@ public abstract class InstancedModel<D extends InstanceData> extends BufferedMod
         int requiredSize = size * stride;
         if (requiredSize > glBufferSize) {
             glBufferSize = requiredSize + stride * 16;
-            GL15.glBufferData(instanceVBO.getBufferType(), glBufferSize, GL15.GL_STATIC_DRAW);
+			instanceVBO.alloc(glBufferSize, GL15.GL_STATIC_DRAW);
 
             instanceVBO.map(glBufferSize, buffer -> {
                 for (D datum : data) {
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/instancing/InstancedTileRenderer.java b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/InstancedTileRenderer.java
index a7bdc1dcc..fd080bf7d 100644
--- a/src/main/java/com/simibubi/create/foundation/render/backend/instancing/InstancedTileRenderer.java
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/InstancedTileRenderer.java
@@ -9,9 +9,9 @@ import javax.annotation.Nullable;
 import com.simibubi.create.foundation.render.backend.Backend;
 import com.simibubi.create.foundation.render.backend.MaterialType;
 import com.simibubi.create.foundation.render.backend.MaterialTypes;
+import com.simibubi.create.foundation.render.backend.core.BasicProgram;
 import com.simibubi.create.foundation.render.backend.core.ModelData;
 import com.simibubi.create.foundation.render.backend.core.OrientedData;
-import com.simibubi.create.foundation.render.backend.gl.BasicProgram;
 import com.simibubi.create.foundation.render.backend.gl.shader.ShaderCallback;
 
 import net.minecraft.client.Minecraft;
diff --git a/src/main/java/com/simibubi/create/foundation/render/backend/instancing/RenderMaterial.java b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/RenderMaterial.java
index f2f3e1bc7..a56770802 100644
--- a/src/main/java/com/simibubi/create/foundation/render/backend/instancing/RenderMaterial.java
+++ b/src/main/java/com/simibubi/create/foundation/render/backend/instancing/RenderMaterial.java
@@ -19,7 +19,7 @@ import com.simibubi.create.foundation.render.Compartment;
 import com.simibubi.create.foundation.render.SuperByteBufferCache;
 import com.simibubi.create.foundation.render.backend.Backend;
 import com.simibubi.create.foundation.render.backend.FastRenderDispatcher;
-import com.simibubi.create.foundation.render.backend.gl.BasicProgram;
+import com.simibubi.create.foundation.render.backend.core.BasicProgram;
 import com.simibubi.create.foundation.render.backend.gl.shader.ProgramSpec;
 import com.simibubi.create.foundation.render.backend.gl.shader.ShaderCallback;
 
diff --git a/src/main/resources/assets/create/flywheel/shaders/chromatic.frag b/src/main/resources/assets/create/flywheel/shaders/chromatic.frag
index 7a1546907..bd70e8d22 100644
--- a/src/main/resources/assets/create/flywheel/shaders/chromatic.frag
+++ b/src/main/resources/assets/create/flywheel/shaders/chromatic.frag
@@ -1,13 +1,62 @@
 #version 120
 
-layout (std140) struct Sphere {
-    vec4 positionRadius;
-    vec4 color;
-} uSpheres;
+varying vec4 Vertex;
+varying vec3 CameraDir;
+
+//layout (std140) struct Sphere {
+//    vec4 positionRadius;
+//    vec4 color;
+//} uSphere;
 
 uniform sampler2D uDepth;
 uniform sampler2D uColor;
+uniform mat4 uInverseProjection;
+uniform mat4 uInverseView;
+
+uniform float uNearPlane = 0.15;
+uniform float uFarPlane = 1;
+uniform vec3 uSphereCenter = vec3(0, 0, 0);
+uniform float uSphereRadius = 1;
+uniform float uSphereFeather = 0.05;
+
+float linearizeDepth(float d, float zNear, float zFar) {
+    float z_n = 2.0 * d - 1.0;
+    return 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear));
+}
+
+vec4 filterColor(vec4 frag) {
+    const vec3 lum = vec3(0.21, 0.71, 0.07);
+    float grey = dot(frag.rgb, lum.rgb);
+    return vec4(grey, grey, grey, frag.a);
+}
+
+vec3 getWorldPos(float depth) {
+    vec3 cameraPos = CameraDir * depth;
+
+    vec3 worldPos = (uInverseView * vec4(cameraPos, 1)).xyz;
+
+    return worldPos;
+}
+
+float getDepth() {
+    float depth = texture2D(uDepth, Vertex.zw).r;
+
+    depth = linearizeDepth(depth, uNearPlane, uFarPlane);
+    //depth = ( - uNearPlane) / (uFarPlane - uNearPlane);
+    depth = depth / uFarPlane;
+
+    return depth;
+}
 
 void main() {
-    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+    float depth = getDepth();
+    vec3 worldPos = getWorldPos(depth);
+
+    float distance = distance(uSphereCenter, worldPos);
+    float strength = smoothstep(uSphereRadius - uSphereFeather, uSphereRadius + uSphereFeather, distance);
+
+    vec4 fragColor = texture2D(uColor, Vertex.zw);
+
+    gl_FragColor = mix(fragColor, filterColor(fragColor), strength);
+    //gl_FragColor = vec4(worldPos, 1);
 }
diff --git a/src/main/resources/assets/create/flywheel/shaders/screen_quad.vert b/src/main/resources/assets/create/flywheel/shaders/screen_quad.vert
new file mode 100644
index 000000000..248157475
--- /dev/null
+++ b/src/main/resources/assets/create/flywheel/shaders/screen_quad.vert
@@ -0,0 +1,20 @@
+#version 120
+
+attribute vec4 aVertex;// <vec2 position, vec2 texCoords>
+
+varying vec4 Vertex;
+varying vec3 CameraDir;
+
+uniform mat4 uInverseProjection;
+
+void main() {
+    gl_Position = vec4(aVertex.xy, 0.0f, 1.0f);
+    Vertex = aVertex;
+
+    vec4 clip = vec4(aVertex.xy, 0, 1);
+
+    clip *= uInverseProjection;
+
+    CameraDir = clip.xyz / clip.w;
+    //worldDirection = (uInverseProjection * vec4(aVertex.xy, 0, 1.)).xyz;
+}