The Book of Shaders by Patricio Gonzalez Vivo & Jen Lowe

Bahasa Indonesia - Tiếng Việt - 日本語 - 中文版 - 한국어 - Español - Portugues - Français - Italiano - Deutsch - Русский - Polski - English


パターン

シェーダーのプログラムはピクセルごとに並列で処理されるので、同じ形を何度も繰り返したとしても計算の回数は一定に留まります。そのためフラグメントシェーダーはタイルのような繰り返しのパターンに特に適しています。

Nina Warmerdam - The IMPRINT Project (2013)
Nina Warmerdam - The IMPRINT Project (2013)

この章ではこれまでに学んだことを応用して、描画領域のなかで繰り返しを行います。前章で行ったのと同様に、空間座標に対して掛け算を行い、0.0 から 1.0 の間に掛かれた図形がグリッド状に反復されるようにします。

「グリッドは人間の直感と創意が働く枠組み、構築と解体のためのフレームワークを提供します。自然界のカオスの中に、規則的なパターンは対比と秩序の兆しをもたらしてくれます。 草創期の陶芸品の模様からローマ時代の浴場の幾何学的なモザイクまで、長い間人々はグリッドを用い生活を豊かに彩ってきました。」10 PRINT, Mit Press, (2013) (訳注:そのまま訳すのが難しい文章だったため、本人に意図を確認して言葉を補いました。)

まず fract() 関数を思い出しましょう。この関数はつまるところ 1 の剰余 (mod(x,1.0))と同等です。言い換えれば fract() は小数点以下の部分の数を返してくれます。正規化された座標(st)は既に 0.0 から 1.0 の間に収まっているので、下記のようなコードは意味を成しません。

void main(){
    vec2 st = gl_FragCoord.xy/u_resolution;
    vec3 color = vec3(0.0);
    st = fract(st);
    color = vec3(st,0.0);
    gl_FragColor = vec4(color,1.0);
}

しかしここで、正規化された座標系を例えば3倍に拡大すると、0 から 1 までの滑らかに変化する値の繰り返しを3つ得ることができます。1つ目は 0 から 1 までの値、2つ目は 1 から 2 までの値の少数部分、3つ目は 2 から 3 までの値の少数部分です。

x
 
1
// Author @patriciogv - 2015
2
3
#ifdef GL_ES
4
precision mediump float;
5
#endif
6
7
uniform vec2 u_resolution;
8
uniform float u_time;
9
10
float circle(in vec2 _st, in float _radius){
11
    vec2 l = _st-vec2(0.5);
12
    return 1.-smoothstep(_radius-(_radius*0.01),
13
                         _radius+(_radius*0.01),
14
                         dot(l,l)*4.0);
15
}
16
17
void main() {
18
    vec2 st = gl_FragCoord.xy/u_resolution;
19
    vec3 color = vec3(0.0);
20
21
    st *= 3.0;      // Scale up the space by 3
22
    st = fract(st); // Wrap around 1.0
23
24
    // Now we have 9 spaces that go from 0-1
25
26
    color = vec3(st,0.0);
27
    //color = vec3(circle(st,0.5));
28
29
    gl_FragColor = vec4(color,1.0);
30
}
31

さて、27行目のコメントを外して分割されたそれぞれの空間の中に何か描いてみましょう。xとyを等しく拡大しているので空間のアスペクト比は変わらず、期待した通りの形が描かれます。

下記の課題を幾つか試して理解を深めましょう。

パターンの中で行列を適用する

それぞれの分割された空間は、これまで使ってきたのと同じ正規化された座標系をただ小さくしたものになっているので、行列変換をそれぞれの空間内での平行移動や回転、拡大・縮小に使うことができます。

xxxxxxxxxx
20
 
1
// Author @patriciogv ( patriciogonzalezvivo.com ) - 2015
2
3
#ifdef GL_ES
4
precision mediump float;
5
#endif
6
7
// Copyright (c) Patricio Gonzalez Vivo, 2015 - http://patriciogonzalezvivo.com/
8
// I am the sole copyright owner of this Work.
9
//
10
// You cannot host, display, distribute or share this Work in any form,
11
// including physical and digital. You cannot use this Work in any
12
// commercial or non-commercial product, website or project. You cannot
13
// sell this Work and you cannot mint an NFTs of it.
14
// I share this Work for educational purposes, and you can link to it,
15
// through an URL, proper attribution and unmodified screenshot, as part
16
// of your educational material. If these conditions are too restrictive
17
// please contact me and we'll definitely work it out.
18
19
uniform vec2 u_resolution;
20
uniform float u_time;
21
22
#define PI 3.14159265358979323846
23
24
vec2 rotate2D(vec2 _st, float _angle){
25
    _st -= 0.5;
26
    _st =  mat2(cos(_angle),-sin(_angle),
27
                sin(_angle),cos(_angle)) * _st;
28
    _st += 0.5;
29
    return _st;
30
}
31
32
vec2 tile(vec2 _st, float _zoom){
33
    _st *= _zoom;
34
    return fract(_st);
35
}
36
37
float box(vec2 _st, vec2 _size, float _smoothEdges){
38
    _size = vec2(0.5)-_size*0.5;
39
    vec2 aa = vec2(_smoothEdges*0.5);
40
    vec2 uv = smoothstep(_size,_size+aa,_st);
41
    uv *= smoothstep(_size,_size+aa,vec2(1.0)-_st);
42
    return uv.x*uv.y;
43
}
44
45
void main(void){
46
    vec2 st = gl_FragCoord.xy/u_resolution.xy;
47
    vec3 color = vec3(0.0);
48
49
    // Divide the space in 4
50
    st = tile(st,4.);
51
52
    // Use a matrix to rotate the space 45 degrees
53
    st = rotate2D(st,PI*0.25);
54
55
    // Draw a square
56
    color = vec3(box(st,vec2(0.7),0.01));
57
    // color = vec3(st,0.0);
58
59
    gl_FragColor = vec4(color,1.0);
60
}
61

Vector Pattern Scottish Tartan By Kavalenkava
Vector Pattern Scottish Tartan By Kavalenkava

パターンをずらす

ブロックの壁を模したパターンを作りたいとします。壁を観察すると、1行おきにブロック半個分だけx座標がずれていることに気づくでしょう。

xをずらす必要があるかを決めるには、まず現在のスレッドが偶数行と奇数行のどちらに当たるのかを知る必要があります。

スレッドが偶数行か奇数行かを求めるには、2.0 の剰余(mod() )を、拡大した座標系に対して用い結果が 1.0 を下回るかどうかを見ます。下記のコードの最後の2行のコメントを外してみましょう。

 
y = mod(x,2.0);
// y = mod(x,2.0) < 1.0 ? 0. : 1. ;
// y = step(1.0,mod(x,2.0));

(訳注:このサンプルではxは 0.0 から 1.0 ではなく -4 から 4 の範囲にすでに拡大されています。コメントを外すとxの整数部分が奇数になる、つまり mod(x, 2.0) が 1.0 以上になる場合にだけyが 1.0 になります。)

見てのとおり、2.0 の剰余(mod())が 1.0 を下回るかどうかの判定には、三項演算子(2行目)を使うか、 step() (3行目) を使ってより高速に処理を行うこともできます。グラフィックカードがどのように最適化されていて、どのようにコードをコンパイルするのかを知るのは難しいことですが、組み込み関数はそうでない関数に比べて高速に処理されると考えてまず問題ありません。組み込み関数が使える場合には必ず使うようにしましょう。

偶奇判定の式ができたので、これでタイルの奇数行をずらしてブロックらしく見せることができます。下記のコードの14行目の関数で、奇数行かどうかを判定して x をブロック半個分ずらしています。この行の step() は偶数行に対して 0.0 を返します。ブロック半個分の 0.5 をかけると結果は 0.0です。奇数行に対しては 1.0 を返すので 0.5 を掛けた結果、座標系をx軸方向に 0.5 だけ移動させることになります。

32行目のコメントを外してみましょう。こうすると座標系のアスペクト比が引き伸ばされて現代のブロックの比率になります。40行目のコメントを外すと座標系の様子が赤と緑で示されるのを見ることができます。

xxxxxxxxxx
20
 
1
// Author @patriciogv ( patriciogonzalezvivo.com ) - 2015
2
3
#ifdef GL_ES
4
precision mediump float;
5
#endif
6
7
uniform vec2 u_resolution;
8
uniform float u_time;
9
10
vec2 brickTile(vec2 _st, float _zoom){
11
    _st *= _zoom;
12
13
    // Here is where the offset is happening
14
    _st.x += step(1., mod(_st.y,2.0)) * 0.5;
15
16
    return fract(_st);
17
}
18
19
float box(vec2 _st, vec2 _size){
20
    _size = vec2(0.5)-_size*0.5;
21
    vec2 uv = smoothstep(_size,_size+vec2(1e-4),_st);
22
    uv *= smoothstep(_size,_size+vec2(1e-4),vec2(1.0)-_st);
23
    return uv.x*uv.y;
24
}
25
26
void main(void){
27
    vec2 st = gl_FragCoord.xy/u_resolution.xy;
28
    vec3 color = vec3(0.0);
29
30
    // Modern metric brick of 215mm x 102.5mm x 65mm
31
    // http://www.jaharrison.me.uk/Brickwork/Sizes.html
32
    // st /= vec2(2.15,0.65)/1.5;
33
34
    // Apply the brick tiling
35
    st = brickTile(st,5.0);
36
37
    color = vec3(box(st,vec2(0.9)));
38
39
    // Uncomment to see the space coordinates
40
    // color = vec3(st,0.0);
41
42
    gl_FragColor = vec4(color,1.0);
43
}
44

トルシェタイル

マス目が偶数行と奇数行(または列)どちらに当たるかの判定方法を学んだので、デザイン要素を場所に応じて再利用することができます。 同じデザイン要素が4種類の方法で使われる、トルシェタイルを例として考えてみましょう。

タイルごとのパターンを変えることによって、複雑なデザインを無限に組み立てることができます。

rotateTilePattern() に注目してください。この関数は空間を4つのマス目に分割しそれぞれに回転の角度を割り当てます。

xxxxxxxxxx
20
 
1
// Author @patriciogv ( patriciogonzalezvivo.com ) - 2015
2
3
#ifdef GL_ES
4
precision mediump float;
5
#endif
6
7
#define PI 3.14159265358979323846
8
9
uniform vec2 u_resolution;
10
uniform float u_time;
11
12
vec2 rotate2D (vec2 _st, float _angle) {
13
    _st -= 0.5;
14
    _st =  mat2(cos(_angle),-sin(_angle),
15
                sin(_angle),cos(_angle)) * _st;
16
    _st += 0.5;
17
    return _st;
18
}
19
20
vec2 tile (vec2 _st, float _zoom) {
21
    _st *= _zoom;
22
    return fract(_st);
23
}
24
25
vec2 rotateTilePattern(vec2 _st){
26
27
    //  Scale the coordinate system by 2x2
28
    _st *= 2.0;
29
30
    //  Give each cell an index number
31
    //  according to its position
32
    float index = 0.0;
33
    index += step(1., mod(_st.x,2.0));
34
    index += step(1., mod(_st.y,2.0))*2.0;
35
36
    //      |
37
    //  2   |   3
38
    //      |
39
    //--------------
40
    //      |
41
    //  0   |   1
42
    //      |
43
44
    // Make each cell between 0.0 - 1.0
45
    _st = fract(_st);
46
47
    // Rotate each cell according to the index
48
    if(index == 1.0){
49
        //  Rotate cell 1 by 90 degrees
50
        _st = rotate2D(_st,PI*0.5);
51
    } else if(index == 2.0){
52
        //  Rotate cell 2 by -90 degrees
53
        _st = rotate2D(_st,PI*-0.5);
54
    } else if(index == 3.0){
55
        //  Rotate cell 3 by 180 degrees
56
        _st = rotate2D(_st,PI);
57
    }
58
59
    return _st;
60
}
61
62
void main (void) {
63
    vec2 st = gl_FragCoord.xy/u_resolution.xy;
64
65
    st = tile(st,3.0);
66
    st = rotateTilePattern(st);
67
68
    // Make more interesting combinations
69
    // st = tile(st,2.0);
70
    // st = rotate2D(st,-PI*u_time*0.25);
71
    // st = rotateTilePattern(st*2.);
72
    // st = rotate2D(st,PI*u_time*0.25);
73
74
    // step(st.x,st.y) just makes a b&w triangles
75
    // but you can use whatever design you want.
76
    gl_FragColor = vec4(vec3(step(st.x,st.y)),1.0);
77
}
78

自分でルールを作る

規則によるパターン作りは、再利用可能な最小の要素を見つける頭の体操です。この行為自体は古くからあるもので、私たちの種はグリッドとパターンを織物や床をはじめ様々なものを装飾するために長い間利用してきました。曲がりくねった古代ギリシャの模様から中国の格子模様まで、反復と変化の楽しみは私たちの想像力をかきたててきました。装飾的なパターン(例1 例2)をゆっくりと眺めて、予測可能な規則正しさと、驚きにあふれた変化とカオスの間の微妙な線を芸術家やデザイナー達が渡り歩いてきた様子を見てみましょう。アラブの幾何学的なパターンから、アフリカの生地のデザインまで、学ぶべき大きな世界がそこにあります。

Franz Sales Meyer - A handbook of ornament (1920)
Franz Sales Meyer - A handbook of ornament (1920)

この章で「アルゴリズムで絵を描く」のセクションは終わりです。続く章では、シェーダーに多少の無秩序さを持ち込んでデザインを生成する方法について学びます。