How to create parallax effect using accelerometer

This is short guide how to create parallax effect using accelerometer on mobile platforms. Some code is related to Android, but all concept is applicable to iOS, etc.

What is parallax effect? There are some more complex definitions but i would define as simple as the following – you move your phone/device in space and some objects inside your application are shifting accordingly to compensate this movement. This allows to create some strong feeling of 3d interface as well as nice interaction effect.

As good example you can check out my live wallpaper (link on market), which is using this effect while rendering particle system of moving objects. More information about this application can be found here.

To create parallax effect we need to grab data from accelerometer sensor (as i found out gyroscope is not present at majority of phones while accelerometer gives enough of data to be happy with it), convert sensor data to relative rotation angles and shift some parts of application interface accordingly. 3 steps:

1. Get data from accelerometer

You can read here about usage of motion sensor inside android sdk. (I provide some code here for Android just as sample – approach should be same for iOS)

sensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);

gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);

sensorManager.registerListener(this, gravitySensor, SensorManager.SENSOR_DELAY_FASTEST);

As any measurement system sensor has noise and you can filter input data using one of signal filters such as low-pass filter – but the most simple way to do it is the following:

static double gravityFilterK = 0.8;

gravityVector[0] = gravityVector[0] * gravityFilterK + (1 – gravityFilterK) * x;

gravityVector[1] = gravityVector[1] * gravityFilterK + (1 – gravityFilterK) * y;

gravityVector[2] = gravityVector[2] * gravityFilterK + (1 – gravityFilterK) * z;

Filtering constant can depend on sensor frequency, but the tricky part is that you cant get frequency as some property and have to measure it directly. Speed can vary from device to device considerably from 7 Hz to 100Hz (source). I found that under 25Hz effect becomes less entertaining and should be disabled, unless you make prediction approximation and perform heavy smooth on input data.

2. Convert accelerometer data to rotation angles

Second task is to get device orientation angles from gravity vector. I use the following formula to get roll and pitch values (desired angles):

roll = atan2(gX, gZ) * 180/M_PI;

pitch = atan2(gY, sqrt(gX*gX + gZ*gZ)) * 180/M_PI;

Note that if device orientation was changed to landscape mode you need to swap roll and pitch values. Also atan2 function can produce 2pi jumps and we can just ignore such events. We dont need absolute angle values – only difference with previous device state (dgX, dgY):

// normalize gravity vector at first

double gSum = sqrt(gX*gX + gY*gY + gZ*gZ);

if (gSum != 0)

{

gX /= gSum;

gY /= gSum;

gZ /= gSum;

}

if (gZ != 0)

roll = atan2(gX, gZ) * 180/M_PI;

pitch = sqrt(gX*gX + gZ*gZ);

if (pitch != 0)

pitch = atan2(gY, pitch) * 180/M_PI;

dgX = (rolllastGravity[0]);

dgY = (pitchlastGravity[1]);

// if device orientation is close to vertical – rotation around x is almost undefined – skip!

if (gY > 0.99) dgX = 0;

// if rotation was too intensive – more than 180 degrees – skip it

if (dgX > 180) dgX = 0;

if (dgX < -180) dgX = 0;

if (dgY > 180) dgY = 0;

if (dgY < -180) dgY = 0;

if (!screen->isPortrait())

{

// Its landscape mode – swap dgX and dgY

double temp = dgY;

dgY = dgX;

dgX = temp;

}

lastGravity[0] = roll;

lastGravity[1] = pitch;

So we have angle shifts inside dgX, dgY variables and can proceed to final step.

3. Perform interface shift

And as final step we need to shift some elements inside application to compensate device rotation. In my case that was particle system – for each particle we perform x/y shift proportionally current particle depth (if it’s 3d environment you can also perform some rotations to make ideal compensation) .

// Parallax effect – if gravity vector was changed we shift particles

if ((dgX != 0) || (dgY != 0))

{

p->x += dgX * (1. + 10. * p->z);

p->y -= dgY * (1. + 10. * p->z);

}

Only practice can help to find optimal coefficients for such shifts which are different for every application. You can shift background details or foreground decorations.

Couple of small details: sensor events and rendering should be done in separate threads. And when device goes to sleep you should unregister sensor listener (and so dont waste battery).

Any feedback is welcome in comments.