World Rendering Integration
World Rendering Integration
Section titled “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.
Overview
Section titled “Overview”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
Core Architecture
Section titled “Core Architecture”LevelRenderer Main Flow
Section titled “LevelRenderer Main Flow”The LevelRenderer orchestrates the entire world rendering pipeline:
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);}Render Level Stages
Section titled “Render Level Stages”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 integrationpublic 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;}Integration Strategies
Section titled “Integration Strategies”1. Feature Renderer Integration
Section titled “1. Feature Renderer Integration”The feature renderer system provides modular integration for custom world objects:
// Example: Custom world feature rendererpublic 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(); } }}2. Submit Node System Integration
Section titled “2. Submit Node System Integration”For direct integration into the frame graph submission system:
// Example: Custom submit node implementationpublic 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 }}3. Event-Driven Integration
Section titled “3. Event-Driven Integration”Hook into world rendering events for seamless integration:
// Example: World rendering event integrationpublic 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); }}Advanced Integration Techniques
Section titled “Advanced Integration Techniques”Custom Terrain Features
Section titled “Custom Terrain Features”Integrate custom rendering into the terrain compilation pipeline:
// Example: Custom terrain feature integrationpublic 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); } } } } }}Weather Effect Integration
Section titled “Weather Effect Integration”Add custom weather effects to the weather rendering system:
// Example: Custom weather effect rendererpublic 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 ); }}Post-Processing Integration
Section titled “Post-Processing Integration”Add custom post-processing effects to the world rendering pipeline:
// Example: Custom post-processing integrationpublic 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); } }}Registration and Setup
Section titled “Registration and Setup”World Rendering Event Registration
Section titled “World Rendering Event Registration”Register your world rendering integration in your mod initialization:
// Example: Main mod class for Fabricpublic 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 renderingpublic class WorldRenderingModClient implements ClientModInitializer { @Override public void onInitializeClient() { // Register world rendering components WorldRenderEventHandler.registerEvents(); MyWorldRenderers.registerRenderers(); }}
// Example: World rendering event handlerpublic 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()); }}Shader and Resource Registration
Section titled “Shader and Resource Registration”Register custom shaders and resources:
// Example: Registering custom world rendering shaderspublic 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 }}Performance Optimization
Section titled “Performance Optimization”Frustum Culling Implementation
Section titled “Frustum Culling Implementation”// Example: Advanced frustum culling for custom world objectspublic 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; }}Update Frequency Control
Section titled “Update Frequency Control”// Example: Update frequency optimization for expensive world renderingpublic 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; }}Common Issues and Solutions
Section titled “Common Issues and Solutions”Custom Objects Not Appearing
Section titled “Custom Objects Not Appearing”Problem: Custom world objects don’t render Solution: Check event registration, render type setup, and culling logic
Performance Issues
Section titled “Performance Issues”Problem: Custom world rendering causes lag Solution: Implement proper culling, update frequency control, and batching
Rendering Order Issues
Section titled “Rendering Order Issues”Problem: Custom objects render in wrong order Solution: Ensure proper render type sorting and transparency handling
Shader Compilation Errors
Section titled “Shader Compilation Errors”Problem: Custom post-processing shaders fail to load Solution: Verify shader syntax, resource paths, and uniform declarations
Best Practices
Section titled “Best Practices”- Use appropriate culling to avoid rendering off-screen objects
- Batch similar objects by render type to minimize state changes
- Implement level of detail for distant objects
- Update expensive calculations at controlled frequencies
- Profile performance and optimize bottlenecks
- Handle edge cases like empty worlds or extreme coordinates
Next Steps
Section titled “Next Steps”- GUI and Overlay Rendering - Custom UI elements and HUD components
- Advanced Shader Techniques - Complex shader effects
- Performance Optimization - Advanced optimization techniques
- World Rendering Example - Complete working example