Tutorial

WebGL 2.0 光線步進渲染原理詳解

深入解析 WebGL 2.0 光線步進(Raymarching)技術,了解如何使用 SDF 符號距離場實現即時體積渲染,以及這種技術如何壓榨 GPU 的極限性能。

🚀 volumeshadertest
4天前
10 分鐘

深入探索 WebGL 2.0 光線步進、SDF 和即時體積渲染

WebGL 2.0 解鎖了強大的 GPU 功能集,使傳統上需要原生 API 的高級渲染技術成為可能。其中,光線步進(Raymarching) 作為最富有表現力和數學優雅的方法之一脫穎而出,可以用最少的幾何體渲染複雜場景。

本文將通過 WebGL 2.0 光線步進的核心概念,解釋**符號距離場(SDF)**如何實現高效渲染,並展示這種技術如何從 GPU 中榨取驚人的性能。


1. 什麼是光線步進?

光線步進是一種渲染方法,它不是將幾何體投影到螢幕上,而是從相機逐像素發射一條光線,逐步穿過虛擬場景,評估距離,直到光線擊中表面或達到最大範圍。

1.1 光線步進的工作原理(視覺化圖表)

相機
  |
  | 光線
  v
  +--------------------------------- 螢幕像素
  |
  | 步進 1      (距離 d1)
  |
  |---------> 步進 2      (距離 d2)
  |
  |-----------------------> 步進 3 ...
  |
當距離 < ε 時到達表面

光線步進本質上是一個循環:

for (int i = 0; i < MAX_STEPS; i++) {
    float dist = sdf(currentPos);
    if (dist < EPSILON) hitSurface();
    currentPos += rayDir * dist;
}

關鍵要素是我們傳入的 SDF 函數。


2. 什麼是符號距離場(SDF)?

符號距離場 是一個返回以下值的函數:

> 0  物體外部距離  
= 0  正好在表面上  
< 0  物體內部距離  

2.1 SDF 圖表

           外部
        +------------+
        |   d = 0.5  |
        |            |
內部    |     SDF    | 表面 d = 0
d < 0   |            |
        |   d = -0.3 |
        +------------+

2.2 範例:球體 SDF

float sdSphere(vec3 p, float r) {
    return length(p) - r;
}

通過將這些基本形狀與布林運算(聯集、差集、交集)結合,你可以在不需要任何傳統幾何體的情況下構建複雜場景。


3. 為什麼在 WebGL 2.0 中使用 SDF 光線步進?

3.1 優勢

  • 不需要頂點緩衝區或網格 所有內容都在片段著色器中通過數學計算。

  • 無限的幾何細節 SDF 定義平滑連續的表面。

  • 動態、可變形對象 用數學輕鬆動畫化形狀。

  • 緊湊的場景 完整的 3D 場景可能只需要幾行 GLSL 程式碼。

3.2 使這成為可能的 WebGL 2.0 特性

  • 高精度浮點數
  • 完整的 GLSL ES 3.0 著色器支援
  • UBO(統一緩衝區)
  • 浮點紋理
  • 更靈活的循環和分支

這使得在 WebGL 1.0 中難以實現的穩定、高效能光線步進成為可能。


4. 逐步構建 WebGL 2.0 光線步進器

4.1 場景光線設置

為每個像素計算一條光線:

vec3 rayOrigin = cameraPos;
vec3 rayDir = normalize(uv.x * camRight + uv.y * camUp + camForward);

4.2 光線步進循環

float marchRay(vec3 ro, vec3 rd) {
    float totalDist = 0.0;

    for (int i = 0; i < MAX_STEPS; i++) {
        vec3 p = ro + rd * totalDist;
        float d = map(p);     // 你的 SDF 函數

        if (d < EPSILON) break;    // 擊中
        if (totalDist > MAX_DISTANCE) break;  // 未擊中

        totalDist += d;
    }
    return totalDist;
}

4.3 定義 SDF 場景

float map(vec3 p) {
    float s = sdSphere(p, 1.0);
    float box = sdBox(p - vec3(2.0, 0.0, 0.0), vec3(0.7));
    return min(s, box); // 聯集
}

4.4 法線計算

使用梯度近似:

vec3 calcNormal(vec3 p) {
    const vec2 e = vec2(0.001, 0.0);
    return normalize(vec3(
        map(p + e.xyy) - map(p - e.xyy),
        map(p + e.yxy) - map(p - e.yxy),
        map(p + e.yyx) - map(p - e.yyx)
    ));
}

4.5 光照模型

簡單的漫反射著色:

float diffuse = max(dot(normal, lightDir), 0.0);

5. 渲染體積(霧、雲、煙霧)

SDF 光線步進可以擴展到體積渲染,其中每一步都貢獻顏色和密度,而不是在表面停止。

5.1 體積圖表

光線 →
[低密度] [中密度] [高密度] [不透明]
   +--------+---------+---------+-------+
   採樣1    採樣2     採樣3     採樣4

5.2 體積累積模型

vec3 color = vec3(0);
float absorption = 1.0;

for (int i = 0; i < STEPS; i++) {
    float density = computeDensity(p);
    vec3 sampleColor = density * vec3(0.6, 0.7, 1.0);

    color += sampleColor * absorption;
    absorption *= (1.0 - density * 0.1);

    p += rd * STEP_SIZE;
}

光線步進體積渲染更加昂貴,但 WebGL 2.0 的性能改進(特別是在行動 GPU 上)使即時雲、霧和星雲成為可能。


6. 性能優化技術

光線步進可能很重,因此優化至關重要。

6.1 關鍵技術

  1. 距離剔除 通過依賴準確的 SDF 距離跳過大的空白區域。

  2. 包圍體積 測試光線是否進入存在 SDF 的區域。

  3. 自適應步長 在表面附近使用較小的步長,在開放空間中使用較大的步長。

  4. 提前退出 一旦擊中表面就停止步進。

  5. 減少 MAX_STEPS 即時渲染的典型值範圍是 64–128。

  6. 高效使用 WebGL 2.0 統一變數和緩衝區 將不變的值移出循環。


7. WebGL 2.0 專案的程式碼結構

目錄佈局範例:

shader/
  raymarch.vert
  raymarch.frag
src/
  webgl2-context.js
  camera.js
  renderer.js
index.html

典型的片段著色器部分:

// 1. 相機/光線設置
// 2. SDF 定義
// 3. 材質和光照函數
// 4. 光線步進循環
// 5. 最終顏色輸出

8. 何時應該使用光線步進?

光線步進在以下情況下表現出色:

  • 無限平滑形狀
  • 分形
  • 科幻場景
  • 符號距離軟陰影
  • 程序化幾何
  • 體積效果(雲、霧、上帝之光)

在以下情況下應避免使用光線步進:

  • 許多詳細的複雜網格
  • 逼真的蒙皮角色
  • 包含數千個物件的大型動態場景

光線步進最適合程序化世界風格化視覺效果


9. 結論

WebGL 2.0 光線步進僅使用數學和 GLSL 就將高級即時渲染帶入瀏覽器。使用 SDF,你可以在不需要單個頂點緩衝區的情況下創建複雜的 3D 場景。儘管該技術在計算上很密集,但仔細的優化允許創建可與原生 GPU 應用程式相媲美的富有表現力的視覺效果。

光線步進代表了數學、藝術和 GPU 程式設計的獨特交叉點,使其成為現代 WebGL 開發中最令人興奮的技術之一。

Tags
WebGL光線步進體積渲染GPU性能

© 2025 Volume Shader BM Test. Original shader core algorithm by cznull.

Disclaimer: Heavy GPU usage may cause high power consumption.