Rajawali 旋转相机有 Sensor.TYPE_ROTATION_VECTOR 奇怪的行为
Rajawali rotating camera with Sensor.TYPE_ROTATION_VECTOR strange behavior
我正在创建一个全景视图,它允许用户通过旋转他的智能手机在球形图像中环顾四周。为此,我将 Rajawali 的天空盒与 TYPE_ROTATION_VECTOR 传感器一起使用。
我让它工作了,但只有当我向前看时(它实际上是基于我的旋转(偏航))
这是行为:
- 向前看:yaw = yaw,pitch = pitch and roll = roll
- 向左看:yaw = yaw,pitch = roll and roll = pitch
- 向后看:yaw = yaw,pitch = pitch * -1 and roll = roll * -1。
现在我确实有预感发生了什么。 "camera object" 似乎一直在看同一个方向,即使它看起来没有。这意味着俯仰似乎与滚动相同,但它仍然是俯仰,因为物体没有旋转。我把它比作在飞机上环顾四周。
我需要做什么来解决这个问题?
我有一种感觉,我将不得不使用 lookAt() 旋转相机,但我不确定如何。
public class SkyboxFragment extends RajawaliFragment implements SensorEventListener {
public static final String TAG = "SkyBoxFragment";
private SensorManager mSensorManager;
private float[] orientationVals = new float[3];
private float[] mRotationMatrix = new float[16];
private Sensor mRotVectSensor;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
LinearLayout ll = new LinearLayout(getActivity());
ll.setOrientation(LinearLayout.VERTICAL);
ll.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP);
mSensorManager = (SensorManager) getActivity().getSystemService(
Context.SENSOR_SERVICE);
mRotVectSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
mLayout.addView(ll);
mSensorManager.registerListener(this,
mRotVectSensor,
10000);
return mLayout;
}
@Override
public AExampleRenderer createRenderer() {
mRenderer = new SkyboxRenderer(getActivity());
return ((SkyboxRenderer) mRenderer);
}
@Override
public void onClick(View v) {
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
SensorManager.getRotationMatrixFromVector(mRotationMatrix, event.values);
SensorManager.remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_X, SensorManager.AXIS_Z, mRotationMatrix);
SensorManager.getOrientation(mRotationMatrix, orientationVals);
orientationVals[0] = (float) Math.toDegrees(orientationVals[0]);
orientationVals[1] = (float) Math.toDegrees(orientationVals[1]) * -1;
orientationVals[2] = (float) Math.toDegrees(orientationVals[2]) * -1;
//Log.d(TAG, "YAW:" + orientationVals[0] + " PITCH:" + orientationVals[1] + "ROLL:" + orientationVals[2]);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
private final class SkyboxRenderer extends AExampleRenderer implements View.OnClickListener {
private final Vector3 mAccValues;
boolean odd = true;
public SkyboxRenderer(Context context) {
super(context);
mAccValues = new Vector3();
}
@Override
protected void initScene() {
getCurrentCamera().setFarPlane(1000);
/**
* Skybox images by Emil Persson, aka Humus. http://www.humus.name humus@comhem.se
*/
try {
getCurrentScene().setSkybox(R.drawable.posx, R.drawable.negx,
R.drawable.posy, R.drawable.negy, R.drawable.posz, R.drawable.negz);
} catch (ATexture.TextureException e) {
e.printStackTrace();
}
}
@Override
protected void onRender(long ellapsedRealtime, double deltaTime) {
super.onRender(ellapsedRealtime, deltaTime);
getCurrentCamera().setRotation(orientationVals[2], orientationVals[0], orientationVals[1]);
}
@Override
public void onClick(View v) {
try {
if (odd) {
/**
* Skybox images by Emil Persson, aka Humus. http://www.humus.name humus@comhem.se
*/
getCurrentScene().updateSkybox(R.drawable.posx2, R.drawable.negx2,
R.drawable.posy2, R.drawable.negy2, R.drawable.posz2, R.drawable.negz2);
} else {
/**
* Skybox images by Emil Persson, aka Humus. http://www.humus.name humus@comhem.se
*/
getCurrentScene().updateSkybox(R.drawable.posx, R.drawable.negx,
R.drawable.posy, R.drawable.negy, R.drawable.posz, R.drawable.negz);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
odd = !odd;
}
}
public void setAccelerometerValues(float x, float y, float z) {
mAccValues.setAll(-x, -y, -z);
}
}
}
你有两个问题。第一个是您描述的问题,但另一个问题是 TYPE_ROTATION_VECTOR
的传感器受到附近磁铁的影响,例如在 phone 案例中发现的那些。
解决磁铁问题
一种解决方案是结合使用加速度计和陀螺仪。幸运的是,Google Cardboard SDK 已经将其抽象化了。
您可以通过使用 HeadTracker.createFromContext(this.getActivity())
实例化 com.google.vrtoolkit.cardboard.sensors.HeadTracker
的实例并在其上调用 startTracking()
来跟踪当前旋转。
现在您不再需要 onSensorChanged
。相反,在您的 onRender
中,您可以这样做:
float[] R = new float[16];
headTracker.getLastHeadView(R, 0);
得到旋转矩阵。这解决了您未说明的磁场问题。
让相机正确环顾四周
使用此旋转矩阵将相机指向正确方向的最简单方法是将其转换为 org.rajawali3d.math.Quaternion
,然后调用 getCurrentCamera().setCameraOrientation(quaternion);
从 float[16]
到四元数的转换可能很难正确完成,但 Google Cardboard SDK 再一次为您完成了。在这种情况下,它位于不再使用的旧 class 的源代码中:HeadTransform.
您可以轻松地将该代码改编为 return new Quaternion(w, x, y, z);
。
现在,如果您不将 orientationVals[1]
和 orientationVals[2]
乘以 -1
,那么这将导致与当前代码相同的问题。
然而,这个问题很容易通过反转旋转矩阵来解决。这将导致 onRender
中的以下代码(假设 getQuaternion(R)
returns 和 org.rajawali3d.math.Quaternion
):
@Override
protected void onRender(long ellapsedRealtime, double deltaTime) {
super.onRender(ellapsedRealtime, deltaTime);
float[] R = new float[16];
headTracker.getLastHeadView(R, 0);
android.opengl.Matrix.invertM(R, 0, R, 0);
Quaternion q = getQuaternion(R);
getCurrentCamera().setCameraOrientation(q);
}
我正在创建一个全景视图,它允许用户通过旋转他的智能手机在球形图像中环顾四周。为此,我将 Rajawali 的天空盒与 TYPE_ROTATION_VECTOR 传感器一起使用。
我让它工作了,但只有当我向前看时(它实际上是基于我的旋转(偏航))
这是行为:
- 向前看:yaw = yaw,pitch = pitch and roll = roll
- 向左看:yaw = yaw,pitch = roll and roll = pitch
- 向后看:yaw = yaw,pitch = pitch * -1 and roll = roll * -1。
现在我确实有预感发生了什么。 "camera object" 似乎一直在看同一个方向,即使它看起来没有。这意味着俯仰似乎与滚动相同,但它仍然是俯仰,因为物体没有旋转。我把它比作在飞机上环顾四周。
我需要做什么来解决这个问题?
我有一种感觉,我将不得不使用 lookAt() 旋转相机,但我不确定如何。
public class SkyboxFragment extends RajawaliFragment implements SensorEventListener {
public static final String TAG = "SkyBoxFragment";
private SensorManager mSensorManager;
private float[] orientationVals = new float[3];
private float[] mRotationMatrix = new float[16];
private Sensor mRotVectSensor;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
LinearLayout ll = new LinearLayout(getActivity());
ll.setOrientation(LinearLayout.VERTICAL);
ll.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP);
mSensorManager = (SensorManager) getActivity().getSystemService(
Context.SENSOR_SERVICE);
mRotVectSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
mLayout.addView(ll);
mSensorManager.registerListener(this,
mRotVectSensor,
10000);
return mLayout;
}
@Override
public AExampleRenderer createRenderer() {
mRenderer = new SkyboxRenderer(getActivity());
return ((SkyboxRenderer) mRenderer);
}
@Override
public void onClick(View v) {
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
SensorManager.getRotationMatrixFromVector(mRotationMatrix, event.values);
SensorManager.remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_X, SensorManager.AXIS_Z, mRotationMatrix);
SensorManager.getOrientation(mRotationMatrix, orientationVals);
orientationVals[0] = (float) Math.toDegrees(orientationVals[0]);
orientationVals[1] = (float) Math.toDegrees(orientationVals[1]) * -1;
orientationVals[2] = (float) Math.toDegrees(orientationVals[2]) * -1;
//Log.d(TAG, "YAW:" + orientationVals[0] + " PITCH:" + orientationVals[1] + "ROLL:" + orientationVals[2]);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
private final class SkyboxRenderer extends AExampleRenderer implements View.OnClickListener {
private final Vector3 mAccValues;
boolean odd = true;
public SkyboxRenderer(Context context) {
super(context);
mAccValues = new Vector3();
}
@Override
protected void initScene() {
getCurrentCamera().setFarPlane(1000);
/**
* Skybox images by Emil Persson, aka Humus. http://www.humus.name humus@comhem.se
*/
try {
getCurrentScene().setSkybox(R.drawable.posx, R.drawable.negx,
R.drawable.posy, R.drawable.negy, R.drawable.posz, R.drawable.negz);
} catch (ATexture.TextureException e) {
e.printStackTrace();
}
}
@Override
protected void onRender(long ellapsedRealtime, double deltaTime) {
super.onRender(ellapsedRealtime, deltaTime);
getCurrentCamera().setRotation(orientationVals[2], orientationVals[0], orientationVals[1]);
}
@Override
public void onClick(View v) {
try {
if (odd) {
/**
* Skybox images by Emil Persson, aka Humus. http://www.humus.name humus@comhem.se
*/
getCurrentScene().updateSkybox(R.drawable.posx2, R.drawable.negx2,
R.drawable.posy2, R.drawable.negy2, R.drawable.posz2, R.drawable.negz2);
} else {
/**
* Skybox images by Emil Persson, aka Humus. http://www.humus.name humus@comhem.se
*/
getCurrentScene().updateSkybox(R.drawable.posx, R.drawable.negx,
R.drawable.posy, R.drawable.negy, R.drawable.posz, R.drawable.negz);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
odd = !odd;
}
}
public void setAccelerometerValues(float x, float y, float z) {
mAccValues.setAll(-x, -y, -z);
}
}
}
你有两个问题。第一个是您描述的问题,但另一个问题是 TYPE_ROTATION_VECTOR
的传感器受到附近磁铁的影响,例如在 phone 案例中发现的那些。
解决磁铁问题
一种解决方案是结合使用加速度计和陀螺仪。幸运的是,Google Cardboard SDK 已经将其抽象化了。
您可以通过使用 HeadTracker.createFromContext(this.getActivity())
实例化 com.google.vrtoolkit.cardboard.sensors.HeadTracker
的实例并在其上调用 startTracking()
来跟踪当前旋转。
现在您不再需要 onSensorChanged
。相反,在您的 onRender
中,您可以这样做:
float[] R = new float[16];
headTracker.getLastHeadView(R, 0);
得到旋转矩阵。这解决了您未说明的磁场问题。
让相机正确环顾四周
使用此旋转矩阵将相机指向正确方向的最简单方法是将其转换为 org.rajawali3d.math.Quaternion
,然后调用 getCurrentCamera().setCameraOrientation(quaternion);
从 float[16]
到四元数的转换可能很难正确完成,但 Google Cardboard SDK 再一次为您完成了。在这种情况下,它位于不再使用的旧 class 的源代码中:HeadTransform.
您可以轻松地将该代码改编为 return new Quaternion(w, x, y, z);
。
现在,如果您不将 orientationVals[1]
和 orientationVals[2]
乘以 -1
,那么这将导致与当前代码相同的问题。
然而,这个问题很容易通过反转旋转矩阵来解决。这将导致 onRender
中的以下代码(假设 getQuaternion(R)
returns 和 org.rajawali3d.math.Quaternion
):
@Override
protected void onRender(long ellapsedRealtime, double deltaTime) {
super.onRender(ellapsedRealtime, deltaTime);
float[] R = new float[16];
headTracker.getLastHeadView(R, 0);
android.opengl.Matrix.invertM(R, 0, R, 0);
Quaternion q = getQuaternion(R);
getCurrentCamera().setCameraOrientation(q);
}