--shader(GLSL)の小技 03-- カメラの使い方
今回はカメラの使い方についてやってみます。これについては、色々を書き方があって人それぞれですが、ここではmat3()を使うやり方を使います。ここをブラックボックスで使うのもアリだけど、理解して使った方が、スッキリしてshaderが書けるし、3D空間のコントロールのスキルも上がるので、少し詳しく説明していきます。ただ、図を描くのが苦手なので、言葉だけで説明になります。なのて頭でイメージしてください。
これを理解するには、cross()で導かれるベクトルの性質をイメージできないと始まりません。イメージで説明します。アナログ時計の時針と分針を2つのベクトルとすれば、2つのベクトルのcross()は軸棒になります。軸棒と時針は直交してます。軸棒と分針は直交してます。これを頭に入れながら話を進めます。
vec3 w=normalize(ta-ro); vec3 u=normalize(cross(w,vec3(0,1,0))); vec3 v=cross(u,w); mat3 lookat=mat3(u,v,w);
カメラ部分だけを書きだしました。
まず、視点の位置をro,目標をtaにした場合のrayの方向の単位ベクトルを求めます。wとおきます。これがカメラの方向ベクトルになります。
このベクトルと直角のベクトルを取得する為に、補助ベクトルを用意します。通常は重力方向のvec3(0,1,0)。ここでcross()の出番です。wとvec3(0,1,0)を入力するとwに対して直角のベクトルが取得できます。normalize()をして、uとおきます。wとvec3(0,1,0)は直交しているとは限らないので、uとwをcross()に入力してベクトルを取得。これは直交している単位ベクトル同士なのでnormalize()の必要はありません。これでu,v,wのそれぞれが直交する単位ベクトルが手に入りました。これらをmat3(u,v,w)としてlookAt行列して使用できます。
mat3(u,v,w)*rd
は u*rd.x+v*rd.y+w*rd.z
又 vec3(dot(rd,u), dot(rd,v), dot(rd,w))
はと同等です。uはrdのxの成分、vはrdのyの成分、wはrdのwの成分に対応してる感じが、こいつの正体(この1文に関しては、ニュアンスで言ってます。上手く表現できないです)。
補助ベクトルは、必ずvec3(0,1,0)でなくても良いです。wと同じでなければ構いません。真下を見たいなら補助ベクトルのvec3(0,1,0)と重なってしまうのでvec3(0,0,1)とかにしないとなりません。視界を斜めにしたければ、補助ベクトルをnormalize(vec3(0.2,1,0))とかにしても良いと思います。
今回はtaをわざわざ書いたけどta=vec3(0) なら必要ないので、vec3 w=normalize(-ro);
で十分でしょう。
今回のshader (shadertoy rule)
mat2 rotate(float a) { return mat2(cos(a),sin(a),-sin(a),cos(a)); } float deBox(vec3 p) { return length(max(abs(p)-vec3(1),0.0)); } float map(vec3 p) { return deBox(p); } void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 uv=(fragCoord*2.0-iResolution.xy)/iResolution.y; vec3 ro=vec3(0,2,-5); ro.zx*=rotate(iTime*0.5); vec3 rd=normalize(vec3(uv,2)); vec3 ta=vec3(0); vec3 w=normalize(ta-ro); vec3 u=normalize(cross(w,vec3(0,1,0))); vec3 v=cross(u,w); mat3 lookat=mat3(u,v,w); rd=lookat*rd; float d,i,t=0.0; vec3 p=ro; vec3 col=vec3(0); for(i=1.0;i>0.0;i-=1./50.0) { t+=d=map(p); if(d<0.001) { col+=i*i; break; } p+=rd*d; } fragColor=vec4(col,1.0); }