Appearance
第07回 ボリュームレイマーチ
今回の授業内容
- 表面と体積をレイで描画する違いを理解する
- レイ上で密度を一定間隔でサンプリングする
- 色と Alpha を手前から合成する
- FBM で霧、煙、雲の密度を作る
- ステップ数、歩幅、透過率の品質を比較する
表面で止まらず体積を通過する
SDF のレイマーチは表面を見つけたら終了します。霧や雲は明確な表面を持たず、レイ上の密度が少しずつ色と透明度に影響します。
カメラ
レイを進める
低密度領域
低密度領域
密度をサンプリングする
高密度領域
高密度領域
色とAlphaを蓄積する
低密度領域
低密度領域
体積を通過する
背景
手前から順に色と Alpha を蓄積し、ほぼ不透明になったらそれ以降の計算を終了できます。
密度を定義する
hlsl
float DensityField(float3 p, float time)
{
float heightFade = saturate(1.0 - abs(p.y) * 0.25);
float noise = Fbm3D(p * 0.7 + float3(time * 0.1, 0.0, 0.0));
return saturate((noise - 0.45) * 3.0) * heightFade;
}密度をノイズだけで決めると、空間全体に散らばります。高さ、球、ボックスなどの大きな形で範囲を決め、ノイズで境界と内部を崩します。
密度をループで蓄積する
hlsl
float4 RaymarchVolume(float3 rayOrigin, float3 rayDirection, float time)
{
float4 accumulated = 0.0;
float distanceAlongRay = 0.0;
const float stepSize = 0.12;
[loop]
for (int i = 0; i < 80; i++)
{
float3 p = rayOrigin + rayDirection * distanceAlongRay;
float density = DensityField(p, time);
float alpha = 1.0 - exp(-density * stepSize * 2.0);
float3 sampleColor = lerp(
float3(0.25, 0.35, 0.5),
float3(1.0, 0.95, 0.85),
density
);
accumulated.rgb += (1.0 - accumulated.a) * sampleColor * alpha;
accumulated.a += (1.0 - accumulated.a) * alpha;
if (accumulated.a > 0.98) break;
distanceAlongRay += stepSize;
}
return accumulated;
}1 - accumulated.a は、手前のサンプルを通過して残っている光の割合です。新しいサンプルはその残りにだけ影響します。
実習:霧、煙、雲を作り分ける
- 一定密度のボックスを描画する
- 手前からの Alpha 合成を確認する
- 第03回の FBM を3D座標で使う
- 時間でノイズ座標を移動する
- 次の3設定を作成する
| 表現 | 調整方針 |
|---|---|
| 霧 | 低密度、広い範囲、少ないコントラスト |
| 煙 | 上方への流れ、局所的な高密度、暗い色 |
| 雲 | 高さの範囲、大きな形、明るい光側 |
授業内課題:品質とシルエットを両立する
- サンプル数を32、64、96に変える
- 歩幅を変えて段差やちらつきを確認する
- 各品質でフレーム時間を記録する
- 低品質でも表現の意図が残る密度と色を調整する
完成条件
- レイ上の密度を固定上限のループでサンプリングできる
- 手前からの Alpha 合成を実装できる
- FBM と大きな形のマスクを組み合わせている
- サンプル数と品質、負荷の関係を説明できる
今回のまとめ
ボリュームレイマーチは、表面で止まらずレイ上の密度を蓄積します。プロシージャルノイズとループが、立体的な霧、煙、雲の表現に直接つながります。
次回: 第08回 水面表現1:波と法線