[Solved] Reduce frame rate of secondary camera rendering to a texture

I have a number of security cameras in my game that ultimately render to a texture that is then presented to the player as a monitor within the world.

I’ve found that when I have 3 or more of these within the world the frame rate starts to drop. I’ve already got these cameras rendering at quite low resolution but the resolution doesn’t seem to be the limiting factor. Are there any options to reduce the quality of these secondary cameras within the scene. One option I’ve been looking for is to reduce their framerate without changing the main game frame rate. Is there any such option?

This is a minimal example of my set up; the traditional blue cube and a secondary camera filming it and putting the captured image back into the world:

package test;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.*;
import com.jme3.renderer.Camera;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.*;
import com.jme3.texture.*;
import java.util.UUID;

public class Main extends SimpleApplication {

    public static int IMAGE_SIZE =256;

    @Override
    public void simpleInitApp() {

        rootNode.attachChild(getBoxToLookAt());

        Camera offCamera= new Camera(IMAGE_SIZE,IMAGE_SIZE);
        offCamera.setLocation(new Vector3f(2,2,2));
        offCamera.lookAt(new Vector3f(0,0,0), Vector3f.UNIT_Y);

        ViewPort offView = getRenderManager().createMainView(UUID.randomUUID().toString(), offCamera);
        offView.setClearFlags(true, true, true);

        Texture2D drawableTexture = new Texture2D(IMAGE_SIZE,IMAGE_SIZE, Image.Format.RGBA8);

        FrameBuffer offBuffer = new FrameBuffer(IMAGE_SIZE,IMAGE_SIZE, 1);

        //setup framebuffer's cam
        offCamera.setFrustumPerspective(90f, 1f, 0.2f, 1000f);

        drawableTexture.setMinFilter(Texture.MinFilter.Trilinear);
        drawableTexture.setMagFilter(Texture.MagFilter.Bilinear);


        //setup framebuffer to use texture
        offBuffer.setDepthBuffer(Image.Format.Depth);
        offBuffer.setColorTexture(drawableTexture);

        //set viewport to render to offscreen framebuffer
        offView.setOutputFrameBuffer(offBuffer);
        offView.attachScene(this.rootNode);
        offView.setBackgroundColor(ColorRGBA.White);

        //set up quad showing what the camera can see
        Geometry screen = getQuadToRenderImageOn();
        screen.getMaterial().setTexture("ColorMap", drawableTexture);
        screen.setLocalTranslation(0,2,0);
        rootNode.attachChild(screen);
        cam.lookAt(screen.getWorldTranslation(), Vector3f.UNIT_Y);
    }


    private Geometry getBoxToLookAt(){
        Box b = new Box(1, 1, 1);
        Geometry geom = new Geometry("Box", b);
        Material mat = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Blue);
        geom.setMaterial(mat);
        return geom;
    }

    private Geometry getQuadToRenderImageOn(){
        Quad q = new Quad(1, 1);
        Geometry geom = new Geometry("Box", q);
        Material mat = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        geom.setMaterial(mat);
        return geom;

    }

    public static void main(String[] args){
        Main main = new Main();
        main.start();
    }
    
}

Its clear to me that I will need to turn on and off my cameras intelligently based on if the player is likely to be able to see them, but I’d like to be able to have at least 3 visible without it causing the frame rate to drop. Is there a way to achieve that by sacrificing quality?

It depends on how your scene is set up. If your scene is one giant mesh it will have to draw it all. If it’s been cut into smaller “chunks” it will cull most of it away. If you decrease the field of view it will do so even more.

You could use a seperate “cheaper” scene for the cameras - a simplified version with less vertices.

You could also render the texture x times per second instead of full FPS - essentially having a “snapshot” method that captures the scene, renders it, and then removes the viewport from the renderManager.

Most of your problems may go away if you have cut your scene into manageable chunks to allow culling.

1 Like

My scene is cut up into chunks, but also quite complicated.

“You could also render the texture x times per second instead of full FPS - essentially having a “snapshot” method that captures the scene, renders it, and then removes the viewport from the renderManager.”

That is exactly what I want. As part of the “turn on and off my cameras intelligently” I mentioned I found the method ViewPort::setEnabled which if I set to false freezes the rendered image until its turned back on which is exactly what I need! Is that what you mean?

Yes, that works wonderfully, I’ve implemented both intelligently turning off the cameras when they are unlikely to be seen and the setEnabled based frame rate and my fps is back up. Here is a minimal example of how i did it in case anyone else wants to do this in the future

package test;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.*;
import com.jme3.renderer.Camera;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.*;
import com.jme3.texture.*;
import java.util.UUID;

public class Main extends SimpleApplication {

    public static int RENDER_CAMERFA_EVERY_X_FRAMES = 10; //the in game camera takes a picture every X real frames

    public static int IMAGE_SIZE =256;
    private int framesSinceUpdate = 0;
    ViewPort offView;

    @Override
    public void simpleInitApp() {

        rootNode.attachChild(getBoxToLookAt());

        Camera offCamera= new Camera(IMAGE_SIZE,IMAGE_SIZE);
        offCamera.setLocation(new Vector3f(2,2,2));
        offCamera.lookAt(new Vector3f(0,0,0), Vector3f.UNIT_Y);

        offView = getRenderManager().createMainView(UUID.randomUUID().toString(), offCamera);
        offView.setClearFlags(true, true, true);

        Texture2D drawableTexture = new Texture2D(IMAGE_SIZE,IMAGE_SIZE, Image.Format.RGBA8);

        FrameBuffer offBuffer = new FrameBuffer(IMAGE_SIZE,IMAGE_SIZE, 1);

        //setup framebuffer's cam
        offCamera.setFrustumPerspective(90f, 1f, 0.2f, 1000f);

        drawableTexture.setMinFilter(Texture.MinFilter.Trilinear);
        drawableTexture.setMagFilter(Texture.MagFilter.Bilinear);


        //setup framebuffer to use texture
        offBuffer.setDepthBuffer(Image.Format.Depth);
        offBuffer.setColorTexture(drawableTexture);

        //set viewport to render to offscreen framebuffer
        offView.setOutputFrameBuffer(offBuffer);
        offView.attachScene(this.rootNode);
        offView.setBackgroundColor(ColorRGBA.White);

        //set up quad showing what the camera can see
        Geometry screen = getQuadToRenderImageOn();
        screen.getMaterial().setTexture("ColorMap", drawableTexture);
        screen.setLocalTranslation(0,2,0);
        rootNode.attachChild(screen);
        cam.lookAt(screen.getWorldTranslation(), Vector3f.UNIT_Y);
    }

    @Override
    public void simpleUpdate(float tpf) {
        super.simpleUpdate(tpf);

        framesSinceUpdate++;

        if(framesSinceUpdate<RENDER_CAMERFA_EVERY_X_FRAMES){
            offView.setEnabled(false);
        }else{
            framesSinceUpdate = 0;
            offView.setEnabled(true);
        }

    }

    private Geometry getBoxToLookAt(){
        Box b = new Box(1, 1, 1);
        Geometry geom = new Geometry("Box", b);
        Material mat = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Blue);
        geom.setMaterial(mat);
        return geom;
    }

    private Geometry getQuadToRenderImageOn(){
        Quad q = new Quad(1, 1);
        Geometry geom = new Geometry("Box", q);
        Material mat = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        geom.setMaterial(mat);
        return geom;

    }

    public static void main(String[] args){
        Main main = new Main();
        main.start();
    }

}

I love the way that in jMonkey the answer to “can I do X” has always ended up being"Yes"

5 Likes