Transparency with render to texture

I don’t like to post transparency issues, I feel like you guys have to repeat yourselves non stop when it comes to transparency, but I am having one :frowning:

I’ll put the entire code at the bottom, not that I think it’s wrong, but I made 2 small cases to keep it simple.

Here is a quick overview though:

Scene:
-blue cube, unshaded material
-a character model comprising of multiple parts
-only one part, the hair, is placed in the transparent bucket and set to blendmode.alpha, rest all left opaque
-an ambient light for the character
-white viewport clear color
-camera set to look at the characters head side on

I changed the color of the transparent hair part to red and the opaque head to cyan to make it easier to see.

This results in exactly what I expected:

Hair works perfectly

Next I have almost the same setup, except the character is rendered to a texture. The blue box is left on the rootnode. The texture is then placed on the guiNode so it appears over the top of the blue box scene.

I don’t understand why the blue from the box is showing through the solid cyan head? For the rendered texture, I thought that it would first draw the characters head since it’s opaque, then draw the transparent hair on top since its set to bucket.transparent, all of that goes on the texture, then it would render the normal rootNode scene and out this texture on top.

These are the 2 head parts, in blender so no transparency here

I would have thought the solid head wouldn’t disappear and allow blue to come through, and don’t understand why rendering to a texture then putting it on top changes it. I did wonder about the blend mode, and changing it helps with this but causes more problems.

Code for working hair:

public class Test2 extends SimpleApplication 
{
    Node playerNode;
    
    public static void main(String[] args){
        Test2 app = new Test2();
        app.start();
    }
 
    @Override
    public void simpleInitApp() 
    {
        Box b = new Box(10, 10, 10); // create cube shape
        Geometry geom = new Geometry("Box", b);  // create cube geometry from the shape
        Material mat = new Material(assetManager,
          "Common/MatDefs/Misc/Unshaded.j3md");  // create a simple material
        mat.setColor("Color", ColorRGBA.Blue);   // set color of material to blue
        geom.setMaterial(mat);                   // set the cube's material
        geom.setLocalTranslation(new Vector3f(24.264202f, 43.10403f, 0.49422723f));
        rootNode.attachChild(geom);              // make the cube appear in the scene
        
        viewPort.setBackgroundColor(ColorRGBA.White);
        flyCam.setMoveSpeed(15);
        
        Spatial body = getModel("Models/Actors/Players/ripley/body.mesh.xml","Materials/Actors/Players/ripley/body.j3m",false);
        Spatial gear = getModel("Models/Actors/Players/ripley/gear.mesh.xml","Materials/Actors/Players/ripley/gear.j3m",false);
        Spatial head = getModel("Models/Actors/Players/ripley/head.mesh.xml","Materials/Actors/Players/ripley/head.j3m",false);
        Spatial feet = getModel("Models/Actors/Players/ripley/feet.mesh.xml","Materials/Actors/Players/ripley/feet.j3m",false);
        Spatial hair = getModel("Models/Actors/Players/ripley/hair.mesh.xml","Materials/Actors/Players/ripley/hair.j3m",true);
        
        playerNode = new Node();
        playerNode.attachChild(body);
        playerNode.attachChild(gear);
        playerNode.attachChild(head);
        playerNode.attachChild(feet);
        playerNode.attachChild(hair);
        
        
        AmbientLight al = new AmbientLight();
        
        rootNode.attachChild(playerNode);
        rootNode.addLight(al);
        
        cam.setLocation(new Vector3f(-12.170704f, 42.28284f, 0.020329474f));
        cam.setRotation(new Quaternion(-0.0077274614f, 0.7256225f, 0.008149218f, 0.68800145f));

    }
    
    public Spatial getModel(String modelName, String materialName, boolean transparent)
    {
        Spatial model = getAssetManager().loadModel(modelName);
        Material mat = getAssetManager().loadMaterial(materialName);
        mat.setColor("Diffuse", new ColorRGBA(1,1,1,1));
        if(transparent)
        {
            mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
            model.setQueueBucket(RenderQueue.Bucket.Transparent);
            //mat.setFloat("AlphaDiscardThreshold", 0.1f);
        }
        model.setMaterial(mat);
        return model;
    }
}

And now the one that renders the character to a texture and attaches to the guiNode

public class Test extends SimpleApplication 
{
    Picture pic;
    Node offRootNode;
    Camera offCamera;
    Node playerNode;
    
    public static void main(String[] args){
        Test app = new Test();
        app.start();
    }
 
    @Override
    public void simpleInitApp() 
    {
        Box b = new Box(10, 10, 10); // create cube shape
        Geometry geom = new Geometry("Box", b);  // create cube geometry from the shape
        Material mat = new Material(assetManager,
          "Common/MatDefs/Misc/Unshaded.j3md");  // create a simple material
        mat.setColor("Color", ColorRGBA.Blue);   // set color of material to blue
        geom.setMaterial(mat);                   // set the cube's material
        geom.setLocalTranslation(new Vector3f(24.264202f, 43.10403f, 0.49422723f));
        rootNode.attachChild(geom);              // make the cube appear in the scene
        
        viewPort.setBackgroundColor(ColorRGBA.White);
        
        Spatial body = getModel("Models/Actors/Players/ripley/body.mesh.xml","Materials/Actors/Players/ripley/body.j3m",false);
        Spatial gear = getModel("Models/Actors/Players/ripley/gear.mesh.xml","Materials/Actors/Players/ripley/gear.j3m",false);
        Spatial head = getModel("Models/Actors/Players/ripley/head.mesh.xml","Materials/Actors/Players/ripley/head.j3m",false);
        Spatial feet = getModel("Models/Actors/Players/ripley/feet.mesh.xml","Materials/Actors/Players/ripley/feet.j3m",false);
        Spatial hair = getModel("Models/Actors/Players/ripley/hair.mesh.xml","Materials/Actors/Players/ripley/hair.j3m",true);
        
        playerNode = new Node();
        playerNode.attachChild(body);
        playerNode.attachChild(gear);
        playerNode.attachChild(head);
        playerNode.attachChild(feet);
        playerNode.attachChild(hair);
        
        
        AmbientLight al = new AmbientLight();
        
        createRenderToTexture();
        rootNode.addLight(al);
        offRootNode.attachChild(playerNode);
        offRootNode.addLight(al);
        
        cam.setLocation(new Vector3f(-12.170704f, 42.28284f, 0.020329474f));
        cam.setRotation(new Quaternion(-0.0077274614f, 0.7256225f, 0.008149218f, 0.68800145f));
        offCamera.setLocation(new Vector3f(-12.170704f, 42.28284f, 0.020329474f));
        offCamera.setRotation(new Quaternion(-0.0077274614f, 0.7256225f, 0.008149218f, 0.68800145f));
    }
    
    public Spatial getModel(String modelName, String materialName, boolean transparent)
    {
        Spatial model = getAssetManager().loadModel(modelName);
        Material mat = getAssetManager().loadMaterial(materialName);
        mat.setColor("Diffuse", new ColorRGBA(1,1,1,1));
        if(transparent)
        {
            mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
            model.setQueueBucket(RenderQueue.Bucket.Transparent);
        }
        model.setMaterial(mat);
        return model;
    }
    
    
    public void simpleUpdate(float d)
    {
        offRootNode.updateGeometricState();
        offRootNode.updateLogicalState(d);

        offCamera.setLocation(cam.getLocation());
        offCamera.setRotation(cam.getRotation());
    }
    
    public void createRenderToTexture()
    {
        width = Display.getWidth();
        height = Display.getHeight();
        offCamera = new Camera((int)width, (int)height);
        offCamera.setFrustumPerspective(60f, (float)((float)width/(float)height), 0.1f, 1000f);
        ViewPort offView = renderManager.createPreView("Offscreen View", offCamera);
        offView.setClearFlags(true, true, true);
        offView.setBackgroundColor(new ColorRGBA(0,0,0,0));
        FrameBuffer offBuffer = new FrameBuffer((int)width, (int)height, 1);
        Texture2D offTex = new Texture2D((int)width, (int)height, Image.Format.RGBA8);
        offTex.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
        offTex.setMagFilter(Texture.MagFilter.Nearest);
        offBuffer.setDepthBuffer(Image.Format.Depth);
        offBuffer.setColorTexture(offTex);
        offView.setOutputFrameBuffer(offBuffer);
        offRootNode = new Node();
        offView.attachScene(offRootNode);
        pic = new Picture("realtime");
        pic.setTexture(assetManager, offTex, true);
        pic.setWidth(width);
        pic.setHeight(height);

        guiNode.attachChild(pic);
    }
}

I’m typing on a phone so I’ll keep it short. I think that the problem is the final alpha component of the texture. Have a look at this thread and play with the blend functions:

Yeah, I agree… you would probably see pretty clearly what is going on if you save the texture to a PNG and open it in some editor.

Thanks for the replies. Ok I managed to get just the texture I render to saved, results are in!

A close up…

These are screenshots from GIMP, and yeah the red parts have transparency around them, allowing that blue from earlier to show through.

Here’s my point of confusion though: why’s it doing this when its render to texture, and not when its straight up normal viewport and so on, like my other test in which hair worked. Should it not be running the same? Blendmode.alpha worked fine in that case, but in this one its literally blocking out the cyan colored head, which is drawn first thanks to the buckets.

I was reading about

Result = Source Alpha * Source Color + (1 - Source Alpha) * Dest Color → (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

So if I remove the hair bit, the only transparent blendmode.alpha part, and again render to texture and save I get

In this setup somehow the pure cyan head is not the source Dest Color.

.

.

Ahh I just saved an edit after you replied :stuck_out_tongue:

Blend mode renders the target pixel with the source pixel… then obliterates any existing alpha with the new alpha. Which is fine when you are blending… .not fine when you are trying to take the final result and blend it with something.

Excuse my ninja edit. So basically the alpha from the hair is still there, and since I’m blending it with something else it’s becoming a problem whereas it would not in a ‘normal’ situation.

Yeah,

Normal render:
color1 + color2 with alpha = color1/2 (alpha from color2 is stored)
color1/2 + color3 with alpha = color1/2/3 (alpha from color 3 is stored, alpha from color 2 was ignored.)

Your texture ender:
color2 with alpha = color 2 with alpha from color 2
color2 + color3 with alpha = color2/3 with alpha from color 3.

Draw that on color1 and you get a mixture of color1 and color2/3 using only color 3’s alpha to mix them.

1 Like

Just had the chance to look it up in my own source code. I always use this blend function when rendering to texture, it may also work in your case:

rs=material.getAdditionalRenderState();
rs.setCustomBlendFactors(RenderState.BlendFunc.Src_Alpha, RenderState.BlendFunc.One_Minus_Src_Alpha, RenderState.BlendFunc.One, RenderState.BlendFunc.One_Minus_Src_Alpha);
rs.setBlendMode(RenderState.BlendMode.Custom);
3 Likes

Thanks, both of you. I get whats going on now.

I have a few solutions in mind, gonna have a crack at it now.

Apollo what version of JME do you use to access setCustomBlendFactors? 3.1? I only just upgraded to 3.1 the other day lol, been using some ancient 3.0 for a while.

edit: Nvm got beta 2 and its in there.

1 Like

Yes, the custom blend mode is only available in very recent versions.

(Proudly made by me, for the purpose to solve these problems. :wink:)

2 Likes