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;

 

rollpitch

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.

  • Jason

    Nice post, but I have a problem when the screen orientation is set to landscape (setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)). I assume pitch is roll and vice-versa but the roll doesn’t work when the device is held upright. I think the calculations need to change but not sure how.

    If you look at the absolute pitch/roll values (not deltas) when screen orientation is landscape you’ll see what I mean.

    • http://vitiy.info Victor Laskin

      Very nice point. I think the best idea would be swap gX and gY before pitch calculation in case of landscape mode. I will try to check this thought and update the code in post. EDIT: I found that simple pitch = atan2(gY, gZ) * 180/M_PI; would be best solution (but i did not test all the cases though)

  • Manjia

    Was it that bad to copy&paste some lines of ACTUAL OCDE instead of writing this shitty unreadable php-like pseudocode? this really sucks man

    • http://vitiy.info Victor Laskin

      This is actually parts of working C++ code. Anyway it always better to think about what you copy-pasting from the web, especially when parts are small indeed.

  • Crusty

    Hi, u r talkin about accelerometer but in code u r initializing sensor type gravity. So?

    • http://vitiy.info Victor Laskin

      “Gravity type” is using accelerometer sensor data. That gives to you almost orientation vector. If device has gyroscope sensor you can switch to it as it provides more accurate angle values.

      • Crusty

        OK, thank you.

  • Ivo

    Very nice tutorial! I’ve just applied it on my Android live wallpaper and with small modifications it works perfectly.

    • http://vitiy.info Victor Laskin

      Thanks, its nice to hear.

  • Nikolas

    If gX, gY and gZ are values of gravityVector[n], so what is the lastGravity array? And when you get values from the sensor and apply filter, what are x, y and z variables?

    • http://vitiy.info Victor Laskin

      x,y,z – raw data from sensor, gravityVector[] – filtered data from sensor, lastGravity[] is saved values of pitch, roll values from last pass

  • paul

    can you please provide a port for unity? i’ll pay you

  • Netexp

    hey man
    how can i use this tutorial in windows phone 8.1 ?!

  • shalini

    What are the initial values gravity vector[0] at the time of first filtering

    • http://vitiy.info Victor Laskin

      Zero, but you can set it to current unfiltered sensor values at start (if its first run or you switch back to app from other application where sensor was paused).

  • Polaris

    Does there exist any small project on Github or similar that does this effect? I would be interested in a “Hello World” type of parallax wallpaper app to learn.

    • Victor Laskin

      There should be something. At least simple wallpaper example which can be extended. The point of post was that such simple effect is very easy to add from scratch.
      In my case i use my own special framework which i yet cant upload to github.

  • america

    Hi, I am in the step 3 and dont understand the last lines. What is the type and value of p. What means this p->x and p->z in the last lines. You have this demo in github? if you has please you could share the link to research more deep. Thanks for the post,

    • Victor Laskin

      Hello, p – stands for particle and x,y – it’s coordinates. In your case instead of p should be any objects which you want move (background image, particles, etc)

      • america

        First, thanks for the response. Reading your answer i understand what means p->x,p->y, so i do this:

        view.getLayoutParams().width += dgX * (1. + 10. * “dont know” );
        view.getLayoutParams().height -= dgY * (1. + 10. * “dont know”);

        But i dont know what value i should put in p->z because my view doesnt have z coordinate, so i put the raw Z value that i get from event.values[2]. I test my app and the image scale in a crazy way, so i think that value is no the correct, what is? and finally in the xml of the imageview, i have to put some specific scaletype? or that doesnt matter.

        Thanks for advance.

        • Victor Laskin

          z is logical depth of item. Its needed when you have several z-layers. If you have only one image background you can use just x=dgX*K, where K – intensity of effect.
          Instead of setting width and height, you should probably change some margin, because x,y are offset coordinates – not size. And so you have to shift – not scale.

  • Umran

    The accelerometer would only give you the gravity component. This means this wouldn’t work if you rotated the phone left-right (in the horizontal plane) only up-down (either away or towards gravity). You should consider perhaps including sensor input from the magnetometer to sense motion in the horizontal plane.

    • Victor Laskin

      You are describing rare ideal situation. In practice gravity vector is enough to get the impression. Additional improvement would be to add gyroscopic sensor.

      • Umran

        I would have to disagree. I think that it’s neither rare nor ideal for people to look left and right in addition to up and down when looking at parallax effects. About gyroscopes, you justify it in your blog that they aren’t available in most of the phones. You say, “…as i found out gyroscope is not present at majority of phones while accelerometer gives enough of data to be happy with it”. I find that’s a good justification not to include it. However, magnetometers/compasses are available in majority of the phones. Isn’t it possible to obtain the bearing from the compass and use that in addition to the pitch and roll you obtain from the accelerometer to include a horizontal component to the parallax effect. Please try out this live wallpaper, it has an option for gravity+compass and compare it to gravity only. (https://play.google.com/store/apps/details?id=com.doctorkettlers.autumn).

        • Victor Laskin

          I see, you are talking about slightly more ‘strong’ parallax effect. In your case i’m agree – navigating through 3D scene should expect such rotation to be handled properly. But if you take iOS8’s parallaxed wallpaper – you’ll find more ‘simple’ parallax there. ( Nice leafs btw! )

  • Виталий Гмызин