87
6
AFrameworkforGLSLEngineUniforms
Patrick Cozzi
Analytical Graphics, Inc.
6.1Introduction
The OpenGL 3.x and 4.x core profiles present a clean, shader-centric API. Many
veteran developers are pleased to say goodbye to the fixed-function pipeline and
the related API entry points. The core profile also says goodbye to the vast ma-
jority of GLSL built-in uniforms, such as
gl_ModelViewMatrix and gl_Proj-
ectionMatrix
. This chapter addresses the obvious question: what do we use in
place of GLSL built-in uniforms?
6.2Motivation
Our goal is to design a framework for commonly used uniforms that is as easy to
use as GLSL built-in uniforms but does not have their drawback: global state.
GLSL built-in uniforms were easy to use because a shader could just include a
built-in uniform, such as
gl_ModelViewMatrix, and it would automatically pick
up the current model-view matrix, which may have been previously set with calls
like the following:
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(modelViewMatrix);
A shader could even use built-in uniforms derived from multiple GL states, such
as
gl_ModelViewProjectionMatrix, which is computed from the current mod-
el-view and projection matrices (see Listing 6.1(a)). Using built-in uniforms
makes it easy to use both fixed-function and shader-based rendering code in the
same application. The drawback is that the global state is error prone and hard to
manage.
88 6.AFrameworkforGLSLEngineUniforms
#version 120
void main()
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
Listing 6.1(a). Pass-through vertex shader using GLSL built-in uniforms.
#version 330
in vec4 position;
uniform mat4 u_ModelViewProjectionMatrix;
void main()
{
gl_Position = u_ModelViewProjectionMatrix * position;
}
Listing 6.1(b). Pass-through vertex shader using our engine uniforms framework.
Our replacement framework should be just as easy to use as built-in uni-
forms, but without relying on global state. Shader authors should be able to de-
fine and use engine uniforms as shown in Listing 6.1(b), and C++ code should
automatically identify and set them.
In Listing 6.1(b),
u_ModelViewProjectionMatrix is an engine uniform that
serves as a replacement for
gl_ModelViewProjectionMatrix. You can use any
naming convention you’d like; in this case, the prefix
u_ stands for “uniform.”
Our framework should support any number of these engine uniforms, and it
should be easy to add new ones.
In addition to ease of use, the goal of our framework is to avoid global state.
The solution to this is to introduce a
State object that is passed to draw methods.
This object should contain all the necessary state to set our engine uniforms be-
fore issuing an OpenGL draw call. Listing 6.2 shows a very minimal interface for
this class. The user can set the model, view, and projection matrices; the model-
view and model-view-projection are derived state (similar to
gl_Model-
ViewProjectionMatrix
). This class uses a matrix type called Matrix44, which
is assumed to have the standard matrix goodies: static methods for affine trans-
6.3Implementation 89
class State
{
public:
const Matrix44& GetModel() const;
void SetModel(const Matrix44& value);
const Matrix44& GetView() const;
void SetView(const Matrix44& value);
const Matrix44& GetProjection() const;
void SetProjection(const Matrix44& value);
const Matrix44& GetModelView() const;
const Matrix44& GetModelViewProjection() const;
};
Listing 6.2. Minimal interface for state used to automatically set engine uniforms.
formations, operator overloads, and a method called Pointer() that gives us di-
rect access to the matrix’s elements for making OpenGL calls.
By encapsulating the state required for engine uniforms in a class, different
instances of the class can be passed to different draw methods. This is less error
prone than setting some subset of global states between draw calls, which was
required with GLSL built-in uniforms. An engine can even take this one step fur-
ther and define separate scene state and object state classes. For example, the
view and projection matrices from our
State class may be part of the scene state,
and the model matrix would be part of the object state. With this separation, an
engine can then pass the scene state to all objects, which then issue draw com-
mands using the scene state and their own state. For conciseness, we only use one
state class in this chapter.
6.3Implementation
To implement this framework, we build on the shader program and uniform ab-
stractions discussed in the previous chapter. In particular, we require a
Uniform
class that encapsulates a
4
4
matrix uniform (mat4). We also need a Shader-
Program
class that represents an OpenGL shader program and contains the pro-
gram’s uniforms in a
std::map, like the following:
std::map<std::string, Uniform *> m_uniformMap;
90 6.AFrameworkforGLSLEngineUniforms
class IEngineUniform
{
public:
virtual ~IEngineUniform() {}
virtual void Set(const State& state) = 0;
};
class IEngineUniformFactory
{
public:
virtual ~IEngineUniformFactory() {}
virtual IEngineUniform *Create(Uniform *uniform) = 0;
};
Listing 6.3. Interfaces used for setting and creating engine uniforms.
Our framework uses a list of engine uniforms’ names to determine what en-
gine uniforms a program uses. A program then keeps a separate list of engine
uniforms, which are set using a
State object before every draw call. We start our
implementation by defining two new interfaces:
IEngineUniform and IEngine-
UniformFactory
, shown in Listing 6.3.
Each engine uniform is required to implement both classes:
IEngineUniform
is used to set the uniform before a draw call, and
IEngineUniformFactory is
used to create a new instance of the engine uniform when the shader program
identifies an engine uniform.
Implementing these two interfaces is usually very easy. Let’s consider im-
plementing them for an engine uniform named
u_ModelViewMatrix, which is
the model-view matrix similar to the built-in uniform
gl_ModelViewMatrix. The
implementation for
IEngineUniform simply keeps the actual uniform as a mem-
ber and sets its value using
State::GetModelView(), as shown in Listing 6.4.
The implementation for
IEngineUniformFactory simply creates a new Mod-
elViewMatrixUniform
object, as shown in Listing 6.5.
The relationship among these interfaces and classes is shown in Figure 6.1.
Implementing additional engine uniforms, such as a model-view-projection ma-
trix, is usually just as straightforward as this case. In isolation, classes for engine
uniforms are not all that exciting. They need to be integrated into our
ShaderProgram class to become useful. In order to identify engine uniforms, a
6.3Implementation 91
class ModelViewMatrixUniform : public IEngineUniform
{
public:
ModelViewMatrixUniform(Uniform *uniform) : m_uniform(uniform)
{
}
virtual void Set(const State& state)
{
m_uniform->SetValue(state.GetModelView());
}
private:
Uniform *m_uniform;
};
Listing 6.4. Implementing an engine uniform for the model-view matrix.
program needs access to a map from uniform name to engine uniform factory.
The program can use this map to see which of its uniforms are engine uniforms.
Each program could have a (potentially different) copy of this map, which
would allow different programs access to different engine uniforms. In practice, I
have not found this flexibility useful. Instead, storing the map as a private static
member and providing a public static
InitializeEngineUniforms() method,
as in Listing 6.6, is generally sufficient. The initialization method should be
called once during application startup so each program has thread-safe, read-only
class ModelViewMatrixFactory : public IEngineUniformFactory
{
public:
virtual IEngineUniform *Create(Uniform *uniform)
{
return (new ModelViewMatrixUniform(uniform));
}
};
Listing 6.5. A factory for the model-view matrix engine uniform.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.218.157