Add ultimate xr
This commit is contained in:
8
Assets/UltimateXR/Runtime/Scripts/Rendering/FX.meta
Normal file
8
Assets/UltimateXR/Runtime/Scripts/Rendering/FX.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e32dbc06baa4e774daad1e8c28886f88
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,436 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrMagnifyingGlassUrp.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using UltimateXR.Avatar;
|
||||
using UltimateXR.Core.Components;
|
||||
using UltimateXR.Devices;
|
||||
using UltimateXR.Extensions.Unity;
|
||||
using UltimateXR.Extensions.Unity.Math;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.XR;
|
||||
|
||||
#if ULTIMATEXR_UNITY_URP
|
||||
using UnityEngine.Rendering.Universal;
|
||||
#endif
|
||||
|
||||
namespace UltimateXR.Rendering.FX
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that renders a magnifying glass effect on an object, using the URP pipeline:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// If the Glass Axes transform is not set, it will use the transform on the component's GameObject.
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// The magnifying glass normal is determined by the -forward axis, so the user will look through the glass
|
||||
/// pointing in the forward axis direction.
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// The component requires a Renderer on the same GameObject with a material compatible with the URP magnifying
|
||||
/// glass refraction. They can be found in the UltimateXR/FX/ category.
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class UxrMagnifyingGlassUrp : UxrComponent
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
// Inspector
|
||||
|
||||
[SerializeField] private bool _forceClearSkyBox;
|
||||
[SerializeField] private Transform _glassAxes;
|
||||
[SerializeField] private bool _disablePixelLights = true;
|
||||
[SerializeField] private int _textureSize = 1024;
|
||||
[SerializeField] private int _antialias = 1;
|
||||
[SerializeField] private float _clipPlaneOffset = 0.01f;
|
||||
[SerializeField] private float _cameraForwardOffset;
|
||||
[SerializeField] [Range(0.5f, 3.0f)] private float _fovScale = 1.0f;
|
||||
[SerializeField] [Range(-10.0f, 10.0f)] private float _ipdAdjust = 1.0f;
|
||||
[SerializeField] [Range(-0.15f, 0.15f)] private float _offsetLeft;
|
||||
[SerializeField] [Range(-0.15f, 0.15f)] private float _offsetRight;
|
||||
[SerializeField] private LayerMask _layers = -1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Frees the allocated resources.
|
||||
/// </summary>
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
if (_refractionCamera)
|
||||
{
|
||||
Destroy(_refractionCamera.gameObject);
|
||||
}
|
||||
|
||||
if (_renderTextureLeft)
|
||||
{
|
||||
DestroyImmediate(_renderTextureLeft);
|
||||
_renderTextureLeft = null;
|
||||
}
|
||||
|
||||
if (_renderTextureRight)
|
||||
{
|
||||
DestroyImmediate(_renderTextureRight);
|
||||
_renderTextureRight = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to the URP rendering event.
|
||||
/// </summary>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
RenderPipelineManager.beginCameraRendering += RenderPipelineManager_BeginCameraRendering;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from the URP rendering event.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
RenderPipelineManager.beginCameraRendering -= RenderPipelineManager_BeginCameraRendering;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handling Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called by Unity when the rendering starts. It is used to render the magnifying glass refraction.
|
||||
/// </summary>
|
||||
private void RenderPipelineManager_BeginCameraRendering(ScriptableRenderContext context, Camera renderCamera)
|
||||
{
|
||||
if (UxrAvatar.LocalAvatarCamera != renderCamera)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Renderer glassRenderer = GetComponent<Renderer>();
|
||||
|
||||
if (!enabled || !glassRenderer || !glassRenderer.sharedMaterial || !glassRenderer.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!renderCamera)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid recursive rendering
|
||||
|
||||
if (s_insideRendering)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_insideRendering = true;
|
||||
|
||||
CreateResources(renderCamera, out Camera refractionCamera);
|
||||
|
||||
if (!_refractionCamera)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Lower quality for refraction
|
||||
|
||||
int oldPixelLightCount = QualitySettings.pixelLightCount;
|
||||
if (_disablePixelLights)
|
||||
{
|
||||
QualitySettings.pixelLightCount = 0;
|
||||
}
|
||||
|
||||
CopyCameraData(renderCamera, refractionCamera);
|
||||
|
||||
// Update parameters
|
||||
|
||||
refractionCamera.cullingMask = ~(1 << 4) & _layers.value;
|
||||
|
||||
if (TryGetComponent<Renderer>(out var theRenderer))
|
||||
{
|
||||
foreach (Material m in theRenderer.sharedMaterials)
|
||||
{
|
||||
if (m.HasProperty(VarRenderTexLeft))
|
||||
{
|
||||
m.SetTexture(VarRenderTexLeft, _renderTextureLeft);
|
||||
}
|
||||
|
||||
if (m.HasProperty(VarRenderTexRight))
|
||||
{
|
||||
m.SetTexture(VarRenderTexRight, _renderTextureRight);
|
||||
}
|
||||
|
||||
if (m.HasProperty(VarGlassScreenCenter))
|
||||
{
|
||||
Vector3 glassScreenCenter = renderCamera.WorldToViewportPoint(_glassAxes.position);
|
||||
//glassScreenCenter.y = 1.0f - glassScreenCenter.y;
|
||||
m.SetVector(VarGlassScreenCenter, glassScreenCenter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render
|
||||
refractionCamera.enabled = true;
|
||||
|
||||
refractionCamera.targetTexture = _renderTextureLeft;
|
||||
RenderRefraction(context, renderCamera, refractionCamera, true, true);
|
||||
|
||||
refractionCamera.targetTexture = _renderTextureRight;
|
||||
RenderRefraction(context, renderCamera, refractionCamera, true, false);
|
||||
|
||||
refractionCamera.enabled = false;
|
||||
|
||||
// Restore quality
|
||||
|
||||
if (_disablePixelLights)
|
||||
{
|
||||
QualitySettings.pixelLightCount = oldPixelLightCount;
|
||||
}
|
||||
|
||||
s_insideRendering = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Renders the refraction.
|
||||
/// </summary>
|
||||
/// <param name="context">Render context of the scriptable render pipeline</param>
|
||||
/// <param name="renderCamera">Main camera</param>
|
||||
/// <param name="refractionCamera">Camera that will render refraction</param>
|
||||
/// <param name="stereo">Is stereo mode active?</param>
|
||||
/// <param name="isLeft">Is it the left eye in stereo mode?</param>
|
||||
private void RenderRefraction(ScriptableRenderContext context, Camera renderCamera, Camera refractionCamera, bool stereo, bool isLeft)
|
||||
{
|
||||
refractionCamera.ResetWorldToCameraMatrix();
|
||||
refractionCamera.ResetCullingMatrix();
|
||||
|
||||
Matrix4x4 projection = renderCamera.projectionMatrix;
|
||||
|
||||
if (stereo && UxrTrackingDevice.GetHeadsetDevice(out InputDevice headsetDevice))
|
||||
{
|
||||
headsetDevice.TryGetFeatureValue(CommonUsages.leftEyePosition, out Vector3 leftEye);
|
||||
headsetDevice.TryGetFeatureValue(CommonUsages.rightEyePosition, out Vector3 rightEye);
|
||||
float ipd = Vector3.Distance(leftEye, rightEye) * _ipdAdjust;
|
||||
|
||||
if (isLeft)
|
||||
{
|
||||
refractionCamera.transform.SetPositionAndRotation(renderCamera.transform.position - 0.5f * ipd * renderCamera.transform.right, renderCamera.transform.rotation);
|
||||
projection = renderCamera.GetStereoProjectionMatrix(Camera.StereoscopicEye.Left);
|
||||
}
|
||||
else
|
||||
{
|
||||
refractionCamera.transform.SetPositionAndRotation(renderCamera.transform.position + 0.5f * ipd * renderCamera.transform.right, renderCamera.transform.rotation);
|
||||
projection = renderCamera.GetStereoProjectionMatrix(Camera.StereoscopicEye.Right);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
refractionCamera.transform.SetPositionAndRotation(renderCamera.transform.position, renderCamera.transform.rotation);
|
||||
}
|
||||
|
||||
refractionCamera.transform.position += renderCamera.transform.forward * _cameraForwardOffset;
|
||||
|
||||
Vector4 clipPlane = CameraSpacePlane(refractionCamera, _clipPlaneOffset, _glassAxes.position, _glassAxes.forward, 1.0f);
|
||||
|
||||
Vector3 screenPos = renderCamera.WorldToViewportPoint(_glassAxes.position, isLeft ? Camera.MonoOrStereoscopicEye.Left : Camera.MonoOrStereoscopicEye.Right);
|
||||
screenPos.x = (screenPos.x - 0.5f) * 2.0f + (isLeft ? _offsetLeft : _offsetRight);
|
||||
screenPos.y = (screenPos.y - 0.5f) * 2.0f;
|
||||
screenPos.z = 0.0f;
|
||||
Matrix4x4 translateMtxA = Matrix4x4.Translate(-screenPos);
|
||||
Matrix4x4 scaleMtx = Matrix4x4.Scale(new Vector3(_fovScale, _fovScale, 0.3f));
|
||||
Matrix4x4 translateMtxB = Matrix4x4.Translate(screenPos);
|
||||
projection = translateMtxB * scaleMtx * translateMtxA * projection;
|
||||
|
||||
projection = projection.GetObliqueMatrix(clipPlane);
|
||||
refractionCamera.projectionMatrix = projection;
|
||||
refractionCamera.cullingMatrix = refractionCamera.projectionMatrix * refractionCamera.worldToCameraMatrix;
|
||||
|
||||
#if ULTIMATEXR_UNITY_URP
|
||||
UniversalRenderPipeline.RenderSingleCamera(context, refractionCamera);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies data from one camera to another.
|
||||
/// </summary>
|
||||
/// <param name="src">Source data</param>
|
||||
/// <param name="dest">Destination data</param>
|
||||
private void CopyCameraData(Camera src, Camera dest)
|
||||
{
|
||||
if (dest == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_forceClearSkyBox == false)
|
||||
{
|
||||
dest.clearFlags = src.clearFlags;
|
||||
dest.backgroundColor = src.backgroundColor;
|
||||
|
||||
if (src.clearFlags == CameraClearFlags.Skybox)
|
||||
{
|
||||
Skybox srcSky = src.GetComponent(typeof(Skybox)) as Skybox;
|
||||
Skybox dstSky = dest.GetComponent(typeof(Skybox)) as Skybox;
|
||||
|
||||
if (dstSky)
|
||||
{
|
||||
if (!srcSky || !srcSky.material)
|
||||
{
|
||||
dstSky.enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
dstSky.enabled = true;
|
||||
dstSky.material = srcSky.material;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dest.farClipPlane = src.farClipPlane;
|
||||
dest.nearClipPlane = src.nearClipPlane;
|
||||
dest.orthographic = src.orthographic;
|
||||
|
||||
if (XRSettings.enabled == false)
|
||||
{
|
||||
dest.fieldOfView = src.fieldOfView;
|
||||
}
|
||||
|
||||
dest.aspect = src.aspect;
|
||||
dest.orthographicSize = src.orthographicSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates the resources.
|
||||
/// </summary>
|
||||
/// <param name="currentCamera">Render camera</param>
|
||||
/// <param name="refractionCamera">Refraction camera</param>
|
||||
private void CreateResources(Camera currentCamera, out Camera refractionCamera)
|
||||
{
|
||||
refractionCamera = null;
|
||||
|
||||
// Render textures
|
||||
|
||||
if (_oldRenderTextureSize != _textureSize)
|
||||
{
|
||||
CreateRenderTexture(ref _renderTextureLeft);
|
||||
CreateRenderTexture(ref _renderTextureRight);
|
||||
_oldRenderTextureSize = _textureSize;
|
||||
}
|
||||
|
||||
if (_renderTextureLeft == null)
|
||||
{
|
||||
CreateRenderTexture(ref _renderTextureLeft);
|
||||
}
|
||||
|
||||
if (_renderTextureRight == null)
|
||||
{
|
||||
CreateRenderTexture(ref _renderTextureRight);
|
||||
}
|
||||
|
||||
// Refraction camera
|
||||
|
||||
refractionCamera = _refractionCamera;
|
||||
|
||||
if (!refractionCamera)
|
||||
{
|
||||
GameObject go = new GameObject($"{nameof(UxrMagnifyingGlassUrp)} Camera", typeof(Camera), typeof(Skybox));
|
||||
refractionCamera = go.GetComponent<Camera>();
|
||||
_refractionCamera = refractionCamera;
|
||||
|
||||
if (XRSettings.enabled == false)
|
||||
{
|
||||
refractionCamera.fieldOfView = 60.0f;
|
||||
}
|
||||
|
||||
refractionCamera.transform.SetPositionAndRotation(transform);
|
||||
refractionCamera.enabled = true;
|
||||
go.hideFlags = HideFlags.HideAndDontSave;
|
||||
|
||||
if (_forceClearSkyBox)
|
||||
{
|
||||
refractionCamera.clearFlags = CameraClearFlags.Skybox;
|
||||
}
|
||||
|
||||
refractionCamera.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a render texture.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to create</param>
|
||||
private void CreateRenderTexture(ref RenderTexture texture)
|
||||
{
|
||||
if (texture)
|
||||
{
|
||||
DestroyImmediate(texture);
|
||||
}
|
||||
|
||||
texture = new RenderTexture(_textureSize, _textureSize, 0, RenderTextureFormat.Default);
|
||||
|
||||
texture.antiAliasing = _antialias;
|
||||
texture.name = $"{nameof(UxrMagnifyingGlassUrp)} Texture";
|
||||
texture.isPowerOfTwo = true;
|
||||
texture.hideFlags = HideFlags.DontSave;
|
||||
texture.autoGenerateMips = true;
|
||||
texture.useMipMap = true; // Mip-mapping can be used for blur
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a plane point and normal in world coordinates, computes the plane in camera space.
|
||||
/// </summary>
|
||||
/// <param name="targetCamera">Camera</param>
|
||||
/// <param name="offset">Clip plane offset</param>
|
||||
/// <param name="position">Point in plane</param>
|
||||
/// <param name="normal">Plane normal</param>
|
||||
/// <param name="sideSign">Plane side of the camera</param>
|
||||
/// <returns>Plane in camera space</returns>
|
||||
private Vector4 CameraSpacePlane(Camera targetCamera, float offset, Vector3 position, Vector3 normal, float sideSign)
|
||||
{
|
||||
Vector3 offsetPos = position + normal * offset;
|
||||
Matrix4x4 worldToCameraMatrix = targetCamera.worldToCameraMatrix;
|
||||
Vector3 localPos = worldToCameraMatrix.MultiplyPoint(offsetPos);
|
||||
Vector3 localNormal = worldToCameraMatrix.MultiplyVector(normal).normalized * sideSign;
|
||||
return new Vector4(localNormal.x, localNormal.y, localNormal.z, -Vector3.Dot(localPos, localNormal));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
// Constants
|
||||
|
||||
private const string VarRenderTexLeft = "_RenderTexLeft";
|
||||
private const string VarRenderTexRight = "_RenderTexRight";
|
||||
private const string VarGlassScreenCenter = "_GlassScreenCenter";
|
||||
|
||||
// Static
|
||||
|
||||
private static bool s_insideRendering;
|
||||
|
||||
// Internal
|
||||
|
||||
private Camera _refractionCamera;
|
||||
private RenderTexture _renderTextureLeft;
|
||||
private RenderTexture _renderTextureRight;
|
||||
private int _oldRenderTextureSize;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bc97d963e1fafc47a8813c745e4f94d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,429 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrPlanarReflectionBrp.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
using UltimateXR.Core.Components;
|
||||
using UltimateXR.Devices;
|
||||
using UltimateXR.Extensions.Unity;
|
||||
using UltimateXR.Extensions.Unity.Math;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR;
|
||||
|
||||
namespace UltimateXR.Rendering.FX
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that renders a planar reflection image of the scene on an object, using the built-in render pipeline:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// If the Mirror Transform is not set, it will use the transform on the component's GameObject.
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// The component requires a Renderer on the same GameObject with a material compatible with the BRP planar
|
||||
/// reflection. They can be found in the UltimateXR/FX/ category.
|
||||
/// </item>
|
||||
/// <item>The mirror normal is determined by the -forward axis of Mirror Transform.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
public class UxrPlanarReflectionBrp : UxrComponent
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
// Inspector
|
||||
|
||||
[SerializeField] private bool _forceClearSkyBox;
|
||||
[SerializeField] private Transform _mirrorTransform;
|
||||
[SerializeField] private bool _disablePixelLights = true;
|
||||
[SerializeField] private int _textureSize = 1024;
|
||||
[SerializeField] private float _clipPlaneOffset = 0.07f;
|
||||
[SerializeField] private LayerMask _reflectLayers = -1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Frees the allocated resources.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
foreach (KeyValuePair<Camera, Camera> camPair in _reflectionCameras)
|
||||
{
|
||||
if (camPair.Value != null)
|
||||
{
|
||||
DestroyImmediate(camPair.Value.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (_reflectionTextureLeft)
|
||||
{
|
||||
DestroyImmediate(_reflectionTextureLeft);
|
||||
_reflectionTextureLeft = null;
|
||||
}
|
||||
|
||||
if (_reflectionTextureRight)
|
||||
{
|
||||
DestroyImmediate(_reflectionTextureRight);
|
||||
_reflectionTextureRight = null;
|
||||
}
|
||||
|
||||
_reflectionCameras.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by Unity when the object will be rendered. It is used to render the reflection.
|
||||
/// </summary>
|
||||
private void OnWillRenderObject()
|
||||
{
|
||||
Renderer mirrorRenderer = GetComponent<Renderer>();
|
||||
_mirrorTransform = _mirrorTransform ? _mirrorTransform : transform;
|
||||
|
||||
if (!enabled || !mirrorRenderer || !mirrorRenderer.sharedMaterial || !mirrorRenderer.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Camera cam = Camera.current;
|
||||
|
||||
if (!cam)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_reflectionCameras.ContainsValue(cam))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid recursive rendering
|
||||
|
||||
if (s_insideRendering)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_insideRendering = true;
|
||||
|
||||
CreateResources(cam, out Camera reflectionCamera);
|
||||
|
||||
// Lower quality for reflection
|
||||
|
||||
int oldPixelLightCount = QualitySettings.pixelLightCount;
|
||||
if (_disablePixelLights)
|
||||
{
|
||||
QualitySettings.pixelLightCount = 0;
|
||||
}
|
||||
|
||||
CopyCameraData(cam, reflectionCamera);
|
||||
|
||||
// Update parameters
|
||||
|
||||
reflectionCamera.cullingMask = ~(1 << 4) & _reflectLayers.value;
|
||||
|
||||
if (TryGetComponent<Renderer>(out var theRenderer))
|
||||
{
|
||||
foreach (Material m in theRenderer.sharedMaterials)
|
||||
{
|
||||
if (m.HasProperty(VarReflectionTexLeft))
|
||||
{
|
||||
m.SetTexture(VarReflectionTexLeft, _reflectionTextureLeft);
|
||||
}
|
||||
|
||||
if (m.HasProperty(VarReflectionTexRight))
|
||||
{
|
||||
m.SetTexture(VarReflectionTexRight, _reflectionTextureRight);
|
||||
}
|
||||
|
||||
m.SetFloat(VarReflectionTexelSize, _reflectionTextureLeft.width == 0 ? 1.0f : 1.0f / _reflectionTextureLeft.width);
|
||||
m.SetFloat(VarReflectionMaxLodBias, _reflectionTextureLeft.width == 0 ? 0.0f : Mathf.Log(_reflectionTextureLeft.width, 2.0f));
|
||||
m.SetInt(VarStereo, cam.stereoEnabled ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Render
|
||||
|
||||
if (cam.stereoEnabled)
|
||||
{
|
||||
if (cam.stereoTargetEye == StereoTargetEyeMask.Both || cam.stereoTargetEye == StereoTargetEyeMask.Left)
|
||||
{
|
||||
reflectionCamera.targetTexture = _reflectionTextureLeft;
|
||||
RenderReflection(cam, reflectionCamera, true, true, _mirrorTransform.position, -_mirrorTransform.forward);
|
||||
}
|
||||
|
||||
if (cam.stereoTargetEye == StereoTargetEyeMask.Both || cam.stereoTargetEye == StereoTargetEyeMask.Right)
|
||||
{
|
||||
reflectionCamera.targetTexture = _reflectionTextureRight;
|
||||
RenderReflection(cam, reflectionCamera, true, false, _mirrorTransform.position, -_mirrorTransform.forward);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reflectionCamera.targetTexture = _reflectionTextureLeft;
|
||||
RenderReflection(cam, reflectionCamera, false, false, _mirrorTransform.position, -_mirrorTransform.forward);
|
||||
}
|
||||
|
||||
// Restore quality
|
||||
|
||||
if (_disablePixelLights)
|
||||
{
|
||||
QualitySettings.pixelLightCount = oldPixelLightCount;
|
||||
}
|
||||
|
||||
s_insideRendering = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Given a plane point and normal in world coordinates, computes the plane in camera space.
|
||||
/// </summary>
|
||||
/// <param name="targetCamera">Camera</param>
|
||||
/// <param name="offset">Clip plane offset</param>
|
||||
/// <param name="position">Point in plane</param>
|
||||
/// <param name="normal">Plane normal</param>
|
||||
/// <param name="sideSign">Plane side of the camera</param>
|
||||
/// <returns>Plane in camera space</returns>
|
||||
private Vector4 CameraSpacePlane(Camera targetCamera, float offset, Vector3 position, Vector3 normal, float sideSign)
|
||||
{
|
||||
Vector3 offsetPos = position + normal * offset;
|
||||
Matrix4x4 worldToCameraMatrix = targetCamera.worldToCameraMatrix;
|
||||
Vector3 localPos = worldToCameraMatrix.MultiplyPoint(offsetPos);
|
||||
Vector3 localNormal = worldToCameraMatrix.MultiplyVector(normal).normalized * sideSign;
|
||||
return new Vector4(localNormal.x, localNormal.y, localNormal.z, -Vector3.Dot(localPos, localNormal));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the reflection.
|
||||
/// </summary>
|
||||
/// <param name="renderCamera">Main camera</param>
|
||||
/// <param name="reflectionCamera">Camera that will render the reflection</param>
|
||||
/// <param name="stereo">Is stereo mode active?</param>
|
||||
/// <param name="isLeft">Is it the left eye in stereo mode?</param>
|
||||
/// <param name="pos">Reflection plane position</param>
|
||||
/// <param name="normal">Reflection plane normal</param>
|
||||
private void RenderReflection(Camera renderCamera, Camera reflectionCamera, bool stereo, bool isLeft, Vector3 pos, Vector3 normal)
|
||||
{
|
||||
reflectionCamera.ResetWorldToCameraMatrix();
|
||||
reflectionCamera.ResetCullingMatrix();
|
||||
|
||||
Vector3 camPos = renderCamera.transform.position;
|
||||
Quaternion camRot = renderCamera.transform.rotation;
|
||||
|
||||
// Reflect camera using reflection plane
|
||||
|
||||
float d = -Vector3.Dot(normal, pos) - _clipPlaneOffset;
|
||||
Vector4 reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);
|
||||
|
||||
Matrix4x4 reflection = Matrix4x4Ext.GetReflectionMatrix(reflectionPlane);
|
||||
Vector3 offset = Vector3.zero;
|
||||
|
||||
Matrix4x4 projection = renderCamera.projectionMatrix;
|
||||
|
||||
if (stereo && UxrTrackingDevice.GetHeadsetDevice(out InputDevice headsetDevice))
|
||||
{
|
||||
reflectionCamera.transform.parent = renderCamera.transform;
|
||||
|
||||
headsetDevice.TryGetFeatureValue(CommonUsages.centerEyePosition, out Vector3 centerEyePos);
|
||||
headsetDevice.TryGetFeatureValue(CommonUsages.centerEyeRotation, out Quaternion centerEyeRot);
|
||||
headsetDevice.TryGetFeatureValue(CommonUsages.leftEyePosition, out Vector3 leftEyePos);
|
||||
headsetDevice.TryGetFeatureValue(CommonUsages.leftEyeRotation, out Quaternion leftEyeRot);
|
||||
headsetDevice.TryGetFeatureValue(CommonUsages.rightEyePosition, out Vector3 rightEyePos);
|
||||
headsetDevice.TryGetFeatureValue(CommonUsages.rightEyeRotation, out Quaternion rightEyeRot);
|
||||
|
||||
renderCamera.transform.SetPositionAndRotation(centerEyePos, centerEyeRot);
|
||||
|
||||
if (isLeft)
|
||||
{
|
||||
reflectionCamera.transform.SetPositionAndRotation(leftEyePos, leftEyeRot);
|
||||
projection = renderCamera.GetStereoProjectionMatrix(Camera.StereoscopicEye.Left);
|
||||
}
|
||||
else
|
||||
{
|
||||
reflectionCamera.transform.SetPositionAndRotation(rightEyePos, rightEyeRot);
|
||||
projection = renderCamera.GetStereoProjectionMatrix(Camera.StereoscopicEye.Right);
|
||||
}
|
||||
|
||||
renderCamera.transform.SetPositionAndRotation(camPos, camRot);
|
||||
}
|
||||
else
|
||||
{
|
||||
reflectionCamera.transform.SetPositionAndRotation(renderCamera.transform.position, renderCamera.transform.rotation);
|
||||
}
|
||||
|
||||
// World->ReflectionCamera matrix
|
||||
|
||||
reflectionCamera.worldToCameraMatrix *= reflection;
|
||||
|
||||
// Create projection matrix. Near plane will be our reflection plane so that we will clip everything on the other side.
|
||||
|
||||
Vector4 clipPlane = CameraSpacePlane(reflectionCamera, _clipPlaneOffset, pos, normal, 1.0f);
|
||||
projection = projection.GetObliqueMatrix(clipPlane);
|
||||
reflectionCamera.projectionMatrix = projection;
|
||||
reflectionCamera.cullingMatrix = reflectionCamera.projectionMatrix * reflectionCamera.worldToCameraMatrix;
|
||||
|
||||
// Render
|
||||
|
||||
GL.invertCulling = true;
|
||||
reflectionCamera.Render();
|
||||
GL.invertCulling = false;
|
||||
|
||||
reflectionCamera.ResetWorldToCameraMatrix();
|
||||
reflectionCamera.ResetCullingMatrix();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy data from one camera to another
|
||||
/// </summary>
|
||||
/// <param name="src">Source data</param>
|
||||
/// <param name="dest">Destination data</param>
|
||||
private void CopyCameraData(Camera src, Camera dest)
|
||||
{
|
||||
if (dest == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_forceClearSkyBox == false)
|
||||
{
|
||||
dest.clearFlags = src.clearFlags;
|
||||
dest.backgroundColor = src.backgroundColor;
|
||||
if (src.clearFlags == CameraClearFlags.Skybox)
|
||||
{
|
||||
Skybox skySrc = src.GetComponent(typeof(Skybox)) as Skybox;
|
||||
Skybox skyDst = dest.GetComponent(typeof(Skybox)) as Skybox;
|
||||
|
||||
if (skyDst)
|
||||
{
|
||||
if (!skySrc || !skySrc.material)
|
||||
{
|
||||
skyDst.enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
skyDst.enabled = true;
|
||||
skyDst.material = skySrc.material;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dest.farClipPlane = src.farClipPlane;
|
||||
dest.nearClipPlane = src.nearClipPlane;
|
||||
dest.orthographic = src.orthographic;
|
||||
|
||||
if (XRSettings.enabled == false)
|
||||
{
|
||||
dest.fieldOfView = src.fieldOfView;
|
||||
}
|
||||
|
||||
dest.aspect = src.aspect;
|
||||
dest.orthographicSize = src.orthographicSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the internal resources if necessary.
|
||||
/// </summary>
|
||||
/// <param name="currentCamera">Render camera</param>
|
||||
/// <param name="reflectionCamera">Reflection camera</param>
|
||||
private void CreateResources(Camera currentCamera, out Camera reflectionCamera)
|
||||
{
|
||||
reflectionCamera = null;
|
||||
|
||||
// Render textures
|
||||
|
||||
if (_oldReflectionTextureSize != _textureSize)
|
||||
{
|
||||
CreateRenderTexture(ref _reflectionTextureLeft);
|
||||
CreateRenderTexture(ref _reflectionTextureRight);
|
||||
_oldReflectionTextureSize = _textureSize;
|
||||
}
|
||||
|
||||
if (_reflectionTextureLeft == null)
|
||||
{
|
||||
CreateRenderTexture(ref _reflectionTextureLeft);
|
||||
}
|
||||
|
||||
if (_reflectionTextureRight == null)
|
||||
{
|
||||
CreateRenderTexture(ref _reflectionTextureRight);
|
||||
}
|
||||
|
||||
// Reflection camera
|
||||
|
||||
_reflectionCameras.TryGetValue(currentCamera, out reflectionCamera);
|
||||
|
||||
if (!reflectionCamera)
|
||||
{
|
||||
GameObject go = new GameObject($"{nameof(UxrPlanarReflectionBrp)} Camera", typeof(Camera), typeof(Skybox));
|
||||
reflectionCamera = go.GetComponent<Camera>();
|
||||
|
||||
if (XRSettings.enabled == false)
|
||||
{
|
||||
reflectionCamera.fieldOfView = 60.0f;
|
||||
}
|
||||
|
||||
reflectionCamera.transform.SetPositionAndRotation(transform);
|
||||
reflectionCamera.enabled = true;
|
||||
go.hideFlags = HideFlags.HideAndDontSave;
|
||||
_reflectionCameras[currentCamera] = reflectionCamera;
|
||||
|
||||
if (_forceClearSkyBox)
|
||||
{
|
||||
reflectionCamera.clearFlags = CameraClearFlags.Skybox;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a render texture.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to create</param>
|
||||
private void CreateRenderTexture(ref RenderTexture texture)
|
||||
{
|
||||
if (texture)
|
||||
{
|
||||
DestroyImmediate(texture);
|
||||
}
|
||||
|
||||
texture = new RenderTexture(_textureSize, _textureSize, 16);
|
||||
|
||||
texture.name = $"{nameof(UxrPlanarReflectionBrp)} Reflection";
|
||||
texture.isPowerOfTwo = true;
|
||||
texture.hideFlags = HideFlags.DontSave;
|
||||
texture.filterMode = FilterMode.Trilinear;
|
||||
texture.autoGenerateMips = true;
|
||||
texture.useMipMap = true; // We will use auto mip-mapping in our shader for blur
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
// Constants
|
||||
|
||||
private const string VarReflectionTexLeft = "_ReflectionTexLeft";
|
||||
private const string VarReflectionTexRight = "_ReflectionTexRight";
|
||||
private const string VarReflectionMaxLodBias = "_ReflectionMaxLODBias";
|
||||
private const string VarReflectionTexelSize = "_ReflectionTexelSize";
|
||||
private const string VarStereo = "_Stereo";
|
||||
|
||||
// Static
|
||||
|
||||
private static bool s_insideRendering;
|
||||
|
||||
// Internal
|
||||
|
||||
private readonly Dictionary<Camera, Camera> _reflectionCameras = new Dictionary<Camera, Camera>();
|
||||
private RenderTexture _reflectionTextureLeft;
|
||||
private RenderTexture _reflectionTextureRight;
|
||||
private int _oldReflectionTextureSize;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba81d8c2671029b47862fd64de467017
|
||||
timeCreated: 1460614707
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,480 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrPlanarReflectionUrp.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
using UltimateXR.Avatar;
|
||||
using UltimateXR.Core.Components;
|
||||
using UltimateXR.Devices;
|
||||
using UltimateXR.Extensions.Unity;
|
||||
using UltimateXR.Extensions.Unity.Math;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.XR;
|
||||
|
||||
#if ULTIMATEXR_UNITY_URP
|
||||
using UnityEngine.Rendering.Universal;
|
||||
#endif
|
||||
|
||||
namespace UltimateXR.Rendering.FX
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that renders a planar reflection image of the scene on an object, using the URP pipeline:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// If either Mirror Transform or Mirror Renderer ar not set, it will try to get these components on the same
|
||||
/// GameObjects where the planar reflection is.
|
||||
/// </item>
|
||||
/// <item>The mirror normal is determined by the -forward axis of Mirror Transform.</item>
|
||||
/// <item>
|
||||
/// For some reason the reflection will not work if the clear skybox is not set on the camera.
|
||||
/// Unity doesn't seem to compute the projection matrices correctly.
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// The mirror renderer should have a material compatible with the URP planar reflection. They can be found
|
||||
/// in the UltimateXR/FX/ category.
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class UxrPlanarReflectionUrp : UxrComponent
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
// Inspector
|
||||
|
||||
[SerializeField] private bool _forceClearSkyBox;
|
||||
[SerializeField] private Transform _mirrorTransform;
|
||||
[SerializeField] private Renderer _mirrorRenderer;
|
||||
[SerializeField] private Material _materialWhenDisabled;
|
||||
[SerializeField] private bool _disablePixelLights = true;
|
||||
[SerializeField] private int _textureSize = 1024;
|
||||
[SerializeField] private float _clipPlaneOffset;
|
||||
[SerializeField] private LayerMask _reflectLayers = -1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Stores the current material so that a "cheap" material can be assigned when disabled and the original material
|
||||
/// re-assigned when enabled back again.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
if (_mirrorRenderer != null)
|
||||
{
|
||||
_originalMaterial = _mirrorRenderer.sharedMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees resources.
|
||||
/// </summary>
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
foreach (KeyValuePair<Camera, Camera> camPair in _reflectionCameras)
|
||||
{
|
||||
if (camPair.Value != null)
|
||||
{
|
||||
DestroyImmediate(camPair.Value.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (_reflectionTextureLeft)
|
||||
{
|
||||
DestroyImmediate(_reflectionTextureLeft);
|
||||
_reflectionTextureLeft = null;
|
||||
}
|
||||
|
||||
if (_reflectionTextureRight)
|
||||
{
|
||||
DestroyImmediate(_reflectionTextureRight);
|
||||
_reflectionTextureRight = null;
|
||||
}
|
||||
|
||||
if (_renderCameraObject)
|
||||
{
|
||||
Destroy(_renderCameraObject);
|
||||
_renderCameraObject = null;
|
||||
}
|
||||
|
||||
_reflectionCameras.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to the URP camera rendering callback.
|
||||
/// </summary>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
if (_mirrorRenderer != null && _originalMaterial)
|
||||
{
|
||||
_mirrorRenderer.sharedMaterial = _originalMaterial;
|
||||
}
|
||||
|
||||
RenderPipelineManager.beginCameraRendering += RenderPipelineManager_BeginCameraRendering;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from the URP camera rendering callback.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
RenderPipelineManager.beginCameraRendering -= RenderPipelineManager_BeginCameraRendering;
|
||||
|
||||
foreach (KeyValuePair<Camera, Camera> camPair in _reflectionCameras)
|
||||
{
|
||||
if (camPair.Value != null)
|
||||
{
|
||||
camPair.Value.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_mirrorRenderer != null && _materialWhenDisabled)
|
||||
{
|
||||
_mirrorRenderer.sharedMaterial = _materialWhenDisabled;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handling Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called by Unity when the rendering starts. It is used in this component to render the reflection.
|
||||
/// </summary>
|
||||
private void RenderPipelineManager_BeginCameraRendering(ScriptableRenderContext context, Camera renderCamera)
|
||||
{
|
||||
// Avoid other cameras except for the main one.
|
||||
|
||||
_mirrorTransform = _mirrorTransform ? _mirrorTransform : transform;
|
||||
_mirrorRenderer = _mirrorRenderer ? _mirrorRenderer : GetComponent<Renderer>();
|
||||
|
||||
if (UxrAvatar.LocalAvatarCamera != renderCamera || !_mirrorRenderer || !_mirrorRenderer.sharedMaterial)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid recursive rendering
|
||||
|
||||
if (s_insideRendering)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_insideRendering = true;
|
||||
|
||||
CreateResources(renderCamera, out Camera reflectionCamera);
|
||||
|
||||
// Lower quality for reflection
|
||||
|
||||
int oldPixelLightCount = QualitySettings.pixelLightCount;
|
||||
if (_disablePixelLights)
|
||||
{
|
||||
QualitySettings.pixelLightCount = 0;
|
||||
}
|
||||
|
||||
CopyCameraData(renderCamera, reflectionCamera);
|
||||
|
||||
// Update parameters
|
||||
|
||||
reflectionCamera.cullingMask = ~(1 << 4) & _reflectLayers.value;
|
||||
|
||||
|
||||
if (TryGetComponent<Renderer>(out var theRenderer))
|
||||
{
|
||||
foreach (Material m in theRenderer.sharedMaterials)
|
||||
{
|
||||
if (m.HasProperty(VarReflectionTexLeft))
|
||||
{
|
||||
m.SetTexture(VarReflectionTexLeft, _reflectionTextureLeft);
|
||||
}
|
||||
|
||||
if (m.HasProperty(VarReflectionTexRight))
|
||||
{
|
||||
m.SetTexture(VarReflectionTexRight, _reflectionTextureRight);
|
||||
}
|
||||
|
||||
m.SetFloat(VarReflectionMaxLodBias, _reflectionTextureLeft.width == 0 ? 0.0f : Mathf.Log(_reflectionTextureLeft.width, 2.0f));
|
||||
}
|
||||
}
|
||||
|
||||
// Render
|
||||
|
||||
reflectionCamera.enabled = true;
|
||||
|
||||
reflectionCamera.targetTexture = _reflectionTextureLeft;
|
||||
RenderReflection(context, renderCamera, reflectionCamera, true, true, _mirrorTransform.position, -_mirrorTransform.forward);
|
||||
reflectionCamera.targetTexture = _reflectionTextureRight;
|
||||
RenderReflection(context, renderCamera, reflectionCamera, true, false, _mirrorTransform.position, -_mirrorTransform.forward);
|
||||
|
||||
reflectionCamera.enabled = false;
|
||||
|
||||
// Restore quality
|
||||
|
||||
if (_disablePixelLights)
|
||||
{
|
||||
QualitySettings.pixelLightCount = oldPixelLightCount;
|
||||
}
|
||||
|
||||
s_insideRendering = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Renders the reflection.
|
||||
/// </summary>
|
||||
/// <param name="context">Render context of the scriptable render pipeline</param>
|
||||
/// <param name="renderCamera">Main camera</param>
|
||||
/// <param name="reflectionCamera">Camera that will render reflection</param>
|
||||
/// <param name="stereo">Is stereo mode active?</param>
|
||||
/// <param name="isLeft">Is it the left eye in stereo mode?</param>
|
||||
/// <param name="pos">Reflection plane position</param>
|
||||
/// <param name="normal">Reflection plane normal</param>
|
||||
private void RenderReflection(ScriptableRenderContext context, Camera renderCamera, Camera reflectionCamera, bool stereo, bool isLeft, Vector3 pos, Vector3 normal)
|
||||
{
|
||||
reflectionCamera.ResetWorldToCameraMatrix();
|
||||
reflectionCamera.ResetCullingMatrix();
|
||||
|
||||
// Reflect camera using reflection plane
|
||||
|
||||
float d = -Vector3.Dot(normal, pos) - _clipPlaneOffset;
|
||||
Vector4 reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);
|
||||
|
||||
Matrix4x4 reflection = Matrix4x4Ext.GetReflectionMatrix(reflectionPlane);
|
||||
Matrix4x4 projection = renderCamera.projectionMatrix;
|
||||
|
||||
if (stereo && UxrTrackingDevice.GetHeadsetDevice(out InputDevice headsetDevice))
|
||||
{
|
||||
headsetDevice.TryGetFeatureValue(CommonUsages.leftEyePosition, out Vector3 leftEye);
|
||||
headsetDevice.TryGetFeatureValue(CommonUsages.rightEyePosition, out Vector3 rightEye);
|
||||
float ipd = Vector3.Distance(leftEye, rightEye);
|
||||
|
||||
if (isLeft)
|
||||
{
|
||||
reflectionCamera.transform.SetPositionAndRotation(renderCamera.transform.position - 0.5f * ipd * renderCamera.transform.right, renderCamera.transform.rotation);
|
||||
projection = renderCamera.GetStereoProjectionMatrix(Camera.StereoscopicEye.Left);
|
||||
}
|
||||
else
|
||||
{
|
||||
reflectionCamera.transform.SetPositionAndRotation(renderCamera.transform.position + 0.5f * ipd * renderCamera.transform.right, renderCamera.transform.rotation);
|
||||
projection = renderCamera.GetStereoProjectionMatrix(Camera.StereoscopicEye.Right);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reflectionCamera.transform.SetPositionAndRotation(renderCamera.transform);
|
||||
}
|
||||
|
||||
// World->ReflectionCamera matrix
|
||||
|
||||
reflectionCamera.worldToCameraMatrix *= reflection;
|
||||
|
||||
// Create projection matrix. Near plane will be our reflection plane so that we will clip everything on the other side.
|
||||
|
||||
Vector4 clipPlane = GetCameraSpacePlane(reflectionCamera, _clipPlaneOffset, pos, normal, 1.0f);
|
||||
projection = projection.GetObliqueMatrix(clipPlane);
|
||||
reflectionCamera.projectionMatrix = projection;
|
||||
reflectionCamera.cullingMatrix = reflectionCamera.projectionMatrix * reflectionCamera.worldToCameraMatrix;
|
||||
|
||||
// Render
|
||||
|
||||
GL.invertCulling = true;
|
||||
#if ULTIMATEXR_UNITY_URP
|
||||
UniversalRenderPipeline.RenderSingleCamera(context, reflectionCamera);
|
||||
#endif
|
||||
GL.invertCulling = false;
|
||||
|
||||
reflectionCamera.ResetWorldToCameraMatrix();
|
||||
reflectionCamera.ResetCullingMatrix();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies data from one camera to another.
|
||||
/// </summary>
|
||||
/// <param name="src">Source data</param>
|
||||
/// <param name="dest">Destination data</param>
|
||||
private void CopyCameraData(Camera src, Camera dest)
|
||||
{
|
||||
if (dest == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_forceClearSkyBox == false)
|
||||
{
|
||||
dest.clearFlags = src.clearFlags;
|
||||
dest.backgroundColor = src.backgroundColor;
|
||||
|
||||
if (src.clearFlags == CameraClearFlags.Skybox)
|
||||
{
|
||||
Skybox srcSky = src.GetComponent(typeof(Skybox)) as Skybox;
|
||||
Skybox dstSky = dest.GetComponent(typeof(Skybox)) as Skybox;
|
||||
|
||||
if (dstSky)
|
||||
{
|
||||
if (!srcSky || !srcSky.material)
|
||||
{
|
||||
dstSky.enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
dstSky.enabled = true;
|
||||
dstSky.material = srcSky.material;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dest.farClipPlane = src.farClipPlane;
|
||||
dest.nearClipPlane = src.nearClipPlane;
|
||||
dest.orthographic = src.orthographic;
|
||||
|
||||
if (XRSettings.enabled == false)
|
||||
{
|
||||
dest.fieldOfView = src.fieldOfView;
|
||||
}
|
||||
|
||||
dest.aspect = src.aspect;
|
||||
dest.orthographicSize = src.orthographicSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the internal resources if necessary.
|
||||
/// </summary>
|
||||
/// <param name="currentCamera">Render camera</param>
|
||||
/// <param name="reflectionCamera">Reflection camera</param>
|
||||
private void CreateResources(Camera currentCamera, out Camera reflectionCamera)
|
||||
{
|
||||
reflectionCamera = null;
|
||||
|
||||
// Render textures
|
||||
|
||||
if (_oldReflectionTextureSize != _textureSize)
|
||||
{
|
||||
CreateRenderTexture(ref _reflectionTextureLeft);
|
||||
CreateRenderTexture(ref _reflectionTextureRight);
|
||||
_oldReflectionTextureSize = _textureSize;
|
||||
}
|
||||
|
||||
if (_reflectionTextureLeft == null)
|
||||
{
|
||||
CreateRenderTexture(ref _reflectionTextureLeft);
|
||||
}
|
||||
|
||||
if (_reflectionTextureRight == null)
|
||||
{
|
||||
CreateRenderTexture(ref _reflectionTextureRight);
|
||||
}
|
||||
|
||||
// Reflection camera
|
||||
|
||||
_reflectionCameras.TryGetValue(currentCamera, out reflectionCamera);
|
||||
|
||||
if (!reflectionCamera)
|
||||
{
|
||||
if (_renderCameraObject != null)
|
||||
{
|
||||
reflectionCamera = _renderCameraObject.GetComponent<Camera>();
|
||||
}
|
||||
else
|
||||
{
|
||||
_renderCameraObject = new GameObject($"{nameof(UxrPlanarReflectionUrp)} Camera", typeof(Camera), typeof(Skybox));
|
||||
reflectionCamera = _renderCameraObject.GetComponent<Camera>();
|
||||
}
|
||||
|
||||
if (XRSettings.enabled == false)
|
||||
{
|
||||
reflectionCamera.fieldOfView = 60.0f;
|
||||
}
|
||||
|
||||
reflectionCamera.transform.SetPositionAndRotation(transform);
|
||||
reflectionCamera.enabled = true;
|
||||
//go.hideFlags = HideFlags.HideAndDontSave;
|
||||
_reflectionCameras[currentCamera] = reflectionCamera;
|
||||
|
||||
if (_forceClearSkyBox)
|
||||
{
|
||||
reflectionCamera.clearFlags = CameraClearFlags.Skybox;
|
||||
}
|
||||
|
||||
reflectionCamera.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a render texture.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to create</param>
|
||||
private void CreateRenderTexture(ref RenderTexture texture)
|
||||
{
|
||||
if (texture)
|
||||
{
|
||||
DestroyImmediate(texture);
|
||||
}
|
||||
|
||||
texture = new RenderTexture(_textureSize, _textureSize, 16);
|
||||
|
||||
texture.name = $"{nameof(UxrPlanarReflectionUrp)} Reflection";
|
||||
texture.isPowerOfTwo = true;
|
||||
texture.hideFlags = HideFlags.DontSave;
|
||||
texture.filterMode = FilterMode.Trilinear;
|
||||
texture.autoGenerateMips = true;
|
||||
texture.useMipMap = true; // Mip mapping can be used in shaders for blurring
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a plane point and normal in world coordinates, computes the plane in camera space.
|
||||
/// </summary>
|
||||
/// <param name="cam">Camera</param>
|
||||
/// <param name="offset">Clip plane offset</param>
|
||||
/// <param name="position">Point in plane</param>
|
||||
/// <param name="normal">Plane normal</param>
|
||||
/// <param name="sideSign">Plane side of the camera</param>
|
||||
/// <returns>Plane in camera space</returns>
|
||||
private Vector4 GetCameraSpacePlane(Camera cam, float offset, Vector3 position, Vector3 normal, float sideSign)
|
||||
{
|
||||
Vector3 offsetPos = position + normal * offset;
|
||||
Matrix4x4 m = cam.worldToCameraMatrix;
|
||||
Vector3 cpos = m.MultiplyPoint(offsetPos);
|
||||
Vector3 cnormal = m.MultiplyVector(normal).normalized * sideSign;
|
||||
return new Vector4(cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos, cnormal));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
// Constants
|
||||
|
||||
private const string VarReflectionTexLeft = "_ReflectionTexLeft";
|
||||
private const string VarReflectionTexRight = "_ReflectionTexRight";
|
||||
private const string VarReflectionMaxLodBias = "_ReflectionMaxLODBias";
|
||||
|
||||
// Static
|
||||
|
||||
private static bool s_insideRendering;
|
||||
private readonly Dictionary<Camera, Camera> _reflectionCameras = new Dictionary<Camera, Camera>();
|
||||
|
||||
// Internal
|
||||
|
||||
private GameObject _renderCameraObject;
|
||||
private RenderTexture _reflectionTextureLeft;
|
||||
private RenderTexture _reflectionTextureRight;
|
||||
private int _oldReflectionTextureSize;
|
||||
private Material _originalMaterial;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93a4d280a4a0ede4bbbb04b782945354
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/UltimateXR/Runtime/Scripts/Rendering/LOD.meta
Normal file
8
Assets/UltimateXR/Runtime/Scripts/Rendering/LOD.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 942870e1137aa2b429bd6713f21ce6ec
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
222
Assets/UltimateXR/Runtime/Scripts/Rendering/LOD/UxrLODGroup.cs
Normal file
222
Assets/UltimateXR/Runtime/Scripts/Rendering/LOD/UxrLODGroup.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrLODGroup.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections;
|
||||
using UltimateXR.Avatar;
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Core.Components;
|
||||
using UltimateXR.Core.Settings;
|
||||
using UltimateXR.Extensions.Unity.Render;
|
||||
using UltimateXR.Locomotion;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Rendering.LOD
|
||||
{
|
||||
/// <summary>
|
||||
/// Fixes LOD levels in VR, which by default do not work correctly. See
|
||||
/// https://forum.unity.com/threads/lodgroup-in-vr.455394/.<br />
|
||||
/// In addition, when using non-smooth locomotion, it can switch LOD levels only when the avatar moved.
|
||||
/// This avoids LOD switching caused by head movement, where the camera is. When using a smooth locomotion
|
||||
/// system it will use regular LOD switching.<br />
|
||||
/// Using LOD switching on teleports only can be disabled using the "Only Fix LOD Bias" parameter.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(LODGroup))]
|
||||
public class UxrLODGroup : UxrComponent<UxrLODGroup>
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private bool _onlyFixLodBias;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Unity LODGroup component.
|
||||
/// </summary>
|
||||
public LODGroup UnityLODGroup => GetCachedComponent<LODGroup>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to the global event called whenever any <see cref="UxrLocomotion" /> component is enabled.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
if (!_onlyFixLodBias)
|
||||
{
|
||||
UxrAvatar.LocalAvatarChanged += UxrAvatar_LocalAvatarChanged;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from the global event called whenever any <see cref="UxrLocomotion" /> component is enabled.
|
||||
/// </summary>
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
if (!_onlyFixLodBias)
|
||||
{
|
||||
UxrAvatar.LocalAvatarChanged -= UxrAvatar_LocalAvatarChanged;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to the event called whenever an avatar was moved.
|
||||
/// Also starts the LOD Bias fix coroutine and sets up the correct LOD level if there is a local avatar.
|
||||
/// </summary>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
if (!_onlyFixLodBias)
|
||||
{
|
||||
UxrAvatar.GlobalAvatarMoved += UxrAvatar_GlobalAvatarMoved;
|
||||
UpdateMode(UxrAvatar.LocalAvatar);
|
||||
}
|
||||
|
||||
StartCoroutine(FixLodBiasCoroutine());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from the event called whenever an avatar was moved.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
if (!_onlyFixLodBias)
|
||||
{
|
||||
UxrAvatar.GlobalAvatarMoved -= UxrAvatar_GlobalAvatarMoved;
|
||||
UnityLODGroup.ForceLOD(-1);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Coroutines
|
||||
|
||||
/// <summary>
|
||||
/// Fixes the LOD bias so that the LOD switching in VR behaves like in the editor.
|
||||
/// From: https://forum.unity.com/threads/lodgroup-in-vr.455394/
|
||||
/// </summary>
|
||||
/// <returns>Coroutine enumerator</returns>
|
||||
private IEnumerator FixLodBiasCoroutine()
|
||||
{
|
||||
// Wait until the camera gets the correct VR FOV
|
||||
|
||||
yield return null;
|
||||
yield return null;
|
||||
yield return null;
|
||||
|
||||
while (UxrAvatar.LocalAvatarCamera == null)
|
||||
{
|
||||
if (UxrAvatar.LocalAvatar != null && UxrAvatar.LocalStandardAvatarController == null)
|
||||
{
|
||||
// Replay or other.
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Fix?
|
||||
|
||||
if (!s_lodGroupFixed)
|
||||
{
|
||||
float oldLodBias = QualitySettings.lodBias;
|
||||
float editorCameraRadians = Mathf.PI / 3.0f;
|
||||
QualitySettings.lodBias *= Mathf.Tan(UxrAvatar.LocalAvatarCamera.fieldOfView * Mathf.Deg2Rad / 2) / Mathf.Tan(editorCameraRadians / 2);
|
||||
s_lodGroupFixed = true;
|
||||
|
||||
if (UxrGlobalSettings.Instance.LogLevelRendering >= UxrLogLevel.Relevant)
|
||||
{
|
||||
Debug.Log($"{UxrConstants.RenderingModule}: Fixing LOD Bias for VR cameras. Old value = {oldLodBias}, new value = {QualitySettings.lodBias}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handling Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever the local avatar changed. We use it to store whether the current local avatar
|
||||
/// uses smooth locomotion.
|
||||
/// </summary>
|
||||
/// <param name="sender">Event sender</param>
|
||||
/// <param name="e">Event parameters</param>
|
||||
private void UxrAvatar_LocalAvatarChanged(object sender, UxrAvatarEventArgs e)
|
||||
{
|
||||
UpdateMode(e.Avatar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever an avatar moved. In non-smooth locomotion mode it will be used to switch LOD levels manually.
|
||||
/// </summary>
|
||||
/// <param name="sender">Event sender</param>
|
||||
/// <param name="e">Event parameters</param>
|
||||
private void UxrAvatar_GlobalAvatarMoved(object sender, UxrAvatarMoveEventArgs e)
|
||||
{
|
||||
UxrAvatar avatar = sender as UxrAvatar;
|
||||
|
||||
if (avatar == UxrAvatar.LocalAvatar && !_isSmoothLocomotionEnabled)
|
||||
{
|
||||
EnableLevelRenderers(avatar.CameraComponent);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Manually enables the LOD levels for a given camera.
|
||||
/// </summary>
|
||||
/// <param name="cam"></param>
|
||||
private void EnableLevelRenderers(Camera cam)
|
||||
{
|
||||
UnityLODGroup.ForceLOD(UnityLODGroup.GetVisibleLevel(cam));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the continuous/discrete operation mode of the component.
|
||||
/// </summary>
|
||||
/// <param name="avatar">Avatar to update the current mode for</param>
|
||||
private void UpdateMode(UxrAvatar avatar)
|
||||
{
|
||||
_isSmoothLocomotionEnabled = avatar != null && avatar.AvatarController != null && avatar.AvatarController.UsesSmoothLocomotion;
|
||||
|
||||
bool autoLod = _isSmoothLocomotionEnabled || avatar == null || avatar.CameraComponent == null;
|
||||
|
||||
if (!autoLod)
|
||||
{
|
||||
if (avatar != null && avatar.CameraComponent != null)
|
||||
{
|
||||
EnableLevelRenderers(avatar.CameraComponent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityLODGroup.EnableAllLevelRenderers();
|
||||
UnityLODGroup.ForceLOD(-1);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
private static bool s_lodGroupFixed;
|
||||
private bool _isSmoothLocomotionEnabled = true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b6dfff9c2b773a40bd5530a4f42a858
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user