So I'm trying to achieve the "Polygon Art/Low Poly" style with LibGDX. I start by constructing a model made of triangles.

Then with the vertex shader, I calculate the colors for each vertex based on height.

Problem is, the terrain is Gouraud shaded when I want it to be flat shaded like this:

I know that with higher versions of OpenGL there's a "flat" keyword in glsl that will disable the interpolation of colors between vertices. From what I read online and in this post: https://i.stack.imgur.com/DrNx9.jpg , I think that I would need to have each triangle in the terrain be separate from each other? I would also need to calculate the normal per triangle? I couldn't understand the code in the other StackOverflow, but this is what I tried to do:
Original
public Model getWorld(){
returnWorld = new Model();
modelBuilder = new ModelBuilder();
modelBuilder.begin();
worldMeshBuilder = modelBuilder.part("worldPart", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, new Material());
pieceMeshBuilder = new MeshBuilder();
meshPiece = new Mesh(false, 3, 3,
new VertexAttribute(Usage.Position, 3, "a_position"),
new VertexAttribute(Usage.Normal, 3, "a_normal"),
new VertexAttribute(Usage.ColorPacked, 4, "a_color"));
Vector3 vectorCopy = new Vector3();
for(int i = 0; i < world.length - 1; i++){
for(int j = 0; j < world[0].length - 1; j++){
if((i + j) % 2 == 0){
pieceMeshBuilder.begin(Usage.Position | Usage.Normal, renderType);
pieceMeshBuilder.triangle(
vectorCopy = verticies[i][j],
vectorCopy = verticies[i][j + 1],
vectorCopy = verticies[i + 1][j + 1]
);
worldMeshBuilder.addMesh(pieceMeshBuilder.end());
pieceMeshBuilder.begin(Usage.Position | Usage.Normal, renderType);
pieceMeshBuilder.triangle(
vectorCopy = verticies[i + 1][j + 1],
vectorCopy = verticies[i + 1][j],
vectorCopy = verticies[i][j]
);
worldMeshBuilder.addMesh(pieceMeshBuilder.end());
} else {
pieceMeshBuilder.begin(Usage.Position | Usage.Normal, renderType);
pieceMeshBuilder.triangle(
vectorCopy = verticies[i][j],
vectorCopy = verticies[i][j + 1],
vectorCopy = verticies[i + 1][j]
);
worldMeshBuilder.addMesh(pieceMeshBuilder.end());
pieceMeshBuilder.begin(Usage.Position | Usage.Normal, renderType);
pieceMeshBuilder.triangle(
vectorCopy = verticies[i + 1][j + 1],
vectorCopy = verticies[i + 1][j],
vectorCopy = verticies[i][j + 1]
);
worldMeshBuilder.addMesh(pieceMeshBuilder.end());
}
}
}
returnWorld = modelBuilder.end();
return returnWorld;
}
Now:
public Model getWorld(){
returnWorld = new Model();
modelBuilder = new ModelBuilder();
modelBuilder.begin();
worldMeshBuilder = modelBuilder.part("worldPart", GL20.GL_LINES, Usage.Position | Usage.Normal, new Material());
for(int i = 0; i < world.length - 1; i++){
for(int j = 0; j < world[0].length - 1; j++){
Vector3 normal1 = calcNormal(verticies[i][j], verticies[i + 1][j], verticies[i + 1][j + 1]);
Vector3 normal2 = calcNormal(verticies[i][j], verticies[i + 1][j + 1], verticies[i][j + 1]);
if((i + j) % 2 == 0){
meshPiece = new Mesh(false, 18, 3,
new VertexAttribute(Usage.Position, 3, "a_position"),
new VertexAttribute(Usage.Normal, 3, "a_normal")//,
//new VertexAttribute(Usage.ColorPacked, 4, "a_color")
);
worldMeshBuilder.addMesh(meshPiece.setVertices(new float[] {
verticies[i][j].x, verticies[i][j].y, verticies[i][j].z, normal1.x, normal1.y, normal1.z,
verticies[i + 1][j].x, verticies[i + 1][j].y, verticies[i + 1][j].z, normal1.x, normal1.y, normal1.z,
verticies[i + 1][j + 1].x, verticies[i + 1][j + 1].y, verticies[i + 1][j + 1].z, normal1.x, normal1.y, normal1.z,
}));
meshPiece = new Mesh(false, 18, 3,
new VertexAttribute(Usage.Position, 3, "a_position"),
new VertexAttribute(Usage.Normal, 3, "a_normal")//,
//new VertexAttribute(Usage.ColorPacked, 4, "a_color")
);
worldMeshBuilder.addMesh(meshPiece.setVertices(new float[] {
verticies[i][j].x, verticies[i][j].y, verticies[i][j].z, normal2.x, normal2.y, normal2.z,
verticies[i + 1][j + 1].x, verticies[i + 1][j + 1].y, verticies[i + 1][j + 1].z, normal2.x, normal2.y, normal2.z,
verticies[i][j + 1].x, verticies[i][j + 1].y, verticies[i][j + 1].z, normal2.x, normal2.y, normal2.z,
}));
} else {
meshPiece = new Mesh(false, 18, 3,
new VertexAttribute(Usage.Position, 3, "a_position"),
new VertexAttribute(Usage.Normal, 3, "a_normal")//,
//new VertexAttribute(Usage.ColorPacked, 4, "a_color")
);
worldMeshBuilder.addMesh(meshPiece.setVertices(new float[] {
verticies[i][j].x, verticies[i][j].y, verticies[i][j].z, normal1.x, normal1.y, normal1.z,
verticies[i + 1][j].x, verticies[i + 1][j].y, verticies[i + 1][j].z, normal1.x, normal1.y, normal1.z,
verticies[i][j + 1].x, verticies[i][j + 1].y, verticies[i][j + 1].z, normal1.x, normal1.y, normal1.z,
}));
meshPiece = new Mesh(false, 18, 3,
new VertexAttribute(Usage.Position, 3, "a_position"),
new VertexAttribute(Usage.Normal, 3, "a_normal")//,
//new VertexAttribute(Usage.ColorPacked, 4, "a_color")
);
worldMeshBuilder.addMesh(meshPiece.setVertices(new float[] {
verticies[i + 1][j].x, verticies[i + 1][j].y, verticies[i + 1][j].z, normal2.x, normal2.y, normal2.z,
verticies[i + 1][j + 1].x, verticies[i + 1][j + 1].y, verticies[i + 1][j + 1].z, normal2.x, normal2.y, normal2.z,
verticies[i][j + 1].x, verticies[i][j + 1].y, verticies[i][j + 1].z, normal2.x, normal2.y, normal2.z,
}));
}
}
}
returnWorld = modelBuilder.end();
return returnWorld;
}
Problem is the new code isn't rendering anything... I have looked at the API for ModelBuilder, MeshBuilder, Mesh, and VertexAttribute/s but I can't figure out why it isn't working. Any help would be great as this has been a very frustrating day. Thank so much!
Flat and smooth shading is usually determined by vertex normals and how colors are interpolated across the face.
Typically in a smooth model the normals at each vertex are averaged for each face point at the same location. The normal at each shared vertex location is the same for each face. This is what makes the lighting smooth because there are no abrupt changes at edges.
In a flat shaded model vertex normals do not match adjacent faces and instead each vertex normals is the same as face normal. This creates abrupt changes in the normal value at the edge.
This can typically be modified in whatever 3D modeling package you use or by slightly modifying your normal generation code if the terrain is generated procedurally.
The following picture shows the difference between flat and smooth normals. The darker blue lines represent the average smoothed normals and the lighter (cyan) lines represent the harsh face normal.
You should be able to draw all the triangles together in one draw call. You do not need to split the mesh.