This page describes how use the EduPhong C++ software to describe material properties and light properties and render a scene with Phong lighting. It is a full-featured implementation that includes:
The documentation below uses code fragments from SimpleLightModern.cpp and from EduPhong.cpp and EduPhong.h. It may be helpful to consult those as you read the documentation. The sample codes below are at times slightly edited from the original.
The page describes only the C++ commands for using the EduPhong code. For a description of how the GLSL shader code works, see the planned second edition of the book 3D Computer Graphics, A Mathematical Introduction with OpenGL by the author.
We assume basic familiarity with the Phong lighting model, and its use of emissive, ambient, diffuse and specular light.
The main portion of the EduPhong program is to calculate Phong lighting, and render triangles with either Phong shading or Gouraud shading. For this it uses the following C++ classes:
phMaterial
:
This C++ class holds the Phong material properties for
a surface. This includes the emissivity,
the ambient, diffuse and specular reflectivity coefficients,
the specular exponent, and the Fresnel property. The
material properties are loaded into the shader
before the surface is rendered.
phLight
:
This C++ class holds the Phong for a single
light source. This includes the light's
ambient, diffure and specular colors,
and its position or direction. It also includes
distance attenuation and spotlight properties.
phGlobal
:
This C++ class holds properties of the Phong
lighting that are independent of any
single light or material. These include
the global ambient light, the number of lights,
and whether to render the emissive, ambient,
diffuse and specular components.
The phLight and PhGlobal data
need to be loaded into the shader only once,
unless the lighting changes.
The EduPhong code also provides two shader programs, one which performs Phong lighting with Phong shading, and a second one which performs Phong lighting with Gouraud shading.
EduPhong also provides a rudimentary ability to use textures; the texture is multiplicatively combined with the non-specular lighting (namely, with the emissive, ambient and diffuse colors).
Material properties give the color/reflective of a surface; namely, they describe how
the surface reflects light. The C++ class phMaterial
contains these
properties. The relevant part of the C++ class description is:
// ********
// phMaterial -
// Material properies describe the color/reflectively of the surface.
// Material properies are vertex attributes; however, they are
// generally the same across a single object and do not vary per vertex,
// so they are therefore implemented as generic vertex attributes.
// ********
class phMaterial {
public:
VectorR3 EmissiveColor;
VectorR3 AmbientColor;
VectorR3 DiffuseColor;
VectorR3 SpecularColor;
float SpecularExponent;
bool UseFresnel;
[…] Constructor omitted here
// Load uniform values in the two shader programs.
void LoadIntoShaders();
};
The four vectors EmissiveColor
,
AmbientColor
, DiffuseColor
and AmbientColor
and the scalar
SpecularExponent
hold the Phong material
properties for the surface. Each vector consists of
red, green and blue components.
These are assigned by the usual commands for
VectorR3
's. For instance, the
emissive and specular components of the
materials for the six spheres in SimpleLightModern.cpp
are set up by the following commands:
// Set the unchanging parts of myMaterial1 (for the large spheres)
phMaterial myMaterial1;
myMaterial1.EmissiveColor.SetZero();
myMaterial1.SpecularColor.Set(1.0, 1.0, 1.0);
myMaterial1.SpecularExponent = 50.0;
The Set
is a convenience function: for example, the specular color
could have equivalently been set by using the following command. (See the
GlLinearMath page for more information
on working with VectorR3
and LinearMapR4
objects.)
myMaterial1.SpecularColor = VectorR3(1.0, 1.0, 1.0);
Each sphere gets its own ambient and diffuse colors: these are set a few lines later by the commands:
myMaterial1.AmbientColor = AmbDiffColors[i]; // Ambient color for the i-th sphere
myMaterial1.DiffuseColor = AmbDiffColors[i]; // Diffuse color for the i-th sphere
Here AmbDiffColors
is an array of VectorR3
's.
By the way, it is usual to set the ambient and diffuse colors
to be the same value, or at least scalar multiples of each other.
It also is usual to set the specular material color to be
a white or gray color so that the specular highlights will have
take on the specular color of the light.
UseFresnel
can be set to either true or false to
enable or disable the Fresnel term for specular lights. It
defaults to false, without the Fresnel term.
Once the entries of the phMaterial
are set,
they need to be loaded into OpenGL's memory as generic
vertex attributes. This is done by the following command:
myMaterial1.LoadIntoShaders(); // Load the phMaterial information into the shaders
LoadIntoShaders()
is
is typically called right before the call to a
glDrawArrays...
or glDrawElements...
function which renders the object. In SimpleLightModern.cpp,
this command is given before the call to
mySphere.Render()
.
The global light properties give the Phong lighting values
with are independent of particular light sources. Traditionally,
this includes just the global ambient color (GlobalAmbientColor
).
The C++ class phGlobal
also contains the number
of lights and contains booleans indicating whether the view is local,
and whether the emissive, ambient, diffuse and specular
components of Phong lighting should be rendered. phGlobal
is defined by:
// ********
// phGlobal - Global illumination properties.
// These are uniform values.
// The viewer is presumed to be at the origin, looking down the negative z-axis
// ********
class phGlobal {
public:
VectorR3 GlobalAmbientColor; // Global ambient light color
unsigned int NumLights; // Number of lights.
bool LocalViewer; // true for local viewer ; false for directional viewer (default is false)
bool EnableEmissive; // Control whether emissive colors are rendered (default is true)
bool EnableDiffuse; // Control whether diffuse colors are rendered (default is true)
bool EnableAmbient; // Control whether ambient colors are rendered (default is true)
bool EnableSpecular; // Control whether specular colors are rendered (default is true)
bool UseHalfwayVector; // Control whether to use the halfway vector (default is false)
[…]
void LoadIntoShaders(); // Load the global lighting data into the shaders
};
SimpleLightModern declares a phGlobal
as a global variable:
phGlobal globalPhongData;
The global lighting information is initialized by the commands
globalPhongData.LocalViewer = true;
globalPhongData.EnableEmissive = true; // The four "Enable" values default to true (enabled)
globalPhongData.EnableDiffuse = true; // and thus they do not really need to be specified
globalPhongData.EnableAmbient = true;
globalPhongData.EnableSpecular = true;
globalPhongData.NumLights = 1;
globalPhongData.GlobalAmbientColor.Set(0.2, 0.2, 0.2);
Then, after the shaders have been setup by a calling
setup_phong_shaders()
,
the global lighting data is
loaded into OpenGL's buffers for use by the shaders by calling
globalPhongData.LoadIntoShaders(); // Must do this after the shaders are set up.
This does not need to be called every render cycle. It is
only necessary to call
globalPhongData.LoadIntoShaders()
when there is change to
the global lighting parameters.
(For this, see the key_callback
routine in SimpleLightModern.)
In Phong lighting, each individual light has its own, rather long,
list of light parameters. In EduPhong, these are stored in
an object belonging to the C++ class phLight
defined by:
// phLight - Light properties describe the color/brightness of a light.
// Light properties may need to be accessed by the fragment shader
// as well as the vertex shader (at least for Phong shading),
// so they are uniform values, not vertex attributes.
class phLight {
public:
bool IsEnabled; // True if light is turned on (default is false)
bool IsAttenuated; // True if attenuation is active (default is false)
bool IsSpotLight; // True if spotlight (default is false)
bool IsDirectional; // True if directional (default is false)
private:
VectorR3 PosOrDir; // Position or direction, already transformed by the model view matrix
public:
VectorR3 AmbientColor;
VectorR3 DiffuseColor;
VectorR3 SpecularColor;
private:
VectorR3 SpotDirection; // Spot light center direction, already transformed by the model view matrix
public:
float SpotCosCutoff; // Cosine of cutoff angle
float SpotExponent;
float ConstantAttenuation;
float LinearAttenuation;
float QuadraticAttenuation;
[…]
void SetPosition(const LinearMapR4& modelviewMatrix, const VectorR3& position);
void SetDirection(const LinearMapR4& modelviewMatrix, const VectorR3& direction);
void SetSpotlightDirection(const LinearMapR4& modelviewMatrix, const VectorR3& direction);
void LoadIntoShaders(int lightNumber);
};
Most of these values can be set directly. For example, SimpleLightModern
declares movingLight
as a phLight
object
and sets its initial parameters by calling:
movingLight.IsEnabled = true; // Must set this, default value is false!
movingLight.IsAttenuated = false; // Optional to set this, default is false.
movingLight.IsSpotLight = false; // Optional to set this, default is false.
movingLight.AmbientColor.Set(0.3, 0.3, 0.3); // Contributes fairly low level of ambient light
movingLight.DiffuseColor.Set(1.0, 1.0, 1.0); // Bright white for diffuse
movingLight.SpecularColor.Set(1.0, 1.0, 1.0); // Bright white for specular
However, the PosOrDir
and SpotDirection
cannot
be set so simply. PosOrDir
will contain either
the (x,y,z) position of the light (if the light is positional)
or the (x,y,z) direction of the light (if the light is
directional). This vector value of PosOrDir
must be the value expressed in the "world coordinates".
The "world coordinate" system is the target
coordinate system of
the ModelView matrix; it is also the coordinate system where
the viewer either is placed at the origin (if positional)
or is looking in the negative z direction (if directional).
The vertex shader does all Phong lighting
calculations using the
"world coordinates" system:
the vertex shader is given a vertex's position and normal
in local coordinates; the shader
transforms the vertex's position and normal
with the model view matrix which is in effect
when the vertex is rendered. Then, the
vertex shader needs the light's position
or direction to be in the same coordinate system for the Phong
lighting calculation.
The routines SetPosition
and SetDirection
thus let you set the position or direction in this way. They
take a 4x4 matrix, modelviewMatrix
, as input
and use it to transform the position or direction. At the same time,
they set the boolean variable IsDirectional
to the
corresponding appropriate value. SimpleLightModern does this with:
if (movingLight.IsDirectional) {
movingLight.SetDirection(theViewMatrix, -lightPosition); // Direction
}
else {
movingLight.SetPosition(theViewMatrix, lightPosition); // Position
}
The above code uses -lightPosition
, with a minus sign,
since the direction is taken to be the direction from
the light's position towards the origin.
Finally, the phLight
parameters need to be
loaded into an OpenGL buffer for use by the shaders. This
done with the routine phLight::LoadIntoShaders
.
In SimpleLightModern, it is
movingLight.LoadIntoShaders(0); // Upload as light #0.
This call to movingLight.LoadIntoShaders(0)
loads all of the lighting parameters from movingLight
into the OpenGL buffers for the uniform light values. The parameter
0
specifies the light number. Valid light numbers are
0 through 7 (controlled by the variable phMaxNumLights
in EduPhong.h and in the shader programs).
The NumLights
of the phGlobal
object must
set high enough; for example, if you are using lights 0, 1, 2, and 3, then
NumLights
must be at least 4. In addition, do not
forget to set the IsEnabled
flag for each phLight
object.
A similar routine, SetSpotlightDirection
, can be
used to set the direction of a spotlight. It is necessary to also
set IsSpotLight
true to activate the light as a spotlight.
SpotCosCutoff
and SpotExponent
both default
to the value 0. In almost all applications, SpotCosCutoff
should be set to a larger value (closer to 1) to make a spotlight with
a tighter beam. Optionally, SpotExponent
can be set larger
to form a spotlight that attenuates away from the center of the
spotlight cone.
The following steps were needed to render with Phong lighting in the program SimpleLightModern.
setup_phong_shaders
. This compiles and links the shaders,
and must be done before any
LoadIntoShaders
routine is called.
phLight
objects describing
the properties of the lights. Initialize them,
update them as needed.
Use the appropriate modelview matrix when setting a
light's position or direction, or spotlight direction.
Load them into OpenGL's buffers by
calling LoadIntoShaders
for each phLight
object (with distinct light numbers).
phGlobal
object describing the
global lighting properties. Initialize it,
update it as needed. Load it into OpenGL's buffers by
calling LoadIntoShaders
before rendering.
phMaterial
with the material
properies for the next object. (If different from
the previous item rendered.) Use LoadIntoShaders
to upload it to the shader program.
EduPhong provides a routine setup_phong_shaders()
for
setting two (2) shader programs. The first one,
called phShaderPhongGouraud
does
Gouraud shading. The second one, called
phShaderPhongPhong
does Phong shading.
Gouraud shading is faster, since it does the Phong lighting
calculation only the vertices, whereas the Phong shading
does the Phong lighting calculation at every vertex.
Phong shading can give better visual effects, however,
especially when there is animation.
The two shader programs are compiled and linked by calling
setup_phong_shaders();
SimpleLightModern selects which shader program to use
with the following commands. The first three lines
choose the active shader program, and save the vertex
shader's location for the projection matrices and
model view matrices. The final glUseProgram
tells OpenGL which shader program to use.
(It is not actually
necessary to call glUseProgram
every render cycle if the choice
of shader program is not changing.)
currentShaderProgram = UsePhongGouraud ? phShaderPhongGouraud : phShaderPhongPhong;
projMatLocation = glGetUniformLocation(currentShaderProgram, phProjMatName);
modelviewMatLocation = glGetUniformLocation(currentShaderProgram, phModelviewMatName);
[…]
glUseProgram(currentShaderProgram);
EduPhong includes a rudimentary ability to add texture maps to surfaces. This is an extraneous feature, but is included for convenience mainly because it is always useful in programming assignments for Math 155A, but also because it illustrates how to apply a texture map that affects only the non-specular component of lighting.
To use EduPhong's texture mapping, you must enable the texture mapping in the shader. This can done by the following two lines:
unsigned int applyTextureLocation = phGetApplyTextureLoc(currentShaderProgram);
glUniform1i(applyTextureLocation, true); // Enable applying the texture!
where currentShaderProgram
is, as above, the shader program
to be used for rendering with a texture. (The call to
phGetApplyTextureLoc
returns the results of
glGetUniformLocation(programID, "applyTexture")
.)
The call to glUniform1i
sets the uniform variable true to
enable texture mapping. Setting it false disables texture mapping.
To be sure the shader program is using the default texture map, you can use the command
glUniform1i(glGetUniformLocation(shaderProgramBitmap, "theTextureMap"), 0);
You also must select the texture to be used by shader program: this is called "binding" the texture, and is done with commands such as
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, myTextureName);
The texture specified with glActiveTexture
should correspond with the value set earlier to
theTextureMap
. The variable
myTextureName
is an unsigned int which "names" a
texture. It must be the name of an OpenGL two-dimensional texture
(a GL_TEXTURE_2D
) that was already created
by the OpenGL program, say by calls to glTexImage2D()
and glGenerateMipmap()
.
For examples of how to read texture data from a file to form a
2D texture in OpenGL, see the programs
TextureBmpModern or
FourTexturesModern.
This collection of C++ programs does not currently have any examples of using textures with EduPhong, but you can refer to the 2019 Math 155A course's Project 6 assignment for a code example.
If you want to customise the Phong lighting shaders for applications,
you will need to rewrite them. A common example would be it do
more sophisticated uses of texture maps in conjunction
with Phong lighting. Fortunately, the code is structured to
facilitate writing your own versions. As an example of how Phong shaders
are compiled, here is the code from the routine setup_phong_shaders()
.
GlShaderMgr::LoadShaderSource("EduPhong.glsl");
unsigned int shader_VPG = GlShaderMgr::CompileShader("vertexShader_PhongGouraud", "calcPhongLighting");
unsigned int shader_FPG = GlShaderMgr::CompileShader("fragmentShader_PhongGouraud", "applyTextureMap");
unsigned int shaders_PG[2] = { shader_VPG, shader_FPG };
phShaderPhongGouraud = GlShaderMgr::LinkShaderProgram(2, shaders_PG);
phRegisterShaderProgram(phShaderPhongGouraud);
unsigned int shader_VPP = GlShaderMgr::CompileShader("vertexShader_PhongPhong");
unsigned int shader_FPP = GlShaderMgr::CompileShader("fragmentShader_PhongPhong", "calcPhongLighting", "applyTextureMap");
unsigned int shaders_PP[2] = { shader_VPP, shader_FPP };
phShaderPhongPhong = GlShaderMgr::LinkShaderProgram(2, shaders_PP);
phRegisterShaderProgram(phShaderPhongPhong);
This code uses the standard GlShaderMgr routines
for compilation and linkage. It also needs to
call phRegisterShaderProgram
for each shader.
phRegisterShaderProgram
links the uniform structures in the EduPhong shaders to a common
Uniform Buffer Object. This allows multiple versions of the Phong
shader to use the data from the same Uniform Buffer Object for the global lighting
properties and the lights' properties.
Explaining how to link uniform blocks to Uniform Buffer Objects is beyond
the scope of this web page. But, the code for phRegisterShaderProgram
shows an example of how it is done. The LoadIntoShaders
routines
show how to update the data in the Uniform Buffer Objects.
For an example of using custom changes to the EduPhong routines, please see again the 2019 Math 155A course's Project 6 assignment.