- Home /
Second Camera Tearing on Android
I'm using two cameras to render my scene - the main camera (depth 0) clears everything, has a perspective projection, and renders everything but my GUI. The second camera (depth 10) clears the depth buffer, has an orthographic projection, and renders just the GUI. This works great except in rare instances, parts of my GUI are tearing. The main camera doesn't seem to have any tearing issues but the camera isn't moving most of the time so you probably wouldn't notice anything anyways.
I've tried using VSync which made things arguably worse - I've tried doing it every other vblank which made the problem almost disappear but not quite.
This is on a Kindle Fire - I have no issues on any other device I've tried this on so far.
Any ideas? Also, why would the backbuffer be allowed to be flipped before all the cameras are done rendering anyways?
Here are some screenshots I made based on what it looks like on the device.
What it's supposed to look like:
What it looks like when the GUI flickers/tears (note that although most of the GUI isn't being drawn, part of the text is being drawn. All of the GUI is being rendered as textures on meshes on a layer called 'GUI' that is drawn by the "GUICamera" as mentioned before):
EDIT: Attached some screenshots.
can you explain 'parts of my GUI are $$anonymous$$ring'? could you make a screenshot? it's will be nice.
I added some screenshots. What I mean is that a portion of my GUI is being drawn while another portion is not. When I draw larger textures for dialog boxes, it seems like the top 20% of the image remains while the bottom 80% will be cut off (horizontally). It seems very much like a standard $$anonymous$$ring issue but is only affecting cameras beyond my main camera.
just for testing: try to disable cameras and draw them manually using 'camera.Render();' in correct order in Update() function
if you render ONLY second camera with GUI - is GUI looks ok?
Answer by daemionx · Aug 27, 2012 at 09:42 PM
I was ultimately able to fix this by creating a camera that gets rendered before any other cameras (depth -100). This camera has its script execution order set to run after everything else. Each frame it does the following:
In LateUpdate it disables all other cameras.
In OnRenderImage, it sets texture of each camera to the source render texture and renders them manually (it internally keeps a list of depth sorted cameras).
A coroutine I set to run (in LateUpdate) that yields WaitForEndOfFrame reenables all of the cameras (a lot of logic in other parts of the code depended on various cameras existing/being enabled).
Unfortunately rendering to texture is SLOW on any version of Unity prior to 3.5.3 (a separate issue we're looking into) and we are having problems with 3.5.3 and later crashing so aren't actually using this solution right now.
Here's the code in case anybody is interested:
public class CameraCombiner : MonoBehaviour
{
private int frameSkip = 0;
private List<Camera> camerasToCombine = new List<Camera>();
private static CameraCombiner instance;
private static bool quitting = false;
void OnApplicationQuit() {
quitting = true;
}
public static CameraCombiner Instance() {
if (Application.isPlaying && instance == null && !quitting) {
Debug.LogWarning("CREATING CAMERA COMBINER");
Debug.LogWarning("###################################################################################");
GameObject cameraCombinerGameObject = GameObject.Find("CameraCombiner");
if (cameraCombinerGameObject == null) {
cameraCombinerGameObject = new GameObject("CameraCombiner");
Object.DontDestroyOnLoad(cameraCombinerGameObject);
}
Camera fakeCamera = cameraCombinerGameObject.GetComponent<Camera>();
if (fakeCamera == null) {
fakeCamera = cameraCombinerGameObject.AddComponent<Camera>();
}
fakeCamera.cullingMask = 0;
fakeCamera.depth = -100;
CameraCombiner combiner = cameraCombinerGameObject.GetComponent<CameraCombiner>();
if (combiner == null ) {
combiner = cameraCombinerGameObject.AddComponent<CameraCombiner>();
}
instance = combiner;
}
return instance;
}
void OnEnable()
{
if (Application.isPlaying)
{
frameSkip = 1;
}
}
private IEnumerator OnFinishedRenderingEverything() {
yield return new WaitForEndOfFrame();
foreach (Camera cameraToCombine in camerasToCombine) {
if (cameraToCombine != null && cameraToCombine.gameObject.active) {
cameraToCombine.enabled = true;
}
}
}
void LateUpdate() {
StartCoroutine(OnFinishedRenderingEverything());
foreach (Camera cameraToCombine in camerasToCombine) {
if (cameraToCombine != null && cameraToCombine.gameObject.active) {
cameraToCombine.enabled = false;
}
}
}
void OnRenderImage(RenderTexture source, RenderTexture destination) {
if (frameSkip > 0)
{
frameSkip--;
return;
}
foreach (Camera cameraToCombine in camerasToCombine) {
if (cameraToCombine != null && cameraToCombine.gameObject.active) {
cameraToCombine.targetTexture = source;
cameraToCombine.Render();
cameraToCombine.targetTexture = null;
}
}
Graphics.Blit(source, destination);
}
private static int CompareCameraDepths(Camera lhs, Camera rhs) {
if (lhs == rhs) return 0;
if (lhs == null) return -1;
if (rhs == null) return 1;
return lhs.depth.CompareTo(rhs.depth);
}
public void RegisterCamera(Camera camera) {
if (!camerasToCombine.Contains(camera)) {
camerasToCombine.Add(camera);
camerasToCombine.Sort(CompareCameraDepths);
}
}
public void DeregisterCamera(Camera camera) {
camerasToCombine.Remove(camera);
}
}