public void Render() { // Shouldn't happen because of the check in Start(), but just in case... if (controller == null) { return; } var camera = GetComponent <Camera>(); var monoCamera = controller.GetComponent <Camera>(); Matrix4x4 proj = Cardboard.SDK.Projection(eye); CopyCameraAndMakeSideBySide(controller, proj[0, 2], proj[1, 2]); // Zoom the stereo cameras if requested. float lerp = Mathf.Clamp01(controller.matchByZoom) * Mathf.Clamp01(controller.matchMonoFOV); // Lerping the reciprocal of proj(1,1) so zooming is linear in the frustum width, not the depth. float monoProj11 = monoCamera.projectionMatrix[1, 1]; float zoom = 1 / Mathf.Lerp(1 / proj[1, 1], 1 / monoProj11, lerp) / proj[1, 1]; proj[0, 0] *= zoom; proj[1, 1] *= zoom; // Calculate stereo adjustments based on the center of interest. float ipdScale; float eyeOffset; controller.ComputeStereoAdjustment(proj[1, 1], transform.lossyScale.z, out ipdScale, out eyeOffset); // Set up the eye's view transform. transform.localPosition = ipdScale * Cardboard.SDK.EyeOffset(eye) + eyeOffset * Vector3.forward; // Set up the eye's projection. float near = monoCamera.nearClipPlane; float far = monoCamera.farClipPlane; FixProjection(ref proj, near, far, ipdScale); camera.projectionMatrix = proj; if (Application.isEditor) { // So you can see the approximate frustum in the Scene view when the camera is selected. camera.fieldOfView = 2 * Mathf.Atan(1 / proj[1, 1]) * Mathf.Rad2Deg; } if (!Cardboard.SDK.nativeDistortionCorrection) { Matrix4x4 realProj = Cardboard.SDK.UndistortedProjection(eye); FixProjection(ref realProj, near, far, ipdScale); // Parts of the projection matrices that we need to convert texture coordinates between // distorted and undistorted frustums. Include the transform between texture space [0..1] // and NDC [-1..1] (that's what the -1 and the /2 are for). Also note that the zoom // factor is removed, because that will interfere with the distortion calculation. Vector4 projvec = new Vector4(proj[0, 0] / zoom, proj[1, 1] / zoom, proj[0, 2] - 1, proj[1, 2] - 1) / 2; Vector4 unprojvec = new Vector4(realProj[0, 0], realProj[1, 1], realProj[0, 2] - 1, realProj[1, 2] - 1) / 2; Shader.SetGlobalVector("_Projection", projvec); Shader.SetGlobalVector("_Unprojection", unprojvec); CardboardProfile p = Cardboard.SDK.Profile; Shader.SetGlobalVector("_Undistortion", new Vector4(p.device.inverse.k1, p.device.inverse.k2)); Shader.SetGlobalVector("_Distortion", new Vector4(p.device.distortion.k1, p.device.distortion.k2)); } RenderTexture stereoScreen = controller.StereoScreen; int screenWidth = stereoScreen ? stereoScreen.width : Screen.width; int screenHeight = stereoScreen ? stereoScreen.height : Screen.height; if (stereoScreen == null) { // We are rendering straight to the screen. Use the reported rect that is visible // through the device's lenses. Rect view = Cardboard.SDK.EyeRect(eye); Rect rect = camera.rect; if (eye == Cardboard.Eye.Right) { rect.x -= 0.5f; } rect.width *= 2 * view.width; rect.x = view.x + 2 * rect.x * view.width; rect.height *= view.height; rect.y = view.y + rect.y * view.height; if (Application.isEditor) { // The Game window's aspect ratio may not match the fake device parameters. float realAspect = (float)screenWidth / screenHeight; float fakeAspect = Cardboard.SDK.Profile.screen.width / Cardboard.SDK.Profile.screen.height; float aspectComparison = fakeAspect / realAspect; if (aspectComparison < 1) { rect.width *= aspectComparison; rect.x *= aspectComparison; rect.x += (1 - aspectComparison) / 2; } else { rect.height /= aspectComparison; } } camera.rect = rect; } // Use the "fast" or "slow" method. Fast means the camera draws right into one half of // the stereo screen. Slow means it draws first to a side buffer, and then the buffer // is written to the screen. The slow method is provided because a lot of Image Effects // don't work if you draw to only part of the window. if (controller.directRender) { // Redirect to our stereo screen. camera.targetTexture = stereoScreen; // Draw! camera.Render(); } else { // Save the viewport rectangle and reset to "full screen". Rect pixRect = camera.pixelRect; camera.rect = new Rect(0, 0, 1, 1); // Redirect to a temporary texture. The defaults are supposedly Android-friendly. int depth = stereoScreen ? stereoScreen.depth : 16; RenderTextureFormat format = stereoScreen ? stereoScreen.format : RenderTextureFormat.RGB565; camera.targetTexture = RenderTexture.GetTemporary((int)pixRect.width, (int)pixRect.height, depth, format); // Draw! camera.Render(); // Blit the temp texture to the stereo screen. RenderTexture oldTarget = RenderTexture.active; RenderTexture.active = stereoScreen; GL.PushMatrix(); GL.LoadPixelMatrix(0, screenWidth, screenHeight, 0); // Camera rects are in screen coordinates (bottom left is origin), but DrawTexture takes a // rect in GUI coordinates (top left is origin). Rect blitRect = pixRect; blitRect.y = screenHeight - pixRect.height - pixRect.y; // Blit! Graphics.DrawTexture(blitRect, camera.targetTexture); // Clean up. GL.PopMatrix(); RenderTexture.active = oldTarget; RenderTexture.ReleaseTemporary(camera.targetTexture); } camera.targetTexture = null; }
private void Setup() { // Shouldn't happen because of the check in Start(), but just in case... if (controller == null) { return; } var monoCamera = controller.GetComponent <Camera>(); Matrix4x4 proj = Cardboard.SDK.Projection(eye); CopyCameraAndMakeSideBySide(controller, proj[0, 2], proj[1, 2]); // Zoom the stereo cameras if requested. float lerp = Mathf.Clamp01(controller.matchByZoom) * Mathf.Clamp01(controller.matchMonoFOV); // Lerping the reciprocal of proj(1,1) so zooming is linear in the frustum width, not the depth. float monoProj11 = monoCamera.projectionMatrix[1, 1]; float zoom = 2 / Mathf.Lerp(1 / proj[1, 1], 1 / monoProj11, lerp) / proj[1, 1]; proj[0, 0] *= zoom; proj[1, 1] *= zoom; // Calculate stereo adjustments based on the center of interest. float ipdScale; float eyeOffset; controller.ComputeStereoAdjustment(proj[1, 1], transform.lossyScale.z, out ipdScale, out eyeOffset); // Set up the eye's view transform. transform.localPosition = ipdScale * Cardboard.SDK.EyePose(eye).Position + eyeOffset * Vector3.forward; // Set up the eye's projection. float near = monoCamera.nearClipPlane; float far = monoCamera.farClipPlane; FixProjection(ref proj, near, far, ipdScale); camera.projectionMatrix = proj; if (Application.isEditor) { // So you can see the approximate frustum in the Scene view when the camera is selected. camera.fieldOfView = 2 * Mathf.Atan(1 / proj[1, 1]) * Mathf.Rad2Deg; } Vuforia.VuforiaBehaviour.Instance.ApplyCorrectedProjectionMatrix(proj, eye == Cardboard.Eye.Left); // Set up variables for an image effect that will do distortion correction, e.g. the // RadialDistortionEffect. Note that native distortion correction should take precedence // over an image effect, so if that is active then we don't need to compute these variables. // (Exception: we're in the editor, so we use the image effect to preview the distortion // correction, because native distortion correction only works on the phone.) if (Cardboard.SDK.UseDistortionEffect) { Matrix4x4 realProj = Cardboard.SDK.Projection(eye, Cardboard.Distortion.Undistorted); FixProjection(ref realProj, near, far, ipdScale); // Parts of the projection matrices that we need to convert texture coordinates between // distorted and undistorted frustums. Include the transform between texture space [0..1] // and NDC [-1..1] (that's what the -1 and the /2 are for). Also note that the zoom // factor is removed, because that will interfere with the distortion calculation. Vector4 projvec = new Vector4(proj[0, 0] / zoom, proj[1, 1] / zoom, proj[0, 2] - 1, proj[1, 2] - 1) / 2; Vector4 unprojvec = new Vector4(realProj[0, 0], realProj[1, 1], realProj[0, 2] - 1, realProj[1, 2] - 1) / 2; Shader.SetGlobalVector("_Projection", projvec); Shader.SetGlobalVector("_Unprojection", unprojvec); CardboardProfile p = Cardboard.SDK.Profile; Shader.SetGlobalVector("_Undistortion", new Vector4(p.device.inverse.k1, p.device.inverse.k2)); Shader.SetGlobalVector("_Distortion", new Vector4(p.device.distortion.k1, p.device.distortion.k2)); } if (controller.StereoScreen == null) { Rect rect = camera.rect; if (!Cardboard.SDK.DistortionCorrection || Cardboard.SDK.UseDistortionEffect) { // We are rendering straight to the screen. Use the reported rect that is visible // through the device's lenses. Rect view = Cardboard.SDK.Viewport(eye); if (eye == Cardboard.Eye.Right) { rect.x -= 0.5f; } rect.width *= 2 * view.width; rect.x = view.x + 2 * rect.x * view.width; rect.height *= view.height; rect.y = view.y + rect.y * view.height; } if (Application.isEditor) { // The Game window's aspect ratio may not match the fake device parameters. float realAspect = (float)Screen.width / Screen.height; float fakeAspect = Cardboard.SDK.Profile.screen.width / Cardboard.SDK.Profile.screen.height; float aspectComparison = fakeAspect / realAspect; if (aspectComparison < 1) { rect.width *= aspectComparison; rect.x *= aspectComparison; rect.x += (1 - aspectComparison) / 2; } else { rect.height /= aspectComparison; } } camera.rect = rect; } }
public void Render() { // Shouldn't happen because of the check in Start(), but just in case... if (controller == null) { return; } var camera = GetComponent <Camera>(); var monoCamera = controller.GetComponent <Camera>(); Matrix4x4 proj = Cardboard.SDK.Projection(eye); CopyCameraAndMakeSideBySide(controller, proj[0, 2], proj[1, 2]); // Calculate stereo adjustments based on the center of interest. float ipdScale; float eyeOffset; controller.ComputeStereoAdjustment(proj[1, 1], transform.lossyScale.z, out ipdScale, out eyeOffset); // Set up the eye's view transform. transform.localPosition = ipdScale * Cardboard.SDK.EyeOffset(eye) + eyeOffset * Vector3.forward; // Set up the eye's projection. // Adjust for non-fullscreen camera. Cardboard SDK assumes fullscreen, // so the aspect ratio might not match. proj[0, 0] *= camera.rect.height / camera.rect.width / 2; // Adjust for IPD scale. This changes the vergence of the two frustums. Vector2 dir = transform.localPosition; // ignore Z dir = dir.normalized * ipdScale; proj[0, 2] *= Mathf.Abs(dir.x); proj[1, 2] *= Mathf.Abs(dir.y); // Cardboard had to pass "nominal" values of near/far to the SDK, which // we fix here to match our mono camera's specific values. float near = monoCamera.nearClipPlane; float far = monoCamera.farClipPlane; proj[2, 2] = (near + far) / (near - far); proj[2, 3] = 2 * near * far / (near - far); camera.projectionMatrix = proj; if (Application.isEditor) { // So you can see the approximate frustum in the Scene view when the camera is selected. camera.fieldOfView = 2 * Mathf.Atan(1 / proj[1, 1]) * Mathf.Rad2Deg; } RenderTexture stereoScreen = controller.StereoScreen; // Use the "fast" or "slow" method. Fast means the camera draws right into one half of // the stereo screen. Slow means it draws first to a side buffer, and then the buffer // is written to the screen. The slow method is provided because a lot of Image Effects // don't work if you draw to only part of the window. if (controller.directRender) { // Redirect to our stereo screen. camera.targetTexture = stereoScreen; // Draw! camera.Render(); } else { // Save the viewport rectangle and reset to "full screen". Rect pixRect = camera.pixelRect; camera.rect = new Rect(0, 0, 1, 1); // Redirect to a temporary texture. The defaults are supposedly Android-friendly. int depth = stereoScreen ? stereoScreen.depth : 16; RenderTextureFormat format = stereoScreen ? stereoScreen.format : RenderTextureFormat.RGB565; camera.targetTexture = RenderTexture.GetTemporary((int)pixRect.width, (int)pixRect.height, depth, format); // Draw! camera.Render(); // Blit the temp texture to the stereo screen. RenderTexture oldTarget = RenderTexture.active; RenderTexture.active = stereoScreen; GL.PushMatrix(); GL.LoadPixelMatrix(0, stereoScreen ? stereoScreen.width : Screen.width, stereoScreen ? stereoScreen.height : Screen.height, 0); Graphics.DrawTexture(pixRect, camera.targetTexture); // Clean up. GL.PopMatrix(); RenderTexture.active = oldTarget; RenderTexture.ReleaseTemporary(camera.targetTexture); camera.targetTexture = null; } }