Game Development Community

dev|Pro Game Development Curriculum

Textured Atlas Terrain

by Vincent BILLET · 01/18/2006 (5:20 pm) · 3 comments

Texture Library
Each Pixel of the terrain (in TQT file) represent a texture. I wanted 64 textures for my terrain. So I made big texture(2048x2048) filled with 8x8 texture of 256x256 pixel size.

Blending Texture
I made a texture for blending terrain. The idea is to use this "blending texture" as 4 alpha channels.
void atlasHugeTerrainCreator::createBlend(const char *filename)
{
   GBitmap tmp(512, 512, false, GFXFormatR8G8B8A8);
   FileStream fsP;
   if(!ResourceManager->openFileForWrite(fsP, filename, File::Write))
   {
      Con::errorf("SavePNG - failed to open output file!");
      return ;
   }
   ColorI Color;
   int rr,gg,bb,aa;
   rr=255;
   aa=0;
   for (int yy=0;yy<512;yy++)
   {
	    gg=255;
		bb=0;
		for (int xx=0;xx<512;xx++)
		{
			if (rr>0) {Color.red=rr;} else {Color.red=0;}
			if (gg>0) {Color.green=gg;} else {Color.green=0;}
			if (bb>0) {Color.blue=bb;} else {Color.blue=0;}
			if (aa>0) {Color.alpha=aa;} else {Color.alpha=0;}
			tmp.setColor(xx,yy,Color);
			if (gg>=0) {gg--;} else {bb++;}
		}
		if (rr>=0) {rr--;} else {aa++;}

   }
   tmp.writePNG(fsP);
   fsP.close();
}
and make it available like thise :
ConsoleFunction(createBlend,void,2,2,"Blending file name.")
{
	createBlend(argv[1]);
}
in console you can call createblend("myfile.jpg");

Atlas update
In void AtlasInstance::renderObject(SceneState *state, SceneRenderImage *image)
void AtlasInstance::renderObject(SceneState *state, SceneRenderImage *image)
{
   PROFILE_START(ChunkInstance_renderObject);
   GFX->pushState();
   // Set up rendering state
   GFX->disableShaders();
   GFX->setTextureStageColorOp( 0, GFXTOPModulate );
   GFX->setTextureStageColorOp( 1, GFXTOPModulate );
   GFX->setTextureStageColorOp( 2, GFXTOPModulate );
   GFX->setTextureStageColorOp( 3, GFXTOPModulate );
   GFX->setTextureStageColorOp( 4, GFXTOPDisable );
   GFX->setAlphaBlendEnable(false);
just after GFX->setAlphaBlendEnable(false);

GFX->setTextureStageMagFilter(0, GFXTextureFilterPoint );//<=========Terrain
   GFX->setTextureStageMinFilter(0, GFXTextureFilterPoint );
   GFX->setTextureStageMipFilter(0, GFXTextureFilterPoint);

   GFX->setTextureStageMagFilter(1, GFXTextureFilterLinear );//<=========Fog
   GFX->setTextureStageMinFilter(1, GFXTextureFilterLinear );
   GFX->setTextureStageMipFilter(1, GFXTextureFilterLinear);

   GFX->setTextureStageMagFilter(2, GFXTextureFilterLinear );//<=========Detail
   GFX->setTextureStageMinFilter(2, GFXTextureFilterLinear );
   GFX->setTextureStageMipFilter(2, GFXTextureFilterLinear);

   GFX->setTextureStageMagFilter(3, GFXTextureFilterLinear );//<=========Texture Library (2048x2048)
   GFX->setTextureStageMinFilter(3, GFXTextureFilterLinear );
   GFX->setTextureStageMipFilter(3, GFXTextureFilterLinear);

   GFX->setTextureStageMagFilter(4, GFXTextureFilterLinear );//<=========Blending texture (made with createBlend)
   GFX->setTextureStageMinFilter(4, GFXTextureFilterLinear );
   GFX->setTextureStageMipFilter(4, GFXTextureFilterLinear);
   GFX->setTextureStageAddressModeU( 0, GFXAddressClamp );
   GFX->setTextureStageAddressModeV( 0, GFXAddressClamp );
   // ^=- This texture is set by the quad tree as we recurse.

   GFX->setTextureStageAddressModeU( 1, GFXAddressClamp );
   GFX->setTextureStageAddressModeV( 1, GFXAddressClamp );
   GFX->setTexture(1, gClientSceneGraph->getFogTexture());

   // And the detail textures...
   GFX->setTextureStageAddressModeU( 2, GFXAddressWrap  );
   GFX->setTextureStageAddressModeV( 2, GFXAddressWrap  );
   GFX->setTexture(2, mDetailTex);

   GFX->setTextureStageAddressModeU( 3, GFXAddressClamp  );
   GFX->setTextureStageAddressModeV( 3, GFXAddressClamp  );
   GFX->setTexture(3, mDetailTex1);
   GFX->setTextureStageAddressModeU( 4, GFXAddressWrap  );
   GFX->setTextureStageAddressModeV( 4, GFXAddressWrap  );
   GFX->setTexture(4, mDetailTex2);
In bool AtlasInstance::onAdd(). Just after mDetailTex1.set(mDetailName1, &GFXDefaultStaticDiffuseProfile);
mDetailTex1.set(mDetailName1, &GFXDefaultStaticDiffuseProfile);
      mDetailTex2.set(mDetailName2, &GFXDefaultStaticDiffuseProfile);

In AtlasInstance::AtlasInstance(), add just after mDetailName1 = StringTable->insert("terrain_water_demo/data/terrains/details/detail1");
mDetailName1   = StringTable->insert("terrain_water_demo/data/terrains/details/detail1");
   mDetailName2   = StringTable->insert("terrain_water_demo/data/terrains/details/detail1");

In void AtlasInstance::initPersistFields(), just add before the end
addField("detailTexture1",  TypeFilename,  Offset(mDetailName1,     AtlasInstance), 
      "Texture Library.");
   addField("detailTexture2",  TypeFilename,  Offset(mDetailName2,     AtlasInstance), 
      "Blending.");

In U32 AtlasInstance::packUpdate(NetConnection * conn, U32 mask, BitStream *stream) just add
stream->writeString(mDetailName1);
   stream->writeString(mDetailName2);

In void AtlasInstance::unpackUpdate(NetConnection * conn, BitStream *stream) just add
mDetailName1   = stream->readSTString();
   mDetailName2   = stream->readSTString();

In atlasInstance.h, just after GFXTexHandle mDetailTex; add:
StringTableEntry        mDetailName1;
   StringTableEntry        mDetailName2;
   GFXTexHandle            mDetailTex1;
   GFXTexHandle            mDetailTex2;

Shaders
Here are the shaders I used : atlasSurfaceV.hlsl (Updated for MS3);
#define IN_HLSL
#include "shdrConsts.h"

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


struct ConnectData
{
   float4 hpos            : POSITION;
   float4 shading         : COLOR;
   float2 texCoord        : TEXCOORD0;
   float2 fogCoord        : TEXCOORD1;
   float2 detailCoord     : TEXCOORD2;
   float2 detailCoord2    : TEXCOORD3;
};

//-----------------------------------------------------------------------------
// Main                                                                        
//-----------------------------------------------------------------------------
ConnectData main( VertData IN,
                  uniform float4x4 modelview       : register(VC_WORLD_PROJ),
                  uniform float4x4 objTrans        : register(VC_OBJ_TRANS),
                  uniform float3   eyePos          : register(VC_EYE_POS),
                  uniform float3   fogData         : register(VC_FOGDATA),
                  uniform float4   inLightColor    : register(VC_LIGHT_DIFFUSE1),
                  uniform float4   texGen          : register(C44),
                  uniform float4   scaleOff        : register(C45),
                  uniform float4   morphInfo       : register(C46)
                  )
{
   ConnectData OUT;
   
   // Do positional transform on the heightfield data, and apply morph.
   float4  realPos;
   realPos.x = IN.position.x * scaleOff.z + scaleOff.x;
   realPos.y = IN.position.y * scaleOff.z + scaleOff.y;
   realPos.z = IN.position.z + IN.texCoord.x * morphInfo.x;
   realPos.w = IN.position.w;
   
   OUT.hpos        = mul(modelview, realPos);
   float3 worldPos = mul(objTrans,  realPos.xyz);
   float3 worldEye = mul(objTrans,  eyePos);

   /// Generate texture co-ords.
   OUT.texCoord.y = realPos.x * texGen.z + texGen.x;
   OUT.texCoord.x = realPos.y * texGen.z + texGen.y;

   // And fog texture co-ords.   
   OUT.fogCoord.x = 1.0 - ( distance( worldPos, worldEye ) / fogData.z );
   OUT.fogCoord.y = (worldPos.z - fogData.x) * fogData.y;

   // And detail texture co-ords.

   OUT.detailCoord  = realPos / 8;
   OUT.detailCoord2 = realPos / 8;

   OUT.shading = inLightColor;

   return OUT;
}

atlasSurfaceP.hlsl (Updated)
//-----------------------------------------------------------------------------
// Structures                                                                  
//-----------------------------------------------------------------------------
struct ConnectData
{
   float4 shading         : COLOR;
   float2 texCoord        : TEXCOORD0;
   float2 fogCoord        : TEXCOORD1;
   float2 detailCoord     : TEXCOORD2;
   float2 detailCoord2    : TEXCOORD3;
};

struct Fragout
{
   float4 col : COLOR0;
};

float4 Couleur(float4 diffuseColor, sampler2D detailMap1, float2 TC)
{
   float num,num1;
   float2 CVB;
//   num = diffuseColor.r*20;//-1.0;
//   num1 = diffuseColor.b*20;//-1.0;
   num = int(diffuseColor.r*20);//-1.0;
   num1 = int(diffuseColor.b*20);//-1.0;

   CVB.x = (TC.x-int(TC.x))/8.0;
   CVB.y = (TC.y-int(TC.y))/8.0;
   CVB.x += num/8.0;
   CVB.y += num1/8.0;
   return tex2D(detailMap1,  CVB);
}

//-----------------------------------------------------------------------------
// Main                                                                        
//-----------------------------------------------------------------------------
Fragout main( ConnectData IN,
              uniform sampler2D diffuseMap      : register(S0),    // Terrain
              uniform sampler2D fogMap          : register(S1),    // Fog
              uniform sampler2D detailMap       : register(S2),	   // Detail
              uniform sampler2D detailMap1      : register(S3),    // Texture
              uniform sampler2D detailMap2      : register(S4)     // Blend
              )
{
   Fragout OUT;
   float Offset = 1.0/256.0;  [B]// = texture size in TQT[/B]
   float4 color,colorU,colorD,colorR,colorL,couleur,colorUL,colorUR,colorDL,colorDR,color1,color2,color3;

   float4 diffuseColor = tex2D(diffuseMap,IN.texCoord);
   color = Couleur(diffuseColor, detailMap2, IN.detailCoord);

   float2 CoordVB = IN.detailCoord;
   CoordVB.x = (CoordVB.x-int(CoordVB.x));
   CoordVB.y = (CoordVB.y-int(CoordVB.y));


   float4 coulb = tex2D(detailMap1,CoordVB);
   if (CoordVB.x<0.5) {
	diffuseColor = tex2D(diffuseMap,float2(IN.texCoord.x,IN.texCoord.y-Offset));   
	colorU = Couleur(diffuseColor, detailMap2, IN.detailCoord);   
	if (CoordVB.y<0.5) {
		diffuseColor = tex2D(diffuseMap,float2(IN.texCoord.x-Offset,IN.texCoord.y-Offset));   
		colorUL = Couleur(diffuseColor, detailMap2, IN.detailCoord);   
		diffuseColor = tex2D(diffuseMap,float2(IN.texCoord.x-Offset,IN.texCoord.y));   
		colorL = Couleur(diffuseColor, detailMap2, IN.detailCoord);
		color1 = lerp(color,colorUL,(coulb.g+coulb.r)/2);
		color2 = lerp(color,colorU,coulb.g);
		color3 = lerp(color,colorL,coulb.r);

	} else
	{
		diffuseColor = tex2D(diffuseMap,float2(IN.texCoord.x+Offset,IN.texCoord.y-Offset));   
		colorUR = Couleur(diffuseColor, detailMap2, IN.detailCoord);   
		diffuseColor = tex2D(diffuseMap,float2(IN.texCoord.x+Offset,IN.texCoord.y));   
		colorR = Couleur(diffuseColor, detailMap2, IN.detailCoord);   
		color1 = lerp(color,colorU,coulb.g);
		color2 = lerp(color,colorUR,(coulb.g+coulb.a)/2);
		color3 = lerp(color,colorR,coulb.a);
	}
   } else
   {
	diffuseColor = tex2D(diffuseMap,float2(IN.texCoord.x,IN.texCoord.y+Offset));   
	colorD = Couleur(diffuseColor, detailMap2, IN.detailCoord);   
	if (CoordVB.y<0.5) {
		diffuseColor = tex2D(diffuseMap,float2(IN.texCoord.x-Offset,IN.texCoord.y+Offset));   
		colorDL = Couleur(diffuseColor, detailMap2, IN.detailCoord);   
		diffuseColor = tex2D(diffuseMap,float2(IN.texCoord.x-Offset,IN.texCoord.y));   
		colorL = Couleur(diffuseColor, detailMap2, IN.detailCoord);
		color1 = lerp(color,colorL,coulb.r);
		color2 = lerp(color,colorDL,(coulb.r+coulb.b)/2);
		color3 = lerp(color,colorD,coulb.b);
	} else
	{
		diffuseColor = tex2D(diffuseMap,float2(IN.texCoord.x+Offset,IN.texCoord.y));   
		colorR = Couleur(diffuseColor, detailMap2, IN.detailCoord);   
		diffuseColor = tex2D(diffuseMap,float2(IN.texCoord.x+Offset,IN.texCoord.y+Offset));   
		colorDR = Couleur(diffuseColor, detailMap2, IN.detailCoord);   
		color1 = lerp(color,colorR,coulb.a);
		color2 = lerp(color,colorD,coulb.b);
		color3 = lerp(color,colorDR,(coulb.b+coulb.a)/2);
	}
   }


   color = (color1+color2+color3)/3;

   float4 fogCol       = tex2D(fogMap,    IN.fogCoord);
   float4 detailCol    = tex2D(detailMap, IN.detailCoord);
   OUT.col = lerp( color *(diffuseColor.g*2+0.4), fogCol, fogCol.a );
   return OUT;
}

Materials Update
Replace AtlasShader in your ~/client/scripts/shader.cs with this :

new ShaderData( AtlasShader )
{
   DXVertexShaderFile   = "shaders/atlasSurfaceV.hlsl";
   DXPixelShaderFile    = "shaders/atlasSurfaceP.hlsl";
   pixVersion = 3.0;    // for sure it's 3.0, Don't work if you set it to 2.0
};

Mission File Update
new AtlasInstance(Large) {
      position = "0 0 0";
      rotation = "1 0 0 0";
      scale = "1 1 1";
      chunkFile = "~/data/terrains/chu.chu";
      tqtFile = "~/data/terrains/tqt.tqt";
      materialName = "AtlasMaterial";
      detailTexture  = "~/data/terrains/grassland/detail1";
      detailTexture1 = "~/data/terrains/grassland/bib1";
      detailTexture2 = "~/data/terrains/grassland/blend";
   };

How to use this code with HTC ?
Go to the texture window. Select a colorset (for example the first). set the colors to 10 40 0 - 20 40 0 - 30 40 0 - etc.. Select the second colorset and the the colors : 10 40 10 - 20 40 10 - 30 40 10 - etc.. and so on for each textureset. Set Bumping to 0. Create your texture Library with texture of equivalent color.
www.solu-si.com/dragonhead/shots/htctex2.jpg
Final Words
You must understand, as I said in beginning of this post, that each color represent a texture. Red component is the "x" Texture coordinate in Library, blue is the "y" coordinate. e.g. I want to paint the texture placed in 2,3 in my texture library. The color is equal to 20,?,30.
The green component is used to make shadows. the less this value is, the more shadows are.

I Hope this migth help some of you, and perhaps some gives some ideas to improve this.

www.solu-si.com/dragonhead/shots/aw11.jpg
Edit (13 Apr 2006) : Added Materials update.
Edit (26 Apr 2006) :
CreateBlend Update
CreateBlend console function added
Update ::OnAdd() Function
Added : Mission File Update
Edit (28 Apr 2006) :
Updated Shaders.

#1
02/08/2006 (10:49 pm)
Probably a very dumb question, but does this work in TGE or just TSE?
#2
02/13/2006 (1:24 pm)
It's for TSE only
#3
06/18/2006 (11:23 am)
nice resource, will this be integrated into the HEAD?