這一篇帶你用 CUDA 在顯示卡上畫出經典的 Mandelbrot 曼德布洛集合。
你會學到:數學概念 → 把「每個像素」交給 GPU 的一個執行緒。
把複數平面上的每個點 (c = x + iy) 當作種子,重複算:
如果 (|z_n|) 永遠不爆掉(半徑不超過 2),就說這個點屬於集合;否則不屬於。
我們會記錄「第幾次迭代才爆掉」,把它轉成顏色。屬於集合(沒爆掉)的點就塗黑。
for (int j = 0; j < height; ++j) {
double y0 = j * ((upper - lower) / height) + lower;
for (int i = 0; i < width; ++i) {
double x0 = i * ((right - left) / width) + left;
int repeats = 0;
double x = 0;
double y = 0;
double length_squared = 0;
while (repeats < iters && length_squared < 4) {
double temp = x * x - y * y + x0;
y = 2 * x * y + y0;
x = temp;
length_squared = x * x + y * y;
++repeats;
}
image[j * width + i] = repeats;
}
}
// CUDA 核心:每條執行緒負責畫一個像素
__global__ void mandelbrotKernel(
uchar3* img, int W, int H,
float xmin, float xmax, float ymin, float ymax,
int maxIter)
{
int x = blockIdx.x * blockDim.x + threadIdx.x;
int y = blockIdx.y * blockDim.y + threadIdx.y;
if (x >= W || y >= H) return;
// 把像素座標映射到複數平面 c = cx + i*cy
float cx = xmin + (x / (float)(W - 1)) * (xmax - xmin);
float cy = ymin + (y / (float)(H - 1)) * (ymax - ymin);
// z_{n+1} = z_n^2 + c ; 初始 z=0
float zx = 0.0f, zy = 0.0f;
float zx2 = 0.0f, zy2 = 0.0f;
int iter = 0;
// 逃逸半徑 2(平方 = 4)
while (zx2 + zy2 <= 4.0f && iter < maxIter) {
zy = 2.0f * zx * zy + cy;
zx = zx2 - zy2 + cx;
zx2 = zx * zx;
zy2 = zy * zy;
iter++;
}
// 著色:屬於集合(沒爆掉)→ 黑色;其他以迭代比例漸層
uchar3 color;
if (iter == maxIter) {
color = make_uchar3(0, 0, 0);
} else {
// 簡單比例;想更平滑可用 "smooth coloring"
float t = iter / (float)maxIter;
color = palette(t);
}
img[y * W + x] = color;
}
while
迴圈次數也不同。使用 float 或 double?float
很快、夠用;極度放大才需要 double
,但多數消費級 GPU 的 double 效能會大幅下降。
Unified Memory
你可以把 cudaMalloc/cudaMemcpy
替換為 cudaMallocManaged
,少寫一行 copy;但清楚掌握資料流仍然很重要。
把每個像素交給一條 GPU 執行緒,用簡單的逃逸迭代決定顏色,你就做出了 CUDA 版的 Mandelbrot!
從這題開始,你已經能把「一個像素一個工作」這種最經典的 資料平行 模式搬上 GPU,接著可以挑戰更多影像處理與數值運算題目。