前言
在笛卡兒座標(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) 回傳的範圍是 -PI 到 PI。如果你需要 0 到 2*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 創作中不可或缺的工具:
- 座標轉換:
length()和atan()把笛卡兒座標轉成極座標 - 玫瑰曲線:
r = cos(n * theta),簡潔的花瓣圖案 - 萬花筒:
mod+ 鏡像,把一小塊圖案重複成對稱圖形 - 旋轉對稱:只用
mod,純旋轉複製 - 創意組合:極座標 + SDF + noise = 無限可能
我個人覺得極座標最迷人的地方在於:它能讓你用極少的程式碼產生極複雜的對稱圖案。一個 cos(n * theta) 就是一朵花,一個 mod(theta, ...) 就是一面萬花筒。
延伸閱讀
- The Book of Shaders — Patterns:用
fract和mod做重複圖案 - Shadertoy — kaleidoscope:社群的萬花筒作品
- Wolfram MathWorld — Rose Curve:玫瑰曲線的數學背景
下一篇我們來聊 iq 的經典 cosine color palette——用一個公式就能產生各種美麗的調色盤。