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

DirectXGraphics: Tips and Tricks
Author: Jack Hoxley
Written: 8th May 2001
Contact: [EMail]


Contents of this lesson
1. Introduction
2. Maths
3. Resources
4. Everything Else
5. Quick Snippets


1. Introduction

wow! I think we've pretty much learnt all of the basics for Direct3D8 now - there's plenty more to play with, but I'm fairly confident in saying that you could, having learnt everything in this series, make a perfectly capable 3D engine for your game. Whilst you're not going to go straight into writing the next Unreal Tournament, the possibilities are almost endless from here - you can, as I said, just use what I've covered; or you can branch out into more complex areas of 3D game design - 75% of what you'll need for a decent 3D game cannot be covered by learning Direct3D, maths and techniques are the key to it all.

Having said that you could go make a 3D engine, could you make a good one? I don't know... but there area many little bits that I keep repeating to people via email, things that once you know you don't forget - and you'll use for everything after that day. The simple statements and facts that you can build your engine around to make it go faster, look smoother and be much more capable. The following 4 sections are all about these things...


2. Maths

As I'm very sure you're aware, mathematics plays a massive role in 3D programming - if your not up to scratch on the basic maths and algebra then your going to find things pretty tough. You will tend to find that computer science degrees require mathematics qualifications for this reason (well they do in the UK!), if you've found many things in this series, and other articles you've read hard then you may want to dig out that old maths book...

Tip 1: Simplify
Whilst trying to scratch every free processor cycle is rather pointless in VB (too high level), there's no reason to be foolish - some of the most processor hungry parts of the Visual Basic language are the maths functions. Therefore it makes sense not to overdo it. Wherever possible use \ / * + - instead of other maths functions, those are the fastest possible. Another key point, if you've derived a very clever maths function to perform a certain job, tidy it up on paper - see if any operations cancel out ( (n \ a) * a = n), see if there are any special cases, see if you can convert it to integer maths...

Tip 2: Datatypes
The Long datatype is fastest, if your only using whole numbers then use this datatype - sure, it takes up twice as much memory as an integer - but unless your storing millions of them it's worth the extra space. Also, if your dividing two integer datatypes (Byte, Integer, Long) use the "\" operator instead of the "/" operator - the former is an integer divide and will be much faster, if you don't want a decimal number then use it. When doing floating point maths use the "single" datatype, as with Long's, they are 32 bits in size, all current processors are optimised for 32 bit processing. Only use double's if you need the extra resolutions. AND PLEASE - NO VARIANTS!! variants are large, slow and horrible - and not good coding practise. Avoid them like the plague.

Tip 3: Don't use complex functions
Tan( ), Atn( ), Sin( ), Cos( ), Log( ), Sqr( ), rnd -  are all pretty useful at times, but they are also the slowest maths functions. Wherever possible restrict the usage of these functions, removing 10 of these from your main frame loop will often see a good speed increase. Also note that things like Log to the base 10 is derived from two log( ) calls, therefore it's going to be twice as slow. Also note that there are several maths functions (sgn and int are good examples) that return variants - which are bad and slow. Work around these functions if possible. Using your own logic tree will be faster than sgn() and \1 will be much faster than int().

Tip 4: Cache values
As already mentioned, maths functions are slow - so if you can pre-calculate them and store them somewhere you'll feel better for it. For example, the hermite spline formula uses v*v and v*v*v many times, if you calculated v*v and v*v*v and stored them in a single datatype (v_Squared and v_Cubed for example), you can then replace all the parts in the formula with v_Squared and v_Cubed - in the case of the hermite spline formula you can get rid of quite a lot of multiplies. Connected to this, use constants, in some cases, the VB compiler will place the number in the raw code - meaning that you dont need any storage space or any calculations whatsoever. Particularly common, and complicated values such as PI should be stored this way (as I have in all my tutorials), calculated 4*atn(1) each time you need PI will not be pretty. Finally, whilst not so popular now, but still an interesting optimisation - lookup tables. If you know your going to need the values of Cos( ) and Sin( ) for 0 - 360 degrees all the time, pre-calculate them and stick them in an array - and then just read out of the array. It's much faster than calculating it each time, and much easier than typing out 720 constants....

Tip 5: Integer Maths is best
Whilst the newer generation of processors (Athlon, Pentium 4) are getting faster and faster as far as floating point (decimal number) calculations, they are still often slower than using integers - particularly 32bit integers (Long's in VB). Where possible use integer maths, but dont keep converting from floating point to integer as that's just as bad... start with integers and convert to floats at the very end, or vice versa...


3. Resources

Data driven games are the norm these days - levels are all loaded from disk, scripts are loading from files, textures, movies, sounds are all loaded from disk, configuration, object information and all the rest is often loaded in at runtime. There is no doubt that this is a good thing, but being sensible about it is another...

Tip 1: Texture Sizes
I'm sure you'll be aware that texture sizes should go in 2^n sizes (32,64,128,256...) and you would be wise to keep them that way, even if the hardware supports non-2^n texture sizes. You may also be aware that you can, on some hardware load textures into the 2048x2048 range. This is not good - rarely will you need a texture that size, 3D cards will run much slower that way, very little hardware supports it. I have a fast 3D card (I like to think so!) - A GeForce 256, whilst it handles textures in most formats, up to 2048x2048, I never use textures larger than 256x256; why? it's the most optimal size to speed ratio, AND the hardware I'm developing games and programs for may not support large textures - take the Voodoo3 card, It's immensely popular and was in many shop-built computers - so assume that quite a few of your end users will have one, yet the largest textures they can handle is 256x256... It is also much faster to store multiple textures in one texture (like a tile set), and square textures are the fastest...

Tip 2: Less Textures = Good
The less textures you use in a scene the better - texture swapping isn't the quickest thing around... Also, keep them to a reasonable size for what your going to be rendering. a small stone that will only ever occupy a 100x100 area on the screen when finally rendered does not warrant a 512x512 32 bit texture - you could keep it to a 128 x 128 x 16 texture and not notice much difference except the go-faster part!

Tip 3: Less data in memory
Whilst not so much relevant to Direct3D itself, the more free memory there is the faster the computer as a whole goes. Requiring a 64mb array will kill all but the most hardy super computers around... Keep things simple and small, I have 288mb of Ram in this computer, and I make the most of it - but I try to keep the end programs memory consumption below 32mb; even on a 64mb RAM machine you're unlikely to get more than 32mb of Free ram to play with - windows loves to chew it all up for you!


4. Everything else

Everything else, what a great title. The following few tips cover some general tips that don't apply to any particular area, but are still important.

Tip 1: Less Lights
Lighting is fast, but don't get cocky - especially if you have a Transform and Lighting enabled 3D card, use the minimum number of lights possible, and keep them as small as possible (so that they don't light too many triangles); try to keep to below 8 lights where possible - if you require more, check the hardware - my GeForce card doesn't like more than 8 lights. When choosing the type of light, use Ambient lighting to increase the overall brightness, Directional lights to a similiar effect - lighting a large area, but not necessarily equally. Point lights for particular sources, and spot lights only if you have to. Specular lighting doubles the processing time for lights, so use it sparingly.

Tip 2: Less rendering
This one is incredibly obvious, but somehow, people often miss it. The rule is simple: Render only what you must, and think hard about how you render what you must render! More complex models require more processing which brings the overall speed of your game/app down. Bare in mind that it's quicker to render 100 triangles in 1 call than rendering 100 triangles in 10 or 100 calls; where possible group your DrawPrimative() calls to a minimum, and for any large amount of geometry stick it in a vertex buffer and/or index buffer. When using vertex and/or index buffers try to create them in video memory - it's much much faster.

Tip 3: Simpler models
Whilst this holds the same truth as rendering less geometry, models have the added problem of often requiring animation - sometimes complex algorithms; reduce the detail of your models and you can see a substantial speed improvement. Also, with regards to storage of model geometry, if you know your going to have 100 big_ugly_green_Alien™ models you could only store 1 copy of the geometry, and then a smaller set of individual frame/state information...

Tip 4: State Changes
Calling SetRenderState( ) or SetTextureStageState( ) functions often requires Direct3D to recalculate internal structures - which can cause slow down; keep calls to these and similar functions to a minimum. Also, changing the World, View and Projection matrices causes internal recalculations - whilst it's often difficult to greatly reduce the number of World matrix changes it is possible to cut down on the view matrix recalculations - make the view matrix an identity matrix (D3DXMatrixIdentity) and apply it to the device - and leave it that way. To change the camera position multiply the world matrix by the view matrix you want (just before committing the world matrix to the device), this will have the same effect, but cause less internal calculations - on TnL cards it often works out much faster... Look into using State blocks if you often set the same block of render states multiple times...

Tip 5: Clearing
Depending on how much your application renders, you can often get away without clearing the render target before the frame is redrawn - it's a good idea to always clear the Z buffer and/or Stencil buffer though... Not clearing the frame buffer (D3DCLEAR_TARGET) can give you a reasonable speed increase.


5. Quick Snippets

There are quite a few things I'd like to have been able to cover, but time has run out - and often, the little things don't really warrant an entire article about them, just a brief explanation and the code... so here we go:

Tip 1: Fogging
fogging is great! It allows you to draw attention to the foreground, and to mask the end of the draw distance by fading into clouds and such... and some clever special effects as well...

'//These lines go in the initialisation section;
D3DDevice.SetRenderState D3DRS_FOGENABLE, 1 'set to 0 to disable
D3DDevice.SetRenderState D3DRS_FOGTABLEMODE, D3DFOG_NONE 'dont use table fog
D3DDevice.SetRenderState D3DRS_FOGVERTEXMODE, D3DFOG_LINEAR 'use standard linear fog
D3DDevice.SetRenderState D3DRS_RANGEFOGENABLE, 0 'enable range based fog, hw dependent
D3DDevice.SetRenderState D3DRS_FOGSTART, FloatToDWord(Start_Distance)
D3DDevice.SetRenderState D3DRS_FOGEND, FloatToDWord(Finish_Distance)

'//And the function the above uses:
Function FloatToDWord(f As Single) As Long
    'this function packs a 32bit floating point number
    'into a 32bit integer number; quite slow - dont overuse.
    'DXCopyMemory or CopyMemory() (win32 api) would
    'probably be faster...
    Dim buf As D3DXBuffer
    Dim l As Long
    Set buf = D3DX.CreateBuffer(4)
    D3DX.BufferSetData buf, 0, 4, 1, f
    D3DX.BufferGetData buf, 0, 4, 1, l
    FloatToDWord = l
End Function

'//To check for Range Based Fog support (it looks better!!)
Public Function CheckForRangeBasedFog(adapter As Byte) As Boolean
On Local Error Resume Next
    Dim DX As New DirectX8
    Dim D3D As Direct3D8
    Dim Caps As D3DCAPS8
    
    Set D3D = DX.Direct3DCreate
    
    D3D.GetDeviceCaps adapter - 1, D3DDEVTYPE_HAL, Caps
    
    If Caps.RasterCaps And D3DPRASTERCAPS_FOGRANGE Then
        CheckForRangeBasedFog = True
    Else
        CheckForRangeBasedFog = False
    End If
End Function

Tip 2: Anti-Aliasing
Anti-Aliasing, when it's done without crippling the frame rate makes the final image something special indeed - gone are those jaggy lines around polygons and textures symbolic of the low resolutions used, instead we have silky smooth edges and textures... Unfortunately it's hardware dependent, and short of the top-of-the-line graphics cards your not going to find much support for it... yet.
'//These lines replace the relevant lines in the D3DPRESENT_PARAMETERS used during 
'//device creation...
D3DWindow.SwapEffect = D3DSWAPEFFECT_DISCARD
D3DWindow.MultiSampleType = D3DMULTISAMPLE_2_SAMPLES

'//Set this render state after device creation, all subsequent rendering will be anti-aliased.
D3DDevice.SetRenderState D3DRS_MULTISAMPLE_ANTIALIAS, 1

'//To Check for FSAA support:
Public Function CheckForFSAA(adapter As Byte, DispModeFormat As CONST_D3DFORMAT) As Boolean
'//0. Any variables
    Dim DX As New DirectX8
    Dim D3D As Direct3D8
    
'//1. Get the data
    Set D3D = DX.Direct3DCreate
    
    If D3D.CheckDeviceMultiSampleType(adapter - 1, D3DDEVTYPE_HAL, DispModeFormat, False, _
                                                           D3DMULTISAMPLE_2_SAMPLES) >= 0 Then
        CheckForFSAA = True
        Exit Function
    Else
        CheckForFSAA = False
        Exit Function
    End If
End Function

Tip 3: Compressed Textures
Compressed textures are a gift when it comes to a heavily texture intensive program - using level 1 compression you can, with minimal quality loss, compress with a 6:1 ratio (6mb becomes 1mb). Even if your not heavily using textures you can, as Unreal Tournament does, use a special set of high detail textures - which will look better, and fit into the same, if not less space... As with all cool things, it's hardware dependent...
'//To check for hardware support:
If D3D.CheckDeviceFormat(0, D3DDEVTYPE_HAL, DispMode.Format, 0, D3DRTYPE_TEXTURE, D3DFMT_DXT1) = D3D_OK Then
       Debug.Print "DXT1 Textures are supported"
End If
If D3D.CheckDeviceFormat(0, D3DDEVTYPE_HAL, DispMode.Format, 0, D3DRTYPE_TEXTURE, D3DFMT_DXT2) = D3D_OK Then
       Debug.Print "DXT2 Textures are supported"
End If
If D3D.CheckDeviceFormat(0, D3DDEVTYPE_HAL, DispMode.Format, 0, D3DRTYPE_TEXTURE, D3DFMT_DXT3) = D3D_OK Then
       Debug.Print "DXT3 Textures are supported"
End If
If D3D.CheckDeviceFormat(0, D3DDEVTYPE_HAL, DispMode.Format, 0, D3DRTYPE_TEXTURE, D3DFMT_DXT4) = D3D_OK Then
       Debug.Print "DXT4 Textures are supported"
End If
If D3D.CheckDeviceFormat(0, D3DDEVTYPE_HAL, DispMode.Format, 0, D3DRTYPE_TEXTURE, D3DFMT_DXT5) = D3D_OK Then
       Debug.Print "DXT5 Textures are supported"
End If

'//To create a texture, use the CreateTextureFromFileEx( ) call, except use one of the following 5 values in the texture format parameter
D3DFMT_DXT1 - Opaque / 1 bit transparent colour
D3DFMT_DXT2 - Explicit Alpha (Alpha Premultiplied)
D3DFMT_DXT3 - Explicit Alpha
D3DFMT_DXT4 - Interpolated Alpha (Alpha Premultiplied)
D3DFMT_DXT5 - Interpolated Alpha
'//For standard textures DXT1 is the best option...

Tip 4: Gamma Correction
Gamma correction is an interesting feature, it allows you to alter the way the video card displays a colour, effectively you can control how much red, green or blue there is in the final image - without altering the existing textures or surfaces. I've written a tutorial here about gamma correction in DirectDraw7 - should you want to learn more. Do not assume gamma correction support in Dx8 if it was there in Dx7.
'//To Check for support, and to apply new settings use the following code:
Dim gRamp As D3DGAMMARAMP, Caps As D3DCAPS8

D3DDevice.GetDeviceCaps Caps
If (Caps.Caps2 And D3DCAPS2_CANCALIBRATEGAMMA) Then
    For I = 0 To 255
        gRamp.red(I) = Interpolate(0, CSng(GammaRedVal), I / 255)
        gRamp.green(I) = Interpolate(0, CSng(GammaGreenVal), I / 255)
        gRamp.blue(I) = Interpolate(0, CSng(GammaBlueVal), I / 255)
    Next I
    'if following line does not work, replace flags with "D3DSGR_CALIBRATE"
    D3DDevice.SetGammaRamp D3DSGR_NO_CALIBRATION, gRamp
End If

Tip 5: Multiple Viewports
Now this is a clever one, everyone will of seen the way that 3D renderers and level editors offer 3-4 different views of the world/level - usually arranged in a 2x2 grid. Want to do that? here's how... Also, using this method of switching render targets, you can get Direct3D to render onto a texture - which can then be used when rendering other geometry, great for mirrors and other strange special effects...

'//Global Objects/Variables required:
     Dim Swap(0 To 1) As Direct3DSwapChain8 'represents our additional windows
     Dim D3DWin(0 To 1) As D3DPRESENT_PARAMETERS 'creation information on the additional windows
     Dim DepthBufferSurf As Direct3DSurface8 'the global Depth buffer
     Dim RenderSurface(0 To 2) As Direct3DSurface8 'the 3 windows...

'//1. Create Device as normal
 '-make sure it's in windowed mode, make it use the first picture box (Picture1.hWnd)

'//2. Create additional swap chains - we're going to create 2 more (total of 3).
 '-In the main initialisation function, after creating the device
 '-Each swap chain represents an additional viewport
     D3D.GetAdapterDisplayMode 0, DispMode
     D3DWin(0).Windowed = 1 '//Tell it we're using Windowed Mode
     D3DWin(0).SwapEffect = D3DSWAPEFFECT_COPY_VSYNC '//We'll refresh when the monitor does
     D3DWin(0).BackBufferFormat = DispMode.Format '//We'll use the format we just retrieved...
     D3DWin(0).EnableAutoDepthStencil = 1
     D3DWin(0).hDeviceWindow = frmMain.Picture2.hWnd

     Set Swap(0) = D3DDevice.CreateAdditionalSwapChain(D3DWin(0))

     D3DWin(1).Windowed = 1  '//Tell it we're using Windowed Mode
     D3DWin(1).SwapEffect = D3DSWAPEFFECT_COPY_VSYNC  '//We'll refresh when the monitor does
     D3DWin(1).BackBufferFormat = DispMode.Format  '//We'll use the format we just retrieved...
     D3DWin(1).EnableAutoDepthStencil = 1
     D3DWin(1).hDeviceWindow = frmMain.Picture3.hWnd

     Set Swap(1) = D3DDevice.CreateAdditionalSwapChain(D3DWin(1))

'//3. Retrieve pointers to all the relevant surfaces.
 '-We recycle the same depth buffer, but should you want separate ones you can create a surface of the correct size
 '-and make it of format D3DFMT_D16 (or any other valid depth buffer format).
     Set RenderSurface(0) = D3DDevice.GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO)
         Set DepthBufferSurf = D3DDevice.GetDepthStencilSurface()
     Set RenderSurface(1) = Swap(0).GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO)
     Set RenderSurface(2) = Swap(1).GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO)

'//4. Restructure our rendering loop.
 '-note that we're rendering 3 times, which is effectively 3 frames, expect frame rate to drop...
    '##START RENDERING OF FIRST WINDOW##
    D3DDevice.SetRenderTarget RenderSurface(0), DepthBufferSurf, 0
        'All subsequent calls for rendering will affect the first window
        D3DDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET Or D3DCLEAR_ZBUFFER, &HFF, 1#, 0
        D3DDevice.BeginScene
            'ALL RENDERING FOR FIRST WINDOW IN HERE!!
        D3DDevice.EndScene
        'display the first window.
        D3DDevice.Present ByVal 0, ByVal 0, 0, ByVal 0
        
    '##START RENDERING OF SECOND WINDOW##
    D3DDevice.SetRenderTarget RenderSurface(1), DepthBufferSurf, 0
        'All subsequent calls for rendering will affect the first window
        D3DDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET Or D3DCLEAR_ZBUFFER, &HFF, 1#, 0
        D3DDevice.BeginScene
            'ALL RENDERING FOR FIRST WINDOW IN HERE!!
        D3DDevice.EndScene
        'display the first window.
        Swap(0).Present ByVal 0, ByVal 0, 0, ByVal 0
        
    '##START RENDERING OF THIRD WINDOW##
    D3DDevice.SetRenderTarget RenderSurface(2), DepthBufferSurf, 0
        'All subsequent calls for rendering will affect the first window
        D3DDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET Or D3DCLEAR_ZBUFFER, &HFF, 1#, 0
        D3DDevice.BeginScene
            'ALL RENDERING FOR FIRST WINDOW IN HERE!!
        D3DDevice.EndScene
        'display the first window.
        Swap(1).Present ByVal 0, ByVal 0, 0, ByVal 0


'//IMPORTANT NOTE:
'ALL SURFACES MUST BE THE SAME SIZE, WHEN USING PICTURE BOXES (AS WE HAVE)
'ALL OF THEM MUST BE THE SAME SIZE, OR IN DESCENDING ORDER (1=>2=>3=>4), IF
'NOT, YOU'LL GET A FATAL ERROR!!
' - ALSO - 
'YOU CAN HAVE ONLY 1 VIEWPORT IN FULLSCREEN MODE AT A TIME.


There! I begun by saying that we'd covered almost everything that you needed to write a 3D engine in Direct3D8 and Visual Basic; now I can [hopefully] say that you can make the best possible 3D engine - fast, efficient and full of features. Despite what I've covered I know that there are 100's, if not 1000's of clever tricks and features that I haven't covered yet - go searching the web if your still hungry for more. Click here to continue to the grand finale of the series!!

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