import com.mojang.blaze3d.pipeline.MainTarget; import com.mojang.blaze3d.platform.DisplayData; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.authlib.properties.PropertyMap; import net.minecraft.SharedConstants; import net.minecraft.client.Minecraft; import net.minecraft.client.User; import net.minecraft.client.main.GameConfig; import net.minecraft.server.Bootstrap; import org.lwjgl.glfw.GLFW; import org.lwjgl.opengl.GL11; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.net.Proxy; import java.nio.ByteBuffer; import java.util.Optional; import java.util.OptionalInt; /** * mcfold render probe —— 起 MC 渲染栈,渲一帧,读像素写 PNG。 * 全程:不连服、不登录、不进主循环、不 gameplay。 */ public class RenderProbe { public static void main(String[] args) throws Exception { String gameDir = arg(args, "--gameDir", "/home/newqqagent/mcview/instances/default"); String assets = arg(args, "--assets", "/home/newqqagent/mcview/store/vanilla-1.20.1/assets"); String assetIdx = arg(args, "--assetIndex", "1.20.1"); String out = arg(args, "--out", "/tmp/scaffold-frame.png"); int width = Integer.parseInt(arg(args, "--width", "854")); int height = Integer.parseInt(arg(args, "--height", "480")); SharedConstants.tryDetectVersion(); SharedConstants.enableDataFixerOptimizations(); Bootstrap.bootStrap(); File runDir = new File(gameDir); User user = new User("mcfold_cam", "0".repeat(32), "mcfold_cam", Optional.empty(), Optional.empty(), User.Type.LEGACY); PropertyMap props = new PropertyMap(); GameConfig cfg = new GameConfig( new GameConfig.UserData(user, props, props, Proxy.NO_PROXY), new DisplayData(width, height, OptionalInt.empty(), OptionalInt.empty(), false), new GameConfig.FolderData(runDir, new File(runDir, "resourcepacks"), new File(assets), assetIdx), new GameConfig.GameData(false, "mcfold", "release", false, false), new GameConfig.QuickPlayData(null, null, null, null) ); RenderSystem.initRenderThread(); RenderSystem.beginInitialization(); Minecraft mc = new Minecraft(cfg); RenderSystem.finishInitialization(); System.out.println("[probe] constructed. w=" + mc.getWindow().getWidth() + " h=" + mc.getWindow().getHeight()); // ★ 渲染一帧:照抄 Minecraft.render() 的实测序列(反编译 nout2/.../Minecraft.java:1110-1140) MainTarget target = (MainTarget) mc.getMainRenderTarget(); for (int frame = 0; frame < 3; frame++) { RenderSystem.clear(16640, Minecraft.ON_OSX); target.bindWrite(true); mc.gameRenderer.render(0.0F, 0L, true); target.unbindWrite(); target.blitToScreen(mc.getWindow().getWidth(), mc.getWindow().getHeight()); mc.getWindow().updateDisplay(); System.out.println("[probe] frame " + frame + " rendered"); } // ★ 读像素:从 GL 后台缓冲直接读(比截屏更可靠,且不依赖 X) GL11.glReadBuffer(GL11.GL_BACK); int w = mc.getWindow().getWidth(), h = mc.getWindow().getHeight(); ByteBuffer buf = org.lwjgl.BufferUtils.createByteBuffer(w * h * 4); GL11.glReadPixels(0, 0, w, h, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf); BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); // GL 原点在左下,PNG 在左上,需翻转 for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { int i = ((h - 1 - y) * w + x) * 4; int r = buf.get(i) & 0xFF, g = buf.get(i + 1) & 0xFF, b = buf.get(i + 2) & 0xFF; img.setRGB(x, y, (r << 16) | (g << 8) | b); } } ImageIO.write(img, "png", new File(out)); System.out.println("[probe] wrote " + out + " (" + w + "x" + h + ")"); // 统计非黑像素,证明真的画了东西(而非空缓冲) long nonBlack = 0; int total = w * h; for (int p : img.getRGB(0, 0, w, h, null, 0, w)) if ((p & 0xFFFFFF) != 0) nonBlack++; System.out.println("[probe] non-black pixels = " + nonBlack + "/" + total + " (" + (100.0 * nonBlack / total) + "%)"); GLFW.glfwTerminate(); System.out.println("[probe] DONE"); System.exit(0); } private static String arg(String[] a, String key, String def) { for (int i = 0; i < a.length - 1; i++) if (a[i].equals(key)) return a[i + 1]; return def; } }