Fragment Operations

A Fragment is a candidate to become a pixel in the framebuffer. For every fragment, OpenGL applies a series of tests in order to eliminate the fragment early to avoid updating the framebuffer.

Most of these tests can be toggled through GL.Enable/Disable, the pages below cover this in more detail. However the most in-depth description of the functionality can only be found in the official OpenGL specification.

The tests are executed from top to bottom, if a fragment did not pass an early test, later tests are ignored. I.e. If the fragment does not pass the Scissor Test, there would be no point in determining whether Depth Test passes or not.

01. Pixel Ownership Test

Citation with minor modifications. Cannot explain it better.

"GL 3.1 spec" wrote:

This test is used to determine if the pixel at the current location in the framebuffer is currently owned by the GL context. If it is not, the window system decides the fate the incoming fragment. Possible results are that the fragment is discarded or that some subset of the subsequent per-fragment operations are applied to the fragment. This test allows the window system to control the GL’s behavior, for instance, when a GL window is obscured.

If the draw framebuffer is a framebuffer object, the pixel ownership test always passes, since the pixels of framebuffer objects are owned by the GL, not the window system. If the draw framebuffer is the default framebuffer, the window system controls pixel ownership.

02. Scissor Test

The Scissor Test is used to limit drawing to a rectangular region of the viewport. When enabled, only fragments inside the rectangle are passed to later pipeline stages.

The ScissorTest can be enabled or disabled using EnableCap.ScissorTest

GL.Enable( EnableCap.ScissorTest );
GL.Disable( EnableCap.ScissorTest ); // default

Only a single command is related to the ScissorTest, GL.Scissor( x, y, width, height ). By default the parameters are set to cover the whole window.

  • X and Y are used to specify the lower-left corner of the rectangle.
  • Width is used to specify the horizontal extension of the rectangle.
  • Height is used to specify the vertical extension of the rectangle.

State Queries

To determine whether ScissorTest is enabled or disabled, use Result = GL.IsEnabled( EnableCap.ScissorTest );

The values set by GL.Scissor() can be queried by GL.GetInteger( GetPName.ScissorBox, ... ); // returns an array

03. Multisample Fragment Operations (WIP)

Multisampling is designed to counter the effects of aliasing at the edges of a primitive, when it is rasterized into fragments. Multisampling can be also applied to transparent textures, like wire fences, blades of grass or the leaves of trees. In this case, it is called 'alpha-to-coverage' and replaces the legacy alpha test.

A multisample buffer contains multiple samples per pixel, with each sample having it's own color, depth and stencil values. The term 'coverage' refers to a bitmask that is used to determine which of these samples will be updated: a coverage value of 1 indicates that the relevant sample will be updated; a value of 0 indicates it will be left untouched.

However, when EnableCap.SampleAlphaToCoverage is used, the coverage is obtained by interpreting the alpha as a percentage: an alpha of 0.0 means that no samples are covered, while a value of 1.0 indicates that all samples are covered. For example, a multisample buffer with 4 samples per pixel and an Alpha value of 0.5 indicates that half of the samples are covered (their coverage bit is 1) and two are not covered (coverage bit is 0).

"figure out whether this is true" wrote:

The coverage bitmask of incoming fragments can be set in a Fragment Shader with the variable gl_Coverage.
http://www.humus.name/index.php?page=Comments&ID=230

To enable alpha-to-coverage, enable multisampling (GL.Enable(EnableCap.Multisample)) and make sure that GL.GetInteger(GetPName.SampleBuffers, out buffers) is 1. If EnableCap.Multisample is disabled but GetPName.SampleBuffers is 1, alpha-to-coverage will be disabled.

There are three OpenGL states related to alpha-to-coverage, they are controlled by GL.Enable() and GL.Disable()

  • EnableCap.SampleAlphaToCoverage
    For each sample at the current pixel, the Alpha value is read and used to generate a temporary coverage bitmask which is then combined through a bitwise AND with the fragment's coverage bitmask. Only samples who's bit is set to 1 after the bitwise AND are updated.
  • EnableCap.SampleCoverage
    Using GL.SampleCoverage( value, invert ) the temporary coverage bitmask is generated by the value parameter - and if the invert parameter is true it is bitwise inverted - before the bitwise AND with the fragment's coverage bitmask.
  • EnableCap.AlphaToOne
    Each Alpha value is replaced by 1.0.

GL.SampleCoverage
The values set by the command GL.SampleCoverage( value, invert ) are only used when EnableCap.SampleCoverage is enabled.

  • value is a single-precision float used to specify the Alpha value used to create the coverage bitmask.
  • invert is a boolean toggle to control whether the bitmask is bitwise inverted before the AND operation.

State Queries

The states of EnableCap.Multisample, EnableCap.SampleAlphaToCoverage, EnableCap.SampleCoverage and EnableCap.AlphaToOne can be queried with Result = GL.IsEnabled( cap )

The value set by GL.SampleCoverage() can be queried with GL.GetFloat( GetPName.SampleCoverageValue, ... )

The boolean set by GL.SampleCoverage() can be queried with GL.GetBoolean( GetPName.SampleCoverageInvert, ... )

04. Stencil Test

The Stencil buffer's primary use is to apply a mask to the framebuffer. Simply put, you can think of it as a cardboard stencil where you cut out holes, so you may use a can of spraypaint to paint shapes. The paint will only pass the holes you had cut out and be blocked otherwise by the cardboard. OpenGL's Stencil testing allows you to layer several of these masks over each other.

A more OpenGL related example: In any vehicle simulation the interior of the cockpit is usually masked by a stencil buffer, because it does not have to be redrawn every frame. That way alot of fragments of the outside landscape can be skipped, as they would not contribute to the final image anyway.

In order to use the Stencil buffer, the window-system provided framebuffer - or the Stencil attachment of a FBO - must explicitly contain a logical stencil buffer. If there is no stencil buffer, the fragment is always passed to the next pipeline stage.

For the purpose of clarity in this article, the Stencil Buffer is assumed to be 8 Bit large and able to represent the values 0..255

StencilTest functionality is enabled and disabled with EnableCap.StencilTest

GL.Enable( EnableCap.StencilTest );
GL.Disable( EnableCap.StencilTest ); // default

The value used by GL.Clear() commands can be set through:

GL.ClearStencil( int ); // 0 is the default, Range [0..255]

If StencilTest is enabled, writing can be limited by a bitfield through a bitwise AND.

GL.StencilMask( bitmask ); // By default all bits are set to 1.

Note: The command GL.StencilMaskSeparate() behaves exactly like GL.StencilMask() but allows separate comparison functions for front- and back-facing polygons.

GL.StencilFunc()

The command GL.StencilFunc( func, ref, mask ) is used to specify the conditions under which the StencilTest succeeds or fails. It sets the comparison function, reference value and mask for the Stencil Test.

  • ref is an integer value to compare against. By default this value is 0, range [0 .. 255]
  • mask is a bitfield which will be used in a bitwise AND. Only the bits which are set to 1 are considered. By default all bits are set to 1.
  • func of the test can have the following values, the default is StencilFunction.Always.

    • StencilFunction.Always - Test will always succeed.
    • StencilFunction.Never - Test will never succeed.
    • StencilFunction.Less - Test will succeed if ( ref & mask ) < ( pixel & mask )
    • StencilFunction.Lequal - Test will succeed if ( ref & mask ) <= ( pixel & mask )
    • StencilFunction.Equal - Test will succeed if ( ref & mask ) == ( pixel & mask )
    • StencilFunction.Notequal - Test will succeed if ( ref & mask ) != ( pixel & mask )
    • StencilFunction.Gequal - Test will succeed if ( ref & mask ) >= ( pixel & mask )
    • StencilFunction.Greater - Test will succeed if ( ref & mask ) > ( pixel & mask )
  • The word 'pixel' means in this case: The value in the Stencil Buffer at the current pixel.

Note: The command GL.StencilFuncSeparate() behaves exactly like GL.StencilFunc() but allows separate comparison functions for front- and back-facing polygons.

GL.StencilOp()

Depending on the result determined by GL.StencilFunc(), the command GL.StencilOp( fail, zfail, zpass ) can be used to decide what action should be taken if the fragment passes the test.

  • fail - behavior when StencilTest fails, regardless of DepthTest.
  • zfail - behavior when StencilTest succeeds, but DepthTest fails.
  • zpass - behavior when both tests succeed, or if StencilTest succeeds and DepthTest is disabled.

The following values are allowed, the default for all operations is StencilOp.Keep

  • StencilOp.Zero - set Stencil Buffer to 0.
  • StencilOp.Keep - Do not modify the Stencil Buffer.
  • StencilOp.Replace - set Stencil Buffer to ref value as specified by last GL.StencilFunc() call.
  • StencilOp.Incr - increment Stencil Buffer by 1. It is clamped at 255.
  • StencilOp.IncrWrap - increment Stencil Buffer by 1. If the result is greater than 255, it becomes 0.
  • StencilOp.Decr - decrement Stencil Buffer by 1. It is clamped at 0.
  • StencilOp.DecrWrap - decrement Stencil Buffer by 1. If the result is less than 0, it becomes 255.
  • StencilOp.Invert - Bitwise invert. If the Stencil Buffer currently contains the bits 00111001, it is set to 11000110.

Note: The command GL.StencilOpSeparate() behaves exactly like GL.StencilOp() but allows separate comparison functions for front- and back-facing polygons.

State Queries

To determine whether StencilTest is enabled or disabled, use Result = GL.IsEnabled( EnableCap.StencilTest );

The bits available in the Stencil Buffer can be queried by GL.GetInteger( GetPName.StencilBits, ... );

The value set by GL.ClearStencil() can be queried by GL.GetInteger( GetPName.StencilClearValue, ... );

The bitfield set by GL.StencilMask() can be queried by GL.GetInteger( GetPName.StencilWritemask, ... );

The state of the Stencil comparison function can be queried with GL.GetInteger() and the following parameters:

GetPName.StencilFunc - GL.StencilFunc's parameter 'func'
GetPName.StencilRef - GL.StencilFunc's parameter 'ref'
GetPName.StencilValueMask - GL.StencilFunc's parameter 'mask'

The state of the Stencil operations can be queried with GL.GetInteger and the following parameters:

GetPName.StencilFail - GL.StencilOp's parameter 'fail'
GetPName.StencilPassDepthFail - GL.StencilOp's parameter 'zfail'
GetPName.StencilPassDepthPass - GL.StencilOp's parameter 'zpass'

If the GL.Stencil***Separate() functions have been used, the tokens GetPName.StencilBack*** become available to query the settings for back-facing polygons. With Intellisense you should not have any problems finding them.

Related Extensions for further reading

http://www.opengl.org/registry/specs/EXT/stencil_clear_tag.txt
http://www.opengl.org/registry/specs/EXT/stencil_wrap.txt (promoted to core in GL 2.0)
http://www.opengl.org/registry/specs/ATI/separate_stencil.txt (promoted to core in GL 2.0)
http://www.opengl.org/registry/specs/EXT/stencil_two_side.txt (basically the same functionality as ATI_separate_stencil, not in core though)

05. Depth Test

A commonly used logical buffer in OpenGL is the Depth buffer, often called Z-Buffer. The name was chosen due to X and Y being used to describe horizontal and vertical displacement on the screen, so Z is used to measure the distance perpendicular to the screen.

The general purpose of this buffer is determining whether a fragment is occluded by a previously drawn pixel. I.e. If the fragment in question is further away from the eye than an already existing pixel, the fragment cannot be visible and is discarded.

In order to use the Depth buffer, the window-system provided framebuffer - or the Depth attachment of a FBO - must explicitly contain a logical Depth buffer. If there is no Depth buffer, the fragment is always passed to the next pipeline stage.

DepthTest functionality is enabled and disabled with EnableCap.DepthTest

GL.Enable( EnableCap.DepthTest );
GL.Disable( EnableCap.DepthTest ); // default

The value used by GL.Clear() commands can be set through:

GL.ClearDepth( double ); // 1.0 is the default, Range: [0.0 .. 1.0]

If DepthTest is enabled, writing to the Depth buffer can be toggled by a boolean flag.

GL.DepthMask( bool ); // true is the default

GL.DepthFunc
The command GL.DepthFunc( func ) is used to specify the comparison method used whether a fragment is closer to the eye than the existing pixel it is compared to.

Function of the test can have the following values, the default is DepthFunction.Less.

  • DepthFunction.Always - Test will always succeed.
  • DepthFunction.Never - Test will never succeed.
  • DepthFunction.Less - Test will succeed if ( fragment depth < pixel depth )
  • DepthFunction.Lequal - Test will succeed if ( fragment depth <= pixel depth )
  • DepthFunction.Equal - Test will succeed if ( fragment depth == pixel depth )
  • DepthFunction.Notequal - Test will succeed if ( fragment depth != pixel depth )
  • DepthFunction.Gequal - Test will succeed if ( fragment depth >= pixel depth )
  • DepthFunction.Greater - Test will succeed if ( fragment depth > pixel depth )

GL.DepthRange
The command GL.DepthRange( near, far ) is used to define the minimum (near plane) and maximum (far plane) z-value that is stored in the Depth Buffer. Both parameters are expected to be of double-precision floating-point and must lie within the range [0.0 .. 1.0].

It is allowed to call GL.DepthRange( 1.0, 0.0 ), there is no rule that must satisfy ( near < far ).

For an in-depth explanation how the distribution of z-values in the Depth buffer works, please read Depth buffer - The gritty details.

State Queries

To determine whether DepthTest is enabled or disabled, use Result = GL.IsEnabled( EnableCap.DepthTest );

The bits available in the Stencil Buffer can be queried by GL.GetInteger( GetPName.DepthBits, ... );

The value set by GL.ClearDepth() can be queried by GL.GetFloat( GetPName.DepthClearValue, ... );

The boolean set by GL.DepthMask() can be queried by GL.GetBoolean( GetPName.DepthWritemask, ... );

The Depth comparison function can be queried with GL.GetInteger( GetPName.DepthFunc, ... );

The Depth range can be queried with GL.GetFloat( GetPName.DepthRange, ... ); // returns an array

06. Occlusion Query

Occlusion queries count the number of fragments (or samples) that pass the depth test, which is useful to determine visibility of objects.

If an object is drawn but 0 fragments passed the depth test, it is fully occluded by another object. In practice this means that a simplification of an object is drawn using an occlusion query (for example: A bounding box can be the occlusion substitute for a truck) and only if fragments of the simple object pass the depth test, the complex object is drawn. Please read Conditional Render for a convenient solution.

Note that the simplified object does not actually have to become visible, one can set GL.ColorMask and GL.DepthMask to false for the purpose of the occlusion query. The only GL.Enable/Disable state associated with it is the DepthTest. If DepthTest is disabled all fragments will automatically pass it and the occlusion test becomes pointless.

Occlusion Query handles are generated and deleted similar to other OpenGL handles:

uint MyOcclusionQuery;
GL.GenQueries( 1, out MyOcculsionQuery );
GL.DeleteQueries( 1, ref MyOcculsionQuery );

The draw commands which contribute to the count must be enclosed with GL.BeginQuery() and GL.EndQuery().

GL.BeginQuery( QueryTarget.SamplesPassed, MyOcculsionQuery );
// draw...
GL.EndQuery( QueryTarget.SamplesPassed );

It is very important to understand that this process is running asynchronous, by the time the CPU is querying the result of the count the GPU might not be done counting yet. OpenGL provides additional query commands to determine whether the occlusion query result is available, but before it is confirmed to be available any query of the count is not reliable. The following code will get a reliable result.

uint ResultReady=0;
while ( ResultReady == 0 )
{
  GL.GetQueryObject( MyOcculsionQuery, GetQueryObjectParam.QueryResultAvailable, out ResultReady );
}
uint MyOcclusionQueryResult=0;
GL.GetQueryObject( MyOcculsionQuery, GetQueryObjectParam.QueryResult, out MyOcclusionQueryResult );
// MyOcclusionQueryResult is now reliable.

However this is not very efficient to use because the CPU will spin in a loop until the GPU is done counting.

A better approach is to do the occlusion queries in the first frame and do not wait for a result. Instead continue drawing as normal and wait for the next frame, before you check the results of the query. In other words frame n executes the query and frame n + 1 reads back the results.

This approach hides the latency inherent in occlusion queries and improves performance, at the cost of slight visual glitches (an object may become visible one frame later than it should). You can read a very detailed description of this technique on Chapter 29 of GPU Gems 1, which also covers other caveats of occlusion queries.

Conditional Render

The Extension NV_conditional_render adds a major improvement to occlusion queries: it allows a simple if ( SamplesPassed > 0 ) conditional to decide whether an object should be drawn based on the result of an occlusion query.

This is probably best shown by a simple example, in the given scene there are 3 objects:

  • A huge cylinder which acts as occluder. Think of it as a pillar in the center of the "room".
  • A small cube which acts as ocludee. Think of it as a box that is anywhere in the "room" but not intersecting the pillar.
  • A small sphere which sits ontop of the cube. If the cube is fully occluded by the cylinder, drawing the sphere can be skipped.

Here is some pseudo-code how the implementation looks like.

uint MyOcculsionQuery;
 
public void OnLoad()
{
  GL.GenQueries(1, out MyOcculsionQuery);
  // etc...
  GL.Enable( EnableCap.DepthTest ); 
}
 
public void OnUnload()
{
  GL.DeleteQueries(1, ref MyOcculsionQuery);
  // etc...
}
 
public void OnRenderFrame()
{
  // The cylinder is drawn unconditionally and used as occluder for the Cube and Sphere
  MyCylinder.Draw();
 
  // Next, the cube is drawn unconditionally, but the samples which passed the depth test are counted.
  GL.BeginQuery( QueryTarget.SamplesPassed, MyOcculsionQuery );
  MyCube.Draw();
  GL.EndQuery( QueryTarget.SamplesPassed );
 
  // depending on whether any sample passed the depth test, the sphere is drawn.
  GL.NV.BeginConditionalRender( MyOcculsionQuery, NvConditionalRender.QueryWaitNv );
  MySphere.Draw();
  GL.NV.EndConditionalRender();
 
  this.SwapBuffers();
}

Although the running program might only show a single object on screen (the cylinder), the cube is always drawn too. Only drawing of the sphere might be skipped, depending on the outcome of the occlusion query used for the cube.

Please note that this is not the standard case how to use occlusion query. The most common way to use them is drawing a simple bounding volume (of a more complex object) to determine whether samples passed and only draw the complex object itself, if the bounding volume is not occluded. For example: Drawing a character with skeletal animation is usually expensive, to determine whether it should be drawn at all, a cylinder can be drawn using an occlusion query and the character is only drawn if the cylinder is not occluded.

07. Blending

Without blending, every fragment is either rejected or written to the framebuffer. That behaviour is desireable for opaque objects, but it does not allow rendering of translucent objects. The correct order of operation to draw a simple scene containing a solid table with a transparent glass ontop of it: draw the opaque table first, then enable blending (also set the desired blend equation and factors) and finally the glass is drawn.

Blending is an operation to mix the incoming fragment color (SourceColor) with the color that is currently in the color buffer (DestinationColor). This happens in two stages for each channel of the color buffer:

  1. The factors used in this stage can be controlled with GL.BlendFunc()
    The SourceColor is multiplied by the SourceFactor.
    The DestinationColor is multiplied by the DestinationFactor.
  2. The equation used in this stage can be controlled with GL.BlendEquation()
    The results of the above multiplications are then combined together to obtain the final result.

In order to use blending, the logical color buffer must have an Alpha channel. If there is no Alpha channel, or the color buffer uses color-index mode (8 Bit), no blending can occur and behaviour is the same as if blending was disabled.

Blending functionality for all draw buffers is enabled and disabled with EnableCap.Blend

GL.Enable( EnableCap.Blend );
GL.Disable( EnableCap.Blend ); // default

To enable or disable only a specific buffer if multiple color buffers are attached to the FBO, use

GL.Enable( IndexedEnableCap.Blend, index);
GL.Disable( IndexedEnableCap.Blend, index);

Where index is used to specify the draw buffer associated with the symbolic constant GL_DRAW_BUFFER(index).

GL.BlendEquation
The command GL.BlendEquation( mode ) specifies how the results from stage 1 are combined with each other. If you wanted to implement this with OpenTK.Math, it would look like this:

Color4 SourceColor, // incoming fragment
       DestinationColor,  // framebuffer contents
       SourceFactor, DestinationFactor, // specified by GL.BlendFunc()
       FinalColor; // the resulting color

Please note that for fixed-point color buffers both Colors are clamped to [0.0 .. 1.0] prior to computing the result. Floating-point color buffers are not clamped. Clamping into this range is left out in this code to improve legibility.

BlendEquationMode.Min: When using this parameter, SourceFactor and DestinationFactor are ignored.

FinalColor.Red = min( SourceColor.Red, DestinationColor.Red );
FinalColor.Green = min( SourceColor.Green, DestinationColor.Green );
FinalColor.Blue = min( SourceColor.Blue, DestinationColor.Blue );
FinalColor.Alpha = min( SourceColor.Alpha, DestinationColor.Alpha );

BlendEquationMode.Max: When using this parameter, SourceFactor and DestinationFactor are ignored.

FinalColor.Red = max( SourceColor.Red, DestinationColor.Red );
FinalColor.Green = max( SourceColor.Green, DestinationColor.Green );
FinalColor.Blue = max( SourceColor.Blue, DestinationColor.Blue );
FinalColor.Alpha = max( SourceColor.Alpha, DestinationColor.Alpha );

BlendEquationMode.FuncAdd: This is the default.

FinalColor.Red = SourceColor.Red*SourceFactor.Red + DestinationColor.Red*DestinationFactor.Red;
FinalColor.Green = SourceColor.Green*SourceFactor.Green + DestinationColor.Green*DestinationFactor.Green;
FinalColor.Blue = SourceColor.Blue*SourceFactor.Blue + DestinationColor.Blue*DestinationFactor.Blue;
FinalColor.Alpha = SourceColor.Alpha*SourceFactor.Alpha + DestinationColor.Alpha*DestinationFactor.Alpha;

BlendEquationMode.FuncSubtract:

FinalColor.Red = SourceColor.Red*SourceFactor.Red - DestinationColor.Red*DestinationFactor.Red;
FinalColor.Green = SourceColor.Green*SourceFactor.Green - DestinationColor.Green*DestinationFactor.Green;
FinalColor.Blue = SourceColor.Blue*SourceFactor.Blue - DestinationColor.Blue*DestinationFactor.Blue;
FinalColor.Alpha = SourceColor.Alpha*SourceFactor.Alpha - DestinationColor.Alpha*DestinationFactor.Alpha;

BlendEquationMode.FuncReverseSubtract:

FinalColor.Red = DestinationColor.Red*DestinationFactor.Red - SourceColor.Red*SourceFactor.Red;
FinalColor.Green = DestinationColor.Green*DestinationFactor.Green - SourceColor.Green*SourceFactor.Green;
FinalColor.Blue = DestinationColor.Blue*DestinationFactor.Blue - SourceColor.Blue*SourceFactor.Blue;
FinalColor.Alpha = DestinationColor.Alpha*DestinationFactor.Alpha - SourceColor.Alpha*SourceFactor.Alpha;

If the color buffer is using fixed-point precision, the result in FinalColor is clamped to [0.0 .. 1.0] before it is passed to the next pipeline stage, no clamping occurs for floating-point color buffers.

OpenGL 2.0 and later supports separate equations for the RGB components and the Alpha component respectively. The command GL.BlendEquationSeparate( modeRGB, modeAlpha ) accepts the same parameters as GL.BlendEquation( mode ).

GL.BlendColor
The command GL.BlendColor( R, G, B, A ) is used to specify a constant color that can be used by GL.BlendFunc(). For the scope of this page it is used to define the variable Color4 ConstantColor;.

GL.BlendFunc
The command GL.BlendFunc( src, dest ) is used to select the SourceFactor (src) and DestinationFactor (dest) in the above equation. By default, SourceFactor is set to BlendingFactorSrc.One and DestinationFactor is BlendingFactorDest.Zero, which gives the same result as if blending were disabled.

  • .Zero: Color4 (0.0, 0.0, 0.0, 0.0)
  • .One: Color4 (1.0, 1.0, 1.0, 1.0)
  • .DstColor: Color4 (DestinationColor.Red, DestinationColor.Green, DestinationColor.Blue, DestinationColor.Alpha)
  • .SrcColor: Color4 (SourceColor.Red, SourceColor.Green, SourceColor.Blue, SourceColor.Alpha)
  • .OneMinusDstColor: Color4 (1.0-DestinationColor.Red, 1.0-DestinationColor.Green, 1.0-DestinationColor.Blue, 1.0-DestinationColor.Alpha)
  • .OneMinusSrcColor: Color4 (1.0-SourceColor.Red, 1.0-SourceColor.Green, 1.0-SourceColor.Blue, 1.0-SourceColor.Alpha)
  • .SrcAlpha: Color4 (SourceColor.Alpha, SourceColor.Alpha, SourceColor.Alpha, SourceColor.Alpha)
  • .OneMinusSrcAlpha: Color4 (1.0-SourceColor.Alpha, 1.0-SourceColor.Alpha, 1.0-SourceColor.Alpha, 1.0-SourceColor.Alpha)
  • .DstAlpha: Color4 (DestinationColor.Alpha, DestinationColor.Alpha, DestinationColor.Alpha, DestinationColor.Alpha)
  • .OneMinusDstAlpha: Color4 (1.0-DestinationColor.Alpha, 1.0-DestinationColor.Alpha, 1.0-DestinationColor.Alpha, 1.0-DestinationColor.Alpha)
  • .SrcAlphaSaturate: f = min( SourceColor.Alpha, 1.0-DestinationColor.Alpha );
    Color4 ( f, f, f, 1.0 )
  • .ConstantColor: Color4 ( ConstantColor.Red, ConstantColor.Green, ConstantColor.Blue, ConstantColor.Alpha )
  • .OneMinusConstantColor: Color4 ( 1.0-ConstantColor.Red, 1.0-ConstantColor.Green, 1.0-ConstantColor.Blue, 1.0-ConstantColor.Alpha )
  • .ConstantAlpha: Color4 (ConstantColor.Alpha, ConstantColor.Alpha, ConstantColor.Alpha, ConstantColor.Alpha)
  • .OneMinusConstantAlpha: Color4 (1.0-ConstantColor.Alpha, 1.0-ConstantColor.Alpha, 1.0-ConstantColor.Alpha, 1.0-ConstantColor.Alpha)

OpenTK uses the enums BlendingFactorSrc and BlendingFactorDest to narrow down your options what is a valid parameter for src and dest. Not all parameters are valid factors for both, SourceFactor and DestinationFactor. Please refer to the inline documentation for details.

OpenGL 2.0 and later supports separate factors for RGB and Alpha, for source and destination respectively. The command GL.BlendFuncSeparate( srcRGB, dstRGB, srcAlpha, dstAlpha ) accepts the same factors as GL.BlendFunc( src, dest ).

State Queries

To determine whether blending for all draw buffers is enabled or disabled, use Result = GL.IsEnabled( EnableCap.Blend );
To query blending state of a specific draw buffer: Result = GL.IsEnabled(IndexedEnableCap.Blend, index);

The selected blend factors can be queried separately for source and destination by using GL.GetInteger() with

  • GetPName.BlendSrc - set by GL.BlendFunc()
  • GetPName.BlendDst - set by GL.BlendFunc()
  • GetPName.BlendSrcRgb - set by GL.BlendFuncSeparate()
  • GetPName.BlendSrcAlpha - set by GL.BlendFuncSeparate()
  • GetPName.BlendDstRgb - set by GL.BlendFuncSeparate()
  • GetPName.BlendDstAlpha - set by GL.BlendFuncSeparate()

The selected blend equation can be queried by using GL.GetInteger() with

  • GetPName.BlendEquation - set by GL.BlendEquation()
  • GetPName.BlendEquationRgb - set by GL.BlendEquationSeparate()
  • GetPName.BlendEquationAlpha - set by GL.BlendEquationSeparate()

08. sRGB Conversion

This stage of the pipeline is only applied if EnableCap.FramebufferSrgb is enabled and if the color encoding for the framebuffer attachment is sRGB (as in: not linear).

  • If those conditions are true, the Red, Green and Blue values after blending are converted into the non-linear sRGB color space.
  • If any of those conditions is false, no conversion is applied.

The resulting values for R, G, and B, and the unmodified Alpha form a new RGBA color value. If the color buffer is fixed-point, each component is clamped to the range [0.0 .. 1.0] and then converted to a fixed-point value. The resulting four values are sent to the subsequent dithering operation.

09. Dithering

Dithering is similar to halftoning in newspapers. Only a single color (black) is used in contrast to the paper (white), but due to using patterns the appearance of many shades of gray can be represented. In a similar way, OpenGL can dither the fragment from a high precision color to a lower precision color. I.e. dithering is used to find one or more representable colors to ensure the image shown on the screen is a best-match between the capability of the monitor and the computed image.

This is always needed when working with 8 Bit color-index mode, where only 256 unique colors can be represented, but the image to be drawn is actually calculated with higher precision. Dithering also applies when a RGBA32f color is converted to display on the screen, which is usually RGBA8. In RGBA mode, dithering is performed separately for Red, Green, Blue and Alpha.

Dithering is the only state that is enabled by default, the programmer has no control over how the image is manipulated (the graphics hardware decides which algorithm is used) besides enabling or disabling dithering with EnableCap.Dither.

GL.Enable( EnableCap.Dither ); // default
GL.Disable( EnableCap.Dither );

State Query

The state of dithering can be queried through Result = GL.IsEnabled( EnableCap.Dither );

10. Logical Operations

Before a fragment is written to the framebuffer, a logical operation is applied which uses the incoming fragment values as source (s) and/or those currently stored in the color buffer as destination (d). After the selected operation is completed, destination is overwritten. Logical operations are performed independently for each Red, Green, Blue and Alpha value and if the framebuffer has multiple color attachments, the logical operation is computed and applied separately for each color buffer.

If Logic Op is enabled, OpenGL behaves as if Blending is disabled regardless whether it was previously enabled.

In order to apply the Logicial Operation, use EnableCap.ColorLogicOp

GL.Enable( EnableCap.ColorLogicOp );
GL.Disable( EnableCap.ColorLogicOp ); // default

Note: If you use EnableCap.LogicOp or EnableCap.IndexLogicOp, only indexed color buffers (8 Bit) are affected.

To select the logical operation to be performed, use GL.LogicOp( op ); where op is by default LogicOp.Copy.

  • LogicOp.Clear: 0
  • LogicOp.And: s & d
  • LogicOp.AndReverse: s & !d
  • LogicOp.Copy: s
  • LogicOp.AndInverted: !s & d
  • LogicOp.Noop: d
  • LogicOp.Xor: s XOR d
  • LogicOp.Or: s | d
  • LogicOp.Nor: !(s | d)
  • LogicOp.Equiv: !(s XOR d)
  • LogicOp.Invert: !d
  • LogicOp.OrReverse: s | !d
  • LogicOp.CopyInverted: !s
  • LogicOp.OrInverted: !s | d
  • LogicOp.Nand: !(s & d)
  • LogicOp.Set: all 1's

State Queries
The state of LogicOp can be queried with Result = GL.IsEnabled( EnableCap.LogicOp );

Which operation has been set through GL.LogicOp() can be queried with GL.GetInteger( GetPName.LogicOpMode, ... )