
在做 Minecraft Java 版模组开发时,给方块设置多个朝向是很常见的需求。一般来说,我们会在 BlockState 的 JSON 文件里给不同朝向指定模型,再顺手加上旋转参数:
"facing=south": {
"model": "xxx:block/xxxx",
"y": 180
} 不过,这里的 y 参数有一个比较硬的限制:只能写 0、90、180、270 这四个值。如果你的方块想像原版头颅那样支持 16 个方向,甚至更多更细的旋转角度,这种写法就不够用了。
遇到这个限制时,有些作者可能会干脆放弃普通方块模型,转而用 BlockEntityRenderer 来画。这样当然能实现效果,但代价也比较明显:普通方块的顶点计算通常只会在区块刷新时发生,而附加实体相关的渲染则要每一帧实时计算。所以如果大量依赖 BlockEntityRenderer,多少会给游戏的渲染效率带来一些压力。
如果想在开发便利和运行效率之间取得一个比较好的平衡,可以利用 Forge 或 NeoForge 提供的扩展模型字段。它们在原版方块、物品模型的基础上,额外提供了 transform、render_type 等很实用的字段。
这个字段经常用来修复模型透明度导致的显示问题。
比如,贴图里本该透明的部分在游戏中变成了奇怪的黑块,这时通常只要配置合适的渲染类型就能解决。
Forge/NeoForge 为不同场景提供了几种常用的渲染策略:
solid
特点:不处理任何透明材质。
用途:适合石头、木板这类完全不透明的方块。
注意事项:不需要混合或排序,渲染性能最好。
cutout
特点:要么全透明,要么完全不透明,没有半透明过渡。
用途:适合带有透明边缘的方块,比如铁栏杆或普通玻璃板。
注意事项:不生成多级渐远纹理(Mipmap),远距离观察时可能出现边缘闪烁或摩尔纹。
cutout_mipped
特点:保留边缘硬切效果,同时开启渐远纹理。
用途:适合草丛、蕨类植物这类需要远处抗锯齿的方块。
注意事项:通过更平滑的过渡,可以有效缓解远景闪烁问题。
cutout_mipped_all
特点:强制物品栏里的微缩模型也应用渐远效果。
用途:主要用于对物品栏显示有特殊渐远需求的情况。
注意事项:因为容易让物品图标看起来发糊,实际开发中很少会用到。
translucent
特点:支持完整的可见度通道混合(Alpha 混合),可以实现真正的半透明。
用途:适合水面、冰块、染色玻璃这类带有混合渐变效果的半透明物体。
注意事项:因为需要进行顶点排序,性能开销会更高一些。
tripwire
特点:比较特化的多阶段渲染类型,结合了 Mipmap 和专用着色器。
用途:主要服务于极细模型(如绊线)以及特殊天气下的视觉表现。
注意事项:普通模组开发里基本很少需要用它。
顺带一提,早期 1.6.4 版本曾经有玩家通过矿透材质包作弊。后来 Mojang 为了避免这类问题,把相关遮挡逻辑(noOcclusion)的参数写死进了方块的构造参数里。
下面这张图可以帮助理解部分相关参数之间的关系:

解决了渲染类型的问题之后,实现任意角度旋转的重点就落在了 transform 字段上。
这个字段可以对整个方块模型应用全局矩阵变换,包括平移、缩放和旋转。
{
"translation": [0, 0, 0],
"scale": [1, 1, 1] // 也可以写成 "scale": 1,此时代表 x y z 缩放都是 1
} 旋转部分一般有两种写法。
最直观的是欧拉角模式,只需要指定旋转中心,以及围绕 X、Y、Z 轴分别旋转多少度即可。旋转中心既可以使用预设位置,也可以自己指定空间坐标:
{
"origin": "center", // 还可以填 corner 或者 opposing-corner,分别指代 (0,0,0) 和 (1,1,1)
"rotation": [0, 0, 0] // 分别表示 x y z 旋转角度
} 如果想自定义旋转中心,可以写成这样:
{
"origin": [0.5, 0.5, 0.5],
"rotation": [0, 0, 0]
} 另一种写法是基于四元数结构的定义方式。它通常需要传入左侧和右侧的旋转矩阵向量,再结合平移或缩放来构建完整矩阵(和 26.1 版本 items 里的变换写法完全一致):
{
"left_rotation": [0.0, -0.7, 0.0, 0.7],
"right_rotation": [0.0, 0.0, 0.0, 1.0],
"scale": [1, -1, -1],
"translation": [0.5, 1.5, 0.5]
} 虽然这种格式看起来没那么直观,但它可以避开欧拉角在某些情况下容易遇到的万向节死锁问题。
实际写模组的时候,手动维护十几个旋转变种显然不太现实。遇到这种批量生成 JSON 的需求,Data Generator 就是最省心的方案。
比如在 1.20/1.21 的开发环境下,可以在对应的 BlockModelProvider 类里这样写:
// 读取基础的、没有矩阵变换的父级模型
ResourceLocation parent = modLoc("block/%s".formatted(name));
// 根据需要的变种数量确定循环范围。以原版头颅为例,一共划分出 16 个方向,每次变化 22.5 度
int max = RotationSegment.getMaxSegmentIndex();
for (int i = 0; i <= max; i++) {
// 把这些变种模型放在 name 目录下
ResourceLocation file = modLoc("block/%s/rot_%d".formatted(name, i));
withExistingParent(file.toString(), parent)
.rootTransforms()
// 将旋转中心设为方块中心
.origin(TransformationHelper.TransformOrigin.CENTER)
// 每个变种对应的角度
.rotation(0, -i * 22.5f, 0, true)
.end();
} 最后,只需要在 BlockState 里给每个旋转状态指定对应的模型变种,就可以实现方块的任意角度旋转了。