我的世界Java版1.21.4的Fabric模组开发教程(十五)方块实体渲染器
这是适用于Minecraft Java版1.21.4的Fabric模组开发系列教程专栏第十五章——方块实体渲染器。想要阅读其他内容,请查看或订阅上面的专栏。
当简单的方块实体不能满足方块的视觉需求时,方块实体渲染器(Block Entity Renderers) 便可以为方块渲染自定义内容。例如,我们可将文字、图像或图形等内容渲染在方块上,或者说以方块为基准,将内容渲染在方块周围。渲染的内容虽然依赖方块实体对应的渲染器,但这与方块模型无关,即使方块模型出现问题,渲染在方块上的内容依然可以正常显示,且始终渲染在方块模型上层。
本章将承接上一章节的内容。将光源方块(light_block) 的光照等级数字垂直地渲染在方块正上方。相关内容可以参考我的世界Java版1.21.4的Fabric模组开发教程(十四)方块实体。
完成上一章节中创建方块实体的步骤后,继续完成下列步骤来借助方块实体渲染器渲染内容:
- 配置客户端项目(可选);
- 创建方块实体渲染器类;
- 注册自定义方块实体渲染器;
- 渲染文字;
当然,要确保已经掌握了创建方块实体的前置知识。
配置客户端项目(可选)
这是本教程首次开始在客户端项目中编写代码,因此从本章开始,仅仅在服务端完成编码已经不能满足部分模组开发的需求,尤其涉及到实体、游戏画面和GUI等内容的渲染时;
通常,所有关于渲染的逻辑代码都应当编写在客户端模块中。如果项目没有将客户端和服务端分离,需要先配置客户端项目才能开始创建方块实体渲染器类;
如果项目已经将客户端与服务端分离成两个模块,则可以跳过本节。
1.首先,需要创建com/example/test/client
目录,用于存放所有客户端文件;
2.然后在client
文件夹中创建TestClient.java
,类名可以为当前模组Id+“Client”。使其继承ClientModInitializer
类,作为当前项目客户端的入口点类,同时实现onInitializeClient()
方法。
public class TestClient implements ClientModInitializer {@Overridepublic void onInitializeClient() {}
}
创建方块实体渲染器类
想要开始渲染内容,首先需要创建一个方块实体渲染器类,然后在其中添加渲染内容的逻辑。一般,创建方块实体渲染器类需要继承BlockEntityRenderer
接口,且在此之前需要完成方块和方块实体类的创建;
在开始前,需要掌握大量有关渲染的API用法。
方块实体渲染器接口BlockEntityRenderer
BlockEntityRenderer
接口用于创建指定方块的方块实体对应的渲染器。一般,在创建指定方块实体的渲染器类时,需要实现此接口并使用方块实体类作为此接口的泛型,其中必须实现的方法为render()
;
void render(T entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay);
其中的参数包括:
T entity
:方块实体对象。重写方法时会自动填充实现BlockEntityRenderer
接口时使用的泛型对象;float tickDelta
:时间差值。一般指的是介于两帧(tick)之间的差值,用于完成动画的平滑过渡;MatrixStack matrices
:矩阵堆栈对象。用于控制渲染内容的位移、旋转和缩放等操作;VertexConsumerProvider vertexConsumers
:顶点消耗提供器对象;int light
:光照值;int overlay
:覆盖纹理效果;
稍后,我们将渲染逻辑添加到此方法中。
矩阵堆栈类MatrixStack
MatrixStack
类指的是一种数据结构为栈的变换矩阵,用于控制渲染的内容在3D空间中的平移、缩放或旋转。当然,以栈的形式存储的数据结构严格遵循“先进后出(FILO)/后进先出(LIFO)”的基本原则。因此,渲染的基本过程应当为“压入→变换→渲染→弹出”。
MatrixStack
类对象中方法的使用贯穿整个渲染过程,这些方法包括但不限于:
push()
:将当前矩阵的副本,也就是即将变换的矩阵压入堆栈;pop()
:丢弃当前矩阵,从堆栈弹出变换完成的矩阵;peek()
:获取当前堆栈最顶层的矩阵;multiply(Quaternionf quaternion)
:应用旋转变换。需要传递一个Quaternionf
对象;scale(float x, float y, float z)
:应用缩放变换。分别传递X、Y、Z轴扩大的倍数;translate(double x, double y, double z)
:应用平移变换。分别传递X、Y、Z轴的偏移量。
四元数类Quaternionf
Quaternionf
类用于表示并处理渲染内容的3D旋转,是调用MatrixStack
对象的multiply()
方法时必须传递的参数之一。不过,在调用multiply()
方法时,我们一般不会直接使用Quaternionf
类来创建它的对象。由Minecraft API提供的RotationAxis
接口提前封装了一些Quaternionf
对象,这些对象可供应用旋转变换时使用;
确切地说,四元数类Quaternionf
不属于Minecraft API。
旋转轴函数式接口RotationAxis
RotationAxis
类是在1.21.4版本中添加的工具类,用于简化常见的轴向旋转操作。尽管接口被@FunctionalInterface
注解所修饰,在本章中暂时没有出现关于此接口函数表达式的相关用法。
X、Y、Z三个基本轴的常量已经在接口中声明并完成了初始化,分别为:
NEGATIVE_X
:指定绕X轴负方向旋转;POSITIVE_X
:指定绕X轴正方向旋转;NEGATIVE_Y
:指定绕Y轴负方向旋转;POSITIVE_Y
:指定绕Y轴正方向旋转;NEGATIVE_Z
:指定绕Z轴负方向旋转;POSITIVE_Z
:指定绕Z轴正方向旋转;
想要完成旋转,还需要调用:
rotation(float rad)
:指定旋转的弧度制数值,返回Quaternionf
对象;rotationDegrees(float deg)
:指定旋转的角度制数值,是接口中最常用的方法,返回Quaternionf
对象。
顶点数据供应器接口VertexConsumerProvider
VertexConsumerProvider
接口用于管理顶点数据渲染,并为不同渲染层提供对应的VertexConsumer
,其对象声明在render()
方法的参数列表中,是渲染过程中需要使用到的参数之一。VertexConsumer
接口旨在构建和提交顶点数据,它可以将模型的几何数据(顶点颜色、纹理坐标、覆盖光效、法线向量等)转换为GPU可处理的格式,并最终渲染到屏幕上;
这属于渲染系统的底层API,一般情况不会直接使用。
方块实体渲染器工厂上下文内部类BlockEntityRendererFactory.Context
BlockEntityRendererFactory.Context
内部类用于构建方块实体渲染器的上下文环境,其中提供了渲染方块实体所需的各种依赖组件和工具。一般作为自定义方块实体渲染器类构造方法的参数。
其中声明的方法大多数是为了获取渲染过程中使用到的工具,包括:
getTextRenderer()
:获取文本渲染器对象;getRenderDispatcher()
:获取渲染调度器对象;getRenderManager()
:获取渲染管理器对象;
为了渲染文字,在本章中使用到了getTextRenderer()
方法来获取文本渲染器。
文本渲染器类TextRenderer
TextRenderer
类用于在游戏界面中渲染文本,是渲染文本时必须用到的API。其中封装了用于处理文本渲染、渲染长度计算和颜色设置的方法。
要使用这些方法,首先需要获取TextRenderer
的对象。通常,获取TextRenderer
对象的方法有两种。一是通过客户端类MinecraftClient
的实例:
MinecraftClient.getInstance().textRenderer;
不过这是旧版的写法;
二是在自定义方块实体渲染器类中声明TextRenderer
对象,然后在构造方法中通过BlockEntityRendererFactory.Context
对象的getTextRenderer()
方法对其进行初始化:
private final TextRenderer textRenderer;public XXXBlockEntityRenderer(BlockEntityRendererFactory.Context context) {textRenderer = context.getTextRenderer();
}
这是最新的写法,也是本章中用到的方法;
如果已经获取到TextRenderer
的对象,就可以开始配置文字相关设置,然后渲染文字。使用到的方法包括:
getWidth(String text)
:用于获取文字渲染后的长度,传递一个字符串作为参数,即要渲染的文字;draw(...)
:用于渲染的核心方法。其中的参数按顺序分别代表:String text
:指定要渲染的文字;float x
:指定文本基线的X坐标;float y
:指定文本基线的Y坐标;int color
:指定文本颜色,需要传递一个16进制RGB颜色码;boolean shadow
: 指定是否渲染阴影;Matrix4f matrix
:指定变换矩阵,用于应用平移、缩放或旋转操作。一般的写法为matrices.peek().getPositionMatrix()
,即通过MatrixStack
对象的peek()
方法获取MatrixStack
对象的栈顶矩阵条目,然后在调用getPositionMatrix()
方法获取变换后的矩阵;VertexConsumerProvider vertexConsumers
:指定顶点数据提供器,一般使用render()
方法中提供的参数;TextRenderer.TextLayerType layerType
:指定文本渲染层类型。需要传递内部枚举类TextRenderer.TextLayerType
中的枚举常量,包括:NORMAL
:正常模式。大多数文本的渲染模式,如GUI文本、物品名称等;SEE_THROUGH
:穿透模式。文本可以穿过其他物体始终可见;POLYGON_OFFSET
:混合偏移模式。旨在解决文本与方块表面深度冲突,即Z-Fighting问题;
int backgroundColor
:指定背景色,数值可以为0,代表透明色;int light
:指定光照值。一般使用render()
方法中提供的参数。
渲染系统类RenderSystem
RenderSystem
类用于控制图形渲染状态(如颜色混合、深度测试、纹理绑定等),它提供了对OpenGL底层渲染的封装,是Minecraft底层渲染控制中枢。在生产环境中,RenderSystem
类可用于创建自定义渲染逻辑;
其中声明的方法大多为静态方法,例如:
enableDepthTest()
:启用深度测试;disableCull()
:禁用背面剔除;setShaderColor(...)
:设置着色器颜色;
如果一切都已准备就绪,便可以开始创建自定义方块实体渲染器类。
1.在客户端项目(或client目录)中创建rendering
文件夹,用于存放渲染相关文件。然后创建LightBlockEntityRenderer.java
,作为“光源方块”的方块实体渲染器类;
使其继承BlockEntityRenderer<T extends BlockEntity>
类,并重写render()
方法;
public class LightBlockEntityRenderer implements BlockEntityRenderer<LightBlockEntity> {@Overridepublic void render(LightBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) {}
}
这里的LightBlockEntity
类是“光源方块”的方块实体类,已经在上一章节创建完毕;
2.声明一个私有成员常量textRenderer
,类型为TextRenderer
,用于渲染文本;
private final TextRenderer textRenderer;
3.声明构造方法,调用BlockEntityRendererFactory.Context
对象的getTextRenderer()
方法完成对textRenderer
的初始化;
public LightBlockEntityRenderer(BlockEntityRendererFactory.Context context) {textRenderer = context.getTextRenderer();
}
注册自定义方块实体渲染器
与方块、物品、魔咒效果等相同,在使用自定义方块实体渲染器前,需要将其在游戏注册表中注册。
方块实体渲染器工厂类BlockEntityRendererFactories
BlockEntityRendererFactories
类专门用于注册方块实体渲染器,是相当实用的工具类,它主要简化了方块实体渲染器与方块实体类型的绑定过程。
想要注册方块实体渲染器,应当调用register()
方法;
public static <T extends BlockEntity> void register(BlockEntityType<? extends T> type, BlockEntityRendererFactory<T> factory) {FACTORIES.put(type, factory);
}
需要传递两个参数:
BlockEntityType<? extends T> type
:指定方块实体对象。一般使用注册方块实体时返回的方块实体对象;BlockEntityRendererFactory<T> factory
:指定方块实体渲染器工厂对象。一般传递自定义方块实体渲染器类的表达式来创建其对象,即LightBlockEntityRenderer::new
。
在客户端入口点类中的onInitializeClient()
方法中注册方块实体渲染器;
@Override
public void onInitializeClient() {BlockEntityRendererFactories.register(ModBlockEntities.LIGHT_BLOCK_ENTITY, LightBlockEntityRenderer::new);
}
调用BlockEntityRendererFactories
类的静态方法register()
完成方块实体渲染器的注册,方法中按顺序传递“光源方块”的方块实体对象ModBlockEntities.LIGHT_BLOCK_ENTITY
以及自定义方块实体渲染器类的表达式LightBlockEntityRenderer::new
创建的BlockEntityRendererFactory
对象。
渲染文字
下面,我们将光照强度数值渲染在“光源方块”正上方。期间可能会随时启动游戏进行测试,以确保文本最终处于正确的位置。所有逻辑都将添加到自定义方块实体渲染器类的render()
方法中。
1.首先,通过方块实体类获取需要渲染的文字;
String text = String.valueOf(entity.getLevels());
通过方块实体对象entity
的getLevels()
方法获取方块的光照强度,然后将其转换为字符串并保存到text
中;
2.可以先将数字直接渲染到方块周围,然后在调整其大小和位置;
textRenderer.draw(text, 1f, 1f, 0xffffff, false, matrices.peek().getPositionMatrix(), vertexConsumers, TextRenderer.TextLayerType.NORMAL, 0, light);
调用文本渲染器对象的draw()
方法,其中的参数按顺序分别设置了要渲染的文本为text
、文本基线的X坐标为1f、Y坐标为1f、文本颜色0xffffff
(白色)、不显示文字阴影、变换矩阵为当前矩阵堆栈的栈顶条目的位置矩阵matrices.peek().getPositionMatrix()
、顶点数据提供器对象、文本渲染层模式为TextLayerType.NORMAL
、背景色为0(透明)以及光照值为light
;
先以上面的数值设置渲染文本,启动游戏后数值可以按需逐步修改;
如果现在启动游戏,放置方块后,会看到渲染已经生效,不过文本大小非常夸张,位置也完全不符合需求;
3.现在,我们使用MatrixStack
对象调整文字的大小和位置,过程中要严格遵循“压入变换矩阵→应用变换→渲染→弹出变换矩阵”的处理逻辑。
- 压入变换矩阵
调用push()
方法将当前矩阵的副本压入矩阵堆栈。matrices.push();
- 应用缩放变换
调用scale()
方法缩小矩阵大小;
将当前矩阵缩小20倍,可以看到文本已经缩小。matrices.scale(1 / 20f, 1 / 20f, 1 / 20f);
- 应用位置变换
调用translate()
方法调整文本的位置;
X、Y、Z轴的位移量分别为0.5、1.5和0.5。可以看到文本位置已经在方块的上方,但位置还不够居中,稍后我们调整matrices.translate(0.5, 1.5, 0.5);
draw()
方法中的参数来解决此问题;
不过,右键方块修改光照值,会发现数字处于完全倒置的状态。 - 应用旋转变换
调用multiply()
方法旋转文本;
使其沿X轴正半轴以角度制旋转180度,可以看到文本大小和朝向目前显示正确。matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180));
- 弹出变换矩阵
调用pop()
方法,丢弃当前矩阵,弹出变换完毕的矩阵。matrices.pop();
4.微调draw()
方法中的文本基线的X和Y坐标。
为了让光照强度无论为一位数还是两位数都处于居中状态,可以调用文本渲染器对象的getWidth()
方法来动态获取文本渲染后的长度。声明width
变量并调用getWidth()
方法对其初始化;
String text = String.valueOf(entity.getLevels());
float width = textRenderer.getWidth(text);
方法传递一个字符串,即渲染的文本。将返回的长度存储在width
变量中。最后,调整draw()
方法中的X和Y参数;
textRenderer.draw(text, -width / 2, -4f,...);
将X设为-width / 2
可以使文本始终在X方向上处于居中状态,然后将Y设为-4f
可以使其底部距离方块更远;
现在,文本的大小、位置、朝向等近乎完美。
5.此时移动到文本的相反方向,会发现文本不显示。这是因为Minecraft的渲染系统默认启用了背面剔除(Back-face Culling) 以减少渲染工作量,从而提高渲染效率。
我们可以在压入变换矩阵前通过RenderSystem
类关闭“背面剔除”功能;
RenderSystem.disableCull();
调用静态方法disableCull()
关闭“背面剔除”,启动游戏可以发现玩家处于文本的相反朝向时,文本始终显示;
现在,文本的渲染已经通过方块实体渲染器完成,所有功能均已实现。render()
方法中的代码应当如下:
@Override
public void render(LightBlockEntity entity, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay) {RenderSystem.disableCull();matrices.push();matrices.scale(1 / 20f, 1 / 20f, 1 / 20f);matrices.translate(0.5, 1.5, 0.5);matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180));String text = String.valueOf(entity.getLevels());float width = textRenderer.getWidth(text);textRenderer.draw(text,-width / 2, -4f,0xffffff,false,matrices.peek().getPositionMatrix(),vertexConsumers,TextRenderer.TextLayerType.NORMAL,0,light);matrices.pop();
}
本章小结
本章详细阐述了通过方块实体渲染器在方块周围渲染内容的过程,也首次在客户端项目中编写代码。本文篇幅较长,难度较大,需要有方块和方块实体创建的前置知识来完成学习。另外,还需要了解众多关于渲染的API用法来减少编码过程中的障碍。感谢各位的阅读,有兴趣可以订阅此专栏!