やまだたいしのシェーダー(GLSL)勉強まとめ!

こんにちは、やまだたいし( やまだ たいし (@OrotiYamatano) / Twitter )です。
以前からシェーダーを勉強していてある場所にまとめていたのですが、公開しないのも勿体ないと思い
今回は記事化することにしました。

もっと初歩的な記事についてはこちら↓

orotiyamatano.hatenablog.com

目次


なぜシェーダーを勉強し始めたか?


プログラムの勉強は見た目を派手にするような実装は少なく
進捗として見たときに地味。

シェーダー周りを勉強すると、見た目は派手だし、ゲームの表現も増すので勉強し始めました。

環境


twiGLを使用。
GLSLは書きやすいって聞いた。
クラシックで書いていくことに。

twigl.app

ちなみに他には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作品目


https://gyazo.com/8a2d007f9dedbf5d4dd5039d901757b5/max_size/1000

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作品目


https://gyazo.com/ad4a4670702e7047bf7f744192828a8c/max_size/1000

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次元の回転行列をなんとなく理解

thebookofshaders.com

(参考にしたシェーダーのマルとシカク変換かっこいいと思ったんだよね)

4作品目


https://gyazo.com/d92a8b36ab6b5db331cab395d07f221c/max_size/1000

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作品目


https://gyazo.com/66ae9633c693792e5861156b4c1d1ceb/max_size/1000

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作品目


https://gyazo.com/f7bf30030dc3825b9b180afd3916b901/max_size/1000

#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作品目


https://gyazo.com/f94543dc1819337f9a8a1e434193a029/max_size/1000

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作品目


https://gyazo.com/753bc0c0e981775d7c8927c0ce5c5b00/max_size/1000

#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作品目


https://gyazo.com/3700c71da9b10902f4a15020e304516c/max_size/1000

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作品目(ほぼコピペコードだったので作品にはカウントしない)


https://gyazo.com/5118141adf27dc2bc303b9f3d283dc4d/max_size/1000

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作品目


https://gyazo.com/a051f928d4159e768a511d936e09d6a3/max_size/1000

#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作品目


https://gyazo.com/2d48420100c801ca22c2c262b036a73e/max_size/1000

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);
    }
}

当時のメモ
初レイマーチング

wgld.org

qiita.com

qiita.com

(この辺からグッと数学レベルが上ってきてキツい)
(ポストエフェクトレベルならココまでの知識である程度かける箇所が多いハズ)

12作品目


https://gyazo.com/9b1c4dfbdb74b1bce1a2441b90000230/max_size/1000

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);
    }
}

当時のメモ
レイマーチングの修行中。前回のサイトの途中から。

(何か四角のやつも作ってた↓)

このサイトとか参考になる。
Inigo Quilez :: computer graphics, mathematics, shaders, fractals, demoscene and more

終了


ココからは忙しくなって書けてませんでした。
とはいえ、大分シェーダーを読むのには抵抗がなくなりました。
凄い人たちには微塵も及ばないけれど、なんとなく遊ぶ程度にはできるようになりました。

最近は業務寄りのシェーダーいじりしてます


ちなみに更にGLSLを学ぶなら


今から更にGLSLを学ぶなら、私は画像処理周りと数学的な部分を学ぶと思います。
(格子点、胞体、傍距、京都距離(マンハッタン距離)、ノルム、特徴点)
全然覚えてない……。

後、日本にもDEMOと呼ばれるシェーダーイベントがあります。ヤバい奴らがいます。
その人達の実況とか参考に勉強をすすめるかも?
tokyodemofest.jp

後、元ピクサーのやばい人のブログとかシェーダーが勉強になりそうです。
やべーシェーダーだって思ったらだいたいこの人w
twitter.com

あとはTwitterで #つぶやきGLSL で検索するといっぱいコードが出てきます。
短く書くことに特化したコードなのでちょっと読みづらいですが、読めれば勉強になると思います。

今は他のことに興味あり


シェーダーはシェーダーでも業務寄りに今は興味あります。
法線をイジったり、デプスとってきたりね。ポストエフェクトとか、VFXとか。

Unityまわりならかもそばさんの本とか参考になります。
zenn.dev

後、KENTOさんのも、いいかも。
zenn.dev

もちろんUnity公式でやってるのもめっちゃ参考になる。
(簡単なやつだとUnityJapanの公式動画が上がってたと思う)
特にKeijiroさんのは、やばばあばば。(Githubリポジトリも読もうね)
twitter.com

まとめ


個人的にあの処理ってなんだったっけって見返すことも多いので、今回書き出してみました。
これが、みんなのシェーダーのコードを学ぶ足がかりのコードになればいいなぁと思います。

以上、やまだたいしでした!