This commit is contained in:
2026-01-06 17:47:11 +01:00
commit 8423e2b790
1313 changed files with 295355 additions and 0 deletions

View File

@@ -0,0 +1,171 @@
using System.Collections.Generic;
using UnityEngine.XR.Hands.Processing;
namespace UnityEngine.XR.Hands.Samples.VisualizerSample
{
/// <summary>
/// Example hand processor that applies transformations on the root poses to
/// modify the hands skeleton. Note it is possible to modify the bones
/// directly for more advanced use cases that are not shown here.
/// </summary>
public class HandProcessor : MonoBehaviour, IXRHandProcessor
{
/// <inheritdoc />
public int callbackOrder => 0;
/// <summary>
/// The mode to use for the sample processor.
/// </summary>
public enum ProcessorExampleMode
{
/// <summary>
/// No processing is applied.
/// </summary>
None,
/// <summary>
/// Smooths the hand root pose of the left and right hands with interpolated positions
/// </summary>
Smoothing,
/// <summary>
/// Inverts the left and right hands.
/// </summary>
Invert
}
// Variables used for smoothing hand movements.
bool m_FirstFrame = false;
Vector3 m_LastLeftHandPosition;
Vector3 m_LastRightHandPosition;
Pose m_LeftHandPose = Pose.identity;
Pose m_RightHandPose = Pose.identity;
[SerializeField]
[Tooltip("The mode to use for the sample processor.")]
ProcessorExampleMode m_ProcessorExampleMode = ProcessorExampleMode.Smoothing;
ProcessorExampleMode m_LastProcessorExampleMode = ProcessorExampleMode.None;
/// <summary>
/// The <see cref="ProcessorExampleMode"/> to use for the sample processor.
/// </summary>
public ProcessorExampleMode processorExampleMode
{
get => m_ProcessorExampleMode;
set => m_ProcessorExampleMode = value;
}
// Smoothing factors for the left and right hands.
[Header("Smoothing parameters")]
[SerializeField]
[Tooltip("The smoothing factor to use when smoothing the root of the left hand in the sample processor. Use 0 for no smoothing.")]
float m_LeftHandSmoothingFactor = 16f;
[SerializeField]
[Tooltip("The smoothing factor to use when smoothing the root of the right hand in the sample processor. Use 0 for no smoothing.")]
float m_RightHandSmoothingFactor = 16f;
/// <inheritdoc />
public void ProcessJoints(XRHandSubsystem subsystem, XRHandSubsystem.UpdateSuccessFlags successFlags, XRHandSubsystem.UpdateType updateType)
{
switch (m_ProcessorExampleMode)
{
case ProcessorExampleMode.Smoothing:
SmoothHandsExample(subsystem, successFlags, updateType, m_LastProcessorExampleMode != m_ProcessorExampleMode);
break;
case ProcessorExampleMode.Invert:
InvertHandsExample(subsystem, successFlags, updateType);
break;
}
m_LastProcessorExampleMode = m_ProcessorExampleMode;
}
// Smooths the hand movements of an XRHandSubsystem by updating the root
// pose of the left and right hands with interpolated positions.
void SmoothHandsExample(XRHandSubsystem subsystem, XRHandSubsystem.UpdateSuccessFlags successFlags, XRHandSubsystem.UpdateType updateType, bool modeChanged)
{
var leftHand = subsystem.leftHand;
var rightHand = subsystem.rightHand;
if (leftHand.isTracked && m_LeftHandSmoothingFactor > 0)
{
var leftPose = leftHand.rootPose;
var currentLeftHandPosition = leftPose.position;
if (!m_FirstFrame && !modeChanged)
{
float tweenAmt = Time.deltaTime * m_LeftHandSmoothingFactor;
currentLeftHandPosition = Vector3.Lerp(m_LastLeftHandPosition, currentLeftHandPosition, tweenAmt);
m_LeftHandPose.position = currentLeftHandPosition;
m_LeftHandPose.rotation = leftPose.rotation;
leftHand.SetRootPose(m_LeftHandPose);
subsystem.SetCorrespondingHand(leftHand);
}
m_LastLeftHandPosition = currentLeftHandPosition;
}
if (rightHand.isTracked && m_RightHandSmoothingFactor > 0)
{
var rightPose = rightHand.rootPose;
var currentRightHandPosition = rightPose.position;
if (!m_FirstFrame && !modeChanged)
{
float tweenAmt = Time.deltaTime * m_RightHandSmoothingFactor;
currentRightHandPosition = Vector3.Lerp(m_LastRightHandPosition, currentRightHandPosition, tweenAmt);
m_RightHandPose.position = currentRightHandPosition;
m_RightHandPose.rotation = rightPose.rotation;
rightHand.SetRootPose(m_RightHandPose);
subsystem.SetCorrespondingHand(rightHand);
}
m_LastRightHandPosition = currentRightHandPosition;
}
}
// Call this from process joints to try inverting the user's hands.
void InvertHandsExample(XRHandSubsystem subsystem, XRHandSubsystem.UpdateSuccessFlags successFlags, XRHandSubsystem.UpdateType updateType)
{
var leftHand = subsystem.leftHand;
var leftHandPose = leftHand.rootPose;
var rightHand = subsystem.rightHand;
var rightHandPose = rightHand.rootPose;
if (leftHand.isTracked)
{
leftHand.SetRootPose(rightHandPose);
subsystem.SetCorrespondingHand(leftHand);
rightHand.SetRootPose(leftHandPose);
subsystem.SetCorrespondingHand(rightHand);
}
}
void Update()
{
if (m_Subsystem != null)
return;
SubsystemManager.GetSubsystems(s_SubsystemsReuse);
if (s_SubsystemsReuse.Count == 0)
return;
m_Subsystem = s_SubsystemsReuse[0];
m_Subsystem.RegisterProcessor(this);
}
void OnDisable()
{
if (m_Subsystem != null)
{
m_Subsystem.UnregisterProcessor(this);
m_Subsystem = null;
}
}
XRHandSubsystem m_Subsystem;
static List<XRHandSubsystem> s_SubsystemsReuse = new List<XRHandSubsystem>();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ac5903b776721d74786a2e43f00b949a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,608 @@
using System.Collections.Generic;
using UnityEngine.Serialization;
namespace UnityEngine.XR.Hands.Samples.VisualizerSample
{
// Hand rig setups can differ between platforms. In these cases, the HandVisualizer supports displaying unique hands on a per-platform basis.
// If you would like to customize the hand meshes that are displayed by the HandVisualizer, based on the platform you are using,
// you will need to replace the rigged hand mesh references assigned to the corresponding fields for that platform.
// For Meta Quest devices, assign your rigged hand meshes to the "m_MetaQuestLeftHandMesh" & "m_MetaQuestRightHandMesh" fields.
// For Android XR devices, assign your rigged hand meshes to the "m_AndroidXRLeftHandMesh" & "m_AndroidXRRightHandMesh" fields.
// The rigged hand meshes that are assigned for a given platform will be displayed when that platform is detected,
// and any other rigged hand meshes assigned for other undetected platforms will not be displayed.
/// <summary>
/// This component visualizes the hand joints and mesh for the left and right hands.
/// </summary>
public class HandVisualizer : MonoBehaviour
{
/// <summary>
/// The type of velocity to visualize.
/// </summary>
public enum VelocityType
{
/// <summary>
/// Visualize the linear velocity of the joint.
/// </summary>
Linear,
/// <summary>
/// Visualize the angular velocity of the joint.
/// </summary>
Angular,
/// <summary>
/// Do not visualize velocity.
/// </summary>
None,
}
[SerializeField]
[Tooltip("If this is enabled, this component will enable the Input System internal feature flag 'USE_OPTIMIZED_CONTROLS'. You must have at least version 1.5.0 of the Input System and have its backend enabled for this to take effect.")]
bool m_UseOptimizedControls;
[SerializeField, FormerlySerializedAs("m_LeftHandMesh")]
[Tooltip("References either a prefab or a GameObject in the scene that will be used to visualize the left hand.")]
GameObject m_MetaQuestLeftHandMesh;
[SerializeField, FormerlySerializedAs("m_RightHandMesh")]
[Tooltip("References either a prefab or a GameObject in the scene that will be used to visualize the right hand.")]
GameObject m_MetaQuestRightHandMesh;
[SerializeField]
[Tooltip("References either a prefab or a GameObject in the scene that will be used to visualize the left hand on Android XR devices." +
"<br><br><b>Instructions for how to setup and use these meshes can be found at the top of the <b>HandVisualizer.cs class</b>")]
GameObject m_AndroidXRLeftHandMesh;
[SerializeField]
[Tooltip("References either a prefab or a GameObject in the scene that will be used to visualize the right hand on Android XR devices." +
"<br><br><b>Instructions for how to setup and use these meshes can be found at the top of the <b>HandVisualizer.cs class</b>")]
GameObject m_AndroidXRRightHandMesh;
[SerializeField]
[Tooltip("(Optional) If this is set, the hand meshes will be assigned this material.")]
Material m_HandMeshMaterial;
[SerializeField]
[Tooltip("Tells the Hand Visualizer to draw the meshes for the hands.")]
bool m_DrawMeshes;
bool m_PreviousDrawMeshes;
/// <summary>
/// Tells the Hand Visualizer to draw the meshes for the hands.
/// </summary>
public bool drawMeshes
{
get => m_DrawMeshes;
set => m_DrawMeshes = value;
}
[SerializeField]
[Tooltip("The prefab that will be used to visualize the joints for debugging.")]
GameObject m_DebugDrawPrefab;
[SerializeField]
[Tooltip("Tells the Hand Visualizer to draw the debug joints for the hands.")]
bool m_DebugDrawJoints;
bool m_PreviousDebugDrawJoints;
/// <summary>
/// Tells the Hand Visualizer to draw the debug joints for the hands.
/// </summary>
public bool debugDrawJoints
{
get => m_DebugDrawJoints;
set => m_DebugDrawJoints = value;
}
[SerializeField]
[Tooltip("Prefab to use for visualizing the velocity.")]
GameObject m_VelocityPrefab;
[SerializeField]
[Tooltip("The type of velocity to visualize.")]
VelocityType m_VelocityType;
VelocityType m_PreviousVelocityType;
/// <summary>
/// The type of velocity to visualize.
/// </summary>
public VelocityType velocityType
{
get => m_VelocityType;
set => m_VelocityType = value;
}
XRHandSubsystem m_Subsystem;
HandGameObjects m_LeftHandGameObjects;
HandGameObjects m_RightHandGameObjects;
static readonly List<XRHandSubsystem> s_SubsystemsReuse = new List<XRHandSubsystem>();
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void Awake()
{
#if ENABLE_INPUT_SYSTEM
if (m_UseOptimizedControls)
InputSystem.InputSystem.settings.SetInternalFeatureFlag("USE_OPTIMIZED_CONTROLS", true);
#endif // ENABLE_INPUT_SYSTEM
}
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void OnEnable()
{
if (m_Subsystem == null)
return;
UpdateRenderingVisibility(m_LeftHandGameObjects, m_Subsystem.leftHand.isTracked);
UpdateRenderingVisibility(m_RightHandGameObjects, m_Subsystem.rightHand.isTracked);
}
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void OnDisable()
{
if (m_Subsystem != null)
{
m_Subsystem.trackingAcquired -= OnTrackingAcquired;
m_Subsystem.trackingLost -= OnTrackingLost;
m_Subsystem.updatedHands -= OnUpdatedHands;
m_Subsystem = null;
}
UpdateRenderingVisibility(m_LeftHandGameObjects, false);
UpdateRenderingVisibility(m_RightHandGameObjects, false);
}
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void OnDestroy()
{
if (m_LeftHandGameObjects != null)
{
m_LeftHandGameObjects.OnDestroy();
m_LeftHandGameObjects = null;
}
if (m_RightHandGameObjects != null)
{
m_RightHandGameObjects.OnDestroy();
m_RightHandGameObjects = null;
}
}
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void Update()
{
if (m_Subsystem != null && m_Subsystem.running)
return;
SubsystemManager.GetSubsystems(s_SubsystemsReuse);
var foundRunningHandSubsystem = false;
for (var i = 0; i < s_SubsystemsReuse.Count; ++i)
{
var handSubsystem = s_SubsystemsReuse[i];
if (handSubsystem.running)
{
UnsubscribeHandSubsystem();
m_Subsystem = handSubsystem;
foundRunningHandSubsystem = true;
break;
}
}
if (!foundRunningHandSubsystem)
return;
GameObject selectedLeftHandMesh = null, selectedRightHandMesh = null;
if (m_Subsystem.detectedHandMeshLayout == XRDetectedHandMeshLayout.OpenXRAndroidXR)
{
selectedLeftHandMesh = m_AndroidXRLeftHandMesh;
selectedRightHandMesh = m_AndroidXRRightHandMesh;
}
else
{
selectedLeftHandMesh = m_MetaQuestLeftHandMesh;
selectedRightHandMesh = m_MetaQuestRightHandMesh;
}
if (m_LeftHandGameObjects == null)
{
m_LeftHandGameObjects = new HandGameObjects(
Handedness.Left,
transform,
selectedLeftHandMesh,
m_HandMeshMaterial,
m_DebugDrawPrefab,
m_VelocityPrefab);
}
if (m_RightHandGameObjects == null)
{
m_RightHandGameObjects = new HandGameObjects(
Handedness.Right,
transform,
selectedRightHandMesh,
m_HandMeshMaterial,
m_DebugDrawPrefab,
m_VelocityPrefab);
}
UpdateRenderingVisibility(m_LeftHandGameObjects, m_Subsystem.leftHand.isTracked);
UpdateRenderingVisibility(m_RightHandGameObjects, m_Subsystem.rightHand.isTracked);
m_PreviousDrawMeshes = m_DrawMeshes;
m_PreviousDebugDrawJoints = m_DebugDrawJoints;
m_PreviousVelocityType = m_VelocityType;
SubscribeHandSubsystem();
}
void SubscribeHandSubsystem()
{
if (m_Subsystem == null)
return;
m_Subsystem.trackingAcquired += OnTrackingAcquired;
m_Subsystem.trackingLost += OnTrackingLost;
m_Subsystem.updatedHands += OnUpdatedHands;
}
void UnsubscribeHandSubsystem()
{
if (m_Subsystem == null)
return;
m_Subsystem.trackingAcquired -= OnTrackingAcquired;
m_Subsystem.trackingLost -= OnTrackingLost;
m_Subsystem.updatedHands -= OnUpdatedHands;
}
void UpdateRenderingVisibility(HandGameObjects handGameObjects, bool isTracked)
{
if (handGameObjects == null)
return;
handGameObjects.ToggleDrawMesh(m_DrawMeshes);
handGameObjects.ToggleDebugDrawJoints(m_DebugDrawJoints && isTracked);
handGameObjects.SetVelocityType(isTracked ? m_VelocityType : VelocityType.None);
}
void OnTrackingAcquired(XRHand hand)
{
switch (hand.handedness)
{
case Handedness.Left:
UpdateRenderingVisibility(m_LeftHandGameObjects, true);
break;
case Handedness.Right:
UpdateRenderingVisibility(m_RightHandGameObjects, true);
break;
}
}
void OnTrackingLost(XRHand hand)
{
switch (hand.handedness)
{
case Handedness.Left:
UpdateRenderingVisibility(m_LeftHandGameObjects, false);
break;
case Handedness.Right:
UpdateRenderingVisibility(m_RightHandGameObjects, false);
break;
}
}
void OnUpdatedHands(XRHandSubsystem subsystem, XRHandSubsystem.UpdateSuccessFlags updateSuccessFlags, XRHandSubsystem.UpdateType updateType)
{
// We have no game logic depending on the Transforms, so early out here
// (add game logic before this return here, directly querying from
// subsystem.leftHand and subsystem.rightHand using GetJoint on each hand)
if (updateType == XRHandSubsystem.UpdateType.Dynamic)
return;
bool leftHandTracked = subsystem.leftHand.isTracked;
bool rightHandTracked = subsystem.rightHand.isTracked;
if (m_PreviousDrawMeshes != m_DrawMeshes)
{
m_LeftHandGameObjects.ToggleDrawMesh(m_DrawMeshes);
m_RightHandGameObjects.ToggleDrawMesh(m_DrawMeshes);
m_PreviousDrawMeshes = m_DrawMeshes;
}
if (m_PreviousDebugDrawJoints != m_DebugDrawJoints)
{
m_LeftHandGameObjects.ToggleDebugDrawJoints(m_DebugDrawJoints && leftHandTracked);
m_RightHandGameObjects.ToggleDebugDrawJoints(m_DebugDrawJoints && rightHandTracked);
m_PreviousDebugDrawJoints = m_DebugDrawJoints;
}
if (m_PreviousVelocityType != m_VelocityType)
{
m_LeftHandGameObjects.SetVelocityType(leftHandTracked ? m_VelocityType : VelocityType.None);
m_RightHandGameObjects.SetVelocityType(rightHandTracked ? m_VelocityType : VelocityType.None);
m_PreviousVelocityType = m_VelocityType;
}
m_LeftHandGameObjects.UpdateJoints(
subsystem.leftHand,
(updateSuccessFlags & XRHandSubsystem.UpdateSuccessFlags.LeftHandJoints) != 0,
m_DebugDrawJoints,
m_VelocityType);
m_RightHandGameObjects.UpdateJoints(
subsystem.rightHand,
(updateSuccessFlags & XRHandSubsystem.UpdateSuccessFlags.RightHandJoints) != 0,
m_DebugDrawJoints,
m_VelocityType);
}
class HandGameObjects
{
GameObject m_HandRoot;
GameObject m_DrawJointsParent;
GameObject[] m_DrawJoints = new GameObject[XRHandJointID.EndMarker.ToIndex()];
GameObject[] m_VelocityParents = new GameObject[XRHandJointID.EndMarker.ToIndex()];
LineRenderer[] m_Lines = new LineRenderer[XRHandJointID.EndMarker.ToIndex()];
JointVisualizer[] m_JointVisualizers = new JointVisualizer[XRHandJointID.EndMarker.ToIndex()];
static Vector3[] s_LinePointsReuse = new Vector3[2];
XRHandMeshController m_MeshController;
const float k_LineWidth = 0.005f;
public HandGameObjects(
Handedness handedness,
Transform parent,
GameObject meshPrefab,
Material meshMaterial,
GameObject debugDrawPrefab,
GameObject velocityPrefab)
{
void AssignJoint(
XRHandJointID jointId,
Transform jointDrivenTransform,
Transform drawJointsParent)
{
var jointIndex = jointId.ToIndex();
m_DrawJoints[jointIndex] = Instantiate(debugDrawPrefab);
m_DrawJoints[jointIndex].transform.parent = drawJointsParent;
m_DrawJoints[jointIndex].name = jointId.ToString();
m_VelocityParents[jointIndex] = Instantiate(velocityPrefab);
m_VelocityParents[jointIndex].transform.parent = jointDrivenTransform;
m_Lines[jointIndex] = m_DrawJoints[jointIndex].GetComponent<LineRenderer>();
m_Lines[jointIndex].startWidth = m_Lines[jointIndex].endWidth = k_LineWidth;
s_LinePointsReuse[0] = s_LinePointsReuse[1] = jointDrivenTransform.position;
m_Lines[jointIndex].SetPositions(s_LinePointsReuse);
if (m_DrawJoints[jointIndex].TryGetComponent<JointVisualizer>(out var jointVisualizer))
m_JointVisualizers[jointIndex] = jointVisualizer;
}
var isSceneObject = meshPrefab.scene.IsValid();
m_HandRoot = isSceneObject ? meshPrefab : Instantiate(meshPrefab, parent);
m_HandRoot.SetActive(false); // Deactivate so that added components do not run OnEnable before they are finished being set up
m_HandRoot.transform.localPosition = Vector3.zero;
m_HandRoot.transform.localRotation = Quaternion.identity;
var handEvents = m_HandRoot.GetComponent<XRHandTrackingEvents>();
if (handEvents == null)
{
handEvents = m_HandRoot.AddComponent<XRHandTrackingEvents>();
handEvents.updateType = XRHandTrackingEvents.UpdateTypes.Dynamic;
handEvents.handedness = handedness;
}
m_MeshController = m_HandRoot.GetComponent<XRHandMeshController>();
if (m_MeshController == null)
{
m_MeshController = m_HandRoot.AddComponent<XRHandMeshController>();
for (var childIndex = 0; childIndex < m_HandRoot.transform.childCount; ++childIndex)
{
var childTransform = m_HandRoot.transform.GetChild(childIndex);
if (childTransform.TryGetComponent<SkinnedMeshRenderer>(out var renderer))
m_MeshController.handMeshRenderer = renderer;
}
m_MeshController.handTrackingEvents = handEvents;
}
if (meshMaterial != null)
{
m_MeshController.handMeshRenderer.sharedMaterial = meshMaterial;
}
var skeletonDriver = m_HandRoot.GetComponent<XRHandSkeletonDriver>();
if (skeletonDriver == null)
{
skeletonDriver = m_HandRoot.AddComponent<XRHandSkeletonDriver>();
skeletonDriver.jointTransformReferences = new List<JointToTransformReference>();
Transform root = null;
for (var childIndex = 0; childIndex < m_HandRoot.transform.childCount; ++childIndex)
{
var child = m_HandRoot.transform.GetChild(childIndex);
if (child.gameObject.name.EndsWith(XRHandJointID.Wrist.ToString()))
root = child;
}
skeletonDriver.rootTransform = root;
XRHandSkeletonDriverUtility.FindJointsFromRoot(skeletonDriver);
skeletonDriver.InitializeFromSerializedReferences();
skeletonDriver.handTrackingEvents = handEvents;
}
m_DrawJointsParent = new GameObject();
m_DrawJointsParent.transform.parent = parent;
m_DrawJointsParent.transform.localPosition = Vector3.zero;
m_DrawJointsParent.transform.localRotation = Quaternion.identity;
m_DrawJointsParent.name = handedness + "HandDebugDrawJoints";
for (var i = 0; i < skeletonDriver.jointTransformReferences.Count; i++)
{
var jointTransformReference = skeletonDriver.jointTransformReferences[i];
var jointTransform = jointTransformReference.jointTransform;
var jointID = jointTransformReference.xrHandJointID;
AssignJoint(jointID, jointTransform, m_DrawJointsParent.transform);
}
m_HandRoot.SetActive(true);
}
public void OnDestroy()
{
Destroy(m_HandRoot);
m_HandRoot = null;
for (var jointIndex = 0; jointIndex < m_DrawJoints.Length; ++jointIndex)
{
Destroy(m_DrawJoints[jointIndex]);
m_DrawJoints[jointIndex] = null;
}
for (var jointIndex = 0; jointIndex < m_VelocityParents.Length; ++jointIndex)
{
Destroy(m_VelocityParents[jointIndex]);
m_VelocityParents[jointIndex] = null;
}
Destroy(m_DrawJointsParent);
m_DrawJointsParent = null;
}
public void ToggleDrawMesh(bool drawMesh)
{
m_MeshController.enabled = drawMesh;
if (!drawMesh)
m_MeshController.handMeshRenderer.enabled = false;
}
public void ToggleDebugDrawJoints(bool debugDrawJoints)
{
for (int jointIndex = 0; jointIndex < m_DrawJoints.Length; ++jointIndex)
{
ToggleRenderers<MeshRenderer>(debugDrawJoints, m_DrawJoints[jointIndex].transform);
m_Lines[jointIndex].enabled = debugDrawJoints;
}
m_Lines[0].enabled = false;
}
public void SetVelocityType(VelocityType velocityType)
{
for (int jointIndex = 0; jointIndex < m_VelocityParents.Length; ++jointIndex)
ToggleRenderers<LineRenderer>(velocityType != VelocityType.None, m_VelocityParents[jointIndex].transform);
}
public void UpdateJoints(
XRHand hand,
bool areJointsTracked,
bool debugDrawJoints,
VelocityType velocityType)
{
if (!areJointsTracked)
return;
var wristPose = Pose.identity;
var parentIndex = XRHandJointID.Wrist.ToIndex();
UpdateJoint(debugDrawJoints, velocityType, hand.GetJoint(XRHandJointID.Wrist), ref wristPose, ref parentIndex);
UpdateJoint(debugDrawJoints, velocityType, hand.GetJoint(XRHandJointID.Palm), ref wristPose, ref parentIndex, false);
for (var fingerIndex = (int)XRHandFingerID.Thumb;
fingerIndex <= (int)XRHandFingerID.Little;
++fingerIndex)
{
var parentPose = wristPose;
var fingerId = (XRHandFingerID)fingerIndex;
parentIndex = XRHandJointID.Wrist.ToIndex();
var jointIndexBack = fingerId.GetBackJointID().ToIndex();
for (var jointIndex = fingerId.GetFrontJointID().ToIndex();
jointIndex <= jointIndexBack;
++jointIndex)
{
UpdateJoint(debugDrawJoints, velocityType, hand.GetJoint(XRHandJointIDUtility.FromIndex(jointIndex)), ref parentPose, ref parentIndex);
}
}
}
void UpdateJoint(
bool debugDrawJoints,
VelocityType velocityType,
XRHandJoint joint,
ref Pose parentPose,
ref int parentIndex,
bool cacheParentPose = true)
{
if (joint.id == XRHandJointID.Invalid)
return;
var jointIndex = joint.id.ToIndex();
m_JointVisualizers[jointIndex].NotifyTrackingState(joint.trackingState);
if (!joint.TryGetPose(out var pose))
return;
m_DrawJoints[jointIndex].transform.localPosition = pose.position;
m_DrawJoints[jointIndex].transform.localRotation = pose.rotation;
if (debugDrawJoints && joint.id != XRHandJointID.Wrist)
{
s_LinePointsReuse[0] = m_DrawJoints[parentIndex].transform.position;
s_LinePointsReuse[1] = m_DrawJoints[jointIndex].transform.position;
m_Lines[jointIndex].SetPositions(s_LinePointsReuse);
}
if (cacheParentPose)
{
parentPose = pose;
parentIndex = jointIndex;
}
if (velocityType != VelocityType.None && m_VelocityParents[jointIndex].TryGetComponent<LineRenderer>(out var renderer))
{
m_VelocityParents[jointIndex].transform.localPosition = Vector3.zero;
m_VelocityParents[jointIndex].transform.localRotation = Quaternion.identity;
s_LinePointsReuse[0] = s_LinePointsReuse[1] = m_VelocityParents[jointIndex].transform.position;
if (velocityType == VelocityType.Linear)
{
if (joint.TryGetLinearVelocity(out var velocity))
s_LinePointsReuse[1] += velocity;
}
else if (velocityType == VelocityType.Angular)
{
if (joint.TryGetAngularVelocity(out var velocity))
s_LinePointsReuse[1] += 0.05f * velocity.normalized;
}
renderer.SetPositions(s_LinePointsReuse);
}
}
static void ToggleRenderers<TRenderer>(bool toggle, Transform rendererTransform)
where TRenderer : Renderer
{
if (rendererTransform.TryGetComponent<TRenderer>(out var renderer))
renderer.enabled = toggle;
for (var childIndex = 0; childIndex < rendererTransform.childCount; ++childIndex)
ToggleRenderers<TRenderer>(toggle, rendererTransform.GetChild(childIndex));
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2e9813c68d7d6f44282ace8bd2d1fd46
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,38 @@
using UnityEngine;
using UnityEngine.XR.Hands.Processing;
namespace UnityEngine.XR.Hands.Samples.VisualizerSample
{
public class JointVisualizer : MonoBehaviour
{
[SerializeField]
GameObject m_JointVisual;
[SerializeField]
Material m_HighFidelityJointMaterial;
[SerializeField]
Material m_LowFidelityJointMaterial;
bool m_HighFidelityJoint;
Renderer m_JointRenderer;
public void NotifyTrackingState(XRHandJointTrackingState jointTrackingState)
{
bool highFidelityJoint = (jointTrackingState & XRHandJointTrackingState.HighFidelityPose) == XRHandJointTrackingState.HighFidelityPose;
if (m_HighFidelityJoint == highFidelityJoint)
return;
m_JointRenderer.material = highFidelityJoint ? m_HighFidelityJointMaterial : m_LowFidelityJointMaterial;
m_HighFidelityJoint = highFidelityJoint;
}
void Start()
{
if (m_JointVisual.TryGetComponent<Renderer>(out var jointRenderer))
m_JointRenderer = jointRenderer;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6e1c395ff62a3a14dbeb293298bb46bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,249 @@
using UnityEngine.Rendering;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.XR.Hands.Samples.VisualizerSample
{
#if UNITY_EDITOR
[InitializeOnLoad]
static class RenderPipelineValidation
{
static RenderPipelineValidation()
{
foreach (var pipelineHandler in GetAllInstances())
pipelineHandler.AutoRefreshPipelineShaders();
}
static List<MaterialPipelineHandler> GetAllInstances()
{
var instances = new List<MaterialPipelineHandler>();
// Find all GUIDs for objects that match the type MaterialPipelineHandler
var guids = AssetDatabase.FindAssets("t:MaterialPipelineHandler");
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
var asset = AssetDatabase.LoadAssetAtPath<MaterialPipelineHandler>(path);
if (asset != null)
instances.Add(asset);
}
return instances;
}
}
#endif
/// <summary>
/// Serializable class that contains the shader information for a material.
/// </summary>
[System.Serializable]
public class ShaderContainer
{
public Material material;
public bool useSRPShaderName = true;
public string scriptableRenderPipelineShaderName = "Universal Render Pipeline/Lit";
public Shader scriptableRenderPipelineShader;
public bool useBuiltinShaderName = true;
public string builtInPipelineShaderName = "Standard";
public Shader builtInPipelineShader;
}
/// <summary>
/// Scriptable object that allows for setting the shader on a material based on the current render pipeline.
/// Will run automatically OnEnable in the editor to set the shaders on project bootup. Can be refreshed manually with editor button.
/// This exists because while objects render correctly using shadergraph shaders, others do not and using the standard shader resolves various rendering issues.
/// </summary>
[CreateAssetMenu(fileName = "MaterialPipelineHandler", menuName = "XR/MaterialPipelineHandler", order = 0)]
public class MaterialPipelineHandler : ScriptableObject
{
[SerializeField]
[Tooltip("List of materials and their associated shaders.")]
List<ShaderContainer> m_ShaderContainers;
[SerializeField]
[Tooltip("If true, the shaders will be refreshed automatically when the editor opens and when this scriptable object instance is enabled.")]
bool m_AutoRefreshShaders = true;
#if UNITY_EDITOR
void OnEnable()
{
if (Application.isPlaying)
return;
AutoRefreshPipelineShaders();
}
#endif
public void AutoRefreshPipelineShaders()
{
if (m_AutoRefreshShaders)
SetPipelineShaders();
}
/// <summary>
/// Applies the appropriate shader to the materials based on the current render pipeline.
/// </summary>
public void SetPipelineShaders()
{
if (m_ShaderContainers == null)
return;
bool isBuiltinRenderPipeline = GraphicsSettings.currentRenderPipeline == null;
foreach (var info in m_ShaderContainers)
{
if (info.material == null)
continue;
// Store original color before shader change
Color originalColor = info.material.GetColor("_Color");
if (!info.material.HasProperty("_Color"))
originalColor = info.material.GetColor("_BaseColor"); // URP uses _BaseColor
// Find the appropriate shaders based on the toggle
Shader birpShader = info.useBuiltinShaderName ? Shader.Find(info.builtInPipelineShaderName) : info.builtInPipelineShader;
Shader srpShader = info.useSRPShaderName ? Shader.Find(info.scriptableRenderPipelineShaderName) : info.scriptableRenderPipelineShader;
// Determine current shader for comparison
Shader currentShader = info.material.shader;
// Update shader for the current render pipeline only if necessary
if (isBuiltinRenderPipeline && birpShader != null && currentShader != birpShader)
{
info.material.shader = birpShader;
info.material.SetColor("_Color", originalColor);
MarkMaterialModified(info.material);
}
else if (!isBuiltinRenderPipeline && srpShader != null && currentShader != srpShader)
{
info.material.shader = srpShader;
info.material.SetColor("_BaseColor", originalColor);
MarkMaterialModified(info.material);
}
}
}
static void MarkMaterialModified(Material material)
{
#if UNITY_EDITOR
EditorUtility.SetDirty(material);
#endif
}
}
#if UNITY_EDITOR
/// <summary>
/// Custom property drawer for the shader container class.
/// </summary>
[CustomPropertyDrawer(typeof(ShaderContainer))]
public class ShaderContainerDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
float singleLineHeight = EditorGUIUtility.singleLineHeight;
float verticalSpacing = EditorGUIUtility.standardVerticalSpacing;
SerializedProperty materialProp = property.FindPropertyRelative("material");
SerializedProperty useSRPShaderNameProp = property.FindPropertyRelative("useSRPShaderName");
SerializedProperty scriptableShaderNameProp = property.FindPropertyRelative("scriptableRenderPipelineShaderName");
SerializedProperty scriptableShaderProp = property.FindPropertyRelative("scriptableRenderPipelineShader");
SerializedProperty useShaderNameProp = property.FindPropertyRelative("useBuiltinShaderName");
SerializedProperty builtInNameProp = property.FindPropertyRelative("builtInPipelineShaderName");
SerializedProperty builtInShaderProp = property.FindPropertyRelative("builtInPipelineShader");
// Draw Material without the header.
position.height = singleLineHeight;
EditorGUI.PropertyField(position, materialProp);
position.y += singleLineHeight + verticalSpacing;
// SRP Shader header and fields.
EditorGUI.LabelField(position, "Scriptable Render Pipeline Shader", EditorStyles.boldLabel);
position.y += EditorGUIUtility.singleLineHeight + verticalSpacing;
EditorGUI.PropertyField(position, useSRPShaderNameProp);
position.y += singleLineHeight + verticalSpacing;
if (useSRPShaderNameProp.boolValue)
{
EditorGUI.PropertyField(position, scriptableShaderNameProp);
position.y += singleLineHeight + verticalSpacing;
}
else
{
EditorGUI.PropertyField(position, scriptableShaderProp);
position.y += singleLineHeight + verticalSpacing;
}
// Built-in Shader header and fields.
EditorGUI.LabelField(position, "Built-In Render Pipeline Shader", EditorStyles.boldLabel);
position.y += singleLineHeight + verticalSpacing;
EditorGUI.PropertyField(position, useShaderNameProp);
position.y += singleLineHeight + verticalSpacing;
if (useShaderNameProp.boolValue)
{
EditorGUI.PropertyField(position, builtInNameProp);
position.y += singleLineHeight + verticalSpacing;
}
else
{
EditorGUI.PropertyField(position, builtInShaderProp);
position.y += singleLineHeight + verticalSpacing;
}
// Draw a separator line at the end.
position.y += verticalSpacing / 2; // Extra space for the line.
position.height = 1;
EditorGUI.DrawRect(new Rect(position.x, position.y, position.width, 1), Color.gray);
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
const int baseFieldCount = 4; // The Material field, the two toggles, and one for an optional field.
int extraLineCount = property.FindPropertyRelative("useBuiltinShaderName").boolValue ? 0 : 1;
extraLineCount += property.FindPropertyRelative("useSRPShaderName").boolValue ? 0 : 1;
float singleLineHeight = EditorGUIUtility.singleLineHeight;
float verticalSpacing = EditorGUIUtility.standardVerticalSpacing;
float headerHeight = EditorGUIUtility.singleLineHeight; // No longer need extra height for headers.
// Calculate height for fields and headers
float fieldsHeight = baseFieldCount * singleLineHeight + (baseFieldCount - 1 + extraLineCount) * verticalSpacing;
// Allow space for header, separator line, and a bit of padding before the line.
float headersHeight = 2 * (headerHeight + verticalSpacing);
float separatorSpace = verticalSpacing / 2 + 1; // Additional vertical spacing and line height.
return fieldsHeight + headersHeight + separatorSpace + singleLineHeight * 1.5f;
}
}
/// <summary>
/// Custom editor MaterialPipelineHandler
/// </summary>
[CustomEditor(typeof(MaterialPipelineHandler)), CanEditMultipleObjects]
public class MaterialPipelineHandlerEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
// Draw the "Refresh Shaders" button
if (GUILayout.Button("Refresh Shaders"))
{
foreach (var t in targets)
{
var handler = (MaterialPipelineHandler)t;
handler.SetPipelineShaders();
}
}
}
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6772a216ed6f29c42abef904c7d6940d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: