Slide 1

Slide 1 text

距離関数を極める! Sho HOSODA (@gam0022)

Slide 2

Slide 2 text

自己紹介 @gam0022(がむ)/ Sho HOSODA
 ● ゲーム会社でグラフィックスエンジニア
 ● 昨日のShader Jamsで解説
 Transcendental Cube by @gam0022 & @sadakkey 
 2nd place in GLSL Graphics Compo at SESSIONS 2023 
 新千歳空港国際アニメーション映画祭 
 NEW CHITOSE 30 Seconds部門 入選 
 CGWORLD vol.266(2020年10月号)寄稿 
 デモシーンを支えるプロシージャル技術 


Slide 3

Slide 3 text

この発表で紹介すること ▪ Twitter(現X)でネタを募集させていただきました🙏 ▫ 距離関数やレイマーチングのハマりそうな罠 ▫ あまり知られていなそうな有用なテクニックや小ネタ

Slide 4

Slide 4 text

距離関数とは? 座標pから物体表面への最短距離を返す関数 float sdSphere(vec3 p, float s) { return length(p) - s; } https://iquilezles.org/articles/distfunctions/

Slide 5

Slide 5 text

直感とは逆の操作になる例(平行移動) 右(X+)方向に a だけ平行移動したい場合 // 動かしたい方向と逆方向にpを平行移動 p.x -= a; a

Slide 6

Slide 6 text

直感とは逆の操作になる例(回転) a * PI だけZ軸に回転したい場合 // 回転したい向きと逆方向にpを回転 p.xy = rot2D(-a * PI) * p.xy;

Slide 7

Slide 7 text

なぜ直感と逆になるのか? ▪ オブジェクトそのものではなく、空間を並行移動しているから ▪ 空間が「左」に動くと、オブジェクトが相対的に逆方向の「右」に動く

Slide 8

Slide 8 text

直感とは逆の操作になる例(スケール) ※要注意 s倍にスケールしたい float map(vec3 p) { // s倍にスケールするなら、 // pを1/s倍し、さらに距離関数をs倍する float d = primitive(p / s) * s; return d; } 謎の * s🤔 ▪ p/sの空間は、元の空間pよりも スケールが1/s倍に小さくなっている ▪ 元の空間pのスケールに戻すために 距離関数をs倍する必要がある

Slide 9

Slide 9 text

直感とは逆の操作になる例(スケール) ※要注意 あっぷるてぃー🍎☕ さん提供🙏 


Slide 10

Slide 10 text

符号付き(signed)の距離関数 符号付き(signed)の距離関数 float sdBox(vec3 p, vec3 b) { vec3 q = abs(p) - b; return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0); } 負の領域 等高線を可視化

Slide 11

Slide 11 text

符号なし(UN signed)の距離関数 負の領域の処理を消すと、unsignedなudBoxになる float udBox(vec3 p, vec3 b) { vec3 q = abs(p) - b; return length(max(q, 0.0)); } 📝通常のレイマーチングでは 負の領域がなくてもOK


Slide 12

Slide 12 text

罠: Subtractionにはsignedが必要 // d1からd2を切り抜く float opSubtraction(float d1, float d2) { return max(d1, -d2); } signedなら 符号を反転できる boolean演算の Subtraction(差集合)

Slide 13

Slide 13 text

罠: 半透明のレイマーチングにはsignedの距離関数が必要 Refraction & Hue shift by setchi https://www.shadertoy.com/view/MdVfWy vec2 intersect(vec3 ro, vec3 ray) { float t = 0.0; for (int i = 0; i < 256; i++) { // 距離関数にabsをつけてオブジェクトの内部もレイが進めるようにする float res = abs(map(ro + ray * t)); if (res < 0.005) return vec2(t, res); t += res; } return vec2(-1.0); }

Slide 14

Slide 14 text

罠: 衝突点からレイを反射・屈折にはオフセットが必要 Refraction & Hue shift by setchi https://www.shadertoy.com/view/MdVfWy bool into = dot(-ray, nor) > 0.0; nor = into ? nor : -nor; eta = into ? 1.0 / eta : eta; ro = pos - nor * OFFSET; ray = refract(ray, nor, eta); kiNaNkomoti さん提供🙏 


Slide 15

Slide 15 text

exactとNOT exactな距離関数の比較 「exact」なsdBox 「NOT exact」なsdBox float sdBox_NOT_exact(vec3 p, vec3 b) { vec3 q = abs(p) - b; return max(max(q.x, q.y), q.z); } float sdBox(vec3 p, vec3 b) { vec3 q = abs(p) - b; return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0); } 角の距離が不正確👀

Slide 16

Slide 16 text

テクニック: 角を丸めるRounding sdBox(p, b) sdBox(p, b) - r rだけ外側に 境界を移動

Slide 17

Slide 17 text

罠: 角を丸めるRoundingのテクニックはNOT exactだと使えない sdBox_NOT_exact(p, b) sdBox_NOT_exact(p, b) - r 角が丸まらない🤯

Slide 18

Slide 18 text

罠: exactな距離関数に定数を足すとNOT exactになる sdBox(p, b) + r 角の距離が不正確👀 @suzuki_ith さん提供🙏 


Slide 19

Slide 19 text

テクニック: modによる繰り返し vec3 a = vec3(2); p = mod(p, a) - 0.5 * a; Kamoshika Shader Jams SESSIONS 2024


Slide 20

Slide 20 text

罠: modによる繰り返しにランダムな変形をすると レイが突き抜けてアーティファクトが起きる 画像出典: https://qiita.com/ukeyshima/items/221b0384d39f521cad8f 想定した結果
 実際の結果😭 


Slide 21

Slide 21 text

罠: modによる繰り返しにランダムな変形をすると レイが突き抜けてアーティファクトが起きる ランダムなし
 ランダムあり


Slide 22

Slide 22 text

罠: modによる繰り返しにランダムな変形をすると レイが突き抜けてアーティファクトが起きる ランダムあり
 ランダムあり + 対策版 グリッドを飛び越えない ように制限

Slide 23

Slide 23 text

罠: modによる繰り返しにランダムな変形をすると レイが突き抜けてアーティファクトが起きる グリッドを飛び越えないように制限する実装 by ukeyshima float distance=0.0; float rLen=0.0; vec3 rPos=origin; vec3 color=vec3(0.1); for(int i=0;i<100;i++){ distance=distFunc(rPos); if(abs(distance)<0.01){ if(distance==floorDistFunc(rPos)){ color=vec3(0.3,0.3,0.7); } break; } float t1=(1.0-fract(rPos.z))/ray.z; vec3 d1=rPos+t1*ray; float t2=(1.0-fract(rPos.x))/ray.x; vec3 d2=rPos+t2*ray; float t3=(-fract(rPos.x))/ray.x; vec3 d3=rPos+t3*ray; float t4=(-fract(rPos.z))/ray.z; vec3 d4=rPos+t4*ray; if(ray.z>0.0 && d1.x>=floor(rPos.x) && d1.x<=floor(rPos.x)+1.0 && abs(distance)>=length(t1*ray)){ rLen+=length(t1*ray)+0.01; }else if(ray.x>0.0 && d2.z>=floor(rPos.z) && d2.z<=floor(rPos.z)+1.0 && abs(distance)>=length(t2*ray)){ rLen+=length(t2*ray)+0.01; }else if(ray.x<0.0 && d3.z>=floor(rPos.z) && d3.z<=floor(rPos.z)+1.0 && abs(distance)>=length(t3*ray)){ rLen+=length(t3*ray)+0.01; }else if(ray.z<0.0 && d4.x>=floor(rPos.x) && d4.x<=floor(rPos.x)+1.0 && abs(distance)>=length(t4*ray)){ rLen+=length(t4*ray)+0.01; }else{ rLen+=distance; } rPos=origin+rLen*ray; }

Slide 24

Slide 24 text

罠: modによる繰り返しにランダムな変形をすると レイが突き抜けてアーティファクトが起きる グリッドを飛び越えないように制限する実装 by iq Cubescape by iq https://www.shadertoy.com/view/Msl3Rr 各グリッドごとに レイマーチング

Slide 25

Slide 25 text

vec3 trace( vec3 ro, in vec3 rd, in float tmin, in float tmax ) { ro += tmin*rd; vec2 pos = floor(ro.xz); vec3 rdi = 1.0/rd; vec3 rda = abs(rdi); vec2 rds = sign(rd.xz); vec2 dis = (pos-ro.xz+ 0.5 + rds*0.5) * rdi.xz; vec3 res = vec3( -1.0 ); // traverse regular grid (in 2D) vec2 mm = vec2(0.0); for( int i=0; i<28; i++ ) { vec3 cub = mapH( pos ); #if 1 vec2 pr = pos+0.5-ro.xz; vec2 mini = (pr-0.5*rds)*rdi.xz; float s = max( mini.x, mini.y ); if( (tmin+s)>tmax ) break; #endif // intersect box vec3 ce = vec3( pos.x+0.5, 0.5*cub.x, pos.y+0.5 ); vec3 rb = vec3(0.3,cub.x*0.5,0.3); vec3 ra = rb + 0.12; vec3 rc = ro - ce; float tN = maxcomp( -rdi*rc - rda*ra ); float tF = maxcomp( -rdi*rc + rda*ra ); if( tN < tF )//&& tF > 0.0 ) { // raymarch float s = tN; float h = 1.0; for( int j=0; j<24; j++ ) { h = udBox( rc+s*rd, rb, 0.1 ); s += h; if( s>tF ) break; } if( h < (surface*s*2.0) ) { res = vec3( s, cub.yz ); break; } } // step to next cell mm = step( dis.xy, dis.yx ); dis += mm*rda.xz; pos += mm*rds; } res.x += tmin; return res; }

Slide 26

Slide 26 text

罠: modによる繰り返しにランダムな変形をすると レイが突き抜けてアーティファクトが起きる ▪ 距離関数に適当な係数を掛けることでも緩和できる float d = map(p) * 0.5; pros 実装が楽 cons 動作が遅くなる

Slide 27

Slide 27 text

罠: modによる繰り返しにランダムな変形をすると レイが突き抜けてアーティファクトが起きる Pentan さん提供🙏 
 ブタジエン さん提供🙏 


Slide 28

Slide 28 text

罠: NOT constantな変形をすると レイが突き抜けてアーティファクトが起きる LiveCoding by gam0022 「WebGL 総本山 + normalize.fm あわせて 13周年感謝祭」夜の部 Shader Jams float sdN(vec3 p, float z) { rot(p.xy, -0.07 * TAU); if (p.x < 0.) p.y = -p.y; // X軸方向にミラーリング p.x = abs(p.x); float w = 0.13; float h = 0.07; float s = 4.; float a = w / h / s * p.y; return min( sdBox(p, vec3(0.2, h, z)), // pや箱のサイズを座標に応じて変化することでskew sdBox(p - vec3(0.25 - a, h * (s - 1.), 0), vec3(w - a, h * s, z))); }

Slide 29

Slide 29 text

罠: NOT constantな変形をすると レイが突き抜けてアーティファクトが起きる なぜ? skewすることで、 空間が歪んで正しい距離関数ではなくなる レイが突き抜けてオブジェクトの一部が欠ける

Slide 30

Slide 30 text

罠: NOT constantな変形をすると レイが突き抜けてアーティファクトが起きる float d = map(p) * 0.5; 対策版 pros 実装が楽 cons 動作が遅くなる 解決策1


Slide 31

Slide 31 text

罠: NOT constantな変形をすると レイが突き抜けてアーティファクトが起きる 解決策2 exactな距離関数を頑張って実装する ▪ 例 ▫ udTriangleとopExtrusionを組み合わせて 直角三角形にする等 ちょっと実装が大変

Slide 32

Slide 32 text

罠: NOT constantな変形をすると レイが突き抜けてアーティファクトが起きる https://iquilezles.org/articles/distance/ あっぷるてぃー🍎☕ さん さはらさん 提供🙏 


Slide 33

Slide 33 text

テクニック: Log-polar Mapping in 3D / Log-spherical Mapping https://www.osar.fr/notes/logspherical/ Kamoshika Shader Jams SESSIONS 2024 Log-polar Mapping in 3Dの例 Kamoshima さん提供🙏 


Slide 34

Slide 34 text

罠: レイの算出で外積を使うときはゼロベクトルに注意 Kamoshima さん提供🙏 


Slide 35

Slide 35 text

テクニック: コンパイル高速化の#define ZERO GLSLでは [unroll] と [loop] を指定できない (コンパイラが自動判断する) ▪ [unroll] ループを展開 ▫ コードが肥大化 ▫ コンパイル時間の増加になる ▪ [loop] ループと展開しない Raymarching - Primitives by iq https://www.shadertoy.com/view/Xds3zN

Slide 36

Slide 36 text

テクニック: コンパイル高速化の#define ZERO Raymarching - Primitives by iq https://www.shadertoy.com/view/Xds3zN #define ZERO (min(iFrame,0)) // https://iquilezles.org/articles/nvscene2008/rwwtt.pdf float calcAO( in vec3 pos, in vec3 nor ) { float occ = 0.0; float sca = 1.0; for( int i=ZERO; i<5; i++ ) { float h = 0.01 + 0.12*float(i)/4.0; float d = map( pos + h*nor ).x; occ += (h-d)*sca; sca *= 0.95; if( occ>0.35 ) break; } return clamp( 1.0 - 3.0*occ, 0.0, 1.0 ) * (0.5+0.5*nor.y); }

Slide 37

Slide 37 text

テクニック: コンパイル高速化の#define ZERO ループ条件に動的な値を含めることで [unroll] を防止している Raymarching - Primitives by iq https://www.shadertoy.com/view/Xds3zN #define ZERO (min(iFrame,0)) for( int i=ZERO; i<5; i++ ) {}

Slide 38

Slide 38 text

まとめ ▪ 距離関数の罠は多い ▪ 空間を操作しているという基本を思い出すと、解決の糸口になるかも

Slide 39

Slide 39 text

No content