# 3D Distortion Effect

I'm trying to make a distortion effect similar to this: However I'm not making a 2d screen space effect. I'm applying it to a 3d scene and I want it to be placed IN the world, so it can be blocked by other 3D objects. I know the general method for doing this is as a post-process effect applied to a 3D sphere, however I can't find any code/equations that work for that case. All the ones I've found assume you're given a 2d point and a radius.

The 2d point is easy to calculate from a 3d point but getting the radius of a 3d sphere projected into 2d space... seems extremely complicated. How can I achieve this kind of outwards-push effect in a 3d space?

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

fractionDistanceFromRadius = 1 - fractionDistanceFromCentre; //reverse the mapping

pushVector.normalise();
pushVector *= pushMagnitude;
plotPosition = centre + pushVector;
plotAt(plotPosition); //draw the pixel at it's final position.

• Hmm, the finalPosition = center + pushVector idea is a lot better than what I was previously toying around with but I'm still having an issue getting a good pushMagnitude value. I'm using the dot product of the normal and the vector to the camera, but it's producing values that very slowly increase from the center. The center 10-15% of the sphere ends up creating a kind of pinching effect. I can't seem to find an equation that fixes that. May 26, 2013 at 2:21
• @Telanor See addition. May 26, 2013 at 11:18
• Well tan is still giving me a pinch effect in the center. Also, there's 1 more issue. Having the pushMagnitude range from 0 to 1 results in sampling values off screen. I would need to divide it by the radius on screen but I don't see any way of getting that value. May 27, 2013 at 1:54

If I'm not wrong, this can be done using shaders.

The actual effect your showing in the example is a 2D effect, so can you achieve the exact same effect in a 3D world? Maybe, but again, it's a effect done after rendering.

In order to actually block it by another object, this would require a more advanced algorithm. I suggest learning how to do this effect in the game using shaders but not worrying about overlapping the effect. Then look into Ray Casting and use a similar algorithm to detect whether to apply the effect to each pixel. Either way, the effect will be slow.