GameTech/URP 渲染实战
源码 / Ch21/UVAnim.hlsl
Ch21/UVAnim.hlsl
// 第 21 章公共函数:序列帧 UV、Flowmap 双相位、极坐标
#ifndef COOKBOOK_CH21_UVANIM_INCLUDED
#define COOKBOOK_CH21_UVANIM_INCLUDED

// 第 frame 帧在图集中的 UV。约定:第 0 帧在左上角,从左到右、从上到下排列(与 Unity 粒子的 Texture Sheet Animation 一致)。
// 纹理坐标的 V 轴向上,所以“第 0 行”对应 V 最大的那一行。
float2 FlipbookUV(float2 uv, float frame, float2 grid)
{
    float total = grid.x * grid.y;
    frame = fmod(floor(frame), total);           // 播完从头循环
    float col = fmod(frame, grid.x);
    float row = floor(frame / grid.x);
    return (uv + float2(col, grid.y - 1.0 - row)) / grid;
}

// Flowmap 双相位采样:两套相位差半个周期的 UV,各自沿流向“漂”,权重呈三角波交替。
// 某一相位重置(跳回原位)的瞬间,它的权重恰好为 0,所以看不到跳变。
struct FlowSample
{
    float2 uv0, uv1;
    float  w0;           // 第一套的权重,第二套为 1 - w0
};

FlowSample FlowUV(float2 uv, float2 flow, float time)
{
    FlowSample s;
    float p0 = frac(time);
    float p1 = frac(time + 0.5);
    s.uv0 = uv - flow * p0;
    s.uv1 = uv - flow * p1 + float2(0.37, 0.21);  // 第二套错开一点,避免两层图案重合时出现“重影节拍”
    s.w0 = 1.0 - abs(1.0 - 2.0 * p0);             // p0 = 0 或 1(重置时)权重为 0,p0 = 0.5 时为 1
    return s;
}

// 直角坐标 → 极坐标:x = 角度(0..1,一圈),y = 半径(中心 0,四边中点 1)
float2 ToPolar(float2 uv, float2 center)
{
    float2 d = uv - center;
    float angle = atan2(d.y, d.x) * (0.5 * INV_PI) + 0.5;
    float radius = length(d) * 2.0;
    return float2(angle, radius);
}

#endif