2
\$\begingroup\$

I'm a noob shader learner, and I came across this question about animated shaders in Unity: Animated textures for models; How to write a shader?

After reading the answer, I kind of understood the concept of using polar coordinate to animate circular movement of texture. What I failed to comprehend was how the log() function reduces the distortion of texture. So my question, is how does log help the image scale up linearly (not sure if it's the right terminology)?

Here's the shader code from the link just in case.

fixed4 frag(v2f i) : SV_Target
{
    float2 polar = float2(
           atan2(i.uv.y, i.uv.x) / (2.0f * 3.141592653589f), // angle
           log(dot(i.uv, i.uv)) * 0.5f                       // log-radius
        );

    // Check how much our texture sampling point changes between
    // neighbouring pixels to the sides (ddx) and above/below (ddy)
    float4 gradient = float4(ddx(polar), ddy(polar));

    // If our angle wraps around between adjacent samples,
    // discard one full rotation from its value and keep the fraction.
    gradient.xz = frac(gradient.xz + 1.5f) - 0.5f;

    // Copy the polar coordinates before we scale & shift them,
    // so we can scale & shift the tint texture independently.
    float2 tintUVs = polar * _TintTex_ST.xy;
    tintUVs += _ScrollSpeeds.zw * _Time.x;

    polar *= _MainTex_ST.xy;
    polar += _ScrollSpeeds.xy * _Time.x;

    // Sample with our custom gradients.
    fixed4 col = tex2Dgrad(_MainTex, polar, 
                           _MainTex_ST.xy * gradient.xy,
                           _MainTex_ST.xy * gradient.zw
                         );

    // Since our tint texture has its own scale,
    // its gradients also need to be scaled to match.
    col *= tex2Dgrad(_TintTex, tintUVs,
                          _TintTex_ST.xy * gradient.xy,
                          _TintTex_ST.xy * gradient.zw
                         );

    UNITY_APPLY_FOG(i.fogCoord, col);
    return col;
}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

This comes from trying to make each tile of the image maintain the same width:height aspect ratio as we reduce in size toward the center of the funnel.

Let's take a swatch of this endless pattern, and look at one ring of texture repeats where the top of the tiles sits exactly on the unit circle (in yellow):

Example tiling

A single tile in this ring has a width \$w\$ along its curved top edge, and a radial height \$h\$. Its bottom edge is shorter, because the image tapers in toward the center - let's express it as some fraction of the top width we'll call \$a\$.

Now we want to ask, how big is the next tile down the funnel? Well, we know its top width is \$aw\$, since its top is the previous tile's bottom. That means, if we want to maintain the same aspect ratio as the original tile, the height has to be \$ah\$ (if we multiplied the width by \$a\$, we have to do the same to height to keep it in the same proportion).

By the same argument, we can conclude the third tile down in this stack must be \$a^2w \times a^2h\$, and the one after that \$a^3w \times a^3 h\$

In other words, we have an exponential progression: the height of the \$i^{th}\$ tile down from our unit circle row is \$h_i = a^i h\$.

By a similar argument, we can show the radius at which we'll find the \$i^{th}\$ row of tiles is also an exponential function of the tile number \$i\$.

But in the shader, we want to do the opposite: we already know the radius from the center for the pixel being shaded, and we want to figure out which tile that is, to sample the texture at that offset. So we use the inverse of the exponential function, the logarithm.

(In the code I showed in my previous answer, I actually take the logarithm of the square of the radius, knowing that the logarithm of the square is just twice the logarithm of the original, and halving the answer after the fact is faster than a square root. There are some extra constant factors if you do the full math treatment, but we don't really care about those because they just end up as a scale/shift, and can be subsumed into our texture scaling/scrolling animation anyway)

You'll find this "do the opposite operation" is common when doing texture transformations in a shader, because you're manipulating the function's domain, not its range. So if I want a texture to look twice as big, I halve the uv coordinates. If I want it to be half as big, I double the uv coordinates. If I want the texture to tile exponentially, I adjust the uv coordinates logarithmically.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .