跳到主要内容

优化:具有固定模型的方块实体

有些时候,我们创造一个方块实体是为了获得比json模型更大的自由度,但最终使用BER渲染出来的是一个固定的模型,不需要旋转或移动之类——就像原版的告示牌一样。

警告
  • 本节仅适用于固定模型。
  • 本节仅适用于只需要使用solid,cutoutMipped,cutout,translucent,tripwire这几种RenderType的情况。如果你需要更多类型,请查看T88的渲染部分

正如BER的原理决定的,在BER中进行渲染需要每一帧都进行一次算来算去填顶点之类的漫长无比的操作。如果渲染结果没有变化(正如开头所言),我们可以想办法在第一次render之后,保存填好的BufferBuilder,以后就直接塞进去,省去重复计算的花费。

信息

当然,你可以选择自己维护一个这样的缓存,自己监听相关事件,自己更新、缓存和渲染。这样做的优点是:

  • 可以绕开ChunkBuffer在渲染时令人头大的坐标系变换;
  • 可以尽可能地减少对原版逻辑的干扰;
  • 可以更方便地使用自定义RenderType。

或许我们也可以想想,原版有没有类似的做法呢?

——当然有,这就是原版渲染世界(地形)时使用的ChunkBuffer,在区块(其实是Section)加载时进行compile,遍历内部所有方块,渲染到缓存里。我们也可以向其中插一脚,在compile时塞进自己的东西。这样做的优点是:

  • 不需要关心更新、缓存和渲染的触发,不用担心漏掉什么情况导致渲染糠掉;

这看起来只有似乎只占一点,但是其中可能会隐藏着巨大的工作量。因此选择借用ChunkBuffer是值得的。

为了把顶点们塞进进ChunkBuffer,我们需要注入sectioncompile相关方法,检测对应方块并进行渲染。

准备方块实体

我们首先需要一个接口来标记这种特殊的固定BER方块:

public interface IFixedBEREntity {

private BlockEntity self() {
return (BlockEntity) this;
}

void renderFixedBER(Set<RenderType> begunRenderTypes, BlockRenderDispatcher blockRenderer, ChunkBufferBuilderPack builderPack, PoseStack poseStack, int packedOverlay);

default int getPackedLight() {
if (self().getLevel() != null) {
return LevelRenderer.getLightColor(self().getLevel(), self().getBlockPos());
} else {
return 0;
}
}

default BufferBuilder getBuilder(Set<RenderType> begunRenderTypes, ChunkBufferBuilderPack bufferBuilderPack, RenderType type) {
var builder = bufferBuilderPack.builder(type);
if (begunRenderTypes.add(type)) {
builder.begin(type.mode(), type.format());
}
return builder;
}
}

除了主要的renderFixedBER方法外,这里还有两个辅助方法。

getPackedLight显然是BER里render方法提供的packedLight的补充;而值得注意的是,与在BER中使用bufferSource.getBuffer()不同,你不能直接使用bufferBuilderPack.builder(),而是需要使用此处的getBuilder,在开始绘制前检查与所需的renderType对应的BufferBuilder是否已初始化。

现在,你只需要让你的方块实体类实现这个接口,并把BER中的render函数(几乎)照样copy过来:

public final class YourFixedModelBlockEntity extends BlockEntity implements IFixedBEREntity {

@OnlyIn(Dist.CLIENT)
@Override
public void renderFixedBER(Set<RenderType> begunRenderTypes, BlockRenderDispatcher blockRenderer, ChunkBufferBuilderPack builderPack, PoseStack poseStack, int packedOverlay) {
...
}
}
提示

如果你的BER里没有其他的部分,那你就可以把你的BER删掉并取消注册了;如果还包含有其他部分的代码,例如某种withoutLevelRenderer,你可以选择保留,或是挪到其他地方去、再去掉BER。

注入

我们只需要找到合适的位置,然后注入渲染调用:

@Mixin(ChunkRenderDispatcher.RenderChunk.RebuildTask.class)
public class ChunkRenderDispatcher$RenderChunk$RebuildTaskMixin {

@Inject(method = "compile", at = @At(value = "INVOKE", shift = At.Shift.AFTER,
target = "Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk$RebuildTask;handleBlockEntity(Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk$RebuildTask$CompileResults;Lnet/minecraft/world/level/block/entity/BlockEntity;)V"),
locals = LocalCapture.CAPTURE_FAILSOFT)
private void t88CompileFixedBlockEntity(float pX, float pY, float pZ, ChunkBufferBuilderPack pChunkBufferBuilderPack,
CallbackInfoReturnable<ChunkRenderDispatcher.RenderChunk.RebuildTask.CompileResults> cir,
ChunkRenderDispatcher.RenderChunk.RebuildTask.CompileResults compileResults, int i, BlockPos from, BlockPos to, VisGraph visgraph, RenderChunkRegion renderchunkregion, PoseStack poseStack, Set<RenderType> renderTypes, RandomSource random, BlockRenderDispatcher blockRenderer, Iterator<BlockPos> posIterator, BlockPos pos, BlockState state, BlockEntity entity) {
if (entity instanceof IFixedBEREntity fixedEntity) {
poseStack.pushPose();
poseStack.translate(pos.getX() & 15, pos.getY() & 15, pos.getZ() & 15);
fixedEntity.renderFixedBER(renderTypes,blockRenderer, pChunkBufferBuilderPack, poseStack, OverlayTexture.NO_OVERLAY);
poseStack.popPose();
}
}
}

我们还需要借助AT打开两个内部类的访问权限:

public net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$RenderChunk$RebuildTask
public net.minecraft.client.renderer.chunk.ChunkRenderDispatcher$RenderChunk$RebuildTask$CompileResults

你可以注意到,在调用renderFixedBER之前,我们使用poseStack.translate(pos.getX() & 15, pos.getY() & 15, pos.getZ() & 15);,其目的是将原点从section原点挪到方块原点处。

信息

关于此处的坐标变换,你可以在Cobalt读到更多信息。

注入点上下文源码,以及LevelRenderer.renderLevel和它的下文也可能对你的理解有帮助。

好了,现在启动你的游戏,你就应该能看到和之前BER一样的渲染结果了。

警告

对渲染相关代码的注入(尤其是需要捕获局部变量时),很有可能会与Optifine打架。参考**这一段**内容来解决冲突。