Skip to content

Masks

A top-level mask block restricts where a post-processing effect applies. The coverage is computed once per frame in a prepass at screen layer 0 (the only layer where scene depth is intact), so the effect itself may run at any screen layer and simply reads the result. A mask is a composition ("op": "union" | "intersection" | "difference") of leaves; every leaf is one of:

  • a screen shapecircle, ellipse, rect, polygon — classified in UV, no depth needed;
  • a world volumesphere or box — classified against the depth-reconstructed world position;
  • a block leaf (the selected blocks' model geometry) or a custom shape (a registered composed SDF or GLSL plugin).

Composition nests on the left only: the right operand of an op must be a leaf, so write intersection(union(A, B), C) (which flattens to min(max(A, B), C)), not union(A, intersection(B, C)) — the latter would flatten to min(max(A, B), C) and is a parse error naming the offending op. A mask carries at most two custom leaves and at most one block leaf (all block leaves share one geometry scratch), both enforced at parse.

invert flips the composed coverage; softness is the edge falloff width (screen units or world blocks); fill: solid|stroke with stroke_width draws a boundary band. softness scales the composed-result falloff and a GLSL plugin's edge. A block leaf's coverage is rasterised full or absent per fragment, so its edge is hard by design and softness does not apply. Every numeric leaf (radius, half_width, center, softness, …) takes a number, { "from": "<node>" } or a world binding ({ "bind": "entity", ... }), so a shape can follow an entity.

A scalar binding derives a number from its source. "derive": "distance" (the default) is the camera distance to the source in blocks, so "radius": { "bind": "entity", "selector": "@e[type=minecraft:villager,limit=1]", "derive": "distance" } makes the sphere grow as the viewer backs away from the villager (see vfxweaver:mask_pulse_demo); "derive": "point" yields the world position (valid on center only) and "derive": "screen_rect" projects the entity's bounding box into a UV rectangle (valid as screen_rect on a screen rect). A binding that cannot be resolved — the source entity is absent or off-screen (or outside the client's tracking range), or there is no camera/player state — fails that leaf closed: the leaf contributes zero coverage and never falls back to its literal default, which for an unbound rect would be the whole screen. Only that leaf is dropped, so an entity leaving the view no longer takes a still-resolved world leaf down with it. An unresolved leaf can never expand coverage (a zero leaf is neutral for union/difference and contracts intersection), and an invert mask does not flip an all-unresolved (empty) result into full-screen coverage. The unresolved source still reports once through VFXLog.warnOnce.

Two caveats. An entity outside the client's tracking range is genuinely unresolvable on the client — that is a client-search limitation, not a bug, and the bound leaf contributes nothing there. To drive a mask from data the client does not have, set its centre from the server every tick with VFXAPI.sendMaskMove(player, effect, leaf, pos) (or sendSetParam on the reserved mask.p<N>.center_* params) instead of binding the leaf to the entity.

Layer note. The coverage prepass reads the intact scene depth once at screen layer 0, and the coverage it produces is screen-space (derived from world depth). An effect that consumes the mask at a later layer — e.g. "screen_layer": 1 — runs after the first-person hand and other late-drawn geometry, so the hand is tinted wherever a masked block lies behind it, even though the hand is nearer. To avoid this, run the masked effect at screen_layer: 0, or depth-occlude the consumer against a layer-0 depth snapshot.

Depth note. The scene depth is read on every supported node. Each node's convention is proven from the client jars and injected into the shader as a per-node flag: 26.2 is reversed (near = 1), 26.1.2 and 1.21.11 are standard (near = 0). A mask that needs depth (a world leaf, an aura volume, or a block leaf) still fails closed — it contributes zero coverage and its depth-tested block pass is disabled — when no trustworthy depth is available (no depth attachment, or the camera snapshot is not ready on the first frame), never a wrong sample. A purely screen mask needs no depth and works on every node and every layer.

Concurrency note. Two simultaneous plays of one masked definition share a single coverage target, so both use the first play's animated centre/radius/softness. To have two masks with independent regions on screen at once, put them in two distinct definitions.

The mask's numeric leaves are ordinary animatable effect parameters under reserved names: mask.p<N>.center_x|center_y|center_z, .rotation, .p<J> (the per-shape parameter J), .soft (falloff), .stroke (stroke width) and .field_amount/.field_scale; <N> is the leaf index in declaration order (0-based). Because they are ordinary params, keyframes, expr, graph { "from": node } driven inputs, datapack bindings and every live-edit API (sendSetParam, sendKeyframe, setParam, …) work on them unchanged. The mask. prefix is reserved (see the Java API).

World-volume evaluation: volume

A sphere/box leaf carries an optional "volume" field choosing how the volume is evaluated against the scene. Both modes are first-class looks; the default is "surface".

volume What it looks like When to use
"surface" (default) The visible surface is classified: a pixel is covered where the depth-reconstructed point lies inside the volume, so only geometry inside the region is tinted and the air/sky around it is not. This is the original look. "Affect the things standing in this region" — the tint follows the objects, not the space.
"aura" The pixel's view ray is cast at the volume and the whole volume is filled — including air and sky — except where a nearer surface occludes it. A glow/haze field that occupies the whole region, so it reads as a volume of light rather than a coat of paint on the objects.

Both modes fade their edge over the leaf's softness. In aura mode the coverage is sampled at the volume depth nearest the viewer along the pixel's view ray, so the interior fills to full coverage and the silhouette edge fades from both sides; coverage is 0 where a nearer surface occludes the volume and 0 where the ray misses it. Sky and missing depth count as "nothing occludes", so the aura still fills the volume's silhouette instead of vanishing against the sky.

{
    "type": "color_grade",
    "duration": 800,
    "loop": true,
    "persistent": true,
    "params": { "screen_layer": 1, "tint_r": 1.0, "tint_g": 0.2, "tint_b": 0.2 },
    "mask": {
        "a": {
            "shape": "sphere",
            "space": "world",
            "volume": "aura",
            "center": { "bind": "entity", "selector": "@e[type=minecraft:villager,limit=1]", "point": "center" },
            "radius": 6.0,
            "softness": 0.5
        }
    }
}

Block masks: occlude

A block leaf carries an optional "occlude" boolean controlling whether its rasterised model geometry is occluded by the scene. Both looks are first-class; the default is true.

occlude What it looks like When to use
true (default) A fragment of a selected block is drawn only where it is nearer than the scene surface at that pixel; a wall in front hides the mask. The mask reads as a tint on the blocks themselves — what the player can actually see.
false Today's see-through ("x-ray") look: the selected blocks are marked regardless of what is in front of them. Highlighting blocks through walls, e.g. locating a vein behind terrain.

occlude is only valid on a block leaf; setting it on any other family is a parse error. It is evaluated per fragment in the geometry pass at screen layer 0, against the main target's depth buffer (per-node convention: 26.2 reversed near = 1, 26.1.2/1.21.11 standard near = 0); it never runs a per-pixel block lookup.

{
    "type": "color_grade",
    "duration": 600,
    "loop": true,
    "persistent": true,
    "params": { "screen_layer": 1, "saturation": 0.2, "tint_r": 0.6, "tint_g": 0.8, "tint_b": 1.0 },
    "mask": {
        "a": {
            "shape": "block",
            "blocks": ["minecraft:stone", "minecraft:cobblestone"],
            "center": [0.0, 64.0, 0.0],
            "radius": 16.0,
            "softness": 1.0,
            "occlude": true
        }
    }
}

Reference examples. vfxweaver:mask_block_demo tints the stone-family blocks around [0, 64, 0] with "occlude": true (a wall in front hides the tint); vfxweaver:mask_block_xray_demo is the same selection with "occlude": false — play one, then the other, to compare the occluded and see-through looks. vfxweaver:mask_entity_demo is an entity-following sphere with a fixed radius of 4 blocks in aura mode plus a screen rectangle; vfxweaver:mask_world_demo is the same fixed-radius sphere in the default surface mode — play one, then the other, to compare the two looks. vfxweaver:mask_pulse_demo keeps the entity-following sphere but binds its radius with "derive": "distance", so the sphere grows with the viewer's distance from the villager (a proximity pulse). vfxweaver:mask_screen_demo is a screen-only mask and works at any layer. vfxweaver:mask_custom_demo is a registered composed SDF (a ringed volume); the built-in GLSL plugin demo vfxweaver:mask_custom_glsl_demo uses vfxweaver:ringed_glsl — a screen ring with 8 petal-modulated lobes — to prove the injected plugin source runs.

Showcase

show_mask_screen_demo

/vfx play vfx_demos:show_mask_screen_demo

Its datapack definition:

{
    "type": "invert",
    "duration": 200,
    "easing": "ease_in_out_cubic",
    "persistent": true,
    "loop": true,
    "fade_ticks": 16,
    "params": {
        "intensity": {
            "keyframes": [
                {
                    "time": 0,
                    "value": 0.3
                },
                {
                    "time": 100,
                    "value": 1.0
                },
                {
                    "time": 200,
                    "value": 0.3
                }
            ]
        },
        "mask.p0.p0": {
            "keyframes": [
                {
                    "time": 0,
                    "value": 0.25
                },
                {
                    "time": 100,
                    "value": 0.4
                },
                {
                    "time": 200,
                    "value": 0.25
                }
            ]
        }
    },
    "mask": {
        "invert": false,
        "op": "difference",
        "a": {
            "shape": "rect",
            "space": "screen",
            "center": [
                0.5,
                0.5
            ],
            "half_width": 0.35,
            "half_height": 0.35,
            "corner_radius": 0.08,
            "softness": 0.02
        },
        "b": {
            "shape": "circle",
            "space": "screen",
            "center": [
                0.5,
                0.5
            ],
            "radius": 0.18,
            "softness": 0.05
        }
    }
}

show_mask_custom_glsl_demo

/vfx play vfx_demos:show_mask_custom_glsl_demo

Its datapack definition:

{
    "type": "color_grade",
    "duration": 200,
    "easing": "ease_in_out_cubic",
    "persistent": true,
    "loop": true,
    "fade_ticks": 16,
    "params": {
        "screen_layer": 1,
        "saturation": {
            "keyframes": [
                {
                    "time": 0,
                    "value": 1.0
                },
                {
                    "time": 100,
                    "value": 0.15
                },
                {
                    "time": 200,
                    "value": 1.0
                }
            ]
        },
        "contrast": 1.0,
        "brightness": 1.0,
        "tint_r": 0.3,
        "tint_g": 0.85,
        "tint_b": 1.0,
        "mask.p0.p0": {
            "keyframes": [
                {
                    "time": 0,
                    "value": 0.25
                },
                {
                    "time": 100,
                    "value": 0.45
                },
                {
                    "time": 200,
                    "value": 0.25
                }
            ]
        }
    },
    "mask": {
        "invert": false,
        "a": {
            "shape": "vfxweaver:ringed_glsl",
            "space": "screen",
            "center": [
                0.5,
                0.5
            ],
            "params": [
                0.5,
                0.5,
                0.28,
                0.02,
                8.0,
                0.25
            ],
            "softness": 0.03
        }
    }
}

show_mask_block_demo

/vfx play vfx_demos:show_mask_block_demo

Its datapack definition:

{
    "type": "color_grade",
    "duration": 200,
    "easing": "ease_in_out_cubic",
    "persistent": true,
    "loop": true,
    "fade_ticks": 16,
    "params": {
        "screen_layer": 0,
        "saturation": {
            "keyframes": [
                {
                    "time": 0,
                    "value": 1.0
                },
                {
                    "time": 100,
                    "value": 0.2
                },
                {
                    "time": 200,
                    "value": 1.0
                }
            ]
        },
        "contrast": 1.0,
        "brightness": 1.0,
        "tint_r": 0.6,
        "tint_g": 0.8,
        "tint_b": 1.0,
        "mask.p0.p0": {
            "keyframes": [
                {
                    "time": 0,
                    "value": 8.0
                },
                {
                    "time": 100,
                    "value": 14.0
                },
                {
                    "time": 200,
                    "value": 8.0
                }
            ]
        }
    },
    "mask": {
        "invert": false,
        "a": {
            "shape": "block",
            "blocks": [
                "minecraft:smooth_stone",
                "minecraft:white_concrete",
                "minecraft:red_concrete",
                "minecraft:blue_concrete"
            ],
            "center": [
                2000.0,
                101.0,
                2000.0
            ],
            "radius": 12.0,
            "softness": 1.0,
            "occlude": true
        }
    }
}

show_mask_block_xray_demo

/vfx play vfx_demos:show_mask_block_xray_demo

Its datapack definition:

{
    "type": "color_grade",
    "duration": 200,
    "easing": "ease_in_out_cubic",
    "persistent": true,
    "loop": true,
    "fade_ticks": 16,
    "params": {
        "screen_layer": 0,
        "saturation": {
            "keyframes": [
                {
                    "time": 0,
                    "value": 1.0
                },
                {
                    "time": 100,
                    "value": 0.2
                },
                {
                    "time": 200,
                    "value": 1.0
                }
            ]
        },
        "contrast": 1.0,
        "brightness": 1.0,
        "tint_r": 1.0,
        "tint_g": 0.5,
        "tint_b": 0.2,
        "mask.p0.p0": {
            "keyframes": [
                {
                    "time": 0,
                    "value": 8.0
                },
                {
                    "time": 100,
                    "value": 14.0
                },
                {
                    "time": 200,
                    "value": 8.0
                }
            ]
        }
    },
    "mask": {
        "invert": false,
        "a": {
            "shape": "block",
            "blocks": [
                "minecraft:smooth_stone",
                "minecraft:white_concrete",
                "minecraft:red_concrete",
                "minecraft:blue_concrete"
            ],
            "center": [
                2000.0,
                101.0,
                2000.0
            ],
            "radius": 12.0,
            "softness": 1.0,
            "occlude": false
        }
    }
}

show_mask_custom_demo

mask_custom_demo showcase

/vfx play vfx_demos:show_mask_custom_demo

Its datapack definition:

{
    "type": "color_grade",
    "duration": 200,
    "easing": "ease_in_out_cubic",
    "persistent": true,
    "loop": true,
    "fade_ticks": 16,
    "params": {
        "screen_layer": 1,
        "saturation": {
            "keyframes": [
                {
                    "time": 0,
                    "value": 1.0
                },
                {
                    "time": 100,
                    "value": 0.3
                },
                {
                    "time": 200,
                    "value": 1.0
                }
            ]
        },
        "contrast": {
            "keyframes": [
                {
                    "time": 0,
                    "value": 1.0
                },
                {
                    "time": 100,
                    "value": 1.25
                },
                {
                    "time": 200,
                    "value": 1.0
                }
            ]
        },
        "brightness": 1.0,
        "tint_r": 1.0,
        "tint_g": 0.7,
        "tint_b": 0.3
    },
    "mask": {
        "invert": false,
        "a": {
            "shape": "vfxweaver:ringed_glsl",
            "space": "screen",
            "center": [
                0.5,
                0.5
            ],
            "params": [
                0.5,
                0.5,
                0.3,
                0.03,
                8.0,
                0.25
            ],
            "softness": 0.03
        }
    }
}

show_mask_entity_demo

/vfx play vfx_demos:show_mask_entity_demo

Its datapack definition:

{
    "type": "color_grade",
    "duration": 200,
    "easing": "ease_in_out_cubic",
    "persistent": true,
    "loop": true,
    "fade_ticks": 16,
    "params": {
        "screen_layer": 1,
        "saturation": {
            "keyframes": [
                {
                    "time": 0,
                    "value": 1.0
                },
                {
                    "time": 100,
                    "value": 0.0
                },
                {
                    "time": 200,
                    "value": 1.0
                }
            ]
        },
        "contrast": 1.0,
        "brightness": 1.0,
        "tint_r": 1.0,
        "tint_g": 0.2,
        "tint_b": 0.2
    },
    "mask": {
        "invert": false,
        "a": {
            "shape": "sphere",
            "space": "world",
            "volume": "aura",
            "center": {
                "bind": "entity",
                "selector": "@e[type=minecraft:villager,limit=1]",
                "point": "center"
            },
            "radius": 4.0,
            "softness": 0.5
        }
    }
}

show_mask_world_demo

mask_world_demo showcase

/vfx play vfx_demos:show_mask_world_demo

Its datapack definition:

{
    "type": "color_grade",
    "duration": 200,
    "easing": "ease_in_out_cubic",
    "persistent": true,
    "loop": true,
    "fade_ticks": 16,
    "params": {
        "screen_layer": 1,
        "saturation": {
            "keyframes": [
                {
                    "time": 0,
                    "value": 1.0
                },
                {
                    "time": 100,
                    "value": 0.0
                },
                {
                    "time": 200,
                    "value": 1.0
                }
            ]
        },
        "contrast": 1.0,
        "brightness": 1.0,
        "tint_r": 0.2,
        "tint_g": 0.4,
        "tint_b": 1.0
    },
    "mask": {
        "invert": false,
        "a": {
            "shape": "sphere",
            "space": "world",
            "volume": "surface",
            "center": {
                "bind": "entity",
                "selector": "@e[type=minecraft:villager,limit=1]",
                "point": "center"
            },
            "radius": 4.0,
            "softness": 0.5
        }
    }
}

show_mask_pulse_demo

/vfx play vfx_demos:show_mask_pulse_demo

Its datapack definition:

{
    "type": "color_grade",
    "duration": 200,
    "easing": "ease_in_out_cubic",
    "persistent": true,
    "loop": true,
    "fade_ticks": 16,
    "params": {
        "screen_layer": 0,
        "saturation": {
            "keyframes": [
                {
                    "time": 0,
                    "value": 1.0
                },
                {
                    "time": 100,
                    "value": 0.0
                },
                {
                    "time": 200,
                    "value": 1.0
                }
            ]
        },
        "contrast": 1.0,
        "brightness": 1.0,
        "tint_r": 1.0,
        "tint_g": 0.2,
        "tint_b": 0.2
    },
    "mask": {
        "invert": false,
        "a": {
            "shape": "sphere",
            "space": "world",
            "volume": "aura",
            "center": {
                "bind": "entity",
                "selector": "@e[tag=vfx_showcase,limit=1]",
                "point": "center"
            },
            "radius": {
                "bind": "entity",
                "selector": "@e[tag=vfx_showcase,limit=1]",
                "derive": "distance"
            },
            "softness": 0.5
        }
    }
}