refactor portal rendering into portal camera

This commit is contained in:
2022-10-09 22:14:46 +02:00
parent 3a343f8811
commit 85e3064171
4 changed files with 135 additions and 88 deletions

View File

@@ -1,108 +1,23 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.XR;
namespace Escape_Room_Engine.Portal
{
public class Portal : MonoBehaviour
{
private static readonly Matrix4x4 HalfRotation = Matrix4x4.Rotate(Quaternion.Euler(0, 180, 0));
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 portal that is connected with this one.
/// </summary>
public Portal other;
/// <summary>
/// The minimum near clip plane distance to be used when calculating the oblique clip plane.
/// </summary>
public float minNearClipPlane = 0.02f;
/// <summary>
/// The camera that will draw the view for this portal.
/// </summary>
[SerializeField] private Camera portalCamera;
/// <summary>
/// The quad where the rendered texture will be drawn on.
/// </summary>
[SerializeField] private MeshRenderer portalQuad;
private PlayerCamera _playerCamera;
private Dictionary<Camera.StereoscopicEye, RenderTexture> _textures = new();
public PortalCamera portalCamera;
private void Awake()
{
// check whether the other portal is set up
if (!other || other.other != this) throw new Exception("Other portal not set up correctly.");
// 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.");
}
private void OnEnable()
{
RenderPipelineManager.beginCameraRendering += Render;
}
private void OnDisable()
{
RenderPipelineManager.beginCameraRendering -= Render;
}
private void Render(ScriptableRenderContext scriptableRenderContext, Camera camera)
{
// check whether the portal plane is visible from the player camera
var frustumPlanes = GeometryUtility.CalculateFrustumPlanes(_playerCamera.camera);
if (!GeometryUtility.TestPlanesAABB(frustumPlanes, other.portalQuad.bounds))
// don't render this portal if it is not visible
return;
var t = transform;
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));
other.portalQuad.material.SetTexture(EyeTextureNames[eye], _textures[eye]);
}
else // no texture was created so nothing should be rendered
continue;
}
// position portal camera
var m = t.localToWorldMatrix * HalfRotation * other.transform.worldToLocalMatrix *
_playerCamera.getEyeTransform(eye).localToWorldMatrix;
portalCamera.transform.SetPositionAndRotation(m.GetPosition(), m.rotation);
portalCamera.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 = -t.forward; // clip plane normal
var portalPlane = new Plane(n, t.position); // clip plane in world space
var clipPlane = portalCamera.worldToCameraMatrix.inverse.transpose *
new Vector4(n.x, n.y, n.z, portalPlane.distance); // vector format clip plane in camera space
if (Math.Abs(portalPlane.GetDistanceToPoint(portalCamera.transform.position)) >= minNearClipPlane)
// only adjust the near clip plane if it doesn't intersect with the camera
portalCamera.projectionMatrix = portalCamera.CalculateObliqueMatrix(clipPlane);
// render portal view
portalCamera.targetTexture = _textures[eye];
UniversalRenderPipeline.RenderSingleCamera(scriptableRenderContext, portalCamera);
}
}
}
}