refactor portal rendering into portal camera
This commit is contained in:
@@ -1,108 +1,23 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Rendering;
|
|
||||||
using UnityEngine.Rendering.Universal;
|
|
||||||
using UnityEngine.XR;
|
|
||||||
|
|
||||||
namespace Escape_Room_Engine.Portal
|
namespace Escape_Room_Engine.Portal
|
||||||
{
|
{
|
||||||
public class Portal : MonoBehaviour
|
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>
|
/// <summary>
|
||||||
/// The portal that is connected with this one.
|
/// The portal that is connected with this one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Portal other;
|
public Portal other;
|
||||||
/// <summary>
|
/// <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.
|
/// The camera that will draw the view for this portal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeField] private Camera portalCamera;
|
public PortalCamera 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();
|
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
// check whether the other portal is set up
|
// check whether the other portal is set up
|
||||||
if (!other || other.other != this) throw new Exception("Other portal not set up correctly.");
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ MonoBehaviour:
|
|||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
other: {fileID: 0}
|
other: {fileID: 0}
|
||||||
portalCamera: {fileID: 7791795762741173939}
|
portalCamera: {fileID: 17691322601746172}
|
||||||
portalQuad: {fileID: 6216133567950691622}
|
|
||||||
--- !u!1 &6911937082098131204
|
--- !u!1 &6911937082098131204
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -144,6 +143,7 @@ GameObject:
|
|||||||
- component: {fileID: 2398425302420252226}
|
- component: {fileID: 2398425302420252226}
|
||||||
- component: {fileID: 7791795762741173939}
|
- component: {fileID: 7791795762741173939}
|
||||||
- component: {fileID: 5969531196797302096}
|
- component: {fileID: 5969531196797302096}
|
||||||
|
- component: {fileID: 17691322601746172}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: Camera
|
m_Name: Camera
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -242,6 +242,21 @@ MonoBehaviour:
|
|||||||
m_RequiresDepthTexture: 0
|
m_RequiresDepthTexture: 0
|
||||||
m_RequiresColorTexture: 0
|
m_RequiresColorTexture: 0
|
||||||
m_Version: 2
|
m_Version: 2
|
||||||
|
--- !u!114 &17691322601746172
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 7398326895463990628}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: ad60e3973ab83f3468637a06970d7f1f, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
minNearClipPlane: 0.02
|
||||||
|
portal: {fileID: 1249363658}
|
||||||
|
portalQuad: {fileID: 6216133567950691622}
|
||||||
--- !u!1001 &1842852527293547269
|
--- !u!1001 &1842852527293547269
|
||||||
PrefabInstance:
|
PrefabInstance:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|||||||
106
Assets/Escape Room Engine/Portal/PortalCamera.cs
Normal file
106
Assets/Escape Room Engine/Portal/PortalCamera.cs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Rendering;
|
||||||
|
using UnityEngine.Rendering.Universal;
|
||||||
|
using UnityEngine.XR;
|
||||||
|
|
||||||
|
namespace Escape_Room_Engine.Portal
|
||||||
|
{
|
||||||
|
[RequireComponent(typeof(Camera))]
|
||||||
|
public class PortalCamera : 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 minimum near clip plane distance to be used when calculating the oblique clip plane.
|
||||||
|
/// </summary>
|
||||||
|
public float minNearClipPlane = 0.02f;
|
||||||
|
/// <summary>
|
||||||
|
/// The portal this camera renders through.
|
||||||
|
/// </summary>
|
||||||
|
[SerializeField] private Portal portal;
|
||||||
|
/// <summary>
|
||||||
|
/// The quad where the rendered texture will be drawn on.
|
||||||
|
/// </summary>
|
||||||
|
[SerializeField] private MeshRenderer portalQuad;
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
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, portal.other.portalCamera.portalQuad.bounds))
|
||||||
|
// don't render this portal if it is not visible
|
||||||
|
return;
|
||||||
|
|
||||||
|
var t = portal.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));
|
||||||
|
portal.other.portalCamera.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 * portal.other.transform.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 = -t.forward; // clip plane normal
|
||||||
|
var portalPlane = new Plane(n, t.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 (Math.Abs(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Assets/Escape Room Engine/Portal/PortalCamera.cs.meta
Normal file
11
Assets/Escape Room Engine/Portal/PortalCamera.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ad60e3973ab83f3468637a06970d7f1f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Reference in New Issue
Block a user