本日はShaderの勉強を行います。
先日、本ブログとは別に英語の記事で[SpatialAwareness]に関し紹介しました。
一昨年HoloLensのふるさとアメリカ合衆国Seattleでの旅で出会ったMicrosoftのMRTKを作っておられる方からメッセージがあり、『いいね!TriplanaerMappingと言えば最近こんな記事読んでるんだよね! HoloLensではまだだけど従来よりも軽くなる可能性があるよ!』と次の記事を公開してくださいました!
今回は改めて[TriplanerMapping]に関してと紹介していただいた記事を訳しながらHoloLensに取り組めないかチャレンジしていきます。
前回はそもそもTriplanerMappingとは何かを復習しました。
今回は新技法である[Biplanar mapping]を勉強します。
〇Biplanar mapping
TriplanerMappingの利便さを持ちながらより軽量化された表現の技法としてBiplanarMappingが考案されました。
まず、TriplanerMappingの処理の重さがどのような点にあるかを考える必要があります。
Shaderが動作するGPUではCPUに比べ並列処理が得意です。 このため数桁に及ぶ数値計算が得意なものではあるのですが、TriplanerMappingの場合3つの軸からテクスチャを投影、合成します。
このテクスチャの処理が重いものとなってしまいます。
そのためテクスチャを貼る回数を3回から2回に減らすことを行えばその分処理が軽くなります。 さて、この方法ですが、3軸の投影のうち関連度の高い2軸を選別しマッピングを行います。 3つ目の軸を無視することで処理を大きく軽くするという手法のようです。
GLSLは次のリンクからみることができます。
// "p" point being textured // "n" surface normal at "p" // "k" controls the sharpness of the blending in the transitions areas // "s" texture sampler vec4 biplanar( sampler2D sam, in vec3 p, in vec3 n, in float k ) { // grab coord derivatives for texturing vec3 dpdx = dFdx(p); vec3 dpdy = dFdy(p); n = abs(n); // determine major axis (in x; yz are following axis) ivec3 ma = (n.x>n.y && n.x>n.z) ? ivec3(0,1,2) : (n.y>n.z) ? ivec3(1,2,0) : ivec3(2,0,1) ; // determine minor axis (in x; yz are following axis) ivec3 mi = (n.x<n.y && n.x<n.z) ? ivec3(0,1,2) : (n.y<n.z) ? ivec3(1,2,0) : ivec3(2,0,1) ; // determine median axis (in x; yz are following axis) ivec3 me = ivec3(3) - mi - ma; // project+fetch vec4 x = textureGrad( sam, vec2( p[ma.y], p[ma.z]), vec2(dpdx[ma.y],dpdx[ma.z]), vec2(dpdy[ma.y],dpdy[ma.z]) ); vec4 y = textureGrad( sam, vec2( p[me.y], p[me.z]), vec2(dpdx[me.y],dpdx[me.z]), vec2(dpdy[me.y],dpdy[me.z]) ); // blend factors vec2 w = vec2(n[ma.x],n[me.x]); // make local support w = clamp( (w-0.5773)/(1.0-0.5773), 0.0, 1.0 ); // shape transition w = pow( w, vec2(k/8.0) ); // blend and return return (x*w.x + y*w.y) / (w.x + w.y); }
TriplanerMappingに対してこちらのルーチンは長いです。
しかしテクスチャに関する処理は次のように2つに減っています。
vec4 x = textureGrad( sam, vec2( p[ma.y], p[ma.z]), vec2(dpdx[ma.y],dpdx[ma.z]), vec2(dpdy[ma.y],dpdy[ma.z]) ); vec4 y = textureGrad( sam, vec2( p[me.y], p[me.z]), vec2(dpdx[me.y],dpdx[me.z]), vec2(dpdy[me.y],dpdy[me.z]) );
この関数が行う処理としては3軸の投影のうちどの投影がメッシュに対してゆがみなく整列するかを選択することです。
メッシュの持つ法線のXYZ軸成分の絶対値を比較し大きいほうを抽出することで、XYZ軸のどれが一番平行に近いかを判別します。
ivec3 ma = (n.x>n.y && n.x>n.z) ? ivec3(0,1,2) : (n.y>n.z) ? ivec3(1,2,0) : ivec3(2,0,1) ;
つまりメッシュに対して平行にテクスチャを貼れる2軸を抽出し必要のない1軸を排除するという考えです。
これらの最大の射影のインデックスを変数[ma]の最初のコンポーネントに格納します。
例えば maのx成分ma.xが0の場合、最大の投影がX軸に沿っていることを意味し、値が1の場合はY軸を参照し、2の場合はZ軸を参照します。
3つの座標のうち最も小さいものを見つけることは、テクスチャリングを行っている現在のメッシュとの平行度が低い投影を教えてくれます。これは変数 [mi] に格納されます。
ivec3 mi = (n.x<n.y && n.x<n.z) ? ivec3(0,1,2) : (n.y<n.z) ? ivec3(1,2,0) : ivec3(2,0,1) ;
このようにして一番制度のよい一軸を抽出します。
中央値の軸、つまり2番目に良い投影に関しては長軸と短軸を除くと、3つの軸しかないので、中央値は必ず中央値になります。これを変数[me]に格納して、me = 3-ma-mi という演算によって2番目に適切な軸が得られます。
ivec3 me = ivec3(3) - mi - ma;
以上で2軸が抽出されます。次にこれらをフィッチします。
vec4 x = texture( sam, vec2( p[ma.y], p[ma.z]) ); vec4 y = texture( sam, vec2( p[me.y], p[me.z]) );
単純にフィッチする際に上記のように合成してはアーチファクト(ノイズ)が発生しする可能性があります。これは二つの投影は不連続的な関数になってしまうためです。
そこで軸を選択し投影を行う前にそれらのグラデーションを取りサンプリングを取ることで不連続性を解消します。
vec3 dpdx = dFdx(p); vec3 dpdy = dFdy(p); ... vec4 x = textureGrad( sam, vec2( p[ma.y], p[ma.z]), vec2(dpdx[ma.y],dpdx[ma.z]), vec2(dpdy[ma.y],dpdy[ma.z]) ); vec4 y = textureGrad( sam, vec2( p[me.y], p[me.z]), vec2(dpdx[me.y],dpdx[me.z]), vec2(dpdy[me.y],dpdy[me.z]) );
これらを行うことで従来の3軸からの投影を2軸に減らし処理を軽くすることができるようです。これはGLSLで記述されたコードです。ShaderlabでUnityに再現してみたいです。