Hey guys, someone asked for this a long time ago (
http://forum.thegamecreators.com/?m=forum_view&t=25481&b=1), and since i havent been able to find any answers to this question, i wrote a tutorial.
So what is an axially aligned billboard you ask? It is a regular billboard (essentially a textured plane which always faces the camera) but instead of rotating around a point, it
rotates about its own arbitary axis to face the camera. This is useful for depicting the smoke trail of a gunshot, and lasers. (eg the laser shots we see released from the barrel of a storm trooper's blaster in SWBF2 are axially-aligned planes)
Here are a few useful links:
http://nehe.gamedev.net/data/articles/article.asp?article=19
http://www.flipcode.com/archives/Billboarding-Excerpt_From_iReal-Time_Renderingi_2E.shtml
They help with getting your mind around the general idea, but i struggled with their mathematical explanations, and couldnt figure out how they managed to make the billboard 'face the camera' using matrices.
After some deep pondering, I had a eureka moment at about 1 am and figured out that 'to face the camera' mathematically meant that the billboard's normal has to fit in the
plane formed by the
view and
billboard tangent vectors (henceforth the Viewplane). I might post up a diagram later if people are interested in this and want me to, but this shouldnt be too hard to visualise. (Ultimately, this is only
my opinion on what 'facing the camera' means; but I like it, so ill stick with it.

)
How to actually go about doing this was a bit complex. At first I found the cross product of the viewplane and the plane normal and tried to get the angle between the billboard's normal and this vector in order to match the normal to it; but as anyone who has dabbled in angles will know, angles are not friendly. (cue - someone mentions EZrotate) Due to some annoying camera-under-billboard problems, i tried changing the camera coordinates to coordinates relating to the billboard using matrices (vector change of basis), but to cut a long story short, i decided that the fastest and easiest method to understand/implement would be to rotate the billboard to fit the viewplane by trial and error.
This screenie doesnt really do the program justice, as you dont see the billboards rotating to face the camera.
The following is in the code below, but ill mention it here for anyone who wants to know at a glance how hard this program works.
As a summary, what is done each game loop that is mathematically significant/intensive FOR EACH BILLBOARD is:
- A normalization (view vector)
- A cross product (V cross Tangent)
- An initial dot product
- If the camera is moving (very likely), which would mean the billboards have to realign themselves:
- A dot product for each iteration (approx 5-10 iterations)
(- a roll object command)
And here is the real tutorial (see the handleShotBoards() function)
`----- Aralox's Axially Aligned Billboard tutorial ------
`Created: 30th September 2010
`These billboards are usually used to represent the particles of gunshots, so I call them 'Shotboards'
autocam off : sync on : hide mouse
move camera up 3
move camera -3
pitch camera down 25
`Used for our vectors
#constant N 1
#constant T 2
#constant V 4
#constant P 5
null = make vector3(N) `normal
null = make vector3(T) `tangent
null = make vector3(V) `View (vector from camera to plane)
null = make vector3(P) `View plane's normal (**View plane is the plane formed from the view vector and the object's tangent vector** = important)
global iterations `So we can see how hard our system is working (see later)
global ShotTimer
global ShotCooldown : ShotCooldown = 100 `change this if you want to shoot boards faster/slower
`Array to hold individual shotboards. In your game, you would probably have more data than this, eg animation stages, etc
type ShotBoardType
obj as integer
endtype
dim ShotBoard() as ShotBoardType
`**Build the shotboard object**
make object plane 1, 0.5,10
xrotate object 1, -90
fix object pivot 1 `So the 'point object' command will make it point using the top of the board, not the surface
set object light 1, 0
`**Construct the Normal limb**
make object cube 2, 0.1
make mesh from object 1, 2
add limb 1,1,1 `**offset by 1, so normal will always be unit vector. Its Z offset because fix object pivot apparently hasnt fixed the pivot for the limb offsets. (or some other reason - im not entirely sure)
offset limb 1,1,0,0,1 `But essentially what we are trying to do is put the limb above the surface of the plane, so it gives us the 'normal' vector.
`**Construct the Tangent limb**
add limb 1,2,1
offset limb 1,2,0,1,0 `Tangent limb defines the arbitary axis (important)
delete object 2
`Make a pretty texture
ink rgb(255,100,100), 0
box 0,0,100,100
ink rgb(255,0,0),0
box 20,0,80,100
get image 1,0,0,100,100
ink rgb(255,255,255),0
texture object 1,1
Do
`Spawn a shotboard on mouseclick
if mouseclick() = 1 then if (timer() - shotTimer) > ShotCooldown
spawnShotBoard()
shotTimer = timer()
endif
text screen width() - 100, 10, "FPS: "+str$(screen fps())
text 10,10,"Iterations last loop: "+str$(iterations)
text 10,20,"Shotboards in existance: "+str$(array count(ShotBoard())+1)
handleShotBoards()
Freecam()
sync
Loop
function spawnShotBoard()
array insert at bottom ShotBoard()
a = array count(ShotBoard())
ShotBoard(a).obj = find free object()
clone object ShotBoard(a).obj, 1
position object ShotBoard(a).obj, camera position x(),camera position y()-1,camera position z()
set object to camera orientation ShotBoard(a).obj `point object can also be used
endfunction
function handleShotBoards()
iterations = 0
amt = array count(ShotBoard())
for i = 0 to amt
`**Get the Normal vector, which is the vector from the object position to the normal limb. this is a unit vector**
set vector3 N, limb position x(ShotBoard(i).obj,1) - object position x(ShotBoard(i).obj), limb position y(ShotBoard(i).obj,1) - object position y(ShotBoard(i).obj), limb position z(ShotBoard(i).obj,1) - object position z(ShotBoard(i).obj)
`**Get the tangent vector of the plane (pointing in the direction of the plane's arbitary axis)
set vector3 T, limb position x(ShotBoard(i).obj,2) - object position x(ShotBoard(i).obj), limb position y(ShotBoard(i).obj,2) - object position y(ShotBoard(i).obj), limb position z(ShotBoard(i).obj,2) - object position z(ShotBoard(i).obj)
`**Get the View vector, from the camera to the object**
set vector3 V, object position x(ShotBoard(i).obj) - camera position x(), object position y(ShotBoard(i).obj) - camera position y(), object position z(ShotBoard(i).obj) - camera position z()
`**Dont forget to normalize the View vector! not doing so will make it so if the camera is far away from the shotboard, the dot product is harder to get to 0,
` resulting in the system having to go though more loops trying to make it 0, which can result in a hang.
normalize vector3 V, V
`**Get the normal of the plane formed by the object tangent and camera view vectors. The normal of this View Plane is the cross product of those vectors (unit vector)**
`**The whole point of axially aligned billboards facing the camera is getting them to fit into this plane. (in my opinion)**
`**Note: its V crossed with T, but order (in the grand scheme of things) shouldnt really matter, as you will soon see**
cross product vector3 P, V, T
`**The dot product of the billboard's normal and the ViewPlane's normal. We want this to be 0, so the billboard's normal lies in the plane**
`**Since we are dealing with unit vectors, this value will be between 0 and 1
dot# = dot product vector3(N, P)
`**'Rolling' the object means rotating it about it's arbitary axis (important). We will roll the plane until it fits the viewplane**
`** value here is how finely you want your billboard to follow the camera. If you scale this down, the game will have lag bursts unless you correspondingly scale down the roll factor (see down) **
if abs(dot#) > 0.1 then repeat
roll object left ShotBoard(i).obj, 1 `**value here is the roll factor. Smaller this is, finer the roll-aligning**
`**Recalculate the updated normal**
set vector3 N, limb position x(ShotBoard(i).obj,1) - object position x(ShotBoard(i).obj), limb position y(ShotBoard(i).obj,1) - object position y(ShotBoard(i).obj), limb position z(ShotBoard(i).obj,1) - object position z(ShotBoard(i).obj)
`**Recheck the dot product**
dot# = dot product vector3(N, P)
`update our counter, so we can see how hard the system is working
`**It would be good programming practice here to count the number of iterations a shotboard takes to roll to its destination, and if it goes for too long,
` delete the shotboard. As reliable as any bit of code can be, its always better to play on the safe side, especially when dealing with repeat/until or while loops.
inc iterations
`**The board is considered in the plane when the dot product is low enough >> Another factor of detail here **
until abs(dot#) < 0.1
`**Note: The object flips between facing you and facing away regularly, as it will try its best to fit the viewplane, either negatively or positively.
` I turned off lighting for the billboards earlier, which is why we wouldnt actually see this if the normal limbs werent visible. This effect shouldnt
` really be a hindrance, as the texture applies to both sides of the billboard
next i
`**As a summary, what is done each game loop that is mathematically significant/intensive FOR EACH BILLBOARD is:
` - A normalization (view vector)
` - A cross product (V cross Tangent)
` - An initial dot product
` - If the camera is moving (very likely), which would mean the billboards have to realign themselves:
` - A dot product for each iteration (approx 5-10 iterations)
` (- a roll object command)
endfunction
`Utility freecam function
function Freecam()
yrotate camera camera angle y() + mouseMoveX()*.2
xrotate camera camera angle x() + mouseMoveY()*.2
dist# = 0.012
if keystate(17) = 1 then move camera dist#`W forward
if keystate(31) = 1 then move camera -dist# `S back
if keystate(30) = 1 then move camera left dist#`A left
if keystate(32) = 1 then move camera right dist# `D right
if keystate(45) = 1 then move camera up dist# `X up
if keystate(46) = 1 then move camera down dist# `C down
endfunction
I can handle about 70 billboards with very little fps loss (from 450 running nothing at all to about 380). This is very good, considering that in a proper game, only about 20-30 billboards would be in existance at any one time.
Note on optimisation: It would be quite easy and beneficial to check if a billboard is visible before running calculations on it.
Thank you for reading this, and I hope it helps someone. It certainly helped me in the creation of it.

Aralox
http://www.freewebs.com/aralox/