SharedMesh RenderState issues

Hello all,

  We have been working with instancing for a project and adapted our system to use the SharedMesh object for hardware supported instancing. This all worked just fine until we began instancing materials as well. We noticed that the material changes we made to an individual SharedMesh node cascaded down onto all instances using that geometry. After extensive troubleshooting we determined that the problem was in how we were using the jME implementation.

  In com.jme.scene.SharedMesh, the setTarget method sets all of the SharedMesh node's RenderStates to the target TriMesh's RenderStates. This led to the material duplication problem on our end, and could potentially cause other problems with CullStates, etc. We were using the SceneElement's getRenderState method to get a MaterialState object.

  I'm not sure if this implementation was intentional (perhaps because of some other issue I am not aware of), but I think it may be more usable if the SharedMesh node was created with it's own RenderStates (perhaps copied from the target, but not the same actual objects). The workaround we are using now is to create a new MaterialState with LWJGL directly, and then setting it to the SharedMesh node.

  If anyone knows anything about this issue, or if it was an intentional decision, please let me know!



  Regards,

Also, another minor thing that may have a workaround is the output of "SharedMesh will ignore reconstruct." everytime a SharedMesh object is created. This happens because the reconstruct method is called by the ancestor class, Geometry, whose constructor has the following body:


        super(name);
        setupBatchList();
        reconstruct(null, null, null, null);



Perhaps the unnecessary reconstruct could be avoided somehow? This is not anything major, just a cosmetic issue :)

if two entities have two different render states, they r obviously not the same mesh.



the shared mesh provides a way for u to clone the exact same mesh with different transform information. render state information is down to the mesh level not the node level.

I guess I was misunderstanding the meaning of the word "Mesh" in jME space. If you think of a mesh as only geometry, then it makes sense to share this geometry with different materials or other renderstates in order to avoid duplicated data. It allows for variation with characters without recreating the same geometric data time and time again.

    Thanks for the speedy reply!

Dahlgren said:

I guess I was misunderstanding the meaning of the word "Mesh" in jME space. If you think of a mesh as only geometry, then it makes sense to share this geometry with different materials or other renderstates in order to avoid duplicated data. It allows for variation with characters without recreating the same geometric data time and time again.
    Thanks for the speedy reply!


yeah, a mesh is not only the geometric data, but also the render states as well. its essentially a view of an object.

if two entities have two different render states, they r obviously not the same mesh.



yeah, a mesh is not only the geometric data, but also the render states as well.


Could you explain that a little further neakor? I have no problems applying different render states to shared meshes; and the wiki http://www.jmonkeyengine.com/wiki/doku.php?id=sharedmesh suggests that shared meshes can indeed have their own render states.

Maybe I am misunderstanding something?
sbayless said:

if two entities have two different render states, they r obviously not the same mesh.



yeah, a mesh is not only the geometric data, but also the render states as well.


Could you explain that a little further neakor? I have no problems applying different render states to shared meshes; and the wiki http://www.jmonkeyengine.com/wiki/doku.php?id=sharedmesh suggests that shared meshes can indeed have their own render states.

Maybe I am misunderstanding something?


maybe i said it the wrong way. ur understanding is correct.

u should be able to set render states on a shared mesh without affecting other instances of SharedMesh since it is a mesh. and that is what i meant by a mesh is not only geometric data but also render state data as well.

and if two meshes or SharedMesh should have different render states, obviously these two r two different objects or two differernt SharedMesh instances.

and to further understand this u need to understand how SharedMesh works.

The way multiple SharedMesh do "sharing" is by drawing the same target TriMesh multiple times. whenever a SharedMesh is called to draw itself, it will enfore all of its states on the target, this includes the transform states, the render states, culling states and etc. then it simply draws the target TriMesh. and the result is that this draw invocation will render a mesh based on the target TriMesh. and since all the transform states, render states and etc. have just been enfored on to the TriMesh by the SharedMesh, the render result is not the original TriMesh (by original i mean the state when it is created) but the SharedMesh states. and each SharedMesh does the same thing, so u will c a bunch of entities all with different transform and render states on the screen but they r actually sharing the same target TriMesh data.

Thanks neakor, that was my understanding as well, somehow I mis-interpreted what you said earlier. Thanks for clearing everything up.

This is true, except when you use the SceneElement's getRenderState method to get a MaterialState object. Changing this material state object (Diffuse color, etc) causes ALL SharedMeshes targeting the original TriMesh to be affected. We did several tests and found this to definitely be the case.

Dahlgren said:

This is true, except when you use the SceneElement's getRenderState method to get a MaterialState object. Changing this material state object (Diffuse color, etc) causes ALL SharedMeshes targeting the original TriMesh to be affected. We did several tests and found this to definitely be the case.


what r u calling the getRenderState method on? the shared mesh instance? that should return u the shared mesh renderstate and modifying that shouldnt affect the target trimesh or any other shared mesh at all.
This is true, except when you use the SceneElement's getRenderState method to get a MaterialState object.


Dahlgren, would you mind clarifying that? Just to make sure we are on the same page:

You have a TriMesh instance, and you have a number of SharedMesh instances that are 'sharing' that original TriMesh.

Are you applying renderstates to the original TriMesh, or to the SharedMeshes? As I understand it, you should only be applying renderstates to the shared meshes, and not to the original TriMesh at all. Further, you should not have the original TriMesh in the scenegraph at all.

In other words, the TriMesh that all the SharedMeshes are sharing should be only used for that purpose - you shouldn't (for the most part) be modifying it, you should only be modifying the SharedMeshes that share it.

Using this set up, I have no problem assigning different MaterialStates to different SharedMeshes that share the same TriMesh instance.

I thought that some code might be helpful to clear up any confusion. The following excerpts are from the system that is wrapping instancing support. Within this class, m_instance is a SharedMesh used for instancing. So first I create a new SharedMesh if this object doesn't already have one


     if (m_instance == null)
            m_instance = new SharedMesh(getName(), m_geometry.getGeometry());


Then later I grab the MaterialState in order to assign the proper materials for this instance.


        //m_matState = (MaterialState)m_instance.getRenderState(RenderState.RS_MATERIAL);
        // or alternatively, use one created by the LWJGL call...
        m_matState = (MaterialState)new LWJGLMaterialState();


If I use the m_instance's getRenderState, then every part of every instanced model that shares the target TriMesh (which is not in the scenegraph) will have the same material applied to it. As a fix, I have been using the direct LWJGL function call (in the constructor, not where it is in this excerpt) which yields the expected result on only that single mesh instance (in this case the left leg of a particular model) shows the material change.

           
        // apply material   
        m_matState.setDiffuse(meshMat.getDiffuse());
        m_matState.setAmbient(meshMat.getAmbient());
        m_matState.setSpecular(meshMat.getSpecular());
        m_matState.setEmissive(meshMat.getEmissive());
        m_matState.setShininess(meshMat.getShininess());
        m_matState.setEnabled(true);
        m_matState.setColorMaterial(meshMat.getVertexColorsMode());
        m_matState.setMaterialFace(meshMat.getApplyMaterialMode());
        m_instance.setRenderState(m_matState);


I don't believe I am using the jME support incorrectly, although I am definitely not a jmonkey veteran! Hope this helps to clarify what we are talking about :)

When you create a SharedMesh, the renderstates of the target are reused on the sharedmesh.  Thus, if you grab the material state from the SharedMesh, unless you previously set a new material state on that SharedMesh then you are really grabbing the target's render state and changes to that state will affect all unaltered shared meshes that use that target.



So, you have the right idea, but you don't have to use an LWJGLMaterialState directly.  Instead ask your renderer to create one.

think it in another way. u shouldnt be grabbing the material state from a newly created sharedmesh since u havent set anything on it yet. just like when u create a box, u wouldnt try to grab the material state from it coz it will be a null pointer but rather u will be creating a new material state and set it to the box. the same idea is used here.

//m_matState = (MaterialState)m_instance.getRenderState(RenderState.RS_MATERIAL);


To supply some code to go along with those suggestions, after instantiated the SharedMesh, and before calling the above, you should do something like the following:


      MaterialState materialState = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();//create a new render state.
            
      //set some properties of the material state
           materialState.setDiffuse(color);

      m_instance.setRenderState(materialState);//apply the render state to the SceneElement
      m_instance.updateRenderState();



From what Neakor and Renanse said below, your newly created SharedMeshes by default had the same RenderStates as the target mesh, and you never assigned new, unique RenderStates to your SharedMeshes after creating them, resulting in them all sharing same RenderState instances.


OK, I understand now. Since I just created a node and got it's states rather than making new ones, the effect was to use the target node's states. I should have realized the RenderStates of a new node were undefined :stuck_out_tongue:



Definitely my mixup, thanks!