Smooth Mouse Filtering
Question submitted by n/a (26 June 2001)
|Return to The Archives|
|This entry is not a response to a question.|
Today we have another posting that doesn't come from a question. I've said
in the (not too distant) past that the little things can really make a big
difference in the overall feel of a "polished" product. Today, I hope to
give (and even show) you an example of one man's opinion on polishing one
very small aspect of a game.
The problem: Retrieve coarse information from the mouse and convert that to smooth reactions within the game.
The solution: Filter the mouse input in such a way as to provide smooth feedback, yet at the same time tracking the user's input as accurately as possible (i.e. with no noticable lag.)
In almost every case, filtering means averaging. However, if we simply average the mouse movement over time, we'll introduce lag. How, then, do we filter without introducing any side-effects? Well, we'll still use averaging, but we'll do it with some intelligence. And at the same time, we'll give the user fine-control over the filtering so they can adjust it themselves.
We'll use a non-linear filter of averaged mouse input over time, where the older values have less influence over the filtered result.
How it works
Every frame, whether you move the mouse or not, we put the current mouse movement into a history buffer and remove the oldest history value. So our history always contains X samples, where X is the "history buffer size", representing the most recent sampled mouse movements over time.
If we used a history buffer size of 10, and a standard average of the entire buffer, the filter would introduce a lot of lag. Fast mouse movements would lag behind 1/6th of a second on a 60FPS machine. In a fast action game, this would be very smooth, but virtually unusable. In the same scenario, a history buffer size of 2 would give us very little lag, but very poor filtering (rough and jerky player reactions.)
The non-linear filter is intended to combat this mutually-exclusive scanario. The idea is very simple. Rather than just blindly average all values in the history buffer equally, we average them with a weight. We start with a weight of 1.0. So the first value in the history buffer (the current frame's mouse input) has full weight. We then multiply this weight by a "weight modifier" (say... 0.2) and move on to the next value in the history buffer. The further back in time (through our history buffer) we go, the values have less and less weight (influence) on the final result.
To elaborate, with a weight modifier of 0.5, the current frame's sample would have 100% weight, the previous sample would have 50% weight, the next oldest sample would have 25% weight, the next would have 12.5% weight and so on. If you graph this, it looks like a curve. So the idea behind the weight modifier is to control how sharply the curve drops as the samples in the history get older.
Reducing the lag means decreasing the weight modifier. Reducing the weight modifier to 0 will provide the user with raw, unfiltered feedback. Increasing it to 1.0 will cause the result to be a simple average of all values in the history buffer.
We'll offer the user two variables for fine control: the history buffer size and the weight modifier. I tend to use a history buffer size of 10, and just play with the weight modifier until I'm happy.
There's only one way to really get the feel of something, and that is to actually feel it. So I've put together a [albiet crude] demo for you to download. It's available here (~800k, requires DX8).
In order to really test our filter, I've implemented mouse input not through DirectX, but rather by simply keeping the mouse in the center of the window and detecting how far it moves from the center, then putting it back. This form of input will give us the lowest resolution. Even still, many people find that the filter performs admirably. Hopefully you will, too.
The controls for the demo are simple. Use the left button to move forward. Hold the right-button to turn off gravity, so you can fly. Use the CTRL key to slow down, and SHIFT key to speed up. If you press "T", you'll see a walkthrough that I've recorded to show off some of the collision detection. Pressing R will overwrite the recording with your own. So if you want to give me feedback about the demo (a bug, for example) the best way is to record it.
The keys to control the filter variables are f/F to decrease/increase the history buffer size and c/C to decrease/increase the weight modifier. You'll find both of these values displayed in the lower-left of the screen, just above your video mode info.
And a final note about the demo. It is crude and has been stripped down to simply render the raw geometry every frame. So it is recommended that you run this on a system with a 3D card at least as powerful as a GeForce1 card.
Response provided by Paul Nettle
This article was originally an entry in flipCode's Ask Midnight, a Question and Answer column with Paul Nettle that's no longer active.