LiquidBounce features a sophisticated rendering system built on OpenGL 4.5+, using render pipelines for ESP, tracers, overlays, and custom UI effects.
Architecture
The rendering system consists of:
- Render Pipelines - Define how geometry is rendered
- Render Environments - Context for 2D/3D rendering
- Shaders - GLSL programs for GPU processing
- Render Targets - Offscreen buffers for effects
Render Pipelines
Location: render/ClientRenderPipelines.kt:37
Pipelines define the complete rendering state:
object ClientRenderPipelines {
// Basic primitives
val Lines = newPipeline("lines") {
forWorldRender()
relativePosColorSnippet(VertexFormat.Mode.LINES)
}
val Quads = newPipeline("quads") {
forWorldRender()
relativePosColorSnippet(VertexFormat.Mode.QUADS)
}
val Triangles = newPipeline("triangles") {
forWorldRender()
relativePosColorSnippet(VertexFormat.Mode.TRIANGLES)
}
}
Creating Custom Pipelines
val MyCustomPipeline = newPipeline("my_custom") {
withVertexShader("path/to/vertex")
withFragmentShader("path/to/fragment")
withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.TRIANGLES)
withBlend(BlendFunction.TRANSLUCENT)
withCull(false)
withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
}
World Rendering
Location: render/RenderShortcuts.kt:83
WorldRenderEnvironment
Provides context for 3D world rendering:
renderEnvironmentForWorld(matrixStack) {
// Draw in world space
withPositionRelativeToCamera(targetPos) {
drawBox(
box = AABB(0.0, 0.0, 0.0, 1.0, 1.0, 1.0),
faceColor = Color4b(255, 0, 0, 100),
outlineColor = Color4b(255, 255, 255, 255)
)
}
}
Drawing Primitives
Lines:
fun WorldRenderEnvironment.drawLine(p1: Vec3f, p2: Vec3f, argb: Int) =
drawCustomMesh(ClientRenderPipelines.Lines) { pose ->
addVertex(pose, p1).setColor(argb)
addVertex(pose, p2).setColor(argb)
}
Boxes (render/RenderShortcuts.kt:331):
fun WorldRenderEnvironment.drawBox(
box: AABB,
faceColor: Color4b? = Color4b.TRANSPARENT,
outlineColor: Color4b? = Color4b.TRANSPARENT,
faceVertices: Int = -1,
outlineVertices: Int = -1
)
Circles (render/RenderShortcuts.kt:519):
fun WorldRenderEnvironment.drawCircle(
radius: Float,
color: Color4b
)
fun WorldRenderEnvironment.drawCircleOutline(
radius: Float,
color: Color4b,
noDepthTest: Boolean = true
)
Gradient Effects (render/RenderShortcuts.kt:428):
fun WorldRenderEnvironment.drawGradientCircle(
outerRadius: Float,
innerRadius: Float,
outerColor: Color4b,
innerColor: Color4b,
innerOffset: Vector3fc = Vector3f(),
noDepthTest: Boolean = true
)
Coordinate Systems
World Space:
// Render at absolute world position
withPositionRelativeToCamera(blockPos) {
drawBox(FULL_BOX, faceColor = Color4b.RED)
}
Camera-Relative:
// Render relative to camera
withPositionRelativeToCamera {
translate(Vec3(0.0, 0.0, -5.0)) // 5 blocks in front
drawCircle(1.0f, Color4b.WHITE)
}
Custom Mesh Rendering
Location: render/RenderShortcuts.kt:182
fun WorldRenderEnvironment.drawCustomMesh(
pipeline: RenderPipeline,
textures: Map<String, AbstractTexture> = emptyMap(),
uniforms: Map<String, GpuBufferSlice> = emptyMap(),
drawer: VertexConsumer.(PoseStack.Pose) -> Unit
)
Example: Custom Textured Quad
val myTexture = /* ... */
drawCustomMeshTextured(myTexture) { pose ->
addVertex(pose, -0.5f, -0.5f, 0f).setUv(0f, 0f).setColor(0xFFFFFFFF.toInt())
addVertex(pose, -0.5f, 0.5f, 0f).setUv(0f, 1f).setColor(0xFFFFFFFF.toInt())
addVertex(pose, 0.5f, 0.5f, 0f).setUv(1f, 1f).setColor(0xFFFFFFFF.toInt())
addVertex(pose, 0.5f, -0.5f, 0f).setUv(1f, 0f).setColor(0xFFFFFFFF.toInt())
}
Shader System
Location: render/ClientShaders.kt
Custom shaders extend rendering capabilities:
object ClientShaders {
object Vertex {
val PosRelativeToCamera = identifier("core/pos_relative_to_camera")
val PosColorRelativeToCamera = identifier("core/poscolor_relative_to_camera")
}
object Fragment {
val BgraPosTex = identifier("core/bgra_pos_tex")
val PosRelativeToCamera = identifier("core/pos_relative_to_camera")
}
}
Location: render/ClientUniformDefine.kt
object ClientUniformDefine {
val GUI_BLUR = UniformDefine("GuiBlur", 16)
val ROUNDED_RECT = UniformDefine("RoundedRect", 12)
val DISTANCE_FADE = UniformDefine("DistanceFade", 8)
}
class UniformDefine(val uboName: String, val size: Int) {
fun createSingleBuffer(): GpuBufferSlice
}
val blurUniform = ClientUniformDefine.GUI_BLUR.createSingleBuffer()
blurUniform.writeStd140 {
putFloat(blurRadius)
putFloat(alphaStart)
putFloat(alphaEnd)
}
drawCustomMesh(
pipeline = MyPipeline,
uniforms = mapOf("GuiBlur" to blurUniform)
) { pose ->
// Render with uniform
}
Advanced Effects
Blur Effect
Location: render/engine/BlurEffectRenderer.kt:37
object BlurEffectRenderer : EventListener {
val overlayRenderTargetHolder = LazyRenderTargetHolder(
"LiquidBounce BlurOverlay",
useDepth = true
)
fun shouldDrawBlur(): Boolean
fun blitBlurOverlay()
}
The blur effect uses a two-pass Gaussian blur:
- Render UI to overlay buffer
- Blur overlay buffer
- Composite blur with main buffer
Outline Shader
Location: render/engine/OutlineShaderRenderer.kt
Renders entity outlines using stencil buffer:
object OutlineShaderRenderer {
fun beginOutlines()
fun renderOutlines(color: Color4b)
fun endOutlines()
}
Distance Fade
Location: render/utils/DistanceFadeUniformValueGroup.kt
Fade rendering based on distance:
class DistanceFadeUniformValueGroup : ValueGroup() {
val fadeStart by float("FadeStart", 50.0f, 0.0f..200.0f)
val fadeEnd by float("FadeEnd", 100.0f, 0.0f..200.0f)
fun updateIfDirty()
fun bindUniform(pass: RenderPass)
}
Render Targets
Location: render/engine/LazyRenderTargetHolder.kt
Offscreen rendering for effects:
class LazyRenderTargetHolder(
val name: String,
val useDepth: Boolean = false
) {
val raw: RenderTarget?
get() = /* lazy initialization */
fun resize(width: Int, height: Int)
fun clear()
}
Example: Custom Post-Processing
val customBuffer = LazyRenderTargetHolder("MyEffect")
// Render to custom buffer
customBuffer.raw!!.bindWrite(true)
drawScene()
customBuffer.raw!!.unbindWrite()
// Apply effect
mc.mainRenderTarget.createRenderPass({ "Custom Effect" }).use { pass ->
pass.setPipeline(MyEffectPipeline)
pass.bindTexture("Input", customBuffer.raw!!.colorTextureView, sampler)
pass.draw(0, 3)
}
Batching
Batch rendering for efficiency:
renderEnvironmentForWorld(matrixStack, mode = DrawMode.BATCH) {
// All draws batched together
for (entity in entities) {
drawBox(entity.boundingBox, Color4b.RED)
}
// Single GPU call
}
AMD Vega Workaround
Location: render/RenderShortcuts.kt:57
val HAS_AMD_VEGA_APU = (GL11C.glGetString(GL11C.GL_RENDERER)
?.startsWith("AMD Radeon(TM) RX Vega") ?: false)
inline fun WorldRenderEnvironment.longLines(draw: WorldRenderEnvironment.() -> Unit) {
if (HAS_AMD_VEGA_APU) GL11C.glDisable(GL11C.GL_LINE_SMOOTH)
try {
draw()
} finally {
if (HAS_AMD_VEGA_APU) GL11C.glEnable(GL11C.GL_LINE_SMOOTH)
}
}
AMD Vega GPU Bug: Newer AMD Vega iGPU drivers (2023+) fail to smooth long lines. The longLines helper disables line smoothing as a workaround.
2D Rendering
Location: render/Render2D.kt
For GUI and HUD elements:
object Render2D {
fun drawRect(x: Float, y: Float, width: Float, height: Float, color: Color4b)
fun drawRoundedRect(x: Float, y: Float, width: Float, height: Float, radius: Float, color: Color4b)
fun drawGradientRect(x: Float, y: Float, width: Float, height: Float, startColor: Color4b, endColor: Color4b)
fun drawTexture(texture: AbstractTexture, x: Float, y: Float, width: Float, height: Float)
}
Font Rendering
Location: render/FontManager.kt
object FontManager {
fun getFont(name: String, size: Int): FontRenderer
}
interface FontRenderer {
fun drawString(text: String, x: Float, y: Float, color: Color4b)
fun getStringWidth(text: String): Float
fun getFontHeight(): Float
}
Best Practices
Minimize State Changes
// Good - same pipeline
renderEnvironmentForWorld(matrices) {
for (box in boxes) {
drawBox(box, Color4b.RED) // Batched
}
}
// Bad - pipeline switches
renderEnvironmentForWorld(matrices) {
drawBox(box1, Color4b.RED)
drawCircle(1.0f, Color4b.BLUE) // Pipeline switch!
drawBox(box2, Color4b.RED) // Pipeline switch!
}
Use Color4b
Prefer Color4b over integer ARGB:
// Good - type-safe
val color = Color4b(255, 0, 0, 128)
// Bad - error-prone
val color = 0x80FF0000.toInt()
Camera-Relative Rendering
Always render relative to camera for large coordinates:
// Good - prevents floating point errors
withPositionRelativeToCamera(farAwayPos) {
drawBox(FULL_BOX, Color4b.RED)
}
// Bad - precision issues at large coordinates
translate(farAwayPos)
drawBox(FULL_BOX, Color4b.RED)