Skip to main content

Optics

We currently rely on a proprietary product purchased on Aliexpress. (See Limitations & Future Work)

[Link] (Note: You may want to pick a different supplier based on the availability & pricing.)

The product is called "Smart AR", while itself is actually not that "smart". The semi-transparent glass surface simply permits the reflection of light emitted by the display of a smartphone, all the while enabling the user to maintain visibility of the surroundings. There is no tracking system, no compensation for non-uniform lens distortion, or IPD adjustment in this setup.

We disassemble and integrate the lenses/optical combiners into our mechanical design. (See Assembly). This approach also eliminates the need to sacrifice a smartphone solely for the purpose of display.

Distortion

We carefully measured the lens distortion and developed distortion shader codes for correction.. (Just like how pincushion & barrel distortions work on VR headsets.). GLSL runs on Headset's Renderer, while HLSL runs on Remote Client's Unity.

Although Unity comes in with Lens Distortion effect in HDRP and URP, these solutions did not fit our needs.

distortion_wisorxr_120.fs (GLSL 1.20)
#version 120

varying vec2 fragTexCoord;
varying vec4 fragColor;

uniform sampler2D texture0;

uniform vec2 _offset;
uniform float _distortion;
uniform float _cubicDistortion;
uniform bool _isRight;
uniform float _scale;
uniform vec4 _OutOfBoundColour;

uniform vec2 leftScreenCenter;
uniform vec2 rightScreenCenter;

vec2 barrel(vec2 uv) {
vec2 h;
vec2 off = _offset;

vec2 center;
if(uv.x < 0.5) {
center = leftScreenCenter;
off.x *= 1.0;
} else {
center = rightScreenCenter;
}
h = uv - center;
h += off;

float r2 = h.x * h.x;
r2 *= 3.0;

float f = 1.0 + r2 * (_distortion + _cubicDistortion * r2);
float dec = f * center.y;
vec2 ret = vec2(f * h.x + center.x, h.y + dec);

if (ret.x < 0.0 || ret.x > 1.0 || ret.x > center.x * 2.0 || ret.x < center.x - leftScreenCenter.x || ret.y < 0.0 || ret.y > 1.0) {
return vec2(-1.0, -1.0);
}

return ret;
}

void main() {
vec2 distortedUV = barrel(fragTexCoord);

if (distortedUV.x < -1.0 && distortedUV.y < -1.0) {
gl_FragColor = _OutOfBoundColour;
} else {
vec4 distorted = texture2D(texture0, distortedUV);
gl_FragColor = distorted;
}
}

WisorLensDistortionShader.shader (HLSL)
Shader "Wisor Lens Distortion Shader" {
Properties {
_MainTex("MainTex", 2D) = "white" {}
[MaterialToggle] _isRight("isRight", Range(0, 1)) = 0.0
_distortion("distortion", Range(-3, 3)) = -0.7
_offsetX("offsetX", Range(-1, 1)) = 0.0
_offsetY("offsetY", Range(-1, 1)) = 0.0
_cubicDistortion("cubicDistortion", Range(0, 3)) = 0.4
_scale("scale", Range(0, 3)) = 1
_OutOfBoundColour ("OutOfBoundColour", Color) = (0, 0, 0, 0)
}

SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" }
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 4.0
#include "UnityCG.cginc"

float _offsetX;
float _offsetY;
float _distortion;
float _cubicDistortion;
bool _isRight;
float _scale;
float4 _OutOfBoundColour;

sampler2D _MainTex;
fixed4 _MainTex_ST;

struct v2f {
fixed4 pos : SV_POSITION;
fixed2 uv_MainTex : TEXCOORD0;
};

v2f vert(appdata_full v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv_MainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}

float2 barrel(float2 uv) {
float2 h = uv.xy - float2(0.5, 0.5);
h.x += _offsetX;
h.y += _offsetY;

float r2 = h.x * h.x; // + h.y * h.y;

if (_isRight) {
if (h.x > 0.12)
return float2(-5, -5);

if (h.x < 0)
r2 *= 3;
}

if (!_isRight) {
if (h.x < -0.12)
return float2(-5, -5);

if (h.x > 0)
r2 *= 3;
}

float f = 1.0 + r2 * (_distortion + _cubicDistortion * r2);
float dec = f * 0.5f;
float2 ret = float2(f * h.x + 0.5f, h.y + dec);

if (ret[0] < 0 || ret[0] > 1 || ret[1] < 0 || ret[1] > 1) {
return float2(-5, -5); //uv out of bound so display out of bound color
}

return ret;
}

fixed4 frag(v2f i) : COLOR
{
float2 barreled = barrel(i.uv_MainTex);

if (barreled[0] < -1 && barreled[1] < -1)
{
return _OutOfBoundColour;
}

fixed4 distorted = tex2D(_MainTex, barreled);
return distorted;
}
ENDCG
}
}
}

Active Area and Field-of-Vision (FOV)

The current casing design is unfortunately flawed due to the limited available room to properly reflect the display's image onto the user's eye. We did our best to eliminate the visual anomalies and distortions with the provided distortion shader code.

We are working on increasing the active area through a new mechanical design.

Originally, the providers state that the field-of-vision of the lenses is 90°.