如何在MC的游戏画面中画出原神的路径点图标?

分析

首先是对OpenGL绘图的一些小分析
OpenGL.jpg
注:笔者在图中忘记画z轴的偏移了

代码

java 复制代码
    ClientHandler.getTrackingWaypoints().forEach(waypoint -> {
        if (waypoint == null) return;

        /* 路径点距离标准化部分 **/
        Vec3d playerVec = renderManager.renderViewEntity.getPositionVector();
        Vec3d waypointVec = waypoint.getPosition().add(0, 0.118, 0);
        //距离
        double actualDistance = waypointVec.distanceTo(playerVec);
        String actualDistanceString = ((int) actualDistance) + "m";

        //如果超过最大渲染距离
        double maxRenderDistance = mc.gameSettings.renderDistanceChunks * 16;
        if (actualDistance > maxRenderDistance) {
            //调整路径点的位置为玩家视角方向最大渲染距离的位置
            //玩家视角 -> 路径点 的单位方向向量
            Vec3d directionVector = waypointVec.subtract(playerVec).normalize();
            //调整路径点位置为 最大渲染距离 · 单位方向向量
            waypointVec = playerVec.add(directionVector.x * maxRenderDistance, directionVector.y * maxRenderDistance, directionVector.z * maxRenderDistance);
        }

        /* OpenGL渲染部分 **/
        //路径点相对于摄像机坐标的坐标偏移
        double playerViewportOffsetX = waypointVec.x - renderManager.viewerPosX;
        double playerViewportOffsetY = waypointVec.y - renderManager.viewerPosY;
        double playerViewportOffsetZ = waypointVec.z - renderManager.viewerPosZ;

        //路径点在视口的缩放倍数
        //基准倍数
        double scale = MapConfig.navigateSize;
        if (!waypoint.isCustomWaypoint()) scale *= waypoint.navigateScale;
        //由距离决定的倍数 距离 : 倍数 为 1000m : 1
        scale *= Math.min(1d, (actualDistance / 1000d));

        // 文字 //
        GlStateManager.pushMatrix();
        //视口坐标系原点变换为路径点位置
        GlStateManager.translate(playerViewportOffsetX, playerViewportOffsetY, playerViewportOffsetZ);
        //使路径点随玩家的偏航、俯仰变化总显示在一个位置
        GlStateManager.rotate(-renderManager.playerViewY, 0.0f, 1.0f, 0.0f);
        GlStateManager.rotate(renderManager.playerViewX, 1.0f, 0.0f, 0.0f);
        //缩放, 再缩放5倍绘制文字, xy面绕z轴镜像对称以使路径点正对着玩家
        GlStateManager.scale(-scale * 5, -scale * 5, scale * 5);
        //绘出文字
        DrawUtil.drawLabel(
                actualDistanceString,
                1d, 12d,
                DrawUtil.HAlign.Center, DrawUtil.VAlign.Above,
                0, 0.4f,
                RGB.WHITE_RGB, 1f,
                1, false
        );
        GlStateManager.popMatrix();

        // 贴图 //
        GlStateManager.pushMatrix();
        GlStateManager.translate(playerViewportOffsetX, playerViewportOffsetY, playerViewportOffsetZ);
        GlStateManager.rotate(-renderManager.playerViewY, 0.0f, 1.0f, 0.0f);
        GlStateManager.rotate(renderManager.playerViewX, 1.0f, 0.0f, 0.0f);
        GlStateManager.scale(-scale, -scale, scale);
        double xDownLeft = -(waypoint.getWidth() / 2);
        double yDownLeft = -(waypoint.getHeight() / 2);
        RenderUtil.drawQuad(
                waypoint.getIconLoc(),
                RGB.WHITE_RGB, 1f,
                xDownLeft, yDownLeft, waypoint.getWidth(), waypoint.getHeight(),
                0d, 0d, 1d, 1d,
                0,false,
                true, 770, 771,
                false
        );
        //结束渲染,恢复上下文矩阵
        GlStateManager.popMatrix();
    });

    // OpenGL //
    //恢复状态
    GlStateManager.enableDepth();
    GlStateManager.enableLighting();
}

注:省略多边形和文字的绘制

代码(Blaze3D)

环境

  • MC 1.20.1
  • Forge 47.4.0
  • Kotlin For Forge
kotlin 复制代码
ClientHandler.trackingWaypoints.forEach { wp ->
    val viewX: Double = mc.entityRenderDispatcher.camera.position.x()
    val viewY: Double = mc.entityRenderDispatcher.camera.position.y()
    val viewZ: Double = mc.entityRenderDispatcher.camera.position.z()
    val shiftX: Double = wp.getX() - viewX
    val shiftY: Double = wp.getY() - viewY
    val shiftZ: Double = wp.getZ() - viewZ

    val waypointVec: Vec3 = wp.position.add(0.0, 0.118, 0.0)
    val playerVec: Vec3 = player.position()
    val actualDistance = playerVec.distanceTo(waypointVec)
    val viewDistance = actualDistance
    val label = "${actualDistance.toInt()}m"
    val scale = 0.00390625 * ((viewDistance + 4.0) / 3.0)
    poseStack.pushPose()
    poseStack.translate(shiftX, shiftY, shiftZ)
    poseStack.mulPose(Minecraft.getInstance().entityRenderDispatcher.camera.rotation())
    poseStack.scale((-scale).toFloat(), (-scale).toFloat(), scale.toFloat())
    val fontScale = 2f
    val alpha = 1f
    val safeColor = RGB.WHITE_RGB
    RenderUtil.drawBatchLabel(
        poseStack,
        Component.literal(label),
        buffers,
        1.0,
        40.0,
        DrawUtil.HAlign.Center,
        DrawUtil.VAlign.Above,
        -16777216,
        0f,
        safeColor,
        alpha,
        fontScale.toDouble(),
        false
    )
    val width: Double = wp.width
    val height: Double = wp.height
    val x = 0 - (width / 2)
    val y = 0 - (height / 2)
    RenderUtil.drawQuad(
        poseStack,
        wp.getIconLoc(),
        RGB.WHITE_RGB,
        1.0f,
        x,
        y,
        wp.width,
        wp.height,
        0.0,
        0.0,
        1.0,
        1.0,
        0.0,
        flip = false,
        blend = true,
        770,
        771,
        false
    )
    poseStack.popPose()
}

其中RenderUtil的drawBatchLabel为调用原版FontRenderer的实现,而drawQuad方法为

kotlin 复制代码
    /**
     * 绘制四边形(核心实现),处理纹理、变换和渲染状态
     * 封装了OpenGL底层渲染逻辑,支持复杂图形绘制
     *
     * @param poseStack 矩阵栈,用于坐标变换
     * 其他参数同drawQuad的GuiGraphics重载
     */
    fun drawQuad(
        poseStack: PoseStack,
        texture: ResourceLocation?,
        color: Int,
        alpha: Float,
        x: Double,
        y: Double,
        width: Double,
        height: Double,
        minU: Double,
        minV: Double,
        maxU: Double,
        maxV: Double,
        rotation: Double,
        flip: Boolean,
        blend: Boolean,
        glBlendSFactor: Int,
        glBlendDFactor: Int,
        clampTexture: Boolean
    ) {
        var alpha = alpha
        // 保存当前矩阵状态
        poseStack.pushPose()

        try {
            if (texture != null) {
                // 设置渲染状态:着色器、纹理、混合模式等
                RenderSystem.setShader { GameRenderer.getPositionTexShader() }
                RenderSystem.activeTexture(33984)  // 激活纹理单元0(GL_TEXTURE0)
                RenderSystem.setShaderTexture(0, texture)  // 绑定纹理
            }

            // 启用混合模式(若指定)
            if (blend) {
                RenderSystem.enableBlend()
                RenderSystem.blendFuncSeparate(glBlendSFactor, glBlendDFactor, 1, 0)
            }

            // 处理透明度(标准化到0.0-1.0范围)
            if (alpha > 1.0f) {
                alpha /= 255.0f
            }

            // 设置颜色(若启用混合,叠加颜色;否则使用白色)
            if (blend) {
                val c = RGB.floats(color)  // 解析颜色通道为0.0-1.0
                RenderSystem.setColor4f(c[0], c[1], c[2], alpha)
            } else {
                RenderSystem.setColor4f(1.0f, 1.0f, 1.0f, alpha)
            }

            // 配置纹理采样模式(clamping或重复)
            val texEdgeBehavior = if (clampTexture) 10496 else 10497  // GL_CLAMP_TO_EDGE=10496, GL_REPEAT=10497
            RenderSystem.texParameter(3553, 10242, texEdgeBehavior)  // GL_TEXTURE_WRAP_S
            RenderSystem.texParameter(3553, 10243, texEdgeBehavior)  // GL_TEXTURE_WRAP_T

            // 处理旋转(绕四边形中心旋转)
            if (rotation != 0.0) {
                val centerX = x + width / 2.0  // 计算中心X坐标
                val centerY = y + height / 2.0  // 计算中心Y坐标
                poseStack.translate(centerX, centerY, 0.0)  // 移动到中心
                poseStack.mulPose(Axis.ZP.rotationDegrees(rotation.toFloat()))  // 绕Z轴旋转
                poseStack.translate(-centerX, -centerY, 0.0)  // 移回原位置
            }

            // 处理纹理翻转(水平翻转时反转U坐标)
            val adjustedMaxU = if (flip) -maxU else maxU

            // 准备绘制:关闭纹理(临时),配置混合
            RenderSystem.disableTexture()
            RenderSystem.enableBlend()
            RenderSystem.blendFuncSeparate(770, 771, 1, 0)  // 默认混合模式(SRC_ALPHA, ONE_MINUS_SRC_ALPHA)

            // 创建顶点缓冲区,绘制四边形
            val tessellator = Tesselator.getInstance()
            val buffer = tessellator.builder
            buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX)

            // 添加四个顶点(顺时针顺序),包含位置和UV坐标
	    val matrix = poseStack.last().pose()
            buffer.vertex(matrix, x, height + y, zLevel, minU, maxV)
            buffer.vertex(matrix, x + width, height + y, zLevel, adjustedMaxU, maxV)
            buffer.vertex(matrix, x + width, y, zLevel, adjustedMaxU, minV)
            buffer.vertex(matrix, x, y, zLevel, minU, minV)

            // 完成绘制
            tessellator.end()

            // 恢复渲染状态
            RenderSystem.enableTexture()
            RenderSystem.enableBlend()
            if (blend) {
                RenderWrapper.setColor4f(1.0f, 1.0f, 1.0f, 1.0f)  // 重置颜色
                // 恢复默认混合模式(若传入的混合因子不同)
                if (glBlendSFactor != 770 || glBlendDFactor != 771) {
                    RenderSystem.enableBlend()
                    RenderSystem.blendFuncSeparate(770, 771, 1, 0)
                }
            }
        } finally {
            // 恢复矩阵状态
            poseStack.popPose()
        }
    }
游客

全部评论 (0)

暂无评论,快来抢沙发吧~