User Tools

Site Tools


Sidebar

安装

基础

物品

方块和方块实体

流体

实体

世界生成

其他

事件

Mixin

Dynamic Data Generation

Advanced

为1.15的教程

为1.14的教程

文档

zh_cn:tutorial:custom_model

使用自定义模型动态渲染方块和物品

通过方块模型JSON文件可以将模型添加到游戏,但也可以通过Java代码来渲染。本教程中,我们将会将一个四面熔炉模型添加到游戏。

注意模型会在区块被重建时渲染。如果需要更加动态的渲染,可以使用方块实体渲染器: blockentityrenderers.

创建模型

模型第一次在Minecraft被注册时,其原始数据包含在UnbakedModel中。比如,这个数据可能会包括形状或材质名。然后在初始化过程中,UnbakedModel::bake()创建一个BakedModel以用于渲染。为了使得渲染尽可能快,这个baking的过程中会尽可能多地操作。我们也会实现FabricBakedModel以利用Fabric Renderer API。

现在创建一个实现所有三个界面的FourSidedFurnace模型。

public class FourSidedFurnaceModel implements UnbakedModel, BakedModel, FabricBakedModel {

Sprites

渲染材质离不开Sprite。我们必须先创建一个SpriteIdentifier然后在bake模型时得到对应的Sprite。 这里,我们会使用两个熔炉材质。它们是方块材质,所以要从方块atlasSpriteAtlasTexture.BLOCK_ATLAS_TEX中加载。

    private static final SpriteIdentifier[] SPRITE_IDS = new SpriteIdentifier[]{
            new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("minecraft:block/furnace_front_on")),
            new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("minecraft:block/furnace_top"))
    };
    private Sprite[] SPRITES = new Sprite[2];

Meshes

Mesh是预备通过Fabric Rendering API渲染的游戏形状。我们会添加一个到我们的类中,然后在bake模型时加载它。

    private Mesh mesh;

UnbakedModel方法

    @Override
    public Collection<Identifier> getModelDependencies() {
        return Collections.emptyList(); // 模型不依赖于其他模型。
    }
 
    @Override
    public Collection<SpriteIdentifier> getTextureDependencies(Function<Identifier, UnbakedModel> unbakedModelGetter, Set<Pair<String, String>> unresolvedTextureReferences) {
        return Arrays.asList(SPRITE_IDS); // 本模型(以及其模型依赖,依赖的依赖,等)依赖的材质。
    }
 
 
    @Override
    public BakedModel bake(ModelLoader loader, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) {
        // 获得sprites
        for(int i = 0; i < 2; ++i) {
            SPRITES[i] = textureGetter.apply(SPRITE_IDS[i]);
        }
        // 用Renderer API构建mesh
        Renderer renderer = RendererAccess.INSTANCE.getRenderer();
        MeshBuilder builder = renderer.meshBuilder();
        QuadEmitter emitter = builder.getEmitter();
 
        for(Direction direction : Direction.values()) {
            int spriteIdx = direction == Direction.UP || direction == Direction.DOWN ? 1 : 0;
            // 将新的面(face)添加到mesh
            emitter.square(direction, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);
            // 设置面的sprite,必须在.square()之后调用
            // 我们还没有指定任何uv坐标,所以我们使用整个材质,BAKE_LOCK_UV恰好就这么做。
            emitter.spriteBake(0, SPRITES[spriteIdx], MutableQuadView.BAKE_LOCK_UV);
            // 启用材质使用
            emitter.spriteColor(0, -1, -1, -1, -1);
            // 将quad添加到mesh
            emitter.emit();
        }
        mesh = builder.build();
 
        return this;
    }

BakedModel方法

注意这里不是所有的方法都会被Fabric Renderer使用,所以我们并不关心这个实现。

@Override
    public List<BakedQuad> getQuads(BlockState state, Direction face, Random random) {
        return null; // 不需要,因为我们使用的是FabricBakedModel
    }
 
    @Override
    public boolean useAmbientOcclusion() {
        return true; // 环境光遮蔽:我们希望方块在有临近方块时显示阴影
    }
 
    @Override
    public boolean isBuiltin() {
        return false;
    }
 
    @Override
    public boolean hasDepth() {
        return false;
    }
 
    @Override
    public boolean isSideLit() {
        return false;
    }
 
    @Override
    public Sprite getSprite() {
        return SPRITES[1]; // 方块破坏材质,使用furnace_top
    }
 
    @Override
    public ModelTransformation getTransformation() {
        return null;
    }
 
    @Override
    public ModelOverrideList getOverrides() {
        return null;
    }

FabricBakedModel方法

    @Override
    public boolean isVanillaAdapter() {
        return false; // false以触发FabricBakedModel渲染
    }
 
    @Override
    public void emitBlockQuads(BlockRenderView blockRenderView, BlockState blockState, BlockPos blockPos, Supplier<Random> supplier, RenderContext renderContext) {
        // 渲染函数
 
        // 我们仅渲染mesh
        renderContext.meshConsumer().accept(mesh);
    }
 
    @Override
    public void emitItemQuads(ItemStack itemStack, Supplier<Random> supplier, RenderContext renderContext) {
 
    }
}

注册模型

我们先写一个ModelResourceProvider,允许你在游戏尝试从JSON加载时提供一个UnbakedModel的界面。参看本文档以了解详情。重要的部分是,对于每个模型都会调用一次loadModelResource()

我们在名称tutorial:block/four_sided_furnace之下注册模型。

public class TutorialModelProvider implements ModelResourceProvider {
    public static final Identifier FOUR_SIDED_FURNACE_MODEL = new Identifier("tutorial:block/four_sided_furnace");
    @Override
    public UnbakedModel loadModelResource(Identifier identifier, ModelProviderContext modelProviderContext) throws ModelProviderException {
        if(identifier.equals(FOUR_SIDED_FURNACE_MODEL)) {
            return new FourSidedFurnaceModel();
        } else {
            return null;
        }
    }
}

现在我们要将这个类注册到客户端初始化器中,the entry point for client-specific code.

public class ExampleModClient implements ClientModInitializer {
    @Override
    public void onInitializeClient() {
        ModelLoadingRegistry.INSTANCE.registerResourceProvider(rm -> new TutorialModelProvider());
 
        /* 其他客户端指定的初始化 */
    }
}

不要忘记在fabric.mod.json中注册这个entrypoint,如果还没有完成的话:

/* ... */
  "entrypoints": {
    /* ... */
    "client": [
      "tutorial.path.to.ExampleModClient"
    ]
  },

使用模型

你现在可以注册你的方块以使用新模型。比如,如果你的方块只有一个状态,把这个放在assets/你的模组id/blockstates/你的方块id.json之下。

{
  "variants": {
    "": { "model": "tutorial:block/four_sided_furnace" }
  }
}

当然,你可以实现更加复杂的渲染。玩得开心!

渲染物品

As you can see in the picture, the item is not rendered correctly. Let's fix this.

更新模型

We will re-use the same model class, with just a small change:

  • We will need a ModelTransformation that rotates/translates/scales the model depending on its position (in right hand, in left hand, in gui, in item frame, etc…). As we are creating a model for a regular block, we will use the one from “minecraft:block/block” which we will load during model baking.

We will update our FourSidedFurnaceModel class as follows:

    // The minecraft default block model
    private static final Identifier DEFAULT_BLOCK_MODEL = new Identifier("minecraft:block/block");
 
    private ModelTransformation transformation;
 
    // We need to add the default model to the dependencies
    public Collection<Identifier> getModelDependencies() {
        return Arrays.asList(DEFAULT_BLOCK_MODEL);
    }
 
    // We need to add a bit of logic to the bake function
    @Override
    public BakedModel bake(ModelLoader loader, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) {
        // Load the default block model
        JsonUnbakedModel defaultBlockModel = (JsonUnbakedModel) loader.getOrLoadModel(DEFAULT_BLOCK_MODEL);
        // Get its ModelTransformation
        transformation = defaultBlockModel.getTransformations();
 
        /* Previous code */
    }
 
    // We need to implement getTransformation() and getOverrides()
    @Override
    public ModelTransformation getTransformation() {
        return transformation;
    }
 
    @Override
    public ModelOverrideList getOverrides() {
        return ModelOverrideList.EMPTY;
    }
 
    // We will also implement this method to have the correct lighting in the item rendering. Try to set this to false and you will see the difference.
    @Override
    public boolean isSideLit() {
        return true;
    }
 
    // Finally, we can implement the item render function
    @Override
    public void emitItemQuads(ItemStack itemStack, Supplier<Random> supplier, RenderContext renderContext) {
        renderContext.meshConsumer().accept(mesh);
    }

Loading the model

Let's update the ModelResourceProvider we created earlier:

public class TutorialModelProvider implements ModelResourceProvider {
    public static final FourSidedFurnaceModel FOUR_SIDED_FURNACE_MODEL = new FourSidedFurnaceModel();
    public static final Identifier FOUR_SIDED_FURNACE_MODEL_BLOCK = new Identifier("tutorial:block/four_sided_furnace");
    public static final Identifier FOUR_SIDED_FURNACE_MODEL_ITEM = new Identifier("tutorial:item/four_sided_furnace");
 
    @Override
    public UnbakedModel loadModelResource(Identifier identifier, ModelProviderContext modelProviderContext) throws ModelProviderException {
        if(identifier.equals(FOUR_SIDED_FURNACE_MODEL_BLOCK) || identifier.equals(FOUR_SIDED_FURNACE_MODEL_ITEM)) {
            return FOUR_SIDED_FURNACE_MODEL;
        } else {
            return null;
        }
    }
}

最终结果

Et voilà! Enjoy!

更加动态的渲染

The renderContext parameter in emitBlockQuads and emitItemQuads contains a QuadEmitter which you can use to build a model on the fly.

    @Override
    public void emitBlockQuads(BlockRenderView blockRenderView, BlockState blockState, BlockPos blockPos, Supplier<Random> supplier, RenderContext renderContext) {
        QuadEmitter emitter = renderContext.getEmitter();
        /* With this emitter, you can directly append the quads to the chunk model. */
    }
zh_cn/tutorial/custom_model.txt · Last modified: 2021/01/16 07:48 by solidblock