Immediate Mode: Lights
and DrawIndexedPrimitive
By: Carl Warwick [Email] [Web: Freeride Designs]
Written: July 9 2000
Download:
IM_Light.Zip (13kb)
Contents
Introduction
I'm back for another tutorial, this time I'll show you
how to create a light which can be either a Point light, Directional light or Spot light
(press the keys 1 to 3 to change the light type). Then we will render a cube made of the
D3DVERTEX type, remember we have mainly been using the D3DLVERTEX type, but as we are now
using lights we want D3D to do the lighting for us, thats why we need the D3DVERTEX type.
We will also render the cube using DrawIndexedPrimitive rather than DrawPrimitive.
Lets get on with it then...
Declarations
As usual our project will have 1 form "frmMain",
and 1 module "modMain", and all our declarations will go in "modMain".
Public
VertexList(7)
As
D3DVERTEX 'Array of vertices of the cube
Public
CubeIndices(35)
As
Integer 'The cubes indices |
|
|
|
As you can see we haven't changed much, but take note that we are using D3DVERTEX,
not D3DLVERTEX. Because we are using DrawIndexedPrimitive we only need to define
the 8 corners of our cube, unlike last time when we had to make 24 vertices.
I've set up an array of integers that will hold our
indices to our vertices. The reason we need 36 indices is because each side of a cube has
4 corners, that means we need to make two triangles to make each side, and as we have six
sides we have 6*3*2=36, 6 sides, 3 points in a triangle and 2 triangles on each side. I'll
go into more detail about indices and DrawIndexedPrimitive a bit later.
Initialisation
We don't need to change much in our
initialisation, but
we will need to add a couple of bits. The first thing we need to do is to make a material,
this is pretty easy.
'We need to make a material, otherwise the cube will not appear
Dim
mtrl
As
D3DMATERIAL7
mtrl.diffuse.r
= 1:
mtrl.diffuse.g
= 1:
mtrl.diffuse.b
= 1
mtrl.Ambient.r
= 1:
mtrl.Ambient.g
= 1:
mtrl.Ambient.b
= 1
'Commit the material to the device.
Device.SetMaterial
mtrl |
|
|
|
And now we need to set up our lighting model:
'==============
'Make
our
light
'==============
'Set
ambient
lighting,
values
range
between
0 and
1,
'1
being
brightest
of
that
color.
'the
RGB
componants
are
used,
A
(alpha)
is not
used
Device.SetRenderState
D3DRENDERSTATE_AMBIENT,
DX.CreateColorRGBA(0.2,
0.2,
0.2,
0)
'Call our sub that makes a light, pass 0 to make a point light
Call
Make_Light(0)
'Enable our light, to have more than one
light just change the index to enable
'the different lights
Device.LightEnable
0,
True
'Enable lighting
Device.SetRenderState
D3DRENDERSTATE_LIGHTING,
True |
|
|
|
First we set our ambient lighting to a dark shade of grey, remember that the
values in CreateColorRGBA range between 0 and 1, 1 being full intensity. Ambient
lighting doesn't use the A (alpha) component.
Ambient lights affect everything in the scene.
Then we call our subroutine that creates our
lights (explained later), we pass a 0 to our sub so that it creates a point light.
Then we enable our light (we only have one
light at index 0), and finally enable lighting.
Making our
cube
Next we need to make our cube, in a real application we
would load our objects from files rather than making them all by hand, in a future
tutorial I will show you how to load D3Ds native format .x files. For now we will make our
cube by hand to save any confusing file handling routines.
Public
Sub
Initialise_Geometry()
'Make the 8 vertices (corners of the cube)
Call
DX.CreateD3DVertex(-10,
-10,
-10,
-0.33,
-0.33,
-0.34,
0, 0,
VertexList(0))
Call
DX.CreateD3DVertex(-10,
10,
-10,
-0.33,
0.33,
-0.34,
0, 0,
VertexList(1))
Call
DX.CreateD3DVertex(10,
-10,
-10,
0.33,
-0.33,
-0.34,
0, 0,
VertexList(2))
Call
DX.CreateD3DVertex(10,
10,
-10,
0.33,
0.33,
-0.34,
0, 0,
VertexList(3))
Call
DX.CreateD3DVertex(-10,
-10,
10,
-0.33,
-0.33,
0.34,
0, 0,
VertexList(4))
Call
DX.CreateD3DVertex(-10,
10,
10,
-0.33,
0.33,
0.34,
0, 0,
VertexList(5))
Call
DX.CreateD3DVertex(10,
-10,
10,
0.33,
-0.33,
0.34,
0, 0,
VertexList(6))
Call
DX.CreateD3DVertex(10,
10,
10,
0.33,
0.33,
0.34,
0, 0,
VertexList(7))
'Create the indices (each side has 2 triangles)
'Remember, they should be in a clockwise order
'Front
CubeIndices(0)
= 0:
CubeIndices(1)
= 1:
CubeIndices(2)
= 2
CubeIndices(3)
= 1:
CubeIndices(4)
= 3:
CubeIndices(5)
= 2
'Right
CubeIndices(6)
= 2:
CubeIndices(7)
= 3:
CubeIndices(8)
= 6
CubeIndices(9)
= 3:
CubeIndices(10)
= 7:
CubeIndices(11)
= 6
'Back
CubeIndices(12)
= 6:
CubeIndices(13)
= 7:
CubeIndices(14)
= 4
CubeIndices(15)
= 7:
CubeIndices(16)
= 5:
CubeIndices(17)
= 4
'Left
CubeIndices(18)
= 4:
CubeIndices(19)
= 5:
CubeIndices(20)
= 0
CubeIndices(21)
= 5:
CubeIndices(22)
= 1:
CubeIndices(23)
= 0
'Top
CubeIndices(24)
= 1:
CubeIndices(25)
= 5:
CubeIndices(26)
= 3
CubeIndices(27)
= 5:
CubeIndices(28)
= 7:
CubeIndices(29)
= 3
'Bottom
CubeIndices(30)
= 2:
CubeIndices(31)
= 6:
CubeIndices(32)
= 0
CubeIndices(33)
= 6:
CubeIndices(34)
= 4:
CubeIndices(35)
= 0
'Set the cube to half its normal size, (because it looks nicer)
CubeScale
=
MakeVector(0.5,
0.5,
0.5)
End
Sub |
|
|
|
Here we just define the positions and normals of each vertex, then we create
our indices.
Normals
What is a normal I hear you all shouting, well I'll try
and explain.
Face Normals
Although you can't actually use face normals in D3D you can work out the vertex
normals from face normals, and its useful to know about them anyway.
A face normal points away from the front side of a face,
to be more specific a face normal is perpendicular to the plane of a face, pointing away
from the front side of the face. You know what side the front side is because it is the
side that is visible to us, it is a front face because its vertices are in a clockwise
order. (image taken from DirectX 7 SDK)
Vertex Normals
Vertex normals can be derived from the face normals of all the faces that use that vertex,
its all pretty simple to calculate, you just take the average of the face normals. eg.
VertexNormal = (FaceNormal1 + FaceNormal2 + FaceNormal3) / 3. (image taken from DirectX 7
SDK)
Normals are represented as vectors, that means they have
an X,Y and Z componant. The length of the normal should always be 1 unit long, this can be
achieved by using dx.VectorNormalise(v as D3DVECTOR) This
just normalises (make a length of 1) the vector "v" for you.
OK then, we know what a normal is, but why do we need it?
This is simple, its so that D3D knows the angle the light makes with the vertex so that it
can correctly light it. If we didn't have normals then D3D wouldn't be able to do our
lighting for us.
Indices
I've told you we are going to use DrawIndexedPrimitive
and I've told you we need an array of indices, but I haven't told you what indices are
yet.
Its easy, indices are just integers that reference a
vertex in our vertex list, so indices(0 to 2) indexes the vertices used in our first
triangle, indices(3 to 5) indexes the vertices used in our second triangle etc. etc.
The reason we use indices is to save us from having to
repeat vertices, remember in the last tutorial we had to specify 24 vertices to make our
cube, in this one we only need to specify 8 vertices to make the same cube!
I hope you all remember that the vertices of our
triangles in D3D need to be specified in a clockwise order otherwise they won't be drawn,
well this is still true when using Indices and DrawIndexedPrimitive.
Lets say we have an array of indices, and Indice(0)=4, Indice(1)=9, and Indice(2)=42 then
our vertices 4, 9 and 42 need to be in a clockwise order to be drawn.
DrawIndexedPrimitive
This is very similar to the DrawPrimitive that we have
been using, but we just need to pass our indices along aswell. (the following line of code
is just one line, but its too long to fit here in one line)
Call
Device.DrawIndexedPrimitive(D3DPT_TRIANGLELIST,
D3DFVF_VERTEX,
VertexList(0),
UBound(VertexList)
+ 1,
CubeIndices,
UBound(CubeIndices)
+ 1,
D3DDP_DEFAULT) |
|
|
|
We are using a TriangleList, of the VERTEX type.
I'm using the Ubound() function to get the number of
Vertices and Indices, if you don't know how Ubound works then look it up in the VB help
files.
As you can see its all pretty self explanatory, just pass
the vertices and indices to the device.
Lights,
camera, action
All we've got left to do now is to make our light.
Point Lights
Point lights are faster than spot lights, but slower than directional
lights, and are my personal favorite. A point light emits light rays equally
in every direction from its source, (just like a light bulb).
You need to specify its position in world space. The direction of a point light
doesn't matter, but it should never be a zero.
Public
Sub
Make_Light(pType
As
Byte)
Dim
dLight
As
D3DLIGHT7
Select
Case
pType
Case 0 'Point
dLight.dltType
=
D3DLIGHT_POINT
dLight.position
=
MakeVector(20,
5,
-20)
dLight.direction
=
MakeVector(-1,
-1, 1) 'Direction isn't used in point lights
'This setup of attenuation values make the lights intensity
decrease in
'a linear path with the
distance from the light source
dLight.attenuation0
= 0
dLight.attenuation1
= 0.05
dLight.attenuation2
= 0
'Lets make our point light red
dLight.diffuse.a
= 1
dLight.diffuse.r
= 1
dLight.diffuse.g
= 0
dLight.diffuse.b
= 0
'Set the range of the light
dLight.range
= 100 |
|
|
|
Directional Lights
Directional lights are the fastest for rendering, they only need a direction, and
not a position. They are good for simulating a distant light source such as the sun.
Case 1 'Directional
dLight.dltType
=
D3DLIGHT_DIRECTIONAL
dLight.position
=
MakeVector(20,
5,
-20) 'Position isn't used in directional lights
dLight.direction
=
MakeVector(1,
-1, 1)
'Lets make our directional light blue
dLight.diffuse.a
= 1
dLight.diffuse.r
= 0
dLight.diffuse.g
= 0
dLight.diffuse.b
= 1
'Set the range of the light
dLight.range
= 100 |
|
|
|
Spot Lights
Spot lights are the slowest to compute. They emit two cones of light,
the bright central cone called the umbra, and a dim outer cone called the penumbra.
Phi is the angle of the penumbra, and theta is the angle of the umbra.
Case 2 'Spot
dLight.dltType
=
D3DLIGHT_SPOT
dLight.position
=
MakeVector(40,
-40,
-40)
dLight.direction
=
MakeVector(-1,
1, 1)
'The lights intensity doesn't change with distance
dLight.attenuation0
= 1#
'Lets make our spot light green
dLight.diffuse.a
= 1
dLight.diffuse.r
= 0
dLight.diffuse.g
= 1
dLight.diffuse.b
= 0
'Set the range of the light
dLight.range
= 100
'Phi is the angle of the outer cone
dLight.phi
= 60 *
Rad
'Theta is the angle of the inner cone
dLight.theta
= 35 *
Rad
'Falloff determines the way the lights intensity
'decreases between the inner
and outer cones
dLight.falloff
= 1 |
|
|
|
Now we have set-up the light we need to actually create it with this simple
call.
'To
make
more
than 1
light
just
change
the
index
for
each
light
'(and
remember
to
enable
that
light
using
'"Device.LightEnable
index,
True",
where
index
is the
light
index)
Device.SetLight
0,
dLight
End
Sub |
|
|
|
If you're not sure what attenuation is, then its how the lights intensity decreases
with distance, there are 3 attenuation values you can change, attenuation0,
attenuation1 and attenuation2.
To calculate the total attenuation Direct3D uses the
following mathmatical formula to work it out:-
Total Attenuation = 1 / (Attenuation0 +
Distance*Attenuation1 + Distance2*Attenuation2)
Conclusions
We've come to the end of yet another tutorial, this time you
should have learnt how to set up lights and how to use DrawIndexedPrimitive.
Whats next hey, I think we should take a look at
textures, I can here you all cheering now (honestly). And after that we'll delve into
loading .x files, and yet another cheer.
I really hope you are all learning something.
Carl Warwick
- Freeride Designs
|