the next step is to render it with shading & lighting effects
Lighting & shading
It is simple to shade coloured lines/polygons using RGB colours.
Consider case where we need K increments to colour between two pixel endpoint locations.
Endpoints define a start_colour and end_colour.
We linearly interpolate RGB between them to shade line segment
Linear interpolation: given a starting and ending value, compute
intermediate values using a linear scale
eg. start = 25, end = 55, and we want 99 intermediate values...
val(i) = start + (end-start) / 100 * i (0 <= i <= 100)
= 25 + (30 / 100) * i
Note that there are 101 values: start that there are, end, and 99 intermediate vals.
Pseudo-code...
start_colour = (Red_init, Green_init, Blue_init)
end_colour = (Red_end, Green_end, Blue_end)
for i=0 to k
Red_val(i) = R_init + (R_end - R_init) / K * i
(...similar for green, blue)
When you do this interpolation for R, G, B, you end up with a "blend"
of colours between start and end pixel colours
You can see this blend on an RGB colour cube: take two colours (points)
on the cube, and the line between them is the blending you will get
Flat shading
gives a faceted appearance
advantages: efficient, and adequate for many applications
disadvantages: not realistic, not all objects appear flat
Technique 1: Compute lighting at point on polygon face...
For each (front) face F on a polygonal model
choose a point P on polygon F
compute its normal (OpenGL: user supplies normal)
need to compute it when polygon defined, and save it with model data structure
the normal will have 3D transformations applied to it along with the model
compute the intensity I using intensity equation (amb, diff, spec)
fill that polygon with shade colour I
Technique 2: Compute lighting at each vertex, and use average for the polygon face.
Technique 3: Use lighting of last vertex supplied. (OpenGL approach!)
Gouraud Shading
Gouraud shading:
a colour interpolation technique
works well with illumination: light model computes light at vertices
Note: can be applied to manually supplied colours too.
linearly interprets shading across a polygonal surface from vertex
intensities
For each (front) face F of polygonal model:
1. Find the vertex normals of each vertex
find normals of polygons surrounding a vertex
find the average --> gives the vertex normal
2. Find the light intensity at each of these vertices
3. Linearly interpolate pixel intensities over the surface of the polygon
a. interpolate edge intensities between vertices
b. interpolate scane lines between edge intensities
only compute step 1 at model definition (transformations apply to normals)
effect: smooth surfaces
however, because interpolation is only across each face, you can still
see polygon artifacts in many cases
OpenGL: programmer supplies normals (step 1)
Gouraud
Each face has a normal.
Vertex normals are the averages of the faces surrounding them.
Note: with OpenGL, user supplies vertex normals directly.
Once normals of vertices of face P known, then the illumination of
those points can be computed with lighting equation.
Then, when scan-converting polygon P, the pixel colour (lighting) is
simply linearly interpolated from these vertex lighting values.
In example below, only 4 lighting computations done; rest of face is
interpolated from these values.
Phong Shading
uses interpolated normal vector at each point in object (rather than
single one per vertex as in Gouraud)
For each face F:
1. Compute vertex normals (as in Gouraud)
2. for each point P being sampled and scan-converted:
a. interpolate its normal using normals of vertices and scan-line edges
b. compute P's intensity, but with normal computed in (a).
Result is more accurate, smoother surfaces, with better highlighting.
Much more computation than with Gouraud: computes illumination at each pixel
of face scan being scan converted.
OpenGL uses these normals to compute light at each vertex
Model transformations are applied to normals as well.
Scaling will change the size of normals! (rotate, translate do not)
if glEnable(GL_NORMALIZE) used, OpenGL will normalize all normals
Calculates them at a computational cost.
Preferable to normalize yourself if possible.
vertices defined after a normal given will share that last defined normal
there may be more than 1 vertex per normal
To perform lighting of simulated curved surfaces...
You need to estimate the "average normal" of polygons sharing a vertex.
3D modeling programs should compute this automatically.
There are more advanced utilities in OpenGL that can compute surface normals
(eg. see glMapGrid, gpEvalMesh, and others)
OpenGL: defining lights
glLight{if}(GLenum light, GLenum pname, TYPE param);
light: identifier for light (GL_LIGHT0 ... GL_LIGHT7)
pname: lighting characteristic being set (see below)
param: values for pname characteristic
example characteristics (see documentation for others):
Parameter
Default
Meaning
GL_AMBIENT
(0.0, 0.0, 0.0, 1.0)
ambient intensity
GL_DIFFUSE
(1.0, 1.0, 1.0, 1.0)
diffuse intensity
GL_SPECULAR
(1.0, 1.0, 1.0, 1.0)
specular intensity
GL_POSITION
(0.0, 0.0, 1.0, 0.0)
(x, y, z, w) position of light
GL_SPOT_DIRECTION
(0.0, 0.0, -1.0)
direction of spot light
Check man pages for meaning of arguments
for the above lighting ones, "1.0" means "100% effect
on given channel"
colours defined as RGBA. Keep A=1.0 (opaque colours)
w parameter for GL_POSITION:
if w=0, then light is infinitely far away, and x, y, z describes direction;
if w <>0, then x, y, z describes homogeneous coords
enable lighting: glEnable(GL_LIGHTING);
enable a defined light source: glEnable(GL_LIGHT0);
causes lighting equations to keep direction from viewer eye to vertices
as a constant; not as realistic, but much faster
(else must recompute direction
from eye to vertex for every vertex processed)
speeds up specular calculations
set to GL_TRUE if you want a finite local viewer
OpenGL: materials
glMaterial{if}[v](GLenum face, GLenum pname, TYPE param);
face: GL_FRONT (usually for solid models), GL_BACK, GL_FRONT_AND_BACK
pname: property being set (see below)
param: values for pname property
example characteristics (see man pages for others):
Parameter
Default
Meaning
GL_AMBIENT
(0.2, 0.2, 0.2, 1.0)
ambient colour of material
GL_DIFFUSE
(0.8, 0.8, 0.8, 1.0)
diffuse colour
GL_SPECULAR
(0.0, 0.0, 0.0, 1.0)
specular colour
GL_SHININESS
0.0
specular exponent
example:
GLfloat mat_ambient = {0.7, 0.7, 0.7, 1.0};GLfloat mat_diffuse = {0.8, 0.2, 0.5, 1.0};GLfloat mat_specular = {0.0, 0.5, 0.0, 1.0};glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);draw_my_object(); /* will be drawn with above material */