Immediate Mode: Rotating a 3D object
By: Carl Warwick [Email] [Web: Freeride Designs]
Written: June 26 2000
Download:
IM_Rotate3D.Zip (9kb)
Contents
Introduction
I'm back with the second tutorial on using D3DIM in
Visual Basic, if you missed the first installment then be sure to read it before going
through this one.
You should now know how to initialise DDraw and D3DIM in
fullscreen mode (Please Note. From this tutorial initialising DDraw and D3DIM will be done
in different subs to make it easier to understand). Now we are going to build on our
knowledge by setting up a viewport, a projection matrix, a view matrix and a world matrix,
we'll then create our first 3D object, a pyramid. Our pyramid will be rotated by making
changes to our world matrix, and finally be rendered as a Triangle Fan.
Our pyramid will be made from LVertices, this means we
have to light them, but D3D will transform them from world space to screen space for us.
Declarations
Once again our project will have 1 form
"frmMain", and 1 module "modMain", and all our declarations will go in
"modMain".
Option
Explicit
Public
DX As
DirectX7 'Direct X
Public
DD As
DirectDraw7 'Direct Draw
Public
Primary
As
DirectDrawSurface7 'Primary surface
Public
BackBuffer
As
DirectDrawSurface7 'Backbuffer
Public
Direct3D
As
Direct3D7 'Direct 3D
Public
Device
As
Direct3DDevice7 'Direct 3D Device
Public
LVertexList(5)
As
D3DLVERTEX 'Array of vertices (L = Lit)
Public
bRunning
As
Boolean 'Boolean to check if program is still running
Public
Const
pi =
3.14 'Constant to hold the value of pi
Public
Const
Rad =
pi /
180 'multiply degrees by Rad to get angle in radians |
|
|
|
You will probably notice that I've only changed one thing
from the last tutorial, and that is I've replaced TLVertexList(3) with LVertexList(5), Our
pyramid has 6 vertices, the reason its not 5 vertices is that 2 of them are the same, this
is needed to complete our last face on the pyramid. We could resolve this problem by using
DrawIndexedPrimitive, but it would be more effort than its worth. (We'll examine
DrawIndexedPrimitive in a later tutorial).
Initialisation
You already know how to initialise DDraw, and you know
the basics of initialising D3DIM. I've added quite a bit to our D3DIM initialisation for
this tutorial, so I'll go through it here.
Public
Sub
Init_D3D(ScreenWidth
As
Integer,
ScreenHeight
As
Integer)
Dim
DEnum
As
Direct3DEnumDevices
Dim
Guid
As
String
Set
Direct3D
=
DD.GetDirect3D
'Get the last device driver (last one is usually the best one)
'you could specify or search for a particular device, eg
RGB or HAL device
Set
DEnum
=
Direct3D.GetDevicesEnum
Guid =
DEnum.GetGuid(DEnum.GetCount)
'Set the Device
Set
Device
=
Direct3D.CreateDevice(Guid,
BackBuffer)
'Define the viewport rectangle.
'This will just be the same size as our screen
Dim
VPDesc
As
D3DVIEWPORT7
VPDesc.lWidth
=
ScreenWidth
VPDesc.lHeight
=
ScreenHeight
VPDesc.lX
= 0
VPDesc.lY
= 0
VPDesc.minz
= 0#
VPDesc.maxz
= 1#
Device.SetViewport
VPDesc
|
|
|
|
You've already seen most of the above code,
the only new bit should be where we define the viewport. This is pretty simple, and you
will probably never need to change this. All the viewport does is tell DX what part of the
screen to render everything to, obviously we want this to be the same size as the screen
and starting in the top-left corner.
'Set
up
transformation
matrices.
'The
world
matrix
controls
the
position
and
orientation
of the
polygons
'in
world
space.
'By
changing
the
world
matrix
you
can
rotate,
move
and
scale
objects.
Dim matWorld As D3DMATRIX
DX.IdentityMatrix matWorld
Device.SetTransform D3DTRANSFORMSTATE_WORLD, matWorld
'The projection matrix defines how the 3D scene is
"projected" onto the
'2D render target (the backbuffer surface).
Dim matProj As D3DMATRIX
'Polygons that are a closer distance than 1 to the camera,
'and are a further distance than 100 will not be rendered.
'We're using a 90 degree (pi/2) field of view (FOV).
'Most people use either a 90 or 60 degree FOV for all
applications
Call DX.ProjectionMatrix(matProj, 1, 100, pi / 2#)
Device.SetTransform D3DTRANSFORMSTATE_PROJECTION, matProj
'The view matrix defines the position, look at position, and roll of the camera.
Dim matView As D3DMATRIX
'We use the MakeVector function to define the positions of our
camera,
'Our camera is -20 units along the Z axis, and looking at
the origin (0,0,0).
'We'll leave the Up vector as (0,1,0) and the roll as 0.
Call DX.ViewMatrix(matView, MakeVector(0, 0, -20),
MakeVector(0, 0, 0), MakeVector(0, 1, 0), 0#)
Device.SetTransform D3DTRANSFORMSTATE_VIEW, matView
'Because we are using the LVertex type we don't want
light to
'have any effect on our scene, so we turn lighting off.
Device.SetRenderState D3DRENDERSTATE_LIGHTING, False
'Lets use Gouraud shading, it much nicer than Flat
shading (D3DSHADE_FLAT)
'DX7 does not support phong shading, so don't try to use
it.
Device.SetRenderState D3DRENDERSTATE_SHADEMODE,
D3DSHADE_GOURAUD
End Sub |
|
|
|
Now we need to define our World Matrix,
Projection Matrix and View Matrix, both the projection matrix and the view matrix will not
change after they have been made here, but we will change the world matrix to rotate the
pyramid.
The world matrix defines the position, scale
and rotation of all the objects, so rather than moving each objects vertices to move an
object we just change the world matrix and it does it for us. This will be explained in
more detail further down.
The projection matrix (matrix, near clipping
plane, far clipping plane, field of view) just describes how far away from the camera
objects need to be before they are clipped (not rendered). The near clipping plane and far
clipping plane are the boundaries that an object must be in before being clipped.
The field of view (FOV) is used to determine the angle that is seen. Its best to use
angles of 90 and 60 degrees for the FOV.
The view matrix (matrix, From, To, Up, Roll)
determines the cameras position and direction.
From is the position of the camera.
To is where the camera is looking at.
Up should never be set to 0, and probably best left as (0,1,0).
Roll is the angle (in radians) that the camera is rotated around the z-axis.
We use a function that is defined in the code called MakeVector, you pass this function
the X,Y and Z co-ordinates and it returns a D3DVector with these co-ordinates.
Making our
pyramid
Next we need to make our pyramid.
Public
Sub
Initialise_Geometry()
'The first element of an array of a Triangle fan has to be
'the tip of the object.
LVertexList(0).X
= 0
LVertexList(0).Y
= 5
LVertexList(0).Z
= 0
LVertexList(0).Color
=
DX.CreateColorRGBA(1,
0, 0,
1)
LVertexList(0).specular
= 1
'I made the first vertex by defining each point, just
to show you that it
'can be done this way, but its much easier to use
DX.CreateD3D**Vertex.
'Therefore we could create the first vertex with the
following line.
'Call DX.CreateD3DLVertex(0, 5, 0, DX.CreateColorRGBA(1, 0,
0, 1), 1, 0, 0, LVertexList(0))
'Then we make the four base points of the pyramid in a clockwise order.
Call
DX.CreateD3DLVertex(0,
-5,
10,
DX.CreateColorRGBA(0,
0, 1,
1), 1,
0, 0,
LVertexList(1))
Call
DX.CreateD3DLVertex(10,
-5, 0,
DX.CreateColorRGBA(0,
1, 0,
1), 1,
0, 0,
LVertexList(2))
Call
DX.CreateD3DLVertex(0,
-5,
-10,
DX.CreateColorRGBA(1,
1, 0,
1), 1,
0, 0,
LVertexList(3))
Call
DX.CreateD3DLVertex(-10,
-5, 0,
DX.CreateColorRGBA(1,
0, 1,
1), 1,
0, 0,
LVertexList(4))
'We need to copy the first base vertex to make the last side of the pyramid.
Call
DX.CreateD3DLVertex(0,
-5,
10,
DX.CreateColorRGBA(0,
0, 1,
1), 1,
0, 0,
LVertexList(5))
End
Sub |
|
|
|
This is all pretty easy, we just define the positions and
colors of each vertex. Our pyramid is a Triangle Fan structure, if you need to know how a
triangle fan works then please refer to the previous tutorial.
We are defining our pyramids positions in world space,
not screen space, this is because we are using the LVertex type rather than the TLVertex
type.
You should see that two of our vertices are the same,
this is so that the last face on our pyramid shows up, if you didn't have this last one
then we would have a pyramid with a hole in it.
Main Loop
Now lets take a look at our Main_Loop.
Public
Sub
Main_Loop()
Dim
stepval
As
Integer 'The angle to rotate by
Dim
matWorld
As
D3DMATRIX 'Stores the world matrix, change this to move,
'scale
and
rotate
objects.
Dim
tmpMatrix
As
D3DMATRIX 'Used in world matrix calculations.
'Start our loop, press the 'Esc' key to exit
Do
Until
bRunning
=
False
'decrease stepval by 1 to rotate the rectangle
clockwise
'If stepval is less than 0
degrees then make stepval = 360 degrees
stepval
=
stepval
- 1
If
stepval
<=
0 Then
stepval
= 360
'We want to rotate the object around its Y-axis
'You can easily make it rotate
around the X or Z axis by
'changing RotateYMatrix to
RotateXMatrix or RotateZMatrix
DX.RotateYMatrix
matWorld,
stepval
* Rad
'===================================================
'If you want to rotate around Y
axis and Z axis then
'include the next 2 lines
aswell.
'DX.RotateZMatrix tmpMatrix,
stepval * Rad
'DX.MatrixMultiply matWorld,
matWorld, tmpMatrix
'===================================================
'Transform the world
Device.SetTransform
D3DTRANSFORMSTATE_WORLD,
matWorld
'Clear the device AFTER all
the changes to any vertices, and BEFORE
'calling Device.BeginScene
and any bltting.
Call
Clear_Device
'Begin the scene, must do
this after Calling Clear_Device, and before
'calling Blt3D()
Device.BeginScene
'Render the pyramid to the device (backbuffer), its a triangle fan.
Call
Device.DrawPrimitive(D3DPT_TRIANGLEFAN,
D3DFVF_LVERTEX,
LVertexList(0),
6,
D3DDP_DEFAULT)
'Call EndScene when finished using D3D routines
Device.EndScene
'Flip the primary surface
Primary.Flip
Nothing,
DDFLIP_WAIT
'Let windows do its stuff
DoEvents
Loop
End
Sub |
|
|
|
Once again we are going to use stepval as our angle (in
degrees) that our object should be rotated by.
DX.RotateYMatrix matWorld,
stepval * Rad This line will fill our matWorld matrix with the correct data to
rotate the pyramind by stepval degrees (we need to multiply stepval by Rad to convert it
into radians).
To combine matrices you have to multiply them together, luckily dx provides us with a matrix multiply function. If you then add the next 2 lines the pyramid will first
rotate around the Y-axis, then it will rotate around the Z-axis. You should note that the
order you multiply these matrices in does make a difference. If you multiply Y by Z you
will get a different rotation than if you multiply Z by Y.
If we wanted to move the object aswell then we would need to create a transformation
matrix and then multiply our Rotation matrix by our Transformation matrix (make sure you
get the order right, Rotation then Transformation). I'll show you how to move objects in
another tutorial.
Now we use Device.SetTransform D3DTRANSFORMSTATE_WORLD, matWorld to transform the world matrix using our matrix
matWorld.
Finally we render the pyramid with Call Device.DrawPrimitive(D3DPT_TRIANGLEFAN,
D3DFVF_LVERTEX, LVertexList(0), 6, D3DDP_DEFAULT) We are using a triangle fan, and using the D3D_LVertex type.
Remember that there are 6 vertices in our array, we could use the Ubound function to get
the number of vertices (this will be used in the later tutorials).
Conclusions
We have now seen how to use D3Ds LVertex type, and how to
set up a view, projection and world matrix, and how to use the world matrix to rotate
objects. I made this tutorial as short and simple as possible, this was to make sure its
as easy as possible to learn the basics. From now on the tutorials will gradually get
harder, and there will be more and more for you to learn, but with a little bit of
patience you should get through it all.
The pyramid in this tutorial did not have a bottom, if
you change the code so that the pyramid rotates around the X-axis then you would see that
nothing shows when looking at the bottom of the pyramid. This is an easily solved problem,
but it would require us to use a z-buffer which we haven't covered yet, but don't worry we
will cover z-buffers in the next tutorial, along with moving and scaling objects.
Until next time, good luck.
Carl Warwick
- Freeride Designs
|