2D Graphics
Part 1 - The Basics
Author : Jack Hoxley
Written : 7th December 2000
Contact : [Email]
Download : Graph_03.Zip
[10 Kb]
Contents
of this lesson:
1. Introduction
2. Some Theory
3. Getting Started
4. Initialising the Geometry
5. Rendering the Triangles
1.
Introduction
Assuming
that you've been following these lessons in the
correct order (1...2...3 :) you'll have noticed
that we haven't really done anything interesting
yet - at least to look at. By the end of this
tutorial you should be capable of doing some interesting
things in DirectX Graphics.
One
thing that I dislike about DirectX8 is the way
that everyone is forced straight into the world
of 3D. Whenever anyone started working with DirectX
7 they'd be told to start with DirectDraw (the
old 2D component) because it's easier; then to
move onto 3D. Having said that, it is [just about]
possible to learn the basics of DirectXGraphics
without getting too confused with all the 3D terminology.
If you have any intentions of writing a fully-2D
game you'd be wise to consider using either a
hybrid of DirectX7 and 8, or just going straight
directX 7.
Having
said all that though, it's important to know how
all this is done; because no matter how 3D your
game is it'll probably need text, icons, pictures,
health bars and so on... All of which will need
to be done using these techniques.
One
thing to note when you work through the initial
tutorials on 2D graphics is that they are essentially
3D. I'll go through alot of things very quickly
- explaining only what you need to know about
them in order to do 2D rendering; and you can
read the later tutorials for a full lesson on
each individual technique.
2.
Some Theory
Vertices
These can be thought of as defining points. You
can define a triangle using 3 points - 3 vertices.
Direct3D will draw a straight line between the
vertices, and in most cases it will shade the
area defined by the vertices. Each vertex can
have additional properties such as texture coordinates,
colours, specular values (for relections/shiny
objects) - all of which are interpolated (blended)
across the distance between the two points; for
example, if vertex 0 is blue and vertex 1 is red
all the points on the line joining them will fade
from blue to red depending on the distance from
each vertex.
Textures
Textures are applied to polygons (3 or more vertices)
to make them more lifelike; without textures all
you'd be able to do is blend colours. Textures
come in the form of 2D bitmaps, loaded into video
(or system) memory and are then stretched or squashed
across the area of the triangle. Two things to
remember when dealing with texturs: 1. The coordinates
are on a scale of 0.00 to 1.00 and aren't on a
pixel scale (more on this later) and 2: all textures
must be of a size to the power of 2 (^2); for
example: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512,
1024, 2048 - although it's uncommon to use textures
with dimensions greater than 256..
Coordinates
Initially we'll only be using 2D coordinates -
X and Y, but when we move to full 3D you'll have
to use the 3rd Dimension (funny that) - Z. Coordinates
are defined in world space and dont really have
a maximum or minimum value (apart from that inherited
from the variable size). coordinates are in
Meters, but when in 2D they are in pixels.
Transforming
Transformations are what takes place when Direct3D
converts our 3D vertices onto our 2D screen amongst
other things. This will be explained in more detail
later; as there is a lot to learn as far as this
topic goes. In this lesson you will come across
Transformed [and lit] Vertices - Theses are vertices
that you have already transformed into 2D space
- which, if you think about it, is basically a
2D vertex.
Lighting
Lighting is lighting - you should know what lighting
is !! Direct3D will generate fairly realistic
lighting for you, or you can do it yourself. The
only reason I bring this up now is because we'll
be using Transformed (see above) and lit vertices
- the lit part meaning that we have already calculated
it's colour....
You'll
meet many more things like this as you go along;
but right now you should be able to survive with
this much.
3.
Getting Started
I'm
going to jump in straight at where we left off
in lesson 01. You should either be able to remember
the initialisation code, or grab your template
- but I'm not going to go over it again now.
We
need to make a few modifications to the existing
frame work that we [should] have. First there
are some new declarations:
'This is the Flexible-Vertex-Format description for a 2D vertex (Transformed and Lit)
Const FVF = D3DFVF_XYZRHW Or D3DFVF_TEX1 Or D3DFVF_DIFFUSE Or D3DFVF_SPECULAR
'This structure describes a transformed and lit vertex - it's identical to the DirectX7 type "D3DTLVERTEX"
Private Type TLVERTEX
X As Single
Y As Single
Z As Single
rhw As Single
color As Long
specular As Long
tu As Single
tv As Single
End Type
Dim TriStrip(0 To 3) As TLVERTEX '//We need 4 vertices to make a simple square...
Dim TriList(0 To 5) As TLVERTEX '//Note that, like in TriStrip, we're generating 2 triangles - yet using 2 more vertices...
Dim TriFan(0 To 5) As TLVERTEX
|
|
|
|
Okay;
nothing greatly complicated in there. The first
part is probably new to you though - Flexible
Vertex Formats. This was possible in DirectX 7,
but we now have to use them all the time. When
we send data to the rendering device it wants
to know what format it's in - what data is to
be found. The order in which you combine the flags
and define your vertex type ARE important. The
following Diagram demonstrates the order that
they must go in:
POSITION
: X,Y,Z coordinates as 'Single' (Transformed
or Untransformed)
RHW : Reciprocal Homogeneous W as 'Single'
BLEND1 : Blending Weights for vertex manipulation...
all as 'single'
BLEND2
BLEND3
NX : Vertex Normals, all as 'Single'
NY
NZ
POINT SIZE
: For point Sprites only
- defined as 'Single'
DIFFUSE : vertex Diffuse colour as a 32bit
ARGB long value
SPECULAR : Vertex Specular data as a 32bit
ARGB long
TEXCOORD1 : First set of texture coordinates
- - -
TEXCOORD8 : Final possible set of Texture
coordinates
alot
of the stuff in there is far in advance of where
we're currently at. But you'll be taught what
they mean as far as you're concerned when you
need to know. When it comes to flexible vertex
formats you're unlikely to need to play with them
unless you're after something very advanced/special.
All standard vertex formats will be discussed
in these lessons as and when they're needed...
You just need to remember them :)
Carrying
on now, we need to make three changes to the General
"Initialise" function; shown below:
'###################################################################
'## FIRST WE NEED TO CHANGE HOW WE ACTUALLY CREATE THE OBJECT
'###################################################################
'//We're going to use Fullscreen mode because I prefer it to windowed mode :)
'DispMode.Format = D3DFMT_X8R8G8B8
DispMode.Format = D3DFMT_R5G6B5 'If this mode doesn't work try the commented one above...
DispMode.Width = 640
DispMode.Height = 480
D3DWindow.SwapEffect = D3DSWAPEFFECT_FLIP
D3DWindow.BackBufferCount = 1 '//1 backbuffer only
D3DWindow.BackBufferFormat = DispMode.Format 'What we specified earlier
D3DWindow.BackBufferHeight = 480
D3DWindow.BackBufferWidth = 640
D3DWindow.hDeviceWindow = frmMain.hWnd
'########################################################
'## THEN WE'LL NEED TO CONFIGURE THE RENDERING DEVICE
'########################################################
'//Set the vertex shader to use our vertex format
D3DDevice.SetVertexShader FVF
'//Transformed and lit vertices dont need lighting
' so we disable it...
D3DDevice.SetRenderState D3DRS_LIGHTING, False
'############################################
'## FINALLY WE'LL IMPLEMENT A NEW FUNCTION
'############################################
'//We can only continue if Initialise Geometry succeeds;
' If it doesn't we'll fail this call as well...
If InitialiseGeometry() = True Then
Initialise = True '//We succeeded
Exit Function
End If
|
|
|
|
So,
we've now configured our application to kick straight
into 640x480 in 16bit mode, we set a couple of
rendering options and we've implemented a new
function. The rendering options (D3DDevice.SetRenderState)
will be used more and more as we go along - we
can control almost anything we would ever need
to through this one call.
I've
now introduced to you the main new part of our
code - InitialiseGeometry() So I suppose I might
as well explain it...
4.
Initialising the Geometry
This
is basically what the entire lesson is about -
this is the main part you have to learn. Unfortunately
it means that we also need to learn some more
theory - this time it starts getting complicated
(well, sort of).
Although
you'll find 100's of references to the usage of
polygons in 3D scenes we'll only ever use triangles.
Which is still the same thing really (A triangle
is the simplest polgon possible), but I prefer
to think of them as being triangles. The reason
we use triangles is purely down to the Direct3D
rendering engine - and general rendering techniques
used by most engines, triangles are always convex,
never concave, this makes them relatively easy
to render. If you think about it, you can make
any shape you so choose by using triangles. A
square is just 2 triangles, therefore any quadrilateral
can be made of two triangles: The following illustration
explains...
As
you can see, some primatives are easier to work
out than others. (Please Excuse my rather poor
drawings!)
When
we're defining data about triangles we'll need
to know what format they go in, well, there are
several different methods for rendering triangles
(and vertex based primatives):
Triangle
Strip (D3DPT_TRIANGLESTRIP)
|
|
Triangle
Strips are one of the
more common types of arrangements.
Using this method you
can render multiple triangles
using less vertices than
you would if you'd chosen
to define every single
triangle. As you can see
from the above illustration
we're defining 6 triangles
with 8 vertices (we'd
need 18 if we'd defined
each one). The only disadvantage
is that it can introduce
some lighting artifacts;
more on that when we actually
get around to doing lighting....
|
Triangle
List (D3DPT_TRIANGLELIST)
|
|
Triangle
lists are slightly older
than the triangle strip
arrangements; but can
offer much much more flexibility.
When using this method
you'll use many more vertices,
but you can gain the advantage
to rendering triangles
that aren't connected
to each other by vertices
OR lighting. As we'll
see later on, if you try
and do a sharp edge (like
a corner of a corridoor)
using triangle strips
you'll start finding that
the lighting may well
leak around the corner
- and not look very pretty.
You can also use this
method to group triangles
all with the same textures
or properties - then only
setting the texture/options
once yet rendering every
triangle that'll use them... |
Triangle
Fan (D3DPT_TRIANGLEFAN)
|
|
Triangle
Fans tend to be used for
things intended to be
circular, as you can see
from the diagram. Attempting
to render circular or
circle like shapes using
triangle strips or lists
would be incredibly difficult.
Triangle fans are basically
a series of triangles
defined so that the first
point is always entry
0 in the array. In this
example the triangles
are defined by the points
0,1,2 - 0,2,3 - 0,3,4 |
Line
Strip (D3DPT_LINESTRIP)
|
|
A
linestrip is extremely
useful; using a linestrip
you can outline all the
triangles you have sent;
or more importantly you
can replace trianglestrip
calls with linestrip calls
to see a wireframe-like
image - excellent for
debugging. |
Line
List (D3DPT_LINELIST)
|
|
A
linestrip is rarely used
in a proper game, except
with the rare exception
of lightning and sparks
- although they tend to
be done by the use of
billboards. |
Point
List (D3DPT_POINTLIST)
|
|
A
point list can, to a certain
extent, be used to render
particles - but that's
now replaced with the
inclusion of Point sprites
in DirectX. You can, like
LineStrips, use these
for debugging, but it's
not always that easy to
see what's going on. |
|
|
|
|
Okay;
so hopefully now you should know what geometry
is; and be able to make a reasonable choice of
data-arrangement based on what you want to render.
Either way, we're going to move onto the actual
code for setting up our geometry:
'//This basically just fills out the relevent array of vertices
'with the information relevent to what we want to display - A square!
Private Function InitialiseGeometry() As Boolean
'//Geometry is basically vertices; you'll find it being
' used to represent vertices/triangles/meshs as we go along
On Error GoTo BailOut: '//Setup our Error handler
'##################
'## TRIANGLE STRIP
'#################
'We're going to create a square (easily adapted to be a rectangle)
'by defining 4 vertices in a particular order - ClockWise.
'If you define them in the wrong order they either wont appear at all
'or wont appear as you intended....
'0 - - - - - 1
' /
' /
' /
' /
' /
' /
' /
'2 - - - - - 3
'//Ignore the ' parts down the side... Only the Z shape should be there...
'//As you can see this is basically two triangles.
'vertex 0
TriStrip(0) = CreateTLVertex(10, 10, 0, 1, RGB(255, 255, 255), 0, 0, 0)
'vertex 1
TriStrip(1) = CreateTLVertex(210, 10, 0, 1, RGB(255, 0, 0), 0, 0, 0)
'vertex 2
TriStrip(2) = CreateTLVertex(10, 210, 0, 1, RGB(0, 255, 0), 0, 0, 0)
'vertex 3
TriStrip(3) = CreateTLVertex(210, 210, 0, 1, RGB(0, 0, 255), 0, 0, 0)
'#################
'## TRIANGLE LIST
'################
'We're going to make an arrow type shape from two triangles; whilst this is still perfectly possible
'using a triangle strip - it's a good enough example of using triangle lists....
'// TRIANGLE 1
'vertex 0
TriList(0) = CreateTLVertex(210, 210, 0, 1, RGB(0, 0, 0), 0, 0, 0)
'vertex 1
TriList(1) = CreateTLVertex(400, 250, 0, 1, RGB(255, 255, 255), 0, 0, 0)
'vertex 2
TriList(2) = CreateTLVertex(250, 250, 0, 1, RGB(255, 255, 0), 0, 0, 0)
'//TRIANGLE 2
'vertex 3
TriList(3) = CreateTLVertex(210, 210, 0, 1, RGB(0, 255, 255), 0, 0, 0)
'vertex 4
TriList(4) = CreateTLVertex(250, 250, 0, 1, RGB(255, 0, 255), 0, 0, 0)
'vertex 5
TriList(5) = CreateTLVertex(250, 400, 0, 1, RGB(0, 255, 0), 0, 0, 0)
'#################
'## TRIANGLE FAN
'################
'We're now going to set up a triangle fan; these are best used for circular
'type shapes. We'll just generate a hexagonal type shape..
'//MAIN POINT
TriFan(0) = CreateTLVertex(300, 100, 0, 1, RGB(255, 0, 0), 0, 0, 0)
'//Subsequent vertices around the shape
TriFan(1) = CreateTLVertex(350, 50, 0, 1, RGB(0, 255, 0), 0, 0, 0)
TriFan(2) = CreateTLVertex(450, 50, 0, 1, RGB(0, 0, 255), 0, 0, 0)
TriFan(3) = CreateTLVertex(500, 100, 0, 1, RGB(255, 0, 255), 0, 0, 0)
TriFan(4) = CreateTLVertex(450, 150, 0, 1, RGB(255, 255, 0), 0, 0, 0)
TriFan(5) = CreateTLVertex(350, 150, 0, 1, RGB(255, 255, 255), 0, 0, 0)
InitialiseGeometry = True
Exit Function
BailOut:
InitialiseGeometry = False
End Function
'//This is just a simple wrapper function that makes filling the structures much much easier...
Private Function CreateTLVertex(X As Single, Y As Single, Z As Single, rhw As Single, color As Long, _
specular As Long, tu As Single, tv As Single) As TLVERTEX
'//NB: whilst you can pass floating point values for the coordinates (single)
' there is little point - Direct3D will just approximate the coordinate by rounding
' which may well produce unwanted results....
CreateTLVertex.X = X
CreateTLVertex.Y = Y
CreateTLVertex.Z = Z
CreateTLVertex.rhw = rhw
CreateTLVertex.color = color
CreateTLVertex.specular = specular
CreateTLVertex.tu = tu
CreateTLVertex.tv = tv
End Function
|
|
|
|
One
of your first observations (I hope) is that there
are quite a lot of lines in there to do something
fairly simple. Imagine trying to write code to
generate a complex shape... This is one of the
main reasons why games use pre-generated models
and load them into the program at runtime; more
on object loading in a later tutorial.
All
the above code does is fill out a relevent data
structure for each entry in the array - I'll now
go over what each part of the structure means:
X
: This is the screens X coordinate
Y : This is the screens Y coordinate
Z : This is a value in a 0.0 to 1.0 scale;
where 0 is the front and 1 is the back. When we
have a depth buffer attached we can make certain
object appear underneath/behind others. If they
all have the same value (as the ones above do)
it's based on the rendering order - whichever
is rendered last will be visible...
RHW : This can be ignored almost always;
you'll very rarely want to use this (In almost
2 years I've never used it). Leave it set to 1,
otherwise shading wont work properly
COLOR : This is a long value for the vertex
- you can often get away with using the RGB(R,G,B)
function to specify this, but where possible use
hexidecimal values.
SPECULAR : This is the reflectivity of
the vertex; only used when we enable specular
highlighting, and isn't really used when you have
transformed & Lit vertices.
TU : This is the textures X-Coordinate;
More on this in the next lesson
TV : This is the textures Y-Coordinate;
again, more on this in the next lesson.
Now
we've done the geometry initialisation we can
go about altering the rendering code.
5.
Rendering the triangles
The
final thing this lesson will cover is rendering
the triangles. In lesson 01 you should have seen
the render procedure, but at that time it didn't
actually do anything. Now we'll make it do something:
Public Sub Render()
'//1. We need to clear the render device before we can draw anything
' This must always happen before you start rendering stuff...
D3DDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET, 0, 1#, 0 '//Clear the screen black
'//2. Rendering the graphics...
D3DDevice.BeginScene
'All rendering calls go between these two lines
'Comment out some of the lines below should the screen seem
'to clustered
'First the Triangle Strip
D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLESTRIP, 2, TriStrip(0), Len(TriStrip(0))
'Next the triangle list
D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLELIST, 2, TriList(0), Len(TriList(0))
'Finally, the triangle fan
D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLEFAN, 4, TriFan(0), Len(TriFan(0))
D3DDevice.EndScene
'//3. Update the frame to the screen...
' This is the same as the Primary.Flip method as used in DirectX 7
' These values below should work for almost all cases...
D3DDevice.Present ByVal 0, ByVal 0, 0, ByVal 0
End Sub
|
|
|
|
You'll
notice that the only real difference is the inclusion
of three new lines between the "D3DDevice.BeginScene"
and "D3DDevice.EndScene". These are
standard calls for rendering triangles - at least
as far as you need to know at this point; later
on we'll come across vertex buffers.
object.DrawPrimitiveUP( _
PrimitiveType As CONST_D3DPRIMITIVETYPE, _
PrimitiveCount As Long, _
VertexStreamZeroDataArray As Any, _
VertexStreamZeroStride As Long)
|
|
|
|
Above is the parameter list for the call we've
just used to render our triangles. The "UP"
suffix on the end of the function name is short
for "User Memory Pointer"; which basically
means that you can, theoretically, send almost
anything to it. The reason for it being a User
Memory Pointer is that we're using a custom FVF
and a custom vertex type.
Object
- A Direct3DDevice8 object
PrimativeType - Just so that DirectX knows
what we're sending it's way
PrimativeCount - The number of triangles
there are; you'll notice that everything but the
triangle fan is made up of 2 triangles
VertexStreamZeroDataArray - Long name,
but simple really. This is where the first element
to render is - you could make it start half way
through your array should you want to, just pass
the variable name with the index of the first
entry.
VertexStreamZeroStride - How far (in bytes)
between the beginning of each datatype; because
Direct3D doesn't actually know what the structure
of the datatype is, it has to use the size and
the FVF to work out where the information it needs
is stored. Always pass the length of one element
to this one - unless something weird is happening
all the elements should be the same size; so it
doesn't matter which entries size you pass..
Okay;
so you should now be able to render some simple
geometry; at the moment it's only 2D; but you
should start to see how this will work out when
we move into 3 dimensions. You should also be
able to see how we'll be able to use this for
rendering 2D graphics - basic texturing will be
covered in the next tutorial...
As
a simple excercise try writing the code for the
star shape in the very first diagram in section
4 (Initialising the Geometry).
You
can download the sample code for this tutorial
from the top of the page; seeing the code running
is the best way of learning from it.
If
all this information's settled, onto the next
tutorial : Lesson 04
: 2D Graphics - Advanced
|