large screenshots for print

Hi.

I’m trying to figure out how to output some large screenshots for print. They need to be bigger than my maximum screen size and I’d like to get some input on how to aproach that to see if it’s something I should dive into.

  1. First thought is using a large rendertexture and saving that to disk, but I’m thinking the step from rendertexture to ‘file on harddisk’ is not very straightforward? Also, would there be any limitation to the size of the rendertexture?

  2. Another thought is to use the built-in CaptureScreenshot function somehow. Maybe a script that pauses the game, creates a camera that moves in a grid and takes multiple screenshots that can be stitched together outside unity?

  3. Should this be done with a plug-in?

Any input appreciated,

thanks.

/Patrik

The “saving to disk” bit is coming in Unity 2.0… other than that, render texture size is limited by hardware (and VRAM). A 4096x4096 render texture will probably take 96 or 128MB of VRAM.

This can be done. Note however that CaptureScreenshot does not execute immediately (because at script execution time nothing is rendered yet); it executes at the end of the frame. So you’d have to set time scale to zero, and then each frame position some camera to render only a portion of the image, and do CaptureScreenshot.

For perspective cameras getting “only render this portion of image” can involve some manual work. I.e. setting projection matrix yourself, so it can still maintain correct perspective but only render a part of the image.

Right, I’ve wandered off to these parts of the woods before and got completely lost :smiley: . Not sure I’ll get much out of it, but is there some good resources for learning abt projection matrices? ( I’ll google it of course :wink: )

Doing it orthogonally would work I guess, but that’s not good enough in most cases.

Thanks, Patrik

Note that doing this thing won’t work proper with most image filters (e.g. glow/blur type will overflow between your images).

Nintendo had a paper on a solution to this: render the same image multiple times, but use the projection matrix to do subpixel offsets. Then stitch them together on a pixel-by-pixel basis.

say, you screenshot 4 times:
at base pos
basePos + (.5,0) pixels
basePos + (.5,.5) pixels
basePos + (0,.5) pixels

then you put the pixels next to each other - so your screenshot order becomes:

0 1 0 1
3 2 3 2
0 1 0 1
3 2 3 2

this will still make glow be a bit wrong, but most likely you can’t see it - I’ve noticed these effects in a lot of hi-res prints from PS2 games.

If you want antialiasing, you just render more and blend some of them on top of each other…

the paper is in Game Programming Gems 4, written by Steve Rabin, and is called “Poster Quality Screenshots”. You’re welcome to drop by our office and borrow the book.

In Director you did this by rendering to an offset screen and saving the information from the rendering, may it by plugin or by pixel.

Things are so much simpler in my head…

Thanks :slight_smile: I’m pretty sure it’s way over my head, but I’ll come by this week and take a look at it.

/Patrik

Wow, this question touched on a problem I was just thinking about: how to render just 2/3 of the width/heigh of the camera’s view,–the bottom-right 2/3–but have that fill the screen.

My goal is to have the vanishing point of the screen be in the top left. You’d never see the top or left of the image, just the vanishing point, some space around it, the right side, and the bottom. Why? So that HUD stuff can overlay the bottom and right portions, leaving the vanishing point properly centered in the remaining open screen.

My first thought is to do this using Matrix4x4.TRS:

camera.projectionMatrix = camera.projectionMatrix * Matrix4x4.TRS( Vector3(HOffset,VOffset,0) , Quaternion.identity , Vector3.one );

And for purposes of printing/stitching, you’d need to zoom in, so you’d use a scale smaller than one:

camera.projectionMatrix = camera.projectionMatrix * Matrix4x4.TRS( Vector3(HOffset,VOffset,0) , Quaternion.identity , Vector3(ImageScale,ImageScale,1) );

I’ll report back.

Nope. Doesn’t work.

That offsets the camera itself, not the rendered rectangle–and thus perspective is not preserved.

I’ll start a new thread to inquire on that subject rather than risk pulling this one off track :slight_smile:

Thanks to the little side-track started by Morgan, and the matrix manipulation code by Aras i now have a small script to create large screenshots. It actually generates a series of screenshots that need to be manually stitched together afterwards, but it works.

Like Nicholas mentioned, this approach doesn’t work well with glow and other Image Effects.

So, the thing I want to get working now is some kind of automatic stitching of the individual screenshots. I’m thinking this needs to be done in a plug-in where I can create a large image file on the HD and paste the smaller ones into that. Anybody up for it? :slight_smile:

The code to generate the smaller screens look like this. To take a screenshot simply set takeScreenshot=true. the scale variable determines how big the resulting image is compared to the original, but it’s exponential so a value of 2 gives an image 4 times larger. It should be attached to the camera

using UnityEngine;
using System.Collections;

[RequireComponent (typeof (Camera))]
public class LargeScreenshot : MonoBehaviour {
	
	public int scale = 2;
	public bool takeScreenshot = false;
	
	private float left,right,top,bottom,w,h = 0;
	private float timeScaleReset = 1.0f;
	private bool takingScreenshot = false;
	
	void Update () {
		if(takeScreenshot){
			if(!takingScreenshot)
			StartCoroutine("TakeLargeScreenshot");
			takeScreenshot = false;
		}
	}
	
	void LimitsFromCameraProjectionMatrix(){
		Matrix4x4 m = camera.projectionMatrix;
		w = 2*camera.nearClipPlane/m.m00;
		h = 2*camera.nearClipPlane/m.m11;
	}
	
	IEnumerator TakeLargeScreenshot(){
		takingScreenshot=true;
		LimitsFromCameraProjectionMatrix();
		
		Camera cam = camera;
		//stop time
		timeScaleReset = Time.timeScale;
		Time.timeScale = 0;
		
		//Take partial screenshots
		int x = 0;
		int y = 0;
		int i = 0;
		int n = scale*scale;
		while(i<n){
			//Set up projection matrix
			left = x*w/scale-w/2;
			right = left+w/scale;
			bottom = y*h/scale-h/2;
			top = bottom+h/scale;
			Matrix4x4 m = PerspectiveOffCenter(left, right, bottom, top, cam.nearClipPlane, cam.farClipPlane ); 
			cam.projectionMatrix = m;
			//Capture screenshot
			Application.CaptureScreenshot("screenshot_"+x+"_"+y+".png");
			x++;
			if(x>=scale){
				x=0;
				y++;
			}
			i++;
			yield return 0;
		}
		//reset camera
		cam.ResetProjectionMatrix();
		//reset time
		Time.timeScale = timeScaleReset;
		takingScreenshot=false;
		yield return 0;
	}
	Matrix4x4 PerspectiveOffCenter(float left, float right, float bottom, float top, float near, float far){        
    	float x =  (2.0f * near)       / (right - left); 
    	float y =  (2.0f * near)       / (top - bottom); 
    	float a =  (right + left)     / (right - left); 
    	float b =  (top + bottom)     / (top - bottom); 
    	float c = -(far + near)       / (far - near); 
    	float d = -(2.0f * far * near) / (far - near); 
    	float e = -1.0f; 

    	Matrix4x4 m = Matrix4x4.zero; 
    	m[0,0] =   x;  m[0,1] = 0.0f;  m[0,2] = a;   m[0,3] = 0.0f; 
    	m[1,0] = 0.0f;  m[1,1] =   y;  m[1,2] = b;   m[1,3] = 0.0f; 
    	m[2,0] = 0.0f;  m[2,1] = 0.0f;  m[2,2] = c;   m[2,3] =   d; 
    	m[3,0] = 0.0f;  m[3,1] = 0.0f;  m[3,2] = e;   m[3,3] = 0.0f; 
    	return m; 
	} 
}

/Patrik

Good work Patrik. Looks like Wiki material to me. I will have to try this out.
AC

Cool!

And maybe the assembly automation could be done another way, like in Photoshop itself.

Something similar to this stitching was brought up at Cheetah3D User Forum and the solution was attributed to graphic converter. the example discussed was image sequence as filmstrip.

See:

I havent used it but it might be worth the effort invested to try it out.
AC

Credit should go to Aras, it’s completely dependent on his matrix manipulation code.

Using Photoshop or GraphicConverter is possible, but for simplicity it would be nice to have an ‘all-in-one’ function where you wouldn’t need any manual post processing. Also, doing a version using the technique Nicholas described would only be possible with some custom program/plug-in.

I’ve been looking at this article abt merging images in .NET using C#. I have no clue of how to do that in a plug-in, but if we can get that in place maybe it would be possible to start looking into the ‘Glow-safe’ method aswell :slight_smile:

/Patrik

Hello!

Now that 2.0 is out do we have this “save to disk”. If not what would be the way to save render texture to disk?

Yes. There is Texture2D.ReadPixels which reads pixels from the screen into a texture. Later you can do whatever you want with the pixels, e.g. using EncodeToPNG and so on.

Thanks Aras,

In documentation there is something about active RenderTexture. How can I set specific RenderTexture active or determine which MRT is active?

RenderTexture.active = …

Thanks again,

Missed that one. This works great. Here’s the code if anyone’s interested:

using UnityEngine;
using System.Collections;
using System.IO;

public class TakeHires : MonoBehaviour {
	
	private Texture2D ScreenShot;
	private RenderTexture rt;
	int res = 4096;
	
	// Use this for initialization
	void Start () {
		rt = new RenderTexture(res,res,24);
		ScreenShot=new Texture2D(res,res);
		camera.targetTexture=rt;
		StartCoroutine("take");
	}
	
	IEnumerator take() {
		yield return new WaitForEndOfFrame();
		RenderTexture.active = rt;
		ScreenShot.ReadPixels( new Rect(0, 0, res, res), 0, 0 );
		ScreenShot.Apply();
		
		byte[] bytes = ScreenShot.EncodeToPNG();
		Destroy( ScreenShot );
		File.WriteAllBytes(Application.dataPath + "/../SavedScreen.png", bytes);
	}
}

Thanks for doing this phantom, Dr Jones just pointed me here. I am not a coder so please excuse the ignorance, but I’m not quite getting the image I expected:
I set a save path in the script, attach the script to a camera and press play. The image saves to disc but as this:

I’ve added a black background to see it easier but basically only the house renders at full transparency. and the background terrain and tree bark textures are missing, only a black silhouette in their place.
Finally :slight_smile: would it be easy to convert this to JavaScript? I’m sort of starting to understand that a wee bit hehe.
TIA
Boxy

Yep:

import System.IO;

private var screenShot : Texture2D; 
private var rt : RenderTexture; 
var res : int = 4096; 
    
function Start () { 
	rt = new RenderTexture(res, res, 24); 
	screenShot = new Texture2D(res, res); 
	camera.targetTexture = rt;
	Take();
}

function Take () { 
	yield WaitForEndOfFrame(); 
	RenderTexture.active = rt; 
	screenShot.ReadPixels( new Rect(0, 0, res, res), 0, 0 ); 
	screenShot.Apply(); 
	 
	var bytes = screenShot.EncodeToPNG(); 
	Destroy( screenShot ); 
	File.WriteAllBytes(Application.dataPath + "/../SavedScreen.png", bytes); 
}

But yeah, it seems to not like taking pictures of the terrain for some reason…haven’t looked into that.

–Eric