Hi, everyone. I’ve been playing around with Unity 2.6 for a few days now, and I’ve spent a lot of time using the forum search function to answer all of my n00b questions up to this point, which turned out nicely. (Thanks to all of the posters involved in those threads!)
'Kay, here’s my thing. I’ve figured out how the orthographic camera works, and I have a pretty good idea of how to use it to take giant screenshots suitable for printing. So, the plan is to set up an orthographic scene, then move the orthographic camera from left to right, top to bottom, screencapping a rectangle at each grid point and saving it to disk. Here’s where my questions come in:
One thing I’m not sure about is the best way to stitch those screenshots together and resize them. From reviewing the threads I searched for here, I gather there isn’t really a good way to do that from Unity itself. Any ideas here? I’m thinking I might have to use ImageMagick or something.
This might be a really stupid question, but does the orthographic camera’s viewport actually have to be visible on the screen in order to take a screen capture? In other words, can I “hide” the orthographic camera from the end user and still generate the screenshots?
The application in question is intended to allow the user to build 3D models out of prefab blocks. The orthographic stuff above is for generating print-resolution top/bottom/side/front/back views that eventually end up in a PDF.
If you have Unity Pro, you can use a RenderTexture to make an image from the current view of any camera. In general, the output from this camera won’t be visible onscreen directly, so the user need not be aware of the capture taking place. You can set the resolution of the RenderTexture to anything you like, so rather than move the camera and stitch the grabbed images together, you could just use one wide orthographic view for the whole thing.
Saving the view is slightly tricky… you need to set RenderTexture’s active property (a class variable) to the texture you are rendering to. Then, use Texture2D.ReadPixels to get the image into an ordinary texture. This texture can then finally be made into an image using Texture2D.EncodeToPNG (the doc page for this function contains an example of using ReadPixels).
Without Unity Pro, it’s much less streamlined. The process would be pretty much as you suggested - show the image, get a screenshot, save it and then use something like ImageMagick to postprocess the set of images.
Thanks! I’m constantly amazed by how helpful and supportive this forum is, not to mention the hidden nuggets of awesome that I keep discovering as I get more acquainted with Unity itself.
I don’t have Pro yet, but I’m saving up my pennies for it, and so will file that for future reference.
One concern I have, though, is that a huge RenderTexture might easily bring an anemic system to its knees by gobbling VRAM like candy, and might not even work at all on machines that don’t support NPOT dimensions or texture sizes larger than 1024x1024. A print resolution US Letter page at 300 dots per inch comes out to around 2550x3300 pixels, and an A4 page would be 2490x3510 pixels. So, I’d probably still want to tile the screenshots as NPOT “cells” no larger than 512x512 pixels just to further ensure that it’ll work on machines with anemic GPUs.
Yeah, I figured on doing that to get the segments onto disk. I’m glad they included that function.
An alternative I was considering was to roll my own stitcher in a Mono-targeted IDE, bundle it with the Unity standalone players, and invoke it from the Unity application. I think it can call other applications–I need to review the documentation again, but I figured using a command line string along the lines of “mono stitchomatic.exe -path_to_image_files -output filename” might work on the Mac, while the Windows command line string would omit the “mono” part. I have to do more research and nail things down a bit more.
Nice! I wouldn’t have thought of that. I’ll definitely give that approach a try, as it would work for the non-Pro version and give me something to test the idea with. After all, the more awesome the app I make in the free Unity is, the easier it is to justify the upgrade to Pro sooner.
I’m having a problem with this. If I omit the RenderTexture code, and take a texture within screen bounds (ie. 256x256) it’s fine. But as soon as I add the RenderTexture stuff back in - even while keeping things to 256x256, I get a blank/black png file. Any ideas?
private void GetScreenshot()
{
var ratio = Camera.mainCamera.aspect;
const int pWidth = 4961;
var pHeight = (int)(pWidth / ratio);
var rt = new RenderTexture(pWidth, pHeight, 24);
rt.Create();
RenderTexture.active = rt;
new WaitForEndOfFrame();
var t = new Texture2D(pWidth, pHeight, TextureFormat.RGB24, false);
t.ReadPixels(new Rect(0, 0, pWidth, pHeight), 0, 0, false);
t.Apply();
var bytes = t.EncodeToPNG();
// rt.Release();
// RenderTexture.active = null;
Destroy(t);
File.WriteAllBytes("C:/hat.png", bytes);
}
And the new version which instead is a script that attaches itself to the main camera, takes the screenshot and destroys itself. Still takes a black screenshot with no errors or warnings in the console
Like before, it does the same at a size of 256, and the _rtChanged thing is there to force the Texture.ReadPixels to occur AFTER the RenderTexture is assigned.
using System.IO;
using UnityEngine;
public class Screenshot : MonoBehaviour
{
private bool _initialized;
private bool _rtChanged;
private int _width;
private int _height;
private string _path;
public static Screenshot Take(int width, string path)
{
var s = Camera.mainCamera.gameObject.AddComponent<Screenshot>();
s._width = width;
s._height = (int)(width / Camera.mainCamera.aspect);
s._path = path;
s._initialized = true;
return s;
}
public void OnPostRender()
{
if (!_initialized) return;
var rt = new RenderTexture(_width, _height, 24);
rt.Create();
RenderTexture.active = rt;
if (!_rtChanged)
{
_rtChanged = true;
return;
}
var t = new Texture2D(_width, _height, TextureFormat.RGB24, false);
t.ReadPixels(new Rect(0, 0, _width, _height), 0, 0, false);
t.Apply();
var bytes = t.EncodeToPNG();
Destroy(t);
File.WriteAllBytes(_path, bytes);
DestroyImmediate(this);
}
}