分析
首先是对OpenGL绘图的一些小分析

注:笔者在图中忘记画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)
暂无评论,快来抢沙发吧~