Heavily optimize infested leaves rendering

This commit is contained in:
embeddedt 2025-08-28 20:12:16 -04:00
parent 9b8363e9e4
commit 910d0adcd1
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
9 changed files with 174 additions and 133 deletions

View File

@ -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;
}
}

View File

@ -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());

View File

@ -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();
}

View File

@ -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<BakedQuad> 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;
}
}

View File

@ -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<InfestedLeavesBlockEntity> {
@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<InfestedLeave
}
// Get infested percentage
int progress = Math.min(te.getProgress(), 16000);
float progress = Math.min(te.getProgress(), 16000) / 16000f;
// Render
var model = mc.getBlockRenderer().getBlockModel(state);
var pos = te.getBlockPos();
mc.getBlockRenderer().getModelRenderer().tesselateBlock(level, model, state, pos, stack, buffer.getBuffer(RenderUtil.TINTED_CUTOUT_MIPPED), false, level.random, state.getSeed(pos), progress, ModelData.EMPTY, null);
for (var renderType : model.getRenderTypes(state, level.random, ModelData.EMPTY)) {
// Dynamically blend the provided vertex colors towards grayscale
var vertexConsumer = new VertexConsumerWrapper(buffer.getBuffer(RenderTypeHelper.getMovingBlockRenderType(renderType))) {
@Override
public VertexConsumer setColor(int r, int g, int b, int a) {
float rF = (r / 255f), gF = (g / 255f), bF = (b / 255f);
float avg = rF * 0.3f + gF * 0.59f + bF * 0.11f;
return super.setColor(
Math.round(RenderUtil.mix(rF, avg, progress) * 255),
Math.round(RenderUtil.mix(gF, avg, progress) * 255),
Math.round(RenderUtil.mix(bF, avg, progress) * 255),
a
);
}
};
mc.getBlockRenderer().getModelRenderer().tesselateBlock(level, model, state, pos, stack, vertexConsumer,
true, level.random, state.getSeed(pos), OverlayTexture.NO_OVERLAY, ModelData.EMPTY,
renderType);
}
}
}

View File

@ -40,15 +40,11 @@ public class EConfig {
public static final Server SERVER;
public static class Client {
public final BooleanValue useFastInfestedLeaves;
public final BooleanValue rainbowCompostDuringJune;
public Client(ModConfigSpec.Builder builder) {
builder.comment("Client configuration for Ex Deorum").push("client");
this.useFastInfestedLeaves = builder
.comment("Whether to use a simplified renderer for infested leaves (reduces FPS lag with lots of infested trees)")
.define("use_fast_infested_leaves", false);
this.rainbowCompostDuringJune = builder
.comment("Whether compost in barrels appears as rainbow colored during the month of June")
.define("rainbow_compost_during_june", true);

View File

@ -1,38 +0,0 @@
#version 150
#moj_import <fog.glsl>
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);
}

View File

@ -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 ] }
]
}

View File

@ -1,34 +0,0 @@
#version 150
#moj_import <light.glsl>
#moj_import <fog.glsl>
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;
}