Category: screen-space

Screen-Space Effects Using Polar Coordinates Linear Transformations

Screen-Space Effects Using Polar Coordinates Linear Transformations

Motivation

Like most graphics engineers I’m familiar with linear transformations applied in texture-space and their usage. I was always impressed by how easy it is to achieve different intuitive effects by simply applying matrix multiplications. 

After implementing a swirl / twist effect that uses an underlying connection between texture-space radius and angle, I was intrigued to investigate the math behind it and look at other effects that could be achieved by applying a simple matrix transformation – this time in polar coordinate space.

This article explores common linear transformations applied in texture polar coordinate space. I will present different possible artistic effects that use these transformations and provide a shadertoy sandbox that allows the reader to experiment further with polar coordinate transformations and their effects.

While these techniques might not have a practical use in a production environment, I thought it would still be valuable to share my findings with the community. I hope you find my experiments interesting and inspiring and would appreciate it if you share with me applications that were not presented in this article.

Notation and Basic Math

The 2D point p can be expressed directly using the cartesian coordinates (x,y) or the polar coordinates (r,θ) as follows:

p = vec2(x,y) = r·vec2(cosθ,sin θ)

The relationship between cartesian and polar coordinates representation is given by:

r(x,y) = sqrt(x2+y2)

θ(x,y) = atan2(y,x)

and:

x(r,θ) = r·cosθ

y(r, θ) = r·sinθ

The fact that atan2 function has a discontinuity along the negative x axis will cause visual artifacts if not properly handled. For that reason, most of the simple angle transformations will not work “out of the box” and will require additional thinking.

In texture space, we usually consider normalized values pnorm-tex (between 0 and 1) where the behavior for values outside of this range depends on the texture sampling / wrapping mode (e.g. clamp, repeat, border, etc.). In the examples below, we will be using ptex in the [-1,1] segment instead, as it will place the origin of the coordinate system at the center of the image by default. In addition, for maximum flexibility, we will allow an arbitrary origin by translating the coordinates.

Putting it all together, we get the following equations:

ptex = 2·pnorm-tex – vec2(1,1) – porigin

pnorm-tex = 0.5·(ptex + vec2(1,1) + porigin)

Finally, when a transformation is needed to be applied to only a part of the image, I will be mixing the original image with the transformed one using a coefficient named α (alpha). This effectively controls the area in which transformation is being applied and unless otherwise specified, it is assumed to be 1 (i.e. apply transform for the whole image).

Linear Transformations

Linear transformations are quite common in graphics programming because they can be easily expressed in matrix form and implemented efficiently on GPU hardware. 

When using a screen-space cartesian coordinate system, several common linear transformations can be used to achieve different effects. Namely: translation, scale, rotation, and shear

The next section will survey these transformations in matrix form and briefly discuss their effect when applied in the cartesian coordinate system. We then focus our attention to examine the effect when applied in the polar coordinate system.

The careful reader may observe that translation is not a linear transform (the origin is not necessarily mapped to the origin). We will use the 2D homogeneous coordinates notation for translations so we can express affine transforms using matrices (i.e. translation matrix dimensions would be 3×3).

Translation

Tough translation is a non-linear transformation (the origin isn’t mapped to itself). It can be however expressed in matrix notation by using 2D homogeneous coordinates. Thus:

In the cartesian system, one use of translations (also sometimes referred to as bias) is to animate a seamless texture by “sliding” it by a given offset (tx,ty) that varies over time (with a repeating sampling mode).

Radius Translation (Pinch and Explode)

Consider the polar coordinates equation after applying a radius bias:

(r+tr)·vec2(cosθ,sinθ )

Pinch

When tr > 0, the radius “contracts” towards the origin. The contraction is most pronounced closest to the origin and since the image borders are also contracting towards the origin – special care should be applied to deal with image orders. The overall effect looks like the image was “pinched” with a distortion towards the image borders (Figure 1a).

Figure 1a: Radius Translation (tr=0.1 and vec2(0.7,0.2) origin, border sampling mode)

We can somewhat avoid the border distortion by setting the α coefficient to use the pinched image around the origin and gradually mix with the original image as distance from origin grows. Unfortunately this idea won’t avoid the distortion completely when the origin is too close to the image border (Figure 1b). Here’s the shadertoy implementation (note, the visible border distortion as the origin moves closer to the image border and pinch intensity gets stronger). Crafting images that have at least 1 pixel border and setting a clamp sampling mode can further reduce distortion.

Figure 1b: Radius Translation (tr=0.1 and vec2(0.7,0.2) origin, PINCH_POWER=0.2, clamp sampling mode)

Explode and Raindrops

When tr < 0, the radius “expands” from the origin. Note that radius can also get negative values in this case. If r > 0, the pixels are being “pushed” away from the origin. When r < 0, the image is essentially flipped. The overall effect looks like a “flipped-image bubble” expanding from the origin (Figure 1c).

Figure 1c: Radius Translation (tr=-0.1 and vec2(0.7,0.2) origin, border sampling mode) 

One usage of explosion effect is to simulate screen-space fake raindrops. Consider multiple small animated explosions in different locations (origins), with α coefficient that decays really quickly and peaks only at the raindrop locations. The result would be blended small explosions that look like (depends on the parameters) fake raindrops (Figure 1d). This shadertoy code demonstrates the idea.

Figure 1d: Raindrops effect (15 animated drops, repeat sampling mode)

Angle Translation (Rotation)

It’s easy to see from the polar coordinate definition that angle translation is equivalent to image rotation around the origin point. 

r·vec2(cos(θ+tθ),sin( θ +tθ))

Positive bias is equivalent to clockwise rotation, while negative bias is equivalent to counterclockwise rotation (Figure 2). 

See the following shadertoy example. Note that just like the last example, when using cartesian coordinates (by setting IS_USING_POLAR_COORD to 0), we can observe, as expected, a sliding animation on the vertical axis (similarly, when dealing with radius translation and setting IS_USING_POLAR_COORDS to 0, we will observe a horizontal sliding movement).

Figure 2: Angle Translation (Clockwise rotation of 45 degrees around (0,0) rotation axis, border sampling mode)

Scale

The traditional scaling matrix is just a diagonal matrix:

In the cartesian system, we use scale for stretching (i.e. shrinking or expanding) the cartesian axes. This technique is useful, for instance, when tiling a pattern by using a seamless periodic texture with a repeating sampling mode. Another usage is to apply the same texture a few times to fill a larger area with a repeating pattern.

Radius Scale (Zoom)

Scaling the radius in polar coordinates is similar to the cartesian case, when using isometric axes scale factor (meaning: x,y are scaled exactly by the same factor sr = sx = sy = s).

r(sx,sy) = sqrt((sx)2+(sy)2) = |s|·sqrt(x2+y2) = |s|·r(x,y) 

The image is flipped if a negative scaling factor is being used, s < 0. Additionally, the image will be zoomed in if s < 1 and zoomed out if s > 1 around the coordinate system origin point.

To achieve zoom-in effect, one can choose any monotonic decreasing function f(t) between 0 and 1 (e.g. e-at, 1/(1+at) with a>0). Conversely, to get a zoom-out effect, choose an increasing function in the same range (e.g. 1-e-at, at/(1+at) with a>0).

Here’s a shadertoy code that demonstrates the polar coordinates zoom-in and-out effects. 

Note that if cartesian coordinates are being used (by setting IS_USING_POLAR_COORD to 0), we get a “stretching” effect on the horizontal axis around the origin point (ZOOM_POINT), as expected.

Figure 3a: Original image (Radius Scale 1.0, zoom point vec2(-0.25,0.5), any sampling mode)

Figure 3b: Zoomed-in image (Radius Scale 0.2, zoom point vec2(-0.25,0.5), any sampling mode)

Angle Scale (Starburst and Rings)

When scaling the angle, we are essentially shrinking / expanding the image according to the angle and around the origin:

r·vec2(cos(sθ·θ),sin(sθ·θ))

Fake Starburst

If s < 0, the image is being mirrored according to the angle around the origin.

When |s| > 1, the replication of the image looks like a “fan” where the “fins” themselves are a stretched version of the lines between the origin to the image corners (Figure 4a). Furthermore, the positive x axis is a “compressed” version of the image, while the negative side has a discontinuity. These artifacts can be somewhat alleviated when a large number of fins is being used along with a repeating texture sampling mode, that is, sθ should be large. Caution though, setting a very big sθ will lead to aliasing (Figure 4b). Experimentation shows that a good number is somewhere between 50 and 200 (Figure 4c).

Figure 4a: Angle Scale (sθ=10, origin=vec2(-0.6,-0.2), border sampling mode)

Figure 4b: Angle Scale (sθ=1000, origin=vec2(-0.6,-0.2), border sampling mode)

Figure 4c: Angle Scale (sθ=70, origin=vec2(-0.6,-0.2), repeat sampling mode)

We can use this pattern to create an interesting starburst effect centered around the origin (Figure 4d). Set the α coefficient to use the original image around the origin and change it gradually to use the above pattern as distance from origin grows. See this shadertoy example for more information.

Figure 4d: Starburst Effect (sθ=160, origin=vec2(-0.6,-0.2), repeat sampling mode)

Concentric Rings

When |s| < 1, the image compresses along concentric rings around the transformation origin with visible negative x axis discontinuity (Figure 4e, Figure 4f). The discontinuity disappears when both ends of the ring are identical, meaning an “infinite” amount of compression, or sθ = 0.

When sθ = 0, the color of each ring is being determined by positive x axis (Figure 4g):

r·vec2(cos0,sin0)=r·vec2(1,0)

We can combine this method with an angle translation tθ to color the rings according to a generic ray that shoots from the origin. This will add a somewhat continuous ring color animation due to neighbouring pixel similarity (except object edges).

r·vec2(cos(tθ),sin(tθ))

Please refer to the shadertoy demo to see the animation.

Figure 4e: Rings Effect (sθ=0.4, origin=vec2(-0.6,-0.2), repeat sampling mode)

Figure 4f: Rings Effect (sθ=0.1, origin=vec2(-0.6,-0.2), repeat sampling mode)

Figure 4g: Rings Effect (sθ=0.0, origin=vec2(-0.6,-0.2), repeat sampling mode)

A potential ring image usage could be for pseudo lens flare generation. Note that we can save the expansive atan2 computation since we don’t really use θ for ring generation.

Rotation

A 2D rotation matrix is a unitary matrix that can be written in the following form for both coordinate systems:

In the cartesian system, we use this transformation to counterclockwise rotate the image by an angle of Φ.

While rotation in cartesian coordinates makes a lot of sense and this transformation is quite common, beyond math, I could not find a good use for rotating polar coordinates (Figure 5).

Here’s the shadertoy prototype I used for experimentation. Please let me know if you have any idea how polar coordinate rotation can be used for screen-space effects creation.

Figure 5: Polar Coordinates Rotation (30 degrees around (0,0) origin, border sampling mode)

Shear

Shearing is a very interesting linear operation, it essentially displaces points using a proportional amount to the other axis. 2D shearing can be written as:

For example, in the cartesian coordinate system, when my = 0 we get vec2(x+mx·y, y). So the shear strength depends on y absolute value, when the direction depends on its sign (i.e. -mx will mirror the effect).

Radius Shear

Let’s shear only the radius (i.e. mθ = 0) to get:

In other words:

(r+mr·θ)·vec2(cosθ,sinθ)

Recall the analysis we made for radius translation. By applying similar arguments we conclude that the overall effect is a pinch / explode, depending on mr·θ sign. Since θ∈[-π,π], we actually get a part of the image with a pinch effect (above the x axis that intersects the transform origin) and the other part with an explode effect. Note that the negative x axis has a discontinuity (Figure 6a).

Figure 6a: Radius Shear (mr=0.12 with origin vec2(0.4, 0.4), border sampling mode)

Here’s the shadertoy demo I was using to produce the image. I couldn’t find a practical effect for this transformation and I would appreciate it if you share with me any interesting thoughts / artistic usage for this transformation. Note that if cartesian coordinates are being used (by setting IS_USING_POLAR_COORD to 0), we get, as expected, a horizontal shearing around the origin point, when the sign of mx dictates the shearing direction.

Angle Shear (Spiral)

Now let’s consider an angle shear (i.e. mr = 0) to get:

In other words:

r·vec2(cos(mθ·r+θ),sin(mθ·r+θ))

Recall the analysis we made earlier for angle translation. We’re essentially rotating each concentric ring by a different rotation amount that depends on the rings’ radii. Since the rotation addition is linear (and therefore monotonic and continuous) – we’re actually creating a continuous spiral around the transformation origin (Figure 6b)!

Since, by definition, r > 0, we see that angle shearing direction is dictated solely by the sign of mθ. Meaning, mθ sign determines the spiral orientation (clockwise or counterclockwise for positive and negative values respectively).

Figure 6b: Spirals (mθ=15 with origin vec2(0.0, 0.0), border sampling mode)

Now, obviously we will use clamp or repeat sampling mode going forward to produce better looking images. I’m also interested in animating these spirals over time, so let’s explore several options for doing so.

First, let’s take mθ to be linearly dependant on time (denoted by t), and denote mθ=2πfθ/rmax·t, where rmax is the maximum image coordinate value for radius. For example, if the origin is exactly at the center (i.e. vec2(0,0)) we get maximum radius at the corners, so rmax=sqrt(2). If the origin is located at one of the corners, the maximum radius is at the other corner – rmax=2sqrt(2).

This notation means that the whole spiral completes a cycle in a frequency of fθ, that is completing 1/fθ cycles per a single time unit (i.e. a second).

r·vec2(cos((2πfθ/rmax)rt+θ),sin((2πfθ/rmax)rt+θ))

From the above equation we can see that larger radii will get stronger frequencies, which makes the spiral look like it is spinning “inwards”. Intuitively, if we want to get the opposite effect, that is, the spiral to look like it is spinning “outwards”, the inner radii should have stronger frequencies compared to outer radii. All what we need to do is to inverse the frequency dependency on r:

r·vec2(cos((2πfθ/rmax)(rmax-r)t+θ),sin((2πfθ/rmax)(rmax-r)t+θ))

But the above equation is equivalent to inversing the sign of the original mθ and adding an angle bias of: 

tθ = 2πfθ·t = -mθ·rmax·t

To sum up, we can get clockwise and counterclockwise spirals that grow inwards or outwards simply by setting the angle shearing and angle bias parameters with the right sign. Here are the formulas:

GrowthOrientationmθtθ
InwardClockwise+(2πfθ/rmax)t0
InwardCounterclockwise-(2πfθ/rmax)t0
OutwardCounterclockwise+(2πfθ/rmax)t-2πfθt
OutwardClockwise-(2πfθ/rmax)t+2πfθt

Listing 1: Spiral parameterization (angle shear and bias)

Note that if mθ and tθ have the same sign – the result is a faster spinning spiral, so I haven’t listed these cases here.

Since we’re investigating spiral animations, I strongly suggest the reader check out the shadertoy demo, which highlights the differences (by switching SPIRAL_GROWTH and SPIRAL_ORIENTATION). 

In practice, since the fθ parameter expresses a degree of freedom, we don’t really need to calculate rmax, mθ and tθ. Instead, we can let the artist plug in numbers until a visually pleasing result is achieved. In fact, we can choose any combination of mθ (controlling growth speed and direction (inwards / outwards)) and tθ (controlling spin speed and orientation (clockwise / counterclockwise)) to achieve interesting spiral effects. 

We can even use a radius scale sr to add a zoom-in or zoom-out effect that interacts with the spiral inwards and outwards growth effect as well as animating these parameters individually over time. Here’s a shadertoy example that allows you to control these three parameters and see the animated spiral results (Figure 6c). Here’s another demo showing a “pulsing” portal effect that reaches a steady state (Figure 6d). There are many possibilities here…

Figure 6c: Animated Spiral (25% of animation sequence, origin at vec2(0.75,0.0), repeat sampling mode)

Figure 6c: Portal (steady state, origin at vec2(-0.52,-0.2), repeat sampling mode)

Closing Thoughts

In this article we investigated the way common linear transformations react with screen-space 2D polar coordinates. We suggested a few artistic effects based on these transformations. 

I hope you found these examples inspiring and that you would like to investigate more yourself. I made this shadertoy sandbox to help get yourself started. All the other demos in this article were initially based on this sandbox. To use it, just change the get_origin and transform functions as well as the alpha variable to achieve your desired effect. You can also use the preprocessor flags to toggle on and off different options for better visualization and debugging. Have fun exploring polar coordinates!