Skip to main content

Effects & Built-ins

Every Nebulon visual is a world effect: an immutable description implementing WorldEffect, spawned through a service that returns a live EffectHandle. This page covers the core model and the four "heavyweight" built-in effects. Billboards and SDF shapes are covered in Preset effects.


The effect model

Descriptions

A WorldEffect description is an immutable value describing what to render:

public interface WorldEffect {
EffectType<? extends WorldEffect> type();
double cullingRadius();
default double maxRenderDistance() { ... } // defaults to 128 blocks
default EffectLodPolicy lodPolicy() { ... } // defaults to a standard 3-band policy
}

Descriptions never mutate. Changing an effect means building a new description and giving it to the handle.

Services

NebulonRendering.effects() returns the client-local EffectService:

<E extends WorldEffect> EffectHandle<E> spawn(World world, E effect);
<E extends WorldEffect> EffectHandle<E> spawn(World world, E effect, int lifetimeTicks);
void clear(World world);
int size();

Effects spawned without a lifetime persist until removed. NebulonNetworking.syncedEffects() offers the same shape server-side — see Server synchronization.

Handles

public interface EffectHandle<E extends WorldEffect> extends AutoCloseable {
long id();
boolean isAlive();
void set(E effect); // replace the description
void update(UnaryOperator<E> updater); // transform the current description
void remove(); // close() is an alias
}

Handles and services are safe to call from any thread. GPU work stays on the render thread.

Culling and LOD

Every description supplies a center, a cullingRadius, and a maxRenderDistance. The dispatcher removes effects beyond their render distance and drops sub-pixel effects based on projected radius. Built-ins additionally use a three-band EffectLodPolicy so expensive renderers (fog, snow) reduce ray-march steps at distance. Custom effects can return EffectLodPolicy.NONE, EffectLodPolicy.standard(maxDistance), or their own bands.


Volumetric fog

Ray-marched, oriented fog volumes, correct from both outside and inside the volume. The march stops at scene geometry via Nebulon's shared depth snapshot, so blocks in front of the volume occlude it correctly.

VolumetricFog fog = VolumetricFog.builder(center, new Vec3d(4, 3, 4))
.rotation(new Quaternionf().rotationY(0.5f))
.color(ColorRgba.rgba(0x7a5cffb8))
.density(1.4f)
.noise(2.8f, 0.15f)
.quality(64)
.maxRenderDistance(160)
.build();
Builder methodDefaultMeaning
builder(center, size)World-space center and box size in blocks
rotation(Quaternionf)identityOrientation of the volume
color(ColorRgba)soft blue-grayFog color and base alpha
density(float)1.35Fog thickness
noise(scale, speed)2.5, 0.12Procedural noise size and animation rate
quality(int)48Ray-march step upper bound
maxRenderDistance(double)128Cull distance in blocks

Magic circles

Procedural layered runes, seals, and halos:

// Sensible defaults: lineWidth 0.035, 3 layers, rotationSpeed 0.12, distance 128
MagicCircle circle = MagicCircle.simple(
center, new Vec3d(0, 1, 0), 2.5, ColorRgba.rgba(0x55ccffff));

For full control, construct the record directly:

new MagicCircle(center, normal, radius, lineWidth, color,
layers, // 1–32 concentric layers
runeSeed, // deterministic rune pattern seed
rotationSpeed, // radians per second
maxRenderDistance);

Arc chains

Camera-facing animated chains and energy ribbons — links, lightning, ropes, beams:

// Defaults: width 0.08, linkLength 0.28, flowSpeed 0.5, distance 128
ArcChain chain = ArcChain.between(start, end, ColorRgba.rgba(0xff55ffff));

The full constructor accepts a path of 2 to 4096 points plus width, link length, flow speed, and render distance:

new ArcChain(List.of(a, b, c, d), color, 0.1f, 0.3f, 1.2f, 128.0);

Arc chains draw the visible geometry only. Pair one with a beam light when the laser should also illuminate its surroundings.


Snow storms

SnowStorm is a dedicated bounded atmosphere for whiteouts and blowing snow. It does not reuse the general fog shader — its extinction is measured in world blocks, so a visibility setting stays consistent across differently sized volumes.

SnowStorm storm = SnowStorm.builder(center, new Vec3d(24, 10, 24))
.color(ColorRgba.rgba(0x71879aed))
.visibility(7)
.wind(new Vec3d(1.6, 0, 0.35))
.turbulence(0.9f)
.noise(0.22f, 0.36f)
.quality(48)
.maxRenderDistance(192)
.build();

EffectHandle<SnowStorm> handle = NebulonRendering.effects().spawn(world, storm);
Builder methodDefaultMeaning
visibility(blocks)5Distance at which ~95% of the scene behind is obscured. 3–8 works well for severe storms
wind(Vec3d)(1.5, 0, 0.35)Wind in blocks/second, world axes. Noise is projected into a wind-aligned basis so cloud bands visibly travel
turbulence(float)0.85Contrast of the wind bands without changing base visibility
noise(scale, speed)0.22, 0.35Band size and animation rate
quality(int)64Ray-march upper bound (8–128). The renderer auto-caps for large screen coverage and lowers via LOD
maxRenderDistance(double)192Cull distance

The outside path depth-tests against the storm-box entry surface; inside the volume, the renderer switches to the exit surface with no depth test so the whiteout always covers the view. Both paths clip against the scene-depth snapshot.

Atmosphere and flakes are separate layers

The atmosphere deliberately owns no individual flakes. Pair it with the particle system for visible snow geometry — that keeps atmosphere, particle, collision, and sync budgets explicit, and a server can synchronize a SnowStorm description without sending thousands of particle updates.