MD5 Loaded Trimesh Smooth Shading

I'm having trouble doing smooth shading on a model I load from an MD5 file using the kproject MD5Reader.  I wrote a short test (below). ShadeState works fine, FLAT or SMOOTH, if I render a primitive shape, a Torus for example, but doesn't do anything on the loaded model. It's just flat either way. Anyone has an idea what would be the way to go?



The MD5 file comes from a Blender model I exported with the Blender2md5 plugin.



Thanks



import com.jme.light.DirectionalLight;
import com.jme.light.PointLight;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jmex.editors.swing.settings.GameSettingsPanel;
import com.jmex.game.StandardGame;
import com.jmex.game.state.GameStateManager;
import com.jmex.game.state.DebugGameState;
import com.jme.scene.Node;
import com.jme.scene.shape.Torus;
import com.jme.scene.state.LightState;
import com.jme.scene.state.ZBufferState;
import com.jme.scene.state.ShadeState;

import gr.kproject.md5loader.animation.MD5Animation;
import gr.kproject.md5loader.jme.MD5JmeNode;
import gr.kproject.md5loader.mesh.MD5Model;
import gr.kproject.md5loader.reader.MD5AnimationReader;
import gr.kproject.md5loader.reader.MD5Reader;

public class TestModel {
   private static final String displayTitle = "Test Model";
   
   private static StandardGame game;
   private static DebugGameState gameState;
   
   private static Node world = null;
   private static MD5Model model = null;
   //MD5Animation animation = null;
   private static MD5JmeNode modelNode = null;
   
   private static int fps = 25;
   
   public static void main(String[] args) {
      parseArguments(args);
      
      try {
         game = new StandardGame(displayTitle);
         GameSettingsPanel.prompt(game.getSettings());
         game.getSettings().setFramerate(fps);
         game.start();   // Start the game thread
         
      } catch (InterruptedException ie) {
         System.out.println(ie.toString());
         System.exit(0);
      }
         
        //Create, Attach and Active Game State
        gameState = new DebugGameState();
        GameStateManager.getInstance().attachChild(gameState);
        gameState.setActive(true);
       
        initGame();
        gameState.getRootNode().attachChild(modelNode);
   }
 
   private static void initGame() {
      //set the background
      game.setBackgroundColor(ColorRGBA.black);      

      //get the camera
      Camera cam = game.getCamera();
      
      //set rendering phase
      gameState.getRootNode().setRenderQueueMode(Renderer.QUEUE_OPAQUE);
      
      world = new Node("World");
      
      initZBuffer();
      initLighting();
      initModel();

      //Node torusNode = new Node();
        //Torus torus = new Torus("shadedTorus", 128, 32, 3.0f, 5.0f);
        //torusNode.attachChild(torus);
        //world.attachChild(torusNode);

        //try to smooth shade it
   ShadeState ss = game.getDisplay().getRenderer().createShadeState();
        ss.setShade(ShadeState.SM_SMOOTH);
        modelNode.setRenderState(ss);
       
        world.attachChild(modelNode);
       
      //update the scene graph for rendering
      world.updateGeometricState(0.0f, true);
      world.updateRenderState();
      
      //attach the habitat
      gameState.getRootNode().attachChild(world);
   }
   
   // parse the argument from the command line
   public static void parseArguments(String args[]) {
      for(int i = 0; i < args.length; i++) {
         if( args[i].equals("") )
         {
         }
      }
   }
   
   public static void initModel() {
      model = MD5Reader.readMeshFile("models/plants/heliconia/heliconiaFlowered2.md5mesh");
      //animation = MD5AnimationReader.readAnimFile(animFile);
      modelNode = new MD5JmeNode("Model", model);
      //world.attachChild(modelNode);
   }

   /**
    * Initialize the lighting.
    */
   public static void initLighting() {      
      // The light that cast shadows.
      DirectionalLight dr = new DirectionalLight();
      dr.setEnabled(true);
      dr.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      dr.setAmbient(new ColorRGBA(.6f, .6f, .5f, .5f));
      dr.setDirection(new Vector3f(0.5f, -0.8f, 0.5f).normalizeLocal());
      dr.setShadowCaster(true);
 
      // Another light so there is no full darkness.
      PointLight pl = new PointLight();
      pl.setEnabled(true);
      pl.setDiffuse(new ColorRGBA(.2f, .2f, .23f, 1.0f));
      pl.setAmbient(new ColorRGBA(.25f, .25f, .28f, .1f));
      pl.setLocation(new Vector3f(20, 20, 20));
      
      // Attach the lights
       LightState lightState = game.getDisplay().getRenderer().createLightState();
       lightState.setEnabled(true);
      lightState.detachAll();
      lightState.attach(dr);
      lightState.attach(pl);
      lightState.setGlobalAmbient(new ColorRGBA(0.8f, 0.8f, 0.8f, 1.0f));
       world.setRenderState(lightState);      
   }

   
   /**
    * Initialize the ZBuffer.
    */
   private static void initZBuffer() {
      ZBufferState buf = game.getDisplay().getRenderer().createZBufferState();
       buf.setEnabled(true);
       buf.setFunction(ZBufferState.CF_LEQUAL);
       world.setRenderState(buf);
   }   
}


Here’s a screenshot of the model.



Generally smooth shading is something that is set on the model, also make sure the model is set to smooth in blender.



(Side note: blender tutorials generally have WAY too many vertices, 11,000 vertices seems pretty high)

I'm still well and truly new to all this, so sorry if this is way off the mark… but when I tried this is turned out that the lack of lighting in the scene was the problem.  If you are using the model loader example, I think if you hit the L key it switches between lights and basic material display.  Its probably something far more complex but I thought I'd mention that anyway just in case.

this is a normal problem. turning off lighting means do not use normal, so u hide the problem but not fix it.



u need to smooth ur model's normals inside the modeling tool u r using. right now, it seems like the normals r set to face where they should be set to vertext.

Normals are not exported in to MD5 format (this is in MD5 format specification). So smoothing normals in to Blender does not affect the exported model.



This is because Doom 3 uses massively Normal Maps.

basixs said:

Generally smooth shading is something that is set on the model, also make sure the model is set to smooth in blender.

(Side note: blender tutorials generally have WAY too many vertices, 11,000 vertices seems pretty high)


What do you think, would be a good approach to reduce those 11K vertices to a more decent amount?
Ender said:

Normals are not exported in to MD5 format (this is in MD5 format specification). So smoothing normals in to Blender does not affect the exported model.

This is because Doom 3 uses massively Normal Maps.


I just figured that one out when looking at the MD5 specs and how the MD5Reader loads the model. It calculates the normal when it load the model, and at first sight, it seems to calculate vertex normals by averaging the normals of the faces each vertex is a part of. (code below from MD5RenderMesh class)

About smoothing the mesh, I can rephrase the question to: how do you go about generate a TriMesh with the correct normals to render it smooth?


public FloatBuffer computeNormals(int numvert,FloatBuffer vertexBuffer,Vector<MD5Triangle> triangle) {
    Vector3f vector1 = new Vector3f();
    Vector3f vector2 = new Vector3f();
    Vector3f vector3 = new Vector3f();
       
    // Get the current object
       
    // Here we allocate all the memory we need to calculate the normals
    Vector3f[] tempNormals = new Vector3f[triangle.size()];
    Vector3f[] normals = new Vector3f[numvert];
    int numtris=triangle.size();
    // Go though all of the faces of this object
    for (int i = 0; i < numtris; i++) {
        BufferUtils.populateFromBuffer(vector1, vertexBuffer, triangle.elementAt(i).triangle[0]);
        BufferUtils.populateFromBuffer(vector2, vertexBuffer, triangle.elementAt(i).triangle[1]);
        BufferUtils.populateFromBuffer(vector3, vertexBuffer, triangle.elementAt(i).triangle[2]);
           
        vector1.subtractLocal(vector3);
           
        tempNormals[i] = vector1.cross(vector3.subtract(vector2)).normalizeLocal();
    }
       
    Vector3f sum = new Vector3f();
    int shared = 0;
      
    for (int i = 0; i < numvert; i++) {
        for (int j = 0; j < numtris; j++) {
            if (triangle.elementAt(j).triangle[0] == i
                    || triangle.elementAt(j).triangle[1] == i
                    || triangle.elementAt(j).triangle[2] == i) {
                sum.addLocal(tempNormals[j]);
                   
                shared++;
            }
        }
           
        normals[i] = sum.divide((float) (-shared)).normalizeLocal();
         
        sum.zero(); // Reset the sum
        shared = 0; // Reset the shared
    }
       
    return BufferUtils.createFloatBuffer(normals);               
}

Darklord said:

basixs said:

Generally smooth shading is something that is set on the model, also make sure the model is set to smooth in blender.

(Side note: blender tutorials generally have WAY too many vertices, 11,000 vertices seems pretty high)


What do you think, would be a good approach to reduce those 11K vertices to a more decent amount?


The model isn't a demo from somewhere btw. It's my own model, so its easy to get rid of vertices in Blender. I'm not too worries about the large count for now cause I'm not building a game with a large world.

Unfortunately removing vertices is a LOT tricker than adding them; maybe just continue on modeling but strive from the beginning to create low-polys. 

basixs said:

Unfortunately removing vertices is a LOT tricker than adding them; maybe just continue on modeling but strive from the beginning to create low-polys. 


Im new to the modelling and am indeed trying to keep poly count low however, the subsurf function in Blender tends to make a vertice/poly fest out of it.  :D

I have a hard time judging how many poly's/vertices are considered "much". For example I made my own custom target board which consists out of 1500 vertices and 1900 poly's (mainly to make it look curved).
How much would a onboard chipset/budget videocard be able to render?

Its hard to answer but I hope to get a range.. would 0-10K be fine? 10-20k? more? less?

1 to ~5000 can be considered low poly.



~5000 to ~20000 can be considered mid poly.



above is ~ high poly. Though, generally we use to talk of high poly with 1M polygons.



Blender has a mesh polygon reducer tool. Try it.



Another approach for lowering polygons can be Retopo. Search Blender documentation about Retopo. You can redesign mesh topology using it. It is a semiautomatic technique.


brown said:

About smoothing the mesh, I can rephrase the question to: how do you go about generate a TriMesh with the correct normals to render it smooth?


You need a normal smoothing algorithm. The one in kman's MD5 Loader is ok for standard normals. You need one more computation to actually obtain the desired effect.
Ender said:

Normals are not exported in to MD5 format (this is in MD5 format specification). So smoothing normals in to Blender does not affect the exported model.

This is because Doom 3 uses massively Normal Maps.


em im not sure about that. the normals r calculated using the information obtained from the MD5 files. so i think if u change the normals, the values in there should change accordingly.
neakor said:

em im not sure about that. the normals r calculated using the information obtained from the MD5 files. so i think if u change the normals, the values in there should change accordingly.


From this page http://tfc.duke.free.fr/coding/md5-specs-en.html


Precomputing normals

You will probably need to compute normal vectors, for example for lighting. Here is how to compute them in order to get

that's sounds like the complex bit of info I was missing. thx. i'll dig into that tomorrow.



i've read a few posts mentioning a new or update MD5 Loader; feels like that should be integrated.

brown said:

that's sounds like the complex bit of info I was missing. thx. i'll dig into that tomorrow.


Even if that could work for you, I still believe that there is no smoothed normal info in the MD5 file format. I also investigated the code of der_ton's blender2md5 script (http://home.mnet-online.de/der/blender2md5.rar, that is derived from the blender2cal3d). Even if a "normal" variable is computed, I think it is a sort of dead code, still there only because it was used by Cal3D format. The reason I think so, is that I have been not able to find any involvement of "normal" variable in the subsequent computation of weights or vertices.

brown said:

i've read a few posts mentioning a new or update MD5 Loader; feels like that should be integrated.


There are currently 5 different codes.

* MD5 Reader 2 (deprecated, still available and usable but not any more maintained)
* MD5 Reader 2 Refactor (currently under development, it will sobstitute the previos one)
* MD5 Importer (by neakor, completed)
* MD5 Loader (by kman, the one you are using)
* and another importer developed by someone that recently posted it on the jME forum (under development)

Not any of those above plan to join any other.

I found another good page about smooth normals computation: http://www.lighthouse3d.com/opengl/terrain/index.php?normals. It is a really good explanation.

Comparing the page explanation with the computeNormals() method of MD5RenderMesh class, it seems that kman did exactly the same thing. So I do not understand why his algorithm does not work as expected.

thanks for the refs, that just made me realized i think i might have been looking at the problem from the wrong direction. i'm not the one making the model, just importing it, so i assumed that the MD5 file exported from Blender was fine.



looking at the MD5 file of the model I posted previously, there are way too many vertices for the amount of triangles, as if it's duplicating the vertices instead of merging them correctly. that would explain why it's not computing the right normals, it just thinks that each vertex is only attach to one face.



they were exported with blender2md5.



damn.


brown said:

looking at the MD5 file of the model I posted previously, there are way too many vertices for the amount of triangles, as if it's duplicating the vertices instead of merging them correctly. that would explain why it's not computing the right normals, it just thinks that each vertex is only attach to one face.

they were exported with blender2md5.


When I investigated the blender2md5 code (some posts above) I saw that it duplicates a vertex if there are more then one UV associated to it. Multiple UVs are not supported in MD5 format. So, you should controll that there is only one UV map applied to the model.
Multiple normals are not supported in Cal3D format (remember the dead code I was talking above?). So, if a face has the smooth property disabled the exporter duplicates its vertices.
Then I suggest you to enable smoothing in your model (you can do it globally). Ironically, even if MD5 format does not store normals, they still affect some way the exporter behaviour, because of that strange Cal3D exporter "dead code", and you have to smooth the model in Blender, as they said at the beginning.

I will see if, it would be possible to modify the exporter to ignore normals. So that you have not anymore to care the smoothing settings.

works pretty good now. global smoothing in blender smooths out almost everything when imported back in jme. (just a few minor glitches with my model)



thx for help. this discussion saved me a lot of brain scratching. :slight_smile: