This can be achieved by rendering the scene from the perspective of a screen camera to a FrameBuffer and then taking the TextureRegion from the FrameBuffer and rendering that to screen and then render the scene again.
For example:

In the above example the cyan rect shows what the in-scene camera sees, and this is rendered to the grey box, it is possible to move both the scene camera and the main camera independentely.
By creating and grabbing the TextureRegion from a FrameBuffer
screenFrameBuffer = new FrameBuffer(Pixmap.Format.RGBA8888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(),true);
screenTexture = new TextureRegion(screenFrameBuffer.getColorBufferTexture());
screenTexture.flip(false, true);
all render calls can be made to affect only the screenFrameBuffer by calling screenFrameBuffer.begin(), after screenFrameBuffer.end() is called the next draw call will again affect the actual screen/window.
So in in the render method you can first draw your scene to the FrameBuffer
screenFrameBuffer.begin();
renderScene(screenCamera, Color.DARK_GRAY);
screenFrameBuffer.end();
And then draw it again, followed by the "screen" as a sprite:
renderScene(sceneCamera, Color.BLACK);
batch.setProjectionMatrix(sceneCamera.combined);
batch.begin();
batch.draw(screenTexture, -sceneCamera.viewportWidth / 2.0f,-sceneCamera.viewportHeight / 2.0f,0,0,screenCamera.viewportWidth,screenCamera.viewportHeight,1.0f, 1.0f, 0.0f);
batch.end();
Complete source code for the gif above is:
package com.bornander.sandbox;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.*;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.RandomXS128;
import com.badlogic.gdx.math.Vector2;
public class MyGdxGame implements ApplicationListener {
public static class Ball {
public Vector2 position = new Vector2();
public Vector2 velocity = new Vector2();
public float size = 1.0f;
public Color color = new Color();
public void render(ShapeRenderer shapeRenderer) {
shapeRenderer.setColor(color);
shapeRenderer.circle(position.x, position.y, size, 16);
}
public void update() {
position.x += velocity.x * Gdx.graphics.getDeltaTime();
position.y += velocity.y * Gdx.graphics.getDeltaTime();
}
}
static RandomXS128 rnd = new RandomXS128();
OrthographicCamera sceneCamera;
OrthographicCamera screenCamera;
ShapeRenderer shapeRenderer;
SpriteBatch batch;
FrameBuffer screenFrameBuffer;
TextureRegion screenTexture;
Ball[] balls;
private static float rnd(float min, float max) {
return min + rnd.nextFloat() * (max - min);
}
@Override
public void create() {
float aspectRatio = (float) Gdx.graphics.getWidth() / (float)Gdx.graphics.getHeight();
sceneCamera = new OrthographicCamera(100.0f, 100.0f / aspectRatio);
screenCamera = new OrthographicCamera(32.0f, 32.0f / aspectRatio);
batch = new SpriteBatch();
screenFrameBuffer = new FrameBuffer(Pixmap.Format.RGBA8888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(),true);
screenTexture = new TextureRegion(screenFrameBuffer.getColorBufferTexture());
screenTexture.flip(false, true);
shapeRenderer = new ShapeRenderer();
balls = new Ball[128];
for(int i = 0; i < balls.length; ++i) {
balls[i] = new Ball();
balls[i].position.set(0, 0);
balls[i].velocity.set(rnd(-4, 4), rnd(-4, 4));
balls[i].size = rnd(1, 1);
balls[i].color.set(rnd(0.5f, 1.0f), rnd(0.5f, 1.0f), rnd(0.5f, 1.0f), 1.0f);
}
}
private void renderScene(Camera camera, Color background) {
camera.update();
Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Gdx.gl.glClearColor(background.r, background.g, background.b, background.a);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
shapeRenderer.setProjectionMatrix(camera.combined);
shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
for(int i = 0; i < balls.length; ++i) {
balls[i].render(shapeRenderer);
}
shapeRenderer.end();
}
@Override
public void render() {
float cs = 8.0f;
for(int i = 0; i < balls.length; ++i)
balls[i].update();
if (Gdx.input.isKeyPressed(Input.Keys.LEFT))
sceneCamera.position.x -= cs * Gdx.graphics.getDeltaTime();
if (Gdx.input.isKeyPressed(Input.Keys.RIGHT))
sceneCamera.position.x += cs * Gdx.graphics.getDeltaTime();
if (Gdx.input.isKeyPressed(Input.Keys.UP))
sceneCamera.position.y -= cs * Gdx.graphics.getDeltaTime();
if (Gdx.input.isKeyPressed(Input.Keys.DOWN))
sceneCamera.position.y += cs * Gdx.graphics.getDeltaTime();
if (Gdx.input.isKeyPressed(Input.Keys.A))
screenCamera.position.x -= cs * Gdx.graphics.getDeltaTime();
if (Gdx.input.isKeyPressed(Input.Keys.D))
screenCamera.position.x += cs * Gdx.graphics.getDeltaTime();
if (Gdx.input.isKeyPressed(Input.Keys.W))
screenCamera.position.y -= cs * Gdx.graphics.getDeltaTime();
if (Gdx.input.isKeyPressed(Input.Keys.S))
screenCamera.position.y += cs * Gdx.graphics.getDeltaTime();
// Render to framebuffer, clear the background to DARK_GRAY
screenFrameBuffer.begin();
renderScene(screenCamera, Color.DARK_GRAY);
screenFrameBuffer.end();
// Render to window/screen, clear backgroun to BLACK
renderScene(sceneCamera, Color.BLACK);
// Draw the framebuffer's texture as a sprite using a normal SpriteBatch
batch.setProjectionMatrix(sceneCamera.combined);
batch.begin();
batch.draw(screenTexture, -sceneCamera.viewportWidth / 2.0f,-sceneCamera.viewportHeight / 2.0f,0,0,screenCamera.viewportWidth,screenCamera.viewportHeight,1.0f, 1.0f, 0.0f);
batch.end();
// This just draws the outline of what the screen camera looks at
shapeRenderer.setProjectionMatrix(sceneCamera.combined);
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
shapeRenderer.setColor(Color.CYAN);
shapeRenderer.rect(
screenCamera.position.x - screenCamera.viewportWidth / 2.0f,
screenCamera.position.y - screenCamera.viewportHeight / 2.0f,
screenCamera.viewportWidth,
screenCamera.viewportHeight
);
shapeRenderer.end();
}
@Override
public void dispose() {
}
@Override
public void resize(int width, int height) {
}
@Override
public void pause() {
}
@Override
public void resume() {
}
}