Skip to content

World Rendering Integration

World rendering integration allows you to add custom visual effects to the game world, from custom terrain features to global environmental effects. This guide covers hooking into Minecraft 26.1’s world rendering pipeline.

The world rendering system follows a modern frame graph architecture:

  • LevelRenderer: Main orchestrator for world rendering
  • Render Level Stages: Distinct rendering passes in pipeline
  • SectionRenderDispatcher: Manages chunk compilation and rendering
  • FeatureRenderDispatcher: Handles modular feature rendering
  • PostChain: Manages post-processing effects

The LevelRenderer orchestrates the entire world rendering pipeline:

net.minecraft.client.renderer.LevelRenderer
public void renderLevel(
final GraphicsResourceAllocator resourceAllocator,
final DeltaTracker deltaTracker,
final boolean renderOutline,
final Camera camera,
final Matrix4f modelViewMatrix,
final Matrix4f projectionMatrix,
final Matrix4f projectionMatrixForCulling,
final GpuBufferSlice terrainFog,
final Vector4f fogColor,
final boolean shouldRenderSky
) {
// Extract visible elements
this.extractVisibleElements(camera, deltaTracker);
// Setup frame graph with render passes
Frame frame = this.createFrame(resourceAllocator, projectionMatrix, fogColor);
// Execute all render passes
this.frameGraph.render(frame);
// Finalize and present
this.finalizeFrame(frame);
}

The rendering pipeline uses distinct stages for different visual elements:

graph TD
    A[Extract Phase] --> B[Sky Pass]
    B --> C[Terrain Pass]
    C --> D[Entity Pass]
    D --> E[Block Entity Pass]
    E --> F[Weather Pass]
    F --> G[Clouds Pass]
    G --> H[Particle Pass]
    H --> I[Post-Process Pass]
    I --> J[Debug Pass]

Each stage provides specific integration points for custom rendering:

// Example: Render level stage integration
public class RenderLevelStages {
public static final int SKY_PASS = 0;
public static final int TERRAIN_PASS = 1;
public static final int ENTITY_PASS = 2;
public static final int BLOCK_ENTITY_PASS = 3;
public static final int WEATHER_PASS = 4;
public static final int PARTICLES_PASS = 5;
public static final int POST_PROCESS_PASS = 6;
}

The feature renderer system provides modular integration for custom world objects:

// Example: Custom world feature renderer
public class CustomWorldFeatureRenderer {
private final List<CustomWorldObject> worldObjects = new ArrayList<>();
private final Map<RenderType, List<CustomWorldObject>> objectsByRenderType = new HashMap<>();
public void extractVisibleObjects(Level level, Camera camera, Frustum frustum,
float partialTicks, CustomWorldRenderState renderState) {
worldObjects.clear();
objectsByRenderType.clear();
// Find visible custom objects in view area
Vec3 cameraPos = camera.getPosition();
int viewDistance = 64; // blocks
for (int x = (int) cameraPos.x - viewDistance; x < cameraPos.x + viewDistance; x++) {
for (int z = (int) cameraPos.z - viewDistance; z < cameraPos.z + viewDistance; z++) {
for (int y = level.getMinBuildHeight(); y < level.getMaxBuildHeight(); y++) {
BlockPos pos = new BlockPos(x, y, z);
if (level.getBlockEntity(pos) instanceof CustomWorldBlockEntity blockEntity) {
CustomWorldObject worldObj = extractWorldObject(blockEntity, partialTicks);
// Frustum culling
if (frustum.isVisible(worldObj.getBoundingBox())) {
worldObjects.add(worldObj);
// Group by render type for batching
objectsByRenderType.computeIfAbsent(
worldObj.getRenderType(),
k -> new ArrayList<>()
).add(worldObj);
}
}
}
}
}
// Sort by distance for transparency
worldObjects.sort(Comparator.comparingDouble(
obj -> obj.getDistanceSquared(cameraPos)));
}
public void submitWorldObjects(PoseStack poseStack, MultiBufferSource bufferSource,
CustomWorldRenderState renderState) {
// Render opaque objects first
for (Map.Entry<RenderType, List<CustomWorldObject>> entry : objectsByRenderType.entrySet()) {
if (!entry.getKey().isTransparent()) {
renderObjectsOfType(entry.getKey(), entry.getValue(), poseStack, bufferSource, renderState);
}
}
// Then render transparent objects (sorted by distance)
for (Map.Entry<RenderType, List<CustomWorldObject>> entry : objectsByRenderType.entrySet()) {
if (entry.getKey().isTransparent()) {
renderObjectsOfType(entry.getKey(), entry.getValue(), poseStack, bufferSource, renderState);
}
}
}
private void renderObjectsOfType(RenderType renderType, List<CustomWorldObject> objects,
PoseStack poseStack, MultiBufferSource bufferSource,
CustomWorldRenderState renderState) {
VertexConsumer vertexConsumer = bufferSource.getBuffer(renderType);
for (CustomWorldObject obj : objects) {
poseStack.pushPose();
// Apply world transformation
poseStack.translate(obj.getX(), obj.getY(), obj.getZ());
poseStack.mulPose(Axis.YP.rotationDegrees(obj.getYRotation()));
poseStack.scale(obj.getScale(), obj.getScale(), obj.getScale());
// Render object
obj.render(vertexConsumer, poseStack, renderState);
poseStack.popPose();
}
}
}

For direct integration into the frame graph submission system:

// Example: Custom submit node implementation
public class CustomWorldSubmitNode implements SubmitNode {
private final CustomWorldObject worldObject;
private final RenderType renderType;
private final BoundingBox boundingBox;
public CustomWorldSubmitNode(CustomWorldObject worldObject) {
this.worldObject = worldObject;
this.renderType = worldObject.getRenderType();
this.boundingBox = worldObject.getBoundingBox();
}
@Override
public BoundingBox getBoundingBox() {
return boundingBox;
}
@Override
public void submit(PoseStack poseStack, SubmitNodeCollector submitNodeCollector,
CameraRenderState camera) {
poseStack.pushPose();
// Transform to world position
poseStack.translate(worldObject.getX(), worldObject.getY(), worldObject.getZ());
// Apply object transformations
poseStack.mulPose(Axis.YP.rotationDegrees(worldObject.getYRotation()));
poseStack.scale(worldObject.getScale(), worldObject.getScale(), worldObject.getScale());
// Submit custom geometry
submitNodeCollector.submitCustomGeometry(
poseStack,
renderType,
(collectorPose, consumer) -> {
worldObject.renderGeometry(consumer, collectorPose);
}
);
poseStack.popPose();
}
@Override
public boolean shouldRender(CameraRenderState camera) {
// Distance-based culling
Vec3 cameraPos = new Vec3(camera.cameraX, camera.cameraY, camera.cameraZ);
Vec3 objPos = new Vec3(worldObject.getX(), worldObject.getY(), worldObject.getZ());
return cameraPos.distanceToSqr(objPos) <= 1024.0; // 32 blocks
}
}

Hook into world rendering events for seamless integration:

// Example: World rendering event integration
public class WorldRenderingEventHandler {
private final CustomWorldFeatureRenderer featureRenderer;
public WorldRenderingEventHandler() {
this.featureRenderer = new CustomWorldFeatureRenderer();
// Register event listeners
RenderLevelStageEvent.register(this::onRenderLevelStage);
RenderWorldLastEvent.register(this::onRenderWorldLast);
}
private void onRenderLevelStage(RenderLevelStageEvent event) {
if (event.getStage() == RenderLevelStageEvent.Stage.AFTER_ENTITIES) {
// Render custom objects after entities but before weather
PoseStack poseStack = event.getPoseStack();
MultiBufferSource bufferSource = event.getVertexConsumers();
Camera camera = event.getCamera();
extractAndRenderCustomObjects(poseStack, bufferSource, camera);
}
}
private void onRenderWorldLast(RenderWorldLastEvent event) {
// Render debug information or overlay effects
PoseStack poseStack = event.getPoseStack();
MultiBufferSource bufferSource = event.getVertexConsumers();
renderDebugInfo(poseStack, bufferSource);
}
private void extractAndRenderCustomObjects(PoseStack poseStack,
MultiBufferSource bufferSource, Camera camera) {
Level level = Minecraft.getInstance().level;
Vec3 cameraPos = camera.getPosition();
Frustum frustum = camera.getFrustum();
float partialTicks = Minecraft.getInstance().getFrameTime();
// Extract visible objects
CustomWorldRenderState renderState = new CustomWorldRenderState();
featureRenderer.extractVisibleObjects(level, camera, frustum, partialTicks, renderState);
// Submit for rendering
featureRenderer.submitWorldObjects(poseStack, bufferSource, renderState);
}
}

Integrate custom rendering into the terrain compilation pipeline:

// Example: Custom terrain feature integration
public class CustomTerrainFeatureIntegrator {
private final SectionRenderDispatcher sectionRenderDispatcher;
public CustomTerrainFeatureIntegrator(SectionRenderDispatcher dispatcher) {
this.sectionRenderDispatcher = dispatcher;
}
public void integrateTerrainFeatures(Level level, BlockPos center, int radius) {
// Find affected chunk sections
for (int x = center.getX() - radius; x <= center.getX() + radius; x += 16) {
for (int z = center.getZ() - radius; z <= center.getZ() + radius; z += 16) {
BlockPos chunkPos = new BlockPos(x >> 4, 0, z >> 4);
// Mark sections as dirty for recompilation with custom features
for (int y = level.getMinSection(); y < level.getMaxSection(); y++) {
SectionRenderDispatcher.RenderSection section =
sectionRenderDispatcher.getSection(chunkPos.getX(), y, chunkPos.getZ());
if (section != null) {
addCustomTerrainFeatures(section, level, chunkPos, y);
section.setDirty();
}
}
}
}
}
private void addCustomTerrainFeatures(SectionRenderDispatcher.RenderSection section,
Level level, BlockPos chunkPos, int sectionY) {
// Add custom terrain data to section compilation
BlockPos sectionOrigin = new BlockPos(
chunkPos.getX() * 16,
sectionY * 16,
chunkPos.getZ() * 16
);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 16; y++) {
BlockPos pos = sectionOrigin.offset(x, y, z);
if (hasCustomTerrainFeature(level, pos)) {
CustomTerrainFeature feature = extractCustomFeature(level, pos);
section.addCustomFeature(feature);
}
}
}
}
}
}

Add custom weather effects to the weather rendering system:

// Example: Custom weather effect renderer
public class CustomWeatherRenderer {
private final List<CustomWeatherParticle> weatherParticles = new ArrayList<>();
public void extractWeatherEffects(Level level, CustomWeatherRenderState renderState,
Vec3 cameraPos, float partialTicks) {
weatherParticles.clear();
int weatherIntensity = level.getWeatherIntensity();
if (weatherIntensity > 0) {
// Extract custom weather particles in visible area
int radius = 64;
for (int x = (int) cameraPos.x - radius; x < cameraPos.x + radius; x += 4) {
for (int z = (int) cameraPos.z - radius; z < cameraPos.z + radius; z += 4) {
for (int y = (int) cameraPos.y - radius; y < cameraPos.y + radius; y += 4) {
BlockPos pos = new BlockPos(x, y, z);
if (shouldSpawnWeatherParticle(level, pos, weatherIntensity)) {
CustomWeatherParticle particle = createWeatherParticle(pos, partialTicks);
weatherParticles.add(particle);
}
}
}
}
}
renderState.weatherParticles = weatherParticles;
}
public void submitWeatherEffects(PoseStack poseStack, MultiBufferSource bufferSource,
CustomWeatherRenderState renderState) {
VertexConsumer vertexConsumer = bufferSource.getBuffer(RenderTypes.CUSTOM_WEATHER);
for (CustomWeatherParticle particle : renderState.weatherParticles) {
poseStack.pushPose();
// Position particle
poseStack.translate(particle.getX(), particle.getY(), particle.getZ());
// Apply wind and animation
poseStack.mulPose(Axis.YP.rotationDegrees(particle.getWindAngle()));
poseStack.scale(particle.getScale(), particle.getScale(), particle.getScale());
// Render particle
particle.render(vertexConsumer, poseStack);
poseStack.popPose();
}
}
private CustomWeatherParticle createWeatherParticle(BlockPos pos, float partialTicks) {
return new CustomWeatherParticle(
pos.getX() + Math.random() * 2.0 - 1.0,
pos.getY() + Math.random() * 2.0,
pos.getZ() + Math.random() * 2.0 - 1.0,
partialTicks
);
}
}

Add custom post-processing effects to the world rendering pipeline:

// Example: Custom post-processing integration
public class CustomPostProcessor {
private PostChain customPostChain;
private final ResourceLocation customShaderId =
ResourceLocation.withDefaultNamespace("shaders/post/custom_effect.json");
public void initializePostProcessor(Minecraft minecraft) {
ShaderManager shaderManager = minecraft.getShaderManager();
try {
this.customPostChain = shaderManager.getPostChain(
customShaderId,
LevelTargetBundle.MAIN_TARGETS
);
} catch (IOException e) {
// Handle shader loading failure
System.err.println("Failed to load custom post processor: " + e.getMessage());
}
}
public void applyPostProcessing(Frame frame, int screenWidth, int screenHeight) {
if (customPostChain != null) {
// Add custom uniform values
customPostChain.getEffect().safeGetUniform("Time").set(Blaze3D.getTime() / 1000.0f);
customPostChain.getEffect().safeGetUniform("ScreenSize").set(screenWidth, screenHeight);
// Apply effect
customPostChain.process(frame.getScreenTarget());
}
}
public void setupCustomEffectUniforms(CustomWorldRenderState renderState) {
if (customPostChain != null) {
// Pass world state to shader
customPostChain.getEffect().safeGetUniform("WeatherIntensity")
.set(renderState.weatherIntensity);
customPostChain.getEffect().safeGetUniform("TimeOfDay")
.set(renderState.timeOfDay);
}
}
}

Register your world rendering integration in your mod initialization:

// Example: Main mod class for Fabric
public class WorldRenderingMod implements ModInitializer {
public static final String MOD_ID = "world_rendering";
@Override
public void onInitialize() {
// Server-side initialization
MyWorldFeatures.register();
}
}
// Example: Client initialization for world rendering
public class WorldRenderingModClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
// Register world rendering components
WorldRenderEventHandler.registerEvents();
MyWorldRenderers.registerRenderers();
}
}
// Example: World rendering event handler
public class WorldRenderEventHandler {
public static void registerEvents() {
WorldRenderEvents.AFTER_ENTITIES.register(WorldRenderEventHandler::renderAfterEntities);
}
private static void renderAfterEntities(WorldRenderContext context) {
// Custom world rendering logic
renderCustomFeatures(context);
}
}
// Register custom post processors
event.registerPostProcessor(new CustomPostProcessor());
}
}

Register custom shaders and resources:

// Example: Registering custom world rendering shaders
public class WorldRenderingResources {
public static final ResourceLocation CUSTOM_WORLD_SHADER =
ResourceLocation.fromNamespaceAndPath("world_rendering", "custom_world");
public static final ResourceLocation CUSTOM_POST_SHADER =
ResourceLocation.fromNamespaceAndPath("world_rendering", "shaders/post/custom_effect.json");
public static void registerShaders() {
// Register vertex shader
ResourceLocation vertShader = ResourceLocation.fromNamespaceAndPath(
"world_rendering", "shaders/world/custom.vert");
// Register fragment shader
ResourceLocation fragShader = ResourceLocation.fromNamespaceAndPath(
"world_rendering", "shaders/world/custom.frag");
// Register post-processing shader configuration
ResourceLocation postConfig = ResourceLocation.fromNamespaceAndPath(
"world_rendering", "shaders/post/custom_effect.json");
// These will be loaded by the shader manager
}
}
// Example: Advanced frustum culling for custom world objects
public class CustomObjectCulling {
private final Frustum frustum;
private final Vec3 cameraPos;
private final Map<ChunkPos, List<CustomWorldObject>> objectsByChunk = new HashMap<>();
public List<CustomWorldObject> cullObjects(List<CustomWorldObject> objects) {
List<CustomWorldObject> visibleObjects = new ArrayList<>();
for (CustomWorldObject obj : objects) {
// Distance culling
if (cameraPos.distanceToSqr(obj.getPosition()) > MAX_RENDER_DISTANCE_SQUARED) {
continue;
}
// Frustum culling
if (!frustum.isVisible(obj.getBoundingBox())) {
continue;
}
visibleObjects.add(obj);
}
// Sort by distance for transparency
visibleObjects.sort(Comparator.comparingDouble(
obj -> obj.getDistanceSquared(cameraPos)));
return visibleObjects;
}
private Map<ChunkPos, List<CustomWorldObject>> organizeByChunk(List<CustomWorldObject> objects) {
objectsByChunk.clear();
for (CustomWorldObject obj : objects) {
BlockPos pos = obj.getBlockPos();
ChunkPos chunkPos = new ChunkPos(pos);
objectsByChunk.computeIfAbsent(chunkPos, k -> new ArrayList<>()).add(obj);
}
return objectsByChunk;
}
}
// Example: Update frequency optimization for expensive world rendering
public class WorldRenderUpdateController {
private long lastFullUpdate = 0;
private long lastPartialUpdate = 0;
private static final long FULL_UPDATE_INTERVAL = 100; // ms
private static final long PARTIAL_UPDATE_INTERVAL = 50; // ms
public boolean shouldUpdateFull(CustomWorldObject object) {
long currentTime = System.currentTimeMillis();
if (currentTime - lastFullUpdate > FULL_UPDATE_INTERVAL) {
lastFullUpdate = currentTime;
return true;
}
return false;
}
public boolean shouldUpdatePartial(CustomWorldObject object) {
long currentTime = System.currentTimeMillis();
if (currentTime - lastPartialUpdate > PARTIAL_UPDATE_INTERVAL) {
lastPartialUpdate = currentTime;
return true;
}
return false;
}
}

Problem: Custom world objects don’t render Solution: Check event registration, render type setup, and culling logic

Problem: Custom world rendering causes lag Solution: Implement proper culling, update frequency control, and batching

Problem: Custom objects render in wrong order Solution: Ensure proper render type sorting and transparency handling

Problem: Custom post-processing shaders fail to load Solution: Verify shader syntax, resource paths, and uniform declarations

  1. Use appropriate culling to avoid rendering off-screen objects
  2. Batch similar objects by render type to minimize state changes
  3. Implement level of detail for distant objects
  4. Update expensive calculations at controlled frequencies
  5. Profile performance and optimize bottlenecks
  6. Handle edge cases like empty worlds or extreme coordinates