How to take a screenshot from any position using CaptureService

So I did a small research on the CaptureService.
The question was, can you take a screenshot from any position without too many side effects?

The first thing that came to my mind was swapping the camera for another, making a capture and returning to the normal camera. (It’s possible to change the current camera to another camera)

The project was made on TypeScript, the examples will be on TypeScript.
The final code for this will be in Luau and TypeScript

Tip:
() => {} in TS means
function() end in Luau

To do this, we cannot take the camera from the Player for too long.
The idea was to change the camera before the screenshot
and then restore it in the CaptureScreenshot function

//set camera here
CaptureService.CaptureScreenshot((temp_image_id: string) =>{
//restore camera here
})

but if we benchmark it, you will see that it takes a gigantic amount of time between these 2 events
It takes 8 frames!

//set camera here
const start = os.clock()
CaptureService.CaptureScreenshot((temp_image_id: string) =>{
  const finish = os.clock();
  print(finish - start); //takes upto 0.14 seconds = 8 frames
  //restore camera here
})

We don’t want to take a camera for that long.
So we need a little bit different way of doing things.

I made an experiment where I found that CaptureService makes a screenshot only on the following frame (waits until the current ends) and then makes a screenshot during the render - in between PreRender and PreAnimation.

So to decrease the amount of frames it takes, we need to act only in one frame period.
As you know it waits until the next frame to start capture. We will just wait until the last RunService Event. And we will start executing the actions.

But when do we restore the camera if it takes 8 frames to send the temp_image_id to us?
The answer is right after the render. Our code looks like this

//wait til the frame ends and send the request
RunService.PostSimulation.Wait();
//set camera
//Makes a screenshot only on the next frame
CaptureService.CaptureScreenshot((temp_image_id: string) => {
  //get an image
});
//comes right after Render so it takes only 1 frame for it to return the camera;
RunService.PreAnimation.Wait();
//restore camera

Then how do we pass the image_id further? We can use a Bindable event and just wait for the result.

//we don't even need to put a parent to the BindableEvent, we can connect directly
const done_event: BindableEvent<(image_id: string) => void> = new Instance("BindableEvent");

//start of the function
CaptureService.CaptureScreenshot((temp_image_id: string) => {
  done_event.Fire(temp_image_id);
});

//end of the function
//returns the temp_image_id after finished;
return done_event.Event.Wait()[0];

The final code looks like

Luau

-- Compiled with roblox-ts v2.3.0
local CaptureService = game:GetService("CaptureService")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local ScreenShoter = {}
do
	local _container = ScreenShoter
	local flash_camera = Instance.new("Camera", Workspace)
	flash_camera.CameraType = Enum.CameraType.Scriptable
	local done_event = Instance.new("BindableEvent")
	local function MakeAScreenShotAtCFrame(cframe)
		--cashing the camera to put the new camera at the cframe;
		local cashed_camera = Workspace.CurrentCamera
		local cashed_camera_parent = cashed_camera.Parent
		--wait til the frame ends and send the request
		RunService.PostSimulation:Wait()
		flash_camera.CFrame = cframe
		Workspace.CurrentCamera = flash_camera
		--Makes a screenshot only on the next frame
		CaptureService:CaptureScreenshot(function(temp_image_id)
			done_event:Fire(temp_image_id)
			--get an image
		end)
		--comes right after Render so it takes only 1 frame for it to return the camera;
		RunService.PreAnimation:Wait()
		--camera changes Parent to nil if was changed
		--restores the original camera Parent;
		cashed_camera.Parent = cashed_camera_parent
		Workspace.CurrentCamera = cashed_camera
		return (done_event.Event:Wait())
	end
	_container.MakeAScreenShotAtCFrame = MakeAScreenShotAtCFrame
end
return {
	ScreenShoter = ScreenShoter,
}

TS

import { CaptureService, RunService, Workspace } from "@rbxts/services";

export namespace ScreenShoter {
  const flash_camera = new Instance("Camera", Workspace);
  flash_camera.CameraType = Enum.CameraType.Scriptable;

  const done_event: BindableEvent<(image_id: string) => void> = new Instance("BindableEvent");
  export function MakeAScreenShotAtCFrame(cframe: CFrame) {
    //cashing the camera to put the new camera at the cframe;
    const cashed_camera = Workspace.CurrentCamera!;
    const cashed_camera_parent = cashed_camera.Parent;

    //wait til the frame ends and send the request
    RunService.PostSimulation.Wait();
    flash_camera.CFrame = cframe;
    Workspace.CurrentCamera = flash_camera
    //Makes a screenshot only on the next frame
    CaptureService.CaptureScreenshot((temp_image_id: string) => {
      done_event.Fire(temp_image_id);
      //get an image
    });
    //comes right after Render so it takes only 1 frame for it to return the camera;
    RunService.PreAnimation.Wait();
    //camera changes Parent to nil if was changed
    //restores the original camera Parent;
    cashed_camera.Parent = cashed_camera_parent;
    Workspace.CurrentCamera = cashed_camera;

    return done_event.Event.Wait()[0];
  };
}

I would like to tell you about some bugs that I found.

  • If you are taking a capture twice in a frame it’s going to stop CaptureService (fix Restart Studio)
  • EditableImage made with AssetService exceeds the size limit of 1024 (1430 x 770)
  • You are not able to delete the temp files and that just stacks in the RAM (each image is 200 - 300kb) (fix Restart Studio, it occupies a lot of RAM immediately after ~500 MB)
  • Capture Service refuses to work if you clean up memory with software like MSI Center (fix Restart Studio)

If you are able, can you send this pls to the bug report?

If you have any ideas about where it could be useful, could you tell me? I’m very curious :face_with_raised_eyebrow:

16 Likes

Wow, I didn’t even realize this was possible…thanks for sharing!

Salute to a fellow TS member :saluting_face:

2 Likes

You’re welcome fellow TS member! :saluting_face:
That was really fun to explore

2 Likes

Thank you for this tutorial. big bump but it deserves it since the captureservice api still hasn’t received any updates to arbitrary camera usage, or really any of the bugs you’ve listed.

Until this is fixed though, very useful resource! :+1:

This means I can do screen space reflections? ON MESHES?

prob not, that thing was like a concept. it’s going to be too slow for that and maybe will cause a ceisure :melting_face:

1 Like

Beamng players don’t care about the reflection being updated every second so….

Already tried this for planar reflections, issues is that 1. it’ll capture player’s UI (including coregui) and 2. you will be able to notice the camera swap here and there. hopefully they’ll give us an official feature for arbitrary camera capturing but :man_shrugging:

1 Like

Like in the hood camera it would work flawlessly (apart from the gui) its just a test to see if its possible

dude my pc will explode if i put the high reflections :sob::sob::sob: im fine with my like 1 reflection per second

1 Like


just tried and this is the result :sob: