前言

在笛卡兒座標(Cartesian coordinates)中,我們用 (x, y) 描述一個點的位置。但有些圖形用另一套座標系統來描述會簡潔得多——那就是極座標(polar coordinates),用 (r, θ) 也就是「距離」和「角度」來定義位置。

在 shader 程式設計中,極座標是一個威力驚人的工具。它可以讓你輕鬆畫出花朵、星形、螺旋、萬花筒等具有旋轉對稱性的圖案。只要理解了座標轉換,你就打開了一個全新的創作空間。


笛卡兒座標到極座標的轉換

轉換公式非常直接:

// 笛卡兒 (x, y) → 極座標 (r, theta)
float r = length(p);         // 距離原點的距離
float theta = atan(p.y, p.x); // 角度(-PI 到 PI)

反向轉換:

// 極座標 (r, theta) → 笛卡兒 (x, y)
vec2 p = vec2(r  cos(theta), r  sin(theta));

在 GLSL 中,atan(y, x) 回傳的範圍是 -PIPI。如果你需要 02*PI,可以這樣做:

float theta = atan(p.y, p.x);
if (theta < 0.0) theta += 2.0 * 3.14159265;
// 或更簡潔:
float theta = atan(p.y, p.x) + 3.14159265; // 0 到 2*PI(偏移版)

基礎極座標圖形

在極座標中,圓就是 r = 常數

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;

float r = length(uv); float theta = atan(uv.y, uv.x);

// 圓:距離 0.5 的地方畫線 float d = abs(r - 0.5); vec3 col = vec3(1.0 - smoothstep(0.0, 0.01, d));

fragColor = vec4(col, 1.0); }

螺旋

螺旋是 rθ 的線性關係:

float spiral = abs(fract(r  3.0 - theta / (2.0  3.14159)) - 0.5);
vec3 col = vec3(1.0 - smoothstep(0.0, 0.05, spiral));

fract(r <em> 3.0 - theta / (2</em>PI)) 產生沿徑向和角度方向都在變化的值,形成螺旋。


玫瑰曲線(Rose Curve)

玫瑰曲線是極座標中最著名的曲線之一。公式為:

r = cos(n * θ)

其中 n 決定花瓣數量:

  • n 為奇數:有 n 片花瓣
  • n 為偶數:有 2n 片花瓣
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;

float r = length(uv); float theta = atan(uv.y, uv.x);

// 玫瑰曲線 float n = 5.0; // 花瓣數 float roseR = 0.5 abs(cos(n theta)); // 曲線的半徑

// 畫填充區域 float d = r - roseR; vec3 col = vec3(1.0 - smoothstep(-0.01, 0.01, d));

// 上色 col *= vec3(1.0, 0.3, 0.4); // 玫瑰紅

fragColor = vec4(col, 1.0); }

動態花瓣

讓花瓣數隨時間變化:

float n = 3.0 + 2.0  sin(iTime  0.5); // 在 1 到 5 之間變化
float roseR = 0.5  abs(cos(n  theta + iTime));

萬花筒效果(Kaleidoscope)

萬花筒的核心概念是角度鏡像——把 360 度分成 N 等分,然後在每個扇形中鏡像反射,讓一小塊區域的圖案重複 N 次。

基本鏡像

vec2 kaleidoscope(vec2 p, float segments) {
    float r = length(p);
    float theta = atan(p.y, p.x);

// 把角度限制在一個扇形內 float segmentAngle = 2.0 * 3.14159 / segments; theta = mod(theta, segmentAngle);

// 鏡像:如果超過扇形的一半,就翻轉 theta = abs(theta - segmentAngle * 0.5);

// 轉回笛卡兒座標 return vec2(cos(theta), sin(theta)) * r; }

使用範例

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;

// 萬花筒變換:8 個鏡像 vec2 p = kaleidoscope(uv, 8.0);

// 在變換後的空間中畫東西 float d = sdBox(p - vec2(0.3, 0.0), vec2(0.1, 0.05)); float circle = length(p - vec2(0.5, 0.1)) - 0.08;

d = min(d, circle);

vec3 col = vec3(0.1, 0.2, 0.4); col = mix(vec3(1.0, 0.8, 0.3), col, smoothstep(0.0, 0.005, d));

fragColor = vec4(col, 1.0); }

八個方向都會出現同樣的圖案,形成萬花筒效果。

動態萬花筒

搭配紋理和時間,效果更驚人:

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;

// 旋轉 float angle = iTime * 0.2; mat2 rot = mat2(cos(angle), -sin(angle), sin(angle), cos(angle)); uv = rot * uv;

// 萬花筒 vec2 p = kaleidoscope(uv, 6.0);

// 用 noise 產生紋理 float n = fbm(p 5.0 + iTime 0.3);

// 用極座標上色 float r = length(p); float theta = atan(p.y, p.x);

vec3 col = vec3( 0.5 + 0.5 sin(n 6.0 + r * 10.0), 0.5 + 0.5 sin(n 6.0 + r * 10.0 + 2.094), 0.5 + 0.5 sin(n 6.0 + r * 10.0 + 4.189) );

col *= smoothstep(1.5, 0.0, r); // 邊緣漸暗

fragColor = vec4(col, 1.0); }


旋轉對稱

不用鏡像,只用 mod 也能產生旋轉對稱:

vec2 rotationalSymmetry(vec2 p, float segments) {
    float r = length(p);
    float theta = atan(p.y, p.x);

// 只用 mod,不做鏡像 float segAngle = 2.0 * 3.14159 / segments; theta = mod(theta + segAngle 0.5, segAngle) - segAngle 0.5;

return vec2(cos(theta), sin(theta)) * r; }

差別在於:

  • 萬花筒(鏡像):左右翻轉,像照鏡子
  • 旋轉對稱(mod):純粹旋轉複製,像齒輪

實際效果比較

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;

// 左半邊:萬花筒(鏡像) // 右半邊:旋轉對稱 vec2 p; if (uv.x < 0.0) { p = kaleidoscope(uv + vec2(0.5, 0.0), 6.0); } else { p = rotationalSymmetry(uv - vec2(0.5, 0.0), 6.0); }

// 畫一個不對稱的形狀來凸顯差異 float d = length(p - vec2(0.3, 0.1)) - 0.05; d = min(d, sdBox(p - vec2(0.2, -0.05), vec2(0.08, 0.02)));

vec3 col = vec3(0.15); col = mix(vec3(0.9, 0.6, 0.2), col, smoothstep(0.0, 0.005, d));

fragColor = vec4(col, 1.0); }


極座標 + SDF 的創意組合

齒輪

float gear(vec2 p, float innerR, float outerR, float teeth, float toothSize) {
    float r = length(p);
    float theta = atan(p.y, p.x);

// 齒的波紋 float toothWave = cos(theta teeth) toothSize; float gearR = mix(innerR, outerR, 0.5) + toothWave;

float d = abs(r - gearR) - (outerR - innerR) * 0.3;

// 中心孔 float hole = -(r - innerR * 0.5); d = max(d, hole);

return d; }

花朵

float flower(vec2 p, float petals, float size) {
    float r = length(p);
    float theta = atan(p.y, p.x);

// 花瓣形狀 float petalR = size (0.5 + 0.5 cos(petals * theta));

// 花心 float center = r - size * 0.2;

return min(r - petalR, -center); }

極座標格線

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;

float r = length(uv); float theta = atan(uv.y, uv.x);

// 同心圓 float circles = abs(fract(r * 5.0) - 0.5); circles = smoothstep(0.0, 0.02, circles);

// 放射線 float rays = abs(fract(theta 6.0 / (2.0 3.14159)) - 0.5); rays = smoothstep(0.0, 0.02, rays);

vec3 col = vec3(circles * rays);

// 中心漸變 col *= smoothstep(0.0, 0.1, r);

fragColor = vec4(col, 1.0); }


進階:多層萬花筒

把不同層數的萬花筒效果疊加,可以產生非常繁複的圖案:

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;

vec3 col = vec3(0.0);

// 三層不同對稱數的萬花筒 for (int i = 0; i < 3; i++) { float fi = float(i); float segments = 4.0 + fi * 2.0; // 4, 6, 8 float scale = 1.0 + fi * 0.5;

vec2 p = kaleidoscope(uv * scale, segments);

// 旋轉每一層 float a = iTime (0.1 + fi 0.05); mat2 rot = mat2(cos(a), -sin(a), sin(a), cos(a)); p = rot * p;

// 每一層的圖案 float d = length(p - vec2(0.2 + fi * 0.1, 0.0)) - 0.05; float ring = abs(length(p) - 0.3 - fi * 0.1) - 0.01; d = min(d, ring);

// 不同顏色 vec3 layerColor = vec3( 0.5 + 0.5 sin(fi 2.0), 0.5 + 0.5 sin(fi 2.0 + 2.0), 0.5 + 0.5 sin(fi 2.0 + 4.0) );

col += layerColor (1.0 - smoothstep(0.0, 0.01, d)) 0.5; }

fragColor = vec4(col, 1.0); }


小結

極座標是 shader 創作中不可或缺的工具:

  1. 座標轉換length()atan() 把笛卡兒座標轉成極座標
  2. 玫瑰曲線r = cos(n * theta),簡潔的花瓣圖案
  3. 萬花筒mod + 鏡像,把一小塊圖案重複成對稱圖形
  4. 旋轉對稱:只用 mod,純旋轉複製
  5. 創意組合:極座標 + SDF + noise = 無限可能

我個人覺得極座標最迷人的地方在於:它能讓你用極少的程式碼產生極複雜的對稱圖案。一個 cos(n * theta) 就是一朵花,一個 mod(theta, ...) 就是一面萬花筒。

延伸閱讀

下一篇我們來聊 iq 的經典 cosine color palette——用一個公式就能產生各種美麗的調色盤。