問題
google vr 的sdk現(xiàn)在還不能適配所有的機(jī)型。有一些特別的機(jī)型就會出現(xiàn)視圖過小的問題。如圖。視圖都縮小在了屏幕的下方,小小的一個。(github上的圖)
原因
設(shè)備沒有反饋一個正確的dpi值給sdk。沒有正確的值,sdk自然不能繪制出一個合適的視圖了。
簡練的解決方案
在unity中,找到BaseDevice腳本,有UpdateProfile()方法。里面就是定義了屏幕的各種屬性。其中,screen.width和screen.height就是屏幕的寬和高了(單位:米)。可以根據(jù)不同的屏幕做自定義。
當(dāng)然啦,這個方法里面還給很多其他的屬性賦值了,我們都可以在這里對這些屬性做操作。
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];
//重點(diǎn)在此。通過改這里的值,就可以修改視圖的大小了。注意。這里的單位是:米。
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);
}
好,簡練的解決方案到此為止,如果只是想解決問題的同學(xué)看到這里就夠了,可以直接滑到底部點(diǎn)個贊啦。下面是我解決這個問題過程中的一些經(jīng)歷和收獲。
過程和收獲
在GvrViewerMain中,可以看到有一個Unity Editor Emulation Settings。這就unity的模擬設(shè)置。
年少無知英文不好的我,看到有手機(jī)型號設(shè)置,就直接點(diǎn)進(jìn)去GvrViewer腳本中看了。
在腳本中,很輕易地發(fā)現(xiàn),是GvrProfile這個類在控制著這些手機(jī)型號的設(shè)置。繼續(xù)點(diǎn)進(jìn)去看咯。
GvrProfile
GvrProfile這個類中,定義了所有關(guān)于屏幕的信息。所有的長度單位都是米。這會決定視圖放在手機(jī)上的哪個位置和方向。
我們看下源碼,看下到底有哪些屏幕信息
/// 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.手機(jī)的寬
public float height; // The short edge of the phone.手機(jī)的高
public float border; // Distance from bottom of the phone to the bottom edge of screen.
}
ps:視圖會盡量保持正方形,所以width和height相差太大,視圖會設(shè)配數(shù)值小的那個屬性,讓視圖顯得很小。
關(guān)于透鏡在視圖中如何放置的信息。也是以米為單位的。
/// Information about the lens placement in the viewer. All distances are in meters.
[System.Serializable]
public struct Lenses {
public float separation; // Center to center.左右兩眼的視圖的中心點(diǎn)之間的距離
public float offset; // Offset of lens center from top or bottom of viewer.視圖中心點(diǎn)與底部的距離
public float screenDistance; // Distance from lens center to the phone screen.視圖邊緣到視圖中心點(diǎn)的距離。說到底就是視圖的大小
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.
}
嗯,,還有還有,關(guān)于透鏡的屬性。沒認(rèn)真看這部分的代碼,所有就不翻譯了。
/// 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編輯器中模擬運(yùn)行的時候看到效果。實(shí)際運(yùn)行的時候并不會有什么作用的。
所以我們回歸到GvrViewer。發(fā)現(xiàn)了如下代碼
用來提供數(shù)據(jù)的vr設(shè)備
// The VR device that will be providing input data.
private static BaseVRDevice device;
我們發(fā)現(xiàn)這個BaseVRDevice中也自己存放著一個GvrProfile,估計(jì)他就是正主了。
在GvrViewer腳本中,我們發(fā)現(xiàn)他是通過
device = BaseVRDevice.GetDevice();
來得到一個BaseVRDevice的實(shí)例的。GetDevice()方法具體的代碼如下。所以,想知道你的設(shè)備具體是使用哪個BaseVRDevice的實(shí)例?打個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;
}
在下的設(shè)備使用的是AndroidDevices。發(fā)現(xiàn)這個類里面,沒做屏幕適配的方法喔。找他的父類GvrDevice。bingo,找到了UpdateProfile()方法。
private void UpdateProfile() {
//profileDate是一個底層的方法。我們通過調(diào)用這個方法,得到手機(jī)屏幕的數(shù)據(jù),然后再像自己的profile賦值,根據(jù)這個值來繪制視圖。
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);
}
然后怎么適配具體的屏幕,交給大家自由發(fā)揮了。