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
)
}
}
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
}
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
- Use appropriate events -
WorldRenderEvent for 3D, OverlayRenderEvent for 2D
- Batch similar draws - Reduce state changes
- Reuse resources - Don’t create new buffers every frame
- Cull invisible objects - Don’t render what’s not visible
- 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