Main Site Links Resources Tutorials
News VB Gaming Code Downloads DirectX 7
Contact Webmaster VB Programming Product Reviews DirectX 8
  General Multimedia Articles DirectX 9
      Miscellaneous

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...

screenshot


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)

FaceNormals.gif (6188 bytes)

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)

VertexNormals.gif (4112 bytes)

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.

spotlight.gif (3051 bytes)

    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

DirectX 4 VB 2000 Jack Hoxley. All rights reserved.
Reproduction of this site and it's contents, in whole or in part, is prohibited,
except where explicitly stated otherwise.
Design by Mateo
Contact Webmaster