Rendering Pipeline (OptiFine, ShadersMod)

From shaderLABS

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)

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.

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 composites, 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


compute [Iris only] none


compute [Iris only] none
Shadow Map
shadow all none
Shadow Composite


<fullscreen pass for shadow map> none


<fullscreen pass> none
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


<fullscreen pass> none
Translucent G-Buffers
gbuffers_water translucent geometry gbuffers_terrain
gbuffers_hand_water translucent handheld items gbuffers_hand


<fullscreen pass> none
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.


Normalized Signed Normalized Float Integer Unsigned Integer
8 Bit
16 Bit
R16 R16_SNORM R16F R16I R16UI
32 Bit
- - R32F R32I R32UI
- - RG32F RG32I RG32UI
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>;


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
  • texture(<framebuffer>, <coordinates>) 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 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).


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 to colortex6,
  • gl_FragData[1] will write to colortex2 and
  • gl_FragData[2] will write to colortex5.

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.