
.
but this is not the case with the MEMS gyroscope. In general, MEMS gyroscopes are the cheapest and most inaccurate of all those that exist; at rest, they have a strong zero drift. When integrating these angular velocities skipping around zero into orientation angles, the error begins to accumulate, resulting in a so-called gyro drift, which is well known to many fans to play VR toys. To reduce the zero drift, special signal filters and threshold values of angular velocities are used, but this is not a panacea, because firstly, this is a reason for so-called deterioration. VR experience (picture inertia and jerks appear), and secondly, it will not be possible to completely eradicate the drift. In this case, the other two sensors of the smartphone come to the rescue, with their help you can almost completely eliminate drift, while maintaining the VR experience.
, the unit of measurement is most often m / s, but for us this is also not critical. At rest, the accelerometer provides the direction of the gravity vector, we can use this feature to stabilize the horizon (Tilt correction). The accelerometer also has flaws. If the gyroscope is noisy mainly at rest, then the accelerometer on the contrary lays more in motion, so the combination of data from these two sensors must be approached wisely. In various INS for quadcopters, the Kalman filter is used, but I believe that in the case of VR, you can do with the usual complement, here and so there is something to load the processor of the smartphone.
, values are usually in microtesla (μT).
- the required orientation quaternion, before the start of the cycle we assign it an initial value
.
and set it as:
- time elapsed from the previous iteration of the cycle;
:
.

: 


where
- the smoothing factor, the smaller it is - the smoother and longer the horizon will stabilize, the optimal value in most cases is 0.1.



where
- the same smoothing factor as
above
package com.sinuxvr.sample; import com.badlogic.gdx.math.Quaternion; /** - */ interface VRSensorManager { /** */ boolean isGyroAvailable(); /** */ boolean isMagAvailable(); /** */ void startTracking(); /** */ void endTracking(); /** - * @param use - true - , false - */ void useDriftCorrection(boolean use); /** * @return */ Quaternion getHeadQuaternion(); } package com.sinuxvr.sample; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; /** Android. * . * : , + , + , * + + */ class VRSensorManagerAndroid implements VRSensorManager { /** */ private enum VRControlMode { ACC_ONLY, ACC_GYRO, ACC_MAG, ACC_GYRO_MAG } private SensorManager sensorManager; // private SensorEventListener accelerometerListener; // private SensorEventListener gyroscopeListener; // private SensorEventListener compassListener; // private Context context; // /** */ private final float[] accelerometerValues = new float[3]; // private final float[] gyroscopeValues = new float[3]; // private final float[] magneticFieldValues = new float[3]; // private final boolean gyroAvailable; // private final boolean magAvailable; // private volatile boolean useDC; // /** , headOrientation */ private final Quaternion gyroQuaternion; private final Quaternion deltaQuaternion; private final Vector3 accInVector; private final Vector3 accInVectorTilt; private final Vector3 magInVector; private final Quaternion headQuaternion; private VRControlMode vrControlMode; /** */ VRSensorManagerAndroid(Context context) { this.context = context; // sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); // ( 100%, ) magAvailable = (sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null); gyroAvailable = (sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null); useDC = false; // vrControlMode = VRControlMode.ACC_ONLY; if (gyroAvailable) vrControlMode = VRControlMode.ACC_GYRO; if (magAvailable) vrControlMode = VRControlMode.ACC_MAG; if (gyroAvailable && magAvailable) vrControlMode = VRControlMode.ACC_GYRO_MAG; // gyroQuaternion = new Quaternion(0, 0, 0, 1); deltaQuaternion = new Quaternion(0, 0, 0, 1); accInVector = new Vector3(0, 10, 0); accInVectorTilt = new Vector3(0, 0, 0); magInVector = new Vector3(1, 0, 0); headQuaternion = new Quaternion(0, 0, 0, 1); // startTracking(); } /** */ @Override public boolean isGyroAvailable() { return gyroAvailable; } /** */ @Override public boolean isMagAvailable() { return magAvailable; } /** - */ @Override public void startTracking() { // sensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); Sensor accelerometer = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0); accelerometerListener = new SensorListener(this.accelerometerValues, this.magneticFieldValues, this.gyroscopeValues); sensorManager.registerListener(accelerometerListener, accelerometer, SensorManager.SENSOR_DELAY_GAME); // if (magAvailable) { sensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); Sensor compass = sensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD).get(0); compassListener = new SensorListener(this.accelerometerValues, this.magneticFieldValues, this.gyroscopeValues); sensorManager.registerListener(compassListener, compass, SensorManager.SENSOR_DELAY_GAME); } // if (gyroAvailable) { sensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); Sensor gyroscope = sensorManager.getSensorList(Sensor.TYPE_GYROSCOPE).get(0); gyroscopeListener = new SensorListener(this.gyroscopeValues, this.magneticFieldValues, this.gyroscopeValues); sensorManager.registerListener(gyroscopeListener, gyroscope, SensorManager.SENSOR_DELAY_GAME); } } /** - */ @Override public void endTracking() { if (sensorManager != null) { if (accelerometerListener != null) { sensorManager.unregisterListener(accelerometerListener); accelerometerListener = null; } if (gyroscopeListener != null) { sensorManager.unregisterListener(gyroscopeListener); gyroscopeListener = null; } if (compassListener != null) { sensorManager.unregisterListener(compassListener); compassListener = null; } sensorManager = null; } } /** - */ @Override public void useDriftCorrection(boolean useDC) { // , this.useDC = useDC; } /** */ @Override public synchronized Quaternion getHeadQuaternion() { // switch (vrControlMode) { // case ACC_ONLY: updateAccData(0.1f); // Yaw ( ) headQuaternion.setFromAxisRad(0, 1, 0, -MathUtils.sin(accelerometerValues[0] / 200f)).mul(gyroQuaternion).nor(); gyroQuaternion.set(headQuaternion); break; // + ( , // , ) case ACC_MAG: updateAccData(0.2f); if (!useDC) { headQuaternion.setFromAxisRad(0, 1, 0, -MathUtils.sin(accelerometerValues[0] / 200f)).mul(gyroQuaternion).nor(); gyroQuaternion.set(headQuaternion); } else updateMagData(1f, 0.05f); break; // + case ACC_GYRO: updateGyroData(0.1f); updateAccData(0.02f); break; // - must have, case ACC_GYRO_MAG: float dQLen = updateGyroData(0.1f); updateAccData(0.02f); if (useDC) updateMagData(dQLen, 0.005f); } return headQuaternion; } /** * * @param driftThreshold - * @return - deltaQuaternion */ private synchronized float updateGyroData(float driftThreshold) { float wX = gyroscopeValues[0]; float wY = gyroscopeValues[1]; float wZ = gyroscopeValues[2]; // float l = Vector3.len(wX, wY, wZ); float dtl2 = Gdx.graphics.getDeltaTime() * l * 0.5f; if (l > driftThreshold) { float sinVal = MathUtils.sin(dtl2) / l; deltaQuaternion.set(sinVal * wX, sinVal * wY, sinVal * wZ, MathUtils.cos(dtl2)); } else deltaQuaternion.set(0, 0, 0, 1); gyroQuaternion.mul(deltaQuaternion); return l; } /** Tilt * @param filterAlpha - */ private synchronized void updateAccData(float filterAlpha) { // accInVector.set(accelerometerValues[0], accelerometerValues[1], accelerometerValues[2]); gyroQuaternion.transform(accInVector); accInVector.nor(); // accInVector UP(0, 1, 0) float xzLen = 1f / Vector2.len(accInVector.x, accInVector.z); accInVectorTilt.set(-accInVector.z * xzLen, 0, accInVector.x * xzLen); // accInVector UP(0, 1, 0) float fi = (float)Math.acos(accInVector.y); // Tilt- headQuaternion.setFromAxisRad(accInVectorTilt, filterAlpha * fi).mul(gyroQuaternion).nor(); gyroQuaternion.set(headQuaternion); } /** Yaw * @param dQLen - deltaQuaternion * @param filterAlpha - * */ private synchronized void updateMagData(float dQLen, float filterAlpha) { // deltaQuaternion if (dQLen < 0.1f) return; // magInVector.set(magneticFieldValues[0], magneticFieldValues[1], magneticFieldValues[2]); gyroQuaternion.transform(magInVector); // Yaw float theta = MathUtils.atan2(magInVector.z, magInVector.x); // headQuaternion.setFromAxisRad(0, 1, 0, filterAlpha * theta).mul(gyroQuaternion).nor(); gyroQuaternion.set(headQuaternion); } /** ( AndroidInput) */ private class SensorListener implements SensorEventListener { final float[] accelerometerValues; final float[] magneticFieldValues; final float[] gyroscopeValues; SensorListener (float[] accelerometerValues, float[] magneticFieldValues, float[] gyroscopeValues) { this.accelerometerValues = accelerometerValues; this.magneticFieldValues = magneticFieldValues; this.gyroscopeValues = gyroscopeValues; } // ( ) @Override public void onAccuracyChanged (Sensor arg0, int arg1) { } // @Override public synchronized void onSensorChanged (SensorEvent event) { if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { accelerometerValues[0] = -event.values[1]; accelerometerValues[1] = event.values[0]; accelerometerValues[2] = event.values[2]; } if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { magneticFieldValues[0] = -event.values[1]; magneticFieldValues[1] = event.values[0]; magneticFieldValues[2] = event.values[2]; } if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) { gyroscopeValues[0] = -event.values[1]; gyroscopeValues[1] = event.values[0]; gyroscopeValues[2] = event.values[2]; } } } } private enum VRControlMode { ACC_ONLY, ACC_GYRO, ACC_MAG, ACC_GYRO_MAG } package com.sinuxvr.sample; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.PerspectiveCamera; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector3; /** VR * VRSensorManager update() */ class VRCamera { private PerspectiveCamera leftCam; // private PerspectiveCamera rightCam; // private Vector3 position; // VR private float parallax; // private Vector3 direction; // VR private Vector3 up; // UP VR private Vector3 upDirCross; // up direction ( 2, ) /** */ VRCamera(float fov, float parallax, float near, float far) { this.parallax = parallax; leftCam = new PerspectiveCamera(fov, Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight()); leftCam.near = near; leftCam.far = far; leftCam.update(); rightCam = new PerspectiveCamera(fov, Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight()); rightCam.near = near; rightCam.far = far; rightCam.update(); position = new Vector3(0, 0, 0); direction = new Vector3(0, 0, 1); up = new Vector3(0, 1, 0); upDirCross = new Vector3().set(direction).crs(up).nor(); } /** */ void update() { Quaternion headQuaternion = GdxVR.vrSensorManager.getHeadQuaternion(); // - // direction.set(0, 0, 1); headQuaternion.transform(direction); up.set(0, 1, 0); headQuaternion.transform(up); upDirCross.set(direction); upDirCross.crs(up).nor(); // float angle = 2 * (float)Math.acos(headQuaternion.w); float s = 1f / (float)Math.sqrt(1 - headQuaternion.w * headQuaternion.w); float vx = headQuaternion.x * s; float vy = headQuaternion.y * s; float vz = headQuaternion.z * s; // leftCam.view.idt(); // leftCam.view.translate(parallax, 0, 0); // + parallax X leftCam.view.rotateRad(vx, vy, vz, -angle); // leftCam.view.translate(-position.x, -position.y, -position.z); // position leftCam.combined.set(leftCam.projection); Matrix4.mul(leftCam.combined.val, leftCam.view.val); // rightCam.view.idt(); // rightCam.view.translate(-parallax, 0, 0); // + parallax X rightCam.view.rotateRad(vx, vy, vz, -angle); // rightCam.view.translate(-position.x, -position.y, -position.z); // position rightCam.combined.set(rightCam.projection); Matrix4.mul(rightCam.combined.val, rightCam.view.val); } /** */ void setPosition(float x, float y, float z) { position.set(x, y, z); } /** */ PerspectiveCamera getLeftCam() { return leftCam; } /** */ PerspectiveCamera getRightCam() { return rightCam; } /** , UP , */ public Vector3 getPosition() { return position; } public Vector3 getDirection() { return direction; } public Vector3 getUp() { return up; } public Vector3 getUpDirCross() { return upDirCross; } } VRCamera(float fov, float parallax, float near, float far) @Override protected void onCreate (Bundle savedInstanceState) { super.onCreate(savedInstanceState); AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); config.useWakelock = true; config.useAccelerometer = false; config.useGyroscope = false; config.useCompass = false; vrSensorManagerAndroid = new VRSensorManagerAndroid(this.getContext()); initialize(new GdxVR(vrSensorManagerAndroid), config); } @Override public void onPause() { vrSensorManagerAndroid.endTracking(); super.onPause(); } @Override public void onResume() { super.onResume(); vrSensorManagerAndroid.startTracking(); } package com.sinuxvr.sample; import android.os.Bundle; import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; public class AndroidLauncher extends AndroidApplication { private VRSensorManagerAndroid vrSensorManagerAndroid; // /** */ @Override protected void onCreate (Bundle savedInstanceState) { super.onCreate(savedInstanceState); AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); // libgdx config.useWakelock = true; config.useAccelerometer = false; config.useGyroscope = false; config.useCompass = false; config.numSamples = 2; // ( useAccelerometer .. ) vrSensorManagerAndroid = new VRSensorManagerAndroid(this.getContext()); initialize(new GdxVR(vrSensorManagerAndroid), config); } /** - */ @Override public void onPause() { vrSensorManagerAndroid.endTracking(); super.onPause(); } /** - */ @Override public void onResume() { super.onResume(); vrSensorManagerAndroid.startTracking(); } } static VRSensorManager vrSensorManager; GdxVR(VRSensorManager vrSensorManager) { GdxVR.vrSensorManager = vrSensorManager; } package com.sinuxvr.sample; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.assets.AssetManager; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.g3d.Model; import com.badlogic.gdx.graphics.g3d.ModelBatch; import com.badlogic.gdx.graphics.g3d.ModelInstance; /** , , */ class GdxVR extends ApplicationAdapter { static VRSensorManager vrSensorManager; // private int scrHeight, scrHalfWidth; // viewport private AssetManager assets; // private ModelBatch modelBatch; // private ModelInstance roomInstance; // private VRCamera vrCamera; // VR /** */ GdxVR(VRSensorManager vrSensorManager) { GdxVR.vrSensorManager = vrSensorManager; } /** */ @Override public void create () { // scrHalfWidth = Gdx.graphics.getWidth() / 2; scrHeight = Gdx.graphics.getHeight(); // modelBatch = new ModelBatch(); assets = new AssetManager(); assets.load("room.g3db", Model.class); assets.finishLoading(); Model roomModel = assets.get("room.g3db"); roomInstance = new ModelInstance(roomModel); // (fov, parallax, near, far) vrCamera = new VRCamera(90, 0.4f, 0.1f, 30f); vrCamera.setPosition(-1.7f, 3f, 3f); // vrSensorManager.useDriftCorrection(true); } /** viewport- */ @Override public void render () { // Gdx.gl.glClearColor(0f, 0f, 0f, 1f); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); // vrCamera.update(); // Gdx.gl.glViewport(0, 0, scrHalfWidth, scrHeight); modelBatch.begin(vrCamera.getLeftCam()); modelBatch.render(roomInstance); modelBatch.end(); // Gdx.gl.glViewport(scrHalfWidth, 0, scrHalfWidth, scrHeight); modelBatch.begin(vrCamera.getRightCam()); modelBatch.render(roomInstance); modelBatch.end(); } /** */ @Override public void dispose () { modelBatch.dispose(); assets.dispose(); } } vrCamera = new VRCamera(90, 0.4f, 0.1f, 30f); vrCamera.setPosition(-1.7f, 3f, 3f); vrSensorManager.useDriftCorrection(true); vrCamera.update(); Gdx.gl.glViewport(0, 0, scrHalfWidth, scrHeight); modelBatch.begin(vrCamera.getLeftCam()); modelBatch.render(roomInstance); modelBatch.end(); Gdx.gl.glViewport(scrHalfWidth, 0, scrHalfWidth, scrHeight); modelBatch.begin(vrCamera.getRightCam()); modelBatch.render(roomInstance); modelBatch.end(); 
Source: https://habr.com/ru/post/318278/
All Articles