Using Unity, AR Foundations, and a wall, I’ve prototyped an AR portal that you can open up into another world. In this case that world is the void of space, but it could be any magical world beyond our own.

Funnily enough the most challenging thing about this was figuring out how to draw a rectangle on the desired plane with two touch points. The solution came through messing around with vectors for a bit.

public void UpdateHandlePosition(GameObject handle, Vector3 position)
    {
        int idx = GetHandleIndex(handle);

        //Find opposite handle:
        int lockedIdx = (idx + 2) % 4;
        GameObject locked = handles[lockedIdx];

        handle.transform.position = position;

        Vector3 diagonal = handle.transform.position - locked.transform.position;

        Vector3 normal = Vector3.Cross(referenceUp, diagonal).normalized;

        Vector3 tangent = Vector3.Cross(referenceUp, normal).normalized;

        float direction = Mathf.Sign(Vector3.Dot(normal, Camera.main.transform.forward));
       
        // Find adjacent corners
        handles[(idx - 1) % 4].transform.position = locked.transform.position + Vector3.Scale(diagonal, referenceUp);

        handles[(idx + 1) % 4].transform.position = locked.transform.position + Vector3.Scale(diagonal, tangent) * direction;

        UpdateMesh();
    }

The ‘window’ itself is a square with two opposite-facing sides, constructed manually using the handle positions

void UpdateMesh()
    {
        Vector3 a = handles[0].transform.position;
        Vector3 b = handles[1].transform.position;
        Vector3 c = handles[2].transform.position;
        Vector3 d = handles[3].transform.position;

        mesh.Clear();

        mesh.vertices = new Vector3[]{a,b,c,d};
        mesh.triangles = new int[] {
            0, 1, 3,
            1, 2, 3,
            0, 3, 1,
            1, 3, 2
        };

        mesh.RecalculateNormals();
    }

Each object in the hidden world is rendered to a stencil buffer:

...
LOD 200

Stencil {
	Ref 1
	Comp Equal
}

The window acts as a mask for the stencil buffer; revealing anything in the buffer that it intersects with:

...
LOD 200

ZWrite Off
ColorMask 0

Pass {
	Stencil {
		Ref 1
		Comp always
		Pass replace
	}
}