Heh guys,

again i got a question about flat shading, and its simple, how can i get this terrain look like this monkey object? I cant fix it and im running out of ideas. Im using Lighting.j3md on both of them.

Itâ€™s not a function of the shader but of the geometry. Flat shaded triangles have all three of their normals pointing in the same direction. There is really no other way to do it.

You could also use a geometry shader with triangle input. Then you can just compute the normal inside the shader.

I know that the normals of each polygon (in terrain case triangles) need to face in the same direction, but i dont know how to â€śconfigureâ€ť them being set like this and not being added to the normals vectors of the polygons beside. If you could give me a really short example, a piece of code, a method, a whatever that would let the normals face that way? Would be a great help to me!

Well, I had no idea where you got your terrain from since that wasnâ€™t mentioned in the original post. I guess from your recent post that you are using JMEâ€™s terrain?

If so then there isnâ€™t really any way without writing a geometry shader because JMEâ€™s terrain uses triangle strips. These inherently share vertexes from one triangle to the next. Anyway, for any smooth mesh youâ€™d have to rework both the vertexes and the normals because smooth shaded meshes almost always share vertexesâ€¦ and you canâ€™t share vertexes if they each should have their own normals. I canâ€™t really provide code for this because itâ€™s 1000 times easier to start from the beginning and generate a proper mesh in the first place. If it were me and I didnâ€™t want to use geometry shaders then Iâ€™d either write my own terrain generator from scratch or fork and hack up the JME one.

You can also do this without geometry shaders, by using the flat keyword in the shader, e.g:

``````varying flat vec3 vNormal;
``````

This causes the value in the fragment shader to be the result from the last vertex in the triangle, instead of an interpolated value.

Note that OpenGL 3.0 or higher is required for this.

1 Like

Note also that the normal will be pointing in the wrong direction for the triangle.

No such thing as free lunch, I guess.

Hi guys,

i used the solution pspeed offered me and i wanted to share it with you for people having the same problem, this class simply generates a mesh out of a heightmap with the given side-length of the heightmap. Its not coded the most efficient way and there is a lot that could be more serialized, but still i hope this will help someone. Putting the standard mountains128.png as a imagebasedheightmap, a bit scaled, into it generates something looking like this:

``````package terrain;

import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;

/**
*
* @author Simon
*/
public class FlatTerrain extends Geometry {
float[] heightmap;
int size;
private static final int SCALE = 1;

public FlatTerrain(float[] heightmap, int size) {
this.mesh = new Mesh();
this.heightmap = heightmap;
this.size = size;

generateMesh();
}

private void generateMesh() {
Vector3f[] vertices = new Vector3f[(heightmap.length * 6) - ((((size * 4) - 4) * 3) + 6)];
int[] indexes  = new int[(heightmap.length * 6) - ((((size * 4) - 4) * 3) + 6)];
float[] normals = new float[((heightmap.length * 6) - ((((size * 4) - 4) * 3) + 6)) * 3];

for(int i = 0; i < (heightmap.length * 6) - ((((size * 4) - 4) * 3) + 6); i++) {
indexes[i] = i;
}

for(int y = 0; y < size - 1; y++) {
for(int x = 0; x < size - 1; x++) {
vertices[((y * (size - 1)) + x) * 6] = new Vector3f((x * SCALE), heightmap[y * size + x], (y * SCALE));
vertices[((y * (size - 1)) + x) * 6 + 1] = new Vector3f((x * SCALE), heightmap[(y + 1) * size + x], (y * SCALE) + SCALE);
vertices[((y * (size - 1)) + x) * 6 + 2] = new Vector3f((x * SCALE) + SCALE, heightmap[y * size + (x + 1)], (y * SCALE));
vertices[((y * (size - 1)) + x) * 6 + 3] = new Vector3f((x * SCALE), heightmap[(y + 1) * size + x], (y * SCALE) + SCALE);
vertices[((y * (size - 1)) + x) * 6 + 4] = new Vector3f((x * SCALE) + SCALE, heightmap[(y + 1) * size + (x + 1)], (y * SCALE) + SCALE);
vertices[((y * (size - 1)) + x) * 6 + 5] = new Vector3f((x * SCALE) + SCALE, heightmap[y * size + (x + 1)], (y * SCALE));

Vector3f normal_1 = new Vector3f();
Vector3f normal_2 = new Vector3f();

Vector3f site_1 = vertices[((y * (size - 1)) + x) * 6].subtract(vertices[((y * (size - 1)) + x) * 6 + 1]);
Vector3f site_2 = vertices[((y * (size - 1)) + x) * 6].subtract(vertices[((y * (size - 1)) + x) * 6 + 2]);
Vector3f site_3 = vertices[((y * (size - 1)) + x) * 6 + 3].subtract(vertices[((y * (size - 1)) + x) * 6 + 4]);
Vector3f site_4 = vertices[((y * (size - 1)) + x) * 6 + 3].subtract(vertices[((y * (size - 1)) + x) * 6 + 5]);

normal_1 = site_1.cross(site_2, normal_1);
normal_2 = site_3.cross(site_4, normal_2);

normals[((y * (size - 1)) + x) * 18] = normal_1.getX();
normals[((y * (size - 1)) + x) * 18 + 1] = normal_1.getY();
normals[((y * (size - 1)) + x) * 18 + 2] = normal_1.getZ();

normals[((y * (size - 1)) + x) * 18 + 3] = normal_1.getX();
normals[((y * (size - 1)) + x) * 18 + 4] = normal_1.getY();
normals[((y * (size - 1)) + x) * 18 + 5] = normal_1.getZ();

normals[((y * (size - 1)) + x) * 18 + 6] = normal_1.getX();
normals[((y * (size - 1)) + x) * 18 + 7] = normal_1.getY();
normals[((y * (size - 1)) + x) * 18 + 8] = normal_1.getZ();

normals[((y * (size - 1)) + x) * 18 + 9] = normal_2.getX();
normals[((y * (size - 1)) + x) * 18 + 10] = normal_2.getY();
normals[((y * (size - 1)) + x) * 18 + 11] = normal_2.getZ();

normals[((y * (size - 1)) + x) * 18 + 12] = normal_2.getX();
normals[((y * (size - 1)) + x) * 18 + 13] = normal_2.getY();
normals[((y * (size - 1)) + x) * 18 + 14] = normal_2.getZ();

normals[((y * (size - 1)) + x) * 18 + 15] = normal_2.getX();
normals[((y * (size - 1)) + x) * 18 + 16] = normal_2.getY();
normals[((y * (size - 1)) + x) * 18 + 17] = normal_2.getZ();
}
}

this.mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
this.mesh.setBuffer(Type.Index, 3, BufferUtils.createIntBuffer(indexes));
this.mesh.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals));

this.mesh.updateBound();
}
}
``````

PS: Vertex Colors can be added, i already tried it but removed it again for someone who might not need them.

2 Likes

Looks nice.

Edit: would also be cool to add some AO maybe. Though I guess the terrain is dramatic enough for that to really show.