[SOLVED] Smooth a mesh by JME

I’am trying to make the surface of a mesh, which is coming from a 3D sensor, much smoother. How you can see on the image, the left one is my input mesh and I want make it up to looks nicer like the image on the right.
The data are created during the runtime of my programm and therefore it is not possible to make it over in blender or some other programms.
How can I make my model smoother for a better looking?
I hope really someone can help.

Average the normals for vertexes in the same location.

1 Like

Much thanks for your fast reply. Unfortunatelly, I don’t know how I can calculate the average of the normal. I’am thinking my approach is correct. I iterate over all triangles.

	for(int i = 0;i<indexes.length;i+=3) {
		Vector3f n1 = normals[i];
		Vector3f n2 = normals[i+1];
		Vector3f n3 = normals[i+2];
		
		// ???????????????????????????????
		
		normals[i] = n1;
		normals[i+1] = n2;
		normals[i+2] = n3;
	}
	
	mesh.setBuffer(Type.Index, components, BufferUtils.createIntBuffer(indexes));
	mesh.setBuffer(Type.Position, components, BufferUtils.createFloatBuffer(vertices));
	mesh.setBuffer(Type.Normal, components, BufferUtils.createFloatBuffer(normals));
	mesh.updateBound();
	mesh.updateCounts();

It’s quite simple in my theory, but maybe jME already has a tool for it, don’t know.

In my theory you derive the normal at each vertex by:
(1) collect all adjacent triangles in the same counter-clockwise order, the so-called “triangle fan”
(2) calculate the vector product (cross product) for each triangle - gives normal-vectors
(3) normalize (or weight-factoring by triangle size) the normal-vectors
(4) add up all normal-vectors, just sum then up
(5) normalize the resulting vector (divide by length of the sum or use normalizeLocal method)
(6) … repeat for each vertex of the model

You might also look how Blender does it, if you can read python.

You can’t do it here.

If you have flat shaded triangles that means you already have normals… but there are no shared vertexes in your mesh. Each triangle has its own three vertexes… you might as well not even have a vertex index because nothing is shared.

You will have to do as ogli says… find the vertexes that are in the same position and average the normals from all of the triangles that share that position.

There are many ways to do this. The easiest is probably to iterate over the triangles, look up each vertex location in some index where you keep some book-keeping data, add the triangle’s normal to that book-keeping data (keeps track of all normals, the count of normals, etc.)…

Then in the end go back through that index and apply the averaged normals to the vertex normals based on that book-keeping data.

Note for “more correct” averaged normals then you probably want to weight them by edge length and so on but a straight average is better than nothing.

1 Like

I did more or less this for smoothing my custom mesh. Basically you calculate a surface normal for the surrounding planes that share that 1 vertex. Take those normals and average them out. I have code that does this and is a bit easier to understand but even with the statement made by @Ogli and others above you should be able to find the equations you need.

It’s the same method I used to generate this smooth rock: (September 2017) Monthly WIP screenshot thread - #17 by thecyberbob

Also, and I can’t stress this enough, look into the Math for Dummies thing at the top there. It’s SUPER helpful when trying to generate a mesh. In particular look at the add, and normalize functions.

Here is a method that I use to calculate smooth normals given the vertices are shared amongst smooth surfaces. Feel free to use it. :chimpanzee_smile:

    public static FloatBuffer computeNormals(Mesh m) {
        return computeNormals(m.getFloatBuffer(VertexBuffer.Type.Position),
                (ShortBuffer)m.getBuffer(VertexBuffer.Type.Index).getData(),
                m.getFloatBuffer(VertexBuffer.Type.Normal));
    } 
    public static FloatBuffer computeNormals(FloatBuffer pos, ShortBuffer idx, FloatBuffer n) {
        Vector3f v1 = new Vector3f();
        Vector3f v2 = new Vector3f();

        if(n == null) {
		n = BufferUtils.createFloatBuffer(pos.limit());
		n.limit(n.capacity());
	}
	else {
		n.limit(n.capacity());
		n.rewind();
		while(n.hasRemaining()) n.put(0).put(0).put(0);
         }
        idx.rewind();
        int a,b,c;
        while(idx.hasRemaining()) {
            a = (3*Short.toUnsignedInt(idx.get())); b =  (3*Short.toUnsignedInt(idx.get()));
            c = (3*Short.toUnsignedInt(idx.get()));

            v1.x = -pos.get(a);
            v1.y = -pos.get(a+1);
            v1.z = -pos.get(a+2);
            v2.x = pos.get(c)+v1.x;
            v2.y = pos.get(c+1)+v1.y;
            v2.z = pos.get(c+2)+v1.z;
            v1.addLocal(pos.get(b), pos.get(b+1), pos.get(b+2));
            v1.crossLocal(v2);//.normalizeLocal();
            
            n.put(a, n.get(a)+v1.x);
            n.put(a+1, n.get(a+1)+v1.y);
            n.put(a+2, n.get(a+2)+v1.z);
            
            n.put(b, n.get(b)+v1.x);
            n.put(b+1, n.get(b+1)+v1.y);
            n.put(b+2, n.get(b+2)+v1.z);
            
            n.put(c, n.get(c)+v1.x);
            n.put(c+1, n.get(c+1)+v1.y);
            n.put(c+2, n.get(c+2)+v1.z);
        }
        n.rewind();
        while(n.hasRemaining()) {
            v1.set(n.get(n.position()), n.get(n.position()+1), n.get(n.position()+2));
//            System.err.println("normal " + v1);

            v1.normalizeLocal();

            n.put(v1.x).put(v1.y).put(v1.z);
        }
        return n;
    }
3 Likes

If I’m not mistaken, your approach relies on the fact that your vertexes are already shared?

From the flat shaded pic in the first post, I can guarantee that OP does not have shared vertexes this way.

Also note your approach relies on a very specific vertex ordering.

A more general solution would involve iterating over the triangles.

1 Like

Yeah, it’s easy for organized data like terrains or spheres (, cubes, deformed spheres, …).

Not so easy for what we folks call an “(arbitrary) triangle soup”. And that seems to be what you get from your 3D scanner (but I might be wrong, since I only see an image, you could already have organized data).

In a triangle soup you have (v1,v2,v3) for every triangle. You need to identify which triangles connect where (i.e. find vertices with the same coordinates).

It’s good if you get an “indexed triangle mesh”, i.e. what @pspeed is trying to tell - in that case you have “index data” (an index buffer) that tells you “how to connect the dots” and each dot (or vertex) exists only once (you don’t need to find vertices with the same coordinates).

Then you need to identify the “outside” of your mesh, which is what I mean by “collect all adjacent triangles in the same counter-clockwise order”. In Blender, you can let a scan run over the mesh to find this “common outside”, so that all normals point in the same direction. When calculating a vector product (cross product) this order matters - i.e. (v1,v2) x (v2, v3) points into the other direction than (v3,v2) x (v2,v1).

I’m really curious how Blender does find this “common outside” and maybe it would be cool to have that for jME too. But simply copying their python script might not be legal, since they have a different software license which is more strict than the one from jME, if I’m right (GPL vs BSD). But I guess you could find the algorithm behind this on some obscure website (I tend to find the coolest things written for C++). :slight_smile:

@The_Leo Much thanks for your method, unfortunately it doesn’t works for my case. The mesh is black.
I found a video Smooth Shading - YouTube and I’m thinking that’s exactly what I need.

In my understanding I have to search all triangles those are conntected to a certain vertice and then I have to compute the average of the normals and set this normal for each vertice on the edge.

Is there a possiblity to find the triangles for a certain point and how I calculate the average of this normal?

This will be the last time I repeat myself and then you are on your own. I’ve essentially said the solution twice already and now you come back with “Maybe I can do the thing pspeed already said twice…”

Go through each triangle. The mesh gives you the ability to iterate over triangles.

Use an index (hashmap whatever) to look up some Vector3f for the position. Add the normal to that and store it back into the index using the position as the key.

You will now have a hashmap of accumulated normals.

Go through your position and normal buffers. For each position, look up the accumulated normal in the index. Normalize it (Vector3f.normalizeLocal()) and store it into the normal buffer.

Done.

Wonder how much easier your life would be (and how much smaller your meshes would be) if you properly shared vertexes…

Ehm … already told it! :smiley:

Need to analyze your “triangle soup” and find vertices with same coordinates.
Then it’s simple to find all “adjacent triangles” (having the same vertex).
Calculate normals with cross product (caution: the order matters - see below).
Average normals either by simply adding or using weight-factoring.

The big problem comes much later - detecting a “common outside”. It’s not enough to just find the outside for one vertex, but also the “common” outside for your whole mesh. Even the “local outside” for one vertex might be non-trivial to find, since triangles can be interpreted from both sides.

Did you even use it correctly? If yes, it can’t be black if you have a light in a scene. At worst it will be flat shaded.

//If buffer does not exist yet:
mesh.setBuffer(VertexBuffer.Type.Normal, 3, computeNormals(mesh));
//otherwise if buffer exists.
computeNormals(mesh);
mesh.getBuffer(VertexBuffer.Type.Normal).setUpdateNeeded();

Thus, to recap: if your vetexes are not shared you will get Flat shading, If they are shared you will get smooth shading.

Thus if you end up with flat shading, you gotta convert your mesh to share the same vertexes.

No. You just have to duplicate your normals in a different way. Once you know what the normal should be for the non-shared-but-same-position then you just have to set it on all vertexes with that position.

Both approaches work. With your approach the mesh will have more vertexes. Why not share the same vertexes? If he does that then, the method that I posted works with shared vertexes.

He already has an inefficient mesh. He’s not asking how to optimize it (which may be a very difficult problem). He’s asking how to smooth it.

I gave the solution for how to smooth it. He won’t read it though since I’ve basically given that same solution three times now.

His data is coming in from sensors. It may be inefficient to try and optimize the mesh depending on how that data is coming in. And that’s not the question asked. My solution does not increase the size of his mesh at all… just uses what is clearly already there from the picture.

Thanks for your help and your great ideas.
Here is my solution. I think is a really complicated solution but it works.

	public void smoothMesh(Mesh mesh) {
	Map<Vector3f, List<Vector3f>> triangleMap = new HashMap<>();
	for (int i = 0; i < mesh.getTriangleCount(); i++) {
		Triangle tri = new Triangle();
		mesh.getTriangle(i, tri);
		tri.calculateNormal();
		for (int y = 0; y < 3; y++) {
			if (!triangleMap.containsKey(tri.get(y)))
				triangleMap.put(tri.get(y), new ArrayList<>());
			triangleMap.get(tri.get(y)).add(tri.getNormal());
		}
	}

	System.out.println(triangleMap.size());
	Map<Vector3f, Vector3f> normalMap = new HashMap<>();

	float[] positions = BufferUtils.getFloatArray(mesh.getFloatBuffer(Type.Position));
	float[] normals = BufferUtils.getFloatArray(mesh.getFloatBuffer(Type.Normal));
	IndexBuffer indexBuffer = mesh.getIndexBuffer();
	for (int i = 0; i < indexBuffer.size(); i++) {
		int index = indexBuffer.get(i) * 3;
		float x = positions[index];
		float y = positions[index + 1];
		float z = positions[index + 2];
		Vector3f p = new Vector3f(x, y, z);
		if (!normalMap.containsKey(p)) {
			Vector3f averageN = new Vector3f();
			for (Vector3f n : triangleMap.get(p)) {
				averageN.x += n.x;
				averageN.y += n.y;
				averageN.z += n.z;
			}
			averageN = averageN.divide(triangleMap.get(p).size());
			normalMap.put(p, averageN);
		}
		Vector3f n = normalMap.get(p);
		normals[index] = n.x;
		normals[index + 1] = n.y;
		normals[index + 2] = n.z;
	}
	mesh.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals));
}
1 Like

Change that to:
A map of Vector3f, Vector3f.

Change that to:

Vector3f accumulator = triangleMap.get(tri.get(y));
if( accumulator == null ) {
    triangleMap.put(tri.get(y), tri.getNormal().clone());
} else {
    accumulator.addLocal(tri.getNormal());
}

change all of that to:

Vector3f n = triangleMap.get(p);
n.normalizeLocal();

You have a bunch of redundant map lookups and checks when you essentially ignore them anyway. That should simplify the code a bunch and be functionally equivalent.

Ah, the triangles already have normals pointing to the “outside”? That’s good. :slight_smile: Solves many problems. :slight_smile:

Yeah, see the first post where the blob already clearly has normals.