Game Development Community

FoliageReplicator Upgrades For TSE

by J.C. Smith · in Torque Game Engine Advanced · 01/18/2007 (10:27 pm) · 64 replies

Hello all. I was looking at this resource the other day from Jeff Loveless:

http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=11963

which implemented clustering and multiple foliage textures for TGE. So I decided to port that resource to TSE. The clustering works in the same way though needed some code changes for the TSE version. Along the way though I decided to do the multi foliage types using a single texture. It works by using a flag to denote if your foliage is normal or a QuadTexture (the UseQuadTexture flag in Mission Editor). So you'd just make a PNG or DDS file and split it into 4 imaginary quadrants, and paste a foliage texture in each of the 4 quadrants. If you leave the quadtexture flag unchecked then it works as normal.

As another bonus I added in some simple color variations. This will allow you to take you texture and blend some different colors with it to give some variation so all your blades of grass aren't the same color for example. That works by setting three four fields: GradientTexture, UseBlend, BlendStart and BlendEnd. If you check the UseBlend flag then it will activate this effect. BlendStart and BlendEnd are a float value from 0.0 to 1.0, which reflects which parts of the gradient texture range you want to use. The GradientTexture is a texture file that you will need to create in photoshop or whatever that stores the color range vertically. It only uses the Y pixels, and is always using the first pixel X wise. So for example if you made a 64x64 texture, and the first line started with a green color, and moving down the Y axis by the time you reached the bottom line it was yellow, and you set the BlendStart to 0 and the BlendEnd to 1 then your foliage would be colored a random shade of green to yellow. If you set the blendEnd to 0.5 then it would start at green and end at a greenishyellow variant. If your using a grayscale image, this allows you to make some nice variations on your foliage colors.

I'm pasting the source code into here in it's entirety for the four files which require changes:

shaders/FxFoliageReplicatorP.hlsl

#include "shdrConsts.h"

//-----------------------------------------------------------------------------
// Structures                                                                  
//-----------------------------------------------------------------------------
struct ConnectData
{
   float2 texCoord        : TEXCOORD0;
   float4 lum		  : COLOR0;
   float4 groundAlphaCoeff : COLOR1;
   float2 alphaLookup	  : TEXCOORD1;
   float2 blendColor      : TEXCOORD2;
};

struct Fragout
{
   float4 col : COLOR0;
};

//-----------------------------------------------------------------------------
// Main                                                                        
//-----------------------------------------------------------------------------
Fragout main( ConnectData IN,
              uniform sampler2D diffuseMap      : register(S0),
              uniform sampler2D alphaMap		: register(S1),
              uniform sampler2D blendMap        : register(S2),
              uniform float4    groundAlpha     : register(C1)       
)
{
   Fragout OUT;

   float4 alpha = tex2D(alphaMap, IN.alphaLookup);
   OUT.col = IN.lum * (tex2D(diffuseMap, IN.texCoord) * tex2D(blendMap, IN.blendColor));
   OUT.col.a = OUT.col.a * min(alpha, groundAlpha + IN.groundAlphaCoeff.x);
   
   return OUT;
}
Page «Previous 1 2 3 4 Last »
#1
01/18/2007 (10:27 pm)
Shaders/fxFoliageReplicatorV.hlsl

//-----------------------------------------------------------------------------
// Structures                                                                  
//-----------------------------------------------------------------------------
struct VertData
{
   float2 texCoord   : TEXCOORD0;
   float2 waveScale	: TEXCOORD1;
   float3 normal     : NORMAL;
   float4 position   : POSITION;
   float2 blendColor : TEXCOORD2;
};

struct ConnectData
{
   float4 hpos            : POSITION;
   float2 outTexCoord     : TEXCOORD0;
   float4 color			  : COLOR0;
   float4 groundAlphaCoeff : COLOR1;
	float2 alphaLookup	  : TEXCOORD1;   
	float2 outBlendColor	  : TEXCOORD2;   
};

//-----------------------------------------------------------------------------
// Main                                                                        
//-----------------------------------------------------------------------------
ConnectData main( VertData IN,
                  uniform float4x4 projection     		: register(C0),
                  uniform float4x4 world	   					: register(C4),
                  uniform float    GlobalSwayPhase 		: register(C8),
                  uniform float 	 SwayMagnitudeSide 	: register(C9),
                  uniform float 	 SwayMagnitudeFront	: register(C10),
                  uniform float    GlobalLightPhase		: register(C11),
                  uniform float    LuminanceMagnitude : register(C12),
                  uniform float		 LuminanceMidpoint	: register(C13),
                  uniform float	DistanceRange : register(C14),
                  uniform float3 CameraPos : register(C15)
)
{
  ConnectData OUT;

	// Init a transform matrix to be used in the following steps
	float4x4 trans = 0;
	trans[0][0] = 1;
	trans[1][1] = 1;
	trans[2][2] = 1;
	trans[3][3] = 1;
	trans[0][3] = IN.position.x;
	trans[1][3] = IN.position.y;
	trans[2][3] = IN.position.z;
	
	// Billboard transform * world matrix
	float4x4 o = world;
	o = mul(o, trans);
		
	// Keep only the up axis result and position transform.
	// This gives us "cheating" cylindrical billboarding.
	o[0][0] = 1;
	o[1][0] = 0;
	o[2][0] = 0;
	o[3][0] = 0;
	o[0][1] = 0;
	o[1][1] = 1;
	o[2][1] = 0;
	o[3][1] = 0;
	
	// Handle sway. Sway is stored in a texture coord. The x coordinate is the sway phase multiplier, 
	// the y coordinate determines if this vertex actually sways or not.
	float xSway, ySway;
	float wavePhase = GlobalSwayPhase * IN.waveScale.x;
	sincos(wavePhase, ySway, xSway);
	xSway = xSway * IN.waveScale.y * SwayMagnitudeSide;
	ySway = ySway * IN.waveScale.y * SwayMagnitudeFront;
	float4 p;	
	p = mul(o, float4(IN.normal.x + xSway, ySway, IN.normal.z, 1));
		
	// Project the point	
	OUT.hpos = mul(projection, p);
	
	// Lighting 
	float Luminance = LuminanceMidpoint + LuminanceMagnitude * cos(GlobalLightPhase + IN.normal.y);

	// Alpha
	float3 worldPos = float3(IN.position.x, IN.position.y, IN.position.z);
	float alpha = abs(distance(worldPos, CameraPos)) / DistanceRange;			
	alpha = clamp(alpha, 0.0f, 1.0f); //pass it through	

	OUT.alphaLookup = float2(alpha, 0.0f);
	OUT.groundAlphaCoeff = all(IN.normal.z);
	OUT.outTexCoord = IN.texCoord;	
	OUT.color = float4(Luminance, Luminance, Luminance, 1.0f);
      OUT.outBlendColor = IN.blendColor;

	return OUT;
}
#2
01/18/2007 (10:35 pm)
Game/fx/fxfoliagereplicator.h

Change the FxFoliageItem area to look like this:

class fxFoliageItem
{
public:
   MatrixF     Transform;		
   F32         Width;			
   F32         Height;			
   Box3F			FoliageBox;		
   bool			Flipped;			
   F32         SwayPhase;     
   F32         SwayTimeRatio; 
   F32         LightPhase;		
   F32         LightTimeRatio; 
   U32         LastFrameSerialID; 
   F32			quadTextureX;
   F32			quadTextureY;
   F32		    blendColor;
};

Change your vertexbuffer definition to look like:

// Define a vertex 
DEFINE_VERT( GFXVertexFoliage,
             GFXVertexFlagXYZ | GFXVertexFlagNormal | GFXVertexFlagTextureCount2 | 
             GFXVertexFlagUV0 | GFXVertexFlagUV1 | GFXVertexFlagTextureCount1 | 
             GFXVertexFlagUV0 | GFXVertexFlagUV1)
{
   Point3F point;
   Point3F normal;
   Point2F texCoord;
   Point2F texCoord2;
   Point2F texCoord3;
};

Add these fields to the tagfieldata:

bool				mUseClustering;			// flag for using clusters
	  U32				mCountPerCluster;		// count per cluster of foliage
	  U32				mClusCountVariation;	// variation (plus or minus) in the count per cluster
	  U32				mClusterRadius;			// radius of a typical cluster
	  U32				mClusRadVariation;		// variation in the radius of a cluster
      U32               mInnerRadiusX;
      U32               mInnerRadiusY;
      U32               mOuterRadiusX;
      U32               mOuterRadiusY;
      StringTableEntry  mGradientFile;
      GFXTexHandle	    mGradientTexture;
      bool			  mQuadFoliage;
      F32			  mBlendStart;
      F32			  mBlendEnd;
     bool			  mUseBlend;

and then into the tagfieldata area below it:

mGradientFile         = StringTable->insert("");
        mGradientTexture      = GFXTexHandle();
		 mUseClustering		   = false;
	  	 mCountPerCluster	   = 10;
	  	 mClusCountVariation   = 0;
	  	 mClusterRadius		   = 8;
	  	 mClusRadVariation	   = 2;
         mInnerRadiusX         = 0;
         mInnerRadiusY         = 0;
         mOuterRadiusX         = 128;
         mOuterRadiusY         = 128;
        mQuadFoliage			= false;
   mBlendStart			= 1.0;
   mBlendEnd				= 1.0;
   mUseBlend				= false;
#3
01/18/2007 (10:40 pm)
Game/fx/fxfoliagereplicator.cpp

Add the following to you void fxFoliageReplicator::initPersistFields()

addField( "GradientFile",		TypeFilename,	Offset( mFieldData.mGradientFile,			fxFoliageReplicator ) );
    addField( "QuadFoliage",		TypeBool,		Offset( mFieldData.mQuadFoliage,			fxFoliageReplicator ) ); //  Does this media have 4 images in one?
    addField( "UseColorBlend",		TypeBool,		Offset( mFieldData.mUseBlend,			fxFoliageReplicator ) ); //  Does this media blend colors?
	addField( "BlendStartColor",	TypeF32,		Offset( mFieldData.mBlendStart,				fxFoliageReplicator ) );
	addField( "BlendEndColor",		TypeF32,		Offset( mFieldData.mBlendEnd,				fxFoliageReplicator ) );
	addGroup( "Clustering" );
	addField( "useClustering",		TypeBool,		Offset( mFieldData.mUseClustering,			fxFoliageReplicator ) );	
	addField( "CountPerCluster",	TypeS32,		Offset( mFieldData.mCountPerCluster,		fxFoliageReplicator ) );
	addField( "ClusCountVariation",	TypeS32,		Offset( mFieldData.mClusCountVariation,		fxFoliageReplicator ) );
	addField( "ClusterRadius",		TypeS32,		Offset( mFieldData.mClusterRadius,			fxFoliageReplicator ) );
	addField( "ClusRadVariation",	TypeS32,		Offset( mFieldData.mClusRadVariation,		fxFoliageReplicator ) );
	endGroup( "Clustering" );

Towards the top of your void fxFoliageReplicator::CreateFoliage(void) function add:

S32				countLeftInCluster;		// count used to determine when a new cluster should be started
	S32				clusterCountMin;		// minimum of the range of plants in a cluster
	S32				clusterCountMax;		// maximum of the range of plants in a cluster
	U32				foliageInnerRadiusX;	// inner radius of the foliage position selection
	U32				foliageInnerRadiusY;	// inner radius of the foliage position selection
	U32				foliageOuterRadiusX;	// outer radius of the foliage position selection
	U32				foliageOuterRadiusY;	// outer radius of the foliage position selection
	U32				foliageOuterRadMinX;	// outer radius X minimum
	U32				foliageOuterRadMaxX;	// outer radius X maximum
	U32				foliageOuterRadMinY;	// outer radius Y minimum
	U32				foliageOuterRadMaxY;	// outer radius Y maximum
	Point3F			clusterPosition;		// position of the center of a cluster
#4
01/18/2007 (10:42 pm)
Now replace everythign between the

// Add Foliage.

and the

// Initialise RayCast Search Start/End Positions

to be:

// initialize the cluster stuff
	clusterCountMin = mFieldData.mCountPerCluster - mFieldData.mClusCountVariation;
	if ( clusterCountMin < 1 ) {
		clusterCountMin = 1;
	}
	clusterCountMax = mFieldData.mCountPerCluster + mFieldData.mClusCountVariation;
	if ( clusterCountMax < 1 ) {
		clusterCountMax = 1;
	}
	// determine the foliage radius of variation here
	if ( mFieldData.mUseClustering ) {
		// plants will be placed around the cluster center
		foliageInnerRadiusX = 0.f;
		foliageInnerRadiusY = 0.f;
		foliageOuterRadMinX = mFieldData.mClusterRadius - mFieldData.mClusRadVariation;
		foliageOuterRadMaxX = mFieldData.mClusterRadius + mFieldData.mClusRadVariation;
		foliageOuterRadMinY = mFieldData.mClusterRadius - mFieldData.mClusRadVariation;
		foliageOuterRadMaxY = mFieldData.mClusterRadius + mFieldData.mClusRadVariation;
	} else {
		// plants will be arranged individually
		foliageInnerRadiusX = mFieldData.mInnerRadiusX;
		foliageInnerRadiusY = mFieldData.mInnerRadiusY;
		foliageOuterRadiusX = mFieldData.mOuterRadiusX;
		foliageOuterRadiusY = mFieldData.mOuterRadiusY;
	}
	// force the first cluster to be set up
	countLeftInCluster = 0;

	for (U32 idx = 0; idx < mFieldData.mFoliageCount; idx++)
	{
		fxFoliageItem*	pFoliageItem;
		Point3F			FoliageOffsetPos;

		// Reset Relocation Retry.
		RelocationRetry = mFieldData.mFoliageRetries;

		// Find it a home ...
		do
		{

			// subtract from the count in cluster right here so that any failures/collisions will eventually get a new cluster
			//   position set
			if ( mFieldData.mUseClustering ) {
				countLeftInCluster--;
			}

			// Get the fxFoliageReplicator Position.

			if (mFieldData.mUseClustering && countLeftInCluster <= 0) 
			{
				clusterPosition = getPosition();
				countLeftInCluster = RandomGen.randI( clusterCountMin, clusterCountMax );
				HypX	= RandomGen.randF((F32) mFieldData.mInnerRadiusX, (F32) mFieldData.mOuterRadiusX);
				HypY	= RandomGen.randF((F32) mFieldData.mInnerRadiusY, (F32) mFieldData.mOuterRadiusY);
				Angle	= RandomGen.randF(0, (F32) M_2PI);
				clusterPosition.x += HypX * mCos(Angle);
				clusterPosition.y += HypY * mSin(Angle);

				foliageOuterRadiusX = RandomGen.randF( foliageOuterRadMinX, foliageOuterRadMaxX );
				foliageOuterRadiusY = RandomGen.randF( foliageOuterRadMinY, foliageOuterRadMaxY );
			}
			// Get the fxFoliageReplicator Position.
			if (mFieldData.mUseClustering)
				FoliagePosition = clusterPosition;
			else
				FoliagePosition = getPosition();

			// Calculate a random offset
			HypX	= RandomGen.randF(foliageInnerRadiusX, foliageOuterRadiusX);
			HypY	= RandomGen.randF(foliageInnerRadiusY, foliageOuterRadiusY);
			Angle	= RandomGen.randF(0, (F32) M_2PI);

			// Calcualte the new position.
			FoliagePosition.x += HypX * mCos(Angle);
			FoliagePosition.y += HypY * mSin(Angle);
#5
01/18/2007 (10:47 pm)
Next find these lines:

// No, so turn-off flipping.
pFoliageItem->Flipped = false;

and add the following block beneath them:

// J.C. this allows us to put more than one texture in single image
		pFoliageItem->quadTextureX = 0.0;
		pFoliageItem->quadTextureY = 0.0;

		// Does this texture have 4 images in one?
		if (mFieldData.mQuadFoliage)
		{
			int x = RandomGen.randI(0,1);
			if (x)
				pFoliageItem->quadTextureX = 0.5;
			x = RandomGen.randI(0,1);
			if (x)
				pFoliageItem->quadTextureY = 0.5;
		}
		// Are we using color blending?
		if (mFieldData.mUseBlend)
		{
			F32 blendColor = RandomGen.randF(mFieldData.mBlendStart,mFieldData.mBlendEnd);
	        pFoliageItem->blendColor=(blendColor);
		}
		else
		{
			pFoliageItem->blendColor = 1.0f;
		}
		// End J.C.

In the SetUpBuffers function change this block to look like this:

Point2F texCoords[4];
	texCoords[0] = Point2F(0.0, 0.0);
	texCoords[1] = Point2F(0.0, 0.5);
	texCoords[2] = Point2F(0.5, 0.5);
	texCoords[3] = Point2F(0.5, 0.0);

and change the code in between the

// Handle texture coordinates

and the

// Handle lighting, lighting happens at the same time as global so this is just an offset

to look like this:
vert->texCoord = texCoords[vertIndex]; 				
						if (pFoliageItem->Flipped)
							vert->texCoord.x = 1.0f - vert->texCoord.x;
						vert->texCoord.x = vert->texCoord.x + pFoliageItem->quadTextureX;	// J.C. add our x/y offset for quad foliages
						vert->texCoord.y = vert->texCoord.y + pFoliageItem->quadTextureY;
						// Handle sway. Sway is stored in a texture coord. The x coordinate is the sway phase multiplier, 
						// the y coordinate determines if this vertex actually sways or not.
						if ((vertIndex == 0) || (vertIndex == 3)) {
							vert->texCoord2.set(pFoliageItem->SwayTimeRatio / mGlobalSwayTimeRatio, 1.0f);
						} else {
							vert->texCoord2.set(0.0f, 0.0f);
						}
						vert->texCoord3.x = 0.0f;
						vert->texCoord3.y = pFoliageItem->blendColor,pFoliageItem->blendColor;
#6
01/18/2007 (10:54 pm)
Underneath the // Yes, so load foliage texture. add:

mFieldData.mGradientTexture = GFXTexHandle( mFieldData.mGradientFile, &GFXDefaultStaticDiffuseProfile );
		if ((GFXTextureObject*) mFieldData.mGradientTexture == NULL)
			Con::printf("fxFoliageReplicator:  %s is an invalid or missing foliage gradient file.", mFieldData.mFoliageFile);

and underneath the // Remove texture
which isn't far below it add:
mFieldData.mGradientTexture = NULL;

Under the // Set up our texture and color ops.
add:

GFX->setTexture(2, mFieldData.mGradientTexture);
	GFX->setTextureStageColorOp(2, GFXTOPModulate);

At the end of your packupdate writes add:

stream->writeString(mFieldData.mGradientFile);
		stream->writeFlag(mFieldData.mQuadFoliage);						// Quad Foliage on a Texture?
		stream->writeFlag(mFieldData.mUseBlend);						// Use Color Blending?
		stream->writeFlag(mFieldData.mUseClustering );					// Foliage Use Clusters
		stream->write( mFieldData.mCountPerCluster );					// Foliage Count per Cluster
		stream->write( mFieldData.mClusCountVariation );				// Foliage Cluster Count Variation
		stream->write( mFieldData.mClusterRadius );						// Foliage Cluster Radius
		stream->write( mFieldData.mClusRadVariation );	
		stream->write(mFieldData.mBlendStart);			
		stream->write(mFieldData.mBlendEnd);

and in the unpackupdate reads add:

mFieldData.mGradientFile = stream->readSTString();
		mFieldData.mQuadFoliage = stream->readFlag();					// Is this a quad texture?
		mFieldData.mUseBlend = stream->readFlag();						// Use Color Blending

		mFieldData.mUseClustering = stream->readFlag();					// Foliage Use Clusters
		stream->read( &mFieldData.mCountPerCluster );					// Foliage Count per Cluster
		stream->read( &mFieldData.mClusCountVariation );				// Foliage Cluster Count Variation
		stream->read( &mFieldData.mClusterRadius );						// Foliage Cluster Radius
		stream->read( &mFieldData.mClusRadVariation );

		stream->read(&mFieldData.mBlendStart);
		stream->read(&mFieldData.mBlendEnd);
#7
01/18/2007 (10:57 pm)
In the same routine underneath the // Load Foliage Texture on the client.
add:

mFieldData.mGradientTexture = GFXTexHandle( mFieldData.mGradientFile, &GFXDefaultStaticDiffuseProfile );
		if ((GFXTextureObject*) mFieldData.mGradientTexture == NULL)
			Con::printf("fxFoliageReplicator:  %s is an invalid or missing gradient texture file.", mFieldData.mGradientFile);

One note on this... Even if you decide not to use the gradient textures, because of the way the shader is set up you will need to add a dummy gradient texture. you can just create a 2x2 jpg that is all white and it will not affect your appearance at all.

Hopefully this is useful for someone. I would have submitted it as a resource but wasn't sure how to do it. And as always, the disclaimer: I think this is 100% working, there may be some bugs in it though, either in the paste to forum or in my code that I hadn't noticed previously.

One other area that I'll probably add at some point, would be to have an option to make the color variations on a per cluster basis, it would be easy to do, though you wouldn't want it in a lot of cases. But if you were using dense grass it would be useful as you could make the variations be patchy, patches of live and dead grass, and easy to add.
#8
01/19/2007 (5:31 am)
One thing you might like to add, is instead of a gradient texture, have a noise texture that alters the colour based on a noise pattern. You can then sample the U,V of the noise texture for your colour variations.
#9
01/19/2007 (11:48 am)
This should be officially added into TGEA, since it does the same as the original, but a lot more useful stuff too!
#10
01/19/2007 (7:07 pm)
Sweetness. I did the same thing for multiple textures (UV offsets and a single texture) but none of the other fancy stuff. Definitely going to swap this in there.

I assume that clustering is optional?
#11
01/19/2007 (8:18 pm)
Clustering is optional, there is a UseClustering flag that you can check or uncheck in the mission editor.
#12
01/27/2007 (1:00 pm)
Are you sure this works correctly?
Has anyone else added it successfully.

I've triple checked and I'm getting reddish foliage (which should be green) which could be a byproduct that this seems to have broken the lighting part of the replicator, and it's always using the top left quadrant when not in quad mode (which means it cuts a single foliage image into a part).
#13
01/28/2007 (12:04 am)
Hmm I know the code works, but it is possible I left something out when cutting and pasting this in. I can email you the cpp and h files if you like to make things easier.
#14
01/28/2007 (5:52 pm)
It all seems to be working fine for us.. although still figuring out good values for various things..

I was curious how hard it would be/is to add in support to duplicate the foliage quad and rotate it 90 degrees. So instead of a single face of foliage, you'd have 2 faces that intersect at the middle (technique used in most games)..
#15
01/28/2007 (6:45 pm)
Shouldn't be hard at all. I don't have time to play around with it at the moment though, run MMOG web sites for a living and Vanguard came out 2 days ago, so scrambling with all the early release stuff. Be a week or two before I have any programming time.
#16
01/28/2007 (9:18 pm)
Mm Vanguard :) Played Beta of it for awhile, probably should pick up retail eventually


and what site? :p
#17
01/29/2007 (12:55 am)
I run the databases at MMODB.com currently, which is an upstart network. In the past I ran the databases at OGaming and going way back at Gamesnet/EQ'Lizer if you want to go way back.
#18
01/29/2007 (1:41 am)
And you do this for a living? weird...
#19
01/29/2007 (3:45 am)
Ahh it's a nice gig, been doing it for nearly ten years now. Started off running normal game related web sites, but MMOG databases are great because the games last a long time, and users check in daily, and typically hit a lot of pages as they are searching for information. One of the few non-subscription ways to make money in online gaming since the ad market collapsed. Best thing about it though is that it allows me to set my own hours, and own living location since I do all my work from home. So I get to live in Thailand, and use all that money I save on game development.
#20
01/29/2007 (6:04 am)
J.C. In the changes I originally made, I went with the four textures instead of the quad, so if you want more than 4 types you could just change the constant and have as many more as you wanted.

Nice changes though, I really like how it looks in TGEA
Page «Previous 1 2 3 4 Last »