Skip to content

Instantly share code, notes, and snippets.

@jasonswearingen
Last active October 5, 2024 15:07
Show Gist options
  • Save jasonswearingen/953b0561aaf503ad41038527dcedb4b2 to your computer and use it in GitHub Desktop.
Save jasonswearingen/953b0561aaf503ad41038527dcedb4b2 to your computer and use it in GitHub Desktop.
PhotoBooth: Render Images of 3D objects in-game.
//This code utilizes some of my private godot c# scafolding, so unfortunately you can't just copy/paste it.
// but the workflow is general to godot 4.3, so just follow the _Setup() workflow and then the workflow to use it is
// - AddToStage()
// - TakePicture()
// - ClearStage()
/// <summary>
/// PhotoBooth class represents a virtual photo booth for capturing images of 3D scenes.
/// It uses a separate scene with a Viewport to render the content, allowing snapshots to be taken with different configurations.
/// </summary>
using env;
using Godot;
using lib.Base;
/// <summary>
/// A class representing a photo booth for capturing 3D scenes.
/// </summary>
public partial class PhotoBooth : NN_Node_Base
{
/// <summary>
/// Reference to a SubViewport used to render the scene
/// </summary>
private SubViewport subViewport;
/// <summary>
/// Reference to the main stage of the photo booth scene
/// </summary>
private Node3D stage;
/// <summary>
/// Called when the node is added to the scene.
/// Initializes the PhotoBooth by setting up the viewport and camera.
/// </summary>
protected override void NN_Ready()
{
base.NN_Ready();
_Setup();
}
/// <summary>
/// Sets up the PhotoBooth by creating the viewport, the stage, and adding necessary components like the camera.
/// </summary>
private void _Setup()
{
// Create a root node for the photo booth content
var photoBooth = new Node3D();
_AddChild(photoBooth);
// Create a SubViewport with specific settings
subViewport = new SubViewport()
{
Size = new Vector2I(512, 512), // Set the render size of the viewport
TransparentBg = true, // Enable transparency
OwnWorld3D = true, // Use an independent 3D world
RenderTargetUpdateMode = SubViewport.UpdateMode.Once, // Update the viewport only once
};
subViewport._AddChild(new PhotoBoothEnvironment()); // Add environment settings to the viewport
// Create a new stage Node3D to hold the 3D objects being captured
stage = new Node3D();
subViewport._AddChild(stage);
// Create a new Camera3D to be used in the separate rendering scene
Camera3D camera = new Camera3D
{
Position = Vector3.Back, // Position the camera to see the content properly
Current = true, // Set this camera to be active in the viewport
};
// Add the camera to the viewport
subViewport._AddChild(camera);
// Add the viewport to the root photo booth node
photoBooth._AddChild(subViewport);
}
/// <summary>
/// Adds a Node3D object to the stage at the given transform.
/// The node should not already be in the scene; duplication is required if it is.
/// </summary>
/// <param name="node">The Node3D to be added to the stage.</param>
/// <param name="xform">The Transform3D specifying the location and rotation of the node.</param>
public void AddToStage(Node3D node, Transform3D xform)
{
__.Assert(node.IsInsideTree() is false, "Cannot take picture of an object already in the scene. Duplicate it first.");
node.Transform = xform * node.Transform;
stage.AddChild(node);
}
/// <summary>
/// Adds a Node3D object to the stage with the option to automatically adjust focus.
/// </summary>
/// <typeparam name="TNode">The type of Node3D being added.</typeparam>
/// <param name="node">The Node3D to be added to the stage.</param>
/// <param name="xform">The Transform3D specifying the location and rotation of the node.</param>
/// <param name="autoFocus">If true, automatically adjusts the transform to focus on the node's center.</param>
public void AddToStage<TNode>(TNode node, Transform3D xform, bool autoFocus) where TNode : Node3D, IAabb
{
if (autoFocus)
{
var aabb = node.GetLocalAabb()._Transformed(xform); // Calculate transformed AABB
var center = aabb.GetCenter(); // Get the center of the AABB
xform.Origin -= center; // Adjust origin to center the object
}
AddToStage(node, xform);
}
/// <summary>
/// Clears all the content from the stage.
/// </summary>
public void ClearStage()
{
stage.QueueFree(); // Free the current stage
stage = new Node3D(); // Create a new empty stage
subViewport._AddChild(stage); // Add the new stage to the viewport
}
/// <summary>
/// Takes a picture of the current state of the viewport and returns it as a Texture2D.
/// </summary>
/// <returns>A Texture2D representing the rendered image of the viewport.</returns>
public Texture2D TakePicture()
{
subViewport.RenderTargetUpdateMode = SubViewport.UpdateMode.Once; // Set the viewport to update once to capture the current frame
return subViewport.GetTexture(); // Return the rendered texture
}
}
//just a helper to setup the environment and lighting
using Godot;
using lib.Base;
using Environment = Godot.Environment;
namespace env;
public partial class PhotoBoothEnvironment : NN_Node_Base
{
protected override void NN_Ready()
{
base.NN_Ready();
var worldEnv = new WorldEnvironment()
{
Environment = new()
{
BackgroundMode = Environment.BGMode.Color,
BackgroundColor = Colors.Beige,
//GlowEnabled = true,
//GlowBlendMode = Environment.GlowBlendModeEnum.Additive,
//AdjustmentEnabled = true,
//AdjustmentBrightness = 1.45f,
//AdjustmentContrast = 1.08f,
}
};
_AddChild(worldEnv);
//ambient light
//extra light from same dir as sun, but no shadows
var ambientLight = new DirectionalLight3D()
{
LightColor = Colors.DarkGray,
RotationDegrees = new(-60, 150, 0),
LightEnergy = 0.2f,
LightSpecular = 0f,
LightIndirectEnergy = 0f,
//ShadowEnabled = true,
LightVolumetricFogEnergy = 0,
//LightNegative = true,
};
this._AddChild(ambientLight);
//also from opposite direction
var ambientLight2 = ambientLight.Duplicate() as DirectionalLight3D;
ambientLight2.RotationDegrees *= -1;
this._AddChild(ambientLight2);
//and again, rotated 180' around y axis
var ambientLight3 = ambientLight2.Duplicate() as DirectionalLight3D;
ambientLight3.RotateY(_PI);
this._AddChild(ambientLight3);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment