From 910d0adcd147a1446205547012d68a79809d8597 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 28 Aug 2025 20:12:16 -0400 Subject: [PATCH] Heavily optimize infested leaves rendering --- .../exdeorum/block/InfestedLeavesBlock.java | 7 +- .../exdeorum/client/ClientHandler.java | 55 +++++++---- .../exdeorum/client/RenderUtil.java | 4 + .../model/InfestedLeavesBakedModel.java | 96 +++++++++++++++++++ .../client/ter/InfestedLeavesRenderer.java | 38 +++++++- .../exdeorum/config/EConfig.java | 4 - .../core/rendertype_tinted_cutout_mipped.fsh | 38 -------- .../core/rendertype_tinted_cutout_mipped.json | 31 ------ .../core/rendertype_tinted_cutout_mipped.vsh | 34 ------- 9 files changed, 174 insertions(+), 133 deletions(-) create mode 100644 src/main/java/thedarkcolour/exdeorum/client/model/InfestedLeavesBakedModel.java delete mode 100644 src/main/resources/assets/exdeorum/shaders/core/rendertype_tinted_cutout_mipped.fsh delete mode 100644 src/main/resources/assets/exdeorum/shaders/core/rendertype_tinted_cutout_mipped.json delete mode 100644 src/main/resources/assets/exdeorum/shaders/core/rendertype_tinted_cutout_mipped.vsh diff --git a/src/main/java/thedarkcolour/exdeorum/block/InfestedLeavesBlock.java b/src/main/java/thedarkcolour/exdeorum/block/InfestedLeavesBlock.java index 2daa3199..e75128a2 100644 --- a/src/main/java/thedarkcolour/exdeorum/block/InfestedLeavesBlock.java +++ b/src/main/java/thedarkcolour/exdeorum/block/InfestedLeavesBlock.java @@ -38,12 +38,8 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.BooleanProperty; import net.minecraft.world.phys.HitResult; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.fml.loading.FMLEnvironment; import org.jetbrains.annotations.Nullable; import thedarkcolour.exdeorum.blockentity.InfestedLeavesBlockEntity; -import thedarkcolour.exdeorum.client.RenderUtil; -import thedarkcolour.exdeorum.config.EConfig; import thedarkcolour.exdeorum.registry.EBlockEntities; import thedarkcolour.exdeorum.registry.EBlocks; @@ -115,7 +111,6 @@ public class InfestedLeavesBlock extends LeavesBlock implements EntityBlock { @Override public RenderShape getRenderShape(BlockState pState) { - if (FMLEnvironment.dist == Dist.DEDICATED_SERVER) return RenderShape.MODEL; - return (EConfig.CLIENT_SPEC.isLoaded() && EConfig.CLIENT.useFastInfestedLeaves.get()) || RenderUtil.IRIS_ACCESS.areShadersEnabled() ? RenderShape.MODEL : RenderShape.INVISIBLE; + return pState.getValue(FULLY_INFESTED) ? RenderShape.MODEL : RenderShape.INVISIBLE; } } \ No newline at end of file diff --git a/src/main/java/thedarkcolour/exdeorum/client/ClientHandler.java b/src/main/java/thedarkcolour/exdeorum/client/ClientHandler.java index ee12b26d..770c3ad3 100644 --- a/src/main/java/thedarkcolour/exdeorum/client/ClientHandler.java +++ b/src/main/java/thedarkcolour/exdeorum/client/ClientHandler.java @@ -19,17 +19,19 @@ package thedarkcolour.exdeorum.client; import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.DefaultVertexFormat; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen; import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState; +import net.minecraft.client.renderer.BiomeColors; import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.ShaderInstance; +import net.minecraft.client.renderer.block.BlockModelShaper; import net.minecraft.client.resources.model.ModelResourceLocation; import net.minecraft.core.Holder; import net.minecraft.core.registries.Registries; +import net.minecraft.util.FastColor; import net.minecraft.util.Unit; +import net.minecraft.world.level.FoliageColor; import net.minecraft.world.level.levelgen.presets.WorldPreset; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModList; @@ -39,6 +41,8 @@ import net.neoforged.neoforge.client.event.*; import net.neoforged.neoforge.common.NeoForge; import thedarkcolour.exdeorum.ExDeorum; import thedarkcolour.exdeorum.asm.ASMHooks; +import thedarkcolour.exdeorum.blockentity.InfestedLeavesBlockEntity; +import thedarkcolour.exdeorum.client.model.InfestedLeavesBakedModel; import thedarkcolour.exdeorum.client.screen.MechanicalHammerScreen; import thedarkcolour.exdeorum.client.screen.MechanicalSieveScreen; import thedarkcolour.exdeorum.client.ter.*; @@ -46,11 +50,10 @@ import thedarkcolour.exdeorum.compat.ModIds; import thedarkcolour.exdeorum.config.EConfig; import thedarkcolour.exdeorum.recipe.RecipeUtil; import thedarkcolour.exdeorum.registry.EBlockEntities; +import thedarkcolour.exdeorum.registry.EBlocks; import thedarkcolour.exdeorum.registry.EFluids; import thedarkcolour.exdeorum.registry.EMenus; -import java.io.IOException; - public class ClientHandler { // Used for the composting recipe category in JEI public static final ModelResourceLocation OAK_BARREL_COMPOSTING = new ModelResourceLocation(ExDeorum.loc("item/oak_barrel_composting"), ModelResourceLocation.STANDALONE_VARIANT); @@ -64,9 +67,10 @@ public class ClientHandler { modBus.addListener(ClientHandler::clientSetup); modBus.addListener(ClientHandler::registerMenuScreens); modBus.addListener(ClientHandler::registerRenderers); - modBus.addListener(ClientHandler::registerShaders); modBus.addListener(ClientHandler::addClientReloadListeners); modBus.addListener(ClientHandler::onConfigChanged); + modBus.addListener(ClientHandler::onModelBake); + modBus.addListener(ClientHandler::addBlockColorHandler); fmlBus.addListener(ClientHandler::onPlayerRespawn); fmlBus.addListener(ClientHandler::onPlayerLogout); fmlBus.addListener(ClientHandler::onScreenOpen); @@ -124,17 +128,6 @@ public class ClientHandler { event.registerBlockEntityRenderer(EBlockEntities.COMPRESSED_SIEVE.get(), ctx -> new CompressedSieveRenderer<>(0.5625f, 16f)); } - private static void registerShaders(RegisterShadersEvent event) { - try { - // NEW_ENTITY is BLOCK except it also uses UV1 (overlay coordinates) - event.registerShader(new ShaderInstance(event.getResourceProvider(), ExDeorum.loc("rendertype_tinted_cutout_mipped"), DefaultVertexFormat.NEW_ENTITY), instance -> { - RenderUtil.renderTypeTintedCutoutMippedShader = instance; - }); - } catch (IOException e) { - ExDeorum.LOGGER.error("Unable to load tinted shader", e); - } - } - // Sets Ex Deorum world type as default private static void onScreenOpen(ScreenEvent.Opening event) { if (event.getNewScreen() instanceof CreateWorldScreen screen && EConfig.COMMON.setVoidWorldAsDefault.get()) { @@ -159,6 +152,36 @@ public class ClientHandler { event.register(OAK_BARREL_COMPOSTING); } + private static void onModelBake(ModelEvent.ModifyBakingResult event) { + var model = new InfestedLeavesBakedModel(); + for (var state : EBlocks.INFESTED_LEAVES.get().getStateDefinition().getPossibleStates()) { + var location = BlockModelShaper.stateToModelLocation(state); + event.getModels().put(location, model); + } + } + + private static void addBlockColorHandler(RegisterColorHandlersEvent.Block event) { + var blockColors = event.getBlockColors(); + event.register((state, level, pos, tintIndex) -> { + int innerColor; + if (level != null && pos != null && level.getBlockEntity(pos) instanceof InfestedLeavesBlockEntity infestedLeavesBlockEntity) { + var mimicState = infestedLeavesBlockEntity.getMimic(); + try { + innerColor = blockColors.getColor(mimicState, level, pos, tintIndex); + } catch (Exception e) { + // The block may be unhappy that the BlockState in the world (infested leaves) does not match + // the mimic state that was provided in the argument. In such cases, use the default foliage + // color resolver. + innerColor = level.getBlockTint(pos, BiomeColors.FOLIAGE_COLOR_RESOLVER); + } + } else { + innerColor = FoliageColor.getDefaultColor(); + } + int gray = (30 * FastColor.ARGB32.red(innerColor) + 59 * FastColor.ARGB32.green(innerColor) + 11 * FastColor.ARGB32.blue(innerColor)) / 100; + return FastColor.ARGB32.color(255, gray, gray, gray); + }, EBlocks.INFESTED_LEAVES.get()); + } + private static void onRecipesUpdated(RecipesUpdatedEvent event) { if (!Minecraft.getInstance().isSingleplayer()) { RecipeUtil.reload(event.getRecipeManager()); diff --git a/src/main/java/thedarkcolour/exdeorum/client/RenderUtil.java b/src/main/java/thedarkcolour/exdeorum/client/RenderUtil.java index 4db12a2d..e1f9b24c 100644 --- a/src/main/java/thedarkcolour/exdeorum/client/RenderUtil.java +++ b/src/main/java/thedarkcolour/exdeorum/client/RenderUtil.java @@ -337,6 +337,10 @@ public class RenderUtil { builder.addVertex(pose, edgeMin, minY, edgeMax).setColor(r, g, b, 255).setUv(uMax, vMax).setUv1(0, 10).setUv2(lightU, lightV).setNormal(normal.x, normal.y, normal.z); } + public static float mix(float a, float b, float progress) { + return Math.fma(b - a, progress, a); + } + public interface IrisAccess { boolean areShadersEnabled(); } diff --git a/src/main/java/thedarkcolour/exdeorum/client/model/InfestedLeavesBakedModel.java b/src/main/java/thedarkcolour/exdeorum/client/model/InfestedLeavesBakedModel.java new file mode 100644 index 00000000..9becd5ed --- /dev/null +++ b/src/main/java/thedarkcolour/exdeorum/client/model/InfestedLeavesBakedModel.java @@ -0,0 +1,96 @@ +package thedarkcolour.exdeorum.client.model; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.block.BlockModelShaper; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.ItemOverrides; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.client.ChunkRenderTypeSet; +import net.neoforged.neoforge.client.model.IDynamicBakedModel; +import net.neoforged.neoforge.client.model.data.ModelData; +import net.neoforged.neoforge.common.util.TriState; +import org.jetbrains.annotations.Nullable; +import thedarkcolour.exdeorum.blockentity.InfestedLeavesBlockEntity; + +import java.util.List; + +public class InfestedLeavesBakedModel implements IDynamicBakedModel { + private final BlockModelShaper modelShaper; + private final BlockState fallbackState; + + public InfestedLeavesBakedModel() { + this.modelShaper = Minecraft.getInstance().getModelManager().getBlockModelShaper(); + this.fallbackState = Blocks.OAK_LEAVES.defaultBlockState(); + } + + private BlockState getMimicState(ModelData modelData) { + var mimicState = modelData.get(InfestedLeavesBlockEntity.MIMIC_PROPERTY); + if (mimicState == null) { + return this.fallbackState; + } + return mimicState; + } + + @Override + public List getQuads(@Nullable BlockState blockState, @Nullable Direction direction, RandomSource randomSource, ModelData modelData, @Nullable RenderType renderType) { + var mimicState = getMimicState(modelData); + var model = modelShaper.getBlockModel(mimicState); + + return model.getQuads(mimicState, direction, randomSource, modelData, renderType); + } + + @Override + public ChunkRenderTypeSet getRenderTypes(BlockState state, RandomSource rand, ModelData data) { + var mimicState = getMimicState(data); + var model = modelShaper.getBlockModel(mimicState); + + return model.getRenderTypes(mimicState, rand, data); + } + + @Override + public TriState useAmbientOcclusion(BlockState state, ModelData data, RenderType renderType) { + var mimicState = getMimicState(data); + var model = modelShaper.getBlockModel(mimicState); + return model.useAmbientOcclusion(mimicState, data, renderType); + } + + @Override + public boolean useAmbientOcclusion() { + return true; + } + + @Override + public boolean isGui3d() { + return true; + } + + @Override + public boolean usesBlockLight() { + return true; + } + + @Override + public boolean isCustomRenderer() { + return false; + } + + @Override + public TextureAtlasSprite getParticleIcon(ModelData data) { + return modelShaper.getBlockModel(getMimicState(data)).getParticleIcon(data); + } + + @Override + public TextureAtlasSprite getParticleIcon() { + return modelShaper.getParticleIcon(this.fallbackState); + } + + @Override + public ItemOverrides getOverrides() { + return ItemOverrides.EMPTY; + } +} diff --git a/src/main/java/thedarkcolour/exdeorum/client/ter/InfestedLeavesRenderer.java b/src/main/java/thedarkcolour/exdeorum/client/ter/InfestedLeavesRenderer.java index 61eb5be4..b7041f04 100644 --- a/src/main/java/thedarkcolour/exdeorum/client/ter/InfestedLeavesRenderer.java +++ b/src/main/java/thedarkcolour/exdeorum/client/ter/InfestedLeavesRenderer.java @@ -19,19 +19,27 @@ package thedarkcolour.exdeorum.client.ter; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; +import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.world.level.block.Blocks; +import net.neoforged.neoforge.client.RenderTypeHelper; import net.neoforged.neoforge.client.model.data.ModelData; +import net.neoforged.neoforge.client.model.pipeline.VertexConsumerWrapper; +import thedarkcolour.exdeorum.block.InfestedLeavesBlock; import thedarkcolour.exdeorum.blockentity.InfestedLeavesBlockEntity; import thedarkcolour.exdeorum.client.RenderUtil; -import thedarkcolour.exdeorum.config.EConfig; public class InfestedLeavesRenderer implements BlockEntityRenderer { + @Override public void render(InfestedLeavesBlockEntity te, float partialTicks, PoseStack stack, MultiBufferSource buffer, int light, int unused) { - if (EConfig.CLIENT.useFastInfestedLeaves.get() || RenderUtil.IRIS_ACCESS.areShadersEnabled()) return; + // We render a static model when the animation is finished + if (te.getBlockState().getValue(InfestedLeavesBlock.FULLY_INFESTED)) { + return; + } var mc = Minecraft.getInstance(); var state = te.getMimic(); @@ -46,10 +54,32 @@ public class InfestedLeavesRenderer implements BlockEntityRenderer - -uniform sampler2D Sampler0; - -uniform vec4 ColorModulator; -uniform float FogStart; -uniform float FogEnd; -uniform vec4 FogColor; - -in float vertexDistance; -in vec4 vertexColor; -in vec2 texCoord0; -in float progress; - -out vec4 fragColor; - -void main() { - vec4 oldColor = texture(Sampler0, texCoord0) * vertexColor * ColorModulator; - if (oldColor.a < 0.5) { - discard; - } - float avg = oldColor.r * 0.3 + oldColor.g * 0.59 + oldColor.b * 0.11; - - vec4 color; - if (progress == 1.0f) { - color = vec4(avg, avg, avg, progress); - } else { - color = vec4( - mix(oldColor.r, avg, progress), - mix(oldColor.g, avg, progress), - mix(oldColor.b, avg, progress), - oldColor.a - ); - } - fragColor = linear_fog(color, vertexDistance, FogStart, FogEnd, FogColor); -} diff --git a/src/main/resources/assets/exdeorum/shaders/core/rendertype_tinted_cutout_mipped.json b/src/main/resources/assets/exdeorum/shaders/core/rendertype_tinted_cutout_mipped.json deleted file mode 100644 index b88d61fc..00000000 --- a/src/main/resources/assets/exdeorum/shaders/core/rendertype_tinted_cutout_mipped.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "blend": { - "func": "add", - "srcrgb": "srcalpha", - "dstrgb": "1-srcalpha" - }, - "vertex": "exdeorum:rendertype_tinted_cutout_mipped", - "fragment": "exdeorum:rendertype_tinted_cutout_mipped", - "attributes": [ - "Position", - "Color", - "UV0", - "UV1", - "UV2", - "Normal" - ], - "samplers": [ - { "name": "Sampler0" }, - { "name": "Sampler2" } - ], - "uniforms": [ - { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, - { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, - { "name": "ChunkOffset", "type": "float", "count": 3, "values": [ 0.0, 0.0, 0.0 ] }, - { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, - { "name": "FogStart", "type": "float", "count": 1, "values": [ 0.0 ] }, - { "name": "FogEnd", "type": "float", "count": 1, "values": [ 1.0 ] }, - { "name": "FogColor", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, - { "name": "FogShape", "type": "int", "count": 1, "values": [ 0 ] } - ] -} diff --git a/src/main/resources/assets/exdeorum/shaders/core/rendertype_tinted_cutout_mipped.vsh b/src/main/resources/assets/exdeorum/shaders/core/rendertype_tinted_cutout_mipped.vsh deleted file mode 100644 index 1f98fdef..00000000 --- a/src/main/resources/assets/exdeorum/shaders/core/rendertype_tinted_cutout_mipped.vsh +++ /dev/null @@ -1,34 +0,0 @@ -#version 150 - -#moj_import -#moj_import - -in vec3 Position; -in vec4 Color; -in vec2 UV0; -in ivec2 UV1; -in ivec2 UV2; -in vec3 Normal; - -uniform sampler2D Sampler2; - -uniform mat4 ModelViewMat; -uniform mat4 ProjMat; -uniform vec3 ChunkOffset; -uniform int FogShape; - -out float vertexDistance; -out vec4 vertexColor; -out vec2 texCoord0; -out float progress; - -void main() { - vec3 pos = Position + ChunkOffset; - gl_Position = ProjMat * ModelViewMat * vec4(pos, 1.0); - - vertexDistance = fog_distance(pos, FogShape); - vertexColor = Color * minecraft_sample_lightmap(Sampler2, UV2); - texCoord0 = UV0; - - progress = UV1.x / 16000.0; -}