Multiple textures on a terrain

Started by AceGIS, August 24, 2012, 06:26:26 AM

Previous topic - Next topic

AceGIS

Hi All,

I have a .ser model that I am loading onto a GLSurfaceView for an Augmented Reality app I am building. So far I am using a single texture. I would like to use different textures based on the height of the polygon in my model.

What is the best way to approach this?

Thanks in advance...

EgonOlsen

Simple and ugly:


  • Use different textures per polygon based on the vertices height.
  • This will create sharp and jaggy edges between the different textures.

Simple, not so ugly but maybe not applicable:


  • Use one larger texture that contains the different parts and covers the whole terrain.
  • Either texture the terrain with only that texture or use multi-texturing and blend it with the base texture.
  • This will (depending on the texture's quality) create smooth transitions, but the result might be too coarse or blurry.

Slow and buggy on some devices:

  • Render two versions of the same mesh with different textures.
  • One contains the base texture, the other the upper and lower parts (i.e. rocks and sand) and use some vertex-alpha to blend to transparency in between.
  • This is slow (it cuts your framerate in half) and simply doesn't work on some devices because the vertex alpha doesn't work there.

Best solution, but a little more complex and it requires OpenGL ES 2.0:


  • Use multitexturing to apply ground, rocks, sand...and a splatting texture all to the same mesh.
  • Use a custom shader to read from the splatting texture and mix the other textures based on that value.
  • Looks good, is very flexible and runs pretty fast. You have to write the shader though.
  • Related: http://www.jpct.net/forum2/index.php/topic,2912.msg21431.html#msg21431

Thomas.

#2
And you can use alpha channel of each texture to save one more material without any performance drop. It needs just a few extra lines of code and ITextureEffect interface ;)

AceGIS

Hi Egon,

Thank you for the very informative answer.

Could you please show a snippet demonstrating how I might use Simple and ugly (sharp edges are OK, jaggy edges I will have to see). Lets say I have 20 - 40 textures that I want to load only when required (runtime), how would I then check each polygon in the camera FOV and draw the correct texture? OR would it be more efficient (for a static model) to load all textures for the entire model based on polygon height?

I am currently using OpenGL ES 2.0, therefore could you please also show a snippet using the Best solution method. My terrain model will be the only object in my world. I am pretty new to 3D modelling. Could you please explain splatting textures?? Oh and how to use a custom shader??

Thanks again Egon.

EgonOlsen

Why do you need 20-40 textures? That's quite a lot and unless they are very small, you'll run into memory problems with that. Anyway, you can't assign them dynamically at runtime (well, you can...but it's complicated and kills performance, so there's no point in doing it). Just assign them at build time. You are creating the object anyway, so why not assign the correct texture based on the current height in the process!?

A custom shader can be created by creating an instance of GLSLShader and assign to an Object3D. For that, you have to write the shader code in glsl...this the shader that i'm using for the terrain:

vertex shader
attribute vec2 texture0;
attribute vec2 texture1;
attribute vec2 texture2;
attribute vec2 texture3;

uniform vec4 additionalColor;
uniform vec4 ambientColor;

uniform mat4 modelViewMatrix;
uniform mat4 modelViewProjectionMatrix;
uniform mat4 textureMatrix;

uniform float fogStart;
uniform float fogEnd;
uniform vec3 fogColor;

uniform vec3 lightPositions[8];
uniform vec3 diffuseColors[8];
uniform vec3 specularColors[8];

uniform float shininess;

attribute vec4 position;
attribute vec3 normal;

varying vec2 texCoord0;
varying vec2 texCoord1;
varying vec2 texCoord2;
varying vec2 texCoord3;

varying vec4 vertexColor;
varying vec4 fogVertexColor;

const vec4 WHITE = vec4(1.0,1.0,1.0,1.0);

void main(void)
{
texCoord0=texture0;
texCoord1=texture1*0.5;
texCoord2=texture2;
texCoord3=texture3;

vertexColor = additionalColor+ambientColor;

vec3 vVertex = vec3(modelViewMatrix * position);
vec3 normalEye   = normalize(modelViewMatrix * vec4(normal, 0.0)).xyz;
float angle = dot(normalEye, normalize(lightPositions[0] - vVertex));

if (angle > 0.0) {
vertexColor += vec4(diffuseColors[0] * angle + specularColors[0] * pow(angle, shininess),0.0);
}

vertexColor=2.0*vec4(min(WHITE, vertexColor).xyz, 0.5);

float fogWeight = clamp((-vVertex.z - fogStart) / (fogEnd - fogStart), 0.0, 1.0);
fogVertexColor = vec4(fogColor, 0.0) * fogWeight;
fogWeight=1.0-fogWeight;

vertexColor*=fogWeight;
gl_Position = modelViewProjectionMatrix * position;
}


fragment shader
precision highp float;

uniform sampler2D textureUnit0;
uniform sampler2D textureUnit1;
uniform sampler2D textureUnit2;
uniform sampler2D textureUnit3;

varying vec2 texCoord0;
varying vec2 texCoord1;
varying vec2 texCoord2;
varying vec2 texCoord3;

varying vec4 vertexColor;
varying vec4 fogVertexColor;

void main (void)
{
vec4 col0 = texture2D(textureUnit0, texCoord0);
vec4 col1 = texture2D(textureUnit1, texCoord1);
vec4 col2 = texture2D(textureUnit2, texCoord2);
vec4 col3 = col2*0.4;
vec4 blend = texture2D(textureUnit3, texCoord3);

float w1=blend.r;
float w2=blend.g;
float w3=blend.b;

gl_FragColor = vertexColor * vec4(((1.0-w1)*(1.0-w2)*(1.0-w3)*col0+col1*w1+col2*w2+col3*w3).rgb, 0.0) + fogVertexColor;
}


If you are a beginner in 3d, i suggest to stay away from shaders if possible. Anyway, a splatting texture is a texture that defines the distribution of other textures on an object based on it's own rgb values. You can see that in the fragment shader posted above. In your case, you might not need this, because you could simply use the height instead.


AceGIS

Hi Egon,

OK. I think the shaders may be over my head for now.  ;)

Do you have a snippet showing how to loop through the models polygons and assign textures to different height ranges (10m - 50m = texture A, 51m - 100m = texture B, ......). I think I will be able to reduce the number of textures down to around 5 - 10.

Also, do you have any idea on how I can convert the jPCT-AE world coordinate system into Lat/Long so I can place my model where it belongs in the "real world"? I will then also need to work out how to place the jPCT-AE camera at the devices "real world" coordinate from the GPS.

In summary, my idea is to place the model in it's "real world" location, then place the camera at the GPS location to enable the user to "look" around the model by moving the device (using the compass for direction) so long as the device is in the same location as the model.

Thanks Egon.

Thomas.

Maximum number of texture layers is 4, you can not use 5-10 textures...

EgonOlsen

#7
Quote from: Thomas. on August 29, 2012, 04:03:57 PM
Maximum number of texture layers is 4, you can not use 5-10 textures...
You can. You just can't use them all on one polygon. jPCT allows you to use as many textures as you wish (and memory permits) on one object...just not more than 4 on one polygon. The object compiler takes care of the splitting and batching that it needed to render these objects.

EgonOlsen

Quote from: AceGIS on August 29, 2012, 05:55:57 AM
Do you have a snippet showing how to loop through the models polygons and assign textures to different height ranges (10m - 50m = texture A, 51m - 100m = texture B, ......). I think I will be able to reduce the number of textures down to around 5 - 10.

I don't think that you have to loop through them. How are you creating the object? Based on your height map using Object3D.addTriangle or are you converting your data into some supported file format and load that? In the latter case, you can use the PolygonManager to determine the height (getTransformedVertex) and set the new texture (setPolygonTexture). Just make sure that you are doing this before calling build() or compile() on that object.

Quote from: AceGIS on August 29, 2012, 05:55:57 AM
Also, do you have any idea on how I can convert the jPCT-AE world coordinate system into Lat/Long so I can place my model where it belongs in the "real world"? I will then also need to work out how to place the jPCT-AE camera at the devices "real world" coordinate from the GPS.
Sorry, no idea...

Thomas.

#9
Quote from: EgonOlsen on August 29, 2012, 09:50:40 PM
Quote from: Thomas. on August 29, 2012, 04:03:57 PM
Maximum number of texture layers is 4, you can not use 5-10 textures...
You can. You just can't use them all on one polygon. jPCT allows you to use as many textures as you wish (and memory permits) on one object...just not more than 4 on one polygon. The object compiler takes care of the splitting and batching that it needed to render these objects.

Oh, are these data loaded from OBJ? How is it working, or how is it called? I have to find the way to define it in 3Ds max :)

EgonOlsen

Quote from: Thomas. on August 29, 2012, 10:23:14 PM
Oh, are these data loaded from OBJ? How is it working, or how is it called? I have to find the way to define it in 3Ds max :)
Sorry for editing your post..it was an accident...i actually wanted to remove my typo in MY post not in your quote...now, both are fixed... ;)
Anyway, i'm not sure if i get the question. You can assign as many textures as you like and that includes textures defined in materials from obj or 3ds.

AceGIS

Hi Egon,

I am converting an Ascii GRID into a 3D Studio .3ds then serializing using the mesh serializer plugin.

So I need to access the polygon manager of my object, then using getTransformedVertex, for each vertex = some height, setPolygonTexture. BEFORE build().

Thanks again Egon..

AceGIS

Hi Egon,

I have succeeded with the Simple and ugly method. ;)


dem1 = Loader.loadSerializedObject(res.openRawResource(R.raw.jcu_lidar_gcs_0));
PolygonManager dem1PolyManager = dem1.getPolygonManager();
Logger.log("Max poly ID DEM1: " + dem1PolyManager.getMaxPolygonID());

for (int i = 0; i < dem1PolyManager.getMaxPolygonID(); i++) {
for (int j = 0; j < 3; j++) {
//Logger.log("Transformed vertex:" + i + "-" + j + " " + dem1PolyManager.getTransformedVertex(i, j).toString());
if (dem1PolyManager.getTransformedVertex(i, 0).z > 0 && dem1PolyManager.getTransformedVertex(i, 0).z < 50) {
dem1PolyManager.setPolygonTexture(i, tm.getTextureID("grey_texture"));
}

else if (dem1PolyManager.getTransformedVertex(i, 0).z > 50 && dem1PolyManager.getTransformedVertex(i, 0).z < 100) {
dem1PolyManager.setPolygonTexture(i, tm.getTextureID("green_texture"));
}

else if (dem1PolyManager.getTransformedVertex(i, 0).z > 100 && dem1PolyManager.getTransformedVertex(i, 0).z < 200) {
dem1PolyManager.setPolygonTexture(i, tm.getTextureID("blue_texture"));
}

else if (dem1PolyManager.getTransformedVertex(i, 0).z > 200 && dem1PolyManager.getTransformedVertex(i, 0).z < 300) {
dem1PolyManager.setPolygonTexture(i, tm.getTextureID("purple_texture"));
}

else if (dem1PolyManager.getTransformedVertex(i, 0).z > 300 && dem1PolyManager.getTransformedVertex(i, 0).z < 400) {
dem1PolyManager.setPolygonTexture(i, tm.getTextureID("red_texture"));
}

else {
dem1PolyManager.setPolygonTexture(i, tm.getTextureID("yellow_texture"));
}
}
}


I am now working on a way to blend the different textures to reduce the jagged edges.

Also, could you please explain how to use the tranformToGL() method. My model is in OpenGL coords, therefore it is on it's side... :o

Thanks for your help.

AceGIS

Screen shot attached showing the models orientation on load.

[attachment deleted by admin]

AceGIS

Screen shot showing correct orientation

[attachment deleted by admin]