烘焙模型(Baked Models)
烘焙模型(Baked Models)
烘焙模型是带有纹理的形状的代码表示。它们可以来自多个来源,例如通过调用 UnbakedModel#bake(默认模型加载器)或 IUnbakedGeometry#bake(自定义模型加载器)。一些方块实体渲染器也使用烘焙模型。模型的复杂程度没有限制。
模型存储在 ModelManager 中,可以通过 Minecraft.getInstance().modelManager 访问。然后,你可以调用 ModelManager#getModel 通过 ResourceLocation 或 ModelResourceLocation 获取某个模型。模组通常会重用之前自动加载和烘焙的模型。
BakedModel 的方法
getQuads
烘焙模型中最重要的方法是 getQuads。该方法负责返回一个 BakedQuad 列表,这些 BakedQuad 可以被发送到 GPU 进行渲染。一个四边形(quad)类似于建模程序(以及大多数其他游戏)中的三角形,但由于 Minecraft 通常专注于方块,开发者选择使用四边形(4 个顶点)而不是三角形(3 个顶点)进行渲染。getQuads 有五个参数可以使用:
-
BlockState:正在渲染的方块状态。可能为null,表示正在渲染物品。 -
Direction:正在剔除的面的方向。可能为null,表示应返回无法被遮挡的四边形。 -
RandomSource:客户端绑定的随机源,可用于随机化。 -
ModelData:要使用的额外模型数据。这可能包含来自方块实体的渲染所需的额外数据。由BakedModel#getModelData 提供。 -
RenderType:用于渲染方块的渲染类型。可能为null,表示应返回此模型使用的所有渲染类型的四边形。否则,它是BakedModel#getRenderTypes 返回的渲染类型之一(见下文)。
模型应大量缓存。这是因为即使区块仅在其中的方块发生变化时才会重建,此方法中的计算仍然需要尽可能快,并且理想情况下应大量缓存,因为每个区块部分(最多 4096 个方块)会多次调用此方法(每个渲染类型最多调用 7 次 * 模型使用的渲染类型数量 * 每个区块部分的方块数量)。此外,方块实体渲染器或实体渲染器实际上可能会每帧多次调用此方法。
applyTransform 和 getTransforms
applyTransform 允许在应用透视变换到模型时自定义逻辑,包括返回完全独立的模型。此方法由 NeoForge 添加,用于替代原版的 getTransforms() 方法,后者仅允许自定义变换本身,而不能自定义应用变换的方式。然而,applyTransform 的默认实现依赖于 getTransforms,因此如果你只需要自定义变换,也可以覆盖 getTransforms 并完成工作。applyTransforms 提供三个参数:
-
ItemDisplayContext:模型正在变换到的视角。 -
PoseStack:用于渲染的姿势堆栈。 -
boolean:是否使用左手渲染的修改值而不是默认的右手渲染值;如果渲染的手是左手(副手,或选项启用了左手模式时的主手),则为true。
注意applyTransform 和 getTransforms 仅适用于物品模型。
其他方法
BakedModel 中你可能覆盖或查询的其他方法包括:
| 方法签名 | 作用 |
|---|---|
TriState useAmbientOcclusion() | 是否使用环境光遮蔽。接受 BlockState、RenderType 和 ModelData 参数,并返回一个 TriState,允许不仅强制禁用 AO,还可以强制启用 AO。有两个重载,每个重载返回一个布尔参数,并接受仅 BlockState 或无参数;这两个重载已被弃用,建议使用第一个变体。 |
boolean isGui3d() | 此模型在 GUI 槽中是渲染为 3D 还是平面。 |
boolean usesBlockLight() | 在光照模型时是否使用 3D 光照(true)或来自前方的平面光照(false)。 |
boolean isCustomRenderer() | 如果为 true,则跳过正常渲染并调用关联的 BlockEntityWithoutLevelRenderer 的 renderByItem 方法。如果为 false,则通过默认渲染器渲染。 |
ItemOverrides getOverrides() | 返回与此模型关联的 ItemOverrides。这仅与物品模型相关。 |
ModelData getModelData(BlockAndTintGetter, BlockPos, BlockState, ModelData) | 返回用于模型的模型数据。此方法传递一个现有的 ModelData,如果方块有关联的方块实体,则它是 BlockEntity#getModelData() 的结果,否则为 ModelData.EMPTY。此方法可用于需要模型数据但没有方块实体的方块,例如具有连接纹理的方块。 |
TextureAtlasSprite getParticleIcon(ModelData) | 返回用于模型的粒子图标。可以使用模型数据为不同的模型数据值使用不同的粒子图标。NeoForge 添加,替代了无参数的原版 getParticleIcon() 重载。 |
ChunkRenderTypeSet getRenderTypes(BlockState, RandomSource, ModelData) | 返回一个 ChunkRenderTypeSet,包含用于渲染方块模型的渲染类型。ChunkRenderTypeSet 是一个基于集合的有序 Iterable<RenderType>。默认情况下回退到从模型 JSON 获取渲染类型。仅用于方块模型,物品模型使用下面的重载。 |
List<RenderType> getRenderTypes(ItemStack, boolean) | 返回一个 List<RenderType>,包含用于渲染物品模型的渲染类型。默认情况下回退到正常的模型绑定渲染类型查找,该查找始终生成一个包含一个元素的列表。仅用于物品模型,方块模型使用上面的重载。 |
视角(Perspectives)
Minecraft 的渲染引擎识别总共 8 种视角类型(如果包括代码中的回退,则为 9 种)用于物品渲染。这些用于模型 JSON 的 display 块,并在代码中通过 ItemDisplayContext 枚举表示。
| 枚举值 | JSON 键 | 用途 |
|---|---|---|
THIRD_PERSON_RIGHT_HAND | "thirdperson_righthand" | 第三人称中的右手(F5 视图或其他玩家) |
THIRD_PERSON_LEFT_HAND | "thirdperson_lefthand" | 第三人称中的左手(F5 视图或其他玩家) |
FIRST_PERSON_RIGHT_HAND | "firstperson_righthand" | 第一人称中的右手 |
FIRST_PERSON_LEFT_HAND | "firstperson_lefthand" | 第一人称中的左手 |
HEAD | "head" | 当在玩家的头部盔甲槽中时(通常只能通过命令实现) |
GUI | "gui" | 库存、玩家快捷栏 |
GROUND | "ground" | 掉落的物品;注意掉落物品的旋转由掉落物品渲染器处理,而不是模型 |
FIXED | "fixed" | 物品展示框 |
NONE | "none" | 代码中的回退用途,不应在 JSON 中使用 |
ItemOverrides
ItemOverrides 是一个类,提供了一种方式让烘焙模型处理 ItemStack 的状态并通过 #resolve 方法返回一个新的烘焙模型。#resolve 有五个参数:
-
BakedModel:原始模型。 -
ItemStack:正在渲染的物品堆叠。 -
ClientLevel:渲染模型的关卡。这应仅用于查询关卡,而不是以任何方式修改它。可能为null。 -
LivingEntity:渲染模型的实体。可能为null,例如从方块实体渲染器渲染时。 -
int:用于随机化的种子。
ItemOverrides 还持有模型的覆盖选项作为 BakedOverrides。BakedOverride 对象是模型 overrides 块的代码表示。烘焙模型可以使用它根据其内容返回不同的模型。可以通过 ItemOverrides#getOverrides() 获取 ItemOverrides 实例的所有 BakedOverrides 列表。
BakedModelWrapper
BakedModelWrapper 可用于修改现有的 BakedModel。BakedModelWrapper 是 BakedModel 的子类,它接受另一个 BakedModel(“原始”模型)作为构造函数参数,并默认将所有方法重定向到原始模型。然后,你的实现可以仅覆盖选定的方法,如下所示:
// 泛型参数可以选择性地是 BakedModel 的更具体的子类。
// 如果是,则构造函数参数必须匹配该类型。
public class MyBakedModelWrapper extends BakedModelWrapper<BakedModel> {
// 将原始模型传递给 super。
public MyBakedModelWrapper(BakedModel originalModel) {
super(originalModel);
}
// 在此覆盖你想要的任何方法。如果需要,你也可以访问 originalModel。
}编写模型包装器类后,你必须将包装器应用于它应影响的模型。在客户端事件处理程序 ModelEvent.ModifyBakingResult 中执行此操作:
@SubscribeEvent
public static void modifyBakingResult(ModelEvent.ModifyBakingResult event) {
// 对于方块模型
event.getModels().computeIfPresent(
// 要修改的模型的模型资源位置。使用 BlockModelShaper#stateToModelLocation 获取,
// 参数为要影响的方块状态。
BlockModelShaper.stateToModelLocation(MyBlocksClass.EXAMPLE_BLOCK.defaultBlockState()),
// 一个 BiFunction,参数为位置和原始模型,返回新模型。
(location, model) -> new MyBakedModelWrapper(model)
);
// 对于物品模型
event.getModels().computeIfPresent(
// 要修改的模型的模型资源位置。
new ModelResourceLocation(
ResourceLocation.fromNamespaceAndPath("examplemod", "example_item"),
"inventory"
),
// 一个 BiFunction,参数为位置和原始模型,返回新模型。
(location, model) -> new MyBakedModelWrapper(model)
);
}警告 通常建议尽可能使用自定义模型加载器而不是在 ModelEvent.ModifyBakingResult 中包装烘焙模型。如果需要,自定义模型加载器也可以使用 BakedModelWrapper。