VPFX Post Effect Graph Format
This document describes the current VPFX post_effect/main.json graph format.
The graph file is referenced from pack.json through:
"entry_post_effect": "post_effect/main.json"The graph defines:
Which render targets the pack declaresWhich fullscreen passes the pack runsWhich inputs each pass readsWhich target each pass writes toWhich shader files each pass usesThis document is based on the current VPFX v1 graph parser and validator.
1. Basic structure
A VPFX post-effect graph is a JSON object with two required root fields:
{ "targets": {}, "passes": []}Both fields are required.
| Field | Type | Required | Description |
|---|---|---|---|
targets | object | Yes | Declares custom render targets used by the graph. Can be empty. |
passes | array | Yes | Ordered list of fullscreen post-processing passes. Must contain at least one pass. |
A minimal valid graph looks like this:
{ "targets": {}, "passes": [ { "id": "final_composite", "debug_label": "Final Composite", "vertex_shader": "example_minimal_pack:composite/final", "fragment_shader": "example_minimal_pack:composite/final", "inputs": [ { "sampler_name": "In", "target": "minecraft:scene_color" } ], "output": "minecraft:main" } ]}This graph reads minecraft:scene_color, runs one fullscreen shader pass, and writes to minecraft:main.
2. Execution model
VPFX v1 graph passes are executed in the order they appear in the passes array.
Example:
passes[0] -> passes[1] -> passes[2] -> ...For now, community-facing VPFX passes should be treated as fullscreen triangle passes.
A normal color grading pack usually looks like this:
minecraft:scene_color | vfinal_composite pass | vminecraft:mainA simple two-pass pack may look like this:
minecraft:scene_color | vexample_pack:temp | vminecraft:mainThe graph must write to:
minecraft:mainat least once, otherwise it will not produce a visible final image and validation fails.
3. Built-in targets
VPFX currently recognizes these built-in target identifiers:
minecraft:mainminecraft:scene_colorminecraft:scene_depthminecraft:shadow_depthvulkanpostfx:scene_depthvulkanpostfx:shadow_depth3.1 minecraft:main
minecraft:main is the final output target.
Use it as the output of your last visible pass:
"output": "minecraft:main"A graph must write to minecraft:main at least once.
3.2 minecraft:scene_color
minecraft:scene_color is the scene color snapshot captured before VPFX post-processing.
Most packs should start by reading this:
{ "sampler_name": "In", "target": "minecraft:scene_color"}In GLSL:
uniform sampler2D InSampler;3.3 minecraft:scene_depth and vulkanpostfx:scene_depth
These are scene depth input aliases.
They represent the main camera depth captured by VPFX.
Recommended target:
vulkanpostfx:scene_depthExample:
{ "sampler_name": "Depth", "target": "vulkanpostfx:scene_depth"}In GLSL:
uniform sampler2D DepthSampler;Use scene depth for effects such as:
depth fogdepth debug viewsdistance-based color gradingdepth-aware outlines3.4 minecraft:shadow_depth and vulkanpostfx:shadow_depth
These are VPFX shadow depth input aliases.
Recommended target:
vulkanpostfx:shadow_depthExample:
{ "sampler_name": "Shadow", "target": "vulkanpostfx:shadow_depth"}In GLSL:
uniform sampler2D ShadowSampler;Important:
shadow_depth is not scene_depth.shadow_depth is not main camera depth.shadow_depth uses shadow-space depth.shadow_depth uses reversed-Z.shadow_depth should be used with VPFX shadow uniforms.Do not use shadow_depth in your first pack. Read the dedicated shadow guide first.
4. Custom targets
Custom targets are declared in the root targets object.
Example:
{ "targets": { "example_pack:temp": { "scale": 1.0, "use_depth": false, "clear_color": [0.0, 0.0, 0.0, 0.0] } }, "passes": []}A custom target ID must match this pattern:
^[a-z0-9_.-]+:[a-z0-9_./-]+$Good target IDs:
example_pack:tempexample_pack:bloom/downsample_0author_pack:color_gradedebug_pack:shadow_viewBad target IDs:
tempExamplePack:Tempexample pack:tempexample_pack:/badRecommended rule:
Use your pack_id as the target namespace.For example, if your pack_id is:
"pack_id": "example_pack"then your custom targets should look like:
example_pack:tempexample_pack:bloom/downsample_0example_pack:history/color5. Target fields
Each target definition is an object.
Supported fields:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
scale | number | No | 1.0 | Relative size compared to the main screen. |
use_depth | boolean | No | false | Whether the target has a depth buffer. |
clear_color | number[4] | No | runtime default | RGBA clear color. |
persistent | boolean | No | false | Keeps the target alive across frames. |
history | boolean | No | false | Intended for previous-frame sampling. |
ping_pong | boolean | No | false | Intended for double-buffered history targets. |
6. scale
Optional.
"scale": 0.5scale controls the target size relative to the main screen.
Allowed range:
0.0 < scale <= 1.0Examples:
"scale": 1.0Full resolution.
"scale": 0.5Half resolution.
"scale": 0.25Quarter resolution.
Use scaled targets for blur, bloom, downsample, and performance-friendly intermediate effects.
Invalid:
"scale": 0Invalid:
"scale": 2.07. use_depth
Optional.
Default:
"use_depth": falseIf use_depth is true, the target is created with a depth buffer.
Example:
"example_pack:depth_target": { "scale": 1.0, "use_depth": true}Only enable this if you actually need to sample the target’s depth buffer with:
"use_depth_buffer": trueMost color-only post-processing targets do not need depth.
8. clear_color
Optional.
"clear_color": [0.0, 0.0, 0.0, 0.0]It must be an array of exactly four numbers:
redgreenbluealphaExamples:
"clear_color": [0.0, 0.0, 0.0, 0.0]"clear_color": [1.0, 0.0, 1.0, 1.0]Do not rely on clear_color as a replacement for writing all pixels in a pass. A fullscreen post-processing pass should normally cover the whole output target.
9. Persistent, history, and ping-pong targets
The graph parser currently accepts these fields:
"persistent": true"history": true"ping_pong": trueTheir intended meaning:
persistent:Keeps the target storage alive across frames.
history:Marks the target as intended for previous-frame sampling.
ping_pong:Uses two buffers so one can be read as previous frame while the other is written this frame.For early community packs, avoid temporal targets unless you are deliberately testing temporal effects.
Recommended first-pack rule:
Do not use persistent, history, or ping_pong in your first pack.Start with transient targets and simple passes.
10. Pass definition
Each pass is an object inside the passes array.
Example:
{ "id": "final_composite", "debug_label": "Final Composite", "vertex_shader": "example_pack:composite/final", "fragment_shader": "example_pack:composite/final", "inputs": [ { "sampler_name": "In", "target": "minecraft:scene_color" } ], "output": "minecraft:main"}Supported pass fields:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | No | Stable pass identifier. Recommended. |
debug_label | string | No | Human-readable label for logs or debugging. |
vertex_shader | string | Yes | Vertex shader resource ID. |
fragment_shader | string | Yes | Fragment shader resource ID. |
inputs | array | Yes | List of target or texture inputs. Must not be empty. |
output | string | Yes | Output target. Must be minecraft:main or a declared custom target. |
11. id
Optional but strongly recommended.
"id": "final_composite"A pass ID helps logs and diagnostics identify the pass.
Good IDs:
final_compositebloom_downsample_0bloom_upsample_1shadow_debugtone_mapAvoid spaces and special characters.
12. debug_label
Optional.
"debug_label": "Final Composite"This is a human-readable label.
Use it for clarity:
"debug_label": "Bloom Downsample 0"If id exists, VPFX generally uses it as the stronger identity. If id is missing, debug_label may be used as a fallback identity in logs.
13. Shader references
Each pass must specify:
"vertex_shader": "example_pack:composite/final","fragment_shader": "example_pack:composite/final"Shader references use this format:
namespace:pathThe recommended namespace is your pack_id.
The path maps to files inside the zip:
shaders/<path>.vshshaders/<path>.fshExample:
"vertex_shader": "example_pack:composite/final"requires:
shaders/composite/final.vshExample:
"fragment_shader": "example_pack:composite/final"requires:
shaders/composite/final.fshShader paths must not:
be blankuse absolute pathscontain ..contain backslashesGood shader references:
example_pack:composite/finalexample_pack:bloom/downsampleexample_pack:debug/shadow_depthBad shader references:
composite/finalexample_pack:example_pack:../finalexample_pack:/finalexample_pack:folder\final14. Inputs
Each pass must have at least one input.
"inputs": [ { "sampler_name": "In", "target": "minecraft:scene_color" }]Each input must contain:
sampler_nameexactly one of target or textureValid target input:
{ "sampler_name": "In", "target": "minecraft:scene_color"}Valid texture input:
{ "sampler_name": "BlueNoise", "texture": "BlueNoise"}Invalid input with both target and texture:
{ "sampler_name": "Bad", "target": "minecraft:scene_color", "texture": "BlueNoise"}Invalid input with neither:
{ "sampler_name": "Bad"}15. sampler_name
Required.
"sampler_name": "In"The sampler name must match:
^[A-Za-z_][A-Za-z0-9_]*$Good sampler names:
InColorDepthShadowBlueNoise_ColorLutBad sampler names:
1Inputcolor-texturescene colorshadow.depthThe GLSL uniform name is:
<sampler_name>SamplerExample:
"sampler_name": "In"GLSL:
uniform sampler2D InSampler;Example:
"sampler_name": "Shadow"GLSL:
uniform sampler2D ShadowSampler;Sampler names must be unique within one pass.
Invalid:
"inputs": [ { "sampler_name": "In", "target": "minecraft:scene_color" }, { "sampler_name": "In", "target": "vulkanpostfx:scene_depth" }]16. Target inputs
A target input reads from a render target.
Example:
{ "sampler_name": "In", "target": "minecraft:scene_color"}The target value can be:
a built-in targeta custom target declared in the root targets objectBuilt-in examples:
minecraft:scene_colorvulkanpostfx:scene_depthvulkanpostfx:shadow_depthCustom target example:
example_pack:tempA custom target must be declared before it can be used in the graph.
17. Read order rule
Custom targets must be written by an earlier pass before a later pass reads them.
Valid:
{ "targets": { "example_pack:temp": { "scale": 1.0, "use_depth": false } }, "passes": [ { "id": "write_temp", "vertex_shader": "example_pack:composite/copy", "fragment_shader": "example_pack:composite/copy", "inputs": [ { "sampler_name": "In", "target": "minecraft:scene_color" } ], "output": "example_pack:temp" }, { "id": "final", "vertex_shader": "example_pack:composite/final", "fragment_shader": "example_pack:composite/final", "inputs": [ { "sampler_name": "Temp", "target": "example_pack:temp" } ], "output": "minecraft:main" } ]}Invalid:
{ "targets": { "example_pack:temp": { "scale": 1.0, "use_depth": false } }, "passes": [ { "id": "bad_read", "vertex_shader": "example_pack:composite/final", "fragment_shader": "example_pack:composite/final", "inputs": [ { "sampler_name": "Temp", "target": "example_pack:temp" } ], "output": "minecraft:main" } ]}The second example reads example_pack:temp before any pass has written it.
18. Self-read/write rule
A pass must not read from the same custom target it writes to.
Invalid:
{ "id": "bad_feedback", "vertex_shader": "example_pack:composite/final", "fragment_shader": "example_pack:composite/final", "inputs": [ { "sampler_name": "Temp", "target": "example_pack:temp" } ], "output": "example_pack:temp"}This creates a read/write feedback hazard.
Use two targets instead:
example_pack:temp_aexample_pack:temp_bor a later ping-pong/history pattern when temporal target support is documented for your VPFX version.
Reading minecraft:scene_color while writing minecraft:main is safe because VPFX uses a scene color snapshot.
19. Texture inputs
A texture input reads a texture declared in pack.json.
Manifest example:
"textures": { "BlueNoise": { "path": "textures/blue_noise.png", "filter": "nearest", "wrap": "repeat" }}Graph input:
{ "sampler_name": "BlueNoise", "texture": "BlueNoise"}GLSL:
uniform sampler2D BlueNoiseSampler;The texture value is the logical texture name from pack.json, not a file path.
Invalid:
{ "sampler_name": "BlueNoise", "texture": "textures/blue_noise.png"}Texture inputs do not support:
"use_depth_buffer": true20. Depth inputs
Input objects may contain:
"use_depth_buffer": trueThis is only valid for target inputs, not texture inputs.
Example:
{ "sampler_name": "Depth", "target": "vulkanpostfx:scene_depth"}Usually, explicit depth target names are preferred over use_depth_buffer.
Recommended scene depth input:
{ "sampler_name": "Depth", "target": "vulkanpostfx:scene_depth"}Recommended shadow depth input:
{ "sampler_name": "Shadow", "target": "vulkanpostfx:shadow_depth"}For a custom target depth buffer, the target must be declared with:
"use_depth": trueExample:
{ "targets": { "example_pack:depth_target": { "scale": 1.0, "use_depth": true } }, "passes": [ { "id": "read_depth_target", "vertex_shader": "example_pack:composite/final", "fragment_shader": "example_pack:composite/final", "inputs": [ { "sampler_name": "Depth", "target": "example_pack:depth_target", "use_depth_buffer": true } ], "output": "minecraft:main" } ]}Do not use use_depth_buffer with texture inputs.
Invalid:
{ "sampler_name": "Noise", "texture": "BlueNoise", "use_depth_buffer": true}21. Output targets
Each pass must have one output.
Valid outputs:
minecraft:maina declared custom targetExample final output:
"output": "minecraft:main"Example custom output:
"output": "example_pack:temp"Invalid output:
"output": "vulkanpostfx:scene_depth"Invalid output:
"output": "vulkanpostfx:shadow_depth"Depth targets are input-only.
The graph must include at least one pass that writes to:
minecraft:main22. One-pass color grading example
{ "targets": {}, "passes": [ { "id": "final_composite", "debug_label": "Final Composite", "vertex_shader": "example_pack:composite/final", "fragment_shader": "example_pack:composite/final", "inputs": [ { "sampler_name": "In", "target": "minecraft:scene_color" } ], "output": "minecraft:main" } ]}This is the recommended first graph.
23. Two-pass example
{ "targets": { "example_pack:temp": { "scale": 1.0, "use_depth": false, "clear_color": [0.0, 0.0, 0.0, 0.0] } }, "passes": [ { "id": "first_pass", "debug_label": "First Pass", "vertex_shader": "example_pack:composite/first", "fragment_shader": "example_pack:composite/first", "inputs": [ { "sampler_name": "In", "target": "minecraft:scene_color" } ], "output": "example_pack:temp" }, { "id": "final_pass", "debug_label": "Final Pass", "vertex_shader": "example_pack:composite/final", "fragment_shader": "example_pack:composite/final", "inputs": [ { "sampler_name": "Temp", "target": "example_pack:temp" } ], "output": "minecraft:main" } ]}This is the basic pattern for multi-pass effects.
24. Downsample target example
{ "targets": { "example_pack:half_res": { "scale": 0.5, "use_depth": false, "clear_color": [0.0, 0.0, 0.0, 0.0] } }, "passes": [ { "id": "downsample", "vertex_shader": "example_pack:composite/downsample", "fragment_shader": "example_pack:composite/downsample", "inputs": [ { "sampler_name": "In", "target": "minecraft:scene_color" } ], "output": "example_pack:half_res" }, { "id": "final", "vertex_shader": "example_pack:composite/final", "fragment_shader": "example_pack:composite/final", "inputs": [ { "sampler_name": "HalfRes", "target": "example_pack:half_res" }, { "sampler_name": "Scene", "target": "minecraft:scene_color" } ], "output": "minecraft:main" } ]}This pattern is useful for blur and bloom-style effects.
25. Scene depth example
Make sure your pack.json declares:
"capabilities": { "scene_color": true, "scene_depth": true, "shadow_depth": false, "custom_targets": true, "compute": false}Graph:
{ "targets": {}, "passes": [ { "id": "depth_debug", "vertex_shader": "example_pack:debug/depth", "fragment_shader": "example_pack:debug/depth", "inputs": [ { "sampler_name": "Depth", "target": "vulkanpostfx:scene_depth" } ], "output": "minecraft:main" } ]}GLSL:
uniform sampler2D DepthSampler;26. Shadow depth example
Make sure your pack.json declares:
"capabilities": { "scene_color": true, "scene_depth": true, "shadow_depth": true, "custom_targets": true, "compute": false},"targets": { "shadow_depth": "vulkanpostfx:shadow_depth"}Graph:
{ "targets": {}, "passes": [ { "id": "shadow_debug", "vertex_shader": "example_pack:debug/shadow", "fragment_shader": "example_pack:debug/shadow", "inputs": [ { "sampler_name": "Shadow", "target": "vulkanpostfx:shadow_depth" } ], "output": "minecraft:main" } ]}GLSL:
uniform sampler2D ShadowSampler;Remember:
shadow_depth is reversed-Z shadow-space depth.Do not treat it as main camera depth.27. Current validation rules
The current graph validator checks:
The graph has at least one pass.Custom target IDs are valid.Target scale is finite and in (0, 1].Each pass has at least one input.Each sampler_name is valid.Sampler names are unique within one pass.Each input has exactly one of target or texture.Target inputs reference built-in or declared targets.Texture inputs reference textures declared in pack.json.Custom targets are written before being read.A pass does not read and write the same custom target.Each output is minecraft:main or a declared target.At least one pass writes to minecraft:main.Capabilities required by the pack are supported by the runtime.If validation fails, VPFX skips the pack and logs an error.
28. Common graph errors
Missing targets
Invalid:
{ "passes": []}Valid:
{ "targets": {}, "passes": []}Missing passes
Invalid:
{ "targets": {}}Valid:
{ "targets": {}, "passes": [ { "id": "final", "vertex_shader": "example_pack:composite/final", "fragment_shader": "example_pack:composite/final", "inputs": [ { "sampler_name": "In", "target": "minecraft:scene_color" } ], "output": "minecraft:main" } ]}Empty passes
Invalid:
{ "targets": {}, "passes": []}A graph must contain at least one pass.
Missing output to minecraft:main
Invalid:
{ "targets": { "example_pack:temp": { "scale": 1.0 } }, "passes": [ { "id": "only_temp", "vertex_shader": "example_pack:composite/final", "fragment_shader": "example_pack:composite/final", "inputs": [ { "sampler_name": "In", "target": "minecraft:scene_color" } ], "output": "example_pack:temp" } ]}The pack writes to example_pack:temp, but never writes to minecraft:main.
Invalid sampler name
Invalid:
{ "sampler_name": "Scene Color", "target": "minecraft:scene_color"}Valid:
{ "sampler_name": "SceneColor", "target": "minecraft:scene_color"}Duplicate sampler names in one pass
Invalid:
"inputs": [ { "sampler_name": "In", "target": "minecraft:scene_color" }, { "sampler_name": "In", "target": "vulkanpostfx:scene_depth" }]Valid:
"inputs": [ { "sampler_name": "Scene", "target": "minecraft:scene_color" }, { "sampler_name": "Depth", "target": "vulkanpostfx:scene_depth" }]Texture input not declared in manifest
Invalid graph input:
{ "sampler_name": "Noise", "texture": "BlueNoise"}if pack.json does not declare:
"textures": { "BlueNoise": { "path": "textures/blue_noise.png" }}Reading a target before writing it
Invalid:
{ "targets": { "example_pack:temp": { "scale": 1.0 } }, "passes": [ { "id": "final", "vertex_shader": "example_pack:composite/final", "fragment_shader": "example_pack:composite/final", "inputs": [ { "sampler_name": "Temp", "target": "example_pack:temp" } ], "output": "minecraft:main" } ]}example_pack:temp is declared, but no earlier pass writes to it.
29. Error code reference
Common parser and validator error codes:
| Code | Meaning |
|---|---|
G001 | Graph entry file not found. |
G002 | Graph root must be an object, or graph has no passes depending on validation stage. |
G003 | Failed to read graph JSON, or pass has no inputs depending on validation stage. |
G004 | Output target not declared. |
G005 | Input target not found. |
G006 | Invalid depth-buffer usage or missing runtime depth capability. |
G007 | Invalid target identifier. |
G008 | Invalid sampler name. |
G009 | Duplicate sampler name within one pass. |
G010 | Target definition must be an object. |
G011 | Invalid target scale. |
G012 | Input must contain exactly one of target or texture. |
G013 | Texture input not declared in manifest. |
G014 | Texture input cannot use use_depth_buffer=true. |
G015 | Self-read/write hazard. |
G016 | Graph does not write to minecraft:main. |
G017 | Custom target is read before being written. |
G020 | Each pass must be an object. |
G030 | Each input must be an object. |
G031 | use_depth_buffer must be boolean. |
G032 | Input must contain exactly one of target or texture. |
G033 | Texture input cannot use use_depth_buffer=true. |
G040 | clear_color must be an array. |
G041 | clear_color must contain exactly 4 elements. |
G042 | clear_color elements must be numbers. |
G050 | Missing required object field. |
G052 | Missing required array field. |
G054 | Missing required string field. |
G056 | Required string field is blank. |
G058 | Optional string field has wrong type. |
G059 | Optional boolean field has wrong type. |
S001 | Vertex shader file not found. |
S002 | Fragment shader file not found. |
S003 | Invalid shader resource ID or shader path. |
S004 | Vertex shader include/preprocess error. |
S005 | Fragment shader include/preprocess error. |
30. Graph authoring checklist
Before sharing a pack, check:
post_effect/main.json exists at the path referenced by pack.json.The root JSON object contains targets and passes.targets is an object, even if empty.passes is a non-empty array.Every custom target ID is namespaced.Every scale is in (0, 1].Every pass has vertex_shader and fragment_shader.Every pass has at least one input.Every sampler_name is GLSL-safe.Sampler names are unique within each pass.Every input has exactly one of target or texture.Every texture input is declared in pack.json.Every custom target is written before being read.No pass reads and writes the same custom target.At least one pass outputs to minecraft:main.Shader files exist under shaders/.Shader references use namespace:path.31. Recommended first graph
For a first VPFX pack, use this:
{ "targets": {}, "passes": [ { "id": "final_composite", "debug_label": "Final Composite", "vertex_shader": "my_first_vpfx_pack:composite/final", "fragment_shader": "my_first_vpfx_pack:composite/final", "inputs": [ { "sampler_name": "In", "target": "minecraft:scene_color" } ], "output": "minecraft:main" } ]}Required files:
shaders/composite/final.vshshaders/composite/final.fshThis graph is intentionally simple. Make this work before adding custom targets, depth, shadow depth, textures, or temporal effects.
32. Next document
This document covers:
post_effect/main.json this work before adding custom targets, depth, shadow depth, textures, or temporal effects.
---
##targetspassesinputsoutputssampler namescustom target validationdepth and shadow target referencesThe next document should cover:
05 - VPFX Uniform ReferenceThat document should explain:
Built-in uniform blockFrame dataScreen sizeTimeScene depth parametersShadow matricesShadow originLight directionShadow map size