HomeAbout inludoServicesGamesClientsDevelopmentsForumContact usHomeMail inludo


Decals
:


Download the source here.

Intro:
Decals are flat planes that are applied to models, usually to denote things like bullet holes, or footprints etc.
Theoretically they can be done simply enough by slapping a small plane with the texture you want on the same location as the surface it's to sit on, with the same normal. You can mimic this easily enough by using modelsUnderLoc() or modelsUnderRay() to get the intersection position with a model's surface (using the #detailed version of these routines gives you the surface normal and the exact collision location you need for this).
Unfortunately this, while technically correct, results in visual errors. Why? Because drawing the decal over the surface causes Z buffer problems. You may not see the decal, you may see it, or you may get bizarre stripes of it. See the demo below with the decal offsetting disabled to visualise what I mean here.
There is a way around this, however! The simplest way is to position the decal, not on the surface, but slightly away from it. One simple solution to this is to move it forward in the direction of the surface normal. Some success results from this, but again it causes problems.
The main one is that the Z buffer is not linear - it doesn't divide it's bits up into a linear sequence starting from the camera.hither property to the camera.yon property. It's actually logarithmic. The Z buffer stores more data about close objects than far away. So while up close moving your decal (say) 1 unit 'up' from the surface it rests on may be fine, moving away means the nasty Z buffer graphical glitch returns. You can remove this completely by setting the decal so far away from the surface it is on that when in the distance it renders fine, but up close you will really really notice that the decal is standing out from the wall. Trust me on this one, it don't work (cos I tried to implement it, and it sucks!).

Method:
OK, so now what?
Well, as usual, there is a solution (well, probably lots, but this is one of them). Sadly it is computationally fairly expensive. Every frame you re-position each decal - moving it forward an amount based on the distance of the decal from the camera. Using this method gains one benefit - rather than just moving the decal 'upwards' from the surface it lies on, we can move it directly towards the camera. This means it never looks like it's floating above the surface below.
So, the nitty gritty. Each decal stores a bit of data about itself. This data is where in an ideal world it should be positioned (flat on the underlying surface). I'll refer to this as the decal's 'ideal position'.
Then, every frame, the vector from the camera to this location is calculated. Let's call this the 'viewing vector'. The decal's new position is calculated as it's ideal position, minus a proportion of the viewing vector's magnitude (IE the distance from the camera to the decal's ideal position). This is basically moving the decal from where it should be, to a point slightly nearer the camera in a direct line.
And what is this proportion?
Well, I'm afraid it's a trial and error situation I'm afraid :(
You need to play around with this so that decals look fine up close and far away - simple as that. It will differ from application to application, although it should remain constant within a given project. This value is based on the full Z buffer range - the camera.hither -> camera.yon distance. Change this and you'll need to change the proportion scalar.
Also, note that using this, test your code in 16 BIT MODE! The reason is that this uses only a 16 bit Z buffer, which is less accurate than a 32 bit Z buffer. Decals that work on a 16 bit Z buffer will work identically on a 32 bit Z buffer, but the reverse is not true. You have been warned....

In Practice:
I won't detail how I pick the actual point for the decal to be, that's not the point of this tutorial. However, it may be of some interest, so here's the brief breakdown:

  • Use modelsUnderLoc(#detailed) to determine the hit point of the mouse click and the chosen model
  • Since this result is in world coordinates, I convert it to model coordinates by inverting the model's worldTransform, and passing the results from modelsUnderLoc() through it. This tells me where on the model the click happened and also it's surface normal.
Back to how the decals are actually processed:
  • Get the camera location (you don't need to know it's direction or anything).
    Because of how decals work, it's best to manipulate the camera location in model coordinates, not the world coordinates. To this end I get the model's worldTransform, and invert it, with the following line:

    tmWorldInvert = inverse(pObj.getWorldTransform())

    This gives you a transform that will take a vector from world coordinates to to model coordinates (for the model pObj, set up in the new() handler of the decal script object.
    Then I get the camera's location and using the above transform convert it into model coordinates. For this you use the following line of code:

    tvEye = tmWorldInvert * pWorld.camera(1).transform.position
  • Get the viewing vector.
    This is done with the following line:

    tvPos = tvDecalIdealLocation
    tvView = tvPos - tvEye
  • Now we need to scale this vector by a proportion. In the code below I simply chose to divide it by 10.0 - it worked first time, so I'm happy! If you divide the vector by too large a value here the decals may not be offset enough and you'll get the Z buffer problem. Divide the vector by too small a value here and the decals may be offset by so much they appear through nearer objects. Trial and error I'm afraid :(
    Anyway, the code used here is:

    tvOffset = tvView / 10.0
  • Now to actually offset the decal. We position it offset from it's ideal location with the following code:

    tDecalModel.transform.position = tvPos - tvOffset

    Repeat with process for all decals, and away you go!


Development
Screenshots
Demos
Tutorials