๐ง Quest Development Fundamentals
๐ฏ Building for Meta Quest
Meta Quest (formerly Oculus Quest) is the most popular standalone VR/MR platform, with 20+ million units sold. It runs Android-based Horizon OS with OpenXR support. Development targets: Unity, Unreal, and native OpenXR. The platform supports VR, AR (passthrough), hand tracking, eye tracking (Quest Pro), and mixed reality.
Why Develop for Quest?
Largest user base: 20M+ headsets sold. No PC required: Standalone, all-in-one. Affordable: $249-$649. Feature-rich: Hand tracking, passthrough, MR, eye tracking. Store access: Reach millions via Meta Horizon Store. Cross-platform: OpenXR ensures portability. Growing market: VR/MR is booming.
// Quest development pipeline (conceptual)
class QuestDevelopment:
def __init__(self):
self.target_device = "Quest 3" # or Quest 2, Quest Pro
self.engine = "Unity" # or Unreal, Native
self.api = "OpenXR"
def setup_project(self):
# 1. Install Unity/Unreal with Android support
# 2. Install Meta XR SDK or OpenXR plugin
# 3. Configure build settings for Android
# 3. Enable XR Plugin Management
# 4. Set up XR Origin / Camera Rig
pass
def build_and_deploy(self):
# 1. Connect Quest via USB
# 2. Enable Developer Mode
# 3. Build APK
# 4. Install via ADB
# 5. Run on device
pass
def iterate(self):
# 1. Test on device
# 2. Profile performance
# 3. Optimize
# 4. Repeat
pass
# Quest specs (Quest 3)
quest_3_specs = {
'chipset': 'Snapdragon XR2 Gen 2',
'ram': '8 GB',
'storage': '128 GB / 512 GB',
'display': '2064ร2208 per eye (LCD)',
'refresh_rate': '90 Hz / 120 Hz',
'fov': '110ยฐ horizontal',
'tracking': '6-DOF inside-out',
'controllers': 'Touch Plus',
'hand_tracking': 'Yes',
'passthrough': 'Color (2 RGB cameras)',
'depth_sensor': 'Yes (Quest 3)',
'weight': '515 g'
}๐ถ๏ธ Quest Hardware & Specs
๐ฏ Know your target
Quest devices: Quest 2 (2020), Quest Pro (2022), Quest 3 (2023), Quest 3S (2024). Each has different specs โ optimize for the lowest common denominator or target specific devices.
Hardware Comparison
Quest 2: XR2 Gen 1, 6GB RAM, LCD, 1832ร1920/eye. Quest Pro: XR2+ Gen 1, 12GB RAM, pancake lenses, eye/face tracking. Quest 3: XR2 Gen 2, 8GB RAM, pancake lenses, 2064ร2208/eye, depth sensor. Quest 3S: XR2 Gen 2, 8GB RAM, Fresnel lenses (Quest 2 style), $299.
// Query device capabilities at runtime (Unity)
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.XR.OpenXR.Features.MetaQuestSupport;
public class QuestDeviceInfo : MonoBehaviour {
void Start() {
// Device name
string deviceName = XRSettings.loadedDeviceName;
Debug.Log($"Device: {deviceName}");
// Display info
Debug.Log($"Resolution: {XRSettings.eyeTextureWidth}x{XRSettings.eyeTextureHeight}");
Debug.Log($"Refresh Rate: {XRSettings.refreshRate}");
// Device model
var device = InputDevices.GetDeviceAtXRNode(XRNode.Head);
if (device.TryGetFeatureValue(CommonUsages.deviceModel, out string model)) {
Debug.Log($"Model: {model}");
}
// Check for specific features
bool hasHandTracking = CheckFeature("hand-tracking");
bool hasPassthrough = CheckFeature("passthrough");
bool hasEyeTracking = CheckFeature("eye-tracking");
bool hasFaceTracking = CheckFeature("face-tracking");
// Log device tier
DeviceTier tier = GetDeviceTier();
Debug.Log($"Device Tier: {tier}");
}
DeviceTier GetDeviceTier() {
// Quest 3 / 3S: Tier 3
// Quest Pro: Tier 2
// Quest 2: Tier 1
string model = SystemInfo.deviceModel;
if (model.Contains("Quest 3")) return DeviceTier.High;
if (model.Contains("Quest Pro")) return DeviceTier.Medium;
if (model.Contains("Quest 2")) return DeviceTier.Low;
return DeviceTier.Unknown;
}
enum DeviceTier {
Low, // Quest 2
Medium, // Quest Pro
High, // Quest 3 / 3S
Unknown
}
}
// Device-specific settings
[System.Serializable]
public class QuestQualitySettings {
public DeviceTier tier;
public int renderScale;
public int textureQuality;
public int antiAliasing;
public bool enableFoveation;
public bool enableSpaceWarp;
public int targetFrameRate;
public static QuestQualitySettings GetForTier(DeviceTier tier) {
switch (tier) {
case DeviceTier.Low:
return new QuestQualitySettings {
tier = DeviceTier.Low,
renderScale = 100,
textureQuality = 1,
antiAliasing = 2,
enableFoveation = true,
enableSpaceWarp = true,
targetFrameRate = 72
};
case DeviceTier.Medium:
return new QuestQualitySettings {
tier = DeviceTier.Medium,
renderScale = 110,
textureQuality = 2,
antiAliasing = 4,
enableFoveation = true,
enableSpaceWarp = true,
targetFrameRate = 90
};
case DeviceTier.High:
return new QuestQualitySettings {
tier = DeviceTier.High,
renderScale = 120,
textureQuality = 3,
antiAliasing = 4,
enableFoveation = true,
enableSpaceWarp = true,
targetFrameRate = 120
};
default:
return GetForTier(DeviceTier.Low);
}
}
}โ๏ธ Development Setup
๐ฏ Getting started
Quest development requires: Unity/Unreal, Android build support, Meta XR SDK or OpenXR plugin, Developer Mode on the headset, and ADB for deployment.
Setup Steps
1. Install Unity: 2021.3 LTS or newer with Android Build Support. 2. Install Meta XR SDK: Via Package Manager or Asset Store. 3. Configure Player Settings: Android, IL2CPP, ARM64, OpenGL ES 3.0. 4. Enable XR Plugin: OpenXR or Oculus. 5. Set up XR Origin. 6. Enable Developer Mode on Quest. 7. Deploy via ADB.
// Unity project setup for Quest (C# editor script)
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.Management;
using UnityEngine.XR.Management;
using UnityEngine.XR.OpenXR;
public class QuestProjectSetup : EditorWindow {
[MenuItem("Quest/Setup Project")]
static void SetupProject() {
// 1. Set build target to Android
EditorUserBuildSettings.SwitchActiveBuildTarget(
BuildTargetGroup.Android, BuildTarget.Android
);
// 2. Configure Player Settings
PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel29;
PlayerSettings.Android.targetSdkVersion = AndroidSdkVersions.AndroidApiLevel32;
PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.IL2CPP);
PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARM64;
PlayerSettings.SetGraphicsAPIs(BuildTarget.Android, new[] {
UnityEngine.Rendering.GraphicsDeviceType.OpenGLES3
});
// 3. Enable XR Plug-in Management
XRGeneralSettingsPerBuildTarget buildTargetSettings;
EditorBuildSettings.TryGetConfigObject(
XRGeneralSettings.k_SettingsKey,
out buildTargetSettings
);
if (buildTargetSettings == null) {
buildTargetSettings = ScriptableObject.CreateInstance();
EditorBuildSettings.AddConfigObject(
XRGeneralSettings.k_SettingsKey,
buildTargetSettings, true
);
}
var settings = buildTargetSettings.SettingsForBuildTarget(BuildTargetGroup.Android);
if (settings == null) {
settings = ScriptableObject.CreateInstance();
buildTargetSettings.SetSettingsForBuildTarget(BuildTargetGroup.Android, settings);
}
settings.InitManagerOnStart = true;
// 4. Enable OpenXR (or Oculus)
var openXRSettings = OpenXRSettings.GetSettingsForBuildTargetGroup(BuildTargetGroup.Android);
if (openXRSettings != null) {
// Enable Meta Quest feature
var questFeature = openXRSettings.GetFeature();
if (questFeature != null) {
questFeature.enabled = true;
}
}
// 5. Add XR Origin to scene
AddXROriginToScene();
Debug.Log("Quest project setup complete!");
}
static void AddXROriginToScene() {
// Create XR Origin GameObject
var xrOrigin = new GameObject("XR Origin");
var originComponent = xrOrigin.AddComponent();
// Add camera
var cameraGO = new GameObject("Main Camera");
cameraGO.transform.SetParent(xrOrigin.transform);
var camera = cameraGO.AddComponent();
camera.tag = "MainCamera";
camera.nearClipPlane = 0.01f;
// Add camera offset
var offsetGO = new GameObject("Camera Offset");
offsetGO.transform.SetParent(xrOrigin.transform);
cameraGO.transform.SetParent(offsetGO.transform);
// Configure XR Origin
originComponent.Camera = camera;
originComponent.CameraFloorOffsetObject = offsetGO;
}
}
#endif
// Android manifest requirements (Assets/Plugins/Android/AndroidManifest.xml)
/*
*/
// Deploy via ADB
/*
# Build APK in Unity
# Then install:
adb install -r path/to/build.apk
# Launch:
adb shell am start -n com.company.app/com.unity3d.player.UnityPlayerActivity
# View logs:
adb logcat -s Unity
# Wireless ADB (Quest 3):
adb tcpip 5555
adb connect :5555
*/ ๐ฎ Unity for Quest
๐ฏ The most popular engine for Quest
Unity is the dominant engine for Quest development. With Meta XR SDK or OpenXR, you get hand tracking, passthrough, spatial anchors, and more. Unity's XR Interaction Toolkit simplifies input and interaction.
Unity Quest Stack
XR Plugin Management: Enable OpenXR or Oculus. XR Interaction Toolkit: Grab, ray, UI interaction. Meta XR SDK: Oculus-specific features (passthrough, anchors). XR Hands: Hand tracking. AR Foundation: For MR features. Universal Render Pipeline: Optimized for mobile XR.
// Unity Quest controller setup
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
public class QuestControllerSetup : MonoBehaviour {
public XRBaseController leftController;
public XRBaseController rightController;
public XRRayInteractor leftRay;
public XRRayInteractor rightRay;
public XRDirectInteractor leftDirect;
public XRDirectInteractor rightDirect;
void Start() {
// Configure controllers for Quest
ConfigureController(leftController, XRNode.LeftHand);
ConfigureController(rightController, XRNode.RightHand);
// Add haptic feedback
leftController.SendHapticImpulse(0.1f, 0.05f);
}
void ConfigureController(XRBaseController controller, XRNode node) {
// Controller follows tracked device
var poseDriver = controller.gameObject.AddComponent();
// Set up model
var model = controller.GetComponent();
if (model != null) {
model.modelPrefab = Resources.Load($"Models/{node}Controller");
}
}
// Button mapping for Quest Touch Plus
void Update() {
// Trigger
if (rightController.TryGetFeatureValue(CommonUsages.trigger, out float trigger)) {
if (trigger > 0.5f) {
// Trigger pressed
}
}
// Grip
if (rightController.TryGetFeatureValue(CommonUsages.grip, out float grip)) {
if (grip > 0.5f) {
// Grip pressed
}
}
// Thumbstick
if (rightController.TryGetFeatureValue(CommonUsages.primary2DAxis, out Vector2 thumbstick)) {
// Thumbstick input
}
// A/B buttons (right controller)
if (rightController.TryGetFeatureValue(CommonUsages.primaryButton, out bool aButton)) {
// A button
}
if (rightController.TryGetFeatureValue(CommonUsages.secondaryButton, out bool bButton)) {
// B button
}
// X/Y buttons (left controller)
if (leftController.TryGetFeatureValue(CommonUsages.primaryButton, out bool xButton)) {
// X button
}
if (leftController.TryGetFeatureValue(CommonUsages.secondaryButton, out bool yButton)) {
// Y button
}
// Menu button
if (leftController.TryGetFeatureValue(CommonUsages.menuButton, out bool menu)) {
// Menu button
}
}
}
// Meta XR SDK: Passthrough setup
using Oculus.Interaction;
using Meta.XR;
public class QuestPassthrough : MonoBehaviour {
public OVRPassthroughLayer passthroughLayer;
public Camera xrCamera;
void Start() {
// Enable passthrough
passthroughLayer.enabled = true;
passthroughLayer.hidden = false;
// Set style
passthroughLayer.SetStyle(PassthroughStyle.Color);
// Set opacity
passthroughLayer.SetAlpha(0.8f);
// Enable camera
OVRManager.instance.enableMixedReality = true;
}
public void SetPassthroughStyle(PassthroughStyle style) {
passthroughLayer.SetStyle(style);
}
public void TogglePassthrough() {
passthroughLayer.hidden = !passthroughLayer.hidden;
}
}
// Scene understanding (Quest 3)
using Meta.XR;
public class QuestSceneUnderstanding : MonoBehaviour {
public OVRSceneManager sceneManager;
public OVRSceneAnchor[] anchors;
void Start() {
sceneManager.SceneModelLoadedSuccessfully += OnSceneLoaded;
sceneManager.LoadSceneModel();
}
void OnSceneLoaded() {
anchors = FindObjectsOfType();
foreach (var anchor in anchors) {
if (anchor.Classification == OVRSceneAnchor.OVRSceneAnchorClassification.Floor) {
// Floor detected
var volume = anchor.GetComponent();
// Use floor for placement
}
else if (anchor.Classification == OVRSceneAnchor.OVRSceneAnchorClassification.Wall) {
// Wall detected
}
}
}
} ๐ฌ Unreal for Quest
๐ฏ High-fidelity visuals on Quest
Unreal Engine offers stunning visuals for Quest, with built-in OpenXR support and Meta XR plugin. Best for high-fidelity VR experiences, though more challenging to optimize than Unity.
Unreal Quest Stack
OpenXR Plugin: Standard XR support. Meta XR Plugin: Quest-specific features. Forward Renderer: Essential for mobile XR. Mobile HDR: Enable for better lighting. Vulkan: Better performance than OpenGL. Instanced Stereo: Required for performance.
// Unreal Quest setup (C++)
// In DefaultEngine.ini:
/*
[/Script/AndroidRuntimeSettings.AndroidRuntimeSettings]
+PackageName=com.yourcompany.yourapp
+MinSDKVersion=29
+TargetSDKVersion=32
+bBuildForArm64=true
+bBuildForArmv7=false
+bBuildForX86_64=false
[/Script/Engine.RendererSettings]
r.DefaultFeature.AutoExposure=False
r.MobileHDR=True
r.Mobile.AntiAliasing=2 # MSAA
r.Mobile.ShadingPath=0 # Forward shading
r.SupportSkyAtmosphere=True
r.SupportStationarySkylight=True
[/Script/OpenXRPlugin.OpenXRPlugin]
+EnabledExtensions=XR_EXT_hand_tracking
+EnabledExtensions=XR_FB_passthrough
+EnabledExtensions=XR_FB_scene
+EnabledExtensions=XR_FB_spatial_anchor
*/
// Unreal Quest controller (Blueprints or C++)
#include "HeadMountedDisplayFunctionLibrary.h"
#include "IXRTrackingSystem.h"
#include "MotionControllerComponent.h"
// C++ setup
AMyQuestPawn::AMyQuestPawn() {
// Create VR root
VROrigin = CreateDefaultSubobject(TEXT("VROrigin"));
RootComponent = VROrigin;
// Create camera
Camera = CreateDefaultSubobject(TEXT("Camera"));
Camera->SetupAttachment(VROrigin);
// Create motion controllers
LeftController = CreateDefaultSubobject(TEXT("LeftController"));
LeftController->SetupAttachment(VROrigin);
LeftController->MotionSource = FXRMotionControllerBase::LeftHandSourceId;
RightController = CreateDefaultSubobject(TEXT("RightController"));
RightController->SetupAttachment(VROrigin);
RightController->MotionSource = FXRMotionControllerBase::RightHandSourceId;
}
// Hand tracking in Unreal (OpenXR)
void AMyQuestPawn::EnableHandTracking() {
// Enable hand tracking extension
UHeadMountedDisplayFunctionLibrary::EnableHandTracking();
// Get hand tracking data
FXRHandTrackingState HandState;
if (UHeadMountedDisplayFunctionLibrary::GetHandTrackingState(HandState)) {
// Process hand joints
for (int i = 0; i < HandState.NumJoints; i++) {
FVector JointPosition = HandState.JointPositions[i];
FQuat JointRotation = HandState.JointRotations[i];
// Use for hand mesh
}
}
}
// Passthrough in Unreal (Meta XR)
void AMyQuestPawn::EnablePassthrough() {
// Enable passthrough layer
UMetaXRFunctionLibrary::EnablePassthrough(true);
UMetaXRFunctionLibrary::SetPassthroughStyle(EPassthroughStyle::Color);
UMetaXRFunctionLibrary::SetPassthroughOpacity(0.8f);
} ๐ฑ Native Development (OpenXR)
๐ฏ Maximum performance and control
Native Quest development uses OpenXR with the Android NDK and C/C++. This gives maximum performance and access to all features, but requires more work. Best for high-performance engines and custom renderers.
Native Quest Stack
OpenXR: Standard XR API. Android NDK: C/C++ development. Vulkan: High-performance graphics. Oboe: Low-latency audio. Meta XR SDK: Native Quest features. CMake: Build system.
// Native Quest app (C++ with OpenXR)
#include
#include
#include
#include
#define LOG_TAG "QuestNative"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
class QuestNativeApp {
private:
XrInstance instance;
XrSystemId systemId;
XrSession session;
XrSpace stageSpace;
XrSwapchain swapchain;
VkDevice device;
VkQueue queue;
public:
bool Initialize(ANativeActivity* activity) {
// 1. Create OpenXR instance
XrInstanceCreateInfo instanceInfo{XR_TYPE_INSTANCE_CREATE_INFO};
strcpy(instanceInfo.applicationInfo.applicationName, "QuestNativeApp");
instanceInfo.applicationInfo.apiVersion = XR_CURRENT_API_VERSION;
// Enable extensions
const char* extensions[] = {
XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME,
XR_EXT_HAND_TRACKING_EXTENSION_NAME,
XR_FB_PASSTHROUGH_EXTENSION_NAME
};
instanceInfo.enabledExtensionCount = 3;
instanceInfo.enabledExtensionNames = extensions;
XrResult result = xrCreateInstance(&instanceInfo, &instance);
if (XR_FAILED(result)) {
LOGI("Failed to create OpenXR instance: %d", result);
return false;
}
// 2. Get system
XrSystemGetInfo systemInfo{XR_TYPE_SYSTEM_GET_INFO};
systemInfo.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
xrGetSystem(instance, &systemInfo, &systemId);
// 3. Create Vulkan device
if (!CreateVulkanDevice()) return false;
// 4. Create session
XrGraphicsBindingVulkan2KHR graphicsBinding{XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR};
graphicsBinding.instance = vkInstance;
graphicsBinding.physicalDevice = vkPhysicalDevice;
graphicsBinding.device = device;
graphicsBinding.queueFamilyIndex = queueFamilyIndex;
graphicsBinding.queueIndex = 0;
XrSessionCreateInfo sessionInfo{XR_TYPE_SESSION_CREATE_INFO};
sessionInfo.next = &graphicsBinding;
sessionInfo.systemId = systemId;
xrCreateSession(instance, &sessionInfo, &session);
// 5. Create reference space
XrReferenceSpaceCreateInfo spaceInfo{XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
spaceInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
spaceInfo.poseInReferenceSpace = identityPose;
xrCreateReferenceSpace(session, &spaceInfo, &stageSpace);
// 6. Create swapchains
CreateSwapchains();
LOGI("Quest native app initialized");
return true;
}
void RenderFrame() {
// 1. Poll events
PollEvents();
// 2. Wait for frame
XrFrameWaitInfo waitInfo{XR_TYPE_FRAME_WAIT_INFO};
XrFrameState frameState{XR_TYPE_FRAME_STATE};
xrWaitFrame(session, &waitInfo, &frameState);
// 3. Begin frame
XrFrameBeginInfo beginInfo{XR_TYPE_FRAME_BEGIN_INFO};
xrBeginFrame(session, &beginInfo);
// 4. Locate views
XrViewLocateInfo viewLocateInfo{XR_TYPE_VIEW_LOCATE_INFO};
viewLocateInfo.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
viewLocateInfo.displayTime = frameState.predictedDisplayTime;
viewLocateInfo.space = stageSpace;
XrViewState viewState{XR_TYPE_VIEW_STATE};
uint32_t viewCount = 2;
XrView views[2] = {{XR_TYPE_VIEW}, {XR_TYPE_VIEW}};
xrLocateViews(session, &viewLocateInfo, &viewState, viewCount, &viewCount, views);
// 5. Render each eye
for (uint32_t i = 0; i < viewCount; i++) {
RenderEye(views[i], i);
}
// 6. End frame
XrCompositionLayerProjectionView projectionViews[2] = {};
for (uint32_t i = 0; i < viewCount; i++) {
projectionViews[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
projectionViews[i].pose = views[i].pose;
projectionViews[i].fov = views[i].fov;
projectionViews[i].subImage.swapchain = swapchains[i];
projectionViews[i].subImage.imageRect = {{0, 0}, {width, height}};
}
XrCompositionLayerProjection projectionLayer{XR_TYPE_COMPOSITION_LAYER_PROJECTION};
projectionLayer.space = stageSpace;
projectionLayer.viewCount = viewCount;
projectionLayer.views = projectionViews;
const XrCompositionLayerBaseHeader* layers[] = {
(XrCompositionLayerBaseHeader*)&projectionLayer
};
XrFrameEndInfo endInfo{XR_TYPE_FRAME_END_INFO};
endInfo.displayTime = frameState.predictedDisplayTime;
endInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
endInfo.layerCount = 1;
endInfo.layers = layers;
xrEndFrame(session, &endInfo);
}
void Shutdown() {
xrDestroySpace(stageSpace);
xrDestroySession(session);
xrDestroyInstance(instance);
}
}; ๐ฎ Quest Input & Controllers
๐ฏ Touch Plus controllers
Quest 3 uses Touch Plus controllers with improved tracking (no rings). They have: trigger, grip, thumbstick, A/B/X/Y buttons, menu button, and haptic feedback. Quest also supports hand tracking and voice commands.
Input Methods
Touch Plus: 6-DOF tracking, haptics, buttons. Hand tracking: 26 joints per hand, pinch gestures. Voice: "Hey Meta" commands. Eye tracking: Quest Pro only. Body tracking: Estimated from head/hands. Keyboard/mouse: For productivity.
// Quest Touch Plus input (Unity)
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.XR.Interaction.Toolkit;
public class QuestTouchPlusInput : MonoBehaviour {
public XRBaseController leftController;
public XRBaseController rightController;
public ActionBasedController leftActionController;
public ActionBasedController rightActionController;
void Start() {
// Set up action-based input
SetupActions();
}
void SetupActions() {
// Trigger -> Select
// Grip -> Grab
// Thumbstick -> Move/Turn
// A/B/X/Y -> Actions
// Menu -> Pause
// Haptic feedback
leftActionController.hapticImpulseDuration = 0.1f;
leftActionController.hapticImpulseAmplitude = 0.5f;
}
// Direct input reading
void Update() {
InputDevice leftDevice = InputDevices.GetDeviceAtXRNode(XRNode.LeftHand);
InputDevice rightDevice = InputDevices.GetDeviceAtXRNode(XRNode.RightHand);
// Trigger (analog)
if (rightDevice.TryGetFeatureValue(CommonUsages.trigger, out float rightTrigger)) {
// 0.0 to 1.0
}
// Grip (analog)
if (rightDevice.TryGetFeatureValue(CommonUsages.grip, out float rightGrip)) {
// 0.0 to 1.0
}
// Thumbstick (2D)
if (rightDevice.TryGetFeatureValue(CommonUsages.primary2DAxis, out Vector2 rightThumbstick)) {
// Move: thumbstick.y
// Turn: thumbstick.x
}
// Thumbstick click
if (rightDevice.TryGetFeatureValue(CommonUsages.primary2DAxisClick, out bool rightThumbClick)) {
// Thumbstick pressed
}
// Buttons
if (rightDevice.TryGetFeatureValue(CommonUsages.primaryButton, out bool aButton)) {
// A button (right)
}
if (rightDevice.TryGetFeatureValue(CommonUsages.secondaryButton, out bool bButton)) {
// B button (right)
}
if (leftDevice.TryGetFeatureValue(CommonUsages.primaryButton, out bool xButton)) {
// X button (left)
}
if (leftDevice.TryGetFeatureValue(CommonUsages.secondaryButton, out bool yButton)) {
// Y button (left)
}
// Menu button
if (leftDevice.TryGetFeatureValue(CommonUsages.menuButton, out bool menuButton)) {
// Menu
}
// Haptic feedback
if (Input.GetKeyDown(KeyCode.Space)) {
SendHaptics(rightController, 0.5f, 0.1f);
}
}
void SendHaptics(XRBaseController controller, float amplitude, float duration) {
controller.SendHapticImpulse(amplitude, duration);
}
}
// Meta XR SDK: Advanced input
using Oculus.Interaction;
using Oculus.Interaction.Input;
public class MetaXRInput : MonoBehaviour {
public ControllerRef leftControllerRef;
public ControllerRef rightControllerRef;
public HandRef leftHandRef;
public HandRef rightHandRef;
void Start() {
// Check if controllers are connected
bool hasControllers = leftControllerRef.Controller != null;
bool hasHands = leftHandRef.Hand != null;
// Switch between controllers and hands
if (hasControllers) {
EnableControllers();
} else if (hasHands) {
EnableHandTracking();
}
}
void EnableControllers() {
leftControllerRef.gameObject.SetActive(true);
rightControllerRef.gameObject.SetActive(true);
leftHandRef.gameObject.SetActive(false);
rightHandRef.gameObject.SetActive(false);
}
void EnableHandTracking() {
leftControllerRef.gameObject.SetActive(false);
rightControllerRef.gameObject.SetActive(false);
leftHandRef.gameObject.SetActive(true);
rightHandRef.gameObject.SetActive(true);
}
}โ Hand Tracking on Quest
๐ฏ Controller-free interaction
Quest supports hand tracking with 26 joints per hand. It enables natural interaction: pinch, grab, point, and gestures. Works alongside controllers (switches automatically when you put them down).
Hand Tracking Features
26 joints: Wrist, palm, and 5 joints per finger. Pinch: Thumb + index finger. Grab: Fist gesture. Point: Index finger extended. Gestures: Thumbs up, peace, etc. Poke: Touch UI with fingertip. Auto-switch: Controllers โ hands.
// Quest hand tracking (Unity XR Hands)
using UnityEngine;
using UnityEngine.XR.Hands;
using UnityEngine.XR.Management;
public class QuestHandTracking : MonoBehaviour {
private XRHandSubsystem handSubsystem;
void Start() {
// Get hand subsystem
var subsystems = new List();
SubsystemManager.GetSubsystems(subsystems);
if (subsystems.Count > 0) {
handSubsystem = subsystems[0];
handSubsystem.updatedHands += OnHandsUpdated;
}
}
void OnHandsUpdated(XRHandSubsystem subsystem,
XRHandSubsystem.UpdateSuccessFlags updateSuccessFlags,
XRHandSubsystem.UpdateType updateType) {
// Left hand
XRHand leftHand = subsystem.leftHand;
if (leftHand.isTracked) {
ProcessHand(leftHand, "left");
}
// Right hand
XRHand rightHand = subsystem.rightHand;
if (rightHand.isTracked) {
ProcessHand(rightHand, "right");
}
}
void ProcessHand(XRHand hand, string side) {
// Get wrist pose
XRHandJoint wrist = hand.GetJoint(XRHandJointID.Wrist);
if (wrist.TryGetPose(out Pose wristPose)) {
// Use wrist position
}
// Get index fingertip
XRHandJoint indexTip = hand.GetJoint(XRHandJointID.IndexTip);
if (indexTip.TryGetPose(out Pose indexPose)) {
// Use index tip for pointing
}
// Get thumb tip
XRHandJoint thumbTip = hand.GetJoint(XRHandJointID.ThumbTip);
if (thumbTip.TryGetPose(out Pose thumbPose)) {
// Compute pinch
float pinchDistance = Vector3.Distance(indexPose.position, thumbPose.position);
bool isPinching = pinchDistance < 0.02f; // 2cm
if (isPinching) {
OnPinch(side, indexPose.position);
}
}
// Check for grab (all fingers curled)
bool isGrabbing = IsGrabbing(hand);
if (isGrabbing) {
OnGrab(side);
}
}
bool IsGrabbing(XRHand hand) {
// Check if middle, ring, pinky are curled
float middleCurl = GetFingerCurl(hand, XRHandFingerID.Middle);
float ringCurl = GetFingerCurl(hand, XRHandFingerID.Ring);
float pinkyCurl = GetFingerCurl(hand, XRHandFingerID.Little);
return middleCurl > 0.7f && ringCurl > 0.7f && pinkyCurl > 0.7f;
}
float GetFingerCurl(XRHand hand, XRHandFingerID fingerId) {
// Get fingertip and base
XRHandJoint tip = hand.GetJoint(GetTipJoint(fingerId));
XRHandJoint base = hand.GetJoint(GetBaseJoint(fingerId));
if (tip.TryGetPose(out Pose tipPose) && base.TryGetPose(out Pose basePose)) {
// Compute curl angle
Vector3 direction = tipPose.position - basePose.position;
return Vector3.Angle(basePose.up, direction) / 180f;
}
return 0f;
}
void OnPinch(string side, Vector3 position) {
// Handle pinch
Debug.Log($"Pinch with {side} hand at {position}");
}
void OnGrab(string side) {
// Handle grab
Debug.Log($"Grab with {side} hand");
}
}
// Meta XR SDK: Hand tracking with interaction
using Oculus.Interaction;
using Oculus.Interaction.HandGrab;
public class MetaHandInteraction : MonoBehaviour {
public HandGrabInteractor leftGrabInteractor;
public HandGrabInteractor rightGrabInteractor;
public PoseUseInteractor leftPoseInteractor;
public PoseUseInteractor rightPoseInteractor;
void Start() {
// Enable hand grab
leftGrabInteractor.enabled = true;
rightGrabInteractor.enabled = true;
// Enable pose use (buttons)
leftPoseInteractor.enabled = true;
rightPoseInteractor.enabled = true;
}
} ๐ท Passthrough & Mixed Reality
๐ฏ Blending real and virtual
Quest 3 and Quest Pro support color passthrough, enabling mixed reality. You can see the real world through the headset cameras and overlay virtual content. Combined with scene understanding, this enables powerful MR experiences.
Passthrough Features
Color passthrough: Full-color view of reality. Layers: Passthrough as a layer behind VR content. Style: Color, grayscale, edge. Opacity: Blend between VR and reality. Scene understanding: Detect walls, floor, furniture. Spatial anchors: Persistent content in real world. Occlusion: Virtual objects behind real ones.
// Quest passthrough setup (Meta XR SDK)
using UnityEngine;
using Meta.XR;
using Oculus.Interaction;
public class QuestMixedReality : MonoBehaviour {
public OVRPassthroughLayer passthroughLayer;
public OVRSceneManager sceneManager;
public Camera xrCamera;
void Start() {
// 1. Enable passthrough
passthroughLayer.enabled = true;
passthroughLayer.hidden = false;
passthroughLayer.SetStyle(PassthroughStyle.Color);
passthroughLayer.SetAlpha(1.0f);
// 2. Enable scene understanding
sceneManager.SceneModelLoadedSuccessfully += OnSceneLoaded;
sceneManager.LoadSceneModel();
// 3. Enable spatial anchors
EnableSpatialAnchors();
}
void OnSceneLoaded() {
// Scene model loaded โ place content on real surfaces
var anchors = FindObjectsOfType();
foreach (var anchor in anchors) {
switch (anchor.Classification) {
case OVRSceneAnchor.OVRSceneAnchorClassification.Floor:
CreateFloorMarker(anchor);
break;
case OVRSceneAnchor.OVRSceneAnchorClassification.Wall:
CreateWallMarker(anchor);
break;
case OVRSceneAnchor.OVRSceneAnchorClassification.Ceiling:
// Ceiling detected
break;
case OVRSceneAnchor.OVRSceneAnchorClassification.Table:
CreateTableContent(anchor);
break;
case OVRSceneAnchor.OVRSceneAnchorClassification.Couch:
CreateCouchContent(anchor);
break;
}
}
}
void EnableSpatialAnchors() {
// Enable spatial anchor storage
OVRAnchor.EnableSpatialAnchorStorage();
}
public void CreateSpatialAnchor(Vector3 position, Quaternion rotation) {
// Create anchor at position
var anchor = OVRAnchor.CreateSpatialAnchorAsync(
new Pose(position, rotation)
);
// Save anchor for persistence
OVRAnchor.SaveAnchorAsync(anchor);
}
// Passthrough styles
public void SetPassthroughStyle(PassthroughStyle style) {
passthroughLayer.SetStyle(style);
}
public void SetPassthroughOpacity(float opacity) {
passthroughLayer.SetAlpha(opacity);
}
}
// Scene understanding (Quest 3)
using Meta.XR;
using UnityEngine;
public class QuestSceneUnderstanding : MonoBehaviour {
public OVRSceneManager sceneManager;
public GameObject floorPrefab;
public GameObject wallPrefab;
public GameObject tablePrefab;
void Start() {
// Request scene permission
sceneManager.RequestSceneCapture();
// Load scene model
sceneManager.SceneModelLoadedSuccessfully += OnSceneLoaded;
sceneManager.LoadSceneModel();
}
void OnSceneLoaded() {
Debug.Log("Scene model loaded!");
// Get all scene anchors
var anchors = FindObjectsOfType();
foreach (var anchor in anchors) {
// Get volume (bounding box)
var volume = anchor.GetComponent();
var plane = anchor.GetComponent();
var classification = anchor.Classification;
switch (classification) {
case OVRSceneAnchor.OVRSceneAnchorClassification.Floor:
Instantiate(floorPrefab, anchor.transform.position, anchor.transform.rotation);
break;
case OVRSceneAnchor.OVRSceneAnchorClassification.Wall:
var wall = Instantiate(wallPrefab, anchor.transform.position, anchor.transform.rotation);
if (plane != null) {
wall.transform.localScale = new Vector3(plane.Width, plane.Height, 0.01f);
}
break;
case OVRSceneAnchor.OVRSceneAnchorClassification.Table:
var table = Instantiate(tablePrefab, anchor.transform.position, anchor.transform.rotation);
if (volume != null) {
table.transform.localScale = volume.Size;
}
break;
}
}
}
}
// Spatial anchors for persistent MR
using Meta.XR;
using UnityEngine;
public class SpatialAnchorManager : MonoBehaviour {
private OVRAnchor anchor;
public async void CreateAnchor(Vector3 position, Quaternion rotation) {
// Create anchor
anchor = await OVRAnchor.CreateSpatialAnchorAsync(new Pose(position, rotation));
if (anchor.IsValid) {
// Save anchor
await OVRAnchor.SaveAnchorAsync(anchor);
Debug.Log($"Anchor saved: {anchor.Uuid}");
}
}
public async void LoadAnchor(string uuid) {
// Load anchor by UUID
var anchors = await OVRAnchor.FetchAnchorsAsync(
new List(),
new OVRAnchor.FetchOptions { Uuids = new[] { Guid.Parse(uuid) } }
);
if (anchors.Count > 0) {
anchor = anchors[0];
// Place content at anchor
transform.position = anchor.Pose.position;
transform.rotation = anchor.Pose.rotation;
}
}
} โก Performance Optimization
๐ฏ Hitting 72/90/120 fps
Quest is a mobile device โ performance is critical. You must maintain the target frame rate (72/90/120 Hz) to avoid judder and motion sickness. Optimization is mandatory.
Optimization Techniques
Fixed Foveated Rendering: Lower resolution at edges. Application SpaceWarp: Synthesize frames. Single Pass Instanced: Render both eyes in one pass. LOD: Level of detail. Occlusion culling: Don't render hidden objects. GPU instancing: Batch draw calls. Texture compression: ASTC. Shaders: Mobile-optimized.
// Quest performance optimization (Unity)
using UnityEngine;
using UnityEngine.XR;
using Unity.XR.Oculus;
public class QuestPerformance : MonoBehaviour {
void Start() {
// 1. Set target frame rate
OVRManager.display.displayFrequency = 90.0f; // or 72, 120
// 2. Enable Fixed Foveated Rendering
OVRManager.foveatedRenderingLevel = OVRManager.FoveatedRenderingLevel.High;
OVRManager.useDynamicFoveatedRendering = true; // Quest Pro/3
// 3. Enable Application SpaceWarp
OVRManager.enableSpaceWarp = true;
// 4. Enable single-pass instanced rendering
// (Set in Player Settings > XR Plugin Management)
// 5. Set CPU/GPU performance levels
OVRManager.cpuLevel = 4; // 0-4 (higher = more performance)
OVRManager.gpuLevel = 4;
// 6. Enable phase sync
OVRPlugin.SetPhaseSync(true);
// 7. Set eye texture resolution
XRSettings.eyeTextureResolutionScale = 1.0f; // 1.0 = native
// 8. Configure quality settings
QualitySettings.vSyncCount = 0; // XR handles VSync
Application.targetFrameRate = 90;
}
void Update() {
// Monitor performance
float fps = 1.0f / Time.unscaledDeltaTime;
if (fps < 85) { // Below 90fps
// Reduce quality
ReduceQuality();
}
}
void ReduceQuality() {
// 1. Lower foveated rendering level (more aggressive)
OVRManager.foveatedRenderingLevel = OVRManager.FoveatedRenderingLevel.HighTop;
// 2. Lower eye texture resolution
XRSettings.eyeTextureResolutionScale = 0.9f;
// 3. Reduce LOD bias
QualitySettings.lodBias = 0.7f;
// 4. Disable expensive effects
// ...
}
}
// LOD setup for Quest
public class QuestLOD : MonoBehaviour {
void Start() {
var lodGroup = GetComponent();
// Configure LOD for Quest (lower distances)
var lods = lodGroup.GetLODs();
// LOD 0: 0-5m (full detail)
// LOD 1: 5-15m (medium)
// LOD 2: 15-30m (low)
// Culled: 30m+
for (int i = 0; i < lods.Length; i++) {
lods[i].screenRelativeTransitionHeight = 0.5f - i * 0.15f;
}
lodGroup.SetLODs(lods);
}
}
// Occlusion culling setup
public class QuestOcclusionCulling : MonoBehaviour {
void Start() {
// Bake occlusion data (Editor)
// StaticOcclusionCulling.Compute();
// Enable occlusion culling at runtime
Camera.main.useOcclusionCulling = true;
}
}
// Texture compression (ASTC)
/*
In Unity:
1. Select texture
2. Set Platform: Android
3. Set Format: ASTC 6x6 (or 8x8 for UI)
4. Set Compression: Normal Quality
*/
// Mobile shader optimization
Shader "Quest/MobileOptimized" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
_Color ("Color", Color) = (1,1,1,1)
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry" }
LOD 100
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f {
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
UNITY_VERTEX_OUTPUT_STEREO
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
v2f vert (appdata v) {
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_OUTPUT(v2f, o);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o, o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target {
fixed4 col = tex2D(_MainTex, i.uv) * _Color;
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDPROC
}
}
}
// Profiling with OVR Metrics Tool
/*
OVR Metrics Tool (installed on Quest):
- CPU/GPU utilization
- Frame rate
- Frames dropped
- Temperature
- Power consumption
- Foveation level
Access via:
adb shell am start -n com.oculus.ovrmetricstool/.MainActivity
*/ ๐ช Store Submission & Distribution
๐ฏ Getting your app to users
Distribute your Quest app via the Meta Horizon Store (formerly Oculus Store) or App Lab (early access). Requirements: passing technical review, content guidelines, and performance targets.
Distribution Options
Horizon Store: Full store, curated, discovery. App Lab: Early access, less curation. SideQuest: Sideloading, open. Direct APK: Enterprise, testing. Requirements: 72fps min, comfort, no crashes. Review: Technical + content review.
// Quest store submission checklist
public class QuestStoreSubmission {
// 1. Technical requirements
bool passesPerformance() {
// Must maintain 72fps (Quest 2) / 90fps (Quest 3)
// No dropped frames > 1%
return true;
}
bool passesComfort() {
// No forced camera movement
// Comfort options available
// No motion sickness triggers
return true;
}
bool passesStability() {
// No crashes in 30min session
// No memory leaks
// Handles pause/resume
return true;
}
// 2. Content requirements
bool passesContentGuidelines() {
// No offensive content
// Age-appropriate
// Proper warnings
return true;
}
// 3. Metadata
void prepareStoreListing() {
// App name
// Description
// Screenshots (16:9, 2560x1440)
// Trailer video
// Icon (1024x1024)
// Category
// Age rating
// Privacy policy
// Support contact
}
// 4. Build requirements
void prepareBuild() {
// Signed APK
// 64-bit ARM
// Target SDK 32+
// Min SDK 29+
// IL2CPP
// OpenGL ES 3.0 or Vulkan
}
}
// App Lab submission
/*
1. Create Meta Developer account
2. Create app in Developer Dashboard
3. Upload APK
4. Fill out metadata
5. Submit for review
6. Once approved, set release channel
*/
// Horizon Store submission
/*
1. Complete App Lab first
2. Meet quality bar
3. Apply for Horizon Store
4. Pass technical review
5. Pass content review
6. Launch!
*/
// Sideloading with SideQuest
/*
1. Enable Developer Mode on Quest
2. Connect via USB
3. Install SideQuest on PC
4. Drag APK to SideQuest
5. Launch from Unknown Sources
*/
// Enterprise distribution
/*
1. Meta Quest for Business
2. Device Management
3. Private channel distribution
4. MDM integration
*/
// Version management
[System.Serializable]
public class AppVersion {
public int major;
public int minor;
public int patch;
public string buildNumber;
public override string ToString() {
return $"{major}.{minor}.{patch} ({buildNumber})";
}
}
// Analytics for Quest
using Unity.Services.Analytics;
public class QuestAnalytics : MonoBehaviour {
void Start() {
// Initialize Unity Analytics
AnalyticsService.Instance.StartDataCollection();
// Log session start
AnalyticsService.Instance.CustomData("session_start", new Dictionary {
{ "device", SystemInfo.deviceModel },
{ "version", Application.version }
});
}
public void LogEvent(string eventName, Dictionary parameters) {
AnalyticsService.Instance.CustomData(eventName, parameters);
}
} ๐งช Testing & Debugging
๐ฏ Finding and fixing issues
Quest testing requires on-device testing (emulators are limited). Tools: ADB logcat, OVR Metrics Tool, RenderDoc, Unity Profiler, and Meta Quest Developer Hub.
Testing Tools
ADB logcat: View logs from device. OVR Metrics: Performance overlay on device. RenderDoc: GPU debugging. Unity Profiler: CPU/GPU profiling. MQDH: Meta Quest Developer Hub. Remote testing: Test without deploying.
# ADB commands for Quest testing
# View logs
adb logcat -s Unity:V
# Filter for errors
adb logcat *:E
# Clear log
adb logcat -c
# Take screenshot
adb exec-out screencap -p > screenshot.png
# Record video
adb shell screenrecord /sdcard/video.mp4
adb pull /sdcard/video.mp4
# Install APK
adb install -r build.apk
# Uninstall
adb uninstall com.company.app
# Launch app
adb shell am start -n com.company.app/com.unity3d.player.UnityPlayerActivity
# Force stop
adb shell am force-stop com.company.app
# Get device info
adb shell getprop ro.product.model
# Battery level
adb shell dumpsys battery | grep level
# Temperature
adb shell dumpsys battery | grep temperature
# Performance stats
adb shell dumpsys gfxinfo com.company.app
# Memory info
adb shell dumpsys meminfo com.company.app
// Unity Profiler for Quest
using Unity.Profiling;
public class QuestProfiler : MonoBehaviour {
ProfilerRecorder cpuRecorder;
ProfilerRecorder gpuRecorder;
ProfilerRecorder memoryRecorder;
void OnEnable() {
cpuRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Internal, "CPU Main Thread Frame Time");
gpuRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, "GPU Frame Time");
memoryRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Memory, "Total Used Memory");
}
void Update() {
if (cpuRecorder.Valid) {
float cpuTime = cpuRecorder.LastValue / 1000000f; // ms
if (cpuTime > 11.1f) { // 90fps = 11.1ms
Debug.LogWarning($"CPU time high: {cpuTime:F2}ms");
}
}
if (gpuRecorder.Valid) {
float gpuTime = gpuRecorder.LastValue / 1000000f;
if (gpuTime > 11.1f) {
Debug.LogWarning($"GPU time high: {gpuTime:F2}ms");
}
}
if (memoryRecorder.Valid) {
long memory = memoryRecorder.LastValue;
if (memory > 3_000_000_000) { // 3GB
Debug.LogWarning($"Memory high: {memory / 1_000_000}MB");
}
}
}
void OnDisable() {
cpuRecorder.Dispose();
gpuRecorder.Dispose();
memoryRecorder.Dispose();
}
}
// Remote testing with Quest Developer Hub
/*
1. Install Meta Quest Developer Hub (MQDH)
2. Connect Quest via USB
3. Enable Developer Mode
4. Use MQDH for:
- App installation
- Log viewing
- Performance monitoring
- Screenshots/video
- Device management
*/
// Automated testing
public class QuestAutomatedTest : MonoBehaviour {
void Start() {
StartCoroutine(RunTests());
}
IEnumerator RunTests() {
// Test 1: Frame rate
yield return TestFrameRate();
// Test 2: Memory
yield return TestMemory();
// Test 3: Input
yield return TestInput();
// Test 4: Tracking
yield return TestTracking();
Debug.Log("All tests complete");
}
IEnumerator TestFrameRate() {
float duration = 10f;
float elapsed = 0;
int frames = 0;
while (elapsed < duration) {
elapsed += Time.deltaTime;
frames++;
yield return null;
}
float avgFps = frames / duration;
Debug.Log($"Average FPS: {avgFps:F1}");
if (avgFps < 85) {
Debug.LogError("Frame rate below target!");
}
}
}โ ๏ธ Challenges & Best Practices
๐ฏ Avoiding common pitfalls
Quest development has unique challenges: limited performance, motion sickness, controller/hand switching, and store requirements. Following best practices ensures a smooth launch.
Common Pitfalls
Performance: Not optimizing for mobile GPU. Comfort: Forced camera movement, no comfort options. Input: Ignoring hand tracking, poor controller mapping. Battery: High power consumption. Heat: Thermal throttling. Store: Missing requirements. Testing: Not testing on actual device.
// Best practices for Quest development
// 1. Performance
public class PerformanceBestPractices {
void Start() {
// Use URP or lightweight render pipeline
// Enable Fixed Foveated Rendering
// Use Single Pass Instanced
// Enable Application SpaceWarp
// Use LOD groups
// Bake lighting
// Use occlusion culling
// Compress textures (ASTC)
// Use mobile shaders
// Batch draw calls
// Limit real-time lights
// Use GPU instancing
// Profile regularly
}
}
// 2. Comfort
public class ComfortBestPractices {
void Start() {
// Never move camera without user input
// Provide comfort options:
// - Teleport vs smooth locomotion
// - Snap turn vs smooth turn
// - Vignette during movement
// - Seated mode
// Maintain stable frame rate
// Keep horizon level
// Avoid acceleration/deceleration
// Provide rest reminders
}
}
// 3. Input
public class InputBestPractices {
void Start() {
// Support both controllers and hands
// Auto-switch when controllers put down
// Provide clear affordances
// Use haptic feedback
// Map buttons consistently
// Support both left and right handed
// Provide input remapping
}
}
// 4. Battery & Heat
public class BatteryBestPractices {
void Start() {
// Lower CPU/GPU levels when possible
// Reduce display brightness
// Pause rendering when not visible
// Use 72Hz mode for less demanding apps
// Avoid continuous high GPU usage
// Monitor temperature
// Implement thermal throttling handling
}
void OnApplicationPause(bool pause) {
if (pause) {
// Reduce rendering when paused
Application.targetFrameRate = 30;
} else {
Application.targetFrameRate = 90;
}
}
}
// 5. Store requirements
public class StoreBestPractices {
void Start() {
// Maintain 72fps minimum
// No crashes
// Handle pause/resume
// Support recentering
// Provide comfort options
// Include age rating
// Privacy policy
// Support contact
// Regular updates
}
}
// 6. Testing
public class TestingBestPractices {
void Start() {
// Test on actual Quest device
// Test all supported devices
// Test for 30+ minutes
// Test with different users
// Monitor performance
// Test edge cases
// Test pause/resume
// Test controller disconnect
// Test hand tracking
}
}
// 7. UX
public class UXBestPractices {
void Start() {
// Large, legible text
// High contrast UI
// Comfortable interaction distances
// Clear feedback for all actions
// Minimize text input
// Use spatial audio
// Provide onboarding
// Support accessibility
}
}๐ Future: Quest 4 & Beyond
๐ฏ What's next for Quest
The future of Quest: higher resolution, better passthrough, eye tracking, face tracking, brain-computer interfaces, and AI integration. Meta is investing billions in XR.
Future Quest Features
Quest 4: Higher res, better optics, more power. Neural interfaces: EMG wristbands for input. Photorealistic avatars: Codec avatars. AI assistant: Context-aware help. Cloud rendering: Offload to cloud. Neural rendering: NeRF/Gaussian Splatting. Smart glasses: Ray-Ban Meta evolution.
// Future Quest development (conceptual)
public class FutureQuestDevelopment {
// 1. Neural input (EMG wristband)
void ProcessNeuralInput() {
// Read muscle signals from wristband
float[] emgSignals = NeuralInput.GetEMGSignals();
// Classify gestures
Gesture gesture = NeuralClassifier.Classify(emgSignals);
// Map to actions
switch (gesture) {
case Gesture.Pinch:
OnPinch();
break;
case Gesture.Point:
OnPoint();
break;
case Gesture.Swipe:
OnSwipe();
break;
}
}
// 2. Photorealistic avatars
void RenderCodecAvatar() {
// Load avatar model
CodecAvatar avatar = CodecAvatar.Load("user_avatar");
// Drive with face/eye tracking
avatar.SetFacePose(FaceTracking.GetPose());
avatar.SetEyeGaze(EyeTracking.GetGaze());
// Render
avatar.Render();
}
// 3. AI assistant
void AIAssistant() {
// Context-aware help
string context = GetUserContext();
string query = GetUserQuery();
string response = LLM.Generate(query, context);
// Display in AR
ShowInAR(response);
}
// 4. Cloud rendering
void CloudRendering() {
// Send camera pose to cloud
Pose pose = GetCameraPose();
CloudRenderer.SendPose(pose);
// Receive rendered frame
Texture2D frame = CloudRenderer.ReceiveFrame();
// Display
DisplayFrame(frame);
}
// 5. Neural rendering (Gaussian Splatting)
void NeuralRendering() {
// Load Gaussian Splatting scene
GaussianSplatting scene = GaussianSplatting.Load("scene.splat");
// Render from user's viewpoint
RenderTexture output = scene.Render(GetCameraPose());
// Display
Display(output);
}
}
// Ray-Ban Meta smart glasses development
public class RayBanMetaDevelopment {
void Start() {
// Camera access
// Audio (open-ear speakers)
// Touch controls
// Voice commands
// AI assistant (Meta AI)
// Live streaming
// Photo/video capture
}
}
// Meta Horizon OS
public class HorizonOS {
void Start() {
// Multi-app support
// Spatial anchors
// Scene understanding
// Passthrough
// Hand tracking
// Eye tracking (future)
// Face tracking (future)
}
}
// WebXR on Quest
public class WebXRQuest {
void Start() {
// Browser-based VR/AR
// No app installation
// Cross-platform
// Three.js, Babylon.js, A-Frame
// WebXR Hand Input
// WebXR Passthrough
}
}