From 2c6a1e30666f17f70f83c26ce35f5e7aa22469be Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Sun, 30 Mar 2025 21:03:19 +0200 Subject: [PATCH 1/3] Add the cposteffect command --- .../clientcommands/ClientCommands.java | 1 + .../command/PostEffectCommand.java | 32 ++++++++++ .../command/arguments/PostChainArgument.java | 60 +++++++++++++++++++ .../assets/clientcommands/lang/en_us.json | 4 ++ src/main/resources/clientcommands.aw | 4 ++ 5 files changed, 101 insertions(+) create mode 100644 src/main/java/net/earthcomputer/clientcommands/command/PostEffectCommand.java create mode 100644 src/main/java/net/earthcomputer/clientcommands/command/arguments/PostChainArgument.java diff --git a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java index a7febe05e..a28d9b33d 100644 --- a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java +++ b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java @@ -168,6 +168,7 @@ public static void registerCommands(CommandDispatcher // PlayerInfoCommand.register(dispatcher); PluginsCommand.register(dispatcher); PosCommand.register(dispatcher); + PostEffectCommand.register(dispatcher); RelogCommand.register(dispatcher); RenderCommand.register(dispatcher); ReplyCommand.register(dispatcher); diff --git a/src/main/java/net/earthcomputer/clientcommands/command/PostEffectCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/PostEffectCommand.java new file mode 100644 index 000000000..7e4dd09db --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/command/PostEffectCommand.java @@ -0,0 +1,32 @@ +package net.earthcomputer.clientcommands.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +import static net.earthcomputer.clientcommands.command.arguments.PostChainArgument.*; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; + +public class PostEffectCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(literal("cposteffect") + .then(argument("posteffect", postChain()) + .executes(ctx -> applyPostEffect(ctx.getSource(), getPostChain(ctx, "posteffect")))) + .then(literal("reset") + .executes(ctx -> applyPostEffect(ctx.getSource(), null)))); + } + + private static int applyPostEffect(FabricClientCommandSource source, @Nullable ResourceLocation postEffect) { + if (postEffect == null) { + source.getClient().gameRenderer.clearPostEffect(); + source.sendFeedback(Component.translatable("commands.cposteffect.reset.success")); + return Command.SINGLE_SUCCESS; + } + source.getClient().gameRenderer.setPostEffect(postEffect); + source.sendFeedback(Component.translatable("commands.cposteffect.apply.success", postEffect)); + return Command.SINGLE_SUCCESS; + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/command/arguments/PostChainArgument.java b/src/main/java/net/earthcomputer/clientcommands/command/arguments/PostChainArgument.java new file mode 100644 index 000000000..61e3b611e --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/command/arguments/PostChainArgument.java @@ -0,0 +1,60 @@ +package net.earthcomputer.clientcommands.command.arguments; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +public class PostChainArgument implements ArgumentType { + + private static final Collection EXAMPLES = Arrays.asList("invert", "minecraft:spider", "minecraft:creeper"); + + private static final DynamicCommandExceptionType UNKNOWN_POST_CHAIN_EXCEPTION = new DynamicCommandExceptionType(postChain -> Component.translatable("commands.cposteffect.unknownPostEffect", postChain)); + + // known working post chains, "minecraft:entity_outline" and "minecraft:transparency" also exist but do not work directly + // see assets/minecraft/post_effect for all post chains + // perhaps this can be extracted from Minecraft.getInstance().getShaderManager().compilationCache.configs.postChains() + private static final Set SUPPORTED_POST_CHAINS = Set.of(GameRenderer.BLUR_POST_CHAIN_ID, ResourceLocation.withDefaultNamespace("creeper"), ResourceLocation.withDefaultNamespace("invert"), ResourceLocation.withDefaultNamespace("spider")); + + public static PostChainArgument postChain() { + return new PostChainArgument(); + } + + public static ResourceLocation getPostChain(final CommandContext context, final String name) { + return context.getArgument(name, ResourceLocation.class); + } + + @Override + public ResourceLocation parse(StringReader reader) throws CommandSyntaxException { + int start = reader.getCursor(); + ResourceLocation postChainId = ResourceLocation.read(reader); + if (!SUPPORTED_POST_CHAINS.contains(postChainId)) { + reader.setCursor(start); + throw UNKNOWN_POST_CHAIN_EXCEPTION.createWithContext(reader, postChainId); + } + return postChainId; + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return SharedSuggestionProvider.suggestResource(SUPPORTED_POST_CHAINS, builder); + } + + @Override + public Collection getExamples() { + return EXAMPLES; + } +} diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index 67ffbdb55..d1349c087 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -226,6 +226,10 @@ "commands.cpos.level.the_end": "the End", "commands.cpos.level.the_nether": "the Nether", + "commands.cposteffect.apply.success": "Successfully applied post effect %s", + "commands.cposteffect.reset.success":"Successfully reset post effect", + "commands.cposteffect.unknownPostEffect": "Unknown post effect %s", + "commands.crelog.failed": "Failed to relog", "commands.crender.entities.success": "Entity rendering rules have been updated", diff --git a/src/main/resources/clientcommands.aw b/src/main/resources/clientcommands.aw index 23a2ec783..003c8e3e5 100644 --- a/src/main/resources/clientcommands.aw +++ b/src/main/resources/clientcommands.aw @@ -41,6 +41,10 @@ accessible field net/minecraft/network/codec/IdDispatchCodec toId Lit/unimi/dsi/ # cpermissionlevel accessible method net/minecraft/client/player/LocalPlayer getPermissionLevel ()I +# cposteffect +accessible field net/minecraft/client/renderer/GameRenderer BLUR_POST_CHAIN_ID Lnet/minecraft/resources/ResourceLocation; +accessible method net/minecraft/client/renderer/GameRenderer setPostEffect (Lnet/minecraft/resources/ResourceLocation;)V + # cwaypoint accessible field net/minecraft/server/MinecraftServer storageSource Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess; accessible method net/minecraft/client/renderer/GameRenderer getFov (Lnet/minecraft/client/Camera;FZ)F From 44e6d8f26ec8b7135b2841b7c6682b22ae8a1cb3 Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Sun, 30 Mar 2025 21:21:30 +0200 Subject: [PATCH 2/3] Fix space --- src/main/resources/assets/clientcommands/lang/en_us.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index d1349c087..6e057a4b1 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -227,7 +227,7 @@ "commands.cpos.level.the_nether": "the Nether", "commands.cposteffect.apply.success": "Successfully applied post effect %s", - "commands.cposteffect.reset.success":"Successfully reset post effect", + "commands.cposteffect.reset.success": "Successfully reset post effect", "commands.cposteffect.unknownPostEffect": "Unknown post effect %s", "commands.crelog.failed": "Failed to relog", From 09630d539c94fe252973a4b0e0f0b9a4469ebe28 Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Tue, 11 Nov 2025 16:22:52 +0100 Subject: [PATCH 3/3] De-hardcode post effects + add custom CRT-like post effect --- .../command/PostEffectCommand.java | 6 +- .../command/arguments/PostChainArgument.java | 60 ---------------- .../command/arguments/PostEffectArgument.java | 69 +++++++++++++++++++ .../clientcommands/post_effect/crt.json | 44 ++++++++++++ .../clientcommands/shaders/post/crt.fsh | 32 +++++++++ src/main/resources/clientcommands.aw | 2 + 6 files changed, 150 insertions(+), 63 deletions(-) delete mode 100644 src/main/java/net/earthcomputer/clientcommands/command/arguments/PostChainArgument.java create mode 100644 src/main/java/net/earthcomputer/clientcommands/command/arguments/PostEffectArgument.java create mode 100644 src/main/resources/assets/clientcommands/post_effect/crt.json create mode 100644 src/main/resources/assets/clientcommands/shaders/post/crt.fsh diff --git a/src/main/java/net/earthcomputer/clientcommands/command/PostEffectCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/PostEffectCommand.java index 7e4dd09db..14e6ee8fb 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/PostEffectCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/PostEffectCommand.java @@ -7,14 +7,14 @@ import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.Nullable; -import static net.earthcomputer.clientcommands.command.arguments.PostChainArgument.*; +import static net.earthcomputer.clientcommands.command.arguments.PostEffectArgument.*; import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; public class PostEffectCommand { public static void register(CommandDispatcher dispatcher) { dispatcher.register(literal("cposteffect") - .then(argument("posteffect", postChain()) - .executes(ctx -> applyPostEffect(ctx.getSource(), getPostChain(ctx, "posteffect")))) + .then(argument("posteffect", postEffect()) + .executes(ctx -> applyPostEffect(ctx.getSource(), getPostEffect(ctx, "posteffect")))) .then(literal("reset") .executes(ctx -> applyPostEffect(ctx.getSource(), null)))); } diff --git a/src/main/java/net/earthcomputer/clientcommands/command/arguments/PostChainArgument.java b/src/main/java/net/earthcomputer/clientcommands/command/arguments/PostChainArgument.java deleted file mode 100644 index 61e3b611e..000000000 --- a/src/main/java/net/earthcomputer/clientcommands/command/arguments/PostChainArgument.java +++ /dev/null @@ -1,60 +0,0 @@ -package net.earthcomputer.clientcommands.command.arguments; - -import com.mojang.brigadier.StringReader; -import com.mojang.brigadier.arguments.ArgumentType; -import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; -import com.mojang.brigadier.suggestion.Suggestions; -import com.mojang.brigadier.suggestion.SuggestionsBuilder; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.minecraft.client.renderer.GameRenderer; -import net.minecraft.commands.SharedSuggestionProvider; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Set; -import java.util.concurrent.CompletableFuture; - -public class PostChainArgument implements ArgumentType { - - private static final Collection EXAMPLES = Arrays.asList("invert", "minecraft:spider", "minecraft:creeper"); - - private static final DynamicCommandExceptionType UNKNOWN_POST_CHAIN_EXCEPTION = new DynamicCommandExceptionType(postChain -> Component.translatable("commands.cposteffect.unknownPostEffect", postChain)); - - // known working post chains, "minecraft:entity_outline" and "minecraft:transparency" also exist but do not work directly - // see assets/minecraft/post_effect for all post chains - // perhaps this can be extracted from Minecraft.getInstance().getShaderManager().compilationCache.configs.postChains() - private static final Set SUPPORTED_POST_CHAINS = Set.of(GameRenderer.BLUR_POST_CHAIN_ID, ResourceLocation.withDefaultNamespace("creeper"), ResourceLocation.withDefaultNamespace("invert"), ResourceLocation.withDefaultNamespace("spider")); - - public static PostChainArgument postChain() { - return new PostChainArgument(); - } - - public static ResourceLocation getPostChain(final CommandContext context, final String name) { - return context.getArgument(name, ResourceLocation.class); - } - - @Override - public ResourceLocation parse(StringReader reader) throws CommandSyntaxException { - int start = reader.getCursor(); - ResourceLocation postChainId = ResourceLocation.read(reader); - if (!SUPPORTED_POST_CHAINS.contains(postChainId)) { - reader.setCursor(start); - throw UNKNOWN_POST_CHAIN_EXCEPTION.createWithContext(reader, postChainId); - } - return postChainId; - } - - @Override - public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { - return SharedSuggestionProvider.suggestResource(SUPPORTED_POST_CHAINS, builder); - } - - @Override - public Collection getExamples() { - return EXAMPLES; - } -} diff --git a/src/main/java/net/earthcomputer/clientcommands/command/arguments/PostEffectArgument.java b/src/main/java/net/earthcomputer/clientcommands/command/arguments/PostEffectArgument.java new file mode 100644 index 000000000..7df07abad --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/command/arguments/PostEffectArgument.java @@ -0,0 +1,69 @@ +package net.earthcomputer.clientcommands.command.arguments; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.LevelTargetBundle; +import net.minecraft.client.renderer.PostChainConfig; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +public class PostEffectArgument implements ArgumentType { + + private static final Collection EXAMPLES = Arrays.asList("invert", "minecraft:spider", "clientcommands:crt"); + + private static final DynamicCommandExceptionType UNKNOWN_POST_EFFECT_EXCEPTION = new DynamicCommandExceptionType(postEffect -> Component.translatable("commands.cposteffect.unknownPostEffect", postEffect)); + + public static PostEffectArgument postEffect() { + return new PostEffectArgument(); + } + + public static ResourceLocation getPostEffect(final CommandContext context, final String name) { + return context.getArgument(name, ResourceLocation.class); + } + + @Override + public ResourceLocation parse(StringReader reader) throws CommandSyntaxException { + int start = reader.getCursor(); + ResourceLocation postEffectId = ResourceLocation.read(reader); + + boolean valid = getValidPostChains().anyMatch(id -> id.equals(postEffectId)); + if (!valid) { + reader.setCursor(start); + throw UNKNOWN_POST_EFFECT_EXCEPTION.createWithContext(reader, postEffectId); + } + return postEffectId; + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return SharedSuggestionProvider.suggestResource(getValidPostChains(), builder); + } + + @Override + public Collection getExamples() { + return EXAMPLES; + } + + private Stream getValidPostChains() { + return Minecraft.getInstance().getShaderManager().compilationCache.configs.postChains().entrySet().stream() + .filter(entry -> entry.getValue().passes().stream() + .flatMap(PostChainConfig.Pass::referencedTargets) + .filter(location -> !entry.getValue().internalTargets().containsKey(location)) + .allMatch(LevelTargetBundle.MAIN_TARGETS::contains)) + .map(Map.Entry::getKey); + } +} diff --git a/src/main/resources/assets/clientcommands/post_effect/crt.json b/src/main/resources/assets/clientcommands/post_effect/crt.json new file mode 100644 index 000000000..0cbb03589 --- /dev/null +++ b/src/main/resources/assets/clientcommands/post_effect/crt.json @@ -0,0 +1,44 @@ +{ + "targets": { + "swap": {} + }, + "passes": [ + { + "vertex_shader": "minecraft:core/screenquad", + "fragment_shader": "clientcommands:post/crt", + "inputs": [ + { + "sampler_name": "In", + "target": "minecraft:main" + } + ], + "output": "swap", + "uniforms": {} + }, + { + "vertex_shader": "minecraft:core/screenquad", + "fragment_shader": "minecraft:post/bits", + "inputs": [ + { + "sampler_name": "In", + "target": "swap" + } + ], + "output": "minecraft:main", + "uniforms": { + "BitsConfig": [ + { + "name": "Resolution", + "type": "float", + "value": 16.0 + }, + { + "name": "MosaicSize", + "type": "float", + "value": 4.0 + } + ] + } + } + ] +} diff --git a/src/main/resources/assets/clientcommands/shaders/post/crt.fsh b/src/main/resources/assets/clientcommands/shaders/post/crt.fsh new file mode 100644 index 000000000..2906b8e33 --- /dev/null +++ b/src/main/resources/assets/clientcommands/shaders/post/crt.fsh @@ -0,0 +1,32 @@ +#version 330 + +uniform sampler2D InSampler; + +in vec2 texCoord; + +layout(std140) uniform SamplerInfo { + vec2 OutSize; + vec2 InSize; +}; + +out vec4 fragColor; + +void main() { + vec2 oneTexel = 1.0 / InSize; + + float aberr = 0.002; + vec3 col; + col.r = texture(InSampler, texCoord + vec2(aberr, 0.0)).r; + col.g = texture(InSampler, texCoord).g; + col.b = texture(InSampler, texCoord - vec2(aberr, 0.0)).b; + + // Add scanlines + float scan = sin(texCoord.y * 800.0) * 0.1; // adjust frequency + col *= 1.0 - scan * 0.3; + + // Vignette + float vignette = smoothstep(1.2, 0.6, length(texCoord - 0.5)); + col *= vignette; + + fragColor = vec4(col, 1.0); +} diff --git a/src/main/resources/clientcommands.aw b/src/main/resources/clientcommands.aw index b5d54d0fa..cca158601 100644 --- a/src/main/resources/clientcommands.aw +++ b/src/main/resources/clientcommands.aw @@ -53,6 +53,8 @@ accessible method net/minecraft/client/player/LocalPlayer getPermissionLevel ()I # cposteffect accessible field net/minecraft/client/renderer/GameRenderer BLUR_POST_CHAIN_ID Lnet/minecraft/resources/ResourceLocation; accessible method net/minecraft/client/renderer/GameRenderer setPostEffect (Lnet/minecraft/resources/ResourceLocation;)V +accessible field net/minecraft/client/renderer/ShaderManager compilationCache Lnet/minecraft/client/renderer/ShaderManager$CompilationCache; +accessible field net/minecraft/client/renderer/ShaderManager$CompilationCache configs Lnet/minecraft/client/renderer/ShaderManager$Configs; # Game Options accessible field net/minecraft/client/OptionInstance value Ljava/lang/Object;