I have a camera in my scene that has a Target Texture set as a Render Texture. I want that camera to render the scene into the Render Texture, but then stop rendering anymore, essentially freezing the frame into the Render Texture. How can I achieve this? Disabling the camera or setting its target texture to null seems to just make the Render Texture appear invisible.
If you want to have like a snapshot function you could do it like this
public class SnapshotController : MonoBehaviour
{
[Tooltip("If you want to capture a specific camera drag it here, otherwise the MainCamera will be used")]
[SerializeField] private Camera _camera;
[Tooltip("If you have a specific RenderTexture for the snapshot drag it here, otherwise one will be generated on runtime")]
[SerializeField] private RenderTexture _renderTexture;
private void Awake()
{
if(!_camera) _camera = Camera.main;
if(!_renderTexture)
{
_renderTexture = new RenderTexture(Screen.width, Screen.height , 24 , RenderTextureFormat.ARGB32);
_renderTexture.useMipMap = false;
_renderTexture.antiAliasing =1;
}
}
public void Snapshot(Action<Texture2D> onSnapshotDone)
{
StartCoroutine(SnapshotRoutine(onSnapshotDone));
}
private IEnumerator SnapshotRoutine (Action<Texture2D> onSnapshotDone)
{
// this also captures gui, remove if you don't wanna capture gui
yield return new WaitForEndOfFrame();
// If RenderTexture.active is set any rendering goes into this RenderTexture
// instead of the GameView
RenderTexture.active = _renderTexture;
_camera.targetTexture = _renderTexture;
// renders into the renderTexture
_camera.Render();
// Create a new Texture2D
var result = new Texture2D(Screen.width,Screen.height,TextureFormat.ARGB32,false);
// copies the pixels into the Texture2D
result.ReadPixels(new Rect(0,0,Screen.width,Screen.height),0,0,false);
result.Apply();
// reset the RenderTexture.active so nothing else is rendered into our RenderTexture
RenderTexture.active = null;
_camera.targetTexture = null;
// Invoke the callback with the resulting snapshot Texture
onSnapshotDone?.Invoke(result);
}
}
You would then use it like e.g.
// Pass in a callback telling the routine what to do when the snapshot is ready
xy.GetComponent<SnapshotController>(). Snapshot(HandleNewSnapshotTexture);
...
private void HandleNewSnapshotTexture (Texture2D texture)
{
var material = GetComponent<Renderer>().material;
// IMPORTANT! Textures are not automatically GC collected.
// So in order to not allocate more and more memory consider actively destroying
// a texture as soon as you don't need it anymore
if(material.mainTexture) Destroy (material.mainTexture);
material.mainTexture = texture;
}
You can take a snapchot of the actual render texture in the frame where you start freezing, get this image data and replace the render texture by an actual texture with this freeze image applied.
Related
In Unity, I'd like to generate 2D map from a large image file (965012573).
I already have a tile/image pieces generator but it generates 1900 images 256256, and it seems hard to create a map by hand like this (and I have more then one large image to process...).
These image pieces are sorted like this :
x/y.png
Where x starts from left to right and y from top to bottom.
As MelvMay suggests me here, I should use textures to achieve that, but how?
Do you have any idea of how to achieve that programmatically? Or may be with an existing bundle?
Thanks a lot!
[edit] As a workaround, I tried this :
public class MapGenerator : MonoBehaviour {
private Renderer m_Renderer;
private Texture2D m_MainTexture;
// Use this for initialization
void Start () {
//Fetch the Renderer from the GameObject
m_Renderer = GetComponent<Renderer> ();
m_MainTexture = LoadPNG("Assets/Tiles/Ressources/Map/0/3.png");
m_Renderer.material.mainTexture = m_MainTexture;
}
private Texture2D LoadPNG(string filePath) {
Texture2D tex = null;
byte[] fileData;
if (File.Exists(filePath)) {
UnityEngine.Debug.Log(filePath);
fileData = File.ReadAllBytes(filePath);
tex = new Texture2D(2, 2);
tex.LoadImage(fileData); //..this will auto-resize the texture dimensions.
}
return tex;
}
}
But even if I apply it on an empty object, I only see my dynamic texture in the inspector, not in the scene when I press run...
I'm buidling a smooth transtion from a level scene to a menu scene.
The idea is to make a screenshot of the level just before the next scene(menu) loads.
then take this screenshot which overlays the entire screen and will fade out to reveal the menu.
At the end of a level I take a screen shot.
ScreenCapture.CaptureScreenshot("Resources/DestroyedBoss.png");
When I load the next scene this screenshot should load and overlay the entire scene.
public Texture2D myTexture;
void Start()
{
// load texture from resource folder
myTexture = Resources.Load("DestroyedBoss") as Texture2D;
GameObject rawImage = GameObject.Find("RawImage");
rawImage.GetComponent<RawImage>().texture = myTexture;
}
The screenshot will fade and the object containing it will be destroyed.
public RawImage imageToFade;
void Update()
{
imageToFade.color -= new Color(0, 0, 0, 0.2f * Time.deltaTime);
if (imageToFade.color.a < 0.1)
Destroy(this.gameObject);
}
This all works fine, except for loading of the screenshot itself.
It would seem that unity does not update the image while running the game. And always uses the screenshot from the previous time I ran the game.
I have been searching/googling and trying for 2 days now and I am at a loss.
How can I make this work? Is there a way to update the png file while running my game? Maybe create a temp container for the screenshot and set this to the RawImage?
Like derHugo commented. you can use Application.persistentDataPath to save your image.
ScreenCapture.CaptureScreenshot(Application.persistentDataPath+"DestroyedBoss.png");
For loading the image, you can use UnityWebRequestTexture.GetTexture.
IEnumerator SetImage()
{
yield return new WaitForSeconds(1);
using (UnityWebRequest uwr =
UnityWebRequestTexture.GetTexture(Application.persistentDataPath + "DestroyedBoss.png"))
{
yield return uwr.SendWebRequest();
if (uwr.isNetworkError || uwr.isHttpError)
{
Debug.Log(uwr.error);
}
else
{
// Get downloaded asset bundle
myTexture = DownloadHandlerTexture.GetContent(uwr);
GameObject rawImage = GameObject.Find("RawImage");
rawImage.GetComponent<RawImage>().texture = myTexture;
FadeOut.instance.StartFade();
}
}
}
It works ok, but it takes a few seconds for the screenshot to appear in the folder. So for me... it does not work great.
I'll be using another technique.
Instead of fading the screenshot while loading the menu. I'll stop the game, (Time.timescale = 0) and fade in a screenshot of the menu, then load the actual menu scene.
Thanks again derHugo.
I am trying to take a photo through the Hololens with my PhotoCapture unity script. I want to use a Vuforia Engine ARCamera for the possibility to see the reality at the same time as I see the AR-GUI I've created (for future functionality).
The main error I get is:
Failed capturing photo (hr = 0xC00D3704)
Why does it occur? How do I fix it?
The FocusManager singleton has not been initialized.
UnityEngine.Debug:Assert(Boolean, String)
HoloToolkit.Unity.Singleton`1:AssertIsInitialized() (at
Assets/HoloToolkit/Common/Scripts/Singleton.cs:51)
HoloToolkit.Unity.CanvasHelper:Start() (at
Assets/HoloToolkit/Utilities/Scripts/CanvasHelper.cs:30)
is also an error occurring when starting the unity scene, but I haven't had it before...
This is code that I am using, placed on an ARCamera (have also tried out mixed reality camera with a vuforia behavior script and it didn't get the second error). Also, I want to apologize to the person who this code is borrowed from because I don't remember the link to your site.
public class PhotoCaptureExample : MonoBehaviour
{
PhotoCapture photoCaptureObject = null;
Texture2D targetTexture = null;
public string path = "";
CameraParameters cameraParameters = new CameraParameters();
void Start()
{
}
void Update()
{
if (Input.GetKeyDown("k"))
{
Debug.Log("k was pressed");
Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
// Create a PhotoCapture object
PhotoCapture.CreateAsync(false, delegate (PhotoCapture captureObject)
{
photoCaptureObject = captureObject;
cameraParameters.hologramOpacity = 0.0f;
cameraParameters.cameraResolutionWidth = cameraResolution.width;
cameraParameters.cameraResolutionHeight = cameraResolution.height;
cameraParameters.pixelFormat = CapturePixelFormat.BGRA32;
// Activate the camera
photoCaptureObject.StartPhotoModeAsync(cameraParameters, delegate (PhotoCapture.PhotoCaptureResult result)
{
// Take a picture
photoCaptureObject.TakePhotoAsync(OnCapturedPhotoToMemory);
});
});
}
}
string FileName(int width, int height)
{
return string.Format("screen_{0}x{1}_{2}.png", width, height, DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"));
}
void OnCapturedPhotoToMemory(PhotoCapture.PhotoCaptureResult result, PhotoCaptureFrame photoCaptureFrame)
{
// Copy the raw image data into the target texture
photoCaptureFrame.UploadImageDataToTexture(targetTexture);
Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
targetTexture.ReadPixels(new Rect(0, 0, cameraResolution.width, cameraResolution.height), 0, 0);
targetTexture.Apply();
byte[] bytes = targetTexture.EncodeToPNG();
string filename = FileName(Convert.ToInt32(targetTexture.width), Convert.ToInt32(targetTexture.height));
//save to folder under assets
File.WriteAllBytes(Application.dataPath + "/Snapshots/" + filename, bytes);
Debug.Log("The picture was uploaded");
// Deactivate the camera
photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
}
void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
{
// Shutdown the photo capture resource
photoCaptureObject.Dispose();
photoCaptureObject = null;
}
}
It seems that I can't get to OnCapturedPhotoToMemory or if it breaks already by the method call. Trying it out again right now the code occasionally won't register that I have pushed k at all...
Any help is appreciated!!
The problem is: Vuforia's VuforiaBehaviour on the camera holds the access on the device's real camera hardware => nothing else can use it meanwhile.
For fixing this you could use a dedicated camera for Vuforia (simply somewhere place a new GameObject e.g. VuforiaCamera in the scene and attach a Camera component as well as a VuforiaBehaviour to it.
On the Vuforia Camera set Culling Mask to Nothing (we don't render anything with that camera) and Depth to e.g. -2 (higher values are rendered on top -> put this behind all other cameras).
You have to do this because otherwise Vuforia automatically adds it to the main camera (which we don't want to disable because then we don't see anything). By manually adding one to the scene Vuforia automatically uses that one instead.
Everywhere in your scene where you need a Camera ofcourse you use the original camera from the Holo-Tookit (your usual MainCamera). Problem you can't fully rely on Camera.main in scripts because on runtime the VuforiaBehaviour automatically marks its Camera as MainCamera as well ... (-_-) Vuforia ... so additionally I always disabled and enabled the VuforiaBehaviour along with the GameObject but maybe it is enough already to only disable and enable the GameObject. At least at GameStart it should be disabled I guess until everything relying on Camera.main is finished.
Then you can simply disable that VuforiaCamera that has the VuforiaBehaviour on it.
VuforiaBehaviour.Instance.gameObject.SetActive(false);
// Note: that part I'm just inventing since I did it different
// using reference etc. I hope vuforia does not destroy the Instance
// OnDisable .. otherwise you would need the reference instead of using Instance here
// but maybe it is already enough to just enable and disable the GameObject
VuforiaBehaviour.Instance.enabled = false;
By doing this the device's camera was "free" and PhotoCapture can now use it.
PhotoCapture.StartPhotoModeAsync(....
If you then need the camera again for vuforia, first stop the PhotoCapture
PhotoCapture.StopPhotoModeAsync(..
and then after it stopped in the callback re-enable the ARCamera (object with the VuforiaBehaviour) and the VuforiaBehaviour again.
Something like
private void Awake()
{
var cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
// Create a PhotoCapture object
PhotoCapture.CreateAsync(false, captureObject =>
{
photoCaptureObject = captureObject;
cameraParameters.hologramOpacity = 0.0f;
cameraParameters.cameraResolutionWidth = cameraResolution.width;
cameraParameters.cameraResolutionHeight = cameraResolution.height;
cameraParameters.pixelFormat = CapturePixelFormat.BGRA32;
});
}
private void Update()
{
// if not initialized yet don't take input
if (photoCaptureObject == null) return;
if (Input.GetKeyDown("k"))
{
Debug.Log("k was pressed");
VuforiaBehaviour.Instance.gameObject.SetActive(false);
// Activate the camera
photoCaptureObject.StartPhotoModeAsync(cameraParameters, result =>
{
if (result.success)
{
// Take a picture
photoCaptureObject.TakePhotoAsync(OnCapturedPhotoToMemory);
}
else
{
Debug.LogError("Couldn't start photo mode!", this);
}
});
}
}
private static string FileName(int width, int height)
{
return $"screen_{width}x{height}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.png";
}
private void OnCapturedPhotoToMemory(PhotoCapture.PhotoCaptureResult result, PhotoCaptureFrame photoCaptureFrame)
{
// Copy the raw image data into the target texture
photoCaptureFrame.UploadImageDataToTexture(targetTexture);
Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
targetTexture.ReadPixels(new Rect(0, 0, cameraResolution.width, cameraResolution.height), 0, 0);
targetTexture.Apply();
byte[] bytes = targetTexture.EncodeToPNG();
string filename = FileName(Convert.ToInt32(targetTexture.width), Convert.ToInt32(targetTexture.height));
//save to folder under assets
File.WriteAllBytes(Application.streamingAssetsPath + "/Snapshots/" + filename, bytes);
Debug.Log("The picture was uploaded");
// Deactivate the camera
photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
}
private void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
{
// Shutdown the photo capture resource
photoCaptureObject.Dispose();
photoCaptureObject = null;
VuforiaBehaviour.Instance.gameObject.SetActive(true);
}
The exception might however also be related to this issue.
First, I want you to understand my English.
I manually change the camera's projection to orthographic using the source code.
Please refer to the code below.
using UnityEngine;
using System.Collections;
public class CameraOrthoController : MonoBehaviour
{
private Matrix4x4 ortho;
private Matrix4x4 perspective;
public float near = 0.001f;
public float far = 1000f;
private float aspect;
public static CameraOrthoController Instance
{
get
{
return instance;
}
set { }
}
//-----------------------------------------------------
private static CameraOrthoController instance = null;
//---------------------------------------------------
// Use this for initialization
void Awake()
{
if (instance)
{
DestroyImmediate(gameObject);
return;
}
// 이 인스턴스를 유효한 유일 오브젝트로 만든다
instance = this;
}
private void Start()
{
perspective = Camera.main.projectionMatrix;
}
public void StartMatrixBlender(float OrthoSize)
{
aspect = (Screen.width + 0.0f) / (Screen.height + 0.0f);
if (OrthoSize != 0f)
{
float vertical = OrthoSize;
float horizontal = (vertical * 16f) / 9f;
ortho = Matrix4x4.Ortho(-horizontal, horizontal, -vertical, vertical, near, far);
BlendToMatrix(ortho, 1f);
}
else
{
BlendToMatrix(perspective, 1f);
}
}
//---------------------------------------
private Matrix4x4 MatrixLerp(Matrix4x4 from, Matrix4x4 to, float time)
{
Matrix4x4 ret = new Matrix4x4();
int i;
for (i = 0; i < 16; i++)
ret[i] = Mathf.Lerp(from[i], to[i], time);
return ret;
}
IEnumerator LerpFromTo(Matrix4x4 src, Matrix4x4 dest, float duration)
{
float startTime = Time.time;
while (Time.time - startTime < duration)
{
Camera.main.projectionMatrix = MatrixLerp(src, dest, (Time.time - startTime) / duration);
yield return new WaitForSeconds(0f);
}
Camera.main.projectionMatrix = dest;
}
//-------------------------------------------------
private Coroutine BlendToMatrix(Matrix4x4 targetMatrix, float duration)
{
StopAllCoroutines();
return StartCoroutine(LerpFromTo(Camera.main.projectionMatrix, targetMatrix, duration));
}
//-------------------------------------------------
public void OnEvent(EVENT_TYPE Event_Type, Component Sender, object Param = null, object Param2 = null)
{
switch (Event_Type)
{
}
}
}
I use code this way.
CameraOrthoController.Instance.StartMatrixBlender(OrthographicSize);
This has worked well so far.
However, the problem occurred when i added particle system for effect.
The screen where the problem is occurring
In a normal state, the effect appears in front of the gameobject, as shown on the scene screen at the bottom of the picture above.
But if I use the code I wrote above to manipulate the camera, the effect will always be obscured by all gameobject, as if it were on the game screen at the top of the picture. Despite the fact that the effects are located in front of the game object.
At first, I thought it would be possible to solve it with layer sorting, but I don't think it's a layer problem because it's visible under normal camera conditions.
I want to know where the problem is with the above codes because I have to use them.
Please let me know if you know how to solve it.
Thank you.
When you modify Camera.projectionMatrix, the camera will no longer update its rendering based on the field of view. The particle will remain behind the GameObject until you call Camera.ResetProjectionMatrix() which ends the effect off setting the Camera.projectionMatrix property.
If this doesn't work, use multiple cameras to make the particle system always appear on top of the 3D object. Basically, you render the 3D Object and other objects with the main camera then render the Particle System with another camera.
Layer:
1.Create new layer and name it "Particle"
2.Change the Particle System layer to Particle
Main Camera:
1.Make sure that the main camera's Clear Flags is set to Skybox.
2.Change the Culling Mask to "Everything". Click on Everything which is a setting of Culling Mask and de-select/uncheck Particle.
3.Make sure that its Depth is set to 0.
The camera should not render the Particle System at this point.
New Camera:
1.Create new Camera. Make sure it's at the-same position/rotation as the main camera. Also remove the AudioListener that is attached to it.
2.Change Clear Flags to Depth only.
3.Change the Culling Mask of the camera to be Particle and make sure that nothing else is selected in the "Culling Mask"
4.Change Depth to 1.
This will make the Particle System to always display on top of every object rendered with the first or main camera.
If you want the Particle System to appear on top of a Sprite/2d Object instead of Mesh/3D Object, change the sortingOrder of the particle's Renderer to be bigger than the SpriteRenderer's sortingOrder. The default is 0 so changing the Particle's sortingOrder to 1 or 2 should be fine.
particle.GetComponent<Renderer>().sortingOrder = 2;
I'm currently making a game in XNA. I have been going well so far but I don't know how to change the texture of a sprite whilst running the game. An example of this would be. When the character is still that's one image, then when he walks that's a different image. How would I make this happen?
Run checks and just set the instance's Texture2D texture to some other texture from your preloaded library.
I usually load all the textures in my content folder into a dictionary and use like so:
var StringTextureDic = new Dictionary<string, Texture2D>();
// code that loads all textures into the dictionary, file names being keys
// whenever I need to assign some texture, I do this:
if (!playerIsMoving)
Player.texture = StringTextureDic["player standing"];
if (playerIsMoving)
Player.texture = StringTextureDic["player moving"];
Well, actually, change the player texture midgame is a bad idea. In such cases, i prefer to use a texture sheet.
Rectangle frame;
int curFrame;
int frameWidth;
int frameHeight;
int runAnimationLength;
Update()
{
//Handle your "animation code"
if(playerIsMoving)
curFrame++; //Running
if(curFrame == runAnimationLength)
curFrame =0;
else
curFrame = 0; //Standing still
}
Draw(SpriteBatch spriteBatch)
{
frame = new Rectangle(curFrame*frameWidth,curFrame*frameHeight,frameWidth,frameHeight);
spriteBatch.Draw(
texture,
position,
**frame**,
color,
rotation,
origin,
SpriteEffects.None,
1);
}