Rendering Pipeline (OptiFine, ShadersMod)
Shadow Map G-Buffer (
Shadows run first, and are responsible for generating the shadow map, which is effectively an image of how far away everything is from the sun. Other objects that render later can then compare their own distance to that of the shadow map to figure out if there's something between it and the sun or not.
G-Buffers (programs beginning with
These files are used to render terrain, entities, the sky, and almost everything else in the game. The specific name of the file tells you a bit more about what it's used to render.
skybasic runs first, and handles the main sky color. This is followed by
skytextured, which handles the sun and moon. Up next comes
terrain, which handles all opaque blocks. Wind effects for grass are typically implemented here.
block (tile entities) are next. Entities include everything from creepers to cows, and tile entities covers things like chests and banners.
textured_lit handle particles.
deferred comes next, but we'll explain that later. Lastly,
weather handles rain and snow, and
water handles all translucent blocks. There are a few other G-Buffer programs, but these are the main ones. GUIs, the F3 menu, and other overlays are not handled by any of these programs.
composites run after all geometry in the world has finished rendering, and
final runs after the
composites. These render over the entire screen, so you can use them to apply post-processing effects, or anything else that has to be done after all the G-Buffers. Many shader packs also use this for lighting, ambient occlusion, fancy clouds (not the square vanilla kind), reflections, refractions and many, many other effects.
This is similar to the
composite programs, but runs in the middle of terrain rendering instead of after it. More specifically, they run after all opaque objects have rendered, and before any transparent objects. There is no real goal here, so what you do here is up to you. Some specific effects require it, others don't. Like the
composites, you can write to as many buffers as you want, with whatever data you want.
List of all programs (in order)
|<fullscreen pass for shadow map>||none|
|gbuffers_line||block selection, fishing line||gbuffers_basic|
|gbuffers_textured_lit||lit/emissive particles, world border||gbuffers_textured|
|gbuffers_skybasic||sky, horizon, stars, void||gbuffers_basic|
|gbuffers_terrain||opaque geometry (including cutout transparency)||gbuffers_textured_lit|
|gbuffers_damagedblock||damaged block overlay||gbuffers_terrain|
|gbuffers_entities_glowing||glowing entities (spectral effect)||gbuffers_entities|
|gbuffers_armor_glint||armor glint overlay||gbuffers_textured|
|gbuffers_spidereyes||eyes of spiders, endermen and enderdragons||gbuffers_textured|
|gbuffers_hand||hand, opaque handheld items||gbuffers_textured_lit|
|gbuffers_hand_water||translucent handheld items||gbuffers_hand|
|final||<fullscreen pass, unaffected by render scale>||none|
Framebuffer attachments (or sometimes just dubbed buffers) can transfer data between fragment programs and any other program that runs after it. Some restrictions apply that prevent certain buffers from being read/written to from specific programs. A buffer holds a value for every pixel on the screen. Typically, each value will be an RGBA color. They can also be formatted to use a specific precision (number of bits) for each component of the color. When a fragment shader writes to a buffer, the previous value is typically either overwritten, or blended together with the new value using the alpha component of the new value.
|Normalized||Signed Normalized||Float||Integer||Unsigned Integer|
All formats follow the same structure of
<channel> <bit> <data type>. The data type can be one of the following:
- Normalized buffers can store unsigned (positive) normalized values. This means that they have a range of [0, 1].
- Signed Normalized buffers behave similarly, but they have an additional sign bit resulting in a range of [-1, 1].
- Float buffers can store floating point values.
- Integer buffers can store signed (positive and negative) integer values.
- Unsigned Integer buffers can store unsigned (positive) integer values.
The only mixed type that is commonly used is
R11F_G11F_B10F. As the name suggests, it has no alpha channel and uses a total of 32 bits, which is very common and should be as fast as
RGBA8 (the default buffer format), while being faster than
RGB16F. It is the fastest available float buffer format.
The format of framebuffer attachments can be changed by using
/* const int <framebuffer>Format = <format>; */
First, you'll need to declare the framebuffer attachment using
uniform <type> <framebuffer>;. If the buffer you want to read from holds floating point values (e.g. has the default format),
<type> should be
sampler2D. It should be
isampler2D when reading from integer buffers, and
usampler2D when reading from unsigned integer buffers. You can read more about sampler types here. A list of buffers which can be read from can be found here. Once you've declared it, you can get data from it using either
texture2D(<framebuffer>, <coordinates>)(GLSL 1.2) or
texelFetch(<framebuffer>, <coordinates>)(GLSL 1.3+).
This will return a four-component vector, which can then be swizzled if you only want certain channels. The
<coordinates> parameter should be a
vec2 in the range of [0, 1] for
texture() and an
ivec2 in the range of [0, size) for
texelFetch(). In contrast to
texelFetch() returns the exact and non-interpolated value at the provided coordinates. You can read more about the different texture lookup functions here.
This section is specific to 2D framebuffer attachments, but reading from 1D and 3D ones works in an analogous manner (e.g.
First, you'll need declare which framebuffers your program should write to using the
/* DRAWBUFFERS: */ directive. It's explained best by using an example:
/* DRAWBUFFERS:625 */ means that the program will write to
colortex5 in that exact order. In this case,
gl_FragDatawill write to
gl_FragDatawill write to
gl_FragDatawill write to
If no framebuffer attachments are explicitly selected, the program will automatically write to the first eight which will hurt performance, overwrite existing data and cause undefined behavior if
gl_FragData[n] does not hold any value.
gl_FragData has been deprecated in GLSL 1.3 and was removed in later versions. It has been replaced with the
layout(location = n) out vec4 <name>; in the global scope and then writing to
<name> (which is a global variable) has the exact same effect as writing to
In the latest versions of OptiFine, you can also use
/* RENDERTARGETS: */ instead of
/* DRAWBUFFERS: */ to write to buffers whose index is 10 or higher. When using
/* RENDERTARGETS: */, the list should be separated by commas and prefixed with a space. The above example would look like:
/* RENDERTARGETS: 6,2,5 */. At the time of writing this, this directive is tied to the OptiFine version adding the additional 8 framebuffers, which is only available in 1.16.4+ and will be in Iris as of 1.1.4.