How to make normal mapped billboards

A place for users of OGRE to discuss ideas and experiences of utilitising OGRE in their games / demos / applications.
Post Reply
User avatar
areay
Bugbear
Posts: 819
Joined: Wed May 05, 2010 4:59 am
Location: Auckland, NZ
x 69

How to make normal mapped billboards

Post by areay »

Hi guys,

I was trying to do some normal mapping on billboards the other day but ran into some problems. Other people have commented on this restriction too, see http://www.ogre3d.org/forums/viewtopic.php?f=3&t=75789 & http://www.ogre3d.org/forums/viewtopic.php?f=2&t=69823 & http://ogre3d.org/forums/viewtopic.php?f=5&t=63350

So I came up with a solution that works for me and thought I'd post it for the team. This solution is a little different from some of the solutions offered above and does handle billboard rotation. One of the big problems with billboards when doing lighting on them is that the light direction is not correct if you rotate the billboard. This is because when you rotate a billboard you're not actually updating the rotation component of world matrix... Think of billboards as being a few vertexes in a single manual object instead of being individual entities. Billboards receive the world matrix orientation from the scenenode that the BBS is attached to.

In my use case I wanted to create impact craters on the ground after an explosion occurs, pretty standard fare. I used a single Ogre::BillboardSet (BBS) for all of the billboards and each billboard can have its own rotation around the Y axis.

The method:
1. Create a BBS that always has billboards facing up in the +Y direction (flat on the ground facing up).
2. Each time a billboard is created give it a random rotation and set the billboard colour's red-channel to represent this rotation.
3. In the billboard's vertex shader rotate the light direction vector around the Y axis by the rotation denoted by the vertex colour red-channel.
4. As the vertexes don't have tangents, use a hard-coded matrix to rotate this adjusted light direction vector, this becomes the tangent-space light direction.

Here's the BBS setup.

Code: Select all

	//death splatter billboardset is used to display the residue of creeps when they die
	blastBBS = m_pSceneMgr->createBillboardSet("Blast", 100);
	blastBBS->setMaterialName("DeathSplatter/Blast");
	blastBBS->setBillboardType(Ogre::BBT_PERPENDICULAR_COMMON);
	blastBBS->setCommonUpVector(Ogre::Vector3(0,0,1));
	blastBBS->setCommonDirection(Ogre::Vector3(0,1,0));
	blastBBS->setCastShadows(false);
	blastBBS->setDefaultDimensions(3,3);
	blastBBS->setBillboardRotationType(Ogre::BBR_VERTEX);
	m_pSceneMgr->getRootSceneNode()->attachObject(blastBBS);
And when an explosion occurs

Code: Select all

	//place death billboard on ground
	Ogre::Billboard *blast = blastBBS->createBillboard(location);
	Ogre::Real rnd = Ogre::Math::UnitRandom(); // a random in range 0.0f -> 1.0f
	blast->setRotation(Ogre::Radian(rnd * 2 * 3.14f)); //set the rotation as a radian value
	blast->mColour.r = rnd; //set the vertex red-channel to represent this rotation

Code: Select all

void vp_blast(
	float4 position				: POSITION,
	float2 uv					: TEXCOORD0,
	float4 colour				: COLOR0,
	float3 normal				: NORMAL,
	
	out float4 oPosition		: POSITION,
	out float2 oUV				: TEXCOORD0,
	out float3 oTSLightDirection : TEXCOORD1,
        out float3 oTSHalfAngle : TEXCOORD2,

	uniform float4x4 worldViewProjMatrix,
	uniform float3 eyePosition,
	uniform float4 lightDirection)
{
	oPosition = mul(worldViewProjMatrix, position);      	
	oUV.xy = uv;

        float3 eyeDirection = normalize(eyePosition - position.xyz);

        //Extract the true radian value for the billboard
	float billboardRotation = colour.r * 2 * 3.14;
	float3x3 modelRotation;
	
	//RHS Y rotation -this works perfect for just getting the true lightdir
	modelRotation[0] = float3(cos(billboardRotation), 0, -sin(billboardRotation));
	modelRotation[1] = float3(0,1,0);
	modelRotation[2] = float3(sin(billboardRotation), 0, cos(billboardRotation));
	
	float3 adjustedLightDir = mul(modelRotation, lightDirection.xyz);
	float3 adjustedViewDir = mul(modelRotation, eyeDirection.xyz);

	float3x3 TSRotation;
	TSRotation[0] = float3(1,0,0);
	TSRotation[1] = float3(0,0,1);
	TSRotation[2] = float3(0,1,0);
	
	oTSLightDirection = mul(TSRotation, adjustedLightDir);
	oTSHalfAngle = mul(TSRotation, adjustedViewDir);
}
Now you can use a standard fragment program that does normal mapping.

Here's a screenshot showing just the NdotH value of the billboard using a simple concrete-like bumpy normalmap.
billboard-NdotH.jpg
billboard-NdotH.jpg (51.81 KiB) Viewed 2131 times
And here's what I'm using in my game. I use another texture as a alpha mask and then only output the NdotL vector, turned out quite well.
billboard-NdotL.jpg
billboard-NdotL.jpg (113.6 KiB) Viewed 2131 times
Some caveats
* If your billboards are oriented in another direction then you'll need to change the rotation matrices to reflect this.
* More complex orientations can be achieved by passing in rotations around different axes using the remaining vertex colour channels.

OK, hope this helps someone :)
Post Reply