Terrain Options

GOD I'm pulling my hair out. I've tried several things yet they just seem to not work well within my game.



I have two COMPLETE terrain solutions and they work well, just slowwww. I experience trippiness of the camera (and character), the character sinking beneath the earth, low FPS (bursts of it), etc.



The problems all occur when changing tiles. I have tried two methods… my first, which is obviously the worst, is to create an entirely new TerrainPage during every update. The second was to reuse old TerrainBlock's (I chose block because I thought it was created faster). On the first I used MrCoder's terrain splatting solution, and on the second I used MomokoFan's.



Everything I have tried would never fly in actual production, so I must ask you, the people, how the hell I am supposed to do this? I have tried to investigate jME Terra but it's poorly documented and I feel that there may be other methods that are faster to get rolling. If you have examples of using jME Terra in a test like TestIsland or TestTerrain, I'd be glad to see them… The jME Terra tests on the google page just didn't click with me (probably more of my problem than the tests').



I'd prefer something I can utilize via heightmaps or something that has a built-in editor, but at this point I'm pretty desperate for something that just functions in-game with GOOD performance.

It is still in jme1.0 format, when fenggui updates to lwjgl2.0 ill update terra to 2.0



www.virginbreed.com/terrademo/src.rar


Approx. how big are your tiles in terms of heightmap resolution? How many tiles appear in-game at any point of time? How many detail textures are used for your splatting? What LOD algorithms are you using?

theprism said:

It is still in jme1.0 format, when fenggui updates to lwjgl2.0 ill update terra to 2.0

I still use 1.0, so perfect! Thanks!


Momoko_Fan said:

Approx. how big are your tiles in terms of heightmap resolution? How many tiles appear in-game at any point of time? How many detail textures are used for your splatting? What LOD algorithms are you using?

64x64 heightmaps, 9 at one time (the one you are on and the 8 that surround it), a base and 4 detail layers, and I'm using the LOD on the RawHeightMaps but not the TerrainBlock's LOD because it's super-slow. However, I'm only experiencing the lag when the program is moving the tiles around, so LOD isn't of much help at that point.

Apparently it was something with my computer-- rerunning the program I really don't notice it all that much (about as much as I notice little jumps while playing WoW).



But if I keep running back and forth, I get this strange error:

com.jme.system.JmeException: Error compiling GLSL shader: Fragment shader failed to compile with the following errors:
ERROR: 0:7: 'r' : redefinition
ERROR: 1 compilation errors.  No code generated.
at com.jme.scene.state.lwjgl.LWJGLShaderObjectsState.checkProgramError(Unknown Source)
        at com.jme.scene.state.lwjgl.LWJGLShaderObjectsState.load(Unknown Source)
        at com.jme.scene.state.lwjgl.LWJGLShaderObjectsState.load(Unknown Source)
        at com.gibbon.mfkarpg.terrain.splatting.SplatEnv.createGLSLShader(SplatEnv.java:307)
        at com.gibbon.mfkarpg.terrain.splatting.TerrainPassNode.generate(TerrainPassNode.java:212)
        at com.gibbon.mfkarpg.terrain.splatting.TerrainPassNode.draw(TerrainPassNode.java:263)
        at com.jme.scene.Spatial.onDraw(Unknown Source)
        at com.jme.scene.Node.draw(Unknown Source)
        at com.jme.scene.Spatial.onDraw(Unknown Source)
        at com.jme.scene.Node.draw(Unknown Source)
        at com.jme.scene.Spatial.onDraw(Unknown Source)
        at com.jme.scene.Node.draw(Unknown Source)
        at com.jme.scene.Spatial.onDraw(Unknown Source)
        at com.jme.renderer.lwjgl.LWJGLRenderer.draw(Unknown Source)
        at com.jme.renderer.pass.RenderPass.doRender(Unknown Source)
        at com.jme.renderer.pass.Pass.renderPass(Unknown Source)
        at com.jme.renderer.pass.BasicPassManager.renderPasses(Unknown Source)
        at gamestates.InGameState.render(InGameState.java:110)
        at com.jmex.game.state.GameStateNode.render(Unknown Source)
        at core.Main.render(Main.java:96)
        at com.jme.app.BaseGame.start(Unknown Source)
        at core.Main.main(Main.java:41)



I assume Momoko might have some clue as to what it is?
I'm only experiencing the lag when the program is moving the tiles around, so LOD isn't of much help at that point.

Ok I see, post the code that's "moving the tiles around" then?

But if I keep running back and forth, I get this strange error:

Looks like an incorrectly generated shader.
In the genGLSLShader method inside SplatEnv, there's a System.out.println call to print the shader, uncomment that, and post the output.

My tile loader is OS and located in the User Code section. Here is the specific code that will change a map.

public void changeMap(int x, int y)
    {
        this.x = x;
        this.y = y;
       
        if(Main.class.getClassLoader().getResource(TileManager.mapDirectory + x + "," + y + TileManager.mapExtension) == null)
        {
            myMap = new RawHeightMap(Main.class.getClassLoader().getResource(TileManager.mapDirectory + TileManager.defaultMap + TileManager.mapExtension), TileManager.mapSize + 1, TileManager.mapFormat, true);
        }
        else myMap = new RawHeightMap(Main.class.getClassLoader().getResource(TileManager.mapDirectory + x + "," + y + TileManager.mapExtension), TileManager.mapSize + 1, TileManager.mapFormat, true);
       
        tpass.clearDetails();
        alphas.clear();
        details.clear();
       
        myBlock.setHeightMap(myMap.getHeightMap());
        myBlock.setLocalTranslation(TileManager.getOriginFor(x, y));
        myBlock.updateFromHeightMap();
       
        tpass.setTileScale(150);
       
        base = TextureManager.loadTexture(Main.class.getClassLoader().getResource(TileManager.textureDirectory + "base.jpg"));
       
        tpass.addDetail(base, null);
       
        for(int i=0; i<TileManager.layers; i++)
        {
            if(Main.class.getClassLoader().getResource(TileManager.alphaDirectory + "layer" + (i+1) + "/" + x + "," + y + ".png") != null)
            {
                alphas.add(TextureManager.loadTexture(Main.class.getClassLoader().getResource(TileManager.alphaDirectory + "layer" + (i+1) + "/" + x + "," + y + ".png")));
                details.add(TextureManager.loadTexture(Main.class.getClassLoader().getResource(TileManager.textureDirectory + "layer" + (i+1) + ".jpg")));
            }
        }
       
        for(int i=0; i<details.size(); i++)
        {
            tpass.addDetail(details.get(i), alphas.get(i));
        }
       
        myBlock.updateRenderState();
        tpass.updateRenderState();
    }



Shader:

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0,tex1,tex2;
void main(){

   vec4 r = texture2D(tex0,tcc);
   gl_FragColor = mix(r,texture2D(tex1,tcc),texture2D(tex2,tca).a);
}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0,tex1,tex2,tex3,tex4,tex5,tex6,tex7,tex8;
void main(){

   vec4 r = texture2D(tex0,tcc);
   r = mix(r,texture2D(tex1,tcc),texture2D(tex2,tca).a);
   r = mix(r,texture2D(tex3,tcc),texture2D(tex4,tca).a);
   r = mix(r,texture2D(tex5,tcc),texture2D(tex6,tca).a);
   gl_FragColor = mix(r,texture2D(tex7,tcc),texture2D(tex8,tca).a);
}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0;
void main(){

   gl_FragColor = texture2D(tex0,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0,tex1,tex2,tex3,tex4,tex5,tex6,tex7,tex8;
void main(){

   vec4 r = texture2D(tex0,tcc);
   r = mix(r,texture2D(tex1,tcc),texture2D(tex2,tca).a);
   r = mix(r,texture2D(tex3,tcc),texture2D(tex4,tca).a);
   r = mix(r,texture2D(tex5,tcc),texture2D(tex6,tca).a);
   gl_FragColor = mix(r,texture2D(tex7,tcc),texture2D(tex8,tca).a);
}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0,tex1,tex2,tex3,tex4,tex5,tex6,tex7,tex8;
void main(){

   vec4 r = texture2D(tex0,tcc);
   r = mix(r,texture2D(tex1,tcc),texture2D(tex2,tca).a);
   r = mix(r,texture2D(tex3,tcc),texture2D(tex4,tca).a);
   r = mix(r,texture2D(tex5,tcc),texture2D(tex6,tca).a);
   gl_FragColor = mix(r,texture2D(tex7,tcc),texture2D(tex8,tca).a);
}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0;
void main(){

   gl_FragColor = texture2D(tex0,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0;
void main(){

   gl_FragColor = texture2D(tex0,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0;
void main(){

   gl_FragColor = texture2D(tex0,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0;
void main(){

   gl_FragColor = texture2D(tex0,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0,tex1;
void main(){

   vec4 r = texture2D(tex0,tcc);
   gl_FragColor = texture2D(tex1,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0,tex1;
void main(){

   vec4 r = texture2D(tex0,tcc);
   gl_FragColor = texture2D(tex1,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0,tex1;
void main(){

   vec4 r = texture2D(tex0,tcc);
   gl_FragColor = texture2D(tex1,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0,tex1,tex2;
void main(){

   vec4 r = texture2D(tex0,tcc);
   vec4 r = texture2D(tex1,tcc);
   gl_FragColor = texture2D(tex2,tcc);}


Perhaps I could eliminate the "falling through the earth" feeling if I threaded it and forced it to sleep for 10 milliseconds at various periods… then it would be spread across other stuff and perhaps it would keep my person from running through the ground.

varying vec2 tcc, tca;
uniform sampler2D tex0,tex1,tex2;
void main(){

   vec4 r = texture2D(tex0,tcc);
   vec4 r = texture2D(tex1,tcc);
   gl_FragColor = texture2D(tex2,tcc);
}


This shader is causing the issue, r is defined twice which causes a syntax error. It looks like you're adding more than one base layer. This shouldn't happen though cause BaseLayer checks if the pass is empty..
Make sure the alpha maps you add in your tile loader are not null.
E.g if (alphas.get(i) == null) throw new RuntimeException("Alphamap cannot be null!");


As for the lagging problem with tile loading.. It's best to benchmark the whole changeMap code and see which one is the performance hog. It's an ad-hoc operation. You find the bottleneck and fix it.
Some things that come to mind:
1) Reloading the heightmap from disk each time instead of caching it
2) Not storing the URLs in a variable.. There were some cases where URL lookups were really slow for some user on the forum
3) Recompiling the shaders is a performance hog too, it's best to cache them for each set of detailmaps and only replace the alphamaps in the texture state (see Radakan's TextureSet class under World_Tool)
4) Updating the heightmap of a block is 3 loops: checking if the heightmap changed, updating the height values, then regenerating the normals for the block.
5) Reading PNG images is awfully slow, the awt loader is terrible, and png is a zlib compressed format. JPG is so-so in terms of decompression speed, but still takes some time. The big performance killer is the mipmap generation for both of those types. Ideally you would use DDS with my DDSLoader update: http://www.jmonkeyengine.com/jmeforum/index.php?topic=8869.msg68662#new
Though now that I think about it, once the texture has been uploaded to the card, you should not experience lags regarding potential issue #5.

The things you mentioned are all great–I'll tell you how those go in a little. For right now, I am having some troubles solving my problems regarding the GLSL error.



I was able to get the error to stop appearing if I reset the SplatEnv every time I moved into a new tile. However, if I do that, as I move into new Tiles they are just… brown. No textures or anything. So I figured it would be easier to just remove every detail from the splatEnv, reset the detail list in the tPass, and reset the base in the tpass, then re-add it all while changing maps.


public void clearDetails()
    {
        detail.clear();
        base = null;
        for(Layer l : env.getLayers())
        {
            env.removeLayer(l);
        }
    }



But if I do that, I see the texturing but get the same GLSL error... I don't understand why the base would be added twice-- the alphas are never null...

Just a thought–I see texturing if I have layers other than the base layer added on. Otherwise I see a block that's colored the general color of the base texture but you can't see the base texture.



Any clues?

Wild guess… texture coordinates?

No–the terrain pass is refreshed every time.

Post shader.

I just did something stupid (moreso Netbeans did) and deleted all of my resources, classes, and build.xml… so now I am in the process of rebuilding… I believe everything is as it was before, and here is the shader…

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0;
void main(){

   gl_FragColor = texture2D(tex0,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0;
void main(){

   gl_FragColor = texture2D(tex0,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0;
void main(){

   gl_FragColor = texture2D(tex0,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0;
void main(){

   gl_FragColor = texture2D(tex0,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0;
void main(){

   gl_FragColor = texture2D(tex0,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0;
void main(){

   gl_FragColor = texture2D(tex0,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0;
void main(){

   gl_FragColor = texture2D(tex0,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0;
void main(){

   gl_FragColor = texture2D(tex0,tcc);}

Shader:
varying vec2 tcc, tca;
uniform sampler2D tex0;
void main(){

   gl_FragColor = texture2D(tex0,tcc);}



I fixed it under radakan SVN. Single layers work now, also there's a refresh method in TerrainPass to update the maps.

Any chance you'd be willing to update the TerrainPassNode (written by BlackBlueGL) code to reflect the changes?

It's an other question, but the topic is similar.


  1. If a have a heightmap(picture) or heightpoint(values), i can create a terrain.

    Now I want to convert the terrain back to a heightmap and/or get the values.

    Should be possible, hopefully simple and clean (clean is better!).



    2)

    Now I have a terrain. I want to change single height values, for example

    Point(4|9) = 0; -->  Point(4|9) = 80; (Only example! I know its not gonna work this way)







    To the upper question. Your solution sounds similar to the solution of the game Oblivion.

    Maybe it helps you a little or gives you a good idea.

    First thing, the developers made a difference between objects and terrain.

    Objects are only displayed, if they are in a special radius around you(Depends on your settings).

    Now the more interesting part!

    Let us say, the character has 8 tiles around him plus 1, which he is using. Now I tell you every tile has 9 parts.

    Completly 81 tiles, but this ist to much. Oblivion has only one big tile, so you could say 81 tiles are in a group, but

    unlike a group, this tile is created as one big. So you can move freely around on this 81 tiles. Let us call 81 tiles a tablet.



    [1][2][3]

    [4][5][6]

    [7][8][9]





    Your character cross the border lets say 5 -->8

    Now we would need



    [4][5][6]

    [7][8][9]

    [A][C]



    One trick of oblivion is, that 5 tablets are saved.



    [1][2][3]  [A][X][X]  [X][X][A]  [A][C]  [X][5][X]

    [4][5][6]  [4][5]  [5][6]  [X][2][X]  [X][8][X]

    [7][8][9]  [C][X][X]  [X][X][C]  [X][5][X]  [A][C]



    So the old tablet will be replaced by the new one. Therfore all tablets can be deleted with exception the new and the old tablet. As long the player moves on the surface, new tablets are created in the background (slowly, because there is no hurry - players can run, but not as fast as your processor - and no resources shall be wasted). Because there is only one surface / talbet your enviroment has only one to handle, what shall make it much faster (Depends how big your enviroment is). That's the theory, anyway!



    Further Oblivion use some kind of copy and cut, to create the tablets faster, but I dont know how this work.

    I can only guess.



    Hope it's usefull or gives you an idea