「リアルタイムグラフィックスの数学」勉強ログ - 第7章 距離とSDF

「リアルタイムグラフィックスの数学」勉強ログ - 第7章 距離とSDF

目次

はじめに#

「リアルタイムグラフィックスの数学」の第 7 章の距離と SDF についての勉強ログです。

2 次元 SDF#

胞体ノイズでは近傍点との距離を返す関数でしたが、近くの「点」ではなく「図形」との距離を返す関数を考えます。ここでは図形との負の値もありうる距離を返す関数を導入します。これがSDF(Signed Distance Function, 符号付き距離関数)となります。

円の SDF#

円の SDF について考えてみます。円は中心点 C と半径 r から決まりますが、C からの距離が r より小さければ円の「内部」、r より大きければ円の「外部」となり、r と等しい場合は円の「境界」です。平面上の点 P に対して、円の外部では値は正、内部では値が負とします。これが円の SDF を定めます。

円のSDF
円のSDF

ここで、円の SDF の等高線を描くようにします。SDF の特徴は、等間隔に値をとると、その値の等高線も等間隔にあらわれることです。サンプルコードではf(x,y)=(x2+y2)d1f(x, y) = (x^2 + y^2)^d - 1として間隔aaごとにf(x,y)=0,±a,±2a,...f(x, y) = 0, \pm{a}, \pm{2a}, ...を満たす等高線を描きます。

円のSDF
float circle(vec2 p, vec2 c, float r){
  float d = 0.5 + u_mouse.x / u_resolution.x; // マウスのx座標に合わせて指数を動かす
  return pow(dot(p - c, p - c), d) - r;
}

ここで d が 0.5 の場合、すなわちx2+y2\sqrt{x^2 + y^2}の場合は等高線は等間隔にあらわれます。それ以外の場合は等間隔ではないので、SDF である場合はd=0.5d = 0.5の場合のみになります。

(x^2 + y^2)^d - 1の等高線
(x^2 + y^2)^d - 1の等高線

矩形の SDF#

円の SDF はシンプルな式で表せましたが、矩形の SDF は少し複雑になります。次は矩形の SDF のコードになります。

矩形のSDF
float rect(vec2 p, vec2 c, vec2 d) {
  p = abs(p - c);
  return length(max(p - d, vec2(0.0))) + min(max(p.x - d.x, p.y - d.y), 0.0);
}

この SDF のコードの意味を考えてみます。
矩形の中心を原点に写し、座標(±d1,±d2)(\pm{d_1}, \pm{d_2})が頂点となる矩形を考えてみます。

折りたたまれた矩形の境界への距離
折りたたまれた矩形の境界への距離

図のように直線x=d1x = d_1y=d2y = d_2を境界として第 1 象限をD,D+,D+,D++D_{--}, D_{+-}, D_{-+}, D_{++}と分け、それぞれの領域で矩形の境界への最短距離を考えます。D+D_{+-}では x 軸方向、D+D_{-+}では y 軸方向の直線距離が最短になります。D++D_{++}では頂点(d1,d2)(d_1, d_2)への直線距離が最短になり、DD_{--}では x 軸方向、y 軸方向の直線距離の小さい方が境界への最短距離を与えます。

外部に対する SDF の値#

矩形の外部はD+,D+,D++D_{+-}, D_{-+}, D_{++}の領域になります。これらの領域では、プラスとなりmin(max(p.x - d.x, p.y - d.y), 0.0)の値は 0 となるので、length(max(p - d, vec2(0.0)))が SDF の値となります。

ここで各領域でのlength(max(p - d, vec2(0.0)))の値は次のようになります。

  • D++D_{++} : length(p - d)
  • D+D_{+-} : p.x - d.x
  • D+D_{-+} : p.y - d.y

内部に対する SDF の値#

矩形の内部はDD_{--}の領域になります。これらの領域では、length(max(p - d, vec2(0.0)))の値は 0 となるので、min(max(p.x - d.x, p.y - d.y), 0.0)が SDF の値となります。実際にp.x - d.xp.y - d.yはどちらも負の値になり、その大きい方は境界への最短距離となるので SDF の定義を満たします。

矩形の SDF の等高線#

矩形のSDFの等高線
矩形のSDFの等高線

上図は矩形の SDF の等高線になります。この図を見ると矩形の外部の等高線は角が丸くなっているのが分かると思います。この部分は、先ほどのD++D_{++}の領域になり頂点への距離が SDF の値になるので角が丸くなります。

ユークリッド距離・マンハッタン距離・チェビシェフ距離#

今までのユークリッド距離ではない、距離の測り方をみていきます。マンハッタン距離・チェビシェフ距離について紹介します。

ユークリッド距離・マンハッタン距離・チェビシェフ距離
ユークリッド距離・マンハッタン距離・チェビシェフ距離

それぞれの距離の測り方の特徴は次のようになります。

  • ユークリッド距離:点と点をつなぐ線分(常に 1 通り)
  • マンハッタン距離:縦方向・横方向に沿って直線距離を取る(1 通りとは限らない)
  • チェビシェフ距離:距離が最大の方向に沿って直線距離を取る

マンハッタン距離・チェビシェフ距離の定義#

2 点A(a0,a1)A(a_0, a_1)B(b0,b1)B(b_0, b_1)に対し、マンハッタン距離dmd_mとチェビシェフ距離dcd_cは、それぞれ次のように定義されます。

dm=a0b0+a1b1d_m = |a_0 - b_0| + |a_1 - b_1| dc=max(a0b0,a1b1)d_c = \max(|a_0 - b_0|, |a_1 - b_1|)

先ほどは円の SDF をユークリッド距離における中心点からの近傍を定めましたが、これをマンハッタン距離・チェビシェフ距離で定めると、等高線の形状が変わります。

マンハッタン距離
float sdfManhattan(vec2 p) {
  p = abs(p);
  return dot(p, vec2(1.0)); // p.x + p.yと同等
}
チェビシェフ距離
float sdfChebyshev(vec2 p) {
  p = abs(p);
  return max(p.x, p.y);
}

マンハッタン距離とチェビシェフ距離の円の SDF の等高線の結果は下図のようになります。

距離を変えた等高線
距離を変えた等高線

ユークリッド距離では円形であった等高線が、マンハッタン距離では斜めに倒した正方形、チェビシェフ距離では正方形になります。

次回リンク#

後で詳しく調べるものリスト#

参考書籍#

Related Post
WebGLで使う行列演算の備忘録

はじめに

ライブラリを使用せず素の WebGL で書けるようになりたいと思い、最近はいろいろ勉強してます。WebGL を触るうえで行列演算は避けては通れません。WebGL 用の行列演算の既存のライブラリだと glMatrix がありますが、今回は線形代数の勉強も兼ねて自作で作ってみました。(自作といっても、ほぼほぼ OGL の数学演算系をもって来ているだけですが)

足し算などの簡単なのは飛ばして、何をやってるか忘れそうな処理について備忘録的に書いていきます。長くなるので、主に 3x3 行列をみていきます。

数学演算系のコードは以下に置いてます。

https://github.com/nono-k/webgl-study-note/tree/main/src/lib/webgl/math

3x3 行列(Mat3)

3x3 (Mat3)行列のクラスは次のようにしてます。 演算系の関数はMat3Funcで書いています。

import * as Mat3Func from "./functions/Mat3Func";

export class Mat3 extends Array<number> {
  constructor(
    m00 = 1,
    m01 = 0,
    m02 = 0,
    m10 = 0,
    m11 = 1,
    m12 = 0,
    m20 = 0,
    m21 = 0,
    m22 = 1
  ) {
    super(m00, m01, m02, m10, m11, m12, m20, m21, m22);
  }
  // ...
}

数式で書くと次のような行列表記になります。

Meta Data
公開日:2025-12-20
Tags:
#数学
「リアルタイムグラフィックスの数学」勉強ログ - 第8章 3Dレンダリング
image

はじめに

「リアルタイムグラフィックスの数学」の第 8 章の 3D レンダリングについての勉強ログです。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

テクスチャマッピング

地面とレイの交点を計算し、地面に市松模様をテクスチャとしてマッピングします。市松模様のテクスチャは次のようになります。

float text(vec2 st) {
  return mod(floor(st.s) + floor(st.t), 2.0);
}

ここで 3D 空間のカメラの設定をします。カメラの向きを$\bm{x} = (0, 0, -1)$とし、カメラの上方向を$\bm{y} = (0, 1, 0)$とします。この値に対して外積をとることで、撮影する向きに対する水平方向$(1, 0, 0)$を得ることができます。実際の計算は次のようになります。

Meta Data
公開日:2025-10-11
Tags:
#GLSL
#数学
#勉強ログ
「リアルタイムグラフィックスの数学」勉強ログ - 第6章 胞体ノイズ
image

はじめに

「リアルタイムグラフィックスの数学」の第 6 章の胞体ノイズについての勉強ログです。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

胞体ノイズとは?

値ノイズと勾配ノイズは格子点でのランダムな値を使ったノイズ関数でした。一方この章で学ぶ胞体ノイズは距離を使ってつくられます。胞体(セル)とは生物の細胞などのことで、胞体ノイズは「近さ」によって空間をバラバラに分割します。

第 1 近傍距離とボロノイ分割

胞体ノイズは空間内に点をバラまいて、各点への距離を測ることで得られますが、バラまかれたこれらの点は特徴点と呼ばれます。ここで特徴点を$A_1,...,A_n$とします。空間内の点 P を定めたとき、P から特徴点$A_i$への距離を$d(P, A_i)$で表すと、すべての特徴点までの距離の最小値は次のように求まります。

$$ \mathrm{min}(d(P, A_1), ... , d(P, A_n)) $$

Meta Data
公開日:2025-09-30
Tags:
#GLSL
#数学
#勉強ログ
「リアルタイムグラフィックスの数学」勉強ログ - 第5章ノイズの調理法
image

はじめに

「リアルタイムグラフィックスの数学」の第 5 章のノイズの調理法についての勉強ログです。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

再帰

再帰関数とは、関数の中で自分自身の関数を呼び出すような関数のことです。GLSL では再帰関数は使えませんが、for 文を使って再帰的な処理を行うことができます。

非整数ブラウン運動(fBM)

1 以下の定数 G に対し、1 変数ノイズ関数 noise(x)の周波数を 2 倍するごとに値を G 倍して、それを足し合わせてみましょう。式にすると次のようになります。

$$ noise(x) + Gnoise(2x) + G^2noise(4x) + ... + G^knoise(2^kx) $$

ここで素材となるノイズ関数 noise(x)はどのような関数でもいいですが、値の範囲を$[-0.5, 0.5]$区画にずらしておきます。このように加工されたノイズ関数は非整数ブラウン運動(fractional Brownian motion, fBM)と呼ばれます。

Meta Data
公開日:2025-09-24
Tags:
#GLSL
#数学
#勉強ログ
「リアルタイムグラフィックスの数学」勉強ログ - 第4章勾配ノイズ
image

はじめに

「リアルタイムグラフィックスの数学」の第 4 章の勾配ノイズについての勉強ログです。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

勾配ノイズとは?

前回は値ノイズについて見てきました。値ノイズは計算量も少なく、簡単につくれるノイズですが、格子で区切っているのでブロック状に見えてしまいますし、ムラが現れやすいです。値ノイズを改善したノイズが勾配ノイズになります。

値ノイズの場合は、格子点での乱数の「値」を使うのに対し、勾配ノイズは乱数のベクトル値を「勾配」として使用します。ノイズ関数でよく使用されるパーリンノイズは、この勾配ノイズの一種であります。この章では、勾配ノイズと 2002 年の Perlin による改良版勾配ノイズ(パーリンノイズ)について見ていきます。

勾配ノイズの構成法

1 変数

値ノイズは乱数値をエルミート補間してつくりました。ここで別の見方として、$h(0) = 0$,$h(1) = 1$,$h'(0) = h'(1) = 0$ を満たすエルミート補間関数$h(x)$に対して、$w(x)$を次のように定義します。

Meta Data
公開日:2025-09-20
Tags:
#GLSL
#数学
#勉強ログ
「リアルタイムグラフィックスの数学」勉強ログ - 第3章値ノイズ
image

はじめに

「リアルタイムグラフィックスの数学」の第 3 章の値ノイズについての勉強ログです。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

値ノイズの構成法

まずは 2 次元の値ノイズの構成法について見てみましょう。

平面上の正方形の4つの頂点(格子点)

平面上の正方形の 4 つの頂点(格子点)を用いて、値ノイズを構成します。各格子点での乱数値、または乱数ベクトルを使って生成されるノイズを格子ノイズと呼びます。格子点の成分は整数になるようにします。点$(x, y)$に対して床値$[]$を使って、$(x, y)$を取り囲むマスの格子点は次の 4 つのベクトルで表します。

Meta Data
公開日:2025-09-16
Tags:
#GLSL
#数学
#勉強ログ
「リアルタイムグラフィックスの数学」勉強ログ - 第2章疑似乱数
image

はじめに

「リアルタイムグラフィックスの数学」の第 2 章の疑似乱数についての勉強ログです。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

レガシー乱数

GLSL ES 1.0 ではビット演算が使えないため、ハッシュ関数ではなくサイン関数を使用した乱数生成が行われていました。The Book of Shaders ではサイン関数を使用した乱数生成の方法が紹介されています。

https://thebookofshaders.com/10/?lan=jp

サイン関数自体には乱数性はないですが、サイン関数の値に大きい値をかけて桁を上げ、fractで小数部分を取り出すことで乱数のように見えることができます。

レガシー乱数の 1 変数と 2 変数のコードは次のようになります。

// 1変数のレガシー乱数
float fractSin11(float x) {
  return fract(1000.0 * sin(x));
}

// 2変数のレガシー乱数
float fractSin21(vec2 xy) {
  return fract(sin(dot(xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
Meta Data
公開日:2025-09-10
Tags:
#GLSL
#数学
#勉強ログ
「リアルタイムグラフィックスの数学」勉強ログ - 第1章補間
image

はじめに

「リアルタイムグラフィックスの数学」の第 1 章の補間についての勉強ログです。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

線形補間とグラデーション

mix 関数について

GLSL における mix 関数は以下のような数式になります。

$$ \bm{a} + x\overrightarrow{AB} = \bm{a} + x(\bm{b} - \bm{a}) = (1 - x)\bm{a} + x\bm{b} $$

これは、線分 AB があり、その線分上を動く 0 以上 1 以下の x に対して、線分 AB を x: (1 - x)に内分する点の位置ベクトルを表しています。上記の式のベクトルをmix(a, b, x)として定義しています。このように 2 点間をつなぐことを補間と呼び、上記の式は線形補間(Linear Interpolation)の公式です。

コード 1.1 のように、赤(1, 0, 0)と青(0, 0, 1)を mix 関数でつなぐことで 2 色のグラデーションを作ることができます。

vec2 RED = vec3(1.0, 0.0, 0.0);
vec3 BLUE = vec3(0.0, 0.0, 1.0);
vec3 col = mix(RED, BLUE, pos.x);

赤と青のグラデーション

Meta Data
公開日:2025-09-07
Tags:
#GLSL
#数学
#勉強ログ
勉強のために「リアルタイムグラフィックスの数学」のGLSLコードを確認できるサイトを作った

はじめに

シェーダに再挑戦したいと思い、「リアルタイムグラフィックスの数学」の本をまた読み直してます。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

勉強する際に、本に書いているサンプルコードを Web サイトですぐに確認できるように、Astro と Three.js を使用して GLSL コードを表示するサイトを作成しました。サイトはこちらになります。

リポジトリはこちらになります!

https://github.com/nono-k/book-of-realtime-graphics-math

勉強方法の方針

いったん、本を読みながらローカルの VSCode 上で GLSL コードを書いて確認して、Web サイトに GLSL のコードと実行結果のサムネをアップしていきたいと思います。

また、このブログで勉強した内容を章ごとに残していきたいと思います。以下はリンクになります。

Meta Data
公開日:2025-09-06
Tags:
#GLSL
#数学
Info