Sampler (GLSL)

From OpenGL.org
(Redirected from GLSL Sampler)
Jump to: navigation, search

A sampler is a set of GLSL variable types. Variables of one of the sampler types must be uniforms or as function parameters. Each sampler in a program represents a single texture of a particular texture type. The type of the sampler corresponds to the type of the texture that can be used by that sampler.

Sampler types

There are a number of sampler types. The various sampler types are separated into 3 categories, based on the basic data type of the Image Format of the texture that they sample from. These are floating-point, signed integer, and unsigned integer. Floating-point also covers normalized integer formats.

The name of the sampler type in GLSL reflects this grouping. The names are very similar to the names of the vector types in GLSL. Floating-point vectors do not have a prefix; they are just "vec​". Signed integer vectors are "ivec​", and unsigned integer vectors are "uvec​".

So for samplers, floating-point samplers begin with "sampler​". Signed integer samplers begin with "isampler​", and unsigned integer samplers begin with "usampler​". If you attempt to read from a sampler where the texture's Image Format doesn't match the sampler's basic format (usampler2D with a GL_R8I​, or sampler1D with GL_R8UI​, for example), all reads will produce undefined values.

Depth-component textures are treated as one-component floating-point textures. Stencil-component textures are treated as one-component unsigned integer textures.

For the sake of clarity, when you see a g preceding "sampler" in a sampler name, it represents any of the 3 possible prefixes (nothing for float, i for signed integer, and u for unsigned integer).

The rest of the sampler's name refers to the texture type that it is a sampler of. The names map as follows:

GLSL sampler OpenGL texture enum Texture type
gsampler1D​ GL_TEXTURE_1D​ 1D texture
gsampler2D​ GL_TEXTURE_2D​ 2D texture
gsampler3D​ GL_TEXTURE_3D​ 3D texture
gsamplerCube​ GL_TEXTURE_CUBE_MAP​ Cubemap Texture
gsampler2DRect​ GL_TEXTURE_RECTANGLE​ Rectangle Texture
gsampler1DArray​ GL_TEXTURE_1D_ARRAY​ 1D Array Texture
gsampler2DArray​ GL_TEXTURE_2D_ARRAY​ 2D Array Texture
gsamplerCubeArray​ GL_TEXTURE_CUBE_MAP_ARRAY​ Cubemap Array Texture
(requires GL 4.0 or ARB_texture_cube_map_array)
gsamplerBuffer​ GL_TEXTURE_BUFFER​ Buffer Texture
gsampler2DMS​ GL_TEXTURE_2D_MULTISAMPLE​ Multisample Texture
gsampler2DMSArray​ GL_TEXTURE_2D_MULTISAMPLE_ARRAY​ Multisample Array Texture

Shadow samplers

If a texture has a depth or depth-stencil image format and has the depth comparison activated, it cannot be used with a normal sampler. Attempting to do so results in undefined behavior. Such textures must be used with a shadow sampler. This type changes the texture lookup functions (see below), adding an additional component to the textures' usual texture coordinate vector. This extra value is used to compare against the value sampled from the texture.

Because cubemap arrays normally take 4D texture coordinates, the texture lookup function overloads that take a cubemap array take an additional parameter, instead of expanding the vector texture coordinate size.

The result of accessing a shadow texture is always a single float value. This value is on the range [0, 1], which is proportional to the number of samples in the shadow texture that pass the comparison. Therefore, if the resulting value is 0.25, then only 1 out of the 4 values sampled by the comparison operation passed.

Notice that none of these types have the g prefix. This is because shadow samplers can only be used with textures with depth components. And those are all floating-point (actual floats or unsigned Normalized Integers) image formats. Furthermore, the result of the comparison is always a single float value, since depth formats only provide one component of data.

GLSL sampler OpenGL texture enum
sampler1DShadow​ GL_TEXTURE_1D​
sampler2DShadow​ GL_TEXTURE_2D​
samplerCubeShadow​ GL_TEXTURE_CUBE_MAP​
sampler2DRectShadow​ GL_TEXTURE_RECTANGLE​
sampler1DArrayShadow​ GL_TEXTURE_1D_ARRAY​
sampler2DArrayShadow​ GL_TEXTURE_2D_ARRAY​
samplerCubeArrayShadow​ GL_TEXTURE_CUBE_MAP_ARRAY​

Language Definition

A variable of sampler can only be defined in one of two ways. It can be defined as a function parameter or as a uniform variable.

uniform sampler2D texture1;
 
void Function(in sampler2D myTexture);

Samplers do not have a value. They can not be set by expressions and the only expression they can be used in is as the direct argument to a function call which takes an in​ sampler of that type.

Texture lookup functions

The only place where you can use a sampler is in one of the GLSL standard library's texture lookup functions. These functions access the texture referred to by the sampler. They take a texture coordinate as parameters.

There are several kinds of texture functions. Some texture functions do not take certain kinds of samplers. Some of them are not available on all shader stages.

The use of g has the same meaning as previously. Types in bold face represent a range of types, as explained in the description beneath the function.

Texture coordinates

Texture coordinates may be normalized or in texel space. A normalized texture coordinate means that the size of the texture maps to the coordinates on the range [0, 1] in each dimension. This allows the texture coordinate to be independent of any particular texture's size. A texel-space texture coordinate means that the coordinates are on the range [0, size], where size​ is the size of the texture in that dimension.

Rectangle Textures always take texture coordinates in texel space. Unless otherwise noted, all other texture coordinates will be normalized.

Components of a texture coordinate that reference an array layer are not normalized to the number of layers. They specify a layer by index.

Texture lookup in shader stages

Texture accessing is not limited to just Fragment Shaders, though this is the primary place where textures are accessed. Any GLSL shader stage may access textures (and OpenGL does not define any limitations on the format for those textures). However, there is one exception.

Mipmapping works based off of the angle and size of the rendered primitive relative to the window. This is computed internally by taking the derivative of the texture coordinate passed to the texture sampling function. However, this is only possible within a Fragment Shader. In any other shader stage, there is no rendered primitive yet; there may be a vertex or a primitive, but the space of the primitive relative to the window is unknown. So derivatives cannot be implicitly computed.

Because of this, non-fragment shader stages cannot use any texture sampling function that requires implicit derivatives. The description of the functions below will state if they need implicit derivatives.

The result of using any of these functions functions outside of a fragment shader and on a mipmapped texture (aka: the texture's minimum mipmap level is greater than the base level and mipmap filtering is active) is undefined.

Non-uniform flow control

In Fragment Shaders, there is one other circumstance that can remove implicit derivatives: non-uniform flow control.

Uniform flow control for a particular location in code means that, no matter how a shader is executed, every invocation will follow the same path to get to that location of code. Consider the following GLSL code for a fragment shader:

flat in int parameterValue;
 
void main()
{
  vec4 firstData = texture(someSampler, textureCoords);
 
  if(parameterValue < 20)
  {
      firstData += texture(someOtherSampler, otherTexCoords);
  }
 
  vec4 moreData = texture(thirdSampler, thirdTexCoords);
}

The first texture access happens in uniform flow control. Thus, the texture access produces definite results. However, the access to someOtherSampler​ is not in uniform flow. This is because the condition is based on an input value to the fragment shader; if parameterValue​ were a uniform​ instead of an input, then it would be in uniform flow.

If the texture associated with someOtherSampler​ uses mipmapping or anisotropic filtering of any kind, then any texture function that requires implicit derivatives will retrieve undefined results. The third texture access happens in uniform flow control again and will produce definite results.

Note: The GLSL compiler will not give you an error for this. It is perfectly legal GLSL code, and it only produces undefined behavior based on the texture and sampler objects associated with someOtherSampler​.

There are two ways to solve this. The simplest is to restructure the code to always do the second texture access, but only add it based on the condition:

void main()
{
  vec4 firstData = texture(someSampler, textureCoords);
 
  vec4 addingData = texture(someOtherSampler, otherTexCoords);
  vec4 moreData = texture(thirdSampler, thirdTexCoords);
 
  if(parameterValue < 20)
  {
      firstData += addingData;
  }
}

The other alternative, which must be used in cases that are not so simple to resolve, is to get gradients for each pair of texture coordinates the texture coordinates with the dFdx​ and dFdy​ functions. Then use those gradients when fetching the textures.

void main()
{
  vec4 firstData = texture(someSampler, textureCoords);
 
  vec2 otherTexDx = dFdx(otherTexCoords);
  vec2 otherTexDy = dFdy(otherTexCoords);
  vec2 thirdTexDx = dFdx(thirdTexCoords);
  vec2 thirdTexDy = dFdy(thirdTexCoords);
 
  if(parameterValue < 20)
  {
      firstData += textureGrad(someOtherSampler, otherTexCoords, otherTexDx, otherTexDy);
  }
 
  vec4 moreData = textureGrad(thirdSampler, thirdTexCoords, thirdTexDx, thirdTexDy);
}

It is important to get the gradients before going into non-uniform flow code. The dFdx​ and dFdy​ become just as useless as the texture​ calls within non-uniform flow control code.

Texture size retrieval

The size of a texture can be retrieved by calling this function:

 ivec textureSize(gsampler sampler​, int lod​);

This function retrieves the size of the given LOD of the texture bound to sampler​. The sampler can be of any type. For sampler types that refer to textures without mipmaps (eg: samplerRect), no lod​ value is needed.

The dimensionality of the return value, ivec, depends on the type of sampler​. For images that are 1-dimensional, the function returns an int. For 2D images, the function returns ivec2, and for 3D ivec3. The array types return one extra coordinate (eg: 1D arrays return ivec2), with the last coordinate being the number of images in the array of images.

This function does not require implicit derivatives.

Texture mipmap retrieval

Texture Query Levels
Core in version 4.5
Core since version 4.3
Core ARB extension ARB_texture_query_levels

It is often useful to fetch the number of mipmap levels that are available in the particular texture associated with a sampler. This function can be used to do that:

 int textureQueryLevels(gsampler sampler​);

Texture sampling functions like textureLod​ which take an explicit mipmap level will clamp their inputs to the range [0, X), where X is the return value from this function.

Sampler types that forbid mipmaps (rectangle, buffer, etc), multisample samplers, and shadow cubemap array samplers cannot be used with this function.

This function does not require implicit derivatives.

Basic texture access

To sample the texture with normalized texture coordinates, this function is used:

 gvec texture(gsampler sampler​, vec texCoord​[, float bias​]);

This samples the texture given by sampler​, at the location texCoord​, with an optional LOD bias value of bias​. For sampler types that cannot have LODs, the bias​ parameter cannot be used.

Sampling from shadow samplers return a "float", representing the result of the comparison. Sampling from other kinds of samplers returns a gvec4, matching the type of gsampler. This function does not work with multisample or buffer samplers.

The size of the vec type of texCoord​ depends on the dimensionality of sampler​. A 1D sampler takes a "float", 2D samplers take "vec2", etc. Array samplers add one additional coordinate for the array level to sample. Shadow samplers add one additional coordinate for the sampler comparison value. Array and shadow samplers add two coordinates: the array level followed by the comparison value. So vec when used with a sampler1DArrayShadow is a "vec3".

This function requires implicit derivatives.

Offset texture access

You can add a texel offset to texture coordinates sampled with texture functions. This is useful for sampling from a collection of images all on one texture. This is done with this function:

 gvec textureOffset(gsampler sampler​, vec texCoord​, ivec offset​[, float bias​]);

The texCoord​ will have its value offset by offset​ texels before performing the lookup. Cubemap, multisample, and buffer samplers are not allowed as types for gsampler.

The type ivec has the same type, but in integers, as vec. So when accessing a 2D sampler, vec is "vec2" and ivec is "ivec2". When accessing a 1D sampler, vec is "float" and ivec is "int".

The value of offset​ must be a constant expression. That is, it must be a compile-time constant.

There are minimum and maximum values for the coordinates of offset​. These are queried through GL_MIN_PROGRAM_TEXEL_OFFSET​ and GL_MAX_PROGRAM_TEXEL_OFFSET​.

This function requires implicit derivatives.

Projective texture access

Projecting a texture onto a surface is often a useful technique. To perform this kind of texture access, this function is available:

 gvec textureProj(gsampler sampler​, vec projTexCoord​[, float bias​]);

The difference between this function and texture​ is that vec is one component larger than it would be for texture​. The previous components are divided by the last components, and this value is used to access the texture.

Array, cubemap, multisample, and buffer samplers cannot be used with this function.

An important note on shadow samplers. The comparison value is still part of the texture coordinate, stored in the next to last component. Because it is part of the texture coordinate, it is also divided by the last coordinate before the comparison. This means that you must pre-multiply the actual value by the projection value.

This function requires implicit derivatives.

Lod texture access

If you want to compute the mipmap LOD parameter entirely on your own (instead of biasing the pre-computed LOD), you may use this function:

 gvec textureLod(gsampler sampler​, vec texCoord​, float lod​);

This will sample the texture at the mipmap LOD lod​. 0 means the base level (as set by the appropriate texture parameter). Sampler types that forbid mipmaps (rectangle, buffer, etc), multisample samplers, and shadow cubemap array samplers cannot be used with texture LOD functions.

Mipmap filtering can still be used by selecting fractional levels. A level of 0.5 would be 50% of mipmap 0 and 50% of mipmap 1.

Note that gradients when using Lod functions (see textureGrad​ below) are effectively zero. So anisotropic filtering is useless when using this function.

This function does not require implicit derivatives, since it samples explicitly from the given mipmap level(s).

Gradient texture access

Being able to bias the mipmap LOD or specify the specific mipmap to use is useful, but it is also useful to change the mipmap lod computation in more subtle ways. This function allows you to specify the two gradients for how the texture coordinates change locally:

 gvec textureGrad(gsampler sampler​, vec texCoord​, gradvec dTdx​, gradvec dTdy​);

The type of gradvec​ is a float-vector with a number of components equal to the dimensionality of the sampler type. A 1D sampler uses a "float", etc. Note that shadow sampler types do not add an additional coordinate to gradvec​, so a sampler2DShadow is still "vec2".

This function works for sampler types that are not multisample, buffer texture, or cubemap array samplers, including those that do not have mipmaps (like sampler2DRect).

The values of dTdx​ and dTdy​ are vectors that represent the change of each texture coordinate per pixel of the window's X and Y coordinates.

This function does not require implicit derivatives, since it provides them.

Texture gather accesses

Texture gather
Core in version 4.5
Core since version 4.0
ARB extension ARB_texture_gather

While OpenGL's standard filtering abilities are useful, sometimes it is useful to be able to bypass filtering altogether. In order to do that for textures that are two-dimensional, you must fetch values from the 4 texels nearest to a given texture coordinate. OpenGL provides the following functions to do this.

 gvec4 textureGather(gsampler sampler​​, vec texCoord​​, int comp​);
 gvec4 textureGatherOffset(gsampler sampler​​, vec texCoord​​, ivec2 offset​, int comp​);
 gvec4 textureGatherOffsets(gsampler sampler​​, vec texCoord​​, ivec2 offsets​[4], int comp​);

These functions only fetch a single component from the texture, specified by comp​, which defaults to 0. All filtering is ignored for these functions, and they only access texels from the texture's base mipmap layer.

These functions fetch just one component at a time, but they return 4 values, as the components of a 4-element vector. These values represent the nearest four texels, in the following order:

  • X = top-left
  • Y = top-right
  • Z = bottom-right
  • W = bottom-left

For shadow sampler fetches, the functions do not take the comp​ parameter. Their texture coordinates are also not expanded to include the reference value, the way they usually are for shadow samplers.

The 4 return values for shadow samplers are the result of the comparison for each texel location.

The offset versions apply a pixel offset to the texture coordinate before doing the fetch, in the same way as the standard offset texture functions. The textureGatherOffsets​ is different, in that each of the four texel fetch locations has its own offset, defined by the offsets​ array.

The sampler types taken for the non-offset textureGather​ calls are non-multisample texture types which are not 1D or 3D. For the offset gather functions, only 2D, 2D array, and rectangle samplers (along with the shadow variations).

This function does not require implicit derivatives, as it only samples from the base mipmap level.

Mixed texture accesses

There are texture functions for different combination of the above. These are:

  • textureProjOffset​
  • textureProjLod​
  • textureProjLodOffset​
  • textureProjGrad​
  • textureProjGradOffset​
  • textureLodOffset​
  • textureGradOffset​

The variations that add parameters to the functions, everything except "Proj", add them in the order that the names appear in the function. So textureProjLodOffset​ has this signature:

 gvec textureProjLodOffset(gsampler sampler​, vec projTexCoord​, float lod​, ivec offset​);

The "Grad" and "Lod" functions do not have a bias​ parameter.

The accepted sampler types is the intersection of all sampler types that each function accepts. "Proj" cannot take cubemaps and "Lod" cannot take rectangles, so textureProjLod​ cannot take cubemaps or rectangles.

The "Grad" and "Lod" versions of functions do not require implicit derivatives. If either "Grad" or "Lod" is not present, then the function does require implicit derivatives.

Direct texel fetches

The above texture functions all use filtering and normalized texture coordinates (except when using rectangle samplers). It is occassionally useful to forgo this and directly access a texel value with non-normalized coordinates. This is done through one of these functions:

 gvec texelFetch(gsampler sampler​, ivec texCoord​[, int lod​] [, int sample​]);
 gvec texelFetchOffset(gsampler sampler​, ivec texCoord​, int lod​, ivec offset​);

The texCoord​ parameter is an unnormalized texture coordinate. The lod​ specifies which mipmap to sample from; if the sampler type does not have mipmaps, this parameter will not be present. The offset version applies an integer texel offset to the texture coordinate before doing the texture access. As before, the offset​ must be a constant expression. The sample​ specifies the sample number to fetch from for multisample sampler types.

texelFetch​ is the only texturing function (besides textureSize​) that takes multisample and buffer samplers. When using multisample samplers, the user must pass a sample​ parameter. This tells the system which sample to retrieve.

texelFetch​ does not take cubemap, cubemap array, or shadow samplers of any kind. texelFetchOffset​ forbids everything except 1D, 2D, 3D, rectangle, 1D, and 2D arrays.

This function does not require implicit derivatives, as it only samples from the given mipmap level.

Binding textures to samplers

Uniforms of sampler types are used in GLSL to represent a texture of a particular kind. Therefore, sampler types represent textures. The way a program is associated with textures is somewhat unintuitive. The mapping is done with the rendering context.

The OpenGL rendering context has multiple independent bind locations for textures called texture image unit. The function to switch which bind location is current is glActiveTexture. The bind locations are named in this function GL_TEXTURE0​, GL_TEXTURE1​, etc. Alternatively, you can use bind locations of the form GL_TEXTURE0​ + i, where i is a texture unit number between 0 and the implementation-defined constant GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS​.

This means you can bind 4 textures to texture units 0, 1, 3, and 8, so long as GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS​ for the implementation is greater than 8.

You can also bind Sampler Objects (no direct relation to samplers in GLSL) to a texture image unit. This makes it much easier to centralize sampling parameters, or to use the same texture with different sampling parameters.

The value of a sampler uniform in a program is not a texture object, but a texture image unit index. So you set the texture unit index for each sampler in a program. Then you bind the textures and sampler objects you wish to use to those texture units. Bind the program to the context, and rendering with that program will use those textures. To set the value of a sampler, use glUniform1i or glUniform1iv (or the glProgramUniform equivalents) for an array of samplers.

Here is an example of how to do this.

//Initial program setup.
glLinkProgram(program); //Initial link
 
GLint baseImageLoc = glGetUniformLocation(program, "baseImage");
GLint normalMapLoc = glGetUniformLocation(program, "normalMap");
GLint shadowMapLoc = glGetUniformLocation(program, "shadowMap");
 
glUseProgram(program);
glUniform1i(baseImageLoc, 0); //Texture unit 0 is for base images.
glUniform1i(normalMapLoc, 2); //Texture unit 2 is for normal maps.
glUniform1i(shadowMapLoc, 4); //Texture unit 4 is for shadow maps.
 
//When rendering an objectwith this program.
glActiveTexture(GL_TEXTURE0 + 0);
glBindTexture(GL_TEXTURE_2D, object1BaseImage);
glBindSampler(0, linearFiltering);
glActiveTexture(GL_TEXTURE0 + 2);
glBindTexture(GL_TEXTURE_2D, object1NormalMap);
glBindSampler(2, linearFiltering); //Same filtering as before
glActiveTexture(GL_TEXTURE0 + 4);
glBindTexture(GL_TEXTURE_2D, shadowMap);
glBindSampler(4, depthComparison); //Special sampler for depth comparisons.
 
//Render stuff
glDraw*();
 
//Render another object with some different textures.
glActiveTexture(GL_TEXTURE0 + 0);
glBindTexture(GL_TEXTURE_2D, object2BaseImage); //Use the same sampler as before.
glActiveTexture(GL_TEXTURE0 + 2);
glBindTexture(GL_TEXTURE_2D, object2NormalMap); //Use the same sampler as before.
 
//Use the same shadow map, so no need to unbind/bind.
//Render stuff
glDraw*();

Notice that the shadow map does not need to be changed here. This helps minimize the number of state changes. Your program could even have a global convention that texture image unit 4 is always used for shadow maps. Since all objects used the same shadow map, you can bind the shadow map before you start rendering anything, and you only have to bind it once per frame.

If a program doesn't use certain texture image units, it is still fine to have a texture bound to them. They will not affect the rendering in any way.

Version 4.20 binding

Shaders that support GLSL 4.20 or the extension ARB_shading_language_420pack can assign the default binding of samplers within a shader. For example:

#version 420
//#extension GL_ARB_shading_language_420pack: enable  Use for GLSL versions before 420.
 
layout(binding=0) uniform sampler2D diffuseTex;

This causes the initial value for the diffuseTex​ uniform to be the sampler 0. You may later change the binding as shown above. The binding​ value must be a compile-time constant.

Multibind and textures

Multibind
Core in version 4.5
Core since version 4.4
ARB extension ARB_multi_bind

A range of textures and sampler objects can be bound to a range of texture image units with a pair of function calls:

void glBindTextures(GLuint first​, GLsizei count​, const GLuint *textures​)

void glBindSamplers(GLuint first​, GLsizei count​, const GLuint *samplers​);

first​ is the initial texture image unit to start binding the array to. count​ is the number of texture image units that will be bound to. Thus, the texture image units that will be changed are those on the half-open range, [first​​, first​​ + count​).

For {{apifunc}glBindSamplers}}, the function will access the samplers​ array and bind each sampler object in the array to the corresponding texture image unit, as though from a call to glBindSampler.

However, glBindTextures works slightly differently. Recall that texture image units can actually have multiple textures of different texture types bound to them. And glBindTexture takes a target​ parameter, even though the texture object knows which target that must be used with. There is also the current "active texture image unit", which is global state, and changing this state is how one binds textures to different texture image units.

glBindTextures handles things in a far more logical fashion. For each of the texture image units that will be affected by the call, all texture types in that unit which do not correspond to the type of the given texture object to be bound will be reset to the zero texture. If the given texture object is itself zero, then all of the texture types for that unit will be reset to zero.

The global active texture image unit state is not affected by this call at all.

If textures​ or samplers​ is NULL, then these functions will behave as though an array of count​ objects was passed, where each object in that array was zero. So passing NULL means to reset all of the texture image units in the range to zero.

External links