iT邦幫忙

2025 iThome 鐵人賽

DAY 25
0

這一篇帶你用 CUDA 在顯示卡上畫出經典的 Mandelbrot 曼德布洛集合
你會學到:數學概念 → 把「每個像素」交給 GPU 的一個執行緒。


1)先用一句話理解 Mandelbrot

把複數平面上的每個點 (c = x + iy) 當作種子,重複算:
eq
如果 (|z_n|) 永遠不爆掉(半徑不超過 2),就說這個點屬於集合;否則不屬於。
我們會記錄「第幾次迭代才爆掉」,把它轉成顏色。屬於集合(沒爆掉)的點就塗黑。

https://paulbourke.net/fractals/mandelbrot/mandel1.jpg


2)我們怎麼把這件事丟給 GPU?

  • 一張圖有 (W * H) 個像素。
  • 每個像素對應複數平面上的一個 (c)。
  • 每個像素交給 一條 GPU 執行緒去做迭代,算出顏色。
  • 全部執行緒一起跑,就很快。

3)CPU版程式碼

  • 邏輯: 每個pixel (x,y)計算跳脫bound需要多少個iteration (逃不出去的=black),大部分的情況是2個iteration逃出 (外圍)
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;
        }
    }

4 Cuda版本

// 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;
}

5)為什麼 GPU 跑得快?有什麼坑?

  • 快的原因:我們把每個像素交給一條執行緒,幾百萬像素就能幾百萬條執行緒分批在 SM 上跑,平行度很高。
  • 小坑:分歧(Divergence)
    曼德布洛每個像素的「爆掉時間」不同,while 迴圈次數也不同。
    同一個 Warp(32 條執行緒)裡有人做很多圈、有人很快結束,就會分時排隊,效能略降。
    這是此題的特性,正常現象,先不用為它煩惱。

6)進階小補充(先知道即可)

  • 使用 float 或 double?
    float 很快、夠用;極度放大才需要 double,但多數消費級 GPU 的 double 效能會大幅下降。

  • Unified Memory
    你可以把 cudaMalloc/cudaMemcpy 替換為 cudaMallocManaged,少寫一行 copy;但清楚掌握資料流仍然很重要。


一句話總結

把每個像素交給一條 GPU 執行緒,用簡單的逃逸迭代決定顏色,你就做出了 CUDA 版的 Mandelbrot!
從這題開始,你已經能把「一個像素一個工作」這種最經典的 資料平行 模式搬上 GPU,接著可以挑戰更多影像處理與數值運算題目。


上一篇
# Day 23|第一個 CUDA 專案:環境與 Hello World
下一篇
Day 25|GPU 的效能分析與最佳化策略
系列文
渲染與GPU編程26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言