こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) / Twitter )です。
以前からシェーダーを勉強していてある場所にまとめていたのですが、公開しないのも勿体ないと思い
今回は記事化することにしました。
もっと初歩的な記事についてはこちら↓
目次
なぜシェーダーを勉強し始めたか?
プログラムの勉強は見た目を派手にするような実装は少なく
進捗として見たときに地味。
シェーダー周りを勉強すると、見た目は派手だし、ゲームの表現も増すので勉強し始めました。
環境
twiGLを使用。
GLSLは書きやすいって聞いた。
クラシックで書いていくことに。
ちなみに他にはglslsandboxやShaderToyなどが有名だそうだ。
glslsandbox.com
www.shadertoy.com
何の勉強から始めるか
最初からかっこいい3Dのは難しい
notargs (@notargs) / Twitter さんに
まずは「ピクセルシェーダーで数式を解いて絵を描く」間隔をつかむのが大事ですね。 2Dで丸とかのプリミティブな形をつくるところからはじめてみるといいかも
って言われたので素直に、そのあたりから勉強。
最初に参考にしたリンクたち
thebookofshaders.com
setchi.hatenablog.com
作品集
1番最初の作品
precision highp float; uniform vec2 resolution; uniform vec2 mouse; uniform sampler2D backbuffer; void main(){ vec2 r = resolution,p = (2.*gl_FragCoord.xy - r) / min(r.x,r.y); float pct = distance(p,vec2(0.)); vec3 color = vec3(pct); float d = step(0.2,pct); gl_FragColor = vec4(vec3(d),1.0); }
当時のメモ:
Shadertoyとtwiglじゃ若干記述が違う?
fragCoordは現在処理中のピクセルの座標
fragColorは現在処理中のPixelの最終的な色
iResolutionは解像度
step指定されたしきい値を元に0にするか1にするかきめるやつ
(lengthとdistanceの違いも分かってなかった)
2作品目
precision highp float; uniform vec2 resolution; uniform vec2 mouse; uniform float time; uniform sampler2D backbuffer; void main(){ vec2 r = resolution,p = (2.*gl_FragCoord.xy - r) / min(r.x,r.y); float pct = distance(p,vec2(cos(time*4.0)*0.5,0.0)); vec3 color = vec3(pct); float d = step(sin(time*4.0)*0.5+0.7,pct); gl_FragColor = vec4(vec3(d),1.0); }
当時のメモ
distanceってやつを使った。shader toyにはないみたい。→0を指定した場合、意味合い的にはlength(p)とほぼ同じ?
timeも利用。
sinとcosはよくあるやつ。
(とりあえず、time使ってみたかったのよね)
3作品目
precision highp float; uniform vec2 resolution; uniform float time; uniform sampler2D backbuffer; mat2 rotate2d(float _angle){ return mat2(cos(_angle),-sin(_angle), sin(_angle),cos(_angle)); } float square(vec2 p) { return abs(p.x) + abs(p.y); } void main(){ vec2 r = resolution,p = (2.*gl_FragCoord.xy - r) / min(r.x,r.y); float a = sin(time * 5.0) * 0.5 + 0.5; vec2 d = mix(vec2(distance(p,vec2(0.0)),0.7), vec2(square(p* rotate2d(radians(time*180.))),0.5), a); vec3 color = mix(vec3(1), vec3(0), step(d.x, d.y)); gl_FragColor = vec4(color,1.0); }
当時のメモ
線形直線→lerp(mix)を理解
2次元の回転行列をなんとなく理解
(参考にしたシェーダーのマルとシカク変換かっこいいと思ったんだよね)
4作品目
precision highp float; uniform vec2 resolution; uniform float time; uniform sampler2D backbuffer; void main(){ vec2 r=resolution,p=(gl_FragCoord.xy*2.-r)/min(r.x,r.y); float d = distance(p,vec2(0.0)); d = (10.* d)+time*-10.; d = abs(sin(d)); d = step(0.5,d); gl_FragColor = vec4(vec3(d,0.0,0.0),1.0); }
当時のメモ
distance部分はlengthでも良さそう。
今回の重要ポイントはabs(sin(d))
geekestモードを使えば更に短くなると知った。
(某ペルなんとかってゲームを意識してた)
5作品目
precision highp float; #define PI 3.14159 uniform vec2 resolution; uniform float time; uniform sampler2D backbuffer; void main(){ vec2 r=resolution,p=(gl_FragCoord.xy*2.-r)/min(r.x,r.y); float a = atan(p.y, p.x); float d = distance(p,vec2(0.)); if(d > 0.6){ d = abs(cos(a * 5.+time)) + 0.1; } gl_FragColor = vec4((d < 0.6) ? vec3(1,.0,.0):vec3(1.),1); }
当時のメモ
atanの使い方をなんとなく理解。
GLSLのatanはatan2と同じ?
(日章旗的な?なにか)
6作品目
#define PI 3.14159 #define ANGLE 360. precision highp float; uniform vec2 resolution; uniform float time; uniform sampler2D backbuffer; mat2 rotate2d(float _angle){ return mat2(cos(_angle),-sin(_angle), sin(_angle),cos(_angle)); } void main(){ vec2 r=resolution,p=(gl_FragCoord.xy*2.-r)/min(r.x,r.y); p = p*rotate2d(radians(((floor(time*45.)*22.5)))); float b = length(p); b = step(0.4,b); float a = length(p); a = step(a,0.15); float d = max(a,b); float at = atan(p.y, p.x); float c = abs(cos(at * 8.)) + 0.1; float num = ((atan(p.y, p.x) / PI) *0.5)+0.5; float at2 = (floor((num*ANGLE)/11.25))*11.25/ANGLE; d = max(c,d); d = step(0.6,d); d = max(at2,d); gl_FragColor = vec4(vec3(d),1); }
当時のメモ
これまでの知識を色々合わせた。
回転部分、グラデーションをどう実装するか分からなくて悩んだ。
ちゃんと書いて覚えよう。
こう書いたほうが良さそう。
float b = length(p)-0.4;
float a = length(p)-0.15;
float d = max(-a,b);
gl_FragColor = vec4(vec3(step(0.0,d)),1);
(逆に今書けないやw携わってるゲームでローディングを書いてるのがあったので書きたくなって書いてみたんだよね)
7作品目
precision highp float; uniform vec2 resolution; uniform float time; void main(){ vec2 r=resolution,p=(gl_FragCoord.xy*2.-r)/min(r.x,r.y); p.x=(0.5*p.x)+0.5; p.y=(-0.5*p.y)+0.5; p.x+= 1.0 + 0.1 * sin(p.x * 5.0 + time) + 0.1 * sin(p.y * 3.0 + time); p.y+= 1.0 + 0.1 * sin(p.x * 5.0 + time) + 0.1 * sin(p.y * 3.0 + time); float t = abs(step(0.5, fract(p.x)) - step(0.5, fract(p.y))); gl_FragColor = vec4((t>0.)?vec3(0.0,.338,0.220):vec3(0.0),1.); }
当時のメモ
ワープ処理、人気のあれ。
floorはに任意の数の整数をだすやつ。
つまり。floor(4.3) とかだったら 4.0 が取得できる。
そして前回つかったfactは n-floor(n) と同義なので fact(4.3) は 0.3
(某なんとかの刃を意識)
8作品目
#define PI 3.1415926 precision highp float; uniform vec2 resolution; uniform float time; mat2 rotate2d(float _angle){ return mat2(cos(_angle),-sin(_angle), sin(_angle),cos(_angle)); } float polygon(vec2 p, int n, float size){ float a = atan(p.x, p.y) + PI; float r = 2. * PI / float(n); return cos(floor(0.5 + a / r) * r - a) * length(p) - size; } float star(vec2 p, int n, float t, float size){ float a = 2. * PI / float(n) / 2.; float c = cos(a); float s = sin(a); vec2 r = p * mat2(c, -s, s, c); return ( polygon(p, n, size) - polygon(r, n, size) * t ) / (1.- t); } void main(){ vec2 r=resolution,p=(gl_FragCoord.xy*2.-r)/min(r.x,r.y); p*=4.; float y = ((mod(p.y,4.) > 2.)?1.:-1.); p.x += time * y; p = mod(p,2.)-1.; p = p * rotate2d(radians(time* y *45.)); float l = length(p); l = star(p,5,4.,0.5); l= step(0.,l); gl_FragColor = vec4((l<1.)?vec3(1.,1.,204./255.):vec3(1.,204./255.,1.),1.); }
当時のメモ
星型、五角形、繰り返しの理解。
(カービィー的な……?やっぱさ、星ってかわいいじゃん?)
9作品目
precision highp float; uniform vec2 resolution; uniform float time; uniform sampler2D _buffer; float interpolat(float f){ return f * f * f * (f * (6.0 * f - 15.0) + 10.0); } float random(vec2 co){ return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); } vec2 random2(vec2 v){ return vec2(random(v), random(v + vec2(0.5, 0.5))); } float animation(float f){ return sin(f * 6.283 + time * 2.0) * 0.5 + 0.5; } vec2 animation2(vec2 v){ return vec2(animation(v.x), animation(v.y)); } float noise(vec2 p){ vec2 i_uv = floor(p * 8.0); vec2 f_uv = fract(p * 8.0); vec2 v1 = animation2(random2(i_uv + vec2(0.0, 0.0))) * 2.0 - 1.0; vec2 v2 = animation2(random2(i_uv + vec2(1.0, 0.0))) * 2.0 - 1.0; vec2 v3 = animation2(random2(i_uv + vec2(0.0, 1.0))) * 2.0 - 1.0; vec2 v4 = animation2(random2(i_uv + vec2(1.0, 1.0))) * 2.0 - 1.0; float o = mix( mix(dot(v1, f_uv - vec2(0.0, 0.0)), dot(v2, f_uv - vec2(1.0, 0.0)), interpolat(f_uv.x)), mix(dot(v3, f_uv - vec2(0.0, 1.0)), dot(v4, f_uv - vec2(1.0, 1.0)), interpolat(f_uv.x)), interpolat(f_uv.y)) * 0.5 + 0.5; o = step(0.3,o); return o; } void main(){ vec2 r=resolution,p=(gl_FragCoord.xy*2.-r)/min(r.x,r.y); vec2 position = ( gl_FragCoord.xy / resolution.xy ); texture2D(_buffer, position); float x = 2. * p.y + sin(time * 5.); float distort = sin(time * 10.) * 0.1 * sin(5. * x) * (- (x - 1.) * (x - 1.) + 1.); p.x += distort; float r_ = noise(vec2(p.x,p.y-distort*-0.3)); float g = noise(vec2(p.x+distort*-0.3,p.y)); float b = noise(vec2(p.x-distort*-0.3,p.y)); vec3 color =vec3(0.); color.r = texture2D(_buffer, p + vec2(distort * -0.3, 0.)).r; color.g = texture2D(_buffer, p + vec2(0., distort * 0.3) ).g; color.b = texture2D(_buffer, p - vec2(0., distort * 0.3) ).b; gl_FragColor = vec4(vec3(r_,g,b),1.); }
当時のメモ
RGBShiftとパーリングの理解。
あわせて、バリューノイズ、疑似ランダム、ホワイトノイズ、ブロックノイズを理解。
(一気に勉強したから忘れてる部分も多い)
9.5作品目(ほぼコピペコードだったので作品にはカウントしない)
precision highp float; uniform vec2 resolution; uniform float time; float interpolation(float f) { return f * f * f * (f * (6.0 * f - 15.0) + 10.0); } float random(vec2 co){ return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); } vec2 random2(vec2 v){ return vec2(random(v), random(v + vec2(0.5, 0.5))); } vec2 animation2(vec2 v) { return vec2(sin(v.x * 6.283 + time * 2.0) * 0.5 + 0.5, sin(v.y * 6.283 + time * 2.0) * 0.5 + 0.5); } void main(){ vec2 r=resolution,p=(gl_FragCoord.xy*2.-r)/min(r.x,r.y); vec2 position = ( gl_FragCoord.xy / resolution.xy ); vec3 color = vec3(.0); vec2 i_uv = floor(p * 8.0); vec2 f_uv = fract(p * 8.0); vec2 v1 = animation2(random2(i_uv + vec2(0.0, 0.0))); vec2 m_neighbor; float m_dist = 1.; for (int y= -1; y <= 1; y++) { for (int x= -1; x <= 1; x++) { vec2 neighbor = vec2(float(x),float(y)); vec2 v1 = animation2(random2(i_uv + neighbor)); vec2 diff = v1-f_uv+neighbor; float dist = length(diff); m_dist=min(dist,m_dist); } } color += m_dist; //color -= step(.7,abs(sin(27.0*m_dist)))*.5; //color += 1.-step(.02, m_dist); //color.r += step(.96, f_uv.x) + step(.96, f_uv.y); gl_FragColor = vec4(color,1.); }
当時のメモ
セルラーノイズの理解。↓をみたら分かりやすい
thebookofshaders.com
(ノイズ系、結構汎用性高いのでシェーダー使う人は覚えておくと便利かも)
10作品目
#define PI 3.1415926 precision highp float; uniform vec2 resolution; uniform float time; float interpolation(float f) { return f * f * f * (f * (6.0 * f - 15.0) + 10.0); } float random(vec2 co){ return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); } vec2 random2(vec2 v){ return vec2(random(v), random(v + vec2(0.5, 0.5))); } vec2 animation2(vec2 v){ return vec2(sin(v.x * 6.283 + time * 2.0) * 0.5 + 0.5, sin(v.y * 6.283 + time * 2.0) * 0.5 + 0.5); } vec2 voronoi(in vec2 p,out vec2 m_point){ vec2 i_uv = floor(p * 8.0); vec2 f_uv = fract(p * 8.0); vec2 v1 = animation2(random2(i_uv + vec2(0.0, 0.0))); vec2 m_neighbor; vec2 m_dist = vec2(8, 8); for (int y= -1; y <= 1; y++) { for (int x= -1; x <= 1; x++) { vec2 neighbor = vec2(float(x),float(y)); vec2 v1 = animation2(random2(i_uv + neighbor)); vec2 diff = v1-f_uv+neighbor; float dist = length(diff); if(dist < m_dist.x){ m_dist.y = m_dist.x; m_dist.x= dist; m_point = v1; }else if(dist < m_dist.y){ m_dist.y = dist; } } } return m_dist; } void main(){ vec2 r=resolution,p=(gl_FragCoord.xy*2.-r)/min(r.x,r.y); vec2 position = ( gl_FragCoord.xy / resolution.xy ); vec3 color = vec3(.0); vec2 point; vec2 v = voronoi(p,point); vec2 c = sqrt(v); float dis = c.x - c.y; color = vec3(1.0-step(-.1,.05+(dis)*2.)); color.rb -= point; gl_FragColor = vec4(color,1.); }
当時のメモ
ボロノイ図らしき何か。境界線の出し方の理解が難しかったです。簡易版の境界線表示で対応しました。
参考リンク
thebookofshaders.com
(いまだにちゃんと理解してない)
11作品目
precision mediump float; uniform float time; uniform vec2 mouse; uniform vec2 resolution; const float sphereSize = 1.0; // 球の半径 float distanceFunc(vec3 p){ return length(p) - sphereSize; } vec3 normal(vec3 pos) { float v = 0.001; return normalize( vec3(distanceFunc(pos) - distanceFunc(vec3(pos.x - v, pos.y, pos.z)), distanceFunc(pos) - distanceFunc(vec3(pos.x, pos.y - v, pos.z)), distanceFunc(pos) - distanceFunc(vec3(pos.x, pos.y, pos.z - v))) ); } void main(void){ // fragment position vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y); // camera 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 = 1.0; // ray vec3 ray = normalize(cSide * p.x + cUp * p.y + cDir * targetDepth); // marching loop float distance = 0.0; // レイとオブジェクト間の最短距離 float rLen = 0.0; // レイに継ぎ足す長さ vec3 rPos = cPos; // レイの先端位置 for(int i = 0; i < 99; i++){ distance = distanceFunc(rPos); rLen += distance; rPos = cPos + ray * rLen; } vec3 lightDirection = normalize(vec3(sin(time), 0.0, 1.)); // hit check if(abs(distance) < 0.001){ vec3 nomal = normal(rPos); float differ = dot(nomal,lightDirection); gl_FragColor = vec4(vec3(differ), 1.0); }else{ gl_FragColor = vec4(vec3(0.0), 1.0); } }
当時のメモ
初レイマーチング
(この辺からグッと数学レベルが上ってきてキツい)
(ポストエフェクトレベルならココまでの知識である程度かける箇所が多いハズ)
12作品目
precision mediump float; uniform float time; uniform vec2 mouse; uniform vec2 resolution; const float sphereSize = 1.0; // 球の半径 vec3 trans(vec3 p){ return mod(p, 8.0) - 4.0; } float distanceFunc(vec3 p){ return length(trans(p)) - sphereSize; } vec3 normal(vec3 pos) { float v = 0.001; return normalize( vec3(distanceFunc(pos) - distanceFunc(vec3(pos.x - v, pos.y, pos.z)), distanceFunc(pos) - distanceFunc(vec3(pos.x, pos.y - v, pos.z)), distanceFunc(pos) - distanceFunc(vec3(pos.x, pos.y, pos.z - v))) ); } void main(void){ // fragment position vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y); // camera vec3 cPos = vec3(0.0, 0.0, 10.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 = 1.0; // ray vec3 ray = normalize(cSide * p.x + cUp * p.y + cDir * targetDepth-cPos); // marching loop float distance = 0.0; // レイとオブジェクト間の最短距離 float rLen = 0.0; // レイに継ぎ足す長さ vec3 rPos = cPos; // レイの先端位置 for(int i = 0; i < 99; i++){ distance = distanceFunc(rPos); if (distance < 0.001) { break; } rLen += distance; rPos = cPos + ray * rLen; } vec3 lightDirection = normalize(vec3(sin(time), 0.0, 1.)); // hit check if(abs(distance) < 0.001){ vec3 nomal = normal(rPos); float differ = dot(nomal,lightDirection); gl_FragColor = vec4(vec3(differ)+vec3(1,0,0), 1.0); }else{ gl_FragColor = vec4(vec3(0.0), 1.0); } }
当時のメモ
レイマーチングの修行中。前回のサイトの途中から。
(何か四角のやつも作ってた↓)
#GLSL
— やまだ たいし (@OrotiYamatano) 2020年11月30日
レイマーチング学んでる途中
12作品目。 pic.twitter.com/wZ9fExIz2J
このサイトとか参考になる。
Inigo Quilez :: computer graphics, mathematics, shaders, fractals, demoscene and more
終了
ココからは忙しくなって書けてませんでした。
とはいえ、大分シェーダーを読むのには抵抗がなくなりました。
凄い人たちには微塵も及ばないけれど、なんとなく遊ぶ程度にはできるようになりました。
最近は業務寄りのシェーダーいじりしてます
アウトラインの太さライトの向きで調整できるシェーダー書いた pic.twitter.com/JdcP3M16f2
— やまだ たいし (@OrotiYamatano) 2022年4月24日
Twitterに上げてなかったので。
— やまだ たいし (@OrotiYamatano) 2022年4月22日
ポストプロセス工程で行う、sobel filter(Depth)ロジックによるエッジ検出でのアウトライン生成処理です pic.twitter.com/iuC03mEHyG
呟いてなかったので投稿。歪みシェーダー。 pic.twitter.com/IO8S1ruHhd
— やまだ たいし (@OrotiYamatano) 2022年4月27日
AOベイクして出力したグレースケールの画像とランプテクスチャを利用してシェーダーで乗算して色付け。 pic.twitter.com/smYU3laQVV
— やまだ たいし (@OrotiYamatano) 2022年3月29日
イラスト塗りを再現したくて、シェーダー適当にいじってたら出来たやつ。
— やまだ たいし (@OrotiYamatano) 2022年4月27日
正式名称
SSS(サブサーフェス・スキャタリング)
表面下散乱
って言うらしいですね。 pic.twitter.com/tJYqIhxDov
ちなみに更にGLSLを学ぶなら
今から更にGLSLを学ぶなら、私は画像処理周りと数学的な部分を学ぶと思います。
(格子点、胞体、傍距、京都距離(マンハッタン距離)、ノルム、特徴点)
全然覚えてない……。
後、日本にもDEMOと呼ばれるシェーダーイベントがあります。ヤバい奴らがいます。
その人達の実況とか参考に勉強をすすめるかも?
tokyodemofest.jp
後、元ピクサーのやばい人のブログとかシェーダーが勉強になりそうです。
やべーシェーダーだって思ったらだいたいこの人w
twitter.com
あとはTwitterで #つぶやきGLSL で検索するといっぱいコードが出てきます。
短く書くことに特化したコードなのでちょっと読みづらいですが、読めれば勉強になると思います。
今は他のことに興味あり
シェーダーはシェーダーでも業務寄りに今は興味あります。
法線をイジったり、デプスとってきたりね。ポストエフェクトとか、VFXとか。
Unityまわりならかもそばさんの本とか参考になります。
zenn.dev
後、KENTOさんのも、いいかも。
zenn.dev
もちろんUnity公式でやってるのもめっちゃ参考になる。
(簡単なやつだとUnityJapanの公式動画が上がってたと思う)
特にKeijiroさんのは、やばばあばば。(Githubのリポジトリも読もうね)
twitter.com
まとめ
個人的にあの処理ってなんだったっけって見返すことも多いので、今回書き出してみました。
これが、みんなのシェーダーのコードを学ぶ足がかりのコードになればいいなぁと思います。
以上、やまだたいしでした!