GLSL ray marching

- フラグメントシェーダでレイマーチング -

あなた誰なの

  • すぎもと まさひろ (doxas)
  • twitter: @h_doxas
  • WebGL 開発支援サイト wgld.org 管理人

doxas

どんな仕事をしてきたの

どんな仕事をしてきたの

業務で開発に携わった経験無し。

いわゆる趣味でプログラムを書いているサンデープログラマ。

どんな仕事をしてきたの

業務で開発に携わった経験無し。

いわゆる趣味でプログラムを書いているサンデープログラマ。

今も、開発とは直接関係のない業界で働いています。

突っ込みどころ満載かもしれませんがよろしくお願いします。

今日のテーマ

GLSL ray marching

WebGL で GLSL を利用して ray marching (レイマーチング) を実装してみよう。

ちょっと待て

ちょっと待て

GLSL ?

ちょっと待て

GLSL ?

ray marching ?

ちょっと待て

GLSL ?

ray marching ?

なんのこっちゃ!?

GLSL とは

GLSL

OpenGL Shading Language のこと。

一般にジーエルエスエルと呼ばれているシェーダを記述するための言語。

名前からもわかるように OpenGL 系で利用されており、複数のバージョンがある。

WebGL は OpenGL ES 用の ver 1.0 を利用。

ray marching とは

ray marching

いわゆるレイトレーシングの技法のうちのひとつ。

レイの長さを少しずつ伸ばしながら、オブジェクトとの衝突判定を行いレンダリングする方法。

ray marching とは

ray marching

いわゆるレイトレーシングの技法のうちのひとつ。

レイの長さを少しずつ伸ばしながら、オブジェクトとの衝突判定を行いレンダリングする方法。

どういうこっちゃ!?

そもそもレイトレーシングって……

そもそもレイトレーシングって……

レイトレーシングといっても様々な技法・実装がある。

原始的なとある方法の場合、たとえば現実世界とは逆の経路で光の軌跡を辿る。

そもそもレイトレーシングって……

レイトレーシングといっても様々な技法・実装がある。

原始的なとある方法の場合、たとえば現実世界とは逆の経路で光の軌跡を辿る。

目の位置から視線を追っていくことで、オブジェクトがどのように見えるはずなのかを調べる。

そもそもレイトレーシングって……

レイトレーシングといっても様々な技法・実装がある。

原始的なとある方法の場合、たとえば現実世界とは逆の経路で光の軌跡を辿る。

目の位置から視線を追っていくことで、オブジェクトがどのように見えるはずなのかを調べる。

よくわからんから図解!

現実世界の光の軌道イメージ

現実世界の光の軌道イメージ

光がオブジェクトに反射して届く。

レイトレーシングのイメージ図

レイトレーシングのイメージ図

光の軌道を逆に辿っていき、どのように見えるか計算する。

レイマーチングの場合は……

レイマーチングの場合は……

レイマーチングの場合もいくつかの実装方法がある。

レイマーチングの場合は……

レイマーチングの場合もいくつかの実装方法がある。

今回紹介する方法の場合、レイを飛ばすところまでは先ほどと同じ。

レイを段階的に伸ばしていきオブジェクトとの衝突を判定する。

レイマーチングの場合は……

レイマーチングの場合もいくつかの実装方法がある。

今回紹介する方法の場合、レイを飛ばすところまでは先ほどと同じ。

レイを段階的に伸ばしていきオブジェクトとの衝突を判定する。

伸ばす単位はオブジェクトまでの最短距離

レイマーチング図解 1

レイマーチング図解 1

レイマーチング図解 2

レイマーチング図解 2

レイマーチング図解 3

レイマーチング図解 3

レイマーチング図解 4

レイマーチング図解 4

レイマーチング図解 5

レイマーチング図解 5

レイマーチング図解 6

レイマーチング図解 6

要するに?

要するに?

レイマーチングでは、この最短距離を指標としてオブジェクトを認識する。

要するに?

レイマーチングでは、この最短距離を指標としてオブジェクトを認識する。

最短距離が十分に小さくなった時点でレイの先端が衝突しているとみなす。

要するに?

レイマーチングでは、この最短距離を指標としてオブジェクトを認識する。

最短距離が十分に小さくなった時点でレイの先端が衝突しているとみなす。

どうやって最短距離を測る!?

ここで登場するのが……

ここで登場するのが……

distance function !

ここで登場するのが……

distance function !

何それ、なんかカッコいい!

distance function とは

distance function

レイの先端とオブジェクト間での最短距離を返す関数。

distance function とは

distance function

レイの先端とオブジェクト間での最短距離を返す関数。

この関数を工夫することで、様々な形状を描くことができる。

distance function とは

distance function

レイの先端とオブジェクト間での最短距離を返す関数。

この関数を工夫することで、様々な形状を描くことができる。

つまりここが腕の見せどころ!

distance function とは

distance function

レイの先端とオブジェクト間での最短距離を返す関数。

この関数を工夫することで、様々な形状を描くことができる。

つまりここが腕の見せどころ!

それってすごい複雑なんじゃ……

単純な distance function

球体の場合

単純な distance function

球体の場合

float distFunc(vec3 p){
	return length(p) - sphereSize;
}

単純な distance function

球体の場合

float distFunc(vec3 p){
	return length(p) - sphereSize;
}

引数にはレイの先端の座標を渡す。

単純な distance function

球体の場合

float distFunc(vec3 p){
	return length(p) - sphereSize;
}

引数にはレイの先端の座標を渡す。

すごくシンプルだ!

ray marching のメリット

ray marching のメリット

  • distance function は比較的少ないコードで書ける

ray marching のメリット

  • distance function は比較的少ないコードで書ける
  • ポリゴンという概念がないため曲線を含むオブジェクトも比較的簡単に描ける

ray marching のメリット

  • distance function は比較的少ないコードで書ける
  • ポリゴンという概念がないため曲線を含むオブジェクトも比較的簡単に描ける
  • 影や反射といった複雑な描画も行うことが可能

ray marching のメリット

  • distance function は比較的少ないコードで書ける
  • ポリゴンという概念がないため曲線を含むオブジェクトも比較的簡単に描ける
  • 影や反射といった複雑な描画も行うことが可能

やだ……いいことづくめやん……

ray marching のデメリット

ray marching のデメリット

  • モデリングツールなどとの連携が難しい

ray marching のデメリット

  • モデリングツールなどとの連携が難しい
  • 難易度が高い

ray marching のデメリット

  • モデリングツールなどとの連携が難しい
  • 難易度が高い
  • 重い!

ray marching のデメリット

  • モデリングツールなどとの連携が難しい
  • 難易度が高い
  • 重い!

いいことばっかりでもなかった!

とは言え……

とは言え……

まずはやってみよう!

HTML と javascript

ソースは wgld.org の GLSL カテゴリで使っているものと同じものを利用。

参考:wgld.org | GLSL |  http://wgld.org/d/glsl/

HTML と javascript

ソースは wgld.org の GLSL カテゴリで使っているものと同じものを利用。

参考:wgld.org | GLSL |  http://wgld.org/d/glsl/

描画はフラグメントシェーダのみで行う。

HTML と javascript

ソースは wgld.org の GLSL カテゴリで使っているものと同じものを利用。

参考:wgld.org | GLSL |  http://wgld.org/d/glsl/

描画はフラグメントシェーダのみで行う。

今回はフラグメントシェーダのソースを見ながら解説。

シェーダの基本となるコード

シェーダの基本となるコード

precision mediump float;

uniform float time;       // 経過時間(秒単位)
uniform vec2  mouse;      // マウスカーソル座標(0 to 1)
uniform vec2  resolution; // スクリーンサイズ

void main(void){
	// 処理対象ピクセルの位置を正規化する
	vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
	
	// 赤・緑 の要素としてそのまま出力
	gl_FragColor = vec4(p.xy, 0.0, 1.0);
}

シェーダの基本となるコード

実行結果 1

実行結果 1

シェーダ内で ray (レイ)を定義

シェーダ内で ray (レイ)を定義

void main(void){
	vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
	
	// カメラ
	vec3 cPos = vec3(0.0, 0.0, 3.0);  // カメラの位置
	vec3 cDir = vec3(0.0, 0.0, -1.0); // 視線の向き
	vec3 cUp  = vec3(0.0, 1.0, 0.0);  // カメラの上方向
	vec3 cSide = cross(cDir, cUp);    // 外積で横方向を算出
	float targetDepth = 0.1;          // フォーカスする深度
	
	// レイ
	vec3 ray = normalize(cSide * p.x + cUp * p.y + cDir * targetDepth);
	
	// 奥行きを青として出力
	gl_FragColor = vec4(ray.xy, -ray.z, 1.0);
}

シェーダ内で ray (レイ)を定義

実行結果 2

実行結果 2

レイマーチングの基本

レイマーチングの基本

カメラとレイの実装ができたら、これをベースに distance function を使ったレイマーチングを実装する。

レイマーチングの基本

カメラとレイの実装ができたら、これをベースに distance function を使ったレイマーチングを実装する。

レイマーチングは徐々にレイを伸ばしていく処理なので……

レイマーチングの基本

カメラとレイの実装ができたら、これをベースに distance function を使ったレイマーチングを実装する。

レイマーチングは徐々にレイを伸ばしていく処理なので……

シェーダ内にループ構造を作る必要がある。

レイマーチングの基本

カメラとレイの実装ができたら、これをベースに distance function を使ったレイマーチングを実装する。

レイマーチングは徐々にレイを伸ばしていく処理なので……

シェーダ内にループ構造を作る必要がある。

これをマーチング ループと呼ぶ。

マーチングループと distance function

マーチングループと distance function

ループ内では、徐々にレイの長さを伸ばしながら distance function を呼び出す。

マーチングループと distance function

ループ内では、徐々にレイの長さを伸ばしながら distance function を呼び出す。

distance function は最短距離を返す関数なので……

マーチングループと distance function

ループ内では、徐々にレイの長さを伸ばしながら distance function を呼び出す。

distance function は最短距離を返す関数なので……

レイの先端とオブジェクトの間の最短距離が十分に小さくなったら、レイとオブジェクトが衝突したとみなす。

球体のサイズと distance function

const float sphereSize = 1.0;

float distFunc(vec3 p){
	return length(p) - sphereSize;
}

球体のサイズは 1.0 を指定。

distance function は、vec3 型の値を受け取り、最短距離を float で返す。

void main(void){
	vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
		// 中略
	vec3 ray = normalize(cSide * p.x + cUp * p.y + cDir * targetDepth);
	
	// マーチングループ
	float distance = 0.0;
	float distLen = 0.0;
	vec3  distPos = cPos;
	for(int i = 0; i < 16; i++){
		distance = distFunc(distPos);
		distLen += distance;
		distPos = cPos + distLen * ray;
	}
	
	// 衝突チェック
	if(abs(distance) < 0.001){
		gl_FragColor = vec4(vec3(1.0), 1.0); // 衝突なら白
	}else{
		gl_FragColor = vec4(vec3(0.0), 1.0); // 未衝突なら黒
	}
}

マーチングループと distance function

実行結果 3

実行結果 3

法線の算出

法線の算出

このままだと単なる塗り潰し状態なので法線を求めてみる。

法線の算出

このままだと単なる塗り潰し状態なので法線を求めてみる。

法線が求まれば、当然ライティングも可能になる。

法線の算出

このままだと単なる塗り潰し状態なので法線を求めてみる。

法線が求まれば、当然ライティングも可能になる。

もちろん平行光源も点光源も実装可能。

法線の算出

このままだと単なる塗り潰し状態なので法線を求めてみる。

法線が求まれば、当然ライティングも可能になる。

もちろん平行光源も点光源も実装可能。

でもどうやって?

distance function と法線

distance function と法線

法線を求めるのにも distance function を使う。

distance function と法線

法線を求めるのにも distance function を使う。

ほんの少しだけレイをずらして、その差分を見ることで勾配を計測。

distance function と法線

法線を求めるのにも distance function を使う。

ほんの少しだけレイをずらして、その差分を見ることで勾配を計測。

勾配をもとに法線を算出。

法線を求める関数を定義

vec3 genNormal(vec3 p){
	float d = 0.0001;
	return normalize(vec3(
		distFunc(p + vec3(  d, 0.0, 0.0)) - distFunc(p + vec3( -d, 0.0, 0.0)),
		distFunc(p + vec3(0.0,   d, 0.0)) - distFunc(p + vec3(0.0,  -d, 0.0)),
		distFunc(p + vec3(0.0, 0.0,   d)) - distFunc(p + vec3(0.0, 0.0,  -d))
	));
}

微妙にレイをずらした状態で、複数回 distance function を呼び出す。

法線を算出しライティング

実行結果 4

実行結果 4

同じオブジェクトの複製

同じオブジェクトの複製

distance function にひと工夫すると、同一形状のオブジェクトを複製することができる。

同じオブジェクトの複製

distance function にひと工夫すると、同一形状のオブジェクトを複製することができる。

GLSL のビルトイン関数 mod を利用する。

同じオブジェクトの複製

vec3 trans(vec3 p){
	return mod(p, 4.0) - 2.0;
}

float distFunc(vec3 p){
	return length(trans(p)) - 1.0;
}

オブジェクト複製用の trans 関数を定義。

同じオブジェクトの複製

vec3 trans(vec3 p){
	return mod(p, 4.0) - 2.0;
}

float distFunc(vec3 p){
	return length(trans(p)) - 1.0;
}

オブジェクト複製用の trans 関数を定義。

distance function に入ってきた引数をそのまま渡す。

同じオブジェクトの複製

実行結果 5

実行結果 5

様々な distance function

様々な distance function

distance function を工夫することで、様々な図形の描画が可能。

様々な distance function

distance function を工夫することで、様々な図形の描画が可能。

球以外にどんなものが?

キューブ型

キューブ型

角を取ったキューブ

角を取ったキューブ

リング型トーラス

リング型トーラス

四角形型トーラス

四角形型トーラス

様々な distance function

様々な distance function

複数の異なる形状のオブジェクトを同時に描画することもできる。

様々な distance function

複数の異なる形状のオブジェクトを同時に描画することもできる。

座標変換や形状変化もでき……

様々な distance function

複数の異なる形状のオブジェクトを同時に描画することもできる。

座標変換や形状変化もでき……

テクスチャを投影したり、影を描いたりすることも!

異なる形状のオブジェクトを同時に描く

異なる形状のオブジェクトを同時に描く

オブジェクト同士を排他的に描く

オブジェクト同士を排他的に描く

異なるオブジェクトをスムーズに結合させる

異なるオブジェクトをスムーズに結合させる

リング型トーラスをねじってみる

リング型トーラスをねじってみる

投影や影の表現

投影や影の表現

レイマーチングに挑戦したい!

レイマーチングに挑戦したい!

レイマーチングを勉強してみたいと思ったのなら……

ぜひチャレンジしてみてもらいたい!

レイマーチングに挑戦したい!

レイマーチングを勉強してみたいと思ったのなら……

ぜひチャレンジしてみてもらいたい!

なんか資料ないの!?

参考資料

ray marching の天才 Inigo Quilez 氏のサイト

http://www.iquilezles.org/

デモシーンの情報サイト demoscene.jp

全能感UP! GLSLで進めレイマーチング ≪ demoscene.jp

参考資料

もはや神の領域? な作品がたくさん

shadertoy

レイマーチングを用いたものも投稿されてます

GLSL Sandbox

最後に……

最後に……

私でもできたので、たぶん皆さんにもできます。

最後に……

私でもできたので、たぶん皆さんにもできます。

みんなでレイマーチングを楽しみましょう!

最後に……

私でもできたので、たぶん皆さんにもできます。

みんなでレイマーチングを楽しみましょう!

あと業界に友達いないので友達になってください (笑)

ありがとうございました。