Uniforms pass data directly from appliaction into any shader stage.

Two flavors of uniforms:

  • declared in the default block
  • values are stored in buffer objects

Defalut Block Uniforms

A uniform is how we pass data into a shader that stays the same.

MOST COMMON UNIFORM
transformation matrix

Any shader variable can be specified as a uniform, and uniforms can be in any shader stages.

1
2
3
4
uniform float fTime;
uniform int iIndex;
uniform vec4 vColorValue;
uniform mat4 mvpMatrix;

Uniforms are always considered to be constant.

1
uniform answer = 42;

if you declare the same unifrom in multiple shader stages, each of those stages will see the same value of that uniform.

Arranging Your Uniforms

Your shader code by using a location layout qualifier.

1
layout (location = 17) uniform vec4 myUniform;

You can figure out what locations were assigned by calling the glGetUniformLocation()

1
2
3
4
GLint glGetUniformLocation(GLuint program,
const GLchar* name)
;


GLint iLocation = glGetUniformLocation(myProgram, "vColorValue");

Setting Scalars and Vector Uniforms

1
2
glUniform?f(GLint, ...);
glUniform?i(GLint, ...);

? : 1 to 4


Setting Uniform Arrays

1
glUniform??v(GLint location, GLuint count, const GLxxxx * value);

Setting Uniform Matrices

1
glUniformMatrix??v(GLint location, GLunit count, GLboolean transpose, const GLXXXXX * m);

The Boolean flag transpose:

  • matrix column-major -> GL_FALSE
  • matrix row-major -> GL_TRUE

Uniform Blocks

To alleviate the cost of all the glUniform*() calls, OpenGL allows you to combine a group of uniforms into a uniform block and store the whole block in a buffer object.

You can’t keep the default bolck in a uniform buffer object; you need to creat one or more named uniform block.

1
2
3
4
5
6
7
uniform TransformBlock
{
float scale;
vec3 translation;
float rotation[3];
mat4 projection_matrix;
} transform;

Building Uniform Blocks

standard, agreed upon layout for the data.

1
2
3
4
5
6
7
layout(std140) uniform TransformBlock
{
float scale;
vec3 translation;
float rotation[3];
mat4 projection_matrix;
} transform;

Once a uniform block has ben decalred to use the stndard, or std140, layout, each member of the block consumes a predefined amount of space in the buffer and begins at an offset that is predictable by following a set of rules:

  • Any type consuming N bytes in a buffer begins on a N-byte boundary within that buffer.

shared layout, let OpenGL decide where it would like the data.

You can determine the offsets that OpenGL assigned to your block members. Each member of a uniform block ahs an index that is used to efre to it to find its size and location within the block.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void glGetUniformIndices(GLuint program,
GLsizei uniformCount,
const GLchar ** uniformNames,
GLuint * uniformIndices)
;


static const GLchar * uniformNames[4] =
{
"TransformBlock.scale",
"TransformBlock.translation",
"TransformBlock.rotation",
"TransformBlock.projection_matrix",
};
GLuint uniformIndices[4];

glGetUniformIndices(program, 4, uniformIndices, uniformIndices);

After you get the uniformIndices array, you have the indices, you can use them to find the location of the block members within the buffer.

1
2
3
4
5
6
7
GLint uniformOffsets[4];
GLint arrayStrides[4];
GLint matrixStrides[4];

glGetActiveUniformsiv(program, 4, unifomIndices, GL_UNIFORM_OFFSET, uniformOffsets);
glGetActiveUniformsiv(program, 4, unifomIndices, GL_UNIFORM_ARRAY_STRIDE, arrayStrides);
glGetActiveUniformsiv(program, 4, unifomIndices, GL_UNIFORM_MATRIX_STRIDE, matrixStrides);
  • unifomIndices contains the offsets of the members to the TransformBlock
  • arrayStrides contains the strides of the array members(only rotaion, for now)
  • matrixStrides contains the strides of the matrix members(only projection_matrix)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
unsigned char *buffer = (unsigned char *)malloc(4096);

// float
*((float *)(buffer + uniformOffsets[0])) = 3.0f;

// vec3
((float*)(buffer + uniformOffsets[1]))[0] = 1.0f;
((float*)(buffer + uniformOffsets[1]))[1] = 1.0f;
((float*)(buffer + uniformOffsets[1]))[2] = 1.0f;


// array
const GLfloat roations[] = {30.0f, 40.0f, 50.0f};
unsigned int offset = uniformOffsets[2];

for (int n = 0; n < 3; n++)
{
*((float*)(buffer + offset)) = rotations[n];
offset += arrayStrides[2];
}

// matrix
const GLfloat matrix[] =
{
1.0f, 2.0f, 3.0f, 4.0f,
1.0f, 2.0f, 3.0f, 4.0f,
1.0f, 2.0f, 3.0f, 4.0f,
1.0f, 2.0f, 3.0f, 4.0f
};

for (int i = 0; i < 4; i++)
{
GLint offset = uniformOffsets[3] + matrixStride[3] * i;
for (int j = 0; j < 4; j++)
{
*((float *)(buffer + offset)) = matrix[i * 4 + j];
offset += sizeof(GLfloat);
}
}

find a index of a uniform block in a program

1
2
GLuint glGetUniformBlockIndex(GLuint program,
const GLchar * uniformBlockName)
;

two-step process to bind a buffer to a uniform block:

  1. Uniform blocks are assigned binding point
  2. buffers can be bound to those binding points

to assign a binding point to a uniform block:

1
2
3
void glUniformBlockBinding(GLuint program,
GLuint uniformBlockIndex,
GLuint uniformBlockBinding)
;

  • uniformBlockIndex is the index of the uniform block you are assiging a binding point to. (call glGetUniformBlockIndex)
  • uniformBlockBinding is the index of the uniform block binding point.

specify the binding index of your uniform blocks right in your shader code.

1
2
3
4
layout(std140, binding = 2) uniform TransformBlock
{
....
} transfrom;

Assigning bindings in your shader code avoids the need to call glUniformBlockBinding()


1
glBindBufferBase(GL_UNIFORM_BUFFER, index, buffer);
  • GL_UNIFORM_BUFFER tells OpenGl that we are binding a buffer to one of the uniform buffer binding point
  • index is the index of the binding point and should match waht you specifed either in your shader
  • buffer buffer is the name of the buffer object that you want to attach

index is not the index of the uniform block, but the index of the uniform buffer binding poing.

1
2
3
4
5
6
7
8
9
10
11
GLuint harry_index = glGetUniformBlockIndex(program, "Harry");
GLuint bob_index = glGetUniformBlockIndex(program, "Bob");
GLuint susan_index = glGetUniformBlockIndex(program, "Susan");

glUniformBlockBinding(program, harry_index, 1);
glUniformBlockBinding(program, bob_index, 3);
glUniformBlockBinding(program, susan_index, 0);

glBindBufferBase(GL_UNIFORM_BUFFER, 0, buffer_b);
glBindBufferBase(GL_UNIFORM_BUFFER, 1, buffer_c);
glBindBufferBase(GL_UNIFORM_BUFFER, 3, buffer_a);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
layout (binding = 1) uniform Harry
{
...
};

layout (binding = 3) uniform Bob
{
...
};

layout (binding = 0) uniform Susan
{
...
};

the shader code is compiled and liked into ta program object, the bindings for the Harry, Bob, and Susan uniform blocks
will be set to the same thing as they would be after executing c code.


The reason of setting the uniform block binding in the shader

  • reduces numbers call to OpenGL
  • allows the shader to associate a uniform block with a binding point without the block name.
  • separate steady state from transient state
  • uniform blocks can be quite large

Using Unifroms to Transform Geometry

This is the classic spinnig cube demo:

  1. create a unit cube located at the origin and store it in buffer objects
  2. use a vertex shader to apply a sequence of transforms
  3. construct a basic view matrix, multiply our model and view matrices together to produce a model-view matrix
  4. create a prespective transformation matrix representing some of the properties of our camera
  5. pass these into a simple vertex shader using uniforms and draw the cube on the screen