型・演算・swizzling

GLSL のベクトル・行列の扱いと、独自記法の swizzling。 ここを使いこなせると、コードが一気に短く・速く書ける。

ベクトル型: vec2 / vec3 / vec4

作り方

vec2 a = vec2(1.0, 2.0);
vec3 b = vec3(0.5, 0.5, 0.5);
vec3 c = vec3(0.5);                 // 全成分 0.5(ブロードキャスト)
vec4 d = vec4(b, 1.0);              // vec3 + float = vec4
vec4 e = vec4(a, a);                // vec2 + vec2 = vec4
vec4 f = vec4(1.0);                 // (1,1,1,1)

要素アクセス

以下の3グループのどれを使ってもよい。意味で使い分けると読みやすい。

vec3 v = vec3(1.0, 2.0, 3.0);

float x = v.x;     // 1.0
float r = v.r;     // 1.0(同じ)
float s = v.s;     // 1.0(同じ)

v.x = 10.0;        // 書き込みも可
グループを混ぜない

v.xgv.rt のように異なるグループを混ぜるとコンパイルエラー。 v.xy / v.rgb / v.st のように同じグループ内で揃える

swizzling

ベクトルの成分を順序を変えて取り出したり、複数まとめたりする記法。 これが GLSL の最大の便利機能

並べ替え

vec3 a = vec3(1.0, 2.0, 3.0);

vec3 b = a.xyz;    // (1, 2, 3) — 同じ
vec3 c = a.zyx;    // (3, 2, 1) — 逆順
vec3 d = a.bgr;    // (3, 2, 1) — RGB → BGR
vec2 e = a.xy;     // (1, 2)
vec4 f = a.xyzz;   // (1, 2, 3, 3) — 重複OK
vec4 g = a.xxxx;   // (1, 1, 1, 1) — 全部同じも OK

vec4 から RGB だけ取る

vec4 col = texture(tex, uv);
vec3 rgb = col.rgb;
float alpha = col.a;

2要素を組む

vec2 uv = gl_FragCoord.xy / resolution.xy;
vec2 center = uv - 0.5;

書き込みも swizzle できる

vec3 v = vec3(0.0);
v.xy = vec2(1.0, 2.0);    // (1, 2, 0)
v.xz = vec2(3.0, 4.0);    // (3, 2, 4)

// ただし重複は不可: v.xx = vec2(...) はエラー

ベクトル演算

component-wise(要素ごと)

ほぼすべての算術演算は要素ごと

vec3 a = vec3(1.0, 2.0, 3.0);
vec3 b = vec3(2.0, 0.5, 4.0);

a + b;       // (3.0, 2.5, 7.0)
a - b;       // (-1.0, 1.5, -1.0)
a * b;       // (2.0, 1.0, 12.0)
a / b;       // (0.5, 4.0, 0.75)

スカラとの演算

vec3 a = vec3(1.0, 2.0, 3.0);

a * 2.0;     // (2.0, 4.0, 6.0)
a + 1.0;     // (2.0, 3.0, 4.0)
1.0 / a;     // (1.0, 0.5, 0.333)

内積(dot)

dot(a, b) = a.x*b.x + a.y*b.y + a.z*b.z。 2つの方向の同じ向きさを表す。法線とライト方向で明るさを出す時の基本。

vec3 N = normalize(vNormal);
vec3 L = normalize(uLightDir);

float diffuse = max(dot(N, L), 0.0);

外積(cross)— 3次元のみ

cross(a, b)a と b に直交するベクトル。法線計算に。

vec3 normal = normalize(cross(edge1, edge2));

長さ・正規化

vec2 p = vUv - 0.5;
float r = length(p);              // 中心からの距離
vec2 dir = normalize(p);          // 中心から外向きの単位ベクトル

整数とブール

int / uint

int  i = 5;
uint u = 10u;     // u サフィックス
int  fromFloat = int(3.7);   // 3(切り捨て)

float toFloat = float(i);    // 5.0

bool 演算

bool a = true;
bool b = (x > 0.5);

if (a && b) { ... }
if (a || !b) { ... }

ベクトル全体の真偽

bvec の全要素が真かを判定:

bvec3 bv = greaterThan(v, vec3(0.5));   // 各要素を比較
bool all_pos = all(bv);
bool any_pos = any(bv);

行列型: mat2 / mat3 / mat4

作り方

// 単位行列
mat3 I = mat3(1.0);

// 列ベクトルから(列優先)
mat3 m = mat3(
  vec3(1.0, 0.0, 0.0),     // 1列目
  vec3(0.0, 1.0, 0.0),     // 2列目
  vec3(0.0, 0.0, 1.0)      // 3列目
);

// 各成分を直接
mat2 r = mat2(
  cos(a), -sin(a),
  sin(a),  cos(a)
);

GLSL の行列は列優先(column-major)m[0]1列目のベクトル。

行列とベクトルの掛け算

vec3 v = vec3(1.0, 0.0, 0.0);
vec3 transformed = m * v;             // 線形変換

// 4x4 行列で位置を変換
vec4 pos = vec4(position, 1.0);
vec4 worldPos = modelMatrix * pos;

行列同士の掛け算

mat4 mvp = projectionMatrix * viewMatrix * modelMatrix;
gl_Position = mvp * vec4(position, 1.0);

要素アクセス

mat3 m = mat3(1.0);
vec3 col0 = m[0];           // 1列目
float a   = m[0][1];        // 1列目の y 成分

転置・逆行列

2D / 3D 回転行列

2D 回転(mat2)
mat2 rot2(float a) {
  float c = cos(a), s = sin(a);
  return mat2(c, -s, s, c);
}

vec2 p = (vUv - 0.5) * rot2(uTime);
Y 軸回転(mat3)
mat3 rotY(float a) {
  float c = cos(a), s = sin(a);
  return mat3(
    c, 0.0, -s,
    0.0, 1.0, 0.0,
    s, 0.0,  c
  );
}

ベクトルから行列を作る(外積から TBN)

Tangent / Bitangent / Normal の TBN 行列。法線マップのときに使う。

vec3 N = normalize(vNormal);
vec3 T = normalize(vTangent);
vec3 B = normalize(cross(N, T));
mat3 TBN = mat3(T, B, N);

暗黙の型変換ルール

GLSL は暗黙の変換に厳しい

定数と const

const float PI = 3.14159265358979;
const vec3 UP = vec3(0.0, 1.0, 0.0);
const int  ITER = 8;

const はコンパイル時定数。配列の長さに使うなら必須:

const int N = 16;
float weights[N] = float[N](...);

典型パターン

距離フィールド(円までの距離)

float circleSDF(vec2 p, float r) {
  return length(p) - r;
}

float d = circleSDF(vUv - 0.5, 0.3);
float c = smoothstep(0.005, -0.005, d);   // エッジを滑らかに

2D 座標の正規化(アスペクト比補正)

vec2 uv = gl_FragCoord.xy / uResolution.xy;
uv -= 0.5;
uv.x *= uResolution.x / uResolution.y;   // アスペクト補正

2D ベクトルの極座標

vec2 p = vUv - 0.5;
float r = length(p);
float a = atan(p.y, p.x);    // -π..π

反射ベクトル

vec3 R = reflect(-V, N);   // V を N で反射(ハイライト計算等)

2 色の補間

vec3 col = mix(
  vec3(0.0, 0.5, 1.0),     // a
  vec3(1.0, 0.2, 0.5),     // b
  smoothstep(0.3, 0.7, vUv.x)  // t
);

swizzle が効くケースの実例

RGB の 2 チャンネルだけ反転

vec3 c = vec3(0.2, 0.5, 0.8);
c.gb = 1.0 - c.gb;          // (0.2, 0.5, 0.2)

UV を x だけスクロール

vec2 uv = vUv;
uv.x += uTime * 0.1;

Y を反転

vec2 uv = vUv;
uv.y = 1.0 - uv.y;

2D ベクトルから vec4(パディング)

vec2 p = vec2(1.0, 2.0);
vec4 v = p.xyyy;        // (1, 2, 2, 2)
vec4 w = vec4(p, 0.0, 1.0);  // 等価
慣習

スウィズルのよく使う組み合わせをパターンとして覚えると、コードがすっきり書ける:
- v.xy / v.xz / v.yz — 2D 平面の取り出し
- v.xyz — 3D 部分(vec4 から色だけ)
- v.rgb / v.a — 色と α の分離
- v.bgr — BGR 形式への並び替え