Materials and Forced renderer state

I managed to understand what PolyOffset is and how it works. Finally… And now I know that I need different values of PolyOffset for certain objects.

Since it is ok and safe to have some big value for most objects on the scene, some other objects (small, thin ones) needs to have small values of PolyOffset.
I solved it by duplicating and modifying my main j3md and now those special objects uses that new definition. But the definition is huge, it have many, many parameters and techniques. I must keep 2 files which differ in only one line. What if I need 15 different PO values? 15 files? Adding some parameter or technique will be pain in the ass…

Is there any (even dirty one) way to do it on java level?
If I get material definition from material, get “PreShadow” technique and it’s forced renderer state, modify that state, then all other materials will use the new values, because all of them share the material definition instance. There is no .clone() for material definition, so far I did not found any way to do that.

I don’t want to modify engine’s source code.

Any idea?

EDIT: I know that I can set PolyOffset to 0 0 and implement something similar on my own and drive it by material’s parameter, but I’m looking for better solution.

Since you mentioned it… how would a material parameter be more convenient? I’m trying to understand in total what the difficulty is but this last bit confused me… because setting the poly offset on the render state is no harder than setting a material parameter to me.

Renderer state is a part of technique in material definition.
The material’s parameter is used for material instance, for one geometry. The change made to renderer’s state is applied to all materials that uses the particular material’s definition.

Custom implementation of PolyOffset… on some forums more experienced people told that it is better to use hardware solution than implementing the new one.
And another thing - next material parameter means next thing I need to pass in GBuffer in deferred shading.

Ah, now I see.

Another curiosity question, why do you need so many poly offsets? Seems strange.

Big value of PolyOffset is somehow safe for general usage. But for small and especially thin objects the shadow generated by the big value looks very bad.
Big PolyOffset is the easy solution for shadow acnes and selfshadowing on some edges. But it may causes peter-panning. Of course peter-panning is not visible because of disabled FaceCulling, but only for big objects. Thin objects looks bad:

you can change the polyoffset by code thought the additionalRenderState.
Something like

mat.getAdditionalRenderState().setPolyOffset(...);

Yes, but when shadow renderer starts the PreShadow phase it changes the technique, that technique have ForcedRendererState, which, as far as I know, replaces the additional renderer state. Am I right?

yes.
Didn’t realised it was for the shadow pass.

I guess you can programmatically clone the MaterialDefinition, change the renderState of the technique, and then use this modified version of MaterialDefinition to create the new Material.

I’m not even sure if it is possible to do it manually…

It should possible because in jME everything is done programmatically, eg load of j3m/j3md.
When I’ll back to home, I’ll search, iirc I did something like that in one of my project.

until back home, a quick sample

You’re right, there is no .clone and MaterialDef aren’t Savable (I test with 3.1.x). In my projects, I fully create Material and MaterialDef.
I wrote a sample code to copy material manually but it failed to find named techniqueDef. Anyway I share the code, if you want to take a look.
The code below also failed to store the Material into cache, In my other project (link in previous post) I created a custom AssetLocator that read Asset from memory (like a HashMap), I used it to edit shader, material at runtime without the need to save into a file and hot refresh.

        app.enqueue(() -> {
            MaterialDef matdef0 = (MaterialDef)app.getAssetManager().loadAsset("Common/MatDefs/Light/Lighting.j3md");
            MaterialDef matdef = new MaterialDef(app.getAssetManager(), "foo");
            matdef0.getMaterialParams().forEach((m) -> {
                if (m instanceof MatParamTexture) {
                    matdef.addMaterialParamTexture(m.getVarType(), m.getName(), ((MatParamTexture)m).getColorSpace());
                } else {
                    matdef.addMaterialParam(m.getVarType(), m.getName(), m.getValue());
                }
            });
            matdef0.getDefaultTechniques().forEach((t) -> {
                matdef.addTechniqueDef(t);
            });
            Arrays.asList("PostShadow", "PostShadow15", "PreNormalPass", "PreNormalPassDerivative", "GBuf", "Glow").forEach((s) -> {
                TechniqueDef td = matdef.getTechniqueDef(s);
                if (td != null) {
                    matdef.addTechniqueDef(td);
                } else {
                    System.err.println("techniquedef not found : " + s);
                }
            });
            TechniqueDef techniquedef0 = matdef.getTechniqueDef("PreShadow");
            if (techniquedef0 != null) {
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                BinaryExporter.getInstance().save(techniquedef0, os);
                ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
                TechniqueDef techniquedef = (TechniqueDef)BinaryImporter.getInstance().load(is);
                os.close();
                is.close();
                RenderState rs = techniquedef.getForcedRenderState();
                if (rs == null) rs = new RenderState();
                rs.setPolyOffset(2f, 1f);
                techniquedef.setForcedRenderState(rs);
                matdef.addTechniqueDef(techniquedef);
            }
                
            //store the material to use it like other j3md
            app.getAssetManager().addToCache(new AssetKey<MaterialDef>("matdefPoly2.j3md"), matdef);
            
            Material mat = new Material(matdef);
            app.getAssetManager().addToCache(new AssetKey<Material>("matPoly2.j3m"), mat);
            return null;
        });

        app.enqueue(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                Material mat0 = app.getAssetManager().loadMaterial("matPoly2.j3m");
                Assert.assertEquals(2f, mat0.getMaterialDef().getTechniqueDef("PreShadow").getRenderState().getPolyOffsetFactor());
                Material mat1 = new Material(app.getAssetManager(), "matdefPoly2.j3md");
                Assert.assertEquals(2f, mat1.getMaterialDef().getTechniqueDef("PreShadow").getRenderState().getPolyOffsetFactor());
                return null;
            }
        });


Alternative create your j3md file via a build script (copy + search and replace) then variation are always in sync with master material.

Thank you, I’ll take a look.
Anyway, I have two j3md files right now but I’m sure that some day things will go worse and doing that on the code level will be the best solution.

EDIT: I have one more idea… and will try it tomorrow. If I succeed I’ll share the code.

I think that the good solution is to copy some JME’s code into my own renderer and modify it.
The main goal is to render PreShadow technique with different PolyOffset values for certain geometries. So… let’s start inside renderShadowMap function of ShadowRenderer. For a long time I have my own ShadowRenderer class so everything goes simple.

_viewPort.getQueue().renderShadowQueue(_shadowMapOccluders, _renderManager, shadowCam, true);

The above line is the key. We can just go into renderShadowQueue and take all the code, because it is not using ANY queue’s resources.

    list.setCamera(cam); // select camera for sorting
    list.sort();
    for (int i = 0; i < list.size(); i++)
    {
        Geometry obj = list.get(i);
        assert obj != null;
        if (something) 
        {
            TechniqueDef td = obj.getMaterial().getMaterialDef().getTechniqueDef("PreShadow");
            float f = td.getForcedRenderState().getPolyOffsetFactor();
            float u = td.getForcedRenderState().getPolyOffsetUnits();
            td.getForcedRenderState().setPolyOffset(NEW_FACTOR, NEW_UNIT);
            rm.renderGeometry(obj);
            td.getForcedRenderState().setPolyOffset(f, u);
        }
        else rm.renderGeometry(obj);
        obj.queueDistance = Float.NEGATIVE_INFINITY;
    }
    if (clear) list.clear();

I’ll try it at home, now I’m at work…

EDIT: It is working :wink: