111 lines
4.8 KiB
C#
111 lines
4.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Rendering.Universal;
|
|
using UnityEngine.XR;
|
|
|
|
namespace EscapeRoomEngine.Portal.Runtime
|
|
{
|
|
[RequireComponent(typeof(Camera))]
|
|
public class PortalCamera : MonoBehaviour
|
|
{
|
|
private static readonly Camera.StereoscopicEye[] Eyes =
|
|
{ Camera.StereoscopicEye.Left, Camera.StereoscopicEye.Right };
|
|
private static readonly Dictionary<Camera.StereoscopicEye, int> EyeTextureNames = new()
|
|
{
|
|
{ Camera.StereoscopicEye.Left, Shader.PropertyToID("_LeftTex") },
|
|
{ Camera.StereoscopicEye.Right, Shader.PropertyToID("_RightTex") }
|
|
};
|
|
|
|
/// <summary>
|
|
/// The minimum near clip plane distance to be used when calculating the oblique clip plane.
|
|
/// </summary>
|
|
public float minNearClipPlane = 0.0001f;
|
|
/// <summary>
|
|
/// The portal this camera renders through.
|
|
/// </summary>
|
|
[SerializeField] private Portal portal;
|
|
/// <summary>
|
|
/// The mesh where the rendered texture will be drawn on.
|
|
/// </summary>
|
|
[SerializeField] public MeshRenderer screen;
|
|
|
|
private PlayerCamera _playerCamera;
|
|
private Camera _camera;
|
|
private readonly Dictionary<Camera.StereoscopicEye, RenderTexture> _textures = new();
|
|
|
|
private void Awake()
|
|
{
|
|
// get player camera
|
|
if (Camera.main != null) _playerCamera = Camera.main.GetComponent<PlayerCamera>();
|
|
else throw new Exception("Main camera has no player camera script set up.");
|
|
|
|
// get portal camera
|
|
_camera = GetComponent<Camera>();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
_camera.enabled = true;
|
|
RenderPipelineManager.beginCameraRendering += Render;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
RenderPipelineManager.beginCameraRendering -= Render;
|
|
_camera.enabled = false;
|
|
}
|
|
|
|
private void Render(ScriptableRenderContext scriptableRenderContext, Camera _)
|
|
{
|
|
// check whether the portal plane is visible from the player camera
|
|
var frustumPlanes = GeometryUtility.CalculateFrustumPlanes(_playerCamera.camera);
|
|
if (!GeometryUtility.TestPlanesAABB(frustumPlanes, portal.linkedPortal.portalCamera.screen.bounds))
|
|
// don't render this portal if it is not visible
|
|
return;
|
|
|
|
screen.enabled = false;
|
|
|
|
foreach (var eye in Eyes)
|
|
{
|
|
// create portal texture if it doesn't exist yet
|
|
if (!_textures.ContainsKey(eye))
|
|
{
|
|
if (XRSettings.eyeTextureWidth > 0 && XRSettings.eyeTextureHeight > 0)
|
|
{
|
|
_textures.Add(eye,
|
|
new RenderTexture(XRSettings.eyeTextureWidth, XRSettings.eyeTextureHeight, 24));
|
|
portal.linkedPortal.portalCamera.screen.material.SetTexture(EyeTextureNames[eye], _textures[eye]);
|
|
}
|
|
else // no texture was created so nothing should be rendered
|
|
continue;
|
|
}
|
|
|
|
// position portal camera
|
|
var m = portal.portalTransform.localToWorldMatrix * Portal.HalfRotation *
|
|
portal.linkedPortal.portalTransform.worldToLocalMatrix *
|
|
_playerCamera.GetEyeTransform(eye).localToWorldMatrix;
|
|
transform.SetPositionAndRotation(m.GetPosition(), m.rotation);
|
|
_camera.projectionMatrix = _playerCamera.camera.GetStereoProjectionMatrix(eye);
|
|
|
|
// set camera clip plane to portal (otherwise the wall behind the portal would be rendered)
|
|
// calculating the clip plane: https://computergraphics.stackexchange.com/a/1506
|
|
var n = -portal.portalTransform.forward; // clip plane normal
|
|
var portalPlane = new Plane(n, portal.portalTransform.position); // clip plane in world space
|
|
var clipPlane = _camera.worldToCameraMatrix.inverse.transpose *
|
|
new Vector4(n.x, n.y, n.z, portalPlane.distance); // vector format clip plane in camera space
|
|
if (-portalPlane.GetDistanceToPoint(transform.position) >= minNearClipPlane)
|
|
// only adjust the near clip plane if it doesn't intersect with the camera
|
|
_camera.projectionMatrix = _camera.CalculateObliqueMatrix(clipPlane);
|
|
|
|
// render portal view
|
|
_camera.targetTexture = _textures[eye];
|
|
UniversalRenderPipeline.RenderSingleCamera(scriptableRenderContext, _camera);
|
|
}
|
|
|
|
screen.enabled = true;
|
|
}
|
|
}
|
|
}
|