
2025/12/09 1:44
A series of tricks and techniques I learned doing tiny GLSL demos
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
本記事では、Moonlight、Entrance 3、Archipelago、および Cutie の4つの超小型 GLSL デモを紹介し、それぞれが 512 文字に制限される理由を説明しています。理由は、芸術的意図、教育目的、ポートフォリオの一貫性、および Mastodon の toot サイズ制限との互換性です。各デモは最小限のコードで特定のグラフィックストリックを詰め込んでいます。
- Moonlight は、逆平方積分から導出されたレイマーチステップごとの単純な (1/d) ボリュメトリターム((v = \Delta t/(d_n d_{n+1}) \rightarrow 1/d_n))を使用します。また、透明度は (d = A,|d| + B) でモデル化できると指摘しており、ここで (A) が吸収を制御し、(B) が浸透を許可します。
- Entrance 3 は距離関数に L‑∞ ノルムを採用し、固体に衝突した後にレイを光源へ向け直すとともに、Snapdragon/Adreno および Imagination/PowerVR デバイスでのモバイル GPU バグを示します。
- Archipelago は (w=\exp(\sin x)) を用いたドメインワーピング FBM とノイズシフト (x -= w\cos x) により、無限に生成される手続き的な日本を作成します。
- Cutie は球体をスムーズミンで丸みのある円錐へマージし、簡易逆運動学で脚部をアニメーション化し、深度マップではなく反復回数でシェーディングします。
Sympy などの実用ツールは、等角投影((-\frac{\sqrt{3}}{3}(1,1,1)))と二重視点投影((-\frac{1}{4}(\sqrt6,2,\sqrt6)))カメラ設定用の回転行列を計算するために使用されます。512 文字制限は Mastodon の toot サイズに合わせており、創造性と実現可能性をバランスさせ、学習リソース、ポートフォリオ展示、および迅速な共有のためにサイズ制限を課すツールチェーンへの影響力となるような簡潔で高品質なコードアーティファクトを提供します。
本文
Tiny GLSL デモ – 512文字で旅する
ここ2か月、私は小さな GLSL デモを書き続けていました。
最初の作品 Red Alp は別記事にまとめていますので、新規読者はそちらからご覧ください。
本稿ではその後に続く4つのデモ ― Moonlight、Entrance 3、Archipelago、そして Cutie を取り上げます。
各作品の重要ポイントを抜粋し、詳細を再解説するのは避けます。
Moonlight
デモ(460文字)
// Moonlight [460] by bµg // License: CC BY-NC-SA 4.0 void main(){ vec3 o,p,u=vec3((P+P-R)/R.y,1),Q; Q++; for(float d,a,m,i,t;i++<1e2;p=t<7.2?Q:vec3(2,1,0), d=abs(d)*.15+.1,o+=p/m+(t>9.?d=9.,Q:p/d), t+=min(m,d)) for(p=normalize(u)*t,p.z-=5e1,m=max(length(p)-1e1,.01), p.z+=T,d=5.-length(p.xy*=mat2(cos(t*.2+vec4(0,33,11,0)))),a=.01; a<1.;a+=a) p.xz*=mat2(8,6,-6,8)*.1, d-=abs(dot(sin(p/a*.6-T*.3),p-p+a)), m+=abs(dot(sin(p/a/5.),p-p+a/5.)); o/=4e2; O=vec4(tanh(mix(vec3(-35,-15,8),vec3(118,95,60), o-o*length(u.xy*.5))*.01),1); }
学んだこと
Red Alp では、雲と霧を扱うボリュームレイマーチングに重い吸収/放射パイプラインを使用しました。
よりシンプルで物理的にも根拠のある方法としては、各ステップごとに
を加える手法があります。ここで 1/d
d はレイ上現在位置の密度です。
なぜ 1/d
が機能するか
1/d放射体が全方向に均等に光子を発生させると考えます。
光子密度は距離の逆二乗((1/r^2))で減衰します。レイを 2 点 (d_n, d_{n+1}) の密度で区切った場合、セグメント全体への寄与は
[ v = \Delta t \int_0^1 \frac{1}{( (1-t)d_n + td_{n+1})^2 },dt = \frac{\Delta t}{d_nd_{n+1}}. ]
レイマーチングループでは (\Delta t = d_{n+1}) になるので
[ v = \frac{1}{d_n}. ]
つまり、各イテレーションの寄与は「現在密度の逆数」だけで済むわけです。複雑な吸収モデルを使う必要はありません。
透明キューブ
固体を通過したい場合は
max(d,.001) を A*abs(d)+B に置き換えます。ここで
≈ 吸収(減衰)A
≈ 基底透過率B
Entrance 3
デモ(465文字)
// Entrance 3 [465] by bµg // License: CC BY-NC-SA 4.0 #define V for(s++;d<l&&s>.001;q=abs(p+=v*s)-45.,b=abs(p+vec3(mod(T*5.,80.)-7.,45.+sin(T*10.)*.2,12))-vec3(1,7,1),d+=s=min(max(p.y,-min(max(abs(p.y+28.)-17.,abs(p.z+12.)-4.),max(q.x,max(q.y,q.z)))),max(b.x,max(b.y,b.z)))) void main(){ float d,s,r=1.7,l=2e2;vec3 b,v=b-.58,q,p= mat3(r,0,-r,-1,2,-1,b+1.4)*vec3((P+P-R)/R.y*20.4,30); V;r=exp(-d*d/1e4)*.2;l=length(v=-vec3(90,30,10)-p);v/=l;d=1.;V; r+=50.*d/l/l; O=vec4(pow(mix(vec3(0,4,9),vec3(80,7,2),r*r)*.01,p-p+.45),1); }
重要ポイント
- L∞ 距離 – キューブはチェビシェフノルムで測るのが最適。SDF を簡素化できます。
- 自己照明 – 最初にレイを固体までマーチした後、光源方向へ再度レイを投げます。そこに到達すればピクセルは点灯します。
- モバイル GPU の落とし穴 – Snapdragon/Adreno は複合
を嫌い、Imagination/PowerVR は連鎖代入を嫌います。for
正射影/斜投影カメラ
Sympy で回転行列を導出しました:
M_iso = √2·√3 [ [ √3 , -1 , √2 ], [ 0 , 2 , √2 ], [-√3 , -1 , √2 ] ] M_dim = (4/√2) [ [ 2 , -1 , √3 ], [ 0 , √6, √2 ], [-2 , -1 , √3 ] ]
レイ方向:
rd_iso = -M_iso * vec3(0,0,1); // → -(√3/3)[1,1,1] rd_dim = -M_dim * vec3(0,0,1); // → -(1/4)[√6,2,√6]
画素レイの原点はスクリーン座標をスケーリングし、同じ行列で変換して微小
eps を足すことで (y>0) を保証します。
Archipelago
デモ(472文字)
// Archipelago [472] by bµg // License: CC BY-NC-SA 4.0 #define r(a)*=mat2(cos(a+vec4(0,11,33,0))), void main(){ vec3 p,q,k; for(float w,x,a,b,i,t,h,e=.1,d=e,z=.001;i++<50.&&d>z;h+=k.y,w=h-d,t+=d=min(d,h)*.8, O=vec4((w>z?k.zxx*e:k.zyz/20.)+i/1e2+max(1.-abs(w/e),z),1)) for(p=normalize(vec3(P+P-R,R.y))*t,p.zy r(1.)p.z+=T+T,p.x+=sin(w=T*.4)*2., p.xy r(cos(w)*e)d=p.y+=4.,h=d-2.3+abs(p.x*.2),q=p,k-=k,a=e,b=.8;a>z;a*=.8, b*=.5)q.xz r(.6)p.xz r(.6)k.y+=abs(dot(sin(q.xz*.4/b),R-R+b)), k.x+=w=a*exp(sin(x=p.x/a*e+T+T)),p.x-=w*cos(x),d-=w; }
コアアイデア
世界は「島」用と「水」用の 2 つのノイズ関数で手軽に生成します。
ポイントは ドメインワーピング を利用することです:
x -= w * cos(x); // ここで w = exp(sin(x))
各 FBM オクターブをその勾配方向にシフトさせることで、より多様な地形が得られます。
Cutie
デモ(602文字)
// Cutie [602] by bµg // License: CC BY-NC-SA 4.0 #define V vec3 #define L length(p #define C(A,B,X,Y)d=min(d,-.2*log2(exp2(X-L-A)/.2)+exp2(Y-L-B)/.2))) #define H(Z)S,k=fract(T*1.5+s),a=V(1.3,.2,Z),b=V(1,.3*max(1.-abs(3.*k-1.),z),Z*.75+3.*max(-k*S,k-1.)),q=b*S,q+=a+sqrt(1.-dot(q,q))*normalize(V(-b.y,b.x,0)),C(a,q,3.5,2.5),C(q,a-b,2.5,2.) void main(){ float i,t,k,z,s,S=.5,d=S; for(V p,q,a,b;i++<5e1&&d>.001;t+=d=min(d,s=L+V(S-2.*p.x,-1,S))-S)) p=normalize(V(P+P-R,R.y))*t,p.z-=5.,p.zy*=mat2(cos(vec4(1,12,34,1))), p.xz*=mat2(cos(sin(T)+vec4(0,11,33,0))),d=1.+p.y, C(z,V(z,z,1.2),7.5,6.),s=p.x<z?p.x=-p.x,z:H(z), s+=H(1.); O=vec4(V(exp(-i/(s>d?1e2:9.))),1); }
取り組みポイント
- スムーズミン(メタボール)で腕 – 各手足は 2 球を
で結合した構造です。smin - IK アニメーション – 単位長の脚部を使った簡易逆運動学により、式を短く保ちました。
- イテレーション数でディテール決定 – レイマーチの反復回数は表面近くになるほど増えるため、これを最終色に活用しました。
なぜ 512 文字?
- 一つか二つの絵画的概念に集中できる → 学習効果が高い。
- 制約があることでプロジェクト完了まで押し切れます。
- Mastodon のトゥートサイズと一致するため、コードをそのまま投稿可能です。
- ポートフォリオ全体の一貫性を保てます ― 文字数制限を変えると今後すべてが揺らいでしまいます。
最新情報は?
Mastodon をフォローするか RSS フィードに登録して、新しい記事やアップデートを受け取ってください。
HackerNews、Lobste.rs、Reddit でも議論に参加しています。