Rendering Pipeline (OptiFine, ShadersMod)
Pipeline Stages
Shadow Map G-Buffer (shadow
)
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 gbuffers_
)
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. entities
and block
(tile entities) are next. Entities include everything from creepers to cows, and tile entities covers things like chests and banners. textured
and 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.
Composite (composite<N>
and final
)
composite
s run after all geometry in the world has finished rendering, and final
runs after the composite
s. 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.
Deferred (deferred<N>
)
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 composite
s, you can write to as many buffers as you want, with whatever data you want.
List of all programs (in order)
Name | Rendered Geometry | Fallback |
---|---|---|
Setup | ||
setup
setup<1-99> |
compute [Iris only] | none |
Begin | ||
begin
begin<1-99> |
compute [Iris only] | none |
Shadow Map | ||
shadow | all | none |
Shadow Composite | ||
shadowcomp
shadowcomp<1-99> |
<fullscreen pass for shadow map> | none |
Prepare | ||
prepare
prepare<1-99> |
<fullscreen pass> | none |
G-Buffers | ||
gbuffers_basic | leash | none |
gbuffers_line | block selection, fishing line | gbuffers_basic |
gbuffers_textured | particles | gbuffers_basic |
gbuffers_textured_lit | lit/emissive particles, world border | gbuffers_textured |
gbuffers_skybasic | sky, horizon, stars, void | gbuffers_basic |
gbuffers_skytextured | sun, moon | gbuffers_textured |
gbuffers_clouds | clouds | gbuffers_textured |
gbuffers_terrain | opaque geometry (including cutout transparency) | gbuffers_textured_lit |
gbuffers_damagedblock | damaged block overlay | gbuffers_terrain |
gbuffers_block | block/tile entities | gbuffers_terrain |
gbuffers_beaconbeam | beacon beam | gbuffers_textured |
gbuffers_entities | entities | gbuffers_textured_lit |
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_weather | rain, snow | gbuffers_textured_lit |
Deferred | ||
deferred
deferred<1-99> |
<fullscreen pass> | none |
Translucent G-Buffers | ||
gbuffers_water | translucent geometry | gbuffers_terrain |
gbuffers_hand_water | translucent handheld items | gbuffers_hand |
Composite | ||
composite
composite<1-99> |
<fullscreen pass> | none |
Final | ||
final | <fullscreen pass, unaffected by render scale> | none |
Framebuffer Attachments
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.
Formats
Normalized | Signed Normalized | Float | Integer | Unsigned Integer |
---|---|---|---|---|
8 Bit | ||||
R8 | R8_SNORM | - | R8I | R8UI |
RG8 | RG8_SNORM | - | RG8I | RG8UI |
RGB8 | RGB8_SNORM | - | RGB8I | RGB8UI |
RGBA8 | RGBA8_SNORM | - | RGBA8I | RGBA8UI |
16 Bit | ||||
R16 | R16_SNORM | R16F | R16I | R16UI |
RG16 | RG16_SNORM | RG16F | RG16I | RG16UI |
RGB16 | RGB16_SNORM | RGB16F | RGB16I | RGB16UI |
RGBA16 | RGBA16_SNORM | RGBA16F | RGBA16I | RGBA16UI |
32 Bit | ||||
- | - | R32F | R32I | R32UI |
- | - | RG32F | RG32I | RG32UI |
- | - | RGB32F | RGB32I | RGB32UI |
- | - | RGBA32F | RGBA32I | RGBA32UI |
Mixed | ||||
R3_G3_B2 | - | - | - | - |
RGB5_A1 | - | - | - | - |
RGB10_A2 | - | - | - | - |
- | - | R11F_G11F_B10F | - | - |
RGB9_E5 | - | - | - | - |
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
directives:
/*
const int <framebuffer>Format = <format>;
*/
Reading
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) ortexture(<framebuffer>, <coordinates>)
ortexelFetch(<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 texture2D()
/texture()
and an ivec2
in the range of [0, size) for texelFetch()
. In contrast to texture2D()
and texture()
, 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. <g>sampler3D
, texture3D()
, vec3
/ivec3
coordinates).
Writing
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 colortex6
, colortex2
, and colortex5
in that exact order. In this case,
gl_FragData[0]
will write tocolortex6
,gl_FragData[1]
will write tocolortex2
andgl_FragData[2]
will write tocolortex5
.
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
-syntax: declaring 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 gl_FragData[n]
.
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.