In Part 1 of this tutorial we looked at how to integrate both WRLD Unity SDK and ARKit Unity Plugin ARKit Unity Plugin in a single scene. In this part you will learn how to attach map to a surface and only show the sections within the bounds provided by ARKit.
We will be using a stencil mask to show area within a cube and we will be updating that cube with the data provided by ARKit so that it covers the visible surface. So let’s jump right in where we left off in our earlier blog post.
Step 1: Creating a cube for stencil mask
We want to create a cube that will be our mask. For that we have to make sure that the pivot of that cube is at the bottom. Unity cube however have pivot in the center but we can easily change that by making it a child of another object. The steps below show how to accomplish just that.
- Create a new GameObject and name it WRLDMapMask. Make sure its position is set to (x: 0, y: 0, z: 0).
- Create a 3D Cube and name it StencilMask. Make it child of WRLDMapMask and set its position to (x:0, y: 0.5, z: 0).
- Now if you select the WRLDMapMask GameObject in the Hierarchy you will be able to see the the cube’s pivot is at the bottom.
Step 2: Creating a parent for WRLD Map and WRLDMapMask
Now that we have both the map and its mask ready, we can put them as a child of single object that will be anchored to the surface provided by ARKit.
- Create a new GameObject and name it WRLDMapParent. Make sure its position is set to (x: 0, y: 0, z: 0).
- Drag drop both WRLD Map and WRLDMapMask on to WRLDMapParent to make them its children.
- Make sure both are positioned at (x: 0, y: 0, z: 0).
- You hierarchy should now look like the image below.
Step 3: Anchoring our WRLDMapParent and updating our WRLDMapMask according to ARKit
Time to do some coding. We will be writing a script that will position our Map onto a surface and update our mask. To do so create a new C# script and name it WRLDARKitAnchorHandler. Now copy the code provided below and paste it in the script.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.iOS;
public class WRLDARKitAnchorHandler : MonoBehaviour
{
private UnityARAnchorManager m_unityARAnchorManager;
[SerializeField]
private Transform m_wrldMapParent;
[SerializeField]
private Transform m_wrldMapMask;
void Start()
{
//Create an instance of UnityARAnchorManager so we can get details for anchors
m_unityARAnchorManager = new UnityARAnchorManager();
}
void Update()
{
//Getting a list of all anchors
List<ARPlaneAnchorGameObject> arpags = m_unityARAnchorManager.GetCurrentPlaneAnchors ();
//For the sake of this tutorial we will simply position our map on the first anchor that we find
if (arpags.Count >= 1)
{
ARPlaneAnchor arPlaneAnchor = arpags [0].planeAnchor;
//Setting the position of our map to match the position of anchor
m_wrldMapParent.position = UnityARMatrixOps.GetPosition (arPlaneAnchor.transform);
//Updating our mask according to the ARKit anchor
m_wrldMapMask.rotation = UnityARMatrixOps.GetRotation (arPlaneAnchor.transform);
m_wrldMapMask.localPosition = new Vector3(arPlaneAnchor.center.x, arPlaneAnchor.center.y, -arPlaneAnchor.center.z);
m_wrldMapMask.localScale = new Vector3(arPlaneAnchor.extent.x, m_wrldMapMask.localScale.y, arPlaneAnchor.extent.z);
}
}
}
Save the script. And back in scene create a new GameObject named ARKitAnchorHandler. Click “Add Component” and add ARKitAnchorHandler component on this object. Now drag drop both WRLDMapParent and WRLDMapMask into their respective fields. See the image below for reference.
Now when you run the application you would be able to see that our maps positions itself on the surface and the 3D Cube covers the surface area correctly.
Step 4: Adding Stencil mask shaders
In order for our mask to work, we will need to add a couple of shaders. One will be used on our mask and one will be used on the materials used by WRLD Map. Create two shaders and copy the code given below.
Create StencilVolume.shader with the following code. This shader will be applied to our cube. So where ever the cube is visible in our view, it will write on the stencil buffer. This shader doesn’t draw anything so our cube will be completely invisible.
Shader "WRLDARKit/StencilVolume"
{
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry+1"}
ColorMask 0
ZWrite on
CGINCLUDE
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target
{
return half4(0,0,0,0);
}
ENDCG
Pass
{
Stencil
{
Ref 1
Comp always
Pass replace
}
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
Create MainShader.shader with the following code. This is a basic surface shader that will be used on our map elements. It will check the stencil buffer and only draw where the value is set by the previous shader.
Shader "WRLDARKit/MainShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color ("Main Color", Color) = (1,1,1,1)
_BumpMap ("Normalmap", 2D) = "bump" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry+2" }
LOD 200
Stencil
{
Ref 1
Comp equal
Pass keep
}
CGPROGRAM
#pragma surface surf Standard
sampler2D _MainTex;
sampler2D _BumpMap;
struct Input
{
float2 uv_MainTex;
float2 uv_BumpMap;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
}
ENDCG
}
FallBack "Diffuse"
}
If you are interested in learning more about stencils then please checkout the Unity documentation that covers the subject in detail.
Step 5: Modifying the materials
Now that we have our shaders ready. We can assign them to our objects. First we will assign the stencil volume shader to our 3D Cube.
- Create a new material named StencilMaskMaterial
- Open the shader menu and assign it our Stencil Volume shader.
- Now assign that material to our StencilMask GameObject.
- Modifying the materials used by WRLD Map is very easy as well.
- Navigate to Assets/Wrld/Resources/WrldMaterials
- Select all the materials in that folder
- Now change the shader to Main Shader.
And that’s it. Now when you run that app on the device, map will only display within the bounds of detected surface.
Step 6: Update streaming
While playing around with the app you might have noticed that if the surface area is large then our mask might grow bigger than the map we have streamed, that’s because we are not updating our Streaming Camera so that it streams the map according to where we are looking. Fortunately doing so is very straight-forward.
Create a new C# script and name it WRLDARStreamingCameraHandler. Copy the following code in the script.
using UnityEngine;
public class WRLDARStreamingCameraHandler : MonoBehaviour
{
[SerializeField]
private Transform m_wrldMapTransform;
//We want to make sure that our Streaming Camera streams that same area of the map
//that is being observer by the ARKit Camera. To achieve this we will set the position and
//rotation of our Streaming Camera according to the ARKit Camera.
void Update ()
{
//Getting the main camera. In our case this will be the ARKit Camera
Transform mainCameraTransform = Camera.main.transform;
//We want our streaming camera to look where ever we are looking
transform.rotation = mainCameraTransform.rotation;
//Transforming the ARKit camera position so our Streaming Camera
//positions itself according to our WRLD Scale
transform.position = m_wrldMapTransform.InverseTransformPoint (mainCameraTransform.position);
}
}
Now select our StreamingCamera GameObject. Click Add Component and add WRLDARStreamingCameraHandler. In the WRLD Map Transform field, drag drop our WRLD Map GameObject.
Our streaming camera will now reorient constantly according to ARKit Camera and will update streaming from whichever angle of position we are looking from.
Step 7: Build and Share
That is all the steps required to make the WRLD Map attached to a surface detected by ARKit. Now you can start working on your own additions and build something awesome We love watching all the new creative experiences that people make with WRLD SDK so please do share with us @wrld3d, #madewithWRLD.
If you have any questions, please reach out to us at support@wrld3d.com as we are here to assist you.
Cheers, Ali Arslan