Firstly, thanks Unity Answers community!
A synopsis of the situation: I’m trying to add content to a scene at runtime, send a secondary camera out to snap a single image of the content, and use that image as a texture (a thumbnail image rendered by the main camera).
I’m using currently using RenderTexture -->ReadPixels -->EncodeToPng → load WWW (the PNG file) which works, but it creates a new file for each screen grab. Is there a way I can internally use the ReadPixels to create a snapshot Texture2D from the RenderTexture without writing an external PNG?
I apologize for this haggard pastiche of code (it’s been pasted together from other Unity Answers):
nextThumbPosition = Vector3(thumbnailStart+(totalScenes*thumbnailSpacer)+(currentScene*sceneSpacer), thumbYValue, thumbZValue);
var newThumbnail = Instantiate(thumbnail, nextThumbPosition, Quaternion.identity);
newThumbnail.transform.parent = scrollPane.transform;
newThumbnail.transform.rotation = Quaternion.Euler(0,0,180);
newThumbnail.name = "Thumbnail_"+totalScenes.ToString();
var rt = new RenderTexture(resWidth, resHeight, 24);
thumbCamera.transform.position = Vector3(totalScenes*sceneSpacer,-19.55,-64.79);
thumbCamera.targetTexture = rt;
screenShot = new Texture2D(resWidth, resHeight, TextureFormat.RGB24, false);
RenderTexture.active = rt;
screenShot.ReadPixels(Rect(0, 0, resWidth, resHeight), 0, 0);
RenderTexture.active = null; // JC: added to avoid errors
var bytes = screenShot.EncodeToPNG();
System.IO.File.WriteAllBytes(Application.dataPath + "/Resources/screenshot"+totalScenes.ToString()+".png", bytes);
var file : String = Application.dataPath + "/Resources/screenshot"+totalScenes.ToString()+".png";
file = "file:"+file;
var www : WWW = new WWW (file);
newThumbnail.renderer.material.mainTexture = www.texture;
thumbCamera.active = false;
I’m looking for a way to texture the newThumbnail with the “screenShot” Texture2D without having to EncodeToPNG(). Is this possible?
Thank you for any feedback!
Thanks Dave. The reason I can’t use the RenderTexture directly as the mainTexture of the thumbnail plane is that the camera will continuously update the RenderTexture (unless I set it inactive), so I would need a dedicated camera for each thumbnail. I was looking for a way to create ten thumbnails without creating ten cameras, or ten PNG files, or ten of anything except what is absolutely necessary. Your response sent me to the GetPixels32/SetPixels32 documentation, of which there is very little! Below is my script reformulation. I still have a few unanswered questions related to this practice, though. The summary:
- The thumbnail camera assigns the targetTexture as the active RenderTexture
- A fresh Texture2D “thumbShot” runs ReadPixels
- GetPixels32 prints the pixels into the thumbPixels array
- SetPixels32 sets and applies the pixels into the thumbShot Texture2D
- Texture2D thumbShot is applied as the mainTexture to the thumbnailPlane.
That this works seems miraculous to me. How does the thumbShot.ReadPixels know that the reading refers to the thumbnailCamera? Is it because “rt” is the active RenderTexture?
Also, I’m wondering if anyone can point out any glaring problems regarding memory management with the script below, in light of the fact that there may be hundreds of these thumbnails created in a scene.
(1)Instantiates Thumbnail Plane as Scene_XXX_Thumbnail.
(2)Sends ThumbnailCamera to Position.
(3)Updates Texture on Thumbnail Plane with Thumbnail Camera Snapshot.
- Get static Vector3 'newThumbnailPosition' as 'thisThumbnailPosition'
- Establish the thumbnailPlane as ThumbnailPlane prefab
- Instantiate Thumbnail Plane at thisThumbnailPosition
- Make a new material if necessary
- MoveTo thisThumbnailPosition
- iTween.MoveTo "oncomplete", Snapshot()
- Set Up RenderTexture "rt" and a new blank Texture2D "thumbShot", ReadPixels(?)
- GetPixels32 from the read pixels
- SetPixels32 to Thumbnail Texture "thumbShot"
var thumbnailResolutionX : int;
var thumbnailResolutionY : int;
var thumbPixels : Color32;
var thisThumbnailPosition = Vector3();
var thisScenePosition = Vector3();
var thumbnailCameraPosition = Vector3();
var thumbnailPlanePrefab : GameObject;
var thumbnailPlane : GameObject;
var thumbnailCamera : Camera;
thisThumbnailPositon = Vector3(-5.21,-2.98,0);
thumbnailCameraPosition = Vector3(-.14,-10.05,-10);
thumbnailCamera = GameObject.Find("ThumbnailCamera").camera;
thumbnailResolutionX = 512;
thumbnailResolutionY = 512;
thumbnailPlane = Instantiate(thumbnailPlanePrefab,thisThumbnailPosition,Quaternion.Euler(90,180,0));
var rt = new RenderTexture(thumbnailResolutionX, thumbnailResolutionY, 24);
var thumbShot = new Texture2D(thumbnailResolutionX, thumbnailResolutionY, TextureFormat.RGB24, false);
thumbnailCamera.targetTexture = rt;
RenderTexture.active = rt;
thumbShot.ReadPixels(Rect(0, 0, thumbnailResolutionX, thumbnailResolutionY), 0, 0);
thumbPixels = thumbShot.GetPixels32();
RenderTexture.active = null;
thumbnailPlane.renderer.material.mainTexture = thumbShot;
Why not just avoid it all together? Can’t you just use the RT as the texture of the thumbnail? If not, you could use ReadPixels from the RT and SetPixels to another 2D texture which is your thumbnail, if you needed to do some processing in between or something. (Note ReadPixels32/SetPixels32 much faster).
The magic bit you were missing was the thumbShot.Apply(); the get/set pixels calls are wasted cycles.