Surface Projected Passthrough Tutorial
Updated: Jun 26, 2024
In this tutorial, you will modify the
Passthrough Basic Tutorial to project a passthrough Mesh Renderer onto a real-world surface. Here are the steps:
Note: In this tutorial, we have both the
OVRPassthroughLayer
and the new script components attached to the
OVRCameraRig
. (The
OVRPassthroughLayer
is added and configured in the Passthrough Basic Tutorial).
You should first do the
Passthrough Basic Tutorial. Once you complete that, you will be ready to do the Passthrough Window tutorial.
Also, the success of this script requires that you have already set up your room, because it requires planar surfaces upon which to set the passthrough quad. This tutorial does not invoke the Meta Quest room capture. If you have not set up your room, then in the headset, go to Settings > Physical Space > Create New Boundary.
- In the Project tab, under the Assets folder, create a new folder named Passthrough Surface Projected Tutorial, and then select it to make it the current folder for new objects.
- In the Project Hierarchy, right click SampleScene, and choose Save Scene As. Give the new scene a unique name, such as SPPScene. and save it in the new Passthrough Surface Projected Tutorial folder. The new scene becomes the active scene in the Hierarchy.
- Remove any game objects from the scene, except for the Directional Light and OVRCameraRig.
Create the SPPSurfaceController Script
To control the passthrough mesh quad, we add a surface geometry to the passthrough layer, and set up a MeshRenderer
object. Then, during Update()
, we detect when the user wants to place a quad, get its position, and place it. With this code, we only allow one quad in the scene at a time.
- On the top menu, go to Assets > Create > C# Script, and name it SPPSurfaceController.
- Double-click the new script to open it.
- Create the working objects:
private OVRPassthroughLayer passthroughLayer;
public MeshFilter projectionObject;
MeshRenderer quadOutline;
During design, we will create a mesh renderer and add it to the script’s Projection Object property.
- Include the following in the
Start()
method:
void Start()
{
...
passthroughLayer.AddSurfaceGeometry(projectionObject.gameObject, true);
// The MeshRenderer component renders the quad as a blue outline
// we only use this when Passthrough isn't visible
quadOutline = projectionObject.GetComponent<MeshRenderer>();
quadOutline.enabled = false;
...
}
- Change the
Update()
method to detect the A
button press to enable and position the quad. The code shows and move the quad when the A
button is depressed. Once the quad in in position, releasing the A
button places the quad if there is a planar surface upon which to place it.
void Update()
{
if (OVRInput.GetDown(OVRInput.Button.One))
{
passthroughLayer.RemoveSurfaceGeometry(projectionObject.gameObject);
quadOutline.enabled = true;
}
if (OVRInput.Get(OVRInput.Button.One))
{
OVRInput.Controller controllingHand = OVRInput.Controller.RTouch;
transform.position = OVRInput.GetLocalControllerPosition(controllingHand);
transform.rotation = OVRInput.GetLocalControllerRotation(controllingHand);
}
if (OVRInput.GetUp(OVRInput.Button.One))
{
passthroughLayer.AddSurfaceGeometry(projectionObject.gameObject);
quadOutline.enabled = false;
}
}
- Save the script.
Create the Surface Projected PT Plane Game Object
Create a game object for the script. We’ll set the transform of this object off a bit so we can find it easily at runtime.
- In the Hierarchy pane, expand the OVRCameraRig and select it to make it the active object.
- On the top menu, go to GameObject > Create Empty. Name the new object Surface Projected PT Plane. Select it to make it the active object in the Inspector In the Inspector, set the following starter Transform values
- Set Position to 0, 0, 1 //-.01, -.01, .9
- Set Rotation to 130, -180, -180. // 136, -171, -179
- Set Scale to 0.05, 0.05, 0.05.
Add a Mesh Object to the Surface Projected PT Plane
- Select the Surface Projected PT Plane to make it the active object in the Inspector.
- Right click the Surface Projected PT Plane game object and choose Create Empty. Name the new object SPPTMesh. Then select it to make it the active object in the Inspector.
- In the Inspector, click Add Component. In the dropdown list, select Mesh > Mesh Filter.
- For the Mesh property, select the default unity Plane object.

- Click Add Component. in the dropdown list, select Mesh > Mesh Renderer.
- In the Project pane, search for QuadOutline. Drag the material into the Passthrough Surface Projected Tutorial Assets folder.
- For the Materials property, search for and choose QuadOutline.

Assign the Script to Surface Projected PT Plane
- In the Hierarchy, pane, select the Surface Projected PT Plane game object.
- In the Inspector, go to the bottom and choose Add Component.
- Search for the SPP Surface Controller script and add it.
- For the Projection Object property, select the SPPMesh object you created.
Save, and Build the Project
- On the top menu, go to Edit > Project Settings.
- In the Meta section, examine the Project Setup Tool checklist, and Apply or Fix any issues. Then close the Project Settings.
- On the top menu, go to File > Save, and then go to File > Save Project.
- On the top menu, go to File > Build Settings to open the settings window.
- Remove the previous scenes from the Scenes in Build pane. Then click Add Open Scenes.
- Make sure that the Run Device is set to Meta Quest headset that is connected via USB cable.
- Click Build and Run.
- Save the .APK file.
When you run the app, you’ll see a window providing a small passthrough area. Press and hold the A button to take control of it.
Position it in a random location and release the A button to set the window in place.
Reference: The Full SPPSurfaceController Script
For your reference, here is the full SPPSurfaceController.cs
Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SPPSurfaceController : MonoBehaviour
{
private OVRPassthroughLayer passthroughLayer;
public MeshFilter projectionObject;
MeshRenderer quadOutline;
void Start()
{
GameObject ovrCameraRig = GameObject.Find("OVRCameraRig");
if (ovrCameraRig == null)
{
Debug.LogError("Scene does not contain an OVRCameraRig");
return;
}
passthroughLayer = ovrCameraRig.GetComponent<OVRPassthroughLayer>();
if (passthroughLayer == null)
{
Debug.LogError("OVRCameraRig does not contain an OVRPassthroughLayer component");
}
passthroughLayer.AddSurfaceGeometry(projectionObject.gameObject, true);
// The MeshRenderer component renders the quad as a blue outline
// we only use this when Passthrough isn't visible
quadOutline = projectionObject.GetComponent<MeshRenderer>();
quadOutline.enabled = false;
}
void Update()
{
// Hide object when A button is held, show it again when button is released, move it while held.
if (OVRInput.GetDown(OVRInput.Button.One))
{
passthroughLayer.RemoveSurfaceGeometry(projectionObject.gameObject);
quadOutline.enabled = true;
}
if (OVRInput.Get(OVRInput.Button.One))
{
OVRInput.Controller controllingHand = OVRInput.Controller.RTouch;
transform.position = OVRInput.GetLocalControllerPosition(controllingHand);
transform.rotation = OVRInput.GetLocalControllerRotation(controllingHand);
}
if (OVRInput.GetUp(OVRInput.Button.One))
{
passthroughLayer.AddSurfaceGeometry(projectionObject.gameObject);
quadOutline.enabled = false;
}
}
}