U3D Terrain Editor


#41

Okay, this morning I was able to re-implement the 8-detail texture technique, with bump-mapping, and eliminate the seams. And although there is a noticeable slowdown on my compy, while editing I’m still getting about 26 fps with 2 directional lights. So this is good.

Some years ago, I had bookmarked gamedev.net/blog/73/entry-16 … explained/ and ran across it again while browsing my bookmarks a few days ago. It pretty much sums up the method I used today for seam-elimination.

First, I wrote a Lua routine to hand-build the mipmap levels for a composite texture by specifying 4 base textures. I hand build them so that when they are downsampled, I downsample each texture individually then pack them together for the composite, rather than the default behavior of downsampling the packed texture as a whole. This way, when the texture is downsampled it doesn’t “bleed” pixels between tile types.

The shader routine described by Ysaneya in the link above then calculates the mip level manually, and adjusts the scaling of the sampled texture area dynamically based on the mip level, rather than fudging it with a constant based on the 0 level pixel size as my previous version was doing. This means that whatever the mip level, the tile area is “shrunk” by one pixel on each border, ensuring that filtering does not occur across tile boundaries. And the seams are gone.

I am quite happy with the result. The one blip I have is that the calculation of blending factors using the local depthmap for each terrain type can sometimes result in “bleeding” of adjacent terrain types into their neighbors. But it is an organic bleeding that looks relatively natural, and is a consequence of the fact that even if a particular terrain type is 0 in the blend map, that terrain type becomes 0+depth in the blend, and if the terrain type’s 0+depth is greater than its neighbor’s blend+depth then that texture will bleed through. In the shader, I calculate the contribution of a particular detail type as a blend between it and its neighbor, then I calculate the blend between these blended pairs, and yet another blend between these blends of blended pairs (an exponential lerp chain, in other words). So if Type A is first blended with Type B, then anywhere that 0+TypeADepth is greater than TypeBBlend+TypeBDepth will result in a contribution from Type A at that pixel even though TypeABlend was equal to 0. This results in the random-ish scattering of different terrain pixels you can see in the above image, especially at the fringes of a swatch of terrain where its blend value decreases toward 0.

Even with that glitch, though, the terrain looks good to me. I especially like that it gives me no seams now, no matter the view distance.


#42

I updated the first post to show some of the recent images:



I am really liking that new terrain type blending scheme. Rather than ugly gradual fades from one type to the next you get a nice transition where it seems like stones are covered in grass, grass is growing on dirt, etc… Looks pretty cool, even with these cruddy textures. And it’s super nice having 8 terrain types. The scheme could easily be extended to 12 or more, but I’m good with 8 for now.

Did some tweaking on the shader. Turns out, I don’t need to manually generate my mipmap levels. The default mip generation works just fine. I’ve altered the filters to take advantage of the new texturing scheme, and removed some of the cruft. There are still a lot of things I need to do to clean it up, though. But I feel like I am getting to a point where I can start doing that cleanup. I have a pretty good handle on how I want this thing to operate, and I’m pretty happy with this texturing scheme.


#43

Im getting an error when compilong under linux. I do cmake . and then make:

roger@gaara ~/projects/U3DTerrainEditor $ make [ 42%] Built target ANLVM Scanning dependencies of target TEdit [ 50%] Building CXX object CMakeFiles/TEdit.dir/bind_anl.cpp.o In file included from /home/roger/projects/U3DTerrainEditor/bind_anl.cpp:11:0: /home/roger/projects/Urho3D/include/Urho3D/ThirdParty/toluapp/tolua++.h:45:17: fatal error: lua.h: No such file or directory #include "lua.h" ^ compilation terminated.


#44

I’m not really the best one to ask for building advice. Especially on Linux, since it’s been years since I last used Linux. But for the sake of thoroughness:

  1. Make sure URHO3D_HOME is set, and points to the proper location
  2. Make sure Lua support is enabled in both this project and the Urho3D library.

Seeing your actual cmake invokation and what defines you specify for both the library and the terrain editor would be a help.


#45

The variable is set, and Urho3D has Lua support (luajit, actually), the build command is:

Do I have to use the Urho build scripts to compile the terrain editor?


#46

You need to use the included CMakeLists and pass it the same defines that you pass to build Urho3D.

One note, though, is that I have not yet written any of the shaders for GLSL, so it’s not going to work on Linux with OpenGL. That is a project for a future date, I’m afraid.


#47

:cry:

Please? *angel smilie goes here *


#48

Hello,

Does the TerrainBlendEdit xml both hlsl glsl still work for Urho3D 1.40? I at the point where I am mixing the textures to get the cliff texture, etc? The old method isn’t working.

This was the old method.

[code] /// Set component
terrainProcedural -> Initialize();
terrainProcedural -> SetDimensions(DEFAULTSIZE,DEFAULTSIZE);
terrainProcedural -> SetWorldType(terrainrule.worldtype, terrainrule.subworldtype, terrainrule.sealevel, terrainrule.creationtime);
terrainProcedural -> SetOctaves(override, octaves, persistence, octave1,octave2,octave3,octave4,octave5,octave6,octave7,octave8);

/// Generate Produracel map
terrain->GenerateProceduralHeightMap(terrainrule);

Image * producedHeightMapImage = new Image(context_);
producedHeightMapImage -> SetSize(DEFAULTSIZE+1,DEFAULTSIZE+1, 1, 4);
producedHeightMapImage -> SetData(terrain->GetData());

terrain->SetMaterial(cache->GetResource<Material>("Materials/TerrainEdit.xml"));

/// Get heightmap for texture blend
Image * terrainHeightMap= new Image(context_);

terrainHeightMap->SetSize(DEFAULTSIZE+1,DEFAULTSIZE+1,1,4);
terrainHeightMap ->SetData(terrain -> GetHeightMap () -> GetData());

terrainHeightMap -> FlipVertical();

/// Generte image

/// Define heightmap texture
int bw=DEFAULTSIZE+1,bh=DEFAULTSIZE+1;

Texture2D * blendtex=new Texture2D(context_);
blendtex -> SetNumLevels(1);
blendtex -> SetSize(0,0,0,TEXTURE_DYNAMIC);
terrain-> GetMaterial() -> SetTexture(TU_DIFFUSE ,blendtex);

/// Shared pointer for blend texture
SharedPtr<Image> blend;
SharedPtr<Image> blendMap;

blend = new Image(context_);
blend -> SetSize(bw,bh,1,4);
blend -> Clear(Color(1,0,0,0));

blendMap = new Image(context_);
blendMap -> SetSize(bw,bh,1,4);
blendMap -> Clear(Color(0,0,0,0));


float steep=0.0f;
float steepforlerp=0.0f;

/// create blend here
for(unsigned int x=0; x<bw; x++)
{
    for(unsigned int y=0; y<bh; y++)
    {

        Color terrainHeightvalue=terrainHeightMap->GetPixel(x,y);

        switch(terrainrule.worldtype)
        {
        case WORLD_DESERT:
        {
            Color currentcolor = blend -> GetPixel(x,y);
            Color resultcolor=currentcolor.Lerp(Color(0.0f,1.0f,0.0f,0.0f), 1.0f);
            blend-> SetPixel(x,y,resultcolor);
        }
        break;
        default:
            /// Compare to sealavel
            if(terrainHeightvalue.r_<terrainrule.sealevel)
            {

                Color currentcolor = blend -> GetPixel(x,y);

                //               float mix=1.0f-((float)terrainHeightvalue.r_/terrainrule.sealevel);
                float mix=(float)terrainHeightvalue.r_/terrainrule.sealevel;

                float sterpforlerp=cutoff(mix,0.05f,0.040f,false);

                Color resultcolor=currentcolor.Lerp(Color(0.0f,1.0f,0.0f,0.0f), sterpforlerp);

                blend-> SetPixel(x,y,resultcolor);

            }
            break;
        }

        /// blend cliff
        Vector2 nworld=Vector2(x/(float)bw, y/(float)bh);
        Vector3 worldvalue=NormalizedToWorld( producedHeightMapImage,terrain,nworld);
        Vector3 normalvalue=terrain->GetNormal(worldvalue);

        steep=1.0f-normalvalue.y_;
        steepforlerp=cutoff(steep,0.05f,0.040f,false);

        Color currentcolor = blend -> GetPixel(x,y);

        int mixfactor=rand()%99;

        float mix=(float)(mixfactor+1)/100;

        // Color resultcolor=currentcolor.Lerp(Color(0,0,mix,1.0f-mix), steepforlerp);
        Color resultcolor=currentcolor.Lerp(Color(0,0,mix,1.0f-mix), steepforlerp);

        blend-> SetPixel(x,y,resultcolor);

    }
}

/// Rotate image and assign texture
blend -> 	FlipVertical ();

environmentbuild_ -> SetTextureMap(blend);

blendtex ->SetData(blend, true);

RigidBody* terrainbody = terrainNode->CreateComponent<RigidBody>();

CollisionShape* terrainshape = terrainNode->CreateComponent<CollisionShape>();

terrainbody->SetCollisionLayer(1);
terrainshape->SetTerrain();

Vector3 position(0.0f,0.0f);
position.y_ = terrain->GetHeight(position) + 1.0f;

/// Add node
manager_->AddGeneratedObject(terrainNode);


/// Position character
Node * characternode_ = Existence->scene_->CreateChild("Character");
characternode_->SetPosition(Vector3(0.0f, position.y_ , 0.0f));

/// Get the materials
Material * skyboxMaterial = skybox->GetMaterial();

/// Change environment
Existence->GenerateSceneUpdateEnvironment(terrainrule);
/// Set component
terrainProcedural -> Initialize();
terrainProcedural -> SetDimensions(DEFAULTSIZE,DEFAULTSIZE);
terrainProcedural -> SetWorldType(terrainrule.worldtype, terrainrule.subworldtype, terrainrule.sealevel, terrainrule.creationtime);
terrainProcedural -> SetOctaves(override, octaves,  persistence, octave1,octave2,octave3,octave4,octave5,octave6,octave7,octave8);

/// Generate Produracel map
terrain->GenerateProceduralHeightMap(terrainrule);

Image * producedHeightMapImage = new Image(context_);
producedHeightMapImage -> SetSize(DEFAULTSIZE+1,DEFAULTSIZE+1, 1, 4);
producedHeightMapImage -> SetData(terrain->GetData());

terrain->SetMaterial(cache->GetResource<Material>("Materials/TerrainEdit.xml"));

/// Get heightmap for texture blend
Image * terrainHeightMap= new Image(context_);

terrainHeightMap->SetSize(DEFAULTSIZE+1,DEFAULTSIZE+1,1,4);
terrainHeightMap ->SetData(terrain -> GetHeightMap () -> GetData());

terrainHeightMap -> FlipVertical();

/// Generte image

/// Define heightmap texture
int bw=DEFAULTSIZE+1,bh=DEFAULTSIZE+1;

Texture2D * blendtex=new Texture2D(context_);
blendtex -> SetNumLevels(1);
blendtex -> SetSize(0,0,0,TEXTURE_DYNAMIC);
terrain-> GetMaterial() -> SetTexture(TU_DIFFUSE ,blendtex);

/// Shared pointer for blend texture
SharedPtr<Image> blend;
SharedPtr<Image> blendMap;

blend = new Image(context_);
blend -> SetSize(bw,bh,1,4);
blend -> Clear(Color(1,0,0,0));

blendMap = new Image(context_);
blendMap -> SetSize(bw,bh,1,4);
blendMap -> Clear(Color(0,0,0,0));


float steep=0.0f;
float steepforlerp=0.0f;

/// create blend here
for(unsigned int x=0; x<bw; x++)
{
    for(unsigned int y=0; y<bh; y++)
    {

        Color terrainHeightvalue=terrainHeightMap->GetPixel(x,y);

        switch(terrainrule.worldtype)
        {
        case WORLD_DESERT:
        {
            Color currentcolor = blend -> GetPixel(x,y);
            Color resultcolor=currentcolor.Lerp(Color(0.0f,1.0f,0.0f,0.0f), 1.0f);
            blend-> SetPixel(x,y,resultcolor);
        }
        break;
        default:
            /// Compare to sealavel
            if(terrainHeightvalue.r_<terrainrule.sealevel)
            {

                Color currentcolor = blend -> GetPixel(x,y);

                //               float mix=1.0f-((float)terrainHeightvalue.r_/terrainrule.sealevel);
                float mix=(float)terrainHeightvalue.r_/terrainrule.sealevel;

                float sterpforlerp=cutoff(mix,0.05f,0.040f,false);

                Color resultcolor=currentcolor.Lerp(Color(0.0f,1.0f,0.0f,0.0f), sterpforlerp);

                blend-> SetPixel(x,y,resultcolor);

            }
            break;
        }

        /// blend cliff
        Vector2 nworld=Vector2(x/(float)bw, y/(float)bh);
        Vector3 worldvalue=NormalizedToWorld( producedHeightMapImage,terrain,nworld);
        Vector3 normalvalue=terrain->GetNormal(worldvalue);

        steep=1.0f-normalvalue.y_;
        steepforlerp=cutoff(steep,0.05f,0.040f,false);

        Color currentcolor = blend -> GetPixel(x,y);

        int mixfactor=rand()%99;

        float mix=(float)(mixfactor+1)/100;

        // Color resultcolor=currentcolor.Lerp(Color(0,0,mix,1.0f-mix), steepforlerp);
        Color resultcolor=currentcolor.Lerp(Color(0,0,mix,1.0f-mix), steepforlerp);

        blend-> SetPixel(x,y,resultcolor);

    }
}

/// Rotate image and assign texture
blend -> 	FlipVertical ();

environmentbuild_ -> SetTextureMap(blend);

blendtex ->SetData(blend, true);

RigidBody* terrainbody = terrainNode->CreateComponent<RigidBody>();

CollisionShape* terrainshape = terrainNode->CreateComponent<CollisionShape>();

terrainbody->SetCollisionLayer(1);
terrainshape->SetTerrain();

Vector3 position(0.0f,0.0f);
position.y_ = terrain->GetHeight(position) + 1.0f;

/// Add node
manager_->AddGeneratedObject(terrainNode);


/// Position character
Node * characternode_ = Existence->scene_->CreateChild("Character");
characternode_->SetPosition(Vector3(0.0f, position.y_ , 0.0f));

/// Get the materials
Material * skyboxMaterial = skybox->GetMaterial();

/// Change environment
Existence->GenerateSceneUpdateEnvironment(terrainrule);[/code]

#49

whoa, roads looks sexy. Seeing it, I want to drop everything and make another rally game.


#50

I’m in the process of updating this project (after a long hiatus) to work with the recent updates to Urho3D, including conditionals for D3D11 shaders and GLSL versions of the various terrain shaders.


#51

The project repo ( github.com/JTippetts/U3DTerrainEditor ) now has an 8-terrain shader that works for D3D9, D3D11 and GLSL. It successfully builds against the latest Urho3D (pulled yesterday, 7-4). Since I walked away from the project for so long, I’m not really sure where it stands, refactor-wise. I’ll probably eliminate most of the experimental shaders I was tinkering with for simplicity, and try to add flexibility to the ones in existence. Additionally, I have a tri-planar shader I’m working on. I’ll try to build a new terrain dialog that gives control over shader choice to make it easier to play with.

Edit:
The repo now has the HLSL and GLSL versions of the 8-detail triplanar shader. It’s pretty heavyweight, but even on my potato it’s still functional in the editor. The tri-planar shader uses the detail blending between layers described in previous posts, along with normal-mapping (enabled by the BUMPMAP option). You can see the difference between the tri-planar shader and the normal shader:

Normal:

Triplanar:


#52

Is this correct?

<material>
<technique name="Techniques/TerrainBlend8EditTriplanar.xml" />
<texture unit="1" name="Textures/TerrainBlend4/Grass0126_2_S.jpg"/>
<texture unit="2" name="Textures/TerrainBlend4/Desert2.jpg" />
<texture unit="3" name="Textures/TerrainBlend4/Cliff2.jpg" />
<texture unit="4" name="Textures/TerrainBlend4/Cliff3.jpg" />
<parameter name="MatSpecColor" value="0 0 0 1" />
<parameter name="DetailTiling" value="1024 1024" />
<parameter name="BumpStrength" value="128" />
<parameter name="PackTexFactors" value="0.25 0.5 512 9" />
</material>

I asked because I’m getting this response.

[Wed Jul 8 14:55:51 2015] ERROR: Failed to compile pixel shader TerrainBlend8EditDetailTriplanar(BUMPMAP DIRLIGHT PERPIXEL): 0(1021) : error C7506: OpenGL does not define the global function mul


#53

mul(x, y) should be replaced by x * y in OpenGL


#54

The line I see in the glsl file is. Are you refering to that?

vec3 normal=normalize(mul((bump1*b1+bump2*b2+bump3*b3+bump4*b4+bump5*b5+bump6*b6+bump7*b7+bump8*b8)/bsum,tbn));


#55

Yes, replace by:


#56

I pushed the changes as Mike suggested. Mike: Is there a chance that some implementations of GLSL might, in fact, provide mul()? I’m no expert at that sort of thing, but I never encountered any errors with the use of mul() while doing my own testing.

GLSL compilation errors aside, that isn’t the correct usage of the shader. Perhaps a bit more explanation of it is in order.

The shader (either 8Detail or 8DetailTriplanar) only uses 3, 4 or 5 texture units. The number used depends on whether or not BUMPMAP and USEMASKTEXTURE are specified as options.

Slot 0: Weight map for terrain types 0,1,2 and 3
Slot 1: Weight map for terrain types 4,5,6 and 7
Slot 2: Terrain texture atlas (more on its format/layout in a bit)
Slot 3: Normal map of terrain texture atlas
Slot 4: Mask texture (provided to allow for visualization of the editing mask in the terrain editor).

The weight maps are straightforward: a value of 0 for a given component corresponding to a terrain type means that terrain type provides no contribution, a value of 1 means it provides full contribution. All weights are balanced in the shader so they add up to 1.

The terrain texture atlas is constructed as an atlas of 8 terrain types. Each type provides the diffuse color in RGB and the height of the texture in A. The height channel is used to alter the blending weight as described earlier in the thread, achieving the effect of terrain such as raised stones more realistically combining with terrain such as dirt, so that dirt tends to fill in the cracks around the stones rather than just simply fading from one to the other. The terrain texture provided with the editor can be found in Bin/TerrainEditorData/Textures/diff.png with the normal map at normal.png. It is laid out in a 4x2 pattern of textures (4 across, 2 down), and each texture is 512 pixels in size. This is important. The PackTexFactors passed to the shader as a float4/vec4 describes this layout, and must be edited if the layout is changed. The first float, 0.25, describes the width of a single terrain texture in comparison to the entire texture. ie, 0.25 (1/4, given that the texture is 4 terrain types wide). The second float, 0.5, describes the height of a single terrain texture in relation to the height of the whole texture. In this case, 0.5 given that the texture is 2 terrain types high. The third float is the size, in pixels, of a single terrain type texture. The final float is the exponential size, of the top level terrain texture dimensions. ie, 9 in this case, given that 2^9=512. This exponent is used in the calculation of the mip-map level in a custom fashion described earlier in the thread.

The mask texture specifies a texture that is mapped across the entire terrain. The red channel is currently the only mask channel used, though I might provide for the ability to use up to 4 mask layers in a layer iteration of the editor. The mask value is used to mix between the final diffuse texture color and a reddish mask color, to give a visual indicator of where your mask is applied. The mask texture only really makes sense in the context of the editor, and in a real game usage should probably be omitted.

The BumpStrength shader uniform is a bit of legacy cruft that is no longer used, and can safely be omitted.


#57

I’m not expert either, I’m using this sheet for reference. Maybe ‘mul’ has been added recently to GLSL.

BTW, I’ve sent a PR to fix the issue reported by rogerdv when building with LuaJit enabled.


#58

GLSL doesn’t have a function called “mul”. Some compilers may allow it, but it’s not in the GLSL specification, so don’t expect it to work on every computer.


#59

I updated terraineditUIOriginal.lua to work in Urho 1.5
(Only need edit to the lines that use GetPtr function) :slight_smile:
http://pastebin.com/qCENVAyD


#60

Hi
Can you post a sample project using this?

thanks