从数学原理到实战案例,系统掌握极坐标在 Shader 中的应用
1. 什么是极坐标?
在 Shader 特效开发中,极坐标(Polar Coordinates) 是一种极具表达力的数学工具。很多令人印象深刻的视觉效果------旋涡、放射光线、雷达扫描、能量爆发环------其背后几乎都离不开极坐标的身影。
我们平时使用的 UV 坐标属于笛卡尔直角坐标系 ,用 (x, y) 描述平面上的点。而极坐标系使用 (r, \\theta) 来描述同一个点:
| 符号 | 含义 |
|---|---|
| r | 极径,点到原点的距离 |
| θ | 极角,从正 x 轴逆时针旋转的角度(弧度) |

▲ 动态展示:r = 极径(点到原点的距离),θ = 极角(旋转角度)
2. 坐标系转换公式
两种坐标系的互转公式是极坐标使用的核心:
直角 → 极坐标
r = √(x² + y²)
θ = atan2(y, x)
θnorm = θ / (2π)
极坐标 → 直角
x = r · cos(θ)
y = r · sin(θ)

▲ 拖动点查看实时坐标转换
3. 直角坐标 vs 极坐标 UV
极坐标的核心魔力在于改变了采样方式:
- 对 r 做操作 → 控制 径向(远近) 方向的效果
- 对 θ 做操作 → 控制 旋转 方向的效果
**关键点:**将 UV 转换为极坐标后,纹理的"扭曲"变得非常自然,可以轻松实现漩涡、放射、锥形扫描等直角坐标系下很难直接实现的效果。

▲ 左:直角坐标 UV(矩形) | 右:极坐标 UV(圆形展开)
4. 基础:UV 转极坐标的 HLSL 实现
在开始案例之前,先封装一个通用的极坐标转换函数,后续所有案例都会用到它:
cs
// 将 UV 从直角坐标转为极坐标
// 返回值: x = 归一化极角 [0,1], y = 极径 [0, ~0.707]
float2 CartesianToPolar(float2 uv)
{
// 将 UV 中心移到 (0,0)
float2 centeredUV = uv - 0.5;
// 计算极径
float r = length(centeredUV);
// 计算极角,atan2 返回 [-PI, PI],归一化到 [0, 1]
float theta = atan2(centeredUV.y, centeredUV.x);
float thetaNorm = theta / (2.0 * 3.14159265); // → [-0.5, 0.5]
thetaNorm = thetaNorm + 0.5; // → [0, 1]
return float2(thetaNorm, r);
}
小提示: thetaNorm 对应横轴(绕圈方向),r 对应纵轴(径向深度)。用这两个值去采样贴图,就把"圆形展开成了矩形"。
5. 案例一:旋转光环(Energy Ring)
效果描述
一个发光的圆环,持续旋转,带有渐变的辉光效果,常用于技能释放、选中提示等场景。
实现思路
- UV 转极坐标,得到 (\\theta, r)
- 用 r 控制圆环的宽度(只在特定半径范围内显示)
- 用 \\theta + \\text{Time} 驱动贴图滚动,实现旋转

cs
// ① 用极角 + 时间滚动采样贴图
float2 texUV = float2(theta + _Time.y * _Speed, r);
fixed4 texCol = tex2D(_MainTex, texUV);
// ② 计算圆环遮罩(距目标半径越近,越不透明)
float dist = abs(r - _RingRadius);
float mask = 1.0 - smoothstep(0.0, _RingWidth, dist);
// ③ 输出带辉光的圆环颜色
fixed4 col = _Color * texCol;
col.a *= mask;
6. 案例二:雷达扫描效果(Radar Sweep)
效果描述
经典的雷达/SONAR 扫描动画:一条发光的扫描线以固定角速度旋转,在扫描线后面留下渐渐消散的余辉。
核心数学:处理角度环绕
float diff = frac(theta - sweep);
frac() 取小数部分,自动处理了 0 和 1 之间的环绕(相当于取模),避免在 0/1 边界出现硬切割。

cs
// 扫描线当前角度
float sweep = frac(_Time.y * _Speed);
// 计算像素到扫描线的角度差(处理环绕)
float diff = frac(theta - sweep);
// 拖尾亮度:只在扫描线后 _TrailLength 内显示
float trail = 1.0 - smoothstep(0.0, _TrailLength, diff);
// 扫描线本体(极细的高亮条)
float line = 1.0 - smoothstep(0.0, 0.01, diff);
7. 案例三:能量旋涡(Energy Vortex)
效果描述
中心向外旋转扭曲的旋涡效果,粒子或纹理沿螺旋轨迹卷入/卷出,常用于传送门、技能吸附等效果。
核心数学:螺旋映射
旋涡的关键在于让旋转角随半径变化:
θdistorted = θ + k · r
内圈旋转多,外圈旋转少。将扭曲后的 (\\theta_{\\text{distorted}}, r) 转回直角坐标,再去采样纹理,就得到了旋涡效果。

cs
// ① 螺旋扭曲:角度 += 紧密度 * 半径 + 时间偏移
float twistedTheta = theta + _Tightness * r + _Time.y * _Speed;
// ② 将扭曲后的极坐标转回直角坐标作为采样 UV
float2 sampleUV;
sampleUV.x = cos(twistedTheta) * r + 0.5;
sampleUV.y = sin(twistedTheta) * r + 0.5;
// ③ 采样纹理
fixed4 texCol = tex2D(_MainTex, sampleUV);
// ④ 中心黑洞遮罩(内圈淡出)
float innerMask = smoothstep(_InnerRadius, _InnerRadius + 0.05, r);
8. 案例四:放射条纹光芒(Sun Rays)
效果描述
从中心向四周发散的光芒条纹,带有动态缩放和呼吸感,适合 BOSS 出场、胜利特效等场景。
实现思路
- UV 转极坐标,只关注 \\theta
- 对 \\theta 做高频 \\cos 运算,产生交替的亮暗条纹(即光芒)
- 用 r 驱动亮度衰减(距离越远越暗)
- 用时间让条纹旋转或脉冲

cs
// ① 旋转偏移
theta += _Time.y * _Speed;
// ② 用 cos 产生 _RayCount 个周期的条纹
float rayPattern = cos(theta * _RayCount) * 0.5 + 0.5;
// ③ 用幂次提高锐度(值越大,光芒越细越尖)
rayPattern = pow(rayPattern, _Sharpness);
// ④ 随距离衰减(中心亮,边缘暗)
float radialFade = 1.0 - smoothstep(0.0, 0.5, r);
参数调整技巧
| 参数 | 效果 |
|---|---|
_RayCount = 6 |
六芒星光芒 |
_RayCount = 8 |
八方光芒(常见 RPG 道具光效) |
_Sharpness = 2 |
宽而柔的光晕感 |
_Sharpness = 15 |
细而硬的激光条 |
| 使用加法混合 | 光芒叠加在场景上,产生真实发光感 |
9. 案例五:极坐标纹理展开与卷绕
应用场景
有时候我们需要反向操作:把一张横向条带 贴图(如火焰横幅、符文带)卷绕成圆形 ,或者把圆形纹理展开成矩形显示在 UI 上(如圆形进度条的纹理)。

条带 → 圆环
将极角映射为贴图 U 轴(绕圈一圈 = 贴图横向完整展示一次),将极径映射为贴图 V 轴(在圆环宽度范围内映射到 [0,1])。
cs
// 将极角映射为贴图 U 轴(绕圈一圈 = 贴图横向完整展示一次)
// 将极径映射为贴图 V 轴(_Radius±_Width 范围内 = [0,1])
float vCoord = (r - (_Radius - _Width * 0.5)) / _Width;
// 只在圆环范围内显示
if (vCoord < 0.0 || vCoord > 1.0) return fixed4(0,0,0,0);
float2 texUV = float2(theta, vCoord);
return tex2D(_MainTex, texUV);
**使用效果:**一张 512×64 的火焰横幅贴图通过此 Shader 可以直接变成一个旋转火焰圆环。配合贴图动画(UV 滚动),无需任何粒子系统,即可实现动态燃烧的能量圆圈。
10. 综合优化技巧
避免中心奇点
极坐标在原点 (0, 0) 处,\\text{atan2}(0, 0) 的结果是未定义的,会导致中心像素出现噪点或闪烁。
解决方案:
cs
// 方式一:中心遮罩
float coreMask = smoothstep(0.0, 0.02, r);
// 方式二:给极径加极小偏移
float r = length(c) + 1e-5;
用 DDX/DDY 优化纹理采样
在极坐标采样贴图时,靠近中心处 UV 梯度变化剧烈,容易出现 Mip 选择错误导致的模糊或闪烁。可以手动指定导数:
cs
float2 dx = ddx(i.uv);
float2 dy = ddy(i.uv);
fixed4 col = tex2Dgrad(_MainTex, polarUV, dx, dy);
极坐标 + 噪声 = 有机感旋涡
将极坐标与 Perlin Noise 或 Simplex Noise 结合,可以制作流体感、有机感的旋涡:
cs
float noise = tex2D(_NoiseTex, float2(theta * 2.0, r * 3.0 + _Time.y)).r;
theta += noise * _NoiseStrength;
性能考量
| 操作 | 性能开销 |
|---|---|
atan2() |
中等(三角函数,但现代 GPU 优化很好) |
sqrt() / length() |
低 |
sin() / cos() |
低 |
| 多次极坐标采样 | 注意 Overdraw,移动端慎用 |
11. 总结
极坐标在 Shader 中的应用本质上是改变了 UV 的"解读方式":
直角坐标 UV → 极坐标 UV → 对角度或半径做变换 → 转回直角 or 直接用于采样
通过本文的五个案例,我们可以总结出极坐标在 Shader 中的几大典型用法:
| 用法 | 核心操作 | 典型效果 |
|---|---|---|
| 旋转 | \\theta += \\text{Time} | 光环旋转、转盘 |
| 扫描 | \\text{frac}(\\theta - \\text{sweep}) | 雷达、探照灯 |
| 螺旋 | \\theta += k \\cdot r | 旋涡、传送门 |
| 放射条纹 | \\cos(\\theta \\cdot N) | 光芒、星芒 |
| 展开/卷绕 | \\theta \\to U, r \\to V | 圆环贴图、进度条 |
掌握极坐标变换,配合 Unity 的 ShaderLab 系统,可以用极少的性能开销实现大量原本需要复杂粒子系统才能完成的动态特效。这正是程序化 Shader 的魅力所在。