Editor utility that captures high-resolution screenshots from the editor scene. It prepares the scene (hiding UI or tagged objects), resolves or builds a camera (including a temporary freecam), applies post-processes, renders to a Bitmap with optional supersampling and resizing, and restores scene state.
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Editor.SuperShot;
public static class SuperShotCapture
{
public static Bitmap Capture( CaptureSettings settings )
{
var scene = SuperShotContext.ActiveScene;
if ( scene is null )
{
Log.Warning( "[Supershot] No active editor scene to capture." );
return null;
}
var (outW, outH) = SuperShotContext.ResolveSize( settings );
var ss = MathX.Clamp( settings.SuperSampling, 1f, 4f );
int renderW = Math.Clamp( (int)(outW * ss), 1, 16384 );
int renderH = Math.Clamp( (int)(outH * ss), 1, 16384 );
IDisposable restore = null;
try
{
restore = PrepareScene( scene, settings );
var bitmap = RenderToBitmap( scene, settings, renderW, renderH );
if ( bitmap is null )
return null;
if ( renderW != outW || renderH != outH )
{
var resized = bitmap.Resize( outW, outH );
bitmap.Dispose();
return resized;
}
return bitmap;
}
catch ( Exception e )
{
Log.Error( $"[Supershot] Capture failed: {e.Message}" );
return null;
}
finally
{
restore?.Dispose();
}
}
public static bool RenderPreview( Bitmap target, CaptureSettings settings )
{
var scene = SuperShotContext.ActiveScene;
if ( scene is null || target is null )
return false;
try
{
RenderInto( scene, settings, target );
return true;
}
catch
{
return false;
}
}
static Bitmap RenderToBitmap( Scene scene, CaptureSettings settings, int width, int height )
{
var bitmap = new Bitmap( width, height );
if ( !RenderInto( scene, settings, bitmap ) )
{
bitmap.Dispose();
return null;
}
return bitmap;
}
static bool RenderInto( Scene scene, CaptureSettings settings, Bitmap bitmap )
{
using ( scene.Push() )
{
GameObject tempGo = null;
try
{
var source = SuperShotContext.EffectiveSource( settings );
var cam = ResolveCamera( scene, settings, source, out tempGo );
if ( cam is null )
{
Log.Warning( "[Supershot] No camera available for capture." );
return false;
}
// FOV override only applies to an explicitly-chosen scene main camera; freecam and auto-attached
// game-camera shots mirror exactly what's on screen.
float? prevFov = null;
if ( settings.OverrideFov && settings.Source == CaptureSource.SceneMainCamera && !SuperShotContext.IsAttachedGameView )
{
prevFov = cam.FieldOfView;
cam.FieldOfView = settings.Fov;
}
var prevBg = cam.BackgroundColor;
if ( settings.TransparentBackground )
cam.BackgroundColor = Color.Transparent;
using ( SuperShotPostFx.Apply( cam.GameObject, settings.PostFx ) )
{
cam.RenderToBitmap( bitmap );
}
if ( prevFov.HasValue )
cam.FieldOfView = prevFov.Value;
if ( settings.TransparentBackground )
cam.BackgroundColor = prevBg;
return true;
}
finally
{
tempGo?.Destroy();
}
}
}
static CameraComponent ResolveCamera( Scene scene, CaptureSettings settings, CaptureSource source, out GameObject tempGo )
{
tempGo = null;
if ( source == CaptureSource.SceneMainCamera )
{
if ( scene.Camera.IsValid() )
return scene.Camera;
return scene.GetAllComponents<CameraComponent>().FirstOrDefault( c => c.IsMainCamera )
?? scene.GetAllComponents<CameraComponent>().FirstOrDefault();
}
if ( !SuperShotContext.TryResolveFreecam( out var freecam, out var freecamFov, out var freecamZNear, out var freecamZFar ) )
{
Log.Warning( "[Supershot] Couldn't read the editor 3D viewport camera. Move the scene view (or activate the Supershot tool) and try again." );
return null;
}
return BuildTempCamera( ref tempGo, settings, freecam, freecamFov, freecamZNear, freecamZFar );
}
static CameraComponent BuildTempCamera( ref GameObject tempGo, CaptureSettings settings, Transform view, float defaultFov, float zNear, float zFar )
{
tempGo = new GameObject( true, "SuperShot_Freecam" );
tempGo.Flags = GameObjectFlags.NotSaved | GameObjectFlags.Hidden;
tempGo.WorldPosition = view.Position;
tempGo.WorldRotation = view.Rotation;
var temp = tempGo.Components.Create<CameraComponent>();
temp.IsMainCamera = false;
temp.FovAxis = CameraComponent.Axis.Horizontal;
temp.FieldOfView = defaultFov;
temp.ZNear = MathX.Clamp( zNear, 1f, 1000f );
temp.ZFar = zFar;
temp.BackgroundColor = settings.TransparentBackground ? Color.Transparent : Color.Black;
// A bare CameraComponent renders raw linear HDR. Without a Tonemapping post-process the image
// blows out to near-white (the symptom that looks like "wrong source": a washed-out frame).
// The editor scene-view tonemaps with the Source 2 filmic curve, so attach the same default
// tonemapper here unless the user already enabled their own under Engine Post FX.
if ( !(settings.PostFx?.TonemapEnabled ?? false) )
{
var tonemap = tempGo.Components.Create<Tonemapping>();
tonemap.Mode = Tonemapping.TonemappingMode.HableFilmic;
tonemap.AutoExposureEnabled = true;
}
return temp;
}
static IDisposable PrepareScene( Scene scene, CaptureSettings settings )
{
var hiddenComponents = new List<Component>();
var hiddenObjects = new List<GameObject>();
if ( settings.HideUI )
{
foreach ( var panel in scene.GetAllComponents<PanelComponent>() )
{
if ( panel.IsValid() && panel.Enabled )
{
panel.Enabled = false;
hiddenComponents.Add( panel );
}
}
}
if ( !string.IsNullOrWhiteSpace( settings.HideTags ) )
{
var tags = settings.HideTags.Split( ',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries );
foreach ( var tag in tags )
{
foreach ( var obj in scene.GetAllObjects( true ).Where( o => o.Tags.Has( tag ) ) )
{
if ( obj.IsValid() && obj.Enabled )
{
obj.Enabled = false;
hiddenObjects.Add( obj );
}
}
}
}
return new RestoreScope( hiddenComponents, hiddenObjects );
}
sealed class RestoreScope : IDisposable
{
readonly List<Component> _components;
readonly List<GameObject> _objects;
public RestoreScope( List<Component> components, List<GameObject> objects )
{
_components = components;
_objects = objects;
}
public void Dispose()
{
foreach ( var c in _components )
{
if ( c.IsValid() ) c.Enabled = true;
}
foreach ( var o in _objects )
{
if ( o.IsValid() ) o.Enabled = true;
}
}
}
}