/** After calling RenderSlaveCamera on this frame, call this with the returned results to make the results of that render visible. */ internal void AppearAsPreviouslyRendered(RenderedFrame renderedFrame) { if (!portalMaterial) { SetupMaterial(); } if (renderedFrame.texture == null) { if (renderedFrame.renderOpaque) { //Nothing rendered, appear flat opaque. portalMaterial.SetVector("_Scale", new Vector4(0, 0, 1, 0)); portalMaterial.SetVector("_Color", renderOptions.fallbackColor); } else { //don't appear portalMaterial.SetVector("_Scale", new Vector4(0, 0, -1, 0)); } } else { //Set our texture to the rendered area beyond the portal portalMaterial.SetTexture("_PortalTex", renderedFrame.texture); portalMaterial.SetVector("_Scale", new Vector4(0, 0, 0, 1f)); } }
/** The portal script will call this method in OnWillRenderObject while the scene is being culled. */ public void PortalIsVisible(Portal portal) { //render the portal right now and save the result var renderResult = RenderedFrame.Get(); portal.RenderSlaveCamera(GetComponent <Camera>(), renderResult); //in a minute we'll apply the results to the portal so it will look right when we render renderedPortals.Add(portal); renderedPortalData.Add(renderResult); }
/** * This will render the slave camera to a texture as it would be seen by the given camera. * Render results are placed in {result}. Pass this to AppearAsPreviouslyRendered to apply the rendered * texture to our current model/appearance. */ internal void RenderSlaveCamera(Camera cam, RenderedFrame result) { //skip if not on or ready if (!enabled || !GetComponent<Renderer>() || !GetComponent<Renderer>().sharedMaterial || !GetComponent<Renderer>().enabled || !destination || !cam) return; CameraInfo trackingInfo = null; if (!cameraInfo.TryGetValue(cam, out trackingInfo)) { //Not actually being tracked? Just use a temp. Generally shouldn't happen. trackingInfo = new CameraInfo(); } Vector3 entryFaceDirection = transform.rotation * entryFace; var isBehind = Vector3.Dot(transform.position - cam.transform.position, entryFaceDirection) >= 0; var isReallyNear = PortalMath.DistanceFromPointToPlane(transform.forward, transform.position, cam.transform.position) <= cam.nearClipPlane; //Depending on where we've been and are, we might or might not want to render. Keep reading. if (isReallyNear) { if (isBehind) { //we are just behind the portal if (trackingInfo.nearFront) { //We were in front of it earlier and can we can still see the portal (the portal mesh has more geometry behind the front plane), //Render. //(If we're going to teleport something, we'll usually do it just after this frame.) } else { //We weren't just in front of the portal. Perhaps we teleported in from somewhere or walked up to a portal that's //invisible from behind while looking backwards. //Don't render. return; } } else { //We are in front of the portal (and rather close to boot). //Render. //Also set the "I was close to the front of the portal" flag so we know to keep rendering if we move behind it. trackingInfo.nearFront = true; } } else { //We are not close. //Reset this flag. trackingInfo.nearFront = false; if (isBehind) { //Don't render return; } else { //Render. } } //Note that, if we don't (try to) render the portal for a frame or so the tracking information will be deleted. //This covers the corner case where you are behind the portal with it rendering, then teleport away and back. //Without clearing the nearFront flag while we're away, we would incorrectly show the inside of the portal if we jumped back. //If we have a two-sided portal on the other end as our destination, the opposite face on the destination portal can sometimes //block the view as we look through. //Therefore, if we are the destination of the currently rendering portal, don't render at all. if (lastRecursivePortal && lastRecursivePortal.destination == this.transform) { return; } //Stop rendering if we are too recursively deep. if (currentPortalDepth + 1 > renderOptions.maximumPortalDepth) { result.renderOpaque = true; return; } #if UNITY_EDITOR if (!Application.isPlaying && currentPortalDepth + 1 > 1) { //don't render more than one deep in the editor (todo: make this configurable) result.renderOpaque = true; return; } #endif currentPortalDepth++; var lastLastRecursiveCamera = lastRecursiveCamera; var lastLastRecursivePortal = lastRecursivePortal; lastRecursiveCamera = cam; lastRecursivePortal = this; try { var camInfo = CreateCameraObjects(cam); var portalCamera = camInfo.portalCamera; UpdateCameraModes(cam, portalCamera); //Move the render target camera to where we'll be rendering from. TeleportRelativeToDestination(cam.transform, portalCamera.transform); //get the portal's plane var pos = destination.transform.position; var normal = destination.transform.rotation * exitFace; if (renderOptions.useOblique) { /* Normally, when you do a projection, the near and far (clipping) planes are perpendicular to the camera. They don't have to be, however, and here we take advantage of this fact to cull unwanted geometry. Here we set up an oblique projection matrix so that near clipping plane coincides with our portal plane. (Then shim it a bit, to avoid z-fighting.) This way the z-buffer will automatically clip out everything between the camera and portal. You'll only see things beyond the destination portal. */ Vector4 clipPlane = PortalMath.CameraSpacePlane(portalCamera, pos, normal, 1.0f, renderOptions.clipPlaneOffset); Matrix4x4 projection; if (currentPortalDepth > 1) { //If we have a regular projection matrix we can just go ahead and turn it into an oblique matrix. //But if we started with an oblique matrix (as happens when a portal renders a portal), re-obliquifying it //messes up the far clipping plane. //Instead, start with a fresh matrix for the camera and tweak that. //Note that we don't want to modify the src camera's matrix, just get a copy of what its normal matrix would be. //(Too bad Unity doesn't have an API to just fetch it.) //Also note: If we do this to a scene camera inside the Unity Editor (even though we put it back) the scene cameras might FREAK OUT. //(That's not a concern, however, because we only do this to slave cameras we generated.) var origMatrix = cam.projectionMatrix;//backup cam.ResetProjectionMatrix(); projection = cam.projectionMatrix;//get what we need cam.projectionMatrix = origMatrix;//leave the original camera unmodified } else { projection = cam.projectionMatrix; } //how far is the camera on this side from the portal entrance? var cameraDistanceFromPortal = PortalMath.DistanceFromPointToPlane(transform.forward, transform.position, cam.transform.position); if (cameraDistanceFromPortal < cam.nearClipPlane * 3) { //When the camera's this close, the math we're using to construct the oblique matrix tends to break down and construct a matrix //with a far plane that intersects the original frustum. //If we're this close, we'll rely on the empty space that should be behind the portal actually being empty and just use a //regular near plan on our frustum. } else { if (portalCamera.orthographic) PortalMath.CalculateOrthographicObliqueMatrix(ref projection, clipPlane); else PortalMath.CalculatePerspectiveObliqueMatrix(ref projection, clipPlane); } //we don't use the normal near clip plane, but still need to tell culling algorithms about where we're looking //Never mind, occlusion culling is broken in Unity. //portalCamera.nearClipPlane = PortalMath.DistanceFromPointToPlane(destination.forward, destination.position, portalCamera.transform.position);//Vector3.Distance(portalCamera.transform.position, destination.transform.position); portalCamera.projectionMatrix = projection; } var renderTexture = result.CreateTexture(renderOptions, cam); portalCamera.cullingMask = renderOptions.renderLayers.value; portalCamera.targetTexture = renderTexture; portalCamera.Render(); // Debug.Log("portal texture (after render) is " + this+ "-"+camInfo.portalTexture.GetInstanceID()); camInfo.renderedLastFrame = true; } finally { currentPortalDepth--; lastRecursiveCamera = lastLastRecursiveCamera; lastRecursivePortal = lastLastRecursivePortal; } }
/** After calling RenderSlaveCamera on this frame, call this with the returned results to make the results of that render visible. */ internal void AppearAsPreviouslyRendered(RenderedFrame renderedFrame) { if (!portalMaterial) SetupMaterial(); if (renderedFrame.texture == null) { if (renderedFrame.renderOpaque) { //Nothing rendered, appear flat opaque. portalMaterial.SetVector("_Scale", new Vector4(0, 0, 1, 0)); portalMaterial.SetVector("_Color", renderOptions.fallbackColor); } else { //don't appear portalMaterial.SetVector("_Scale", new Vector4(0, 0, -1, 0)); } } else { //Set our texture to the rendered area beyond the portal portalMaterial.SetTexture("_PortalTex", renderedFrame.texture); portalMaterial.SetVector("_Scale", new Vector4(0, 0, 0, 1f)); } }
/** * This will render the slave camera to a texture as it would be seen by the given camera. * Render results are placed in {result}. Pass this to AppearAsPreviouslyRendered to apply the rendered * texture to our current model/appearance. */ internal void RenderSlaveCamera(Camera cam, RenderedFrame result) { //skip if not on or ready if (!enabled || !GetComponent <Renderer>() || !GetComponent <Renderer>().sharedMaterial || !GetComponent <Renderer>().enabled || !destination || !cam) { return; } CameraInfo trackingInfo = null; if (!cameraInfo.TryGetValue(cam, out trackingInfo)) { //Not actually being tracked? Just use a temp. Shouldn't happen if the colliders are set up right and traveling sane speeds. trackingInfo = new CameraInfo(); } Vector3 entryFaceDirection = transform.rotation * entryFace; var isBehind = Vector3.Dot(transform.position - cam.transform.position, entryFaceDirection) >= 0; var isReallyNear = PortalMath.DistanceFromPointToPlane(transform.forward, transform.position, cam.transform.position) <= cam.nearClipPlane; //Depending on where we've been and are, we might or might not want to render. Keep reading. if (isReallyNear) { if (isBehind) { //we are just behind the portal if (trackingInfo.nearFront) { //We were in front of it earlier and can we can still see the portal (the portal mesh has more geometry behind the front plane), //Render. //(Usually this shouldn't happen, we should have been teleported by now.) } else { //We weren't just in front of the portal. Perhaps we teleported in from somewhere or walked up to a portal that's //invisible from behind while looking backwards. //Don't render. return; } } else { //We are in front of the portal (and rather close to boot). //Render. //Also set the "I was close to the front of the portal" flag so we know to keep rendering if we move behind it. trackingInfo.nearFront = true; } } else { //We are not close. //Reset this flag. trackingInfo.nearFront = false; if (isBehind) { //Don't render return; } else { //Render. } } //Note that, if we don't (try to) render the portal for a frame or so the tracking information will be deleted. //This covers the corner case where you are behind the portal with it rendering, then teleport away and back. //Without clearing the nearFront flag while we're away, we would incorrectly show the inside of the portal if we jumped back. //If we have a two-sided portal on the other end as our destination, the opposite face on the destination portal can sometimes //block the view as we look through. //Therefore, if we are the destination of the currently rendering portal, don't render at all. if (lastRecursivePortal && lastRecursivePortal.destination == this.transform) { return; } //Stop rendering if we are too recursively deep. if (currentPortalDepth + 1 > renderOptions.maximumPortalDepth) { result.renderOpaque = true; return; } #if UNITY_EDITOR if (!Application.isPlaying && currentPortalDepth + 1 > 1) { //don't render more than one deep in the editor (todo: make this configurable) result.renderOpaque = true; return; } #endif currentPortalDepth++; var lastLastRecursiveCamera = lastRecursiveCamera; var lastLastRecursivePortal = lastRecursivePortal; lastRecursiveCamera = cam; lastRecursivePortal = this; try { var camInfo = CreateCameraObjects(cam); var portalCamera = camInfo.portalCamera; UpdateCameraModes(cam, portalCamera); //Move the render target camera to where we'll be rendering from. TeleportRelativeToDestination(cam.transform, portalCamera.transform); //get the portal's plane var pos = destination.transform.position; var normal = destination.transform.rotation * exitFace; if (renderOptions.useOblique) { /* * Normally, when you do a projection, the near and far (clipping) planes are perpendicular to the camera. * * They don't have to be, however, and here we take advantage of this fact to cull unwanted geometry. * * Here we set up an oblique projection matrix so that near clipping plane coincides with our portal plane. * (Then shim it a bit, to avoid z-fighting.) * This way the z-buffer will automatically clip out everything between the camera and portal. * You'll only see things beyond the destination portal. */ Vector4 clipPlane = PortalMath.CameraSpacePlane(portalCamera, pos, normal, 1.0f, renderOptions.clipPlaneOffset); Matrix4x4 projection; if (currentPortalDepth > 1) { //If we have a regular projection matrix we can just go ahead and turn it into an oblique matrix. //But if we started with an oblique matrix (as happens when a portal renders a portal), re-obliquifying it //messes up the far clipping plane. //Instead, start with a fresh matrix for the camera and tweak that. //Note that we don't want to modify the src camera's matrix, just get a copy of what its normal matrix would be. //(Too bad Unity doesn't have an API to just fetch it.) //Also note: If we do this to a scene camera inside the Unity Editor (even though we put it back) the scene cameras might FREAK OUT. //(That's not a concern, however, because we only do this to slave cameras we generated.) var origMatrix = cam.projectionMatrix; //backup cam.ResetProjectionMatrix(); projection = cam.projectionMatrix; //get what we need cam.projectionMatrix = origMatrix; //leave the original camera unmodified } else { projection = cam.projectionMatrix; } //how far is the camera on this side from the portal entrance? var cameraDistanceFromPortal = PortalMath.DistanceFromPointToPlane(transform.forward, transform.position, cam.transform.position); if (cameraDistanceFromPortal < cam.nearClipPlane * 3) { //When the camera's this close, the math we're using to construct the oblique matrix tends to break down and construct a matrix //with a far plane that intersects the original frustum. //If we're this close, we'll rely on the empty space that should be behind the portal actually being empty and just use a //regular near plan on our frustum. } else { if (portalCamera.orthographic) { PortalMath.CalculateOrthographicObliqueMatrix(ref projection, clipPlane); } else { PortalMath.CalculatePerspectiveObliqueMatrix(ref projection, clipPlane); } } //we don't use the normal near clip plane, but still need to tell culling algorithms about where we're looking //Never mind, occlusion culling is broken in Unity. //portalCamera.nearClipPlane = PortalMath.DistanceFromPointToPlane(destination.forward, destination.position, portalCamera.transform.position);//Vector3.Distance(portalCamera.transform.position, destination.transform.position); portalCamera.projectionMatrix = projection; } var renderTexture = result.CreateTexture(renderOptions, cam); portalCamera.cullingMask = renderOptions.renderLayers.value; portalCamera.targetTexture = renderTexture; var hideState = HideBehindPortal.HideObjects(this); portalCamera.Render(); HideBehindPortal.RestoreObjects(hideState); // Debug.Log("portal texture (after render) is " + this+ "-"+camInfo.portalTexture.GetInstanceID()); camInfo.renderedLastFrame = true; } finally { currentPortalDepth--; lastRecursiveCamera = lastLastRecursiveCamera; lastRecursivePortal = lastLastRecursivePortal; } }