PS2 Linux Programming
One Simple Directional Light & Ambient Light
Introduction
This tutorial demonstrates the application of lighting to a 3D model using a single white directional light. The surface of the model is considered to be an ideal diffuse reflector. Background to the lighting process is presented.
Directional Lights
Directional lights have only colour and direction, not position. They emit parallel light. This means that all light generated by directional lights travels through a scene in the same direction. A directional can be considered as a light source at near infinite distance, such as the sun. Directional lights are not affected by attenuation or range, so the direction and colour specified are the only factors considered when calculating vertex colours. Because of the small number of illumination factors, these are the least computationally intensive lights to use.
Ambient Light
The ambient light component is one that affects all surfaces equally, regardless of their position or orientation. There is really no such thing as an ambient light; it is just a “fudge factor” to approximate the complex reflections between objects in a virtual environment. Ambient light can also be considered as a background light, that affects everything equally.
Ambient light can be specified as a single constant value of diffuse white light, or as a triple of constants in the red, green, and blue spectra.
Consider a point on the surface of an object having colour (Ro, Go, Bo). If a single white light constant for ambient light is used, Aw, then the light emitted from the object due to ambient light is:
Ambient Illumination = (Ro*Aw, Go*Aw, Bo*Aw)
If the ambient light is specified as a triplet of constants, (Ra, Ga, Ba), then the light emitted from the object due to ambient light, (R, G, B) is:
Ideal Diffuse Reflection
An ideal diffuse surface is, at the microscopic level, a very rough surface. Chalk is a good approximation to an ideal diffuse surface. Because of the microscopic variations in the surface, an incoming ray of light is equally likely to be reflected in any direction over the a hemisphere as shown in the figures below.
Ideal diffuse reflectors reflect light according to Lambert's cosine law, (they are sometimes called Lambertian reflectors). Lambert's law states that the reflected energy from a small surface area in a particular direction is proportional to cosine of the angle between that direction and the surface normal. Lambert's law determines how much of the incoming light energy is reflected. Remember that the amount of energy that is reflected in any one direction is constant in this model. In other words the reflected intensity is independent of the viewing direction. The intensity does however depend on the orientation of the light source relative to the surface, and it is this property that is governed by Lambert's law.
The angle between the surface normal and the incoming light ray is called the angle of incidence and the reflected diffuse light intensity can be expressed in terms of this angle.
The Ilight term represents the intensity of the incoming light at a particular colour. The kd term represents the diffuse reflectivity of the surface which is a value between zero and one. A value of zero means that all of the light is absorbed and converted to heat; a value of one is an ideal reflector.
When computing this equation we can make use of vector maths to compute the cosine term without directly determining theta. If both the normal vector and the incoming light vector are normalised (unit length) then the diffuse shading component is given by:
where n is the surface normal and l is the normalised light direction vector. In this equation only angles from 0 to 90 degrees need to be considered. Greater angles are blocked by the surface, and the reflected energy is zero.
Program Overview
In order to compute the colour and illumination level of a vertex it is necessary to know the vertex normal. The information associated with a vertex is therefore modified to contain the vertex normal rather than the vertex colour. The definition of one of the faces of the cube is repeated below to illustrate this point:
// Front face
VIFStaticDMA.AddVector(Vector4(0, 0, 1, 0)); // TexCoord (STQ)
VIFStaticDMA.AddVector(Vector4( 0, 0, 1, 0)); // Normal
VIFStaticDMA.AddVector(Vector4(-1.0f, 1.0f, 1.0f, 1.0f)); // Vert (xyzw)
VIFStaticDMA.AddVector(Vector4(1, 0, 1, 0)); // TexCoord (STQ)
VIFStaticDMA.AddVector(Vector4( 0, 0, 1, 0)); // Normal
VIFStaticDMA.AddVector(Vector4(1.0f, 1.0f, 1.0f, 1.0f)); // Vert (xyzw)
VIFStaticDMA.AddVector(Vector4(0, 1, 1, 0)); // TexCoord (STQ)
VIFStaticDMA.AddVector(Vector4( 0, 0, 1, 0)); // Normal
VIFStaticDMA.AddVector(Vector4(-1.0f,-1.0f, 1.0f, 1.0f)); // Vert (xyzw)
VIFStaticDMA.AddVector(Vector4(0, 1, 1, 0)); // TexCoord (STQ)
VIFStaticDMA.AddVector(Vector4( 0, 0, 1, 0)); // Normal
VIFStaticDMA.AddVector(Vector4(-1.0f,-1.0f, 1.0f, 1.0f)); // Vert (xyzw)
VIFStaticDMA.AddVector(Vector4(1, 0, 1, 0)); // TexCoord (STQ)
VIFStaticDMA.AddVector(Vector4( 0, 0, 1, 0)); // Normal
VIFStaticDMA.AddVector(Vector4(1.0f, 1.0f, 1.0f, 1.0f)); // Vert (xyzw)
VIFStaticDMA.AddVector(Vector4(1 , 1, 1, 0)); // TexCoord (STQ)
VIFStaticDMA.AddVector(Vector4( 0, 0, 1, 0)); // Normal
VIFStaticDMA.AddVector(Vector4(1.0f, -1.0f, 1.0f, 1.0f)); // Vert (xyzw)
It is also necessary to specify a light direction vector for the directional light:
Vector4 vLight(1.0f, 0.0f, -0.5f, 1.0f);
vLight.NormaliseSelf();
In this case the light vector is coming from the left and slightly down the negative z axis. The light vector is also normalised. It is important to point out that this light vector is defined in the World Coordinate System. The light vector does not change in this application so it is packed into the static memory area that is sent to the VU every frame.
In the previous tutorials, only the combined vertex transformation matrix was sent to the VU for processing the vertex data. However, when computing directional lighting it is also necessary to send the world transformation matrix for the object being rendered. This is in order to transform the vertex normals from local space into world space before the lighting calculation can be done. Remember that the vertex normals are defined in local model space and the light vector is defined in world space. It is therefore necessary to transform the vertex normals from local to world space, and this requires the use of the world transformation matrix.
The layout of data in VU data memory is therefore as illustrated below:
Address |
Data |
0 |
Transformation Matrix Row #0 |
1 |
Transformation Matrix Row #1 |
2 |
Transformation Matrix Row #2 |
3 |
Transformation Matrix Row #3 |
4 |
World Matrix Row #0 |
5 |
World Matrix Row #1 |
6 |
World Matrix Row #2 |
7 |
World Matrix Row #3 |
8 |
Scaling Vector (number of vertices in w) |
9 |
Directional Light Vector (normalised) |
10 |
GIFTag |
11 |
Texture Coordinate (STQ) for Vertex #1 |
12 |
Normal for Vertex #1 |
13 |
Position for Vertex #1 |
14 |
Texture Coordinate (STQ) for Vertex #2 |
15 |
Normal for Vertex #2 |
16 |
Position for Vertex #2 |
17 |
Texture Coordinate (STQ) for Vertex #3 |
18 |
Normal for Vertex #3 |
19 |
Position for Vertex #3 |
etc |
etc |
VU1 Data Memory Layout
VU1 Micro Program.
The complete VU1 micro program is repeated below for clarity. Only the new sections associated with the lighting will be described here.
ProjMat .equ 0
WorldMat .equ 4
Scale .equ 8
NumVertsMem .equ 8
LightMem .equ 9
GIFTag .equ 10
StartSTQ .equ 11
StartNorm .equ 12
StartVert .equ 13
; This is a file full of macros that comes with VCL
.include "vcl_sml.i"
; Tell VCL that we want to be able to use all vf, and vi registers
.init_vf_all
.init_vi_all
.syntax new
.vu
; This is the entry point, and is where execution will start on VU1
--enter
--endenter
fcset 0x000000 ; VCL won't let us use CLIP without first zeroing
; the clip flags
iaddiu iVert, vi00, 0 ; Start vertex counter
iaddiu iVertPtr, vi00, 0 ; Point to the first vert
ilw.w iNumVerts, NumVertsMem(vi00) ; Load the number of verts from memory
lq fScales, Scale(vi00) ; A scale vector that we will add
; to and scale the vertices by after
; projecting them
lq fLight, LightMem(vi00) ; Load the light direction
sub.xyz fLight, vf00, fLight ; Reverse the light vector so that we can
; dot product it with the normals to give
; the light's intensity
; Load the transformation matrix
MatrixLoad fTransform, ProjMat, vi00
; Load the world matrix
MatrixLoad fWorldMat, WorldMat, vi00
loop: ; For each vertex
lq Vert, StartVert(iVertPtr) ; Load the vector (currently in object space
; and floating point format)
; Transform the vertex by the projection matrix
MatrixMultiplyVertex Vert, fTransform, Vert
clipw.xyz Vert, Vert ; This instruction checks if the vertex is outside
; the viewing frustum.
fcand vi01, 0x3FFFF ; Bitwise AND the clipping flags with 0x3FFFF
iaddiu iADC, vi01, 0x7FFF ; Add 0x7FFF.
isw.w iADC, StartVert(iVertPtr); store the ADC bit back to memory
div q, vf00[w], Vert[w] ; Work out 1.0f / w
mul.xyz Vert, Vert, q ; Project the vertex by dividing by W
lq STQ, StartSTQ(iVertPtr) ; Load the texture coordinates
mul STQ, STQ, q ; Divide by W
sq STQ, StartSTQ(iVertPtr) ; Store the texture coordinates back
lq Normal, StartNorm(iVertPtr) ; Load the vertex normal
; Transform the normal into world space
MatrixMultiplyVertex Normal, fWorldMat, Normal
VectorDotProduct Normal, fLight, Normal ; Puts the light intensity into Normal.x
loi 0.1 ; Ambient
maxi.x Normal, Normal, i ; if( Normal.x < Ambient ) Normal.x = Ambient;
loi 128
addi.xyz FinalCol, vf00, i ; Light colour (0x80, 0x80, 0x80)
mul.xyz FinalCol, FinalCol, Normal[x] ; Scale by the intensity
addi.w FinalCol, vf00, i
ftoi0 FinalCol, FinalCol ; Convert to integer
sq FinalCol, StartNorm(iVertPtr) ; Then write back to memory
mula.xyz acc, fScales, vf00[w] ; Move fScales into the accumulator
madd.xyz Vert, Vert, fScales ; (Vert = Vert * fScales + fScales)
ftoi4.xyz Vert, Vert ; Convert to 12:4 fixed point for the GS
sq.xyz Vert, StartVert(iVertPtr) ; Write the vertex back to VU memory
iaddiu iVert, iVert, 1 ; Increment the vertex counter
iaddiu iVertPtr, iVertPtr, 3 ; Increment the vertex pointer
ibne iVert, iNumVerts, loop ; Branch
iaddiu iGIFTag, vi00, GIFTag ; Load the position of the giftag
xgkick iGIFTag ; and tell the PS2 to send that to the GS
--exit
--endexit
The light direction vector is loaded and reversed in direction with the following two lines of code:
lq fLight, LightMem(vi00) ; Load the light direction
sub.xyz fLight, vf00, fLight ; Reverse the light vector so that we can
It is necessary to reverse the light direction so that the dot product with the surface normal will give the correct sign. As well as loading the vertex transformation matrix, the world transformation matrix is also loaded:
; Load the world matrix
MatrixLoad fWorldMat, WorldMat, vi00
The following section of code performs the lighting calculation:
lq Normal, StartNorm(iVertPtr) ; Load the vertex normal
; Transform the normal into world space
MatrixMultiplyVertex Normal, fWorldMat, Normal
VectorDotProduct Normal, fLight, Normal ; Puts the light intensity into Normal.x
loi 0.1 ; Ambient
maxi.x Normal, Normal, i ; if( Normal.x < Ambient ) Normal.x = Ambient;
loi 128
addi.xyz FinalCol, vf00, i ; Light colour (0x80, 0x80, 0x80)
mul.xyz FinalCol, FinalCol, Normal[x] ; Scale by the intensity
addi.w FinalCol, vf00, i
ftoi0 FinalCol, FinalCol ; Convert to integer
sq FinalCol, StartNorm(iVertPtr) ; Then write back to memory
Firstly the vertex normal is loaded and transformed into world space using the world transformation matrix. The dot product between the vertex normal and the light direction vector is then taken and stored in the x component of the normal vector register (see the macro file). An ambient light intensity of 0.1 is used, and the light intensity is clamped so that it cannot fall below 0.1. Next a default colour of white (R, G, B) = (0x80, 0x80, 0x80) is loaded into the FinalCol floating point register and this is scaled by the light intensity result. An alpha value of 0x80 is also loaded into the FinalCol register. Finally, the vertex colour data is converted from floating point into integer and stored back into VU memory.
Example Program
The example program displays a spinning cube which can be rotated round the viewer with the left and right DPads. The directional light comes form the left of the scene and it is seen to illuminated the left had side of the cube as it rotates.
Conclusions
This tutorial has provided background information on the use of directional light on a diffuse surface. A VU program has been presented which performs the required lighting calculations.
Dr Henry S Fortuna
University of Abertay Dundee