Ogre::Terrain - Simple Material Generator for .material

A place for users of OGRE to discuss ideas and experiences of utilitising OGRE in their games / demos / applications.
Post Reply
User avatar
Nauk
Gnoll
Posts: 653
Joined: Thu May 11, 2006 9:12 pm
Location: Bavaria
x 36
Contact:

Ogre::Terrain - Simple Material Generator for .material

Post by Nauk »

Hello everyone, since the question keeps coming up, with the Ogre::Terrain documentation still being really thin and it took me quite some hours to figure out how it works too I thought I'd share my findings back:

Short Version:

Code: Select all

This sample shows how to use your old fashioned Ogre material with Ogre::Terrain.
The sample consists of a very basic and simple TerrainMaterialGenerator that appends the global terrain normalmap to the chosen Ogre material and a simple CG 2.x singlepass 9 texture splatting shader with dynamic lighting, 3 splattmaps, global lightmap and global colourmap support.

More or less typical ArtifexTerra / ETM terrain data. I tried to find how to get a multipass material to work, but I didn't manage or find a solution to avoid the skirts flashing through in a very disturbing way. So if anyone knows how to do that it would be great if you could share your knowledge :). The shader works with common JPG and PNG textures, so no DDS required. And of course you don't need the provided shaders, any single pass material works.

There is still a small glitch with the dynamic lighting, in certain light angles the tiles seem to have slightly different lighting, if anyone has a clue how to fix it... would be great :)
Edit: *apparently there is something wrong with the tangent calculation, working on a fix.

You can download the whole sample with textures etc as zip here: https://sourceforge.net/projects/artifexterra3d/files/

Image

Usage:

Code: Select all

// Init custom materialgenerator
TerrainMaterialGeneratorPtr terrainMaterialGenerator;

// Set Ogre Material  with the name "TerrainMaterial" in constructor
TerrainMaterial *terrainMaterial = OGRE_NEW TerrainMaterial("TerrainMaterial");         
terrainMaterialGenerator.bind( terrainMaterial );  
               
terrainGlobals->setDefaultMaterialGenerator( terrainMaterialGenerator );
Note: this needs to be done before your load your terrain.

Change Terrain material:

Code: Select all

terrainMaterial->setMaterialByName("MyOtherMaterialName");
TerrainMaterial.h v1.0:

Code: Select all

#ifndef TERRAINMATERIAL_H
#define TERRAINMATERIAL_H

// V1.0

#include "Ogre.h"
#include "OgreTerrain.h"
#include "OgreTerrainMaterialGenerator.h"

    class TerrainMaterial : public Ogre::TerrainMaterialGenerator
    {
    public:
		
		TerrainMaterial(Ogre::String materialName, bool addNormalmap=true, bool cloneMaterial=true);
		
		void setMaterialByName(const Ogre::String materialName); 
		void addNormalMapOnGenerate(bool set) { mAddNormalMap=set; };
		void cloneMaterialOnGenerate(bool set) { mCloneMaterial=set; };

		Ogre::String getMaterialName() { return mMaterialName; };
        
        class Profile : public Ogre::TerrainMaterialGenerator::Profile
        {
        public:
            Profile(Ogre::TerrainMaterialGenerator* parent, const Ogre::String& name, const Ogre::String& desc);
            ~Profile();

            bool isVertexCompressionSupported() const { return false; }

            Ogre::MaterialPtr generate(const Ogre::Terrain* terrain);

            Ogre::MaterialPtr generateForCompositeMap(const Ogre::Terrain* terrain);

            Ogre::uint8 getMaxLayers(const Ogre::Terrain* terrain) const;

            void updateParams(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain);

            void updateParamsForCompositeMap(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain);

            void requestOptions(Ogre::Terrain* terrain);       

        };
    protected:			
		Ogre::String mMaterialName; 
		bool mCloneMaterial;
		bool mAddNormalMap;
    };
    
#endif

TerrainMaterial.cpp v1.0:

Code: Select all

#include "TerrainMaterial.h"

	TerrainMaterial::TerrainMaterial(Ogre::String materialName, bool addNormalmap, bool cloneMaterial) 
		: mMaterialName(materialName), mAddNormalMap(addNormalmap), mCloneMaterial(cloneMaterial)
    {
        mProfiles.push_back(OGRE_NEW Profile(this, "OgreMaterial", "Profile for rendering Ogre standard material"));
        setActiveProfile("OgreMaterial");
    }
    
    void TerrainMaterial::setMaterialByName(const Ogre::String materialName) {
		mMaterialName = materialName;
		_markChanged();
    };

    // -----------------------------------------------------------------------------------------------------------------------

    TerrainMaterial::Profile::Profile(Ogre::TerrainMaterialGenerator* parent, const Ogre::String& name, const Ogre::String& desc)
        : Ogre::TerrainMaterialGenerator::Profile(parent, name, desc)        
    {
    };

    TerrainMaterial::Profile::~Profile()
    {
    };

    Ogre::MaterialPtr TerrainMaterial::Profile::generate(const Ogre::Terrain* terrain)
    {
		const Ogre::String& matName = terrain->getMaterialName();        

		Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(matName);
		if (!mat.isNull()) 
			Ogre::MaterialManager::getSingleton().remove(matName);

		TerrainMaterial* parent = (TerrainMaterial*)getParent();

		// Set Ogre material 
		mat = Ogre::MaterialManager::getSingleton().getByName(parent->mMaterialName);

		// Clone material
		if(parent->mCloneMaterial) {
			mat = mat->clone(matName);
			parent->mMaterialName = matName;
		}
		
		// Add normalmap
		if(parent->mAddNormalMap) {
			// Get default pass
			Ogre::Pass *p = mat->getTechnique(0)->getPass(0);		

			// Add terrain's global normalmap to renderpass so the fragment program can find it.
			Ogre::TextureUnitState *tu = p->createTextureUnitState(matName+"/nm");

			Ogre::TexturePtr nmtx = terrain->getTerrainNormalMap();
			tu->_setTexturePtr(nmtx);	
		}
		
		return mat;
    };
    
    Ogre::MaterialPtr TerrainMaterial::Profile::generateForCompositeMap(const Ogre::Terrain* terrain)
    {
		return terrain->_getCompositeMapMaterial();
    };

    Ogre::uint8 TerrainMaterial::Profile::getMaxLayers(const Ogre::Terrain* terrain) const
    {
        return 0;
    };

    void TerrainMaterial::Profile::updateParams(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain)
    {
    };

    void TerrainMaterial::Profile::updateParamsForCompositeMap(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain)
    {
    };

    void TerrainMaterial::Profile::requestOptions(Ogre::Terrain* terrain)
    {
        terrain->_setMorphRequired(false);
        terrain->_setNormalMapRequired(true); // enable global normal map
        terrain->_setLightMapRequired(false);
        terrain->_setCompositeMapRequired(false);
    };

Note: The following material files are optional samples and you don't really need them.
CG 2_x Vertex Shader terrain_vp.cg:

Code: Select all

    void terrain_vp(
	    float4 position : POSITION,
	    float2 uv   	: TEXCOORD0,
	    float delta     : BLENDWEIGHT,

	    out float4 oPosition : POSITION,
	    out float2 oUv		 : TEXCOORD0,
	    out float4 oColor	 : COLOR,
	    
	    uniform float4 ambient,
	    uniform float4x4 worldViewProj,
	    uniform float morphFactor
	    )
    {
	    position.y = position.y + (delta.x * morphFactor);
	    oPosition = mul(worldViewProj, position);
	    oUv = uv;
	    oColor = ambient;
    }
CG 2_x Fragment Shader terrain_fp.cg:

Code: Select all

float4 expand(float4 v)
{ 
	return v * 2 - 1;
}

void terrain_fp
(
  float2 iTexCoord0 : TEXCOORD0,
  float4 iPosition : TEXCOORD1,
  float iAmbient    : COLOR,
  
  uniform float4 lightDiffuse,
  uniform float4 lightSpecular,
  uniform float exponent,
  uniform float4 lightPosition,
  uniform float3 eyePosition,
  uniform float4 attenuation,
 
  out float4 oColor : COLOR,
 
  uniform sampler2D covMap1,
  uniform sampler2D covMap2,
  uniform sampler2D covMap3,
  uniform sampler2D splat1,
  uniform sampler2D splat2,
  uniform sampler2D splat3,
  uniform sampler2D splat4,
  uniform sampler2D splat5,
  uniform sampler2D splat6,
  uniform sampler2D splat7,
  uniform sampler2D splat8,
  uniform sampler2D splat9,
  uniform sampler2D colourMap,
  uniform sampler2D lightMap, 
   
  /* global normalmap is added by the TerrainMaterialGenerator to TerrainMaterial
	since Ogre::Terrain doesn't have vertex normals */	
  uniform sampler2D normalMap, 
  
  uniform float splatScaleX,
  uniform float splatScaleZ
) 
{
	float3 cov1 = tex2D(covMap1, iTexCoord0).rgb;
	float3 cov2 = tex2D(covMap2, iTexCoord0).rgb;
	float3 cov3 = tex2D(covMap3, iTexCoord0).rgb;

	// save TexCoord for Global Textures
	float2 globalTexCoord = iTexCoord0;

	iTexCoord0.x *= splatScaleX;
	iTexCoord0.y *= splatScaleZ;

	// calculate Splatting
	oColor = tex2D(splat1, iTexCoord0) * cov1.x
           + tex2D(splat2, iTexCoord0) * cov1.y
           + tex2D(splat3, iTexCoord0) * cov1.z
           + tex2D(splat4, iTexCoord0) * cov2.x
           + tex2D(splat5, iTexCoord0) * cov2.y
           + tex2D(splat6, iTexCoord0) * cov2.z
		   + tex2D(splat7, iTexCoord0) * cov3.x
		   + tex2D(splat8, iTexCoord0) * cov3.y
		   + tex2D(splat9, iTexCoord0) * cov3.z;	
	// add global Colourmap		 
	oColor *= tex2D(colourMap, globalTexCoord);	   
	// add global Lightmap
	oColor *= tex2D(lightMap, globalTexCoord); //*tex2D(lightMap, globalTexCoord);	
   
	// lighting
	float3 normal = expand(tex2D(normalMap, globalTexCoord)).rgb;	
	normal = normalize(normal);

	float3 lightDir = lightPosition.xyz - (iPosition.xyz * lightPosition.w);
	lightDir = normalize(lightDir); 
	
	float distance = length(lightDir);
	float lumination = 1 / (attenuation.y + attenuation.z * distance + attenuation.w * distance * distance);

	lumination = min(lumination, 1.0);

	float3 eyeDir = normalize(eyePosition - iPosition.xyz);
	eyeDir = normalize(eyeDir);

	float3 halfAngle = normalize(lightDir + eyeDir);

	float NdotL = dot(lightDir, normal);
	float NdotH = dot(halfAngle, normal);
	float4 Lit = lit(NdotL, NdotH, exponent);

	// add lighting to color
	oColor *= iAmbient + lumination * (lightDiffuse * Lit.y + lightSpecular * Lit.z * Lit.y);    
}
Program File terrain.program:

Code: Select all

vertex_program Terrain/Programs/TerrainVP cg
{
	source TerrainVP.cg
	entry_point terrain_vp
	profiles vs_2_x arbvp1

	default_params
	{
		param_named_auto morphFactor custom 77
		param_named_auto worldViewProj worldviewproj_matrix
		param_named_auto ambient ambient_light_colour
	}
}
fragment_program Terrain/Programs/TerrainFP cg
{
	source TerrainFP.cg
	entry_point terrain_fp
	profiles ps_2_x arbfp1
	default_params
	{
		param_named_auto lightDiffuse light_diffuse_colour 0
		param_named_auto lightSpecular light_specular_colour 0
		
		param_named_auto lightPosition light_position_object_space 0
		param_named_auto eyePosition camera_position_object_space
		
		param_named_auto attenuation light_attenuation 0
	}
}
Material file terrain.material:

Code: Select all

material TerrainMaterial 
{

  technique
  {
    // requires PS 2.x
   	
    pass
    {
      vertex_program_ref Terrain/Programs/TerrainVP
      {
      }
      
      fragment_program_ref Terrain/Programs/TerrainFP
      {		
		param_named exponent float 63
                param_named splatScaleX float 96
		param_named splatScaleZ float 96
      }
      
		texture_unit 0
		{
			// first coverage map
			texture ETcoverage.0.png
		}
		texture_unit 1
		{
			// second coverage map
			texture ETcoverage.1.png
		}
		texture_unit 2
		{
			// third coverage map
			texture ETcoverage.2.png
		}

		// splatting textures
		texture_unit 3
		{
			texture splatting0.png
		}
		texture_unit 4
		{
			texture splatting1.png
		}
		texture_unit
		{
			texture splatting2.png
		}
		texture_unit
		{
			texture splatting3.png
		}
		texture_unit
		{
			texture splatting4.png
		}
		texture_unit
		{
			texture splatting5.png
		}
		texture_unit
		{
			texture splatting6.png
		}
		texture_unit
		{
			texture splatting7.png
		}
		texture_unit
		{
			texture splatting8.png
		}	 

		texture_unit 
		{
			texture artifexterra3d_colourmap.png
		}
		
		texture_unit 
		{
			texture ETlightmap.png
		}
    } // pass
    
} // technique

}


Going to set a wiki page up with it when I find the time. Hope this is of use for some of you. Feedback, critics and improvment are most welcome. :)
//Nauk
Last edited by Nauk on Tue Sep 25, 2012 11:57 pm, edited 9 times in total.
Chapiscado
Gnoblar
Posts: 3
Joined: Tue Jan 10, 2012 7:47 pm

Re: Ogre::Terrain - Simple Material Generator for .material

Post by Chapiscado »

Congratulations! This help was very useful to me!
One question: do you know how to change the texture or material at runtime?
I tried using the SetMaterialByName and upgrade the ground but could not ...

thank you
User avatar
Nauk
Gnoll
Posts: 653
Joined: Thu May 11, 2006 9:12 pm
Location: Bavaria
x 36
Contact:

Re: Ogre::Terrain - Simple Material Generator for .material

Post by Nauk »

Chapiscado wrote:Congratulations! This help was very useful to me!
One question: do you know how to change the texture or material at runtime?
I tried using the SetMaterialByName and upgrade the ground but could not ...

thank you
Thank you, glad it was of help :)
I tried using the SetMaterialByName and upgrade the ground but could not ...
You need to call generate again, after you set the material name to a new material.

Code: Select all

terrainMaterial->setMaterialByName("test");
terrainMaterial->generate(mTerrainGroup->getTerrain(0,0));
*Edit:* going to change that in the next version, so setMaterialByName does that automatically.
Chapiscado
Gnoblar
Posts: 3
Joined: Tue Jan 10, 2012 7:47 pm

Re: Ogre::Terrain - Simple Material Generator for .material

Post by Chapiscado »

It is Necessary to call some method to update?
I tried using this code and this has not changed anything.
What I'm really wanting to get to the the texture is applied field, modify it and update it at runtime. Got it?
Thank you for your attention
User avatar
Nauk
Gnoll
Posts: 653
Joined: Thu May 11, 2006 9:12 pm
Location: Bavaria
x 36
Contact:

Re: Ogre::Terrain - Simple Material Generator for .material

Post by Nauk »

Chapiscado wrote:It is Necessary to call some method to update?
Jap :)
Chapiscado wrote:What I'm really wanting to get to the the texture is applied field, modify it and update it at runtime.
You can see halfway how to do that in the TerrainMaterial.cpp where the normalmap is added. Iterate through the texture unit states (textures)
of the material pass and use Ogre::TextureUnitState::setTexture on the texture you want to change should do the trick.
User avatar
Nauk
Gnoll
Posts: 653
Joined: Thu May 11, 2006 9:12 pm
Location: Bavaria
x 36
Contact:

Re: Ogre::Terrain - Simple Material Generator for .material

Post by Nauk »

I tried using the SetMaterialByName and upgrade the ground but could not ...
You need to call generate again, after you set the material name to a new material.

Code: Select all

terrainMaterial->setMaterialByName("test");
terrainMaterial->generate(mTerrainGroup->getTerrain(0,0));
Actually I just found out that does not work always, when I tested that I did it short after the terrain was loaded where it still worked while later on during execution it didn't, sorry for the confusion. Correct would be:

Code: Select all

terrainMaterial->setMaterialByName("test");
terrainMaterial->_markChanged();
User avatar
Nauk
Gnoll
Posts: 653
Joined: Thu May 11, 2006 9:12 pm
Location: Bavaria
x 36
Contact:

Re: Ogre::Terrain - Simple Material Generator for .material

Post by Nauk »

Update:
- setMaterialByName() now calls _markChanged() so that's all you need to change the material.
- 2 new constructor parameters which let you decide wether the normalmap is added to your material or not and if you want the material cloned so you can get Ogre::Terrain autonaming for it.
Arkiruthis
Gremlin
Posts: 178
Joined: Fri Dec 24, 2010 7:55 pm
x 10

Re: Ogre::Terrain - Simple Material Generator for .material

Post by Arkiruthis »

Many thanks for this! Particularly useful as I have a custom terrain shader that I want to use with the Ogre Terrain.

Something that's confusing me though is how you access the Terrain's normal map?

In the FP:

Code: Select all

  /* global normalmap is added by the TerrainMaterialGenerator to TerrainMaterial
	since Ogre::Terrain doesn't have vertex normals */	
  uniform sampler2D normalMap, 
In your example (in the FP), if I just pass "tex2d(normalMap, UV).rgb" to the fragment shader to see the normals, I get this:
Image

This doesn't look like a generated mesh normal map? Looks like "ETcoverage.0.png"?

In the custom TerrainMaterial::Profile::generate(const Ogre::Terrain* terrain):

Code: Select all

		// Add terrain's global normalmap to renderpass so the fragment program can find it.
		Ogre::TextureUnitState *tu = p->createTextureUnitState(matName+"/nm");
	
		// LOGGING LINE ADDED BY ME
		Ogre::LogManager::getSingletonPtr()->logMessage(matName+"/mn");

		Ogre::TexturePtr nmtx = terrain->getTerrainNormalMap();
        tu->_setTexturePtr(nmtx);
The log calls this material:
11:06:33: OgreTerrain/2309288290/mn

I need to access the terrain normal map in my custom shader but I'm still confused about where and how it's being accessed? I've probably missed something obvious.
Arkiruthis
Gremlin
Posts: 178
Joined: Fri Dec 24, 2010 7:55 pm
x 10

Re: Ogre::Terrain - Simple Material Generator for .material

Post by Arkiruthis »

Ah! After I removed all the other texture units, it appeared... I guess my GFX card has a tex unit limit lower than the shader was set for.

Image

Question: The custom material seems to be be applying a form of fog though (visible in that screenshot)? I'm not sure where this is happening in the code?
User avatar
Nauk
Gnoll
Posts: 653
Joined: Thu May 11, 2006 9:12 pm
Location: Bavaria
x 36
Contact:

Re: Ogre::Terrain - Simple Material Generator for .material

Post by Nauk »

Arkiruthis wrote: In the custom TerrainMaterial::Profile::generate(const Ogre::Terrain* terrain):

Code: Select all

		// Add terrain's global normalmap to renderpass so the fragment program can find it.
		Ogre::TextureUnitState *tu = p->createTextureUnitState(matName+"/nm");
	
		// LOGGING LINE ADDED BY ME
		Ogre::LogManager::getSingletonPtr()->logMessage(matName+"/mn");

		Ogre::TexturePtr nmtx = terrain->getTerrainNormalMap();
        tu->_setTexturePtr(nmtx);
The log calls this material:
11:06:33: OgreTerrain/2309288290/mn

I need to access the terrain normal map in my custom shader but I'm still confused about where and how it's being accessed? I've probably missed something obvious.
The normal map is generated by Ogre::Terrain and yes that is the part where I retrieve and add it.
Arkiruthis wrote:Ah! After I removed all the other texture units, it appeared... I guess my GFX card has a tex unit limit lower than the shader was set for.

Image

Question: The custom material seems to be be applying a form of fog though (visible in that screenshot)? I'm not sure where this is happening in the code?
There is a fog setup in the sampleapp, just remove it, it is one line. Note I havent updated the zip archive yet, so the code I pasted here is the latest. And it seems your card is limited to shader model 2_0 which has I think only 2 texture registers, shader model 2_x has 16. So if you want to use more than 12 textures you would have to do multipass which leaves you with another problem: The terrain skirts are flashing which I still have not found a solution for.
Arkiruthis
Gremlin
Posts: 178
Joined: Fri Dec 24, 2010 7:55 pm
x 10

Re: Ogre::Terrain - Simple Material Generator for .material

Post by Arkiruthis »

Nauk wrote:There is a fog setup in the sampleapp, just remove it, it is one line.
Ah yep, thanks, found it.
Nauk wrote:And it seems your card is limited to shader model 2_0 which has I think only 2 texture registers, shader model 2_x has 16. So if you want to use more than 12 textures you would have to do multipass which leaves you with another problem: The terrain skirts are flashing which I still have not found a solution for.
No worries, 16 is more than enough for my needs. Shader about 80% ported to OgreTerrain. :D
Image

I take it that if I want to have tangents in my shader, I can only do that in the fragment shader? (i.e. cross product of normalMap value to X-UNIT or Z-UNIT binomial)?

There's no way to offload any of these calculations to the vertex shader?
User avatar
Nauk
Gnoll
Posts: 653
Joined: Thu May 11, 2006 9:12 pm
Location: Bavaria
x 36
Contact:

Re: Ogre::Terrain - Simple Material Generator for .material

Post by Nauk »

Wow, nice screenshot :) Not that I know, that is one of the drawbacks with not having the normals on the geometry and storing them in an image instead. 2_0 / 2_x vertex shaders cannot do texture lookups, aka tex2D(). I have no clue how to do it otherwise and I couldn't find something on the net. Well if you find something, let me know because I certainly would like to have that rather in the vertex shader too. And yeah you calculate it from the normal. There are severals ways to do it what I have seen:

Code: Select all

float3 tangent = float3(1,0,0);
float3 binormal = cross(normal, tangent);

Code: Select all

float3 tangent  = float3(abs(normal.y) + abs(normal.z), abs(normal.x), 0); 

Code: Select all

float3 tangent;

float3 c1 = cross(normal, float3(0.0, 0.0, 1.0));
float3 c2 = cross(normal, float3(0.0, 1.0, 0.0));

if( length(c1) > length(c2) ) {
	tangent = c1;
} else {
	tangent = c2;
}
Not sure which one is actually the better more acurate one, I am still at the very beginning when it comes to 3D math, and fighting with lighting problems in the terrain shader at the moment, which I believe are rooted in the tangent calculation.
User avatar
Nauk
Gnoll
Posts: 653
Joined: Thu May 11, 2006 9:12 pm
Location: Bavaria
x 36
Contact:

Re: Ogre::Terrain - Simple Material Generator for .material

Post by Nauk »

I made a little QT testbed there to toy around with light and tangent settings. You can clearly see there is different lighting on the tiles, which I think might be tangent related? Lighting works fine for the per pixel lighting shader without texture normalmapping tho.
Image
raven_3rdwing
Gnoblar
Posts: 5
Joined: Sun Aug 28, 2011 11:55 am
Location: France
Contact:

Re: Ogre::Terrain - Simple Material Generator for .material

Post by raven_3rdwing »

hi,
is it possible to send parameters to the shader from the C++ ogre side ? like setCustomParameter
thanks in advance .
raven_3rdwing
Gnoblar
Posts: 5
Joined: Sun Aug 28, 2011 11:55 am
Location: France
Contact:

Re: Ogre::Terrain - Simple Material Generator for .material

Post by raven_3rdwing »

hi
thanks , i will try this evening
best regards
raven_3rdwing
Gnoblar
Posts: 5
Joined: Sun Aug 28, 2011 11:55 am
Location: France
Contact:

Re: Ogre::Terrain - Simple Material Generator for .material

Post by raven_3rdwing »

it works ! :D 1000 x thanks!!!!
Evorlde
Gnoblar
Posts: 8
Joined: Tue Mar 25, 2014 9:18 pm
x 1

Re: Ogre::Terrain - Simple Material Generator for .material

Post by Evorlde »

Hi,

Sorry for this "ressurection of topic", but i need help on your tutorial.
I use your MaterialGenerator system and i want to know how can i do a setMaterialByName just on one terrain.

Actually when i do a setMaterialByName, it works but it reloads all my terrains present in my terrain group. I want to apply the modification just on one terrain.

Tank,
Post Reply