Algorithm
Assuming your current centre is e.g. wherever the mouse cursor currently is, for ease of understanding...
For each source pixel:
- Create
pushVector
, a vector passing through the centre and the source pixel position.
- Store its magnitude as
distanceFromCentre
, then normalise the vector.
- Apply some function used to produce
pushMagnitude
from length distanceFromCentre
. This should result in 0 for any pixel that lies on the lens' radius (distanceFromCentre
== lensRradius
), and a large amount for any pixel which is very close to the centre (distanceFromCentre
≈≈ 0
). The function used to map distanceFromCentre
-> pushMagnitude
is up to you, but I suspect a tangent function would give a spherical look.
pushVector
= normalisedVector
* pushMagnitude
.
finalPosition
= centre
+ pushVector
; plot your source pixel colour here.
Magnitude function
You need to map your input range for tan
, correctly. As you know, tan
maps to 0 at 0 degrees and infinity at 90 degrees. This refers to the edge of your circle and the centre, respectively: While a pixel exactly on the radius does not shift at all, any vector exactly at the centre of the lens could not be pushed outward in any given direction (i.e. we could not choose a pushVector
for it), hence infinity. This means that you need to remap your distanceFromCentre
which ordinarily lies in range 0->r
to a new range 0->90 degrees
.
Practicalities
A pixel / fragment shader can be used for this; sometimes it is easier to get a feel for the basic algorithm in something like Processing or Flash first, and then translate it into a shader kernel... just a thought.
Remember that you do not have to process the whole screen, only the bounding box that will contain your lens-effect radius.
** Pseudocode **
distanceFromCentre = pushVector.magnitude(); //range to 0->1 for ease of manipulation
fractionDistanceFromCentre = distanceFromCentre / lensRadius;
fractionDistanceFromRadius = 1 - fractionDistanceFromCentre; //reverse the mapping
pushMagnitude = tan(fractionDistanceFromRadius * PI / 2); //radians; or 90 degrees
pushVector.normalise();
pushVector *= pushMagnitude;
plotPosition = centre + pushVector;
plotAt(plotPosition); //draw the pixel at it's final position.