「リアルタイムグラフィックスの数学」勉強ログ - 第5章ノイズの調理法

「リアルタイムグラフィックスの数学」勉強ログ - 第5章ノイズの調理法

目次

はじめに#

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

再帰#

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

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

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

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

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

ノイズ関数として値ノイズを使用した fBM のコードは次のようになります。

float fbm21(vec2 p, float g) {
  float val = 0.0; // 値の初期値
  float amp = 1.0; // 振幅の重みの初期値
  float freq = 1.0; // 周波数の重みの初期値
 
  for (int i = 0; i < 4; i++) {
    val += amp * (vnoise21(freq * p) - 0.5); // [-0.5, 0.5]区画にずらす
    amp *= g; // 繰り返しのたびに振幅をg倍
    freq *= 2.01; // 繰り返しのたびに周波数を倍増
  }
  return 0.5 * val + 0.5; // 値の範囲を[0, 1]区画に正規化
}

式では周波数を 2 倍していましたが、格子由来のクセがあらわれるのを防ぐために、周波数の倍数をぴったりの 2.0 ではなく少しずらした 2.01 を使用します。

下図は、上が値ノイズで下がパーリンノイズを素材にした fBM の結果になります。

値ノイズとパーリンノイズのfBM
値ノイズとパーリンノイズのfBM

G の値が 0 から 1 に近づくにしたがって、ノイズの粗さが変化することが分かります。fBM は繰り返し操作を行うため処理が重くなりがちなため、軽くする場合は値ノイズを使うことが好まれます。

ドメインワーピング#

ノイズ関数の値に再帰的な処理を加えたものが fBM でしたが、値ではなく座標に再帰的な処理を加えたものはドメインワーピングと呼ばれます。

定数 G を用意し、新たなノイズ関数を次のようにつくります。

noise1(x)=noise(x+Gnoise(x))noise_1(x) = noise(x + Gnoise(x))

これはノイズのテクスチャ座標を、G だけ重みをつけたノイズ関数で歪ませています。これを繰り返すと次のようになります。

noise2(x)=noise(x+Gnoise1(x)),noise3(x)=noise(x+Gnoise2(x))...noise_2(x) = noise(x + Gnoise_1(x)), \\ noise_3(x) = noise(x + Gnoise_2(x)) \\ . \\ . \\ .

値ノイズを素材にした fBM を使用したドメインワーピングのコードは次のようになります。

float warp21(vec2 p, float g) {
  float val = 0.0;
  for (int i = 0; i < 4; i++) {
    val = fbm21(p + g * val, 0.5);
  }
  return val;
}

下図は、上が fBM、下がパーリンノイズを素材にしたドメインワーピングの結果になります。

fBMとパーリンノイズを素材にしたドメインワーピング
fBMとパーリンノイズを素材にしたドメインワーピング

座標にノイズ関数の値を加えることで歪めていますが、歪める方向が常に同じ方向になるので、歪ませる方向のクセがついてしまいます。これを防ぐために、ノイズ関数の値を回転のパラメータに使うことで、歪ませる方向をずらすことができます。

ここでは、vec2(cos(2.0 * PI * val), sin(2.0 * PI * val))を G かけることで、歪ませる方向をずらすことができます。

float warp21(vec2 p, float g) {
  float val = 0.0;
  for (int i = 0; i < 4; i++) {
    val = fbm21(p + g * vec2(cos(2.0 * PI * val), sin(2.0 * PI * val)), 0.5);
  }
  return val;
}

結果は下図のようになります。

歪ませる方向を回転させたドメインワーピング
歪ませる方向を回転させたドメインワーピング

階調の変換#

フラグメントシェーダでは最終的に fragColor 変数に代入された値によって、各ピクセルの色が決まります。fragColor 変数に値を代入する前に最終段階で別の関数を合成すると、色の濃度の対応付けを変化させることができます。この関数を階調変換関数と呼びます。また、そのグラフのことをトーンカーブと呼びます。

以下は階調変換関数で変更する前の、fBM を使用したドメインワーピングの元画像になります。

変換する前の元画像
変換する前の元画像

二階調化#

二階調化
二階調化

二階調化はある範囲を基準に 0 か 1 かの値に変換します。これは GLSL ではstep関数を使用することで実現できます。デモでは時間によって範囲を変更しています。

二階調化
float converter(float v) {
  float time = abs(mod(0.1 * u_time, 2.0) - 1.0);
  return step(time, v);
}

ポスタリゼーション#

ポスタリゼーション
ポスタリゼーション

ポスタリゼーションは第 1 章でみたように、出力値が階段状で変化します。サンプルコードでは時間によって数段階変化し最大が 8 段階になります。

ポスタリゼーション
float converter(float v) {
  float time = abs(mod(0.1 * u_time, 2.0) - 1.0);
  float n = floor(8.0 * time);
  return (floor(n * v) + step(0.5, fract(n * v))) / n;
}

S 字トーンカーブ#

S字トーンカーブ
S字トーンカーブ

S 字トーンカーブは、S 字型のトーンカーブを利用した変換をします。これは画像のコントラストが上がったような効果をもたらします。S 字型にするためにはsmoothstep関数を使用します。

S字トーンカーブ
float converter(float v) {
  float time = abs(mod(0.1 * u_time, 2.0) - 1.0);
  return smoothstep(0.5 * (1.0 - time), 0.5 * (1.0 + time), v);
}

ガンマ補正#

ガンマ補正
ガンマ補正

ガンマ補正は、pow(x, a)によってxax^aを計算します。トーンカーブはa>1a > 1 のとき上に膨らんだ曲線になり画像の明るさは増します。逆に0<a<10 < a < 1 のときは下に膨らんだ曲線になり全体的に明るさは落ちます。このようにべき乗を使った明るさ調整をガンマ補正と呼びます。

ガンマ補正
float converter(float v) {
  float time = abs(mod(0.1 * u_time, 2.0) - 1.0);
  return pow(v, time);
}

ソラリゼーション#

ソラリゼーション
ソラリゼーション

ソラリゼーションはサイン波のようなトーンカーブを使用します。これは画像の濃淡の一部分を反転させることにより、ネガ画像とポジ画像が混ざりあったような効果が得られます。

ソラリゼーション
float converter(float v) {
  return 0.5 * sin(4.0 * PI * v + u_time) + 0.5;
}

それぞれのデモの画像処理に関して詳しく知りたい方は下記の書籍をおすすめします。

ブレンディング#

複数の画像からその中間の画像をつくることをブレンディングと呼びます。GLSL ではmix関数を使用することで 2 つの画像をブレンディングすることができます。サンプルコードは次のようになります。

ブレンディング
vec3 blend(float a, float b) {
  float time = abs(mod(0.1 * u_time, 2.0) - 1.0);
  vec3[2] col2 = vec3[](
    vec3(a, a, 1.0), // aの値を青と白の中間色に変換
    vec3(0.0, b, b) // bの値を黒と緑の中間色に変換
  );
  return mix(
    col2[0],
    col2[1],
    smoothstep(0.5 - 0.5 * time, 0.5 + 0.5 * time, b / (a + b))
    );
}
 
void main() {
  vec2 pos = gl_FragCoord.xy / min(u_resolution.x, u_resolution.y);
  pos = 10.0 * pos + u_time;
  float a = warp21(pos, 1.0);
  float b = warp21(pos + 10.0, 1.0);
  vec3 col = blend(a, b);
 
  fragColor = vec4(col, 1.0);
}

ここでは、ドメインワーピングを使用した素材を 2 つ用意してます。1 つは 10.0 ずらしておきます。

blend関数をみてみると、col2[0]には a の値を青と白の中間色に変換した結果が入っており、col2[1]には b の値を黒と緑の中間色に変換した結果が入っています。これをmix関数で補間します。また、mix関数の第 3 引数では、a と b の値の比に応じて補間しており、a の比重が大きいほどcol2[0]の色が強く出ます。

ブレンディング
ブレンディング

集合演算#

与えられた集合に対し、その和集合や補集合、共通部分をとる操作を**集合演算(ブーリアン演算)**と呼びます。集合演算は下図に示すベン図と呼ばれる模式図によって表すことができます。

ベン図
ベン図

画像を二値化すると値が 0 か 1 の部分に分けられます。真偽値と論理演算を使うことによって、2 つの二値画像の集合演算ができます。コードで書くと次のようになります。

集合演算
vec2 f = vec2(warp21(pos, 1.0), warp21(pos + 10.0, 1.0));
f -= 0.5; // 値を[-0.5, 0.5]にずらす
 
vec4 x;
bvec2 b = bvec2(step(f, vec2(0))); // 0より小さければ真、そうでなければ偽
x = vec4(
  b[0] && b[1],  // 共通部分
  b[0] && !b[1], // 差集合
  !b[0] && b[1], // 差集合
  !(b[0] || b[1]) // 和集合の補集合
);
 
vec3[4] col4 = vec3[](
  vec3(1.0, 0.0, 0.0), // 赤
  vec3(0.0, 1.0, 0.0), // 緑
  vec3(0.0, 0.0, 1.0), // 青
  vec3(1.0, 1.0, 0.0) // 黄
);
 
for (int i = 0; i < 4; i++) {
  fragColor.rgb += x[i] * col4[i];
}

bvec2 bstep関数によって 0 と 1 に分け、それを真偽値に型変換しています。ここで 0 は偽(false)、1 は真(true)となります。

xでは真偽値に対する論理演算を行っています。論理積&&は共通部分に、論理和||は和集合に、論理否定!は補集合に該当します。この論理演算を行った真偽値を 0 と 1 の数値に戻して、それに色を対応させて塗り分けています。

色の塗り分けは、上記のベン図と同様に、共通部分は赤、差集合はそれぞれ青と緑、和集合の補集合は黄色に塗り分けます。fBM とパーリンノイズのドメインワーピングに対して上記の集合演算をした結果が下図になります。

集合演算
集合演算

次回リンク#

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

参考書籍#

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
#数学
#勉強ログ
「リアルタイムグラフィックスの数学」勉強ログ - 第7章 距離とSDF
image

はじめに

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

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

2 次元 SDF

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

円の SDF

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

円のSDF

Meta Data
公開日:2025-10-05
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
#数学
#勉強ログ
「リアルタイムグラフィックスの数学」勉強ログ - 第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