Skip to main content

Overview

LiquidBounce provides extensive rendering utilities for drawing 2D overlays, 3D world objects, and managing render state.

Core Rendering Packages

  • net.ccbluex.liquidbounce.render - Core rendering engine
  • net.ccbluex.liquidbounce.utils.render - Rendering utilities and extensions

3D World Rendering

usePoseStack

Borrows a pose stack from the pool for transformations.
inline fun <T> usePoseStack(block: PoseStack.() -> T): T
Example:
usePoseStack {
    translate(x, y, z)
    scale(2.0f, 2.0f, 2.0f)
    // Render at transformed position
}

withPush

Pushes the pose stack, executes a block, then pops.
inline fun PoseStack.withPush(block: PoseStack.() -> Unit)
Example:
matrices.withPush {
    translate(0.5, 0.5, 0.5)
    // Transformation only applies within this block
}

DrawMode

Controls how geometry is submitted:
  • IMMEDIATE - Draw immediately
  • BATCH - Collect and draw in batch
enum class DrawMode {
    IMMEDIATE,
    BATCH
}

Rendering Context

WorldRenderEnvironment

Provides context for world rendering:
val renderHandler = handler<WorldRenderEvent> { event ->
    val env = event.worldRenderEnvironment
    
    env.withPush {
        // Your rendering code
    }
}

Common Rendering Utilities

Render Boxes

// Draw a box outline
fun drawBox(box: AABB, color: Color4b) {
    // Box rendering
}

// Draw filled box
fun drawFilledBox(box: AABB, color: Color4b) {
    // Filled box rendering
}

Render Lines

// Draw a line between two points
fun drawLine(
    from: Vec3,
    to: Vec3,
    color: Color4b,
    lineWidth: Float = 2.0f
)

Render Text in World

// Render text at world position
fun renderTextAtPosition(
    text: String,
    pos: Vec3,
    scale: Float = 1.0f
)

Rendering Events

WorldRenderEvent

Fired during world rendering.
val renderHandler = handler<WorldRenderEvent> { event ->
    // Draw ESP boxes, tracers, etc.
    for (entity in world.entities) {
        if (entity.shouldBeAttacked()) {
            drawBox(entity.boundingBox, Color4b(255, 0, 0, 100))
        }
    }
}

OverlayRenderEvent

Fired during overlay rendering (2D HUD).
val overlayHandler = handler<OverlayRenderEvent> { event ->
    // Draw 2D elements on screen
}

ScreenRenderEvent

Fired when rendering GUI screens.

Rendering Primitives

Color4b

Represents RGBA color.
val red = Color4b(255, 0, 0, 255)
val transparentBlue = Color4b(0, 0, 255, 100)

// From hex
val color = Color4b.fromHex(0xFF0000FF)

Vec3f

3D vector for positions and directions.
val position = Vec3f(x, y, z)

Batch Rendering

For performance, batch similar draw calls:
val batchCollector = BatchCollector()

// Collect draws
for (entity in entities) {
    batchCollector.draw(entity.box, color)
}

// Render all at once
batchCollector.flush()

Shader Usage

ClientShaders

Access built-in shaders:
ClientShaders.POSITION_COLOR
ClientShaders.POSITION_COLOR_TEXTURE

Custom Rendering Pipeline

val pipeline = RenderPipeline(
    vertexFormat = VertexFormat.POSITION_COLOR,
    mode = VertexFormat.Mode.QUADS
)

Rendering Examples

ESP Box Rendering

val espHandler = handler<WorldRenderEvent> { event ->
    val env = event.worldRenderEnvironment
    
    for (entity in world.entities) {
        if (!entity.shouldBeAttacked()) continue
        
        val box = entity.boundingBox
        val color = if (entity.isPlayer) {
            Color4b(255, 0, 0, 100)  // Red for players
        } else {
            Color4b(0, 255, 0, 100)  // Green for mobs
        }
        
        env.drawBox(box, color)
    }
}

Tracer Rendering

val tracerHandler = handler<WorldRenderEvent> { event ->
    val env = event.worldRenderEnvironment
    val eyePos = player.eyePosition
    
    for (entity in world.entities) {
        if (!entity.shouldBeAttacked()) continue
        
        val targetPos = entity.position()
        
        env.drawLine(
            from = eyePos,
            to = targetPos,
            color = Color4b(255, 255, 255, 200),
            lineWidth = 2.0f
        )
    }
}

Name Tags

val nameTagHandler = handler<WorldRenderEvent> { event ->
    val env = event.worldRenderEnvironment
    
    for (entity in world.entities) {
        val namePos = entity.position().add(0.0, entity.bbHeight + 0.3, 0.0)
        
        env.renderTextAtPosition(
            text = entity.name.string,
            pos = namePos,
            scale = 0.02f
        )
    }
}

Block ESP

val blockEspHandler = handler<WorldRenderEvent> { event ->
    val env = event.worldRenderEnvironment
    val searchRadius = 50
    
    for (x in -searchRadius..searchRadius) {
        for (y in -searchRadius..searchRadius) {
            for (z in -searchRadius..searchRadius) {
                val pos = player.blockPosition().offset(x, y, z)
                val block = world.getBlockState(pos).block
                
                if (block == Blocks.DIAMOND_ORE) {
                    val box = AABB.ofSize(pos.center(), 1.0, 1.0, 1.0)
                    env.drawFilledBox(box, Color4b(0, 255, 255, 50))
                    env.drawBox(box, Color4b(0, 255, 255, 200))
                }
            }
        }
    }
}

Custom Shapes

val customShapeHandler = handler<WorldRenderEvent> { event ->
    val env = event.worldRenderEnvironment
    
    env.withPush {
        translate(player.x, player.y, player.z)
        
        // Draw circle on ground
        val radius = 3.0
        val segments = 32
        
        for (i in 0 until segments) {
            val angle1 = (i.toDouble() / segments) * 2 * Math.PI
            val angle2 = ((i + 1).toDouble() / segments) * 2 * Math.PI
            
            val x1 = cos(angle1) * radius
            val z1 = sin(angle1) * radius
            val x2 = cos(angle2) * radius
            val z2 = sin(angle2) * radius
            
            drawLine(
                Vec3(x1, 0.0, z1),
                Vec3(x2, 0.0, z2),
                Color4b(255, 255, 0, 255)
            )
        }
    }
}

2D Overlay Rendering

Screen Coordinates

val overlayHandler = handler<OverlayRenderEvent> { event ->
    val screenWidth = mc.window.guiScaledWidth
    val screenHeight = mc.window.guiScaledHeight
    
    // Draw at screen position
    drawText(
        text = "LiquidBounce",
        x = 5f,
        y = 5f
    )
}

Render Statistics

val statsHandler = handler<OverlayRenderEvent> { event ->
    val stats = listOf(
        "FPS: ${mc.fps}",
        "Position: ${player.blockPosition()}",
        "Speed: ${getSpeed()}"
    )
    
    stats.forEachIndexed { index, stat ->
        drawText(
            text = stat,
            x = 5f,
            y = 5f + (index * 10f)
        )
    }
}

World to Screen Conversion

Convert 3D world positions to 2D screen coordinates:
fun worldToScreen(worldPos: Vec3): Vec2? {
    // Projection logic
    // Returns null if position is behind camera
}

Performance Tips

Use batch rendering for multiple objects with the same render state to improve performance.
Avoid creating new objects in render handlers - reuse objects or use object pools.
Rendering happens on the render thread - don’t perform game logic in render handlers.

Best Practices

  1. Use appropriate events - WorldRenderEvent for 3D, OverlayRenderEvent for 2D
  2. Batch similar draws - Reduce state changes
  3. Reuse resources - Don’t create new buffers every frame
  4. Cull invisible objects - Don’t render what’s not visible
  5. Use appropriate precision - Float for most rendering, Double for positions

Common Utilities

Distance Checks

fun shouldRender(entity: Entity): Boolean {
    val distance = player.distanceTo(entity)
    return distance <= renderDistance
}

Frustum Culling

fun isInView(box: AABB): Boolean {
    // Check if box is in camera frustum
}

See Also

Build docs developers (and LLMs) love