Terrain Texture Splatting

You are an experienced programmer and have a problem with the engine, shaders, or advanced effects? Here you'll get answers.
No questions about C++ programming or topics which are answered in the tutorials!
jarhead
Posts: 5
Joined: Tue Feb 19, 2008 8:44 pm

Terrain Texture Splatting

Post by jarhead »

It seems I've been able to get texture splatting to work on my terrain. I'd really be interested in hearing suggestions on different approaches or improvements to what i've done here.

Before attempting texture splatting, I used one texture streched over the terrain:

Image

My first step was to export my alpha maps for each of my texures. I have 3 textures snow, dirt, and grass. That means to do splatting I would need 6 textures. Since Irrlicht only supports 4 textures I decided to merge my 3 apha map textures into a different color channel of one texture. See the example below:

Image

With this approach I reduce my 6 texture requirement down to 4 textures. Plus it saves texture memory which is a good thing.

Since the fixed function pipeline doesn't support reading individual texture color channels, I had to write a pixel shader to make this work. Here is the code for the pixel shader:

Code: Select all

float4x4 matViewProjection : ViewProjection;
float texScale = 10.0;

sampler AlphaMap = sampler_state
{
   ADDRESSU = WRAP;
   ADDRESSV = WRAP;
   ADDRESSW = WRAP;
};

sampler TextureOne = sampler_state
{
   MipFilter = LINEAR;
   MinFilter = LINEAR;
   MagFilter = LINEAR;
   ADDRESSU = WRAP;
   ADDRESSV = WRAP;
   ADDRESSW = WRAP;
};

sampler TextureTwo = sampler_state
{
   MipFilter = LINEAR;
   MinFilter = LINEAR;
   MagFilter = LINEAR;
   ADDRESSU = WRAP;
   ADDRESSV = WRAP;
   ADDRESSW = WRAP;
};

sampler TextureThree = sampler_state
{
   MipFilter = LINEAR;
   MinFilter = LINEAR;
   MagFilter = LINEAR;
   ADDRESSU = WRAP;
   ADDRESSV = WRAP;
   ADDRESSW = WRAP;
};

struct VS_INPUT
{
   float4 Position : POSITION0;
   float2 alphamap : TEXCOORD0;
   float2 tex : TEXCOORD1;
};

struct VS_OUTPUT
{
   float4 Position : POSITION0;
   float2 alphamap : TEXCOORD0;
   float2 tex : TEXCOORD1;
};

struct PS_OUTPUT
{
   float4 diffuse : COLOR0;
};

VS_OUTPUT vs_main( VS_INPUT Input )
{
   VS_OUTPUT Output; 
   Output.Position = mul( Input.Position, matViewProjection );
   Output.alphamap = Input.alphamap;
   Output.tex = Input.tex;

   return( Output );
}

PS_OUTPUT ps_main(in VS_OUTPUT input)
{
   PS_OUTPUT output = (PS_OUTPUT)0;

   vector a = tex2D(AlphaMap, input.alphamap);
   vector i = tex2D(TextureOne, mul(input.tex, texScale));
   vector j = tex2D(TextureTwo, mul(input.tex, texScale));
   vector k = tex2D(TextureThree, mul(input.tex, texScale));

   float4 oneminusx = 1.0 - a.x;
   float4 oneminusy = 1.0 - a.y;
   float4 oneminusz = 1.0 - a.z;

   vector l = a.x * i + oneminusx * i;
   vector m = a.y * j + oneminusy * l;
   vector n = a.z * k + oneminusz * m;

   output.diffuse = n;

   return output;
}

technique Default_DirectX_Effect
{
   pass Pass_0
   {
      VertexShader = compile vs_2_0 vs_main();
      PixelShader = compile ps_2_0 ps_main();
   }
}
Here is what the final result looks like:

Image

If anyone has any suggestions on how to improve this I'd love to hear it. Also, i'd really like to find out if there is way to by-pass the 4 texture limitation. I know you can reference texture paths in the pixel shader but this approach doesn't seem to work with Irrlicht. I'm currently at my 4 texture limit, so what options do i have if i wanted to incorporate a lightmap?
BlindSide
Admin
Posts: 2821
Joined: Thu Dec 08, 2005 9:09 am
Location: NZ!

Post by BlindSide »

Ive been thinking about this also. I guess that if the terrain mesh is detailed enough, and the alpha map looks like it has a low resolution, you could store the alpha maps in the vertex colour, this should make it run much faster and frees up a texture slot. It should give you similar results I hope.

Other ideas like storing lots of different textures in the same texture and reading them by offsetting the texcoords by reading from an index (eg, if red == 1 use this, if red == 2 use this one, if red...) would work but you would need to do your own bilinear filtering, this means 4 texture reads per pixel, which isnt so bad I guess. (And yes it will look just as smooth as long as you do bilinear filtering.) But remember to disable bilinear filtering from inside Irrlicht on the "alpha" map if you use this method because you want to read exact integer values.

Cheers
ShadowMapping for Irrlicht!: Get it here
Need help? Come on the IRC!: #irrlicht on irc://irc.freenode.net
PuG
Posts: 13
Joined: Wed Mar 05, 2008 12:11 pm

Post by PuG »

Hi, looks good! well its similar to how Atlas does terrain in TGEA using a opacity/attributes map.

I think its a good way of doing it, but for an example you have four 512 texture tiles, and one opacity PNG image with 4 channels.

Each channel has a defined zone (R,G,B,A), and then all you have todo is for the tile textures to be repeated in those defined areas (I believe yours already does this?).

Perhaps then you could stack PNG opacity texture upon each other to support even more terrain textures, so 4, 8 & even 16 perhaps?

Good program for sculpting and outputting opacity files for terrains is Grome from Quad Software.

Ive added a link below to a zip file with an example heightmap (16bit RAW file + 1 pixel seam), lightmap (JPG) & PNG texture with 4 channels.

Terrain File Download
jarhead
Posts: 5
Joined: Tue Feb 19, 2008 8:44 pm

Post by jarhead »

Thanks for the feed back. Grome looks pretty nice. I"m using PnP Terrain Creator but I may switch to this if all goes well. The only problem is that it's kind of pricy...
PuG
Posts: 13
Joined: Wed Mar 05, 2008 12:11 pm

Post by PuG »

Well I suppose your paying for the quality of toolset & to make sure development continues - ive tried allot of different programs, short of Grome I thought Earth Sculptor? was pretty good.
doqkhanh
Posts: 158
Joined: Sat Mar 01, 2008 3:14 am
Location: Tokyo, Japan
Contact:

Post by doqkhanh »

:shock:
I don't know how to pass 3 alpha map to the shader.

I created shader call back.
I created new material with your shader. But I am so confuse because I totally new with shader.

Please help me.
christianclavet
Posts: 1638
Joined: Mon Apr 30, 2007 3:24 am
Location: Montreal, CANADA
Contact:

Post by christianclavet »

Wow! That's a very good idea!

This method could be used, and for example on tiled terrain, each tile could use it own set of textures. Neverwinter nights II use this method for it terrain tool.
bitplane
Admin
Posts: 3204
Joined: Mon Mar 28, 2005 3:45 am
Location: England
Contact:

Post by bitplane »

doqkhanh wrote::shock:
I don't know how to pass 3 alpha map to the shader.

I created shader call back.
I created new material with your shader. But I am so confuse because I totally new with shader.

Please help me.
Add them to the material: SMaterial.TextureLayer[x].Texture, currently you can have a maximum of 4 textures.

As for splatting in general, most game engines use the steepness of the terrain to decide the type of texture. I'm not sure if this is added as vertex information or into the texture, but it works really well for cliff faces
Submit bugs/patches to the tracker!
Need help right now? Visit the chat room
BlindSide
Admin
Posts: 2821
Joined: Thu Dec 08, 2005 9:09 am
Location: NZ!

Post by BlindSide »

bitplane wrote:
doqkhanh wrote::shock:
I don't know how to pass 3 alpha map to the shader.

I created shader call back.
I created new material with your shader. But I am so confuse because I totally new with shader.

Please help me.
Add them to the material: SMaterial.TextureLayer[x].Texture, currently you can have a maximum of 4 textures.

As for splatting in general, most game engines use the steepness of the terrain to decide the type of texture. I'm not sure if this is added as vertex information or into the texture, but it works really well for cliff faces
I modified Elvman's terrain editor shader to do this. I simply modulated using the Y component of the surface Normal, heres the shot and source code:

Image


HLSL PS:

Code: Select all

sampler2D coverage; //coverage
sampler2D lightmap; //lightmap

sampler2D splat1;
sampler2D splat2;

// Pixel shader output structure
struct PS_OUTPUT
{
    float4 Color : COLOR0;  // Pixel color    
};

struct PS_INPUT
{
	float4 Position : POSITION;
	float4 Diffuse  : COLOR0;
	
	float2 LightmapTexCoord : TEXCOORD1; //lightmap texture coords
	
	float2 SplatTexCoord1 : TEXCOORD2; //1st spalt texture coords
	float2 SplatTexCoord2 : TEXCOORD3; //2nd spalt texture coords
	float normalDist : TEXCOORD0;
};
	
PS_OUTPUT main( PS_INPUT Input ) 
{ 
	PS_OUTPUT Output;

		
	float4 TexColor = lerp(tex2D( splat2, Input.SplatTexCoord2),
					tex2D( splat1, Input.SplatTexCoord1),Input.normalDist);
	
	float4 LightmapColor = tex2D( lightmap, Input.LightmapTexCoord );  // lightmap

	Output.Color = Input.Diffuse * TexColor * LightmapColor;

	return Output;
}

HLSL VS:

Code: Select all

float4x4 mWorldViewProj;  // World * View * Projection transformation

// Vertex shader output structure
struct VS_OUTPUT
{
	float4 Position   : POSITION;   // vertex position 
	float4 Diffuse    : COLOR0;     // vertex diffuse color
	
	float2 LightmapTexCoord : TEXCOORD1; //lightmap texture coords
	
	float2 SplatTexCoord1 : TEXCOORD2; //1st spalt texture coords
	float2 SplatTexCoord2 : TEXCOORD3; //2nd spalt texture coords
	float normalDist : TEXCOORD0;
};

struct VS_INPUT
{
	float4 Position	: POSITION;
	float3 Normal		: NORMAL;
	float4 Diffuse		: COLOR0;
	float2 TexCoord0	: TEXCOORD0;
	float2 TexCoord1	: TEXCOORD1;
};


VS_OUTPUT main(VS_INPUT Input)
{
	VS_OUTPUT Output;

	// transform position to clip space 
	Output.Position = mul(Input.Position, mWorldViewProj);
	
	Output.Diffuse = Input.Diffuse;
	Output.LightmapTexCoord = Input.TexCoord1;
	
	Output.SplatTexCoord1 = Input.TexCoord0;
	Output.SplatTexCoord2 = Input.TexCoord0;
	
	Output.normalDist = Input.Normal.y;
	
	return Output;
}
ShadowMapping for Irrlicht!: Get it here
Need help? Come on the IRC!: #irrlicht on irc://irc.freenode.net
matgaw
Posts: 45
Joined: Sat Oct 06, 2007 11:33 am

Post by matgaw »

I don't know shaders, but these look like DirectX ones.

Could any kind person rewrite them also to GLSL to make it working on both engines?

These texture splatting shaders could be added to new Irrlicht version, since they are EXTREMELY useful and EXTREMELY missing.
MasterD
Posts: 153
Joined: Sun Feb 15, 2004 4:17 pm
Location: Lübeck, Germany
Contact:

Post by MasterD »

@jarhead:
I've tried your implementation with YASS it works like a charm, do you mind if I use it further?

Here's also one tip for advancement: use the submitted textures one/two/three as detail maps. This improves the visual and looks very nice, look here:
Image

This is the additional code for the detailmaps (factor 40 should be set from the app):

Code: Select all

   vector detail = tex2D(TextureOne, mul(input.tex, texScale*40));
   vector detail2 = tex2D(TextureTwo, mul(input.tex, texScale*40));
   vector detail3 = tex2D(TextureThree, mul(input.tex, texScale*40));

   float d1 = (detail.r + detail.g + detail.b) / 3;
   float d2 = (detail2.r + detail2.g + detail2.b) / 3;
   float d3 = (detail3.r + detail3.g + detail3.b) / 3;
 
   i = saturate(i + d1 - 0.5);
   j = saturate(j + d2 - 0.5);
   k = saturate(k + d3 - 0.5);
YASS - Yet another Space Shooter
under Devolpment, see http://yass-engine.de
MasterD
Posts: 153
Joined: Sun Feb 15, 2004 4:17 pm
Location: Lübeck, Germany
Contact:

Multipass rendering

Post by MasterD »

I've managed to get multipass terrain splatting working with D3D9, so you are beyond the max 3 color maps limitation.

For this to work I created a new SceneNode, which basically serves as a proxy for the TerrainSceneNode. It's still prototyped code, so some adaptions are neccessary to make it work fully, but you will get the idea.
Have a look at the results, 6 texture glory :-D
Image

The shader is nearly the same, I just added the following into the pixel shader for alpha blending:

Code: Select all

output.diffuse.a = saturate(a.r + a.g + a.b);
And the code which enables framebuffer blending while rendering:
SceneNode.h

Code: Select all

#ifndef ___CEXTENDEDTERRAINSCENENODE_INCLUDED
#define ___CEXTENDEDTERRAINSCENENODE_INCLUDED

#include "include/CCamera.h"

namespace irr
{
namespace scene
{

	/**
	*/
class CExtendedTerrainSceneNode : public ISceneNode
{
public:
	CExtendedTerrainSceneNode(ISceneNode* parent, ISceneManager* mgr, s32 id)
			: 
		ISceneNode(parent, mgr, id, core::vector3df())
	{
		Node = mgr->addTerrainSceneNode("game/td3/textures/terrain/earth01/earth01hightmap.bmp", this);
	}

	virtual ~CExtendedTerrainSceneNode()
	{
	}

	virtual void OnRegisterSceneNode()
	{
		if (IsVisible)
		{
			// Don't register childs
			//ISceneNode::OnRegisterSceneNode();
			SceneManager->registerNodeForRendering(this);
			Node->setVisible(true);
			Node->OnRegisterSceneNode();
			Node->setVisible(false);
		}
	}

	virtual const core::aabbox3d<f32>& getBoundingBox() const
	{
		return Node->getBoundingBox();
	}

	const ITerrainSceneNode & getNode() const
	{
		return *Node;
	}
	ITerrainSceneNode & getNode()
	{
		return *Node;
	}

	void render();

	virtual video::SMaterial& getMaterial(u32 num)
	{
		return (Node? Node->getMaterial(num) : *((video::SMaterial*)0));
	}

	virtual u32 getMaterialCount() const
	{
		return (Node? Node->getMaterialCount() : 0);
	}

private:
	ITerrainSceneNode * Node;
};

}
}

#endif
SceneNode.cpp

Code: Select all

#include "include/CExtendedTerrainSceneNode.h"

#include <d3d9.h>

namespace irr
{
	namespace scene
	{
		void CExtendedTerrainSceneNode::render()
		{
			Node->setVisible(true);
			video::IVideoDriver * driver = SceneManager->getVideoDriver();
			IDirect3DDevice9 * device = driver->getExposedVideoData().D3D9.D3DDev9;

			// First Pass
			Node->setMaterialTexture(0,
				driver->getTexture("game/td3/textures/tex0.jpg"));
			Node->setMaterialTexture(1,
				driver->getTexture("game/td3/textures/tex1.jpg"));
			Node->setMaterialTexture(2,
				driver->getTexture("game/td3/textures/tex2.jpg"));
			Node->setMaterialTexture(3,
				driver->getTexture("game/td3/textures/alpha1.bmp"));

			Node->render();

			DWORD alpha, srcblend, destblend;
			device->GetRenderState(D3DRS_ALPHABLENDENABLE, &alpha);
			device->GetRenderState(D3DRS_SRCBLEND, &srcblend);
			device->GetRenderState(D3DRS_DESTBLEND, &destblend);
			device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
			device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
			device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

			// Second pass
			Node->setMaterialTexture(0,
				driver->getTexture("game/td3/textures/tex3.jpg"));
			Node->setMaterialTexture(1,
				driver->getTexture("game/td3/textures/tex4.jpg"));
			Node->setMaterialTexture(2,
				driver->getTexture("game/td3/textures/tex5.jpg"));
			Node->setMaterialTexture(3,
				driver->getTexture("game/td3/textures/alpha2.bmp"));

			Node->render();

			device->SetRenderState(D3DRS_ALPHABLENDENABLE, alpha);
			device->SetRenderState(D3DRS_SRCBLEND, srcblend);
			device->SetRenderState(D3DRS_DESTBLEND, destblend);
			
			Node->setVisible(false);
		}
	}
}
YASS - Yet another Space Shooter
under Devolpment, see http://yass-engine.de
yamashi
Posts: 82
Joined: Sat Jan 03, 2009 4:53 am

Post by yamashi »

Can somebody post an example code on how to use it ?
matgaw
Posts: 45
Joined: Sat Oct 06, 2007 11:33 am

Post by matgaw »

Tried shader from first post. I got only textures with no details, changing scale doesn't affect this. What am I doing wrong? I tried nearly all combinations...
MasterD
Posts: 153
Joined: Sun Feb 15, 2004 4:17 pm
Location: Lübeck, Germany
Contact:

Post by MasterD »

Are you submitting the textures in the correct order? Irrlicht transfers the textures 1 to 1, meaning TextureLayer[0].Texture will be the AlphaMap.

Additionally, the constant set within the shader (float texScale = 10.0; ) seamed to have no effect, try to put that declaration within the pixel shader function.

Here's the code I use, which has lighting using ambient lighting and one directional light. NOTE: I've changed the order of the textures, TextureLayer[4] is Alpha Map.

Code: Select all

float4x4 matViewProjection;
float3 fSunlightDir;
float3 fAmbientColor;
float3 fSunlightColor;

sampler2D TextureOne;
sampler2D TextureTwo;
sampler2D TextureThree;
sampler2D AlphaMap;

struct VS_INPUT
{
   float4 Position  : POSITION0;
   float3 Normal    : NORMAL0;
   float2 alphamap  : TEXCOORD0;
   float2 tex       : TEXCOORD1;
};

struct VS_OUTPUT
{
   float4 Position  : POSITION0;
   float2 alphamap  : TEXCOORD0;
   float2 tex       : TEXCOORD1;
   float3 Normal    : TEXCOORD2;
};

struct PS_OUTPUT
{
   float4 diffuse : COLOR0;
};

VS_OUTPUT vs_main( VS_INPUT Input )
{
   VS_OUTPUT Output;
   Output.Position = mul( Input.Position, matViewProjection );
   Output.alphamap  = Input.alphamap;
   Output.tex       = Input.tex;
   Output.Normal    = Input.Normal;

   return( Output );
}

PS_OUTPUT ps_main(in VS_OUTPUT input)
{
    float texScale = 40.0;

    float3 N = normalize(input.Normal);

    PS_OUTPUT output;// = (PS_OUTPUT)0;

    float3 a =  tex2D(AlphaMap,       input.alphamap);
    float3 c1 = tex2D(TextureOne,     input.tex);
    float3 c2 = tex2D(TextureTwo,     input.tex);
    float3 c3 = tex2D(TextureThree,   input.tex);

    float3 detail1 = tex2D(TextureOne,   input.tex * texScale);
    float3 detail2 = tex2D(TextureTwo,   input.tex * texScale);
    float3 detail3 = tex2D(TextureThree, input.tex * texScale);

    float d1 = (detail1.r + detail1.g + detail1.b) / 3;
    float d2 = (detail2.r + detail2.g + detail2.b) / 3;
    float d3 = (detail3.r + detail3.g + detail3.b) / 3;

    c1 = saturate(c1 + d1 - 0.5);
    c2 = saturate(c2 + d2 - 0.5);
    c3 = saturate(c3 + d3 - 0.5);

    float3 oneminusx = 1.0 - a.r;
    float3 oneminusy = 1.0 - a.g;
    float3 oneminusz = 1.0 - a.b;

    output.diffuse.rgb = a.r * c1.rgb;
    output.diffuse.rgb = a.g * c2.rgb + oneminusy * output.diffuse.rgb;
    output.diffuse.rgb = a.b * c3.rgb + oneminusz * output.diffuse.rgb;

    // Lighting
    output.diffuse.rgb = output.diffuse.rgb * 
        saturate((fAmbientColor + fSunlightColor*saturate(dot(N, fSunlightDir))));

// this term is only neccessary for multipass rendering, 
// will be ignored with standard rendering
    output.diffuse.a = saturate(a.r + a.g + a.b);


    return output;
}
[/size]

Edit: Just Realized that I'm responding to quite an old thread, sorry for that, would've made a new one, if I realized that earlier :)
YASS - Yet another Space Shooter
under Devolpment, see http://yass-engine.de
Post Reply