テラByteの時代にキロByte

shader又はdemosceneに関係する事

shaderでmat4を使う為のあれこれ

最近はGPUが強くなってきたのでraymarchingで複数のオブジェクトも大丈夫かなと思えるようになってきた。だけど扱うオブジェクトの数が増えてくれば姿勢の管理の大変になる。そこでmat4を使ってみます。 普通raymarchingをするならmat4なんて面倒で使いたくない。最初はそうだったけどスキニングを始めたらmat4が便利でビックリでした。そこで得た方法なんかを書いていこうと思います。ただ、1個2個程度なら使わない方が良いかな。

使い方

mat4は数学どうのこうのというより乗算する順番が全てです。どちらかというと感覚で使った方が良いんじゃないって思えてきます。使っていて変なら掛ける順番を入れ替えるだけ。まあそんな感じで使ってます。使う行列は移動行列と回転行列だけです。skew行列とか拡大行列とかありますが距離関数の特性上アーティファクトが発生するので使えません。かえって分かりやすくて良いです。基本姿勢操作だけと思ってください。
説明が難しいので、雰囲気で書いて順番を入れ替えていけば、そのうちにツボが分かります。ここは確かめてはいませんが使っている感じだとvertex shaderの行列処理の逆行列を使っているみたいです。vertex shaderの行列処理とは反対だと思った方が良さそうです。
使える行列を書きだします。

mat4 move(vec3 p) 
{
  return mat4(1,0,0,0,0,1,0,0,0,0,1,0,-p,1);
}

mat4 rotX(float a) 
{
  float s=sin(a),c=cos(a);
  return mat4(1,0,0,0,0,c,s,0,0,-s,c,0,0,0,0,1);
}

mat4 rotY(float a) 
{
  float s=sin(a),c=cos(a);
  return mat4(c,0,s,0,0,1,0,0,-s,0,c,0,0,0,0,1);
}

mat4 rotZ(float a) 
{
  float s=sin(a),c=cos(a);
  return mat4(c,s,0,0,-s,c,0,0,0,0,1,0,0,0,0,1);
}

mat4 rot(vec3 axis, float t) 
{
    vec3 a = normalize(axis);
    float s=sin(t),c=cos(t),r=1.0-c;
    return mat4(
      a.x*a.x*r+c,a.y*a.x*r+a.z*s,a.z*a.x*r-a.y*s,0,
      a.x*a.y*r-a.z*s,a.y*a.y*r+c,a.z*a.y*r+a.x*s,0,
      a.x*a.z*r+a.y*s,a.y*a.z*r-a.x*s,a.z*a.z*r+c,0,
      0,0,0,1);
}

mat4 pointAtX(vec3 d) 
{
    vec3 s = normalize(cross(d,vec3(0,1,0)));
    return inverse(mat4(d,0,cross(s,d),0,s,0,0,0,0,1));
}

mat4 pointAtY(vec3 d) 
{
    vec3 s = normalize(cross(d,vec3(0,0,1)));
    return inverse(mat4(s,0,d,0,cross(s,d),0,0,0,0,1));
}

mat4 pointAtZ(vec3 d) 
{
    vec3 s = normalize(cross(d,vec3(0,1,0)));
    return inverse(mat4(s,0,cross(s,d),0,d,0,0,0,0,1));
}

moveは移動行列(引数は移動ベクトル)
rotX,rotY,rotZは各軸ごとの回転行列(引数は回転角)
rotは任意軸の回転行列(引数は回転軸、回転角)
pointAtX,pointAtY,pointAtZは各軸を任意方向と一致させる行列。これについては私がつくったので怪しいところがあったら適宜直してください。これには補助ベクトルが必要ですが関数の中に組み込んじゃってます。これにはinserveを使っているのでes300でしか使えません。inserveでなくてもtransposeでもいいみたいですがWebGL1.0では使えませんでした。WebGL1.0で使いたいならmat4の中身をtransposeして書き換えてください。これはジンバルロックを起こす可能性があります。補助ベクトルと任意ベクトルの一致は避けてください。
これらをこんな感じで使ってます。

      mat4 m=mat4(1);
      m*=rot(normalize(vec3(1,2,3)),time);
      m*=move(vec3(1,2,3));
      m*=rotZ(time*0.2);
      m*=rotX(time*0.3);
      
     p=(m*vec4(p,1)).xyz;

サンプル

f:id:gaziya:20200616172301g:plain bit.ly

最後に

色々なパターンの行列処理を考えましたが使って覚えるしかないかなって思ってしまいました。グループで行列を作って置いて、それに移動行列を乗算して使ったりとか色々できると思います。個々に回転させてベクトルを足して移動とかしなくていいので管理は楽になると思います。任意軸の回転行列も使えるのでクォターニオンじゃなくてもmat4だけで済んでしまうのもいいと思います。