Начинающий
- Статус
- Оффлайн
- Регистрация
- 29 Мар 2026
- Сообщения
- 43
- Реакции
- 1
Нужные миксины:
Сам блюр:
MixinLevelRenderer.java:
package ru.motionreblur.mixin;
import net.minecraft.client.util.ObjectAllocator;
import net.minecraft.client.render.*;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Mixin;
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 ru.motionreblur.MotionBlurModule;
@Mixin(WorldRenderer.class)
public class MixinLevelRenderer {
@Unique
private Matrix4f prevModelView = new Matrix4f();
@Unique
private Matrix4f prevProjection = new Matrix4f();
@Unique
private Vector3f prevCameraPos = new Vector3f();
@Inject(method = "render", at = @At("HEAD"))
private void setMatrices(ObjectAllocator allocator, RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, Matrix4f positionMatrix, Matrix4f projectionMatrix, CallbackInfo ci) {
float tickDelta = tickCounter.getTickDelta(true);
float fov = ((GameRendererAccessor) gameRenderer).invokeGetFov(camera, tickDelta, true);
MotionBlurModule.getInstance().shader.setFrameMotionBlur(positionMatrix, prevModelView,
gameRenderer.getBasicProjectionMatrix(fov),
prevProjection,
new Vector3f(
(float) (camera.getPos().x % 30000f),
(float) (camera.getPos().y % 30000f),
(float) (camera.getPos().z % 30000f)
),
prevCameraPos
);
}
@Inject(method = "render", at = @At("RETURN"))
private void setOldMatrices(ObjectAllocator allocator, RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, Matrix4f positionMatrix, Matrix4f projectionMatrix, CallbackInfo ci) {
prevModelView = new Matrix4f(positionMatrix);
float tickDelta = tickCounter.getTickDelta(true);
float fov = ((GameRendererAccessor) gameRenderer).invokeGetFov(camera, tickDelta, true);
prevProjection = new Matrix4f(gameRenderer.getBasicProjectionMatrix(fov));
prevCameraPos = new Vector3f(
(float) (camera.getPos().x % 30000f),
(float) (camera.getPos().y % 30000f),
(float) (camera.getPos().z % 30000f)
);
}
}
GameRendererAccessor.java:
package ru.motionreblur.mixin;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.GameRenderer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(GameRenderer.class)
public interface GameRendererAccessor {
@Invoker("getFov")
float invokeGetFov(Camera camera, float tickDelta, boolean changingFov);
}
Сам блюр:
ShaderMotionBlur.java:
package ru.motionreblur;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Identifier;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.ladysnake.satin.api.managed.ManagedShaderEffect;
import org.ladysnake.satin.api.managed.ShaderEffectManager;
public class ShaderMotionBlur {
private final MotionBlurModule config;
private final ManagedShaderEffect motionBlurShader;
public ShaderMotionBlur(MotionBlurModule config) {
this.config = config;
motionBlurShader = ShaderEffectManager.getInstance().manage(
Identifier.of(MotionReBlur.MOD_ID, "motion_blur"),
shader -> shader.setUniformValue("BlendFactor", config.getStrength())
);
}
private long lastNano = System.nanoTime();
private float currentBlur = 0.0f;
private float currentFPS = 0.0f;
public void registerShaderCallbacks() {
WorldRenderEvents.END.register(context -> {
long now = System.nanoTime();
float deltaTime = (now - lastNano) / 1_000_000_000.0f;
float deltaTick = deltaTime * 20.0f;
lastNano = now;
if (deltaTime > 0 && deltaTime < 1.0f) {
currentFPS = 1.0f / deltaTime;
} else {
currentFPS = 0.0f;
}
if (shouldRenderMotionBlur()) {
applyMotionBlur(deltaTick);
}
});
}
private boolean shouldRenderMotionBlur() {
if (config.getStrength() == 0 || !config.isEnabled()) {
return false;
}
if (FabricLoader.getInstance().isModLoaded("iris")) {
MotionReBlur.LOGGER.warn("Motion Blur cannot work with Iris Shaders!");
config.setEnabled(false);
return false;
}
return true;
}
private void applyMotionBlur(float deltaTick) {
MinecraftClient client = MinecraftClient.getInstance();
MonitorInfoProvider.updateDisplayInfo();
int displayRefreshRate = MonitorInfoProvider.getRefreshRate();
float baseStrength = config.getStrength();
float scaledStrength = baseStrength;
if (config.isUseRRC()) {
float fpsOverRefresh = (displayRefreshRate > 0) ? currentFPS / displayRefreshRate : 1.0f;
if (fpsOverRefresh < 1.0f) fpsOverRefresh = 1.0f;
scaledStrength = baseStrength * fpsOverRefresh;
}
if (currentBlur != scaledStrength) {
motionBlurShader.setUniformValue("BlendFactor", scaledStrength);
currentBlur = scaledStrength;
}
int sampleAmount = getSampleAmountForFPS(currentFPS);
int halfSampleAmount = sampleAmount / 2;
float invSamples = 1.0f / sampleAmount;
motionBlurShader.setUniformValue("view_res", (float) client.getFramebuffer().viewportWidth, (float) client.getFramebuffer().viewportHeight);
motionBlurShader.setUniformValue("view_pixel_size", 1.0f / client.getFramebuffer().viewportWidth, 1.0f / client.getFramebuffer().viewportHeight);
motionBlurShader.setUniformValue("motionBlurSamples", sampleAmount);
motionBlurShader.setUniformValue("halfSamples", halfSampleAmount);
motionBlurShader.setUniformValue("inverseSamples", invSamples);
motionBlurShader.setUniformValue("blurAlgorithm", MotionBlurModule.BlurAlgorithm.CENTERED.ordinal());
motionBlurShader.render(deltaTick);
}
private int getSampleAmountForFPS(float fps) {
int quality = config.getQuality();
int baseSamples = switch (quality) {
case 0 -> 8;
case 1 -> 12;
case 2 -> 16;
case 3 -> 24;
default -> 12;
};
if (fps < 30) {
return Math.max(6, baseSamples / 2);
} else if (fps < 60) {
return Math.max(8, (int) (baseSamples * 0.75f));
} else if (fps > 144) {
return Math.min(32, (int) (baseSamples * 1.25f));
}
return baseSamples;
}
private final Matrix4f tempPrevModelView = new Matrix4f();
private final Matrix4f tempPrevProjection = new Matrix4f();
private final Matrix4f tempProjInverse = new Matrix4f();
private final Matrix4f tempMvInverse = new Matrix4f();
public void setFrameMotionBlur(Matrix4f modelView, Matrix4f prevModelView,
Matrix4f projection, Matrix4f prevProjection,
Vector3f cameraPos, Vector3f prevCameraPos) {
motionBlurShader.setUniformValue("mvInverse", tempMvInverse.set(modelView).invert());
motionBlurShader.setUniformValue("projInverse", tempProjInverse.set(projection).invert());
motionBlurShader.setUniformValue("prevModelView", tempPrevModelView.set(prevModelView));
motionBlurShader.setUniformValue("prevProjection", tempPrevProjection.set(prevProjection));
motionBlurShader.setUniformValue("cameraPos", cameraPos.x, cameraPos.y, cameraPos.z);
motionBlurShader.setUniformValue("prevCameraPos", prevCameraPos.x, prevCameraPos.y, prevCameraPos.z);
}
public void updateBlurStrength(float strength) {
motionBlurShader.setUniformValue("BlendFactor", strength);
currentBlur = strength;
}
}
MotionReBlur.java:
package ru.motionreblur;
import net.fabricmc.api.ClientModInitializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MotionReBlur implements ClientModInitializer {
public static final String MOD_ID = "motionreblur";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
@Override
public void onInitializeClient() {
LOGGER.info("Motion ReBlur initialized!");
MotionBlurModule.getInstance();
MotionBlurCommand.register();
MotionBlurKeyBinding.register();
}
}
MotionBlurModule.java:
package ru.motionreblur;
public class MotionBlurModule {
private static final MotionBlurModule instance = new MotionBlurModule();
public final ShaderMotionBlur shader;
private boolean enabled = false;
private float strength = -0.8f;
private boolean useRRC = true;
private int quality = 2;
private MotionBlurModule() {
shader = new ShaderMotionBlur(this);
shader.registerShaderCallbacks();
}
public static MotionBlurModule getInstance() {
return instance;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
MotionReBlur.LOGGER.info("Motion Blur " + (enabled ? "enabled" : "disabled"));
}
public float getStrength() {
return strength;
}
public void setStrength(float strength) {
this.strength = Math.max(-2.0f, Math.min(2.0f, strength));
shader.updateBlurStrength(this.strength);
MotionReBlur.LOGGER.info("Motion Blur strength set to " + this.strength);
}
public boolean isUseRRC() {
return useRRC;
}
public void setUseRRC(boolean useRRC) {
this.useRRC = useRRC;
MotionReBlur.LOGGER.info("Refresh Rate Scaling " + (useRRC ? "enabled" : "disabled"));
}
public int getQuality() {
return quality;
}
public void setQuality(int quality) {
this.quality = Math.max(0, Math.min(3, quality));
MotionReBlur.LOGGER.info("Motion Blur quality set to " + getQualityName());
}
public String getQualityName() {
return switch (quality) {
case 0 -> "Низкое";
case 1 -> "Среднее";
case 2 -> "Высокое";
case 3 -> "Ультра";
default -> "Среднее";
};
}
public enum BlurAlgorithm {BACKWARDS, CENTERED}
public static BlurAlgorithm blurAlgorithm = BlurAlgorithm.CENTERED;
}
MotionBlurKeyBinding.java:
package ru.motionreblur;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil;
import org.lwjgl.glfw.GLFW;
public class MotionBlurKeyBinding {
private static KeyBinding openGuiKey;
public static void register() {
openGuiKey = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key.motionreblur.open_gui",
InputUtil.Type.KEYSYM,
GLFW.GLFW_KEY_M,
"category.motionreblur"
));
ClientTickEvents.END_CLIENT_TICK.register(client -> {
while (openGuiKey.wasPressed()) {
client.setScreen(new MotionBlurConfigScreen(client.currentScreen));
}
});
}
}
MotionBlurConfigScreen.java:
package ru.motionreblur;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.SliderWidget;
import net.minecraft.text.Text;
public class MotionBlurConfigScreen extends Screen {
private final Screen parent;
private SliderWidget strengthSlider;
private ButtonWidget toggleButton;
private ButtonWidget rrcButton;
private ButtonWidget qualityButton;
public MotionBlurConfigScreen(Screen parent) {
super(Text.literal("Motion ReBlur Settings"));
this.parent = parent;
}
@Override
protected void init() {
MotionBlurModule mb = MotionBlurModule.getInstance();
int centerX = this.width / 2;
int startY = this.height / 2 - 60;
toggleButton = ButtonWidget.builder(
Text.literal("Motion Blur: " + (mb.isEnabled() ? "§aВКЛ" : "§cВЫКЛ")),
button -> {
mb.setEnabled(!mb.isEnabled());
button.setMessage(Text.literal("Motion Blur: " + (mb.isEnabled() ? "§aВКЛ" : "§cВЫКЛ")));
}
).dimensions(centerX - 100, startY, 200, 20).build();
strengthSlider = new SliderWidget(
centerX - 100, startY + 30, 200, 20,
Text.literal("Сила: " + String.format("%.1f", mb.getStrength())),
(mb.getStrength() + 2.0) / 4.0
) {
@Override
protected void updateMessage() {
double value = this.value * 4.0 - 2.0;
this.setMessage(Text.literal("Сила: " + String.format("%.1f", value)));
}
@Override
protected void applyValue() {
float value = (float) (this.value * 4.0 - 2.0);
mb.setStrength(value);
}
};
rrcButton = ButtonWidget.builder(
Text.literal("Адаптация к частоте монитора: " + (mb.isUseRRC() ? "§aВКЛ" : "§cВЫКЛ")),
button -> {
mb.setUseRRC(!mb.isUseRRC());
button.setMessage(Text.literal("Адаптация к частоте монитора: " + (mb.isUseRRC() ? "§aВКЛ" : "§cВЫКЛ")));
}
).dimensions(centerX - 100, startY + 60, 200, 20).build();
qualityButton = ButtonWidget.builder(
Text.literal("Качество: " + mb.getQualityName()),
button -> {
int newQuality = (mb.getQuality() + 1) % 4;
mb.setQuality(newQuality);
button.setMessage(Text.literal("Качество: " + mb.getQualityName()));
}
).dimensions(centerX - 100, startY + 90, 200, 20).build();
ButtonWidget doneButton = ButtonWidget.builder(
Text.translatable("gui.done"),
button -> this.close()
).dimensions(centerX - 100, startY + 120, 200, 20).build();
addDrawableChild(toggleButton);
addDrawableChild(strengthSlider);
addDrawableChild(rrcButton);
addDrawableChild(qualityButton);
addDrawableChild(doneButton);
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
this.renderBackground(context, mouseX, mouseY, delta);
super.render(context, mouseX, mouseY, delta);
context.drawCenteredTextWithShadow(
this.textRenderer,
this.title,
this.width / 2,
20,
0xFFFFFF
);
MotionBlurModule mb = MotionBlurModule.getInstance();
int refreshRate = MonitorInfoProvider.getRefreshRate();
String info = "Refresh Rate: " + refreshRate + " Hz";
context.drawCenteredTextWithShadow(
this.textRenderer,
Text.literal(info),
this.width / 2,
this.height / 2 + 80,
0x808080
);
if (mb.isEnabled()) {
String hint = "Двигайте камеру, чтобы увидеть эффект";
context.drawCenteredTextWithShadow(
this.textRenderer,
Text.literal(hint),
this.width / 2,
this.height / 2 + 95,
0x808080
);
}
}
@Override
public void close() {
if (this.client != null) {
this.client.setScreen(parent);
}
}
}
MotionBlurCommand.java:
package ru.motionreblur;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.context.CommandContext;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
public class MotionBlurCommand {
public static void register() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
registerCommand(dispatcher);
});
}
private static void registerCommand(CommandDispatcher<FabricClientCommandSource> dispatcher) {
dispatcher.register(ClientCommandManager.literal("motionreblur")
.executes(ctx -> openGui(ctx))
.then(ClientCommandManager.literal("on")
.executes(ctx -> enable(ctx)))
.then(ClientCommandManager.literal("off")
.executes(ctx -> disable(ctx)))
.then(ClientCommandManager.literal("toggle")
.executes(ctx -> toggle(ctx)))
.then(ClientCommandManager.literal("strength")
.then(ClientCommandManager.argument("value", FloatArgumentType.floatArg(-2.0f, 2.0f))
.executes(ctx -> setStrength(ctx, FloatArgumentType.getFloat(ctx, "value")))))
.then(ClientCommandManager.literal("rrc")
.executes(ctx -> toggleRRC(ctx)))
.then(ClientCommandManager.literal("status")
.executes(ctx -> showStatus(ctx)))
.then(ClientCommandManager.literal("help")
.executes(ctx -> showHelp(ctx)))
);
}
private static int openGui(CommandContext<FabricClientCommandSource> ctx) {
MinecraftClient client = MinecraftClient.getInstance();
client.execute(() -> {
client.setScreen(new MotionBlurConfigScreen(null));
});
ctx.getSource().sendFeedback(Text.literal("§aОткрываю меню настроек..."));
return 1;
}
private static int showStatus(CommandContext<FabricClientCommandSource> ctx) {
MotionBlurModule mb = MotionBlurModule.getInstance();
ctx.getSource().sendFeedback(Text.literal("§7§m "));
ctx.getSource().sendFeedback(Text.literal("§6Motion ReBlur"));
ctx.getSource().sendFeedback(Text.literal("§7Состояние: " + (mb.isEnabled() ? "§aВКЛ" : "§cВЫКЛ")));
ctx.getSource().sendFeedback(Text.literal("§7Сила: §f" + String.format("%.1f", mb.getStrength())));
ctx.getSource().sendFeedback(Text.literal("§7RRC: " + (mb.isUseRRC() ? "§aВКЛ" : "§cВЫКЛ")));
ctx.getSource().sendFeedback(Text.literal("§7Refresh Rate: §f" + MonitorInfoProvider.getRefreshRate() + " Hz"));
ctx.getSource().sendFeedback(Text.literal("§7§m "));
return 1;
}
private static int enable(CommandContext<FabricClientCommandSource> ctx) {
MotionBlurModule.getInstance().setEnabled(true);
ctx.getSource().sendFeedback(Text.literal("§aMotion Blur включен"));
return 1;
}
private static int disable(CommandContext<FabricClientCommandSource> ctx) {
MotionBlurModule.getInstance().setEnabled(false);
ctx.getSource().sendFeedback(Text.literal("§cMotion Blur выключен"));
return 1;
}
private static int toggle(CommandContext<FabricClientCommandSource> ctx) {
MotionBlurModule mb = MotionBlurModule.getInstance();
boolean newState = !mb.isEnabled();
mb.setEnabled(newState);
ctx.getSource().sendFeedback(Text.literal("§7Motion Blur: " + (newState ? "§aВКЛ" : "§cВЫКЛ")));
return 1;
}
private static int setStrength(CommandContext<FabricClientCommandSource> ctx, float value) {
MotionBlurModule.getInstance().setStrength(value);
ctx.getSource().sendFeedback(Text.literal("§aСила установлена на §f" + String.format("%.1f", value)));
return 1;
}
private static int toggleRRC(CommandContext<FabricClientCommandSource> ctx) {
MotionBlurModule mb = MotionBlurModule.getInstance();
boolean newState = !mb.isUseRRC();
mb.setUseRRC(newState);
ctx.getSource().sendFeedback(Text.literal("§7Адаптация к частоте монитора: " + (newState ? "§aВКЛ" : "§cВЫКЛ")));
return 1;
}
private static int showHelp(CommandContext<FabricClientCommandSource> ctx) {
ctx.getSource().sendFeedback(Text.literal("§7§m "));
ctx.getSource().sendFeedback(Text.literal("§6Motion ReBlur - Команды"));
ctx.getSource().sendFeedback(Text.literal("§7§m "));
ctx.getSource().sendFeedback(Text.literal("§e/motionreblur §7- открыть GUI"));
ctx.getSource().sendFeedback(Text.literal("§e/motionreblur on §7- включить"));
ctx.getSource().sendFeedback(Text.literal("§e/motionreblur off §7- выключить"));
ctx.getSource().sendFeedback(Text.literal("§e/motionreblur toggle §7- переключить"));
ctx.getSource().sendFeedback(Text.literal("§e/motionreblur strength <значение> §7- установить силу"));
ctx.getSource().sendFeedback(Text.literal("§7 Диапазон: от -2.0 до 2.0"));
ctx.getSource().sendFeedback(Text.literal("§e/motionreblur rrc §7- переключить адаптацию к частоте"));
ctx.getSource().sendFeedback(Text.literal("§e/motionreblur status §7- показать статус"));
ctx.getSource().sendFeedback(Text.literal("§7§m "));
return 1;
}
}
MonitorInfoProvider.java:
package ru.motionreblur;
import net.minecraft.client.MinecraftClient;
import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWVidMode;
public class MonitorInfoProvider {
private static long lastMonitorHandle = 0;
private static int lastRefreshRate = 60;
private static long lastCheckTime = 0;
private static final long CHECK_INTERVAL_NS = 1_000_000_000L;
public static void updateDisplayInfo() {
long now = System.nanoTime();
if (now - lastCheckTime < CHECK_INTERVAL_NS) {
return;
}
lastCheckTime = now;
MinecraftClient client = MinecraftClient.getInstance();
if (client == null || client.getWindow() == null) return;
long window = client.getWindow().getHandle();
long monitor = GLFW.glfwGetWindowMonitor(window);
if (monitor == 0) {
monitor = getMonitorFromWindowPosition(window, client.getWindow().getWidth(), client.getWindow().getHeight());
}
if (monitor != lastMonitorHandle) {
lastRefreshRate = detectRefreshRateFromMonitor(monitor);
lastMonitorHandle = monitor;
}
}
public static int getRefreshRate() {
return lastRefreshRate;
}
private static long getMonitorFromWindowPosition(long window, int windowWidth, int windowHeight) {
int[] winX = new int[1];
int[] winY = new int[1];
GLFW.glfwGetWindowPos(window, winX, winY);
int windowCenterX = winX[0] + windowWidth / 2;
int windowCenterY = winY[0] + windowHeight / 2;
long monitorResult = GLFW.glfwGetPrimaryMonitor();
PointerBuffer monitors = GLFW.glfwGetMonitors();
if (monitors != null) {
for (int i = 0; i < monitors.limit(); i++) {
long m = monitors.get(i);
int[] mx = new int[1];
int[] my = new int[1];
GLFW.glfwGetMonitorPos(m, mx, my);
GLFWVidMode mode = GLFW.glfwGetVideoMode(m);
if (mode == null) continue;
int mw = mode.width();
int mh = mode.height();
if (windowCenterX >= mx[0] && windowCenterX < mx[0] + mw &&
windowCenterY >= my[0] && windowCenterY < my[0] + mh) {
monitorResult = m;
break;
}
}
}
return monitorResult;
}
private static int detectRefreshRateFromMonitor(long monitor) {
GLFWVidMode vidMode = GLFW.glfwGetVideoMode(monitor);
return (vidMode != null) ? vidMode.refreshRate() : 60;
}
}