问题
google vr 的sdk现在还不能适配所有的机型。有一些特别的机型就会出现视图过小的问题。如图。视图都缩小在了屏幕的下方,小小的一个。(github上的图)
原因
设备没有反馈一个正确的dpi值给sdk。没有正确的值,sdk自然不能绘制出一个合适的视图了。
简练的解决方案
在unity中,找到BaseDevice脚本,有UpdateProfile()方法。里面就是定义了屏幕的各种属性。其中,screen.width和screen.height就是屏幕的宽和高了(单位:米)。可以根据不同的屏幕做自定义。
当然啦,这个方法里面还给很多其他的属性赋值了,我们都可以在这里对这些属性做操作。
private void UpdateProfile()
{
GetProfile(profileData);
GvrProfile.Viewer device = new GvrProfile.Viewer();
GvrProfile.Screen screen = new GvrProfile.Screen();
device.maxFOV.outer = profileData[0];
device.maxFOV.upper = profileData[1];
device.maxFOV.inner = profileData[2];
device.maxFOV.lower = profileData[3];
//重点在此。通过改这里的值,就可以修改视图的大小了。注意。这里的单位是:米。
screen.width = 0.133f;
screen.height = 0.074f;
screen.border = profileData[6];
device.lenses.separation = profileData[7];
device.lenses.offset = profileData[8];
device.lenses.screenDistance = profileData[9];
device.lenses.alignment = (int)profileData[10];
device.distortion.Coef = new [] { profileData[11], profileData[12] };
Profile.screen = screen;
Profile.viewer = device;
float[] rect = new float[4];
Profile.GetLeftEyeNoLensTanAngles(rect);
float maxRadius = GvrProfile.GetMaxRadius(rect);
Profile.viewer.inverse = GvrProfile.ApproximateInverse(
Profile.viewer.distortion, maxRadius);
}
好,简练的解决方案到此为止,如果只是想解决问题的同学看到这里就够了,可以直接滑到底部点个赞啦。下面是我解决这个问题过程中的一些经历和收获。
过程和收获
在GvrViewerMain中,可以看到有一个Unity Editor Emulation Settings。这就unity的模拟设置。
年少无知英文不好的我,看到有手机型号设置,就直接点进去GvrViewer脚本中看了。
在脚本中,很轻易地发现,是GvrProfile这个类在控制着这些手机型号的设置。继续点进去看咯。
GvrProfile
GvrProfile这个类中,定义了所有关于屏幕的信息。所有的长度单位都是米。这会决定视图放在手机上的哪个位置和方向。
我们看下源码,看下到底有哪些屏幕信息
/// Information about the screen. All distances are in meters, measured relative to how
/// the phone is expected to be seated in the viewer, i.e. landscape orientation.
[System.Serializable]
public struct Screen {
public float width; // The long edge of the phone.手机的宽
public float height; // The short edge of the phone.手机的高
public float border; // Distance from bottom of the phone to the bottom edge of screen.
}
ps:视图会尽量保持正方形,所以width和height相差太大,视图会设配数值小的那个属性,让视图显得很小。
关于透镜在视图中如何放置的信息。也是以米为单位的。
/// Information about the lens placement in the viewer. All distances are in meters.
[System.Serializable]
public struct Lenses {
public float separation; // Center to center.左右两眼的视图的中心点之间的距离
public float offset; // Offset of lens center from top or bottom of viewer.视图中心点与底部的距离
public float screenDistance; // Distance from lens center to the phone screen.视图边缘到视图中心点的距离。说到底就是视图的大小
public int alignment; // Determines whether lenses are placed relative to top, bottom or
// center. It is actually a signum (-1, 0, +1) relating the scale of
// the offset's coordinates to the device coordinates.
//视图的align。。靠中,靠上,靠下咯,就是下面定义的三个变量。
public const int AlignTop = -1; // Offset is measured down from top of device.
public const int AlignCenter = 0; // Center alignment ignores offset, hence scale is zero.
public const int AlignBottom = 1; // Offset is measured up from bottom of device.
}
嗯,,还有还有,关于透镜的属性。没认真看这部分的代码,所有就不翻译了。
/// Information about the viewing angles through the lenses. All angles in degrees, measured
/// away from the optical axis, i.e. angles are all positive. It is assumed that left and right
/// eye FOVs are mirror images, so that both have the same inner and outer angles. Angles do not
/// need to account for the limits due to screen size.
[System.Serializable]
public struct MaxFOV {
public float outer; // Towards the side of the screen.
public float inner; // Towards the center line of the screen.
public float upper; // Towards the top of the screen.
public float lower; // Towards the bottom of the screen.
}
/// Information on how the lens distorts light rays. Also used for the (approximate) inverse
/// distortion. Assumes a radially symmetric pincushion/barrel distortion model.
[System.Serializable]
public struct Distortion {
private float[] coef;
public float[] Coef {
get {
return coef;
}
set {
if (value != null) {
coef = (float[])value.Clone();
} else {
coef = null;
}
}
}
public float distort(float r) {
float r2 = r * r;
float ret = 0;
for (int j=coef.Length-1; j>=0; j--) {
ret = r2 * (ret + coef[j]);
}
return (ret + 1) * r;
}
public float distortInv(float radius) {
// Secant method.
float r0 = 0;
float r1 = 1;
float dr0 = radius - distort(r0);
while (Mathf.Abs(r1 - r0) > 0.0001f) {
float dr1 = radius - distort(r1);
float r2 = r1 - dr1 * ((r1 - r0) / (dr1 - dr0));
r0 = r1;
r1 = r2;
dr0 = dr1;
}
return r1;
}
}
看完这里,大家就会觉得好面熟喔,是的,这些属性就是我们在解决方案中,在androiddevice中修改的属性。不过,在直接在这里改的话,就这可以在unity编辑器中模拟运行的时候看到效果。实际运行的时候并不会有什么作用的。
所以我们回归到GvrViewer。发现了如下代码
用来提供数据的vr设备
// The VR device that will be providing input data.
private static BaseVRDevice device;
我们发现这个BaseVRDevice中也自己存放着一个GvrProfile,估计他就是正主了。
在GvrViewer脚本中,我们发现他是通过
device = BaseVRDevice.GetDevice();
来得到一个BaseVRDevice的实例的。GetDevice()方法具体的代码如下。所以,想知道你的设备具体是使用哪个BaseVRDevice的实例?打个log看看呗。
public static BaseVRDevice GetDevice() {
if (device == null) {
#if UNITY_EDITOR
device = new EditorDevice();
#elif ANDROID_DEVICE
#if UNITY_HAS_GOOGLEVR
device = new UnityVRDevice();
#else
device = new AndroidDevice();
#endif // UNITY_HAS_GOOGLEVR
#elif IPHONE_DEVICE
device = new iOSDevice();
#else
throw new InvalidOperationException("Unsupported device.");
#endif // UNITY_EDITOR
}
return device;
}
在下的设备使用的是AndroidDevices。发现这个类里面,没做屏幕适配的方法喔。找他的父类GvrDevice。bingo,找到了UpdateProfile()方法。
private void UpdateProfile() {
//profileDate是一个底层的方法。我们通过调用这个方法,得到手机屏幕的数据,然后再像自己的profile赋值,根据这个值来绘制视图。
GetProfile(profileData);
GvrProfile.Viewer device = new GvrProfile.Viewer();
GvrProfile.Screen screen = new GvrProfile.Screen();
device.maxFOV.outer = profileData[0];
device.maxFOV.upper = profileData[1];
device.maxFOV.inner = profileData[2];
device.maxFOV.lower = profileData[3];
screen.width = profileData[4];
screen.height = profileData[5];
screen.border = profileData[6];
device.lenses.separation = profileData[7];
device.lenses.offset = profileData[8];
device.lenses.screenDistance = profileData[9];
device.lenses.alignment = (int)profileData[10];
device.distortion.Coef = new [] { profileData[11], profileData[12] };
Profile.screen = screen;
Profile.viewer = device;
float[] rect = new float[4];
Profile.GetLeftEyeNoLensTanAngles(rect);
float maxRadius = GvrProfile.GetMaxRadius(rect);
Profile.viewer.inverse = GvrProfile.ApproximateInverse(
Profile.viewer.distortion, maxRadius);
}
然后怎么适配具体的屏幕,交给大家自由发挥了。