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 method | Default | Meaning |
|---|---|---|
builder(center, size) | — | World-space center and box size in blocks |
rotation(Quaternionf) | identity | Orientation of the volume |
color(ColorRgba) | soft blue-gray | Fog color and base alpha |
density(float) | 1.35 | Fog thickness |
noise(scale, speed) | 2.5, 0.12 | Procedural noise size and animation rate |
quality(int) | 48 | Ray-march step upper bound |
maxRenderDistance(double) | 128 | Cull 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 method | Default | Meaning |
|---|---|---|
visibility(blocks) | 5 | Distance 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.85 | Contrast of the wind bands without changing base visibility |
noise(scale, speed) | 0.22, 0.35 | Band size and animation rate |
quality(int) | 64 | Ray-march upper bound (8–128). The renderer auto-caps for large screen coverage and lowers via LOD |
maxRenderDistance(double) | 192 | Cull 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.
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.