ShaderTips

シェーダーTips

主にUnityシェーダーについての記事を書いています。

WVP行列(ワールド・ビュー・プロジェクション行列)の中身を見て、設定した値を抜き出す

WVP行列の解説とそれぞれの行列から要素を抜き出す方法について解説します。

要素を抜き出せるとシェーダー 内で行列から、様々な情報を得ることができます。

 

注意していただきたいのが、行列から成分を抜き取る際に行列の種類によって同じ行列でも取得の方法が変化します。行列には列オーダーと行オーダーがあります。

 

要は列オーダと行オーダーで行列の要素が縦横入れ替わります。この記事では行オーダーになっておりますので、ご注意ください。

 

参考資料です。

大変、分かりやすく解説されています。

MVP行列による座標変換について

その39 知っていると便利?ワールド変換行列から情報を抜き出そう

その70 完全ホワイトボックスなパースペクティブ射影変換行列

その72 ビュー・射影変換行列が持つ情報を抜き出そう

ワールド行列

ローカル空間(モデル上の座標)からワールド空間(シーン上の座標)へ移動するワールド行列はスケール行列 x 回転行列 x 平行移動行列で構成されています。

順番は問われませんが、行列はかける順番で結果が変わってしまうため、この順番が一番思い通りの位置に操作できます。

ワールド行列の中身はこんな感じになっています。

ワールド行列を構成している行列について解説していきます。

f:id:Ny_Program:20190914213311g:plain

その39 知っていると便利?ワールド変換行列から情報を抜き出そう : より引用

オフセット行列 

平行移動させるための行列です。

  

{\displaystyle 
    \begin{pmatrix}
      x & y & z & 1 
    \end{pmatrix}
    \begin{pmatrix}
      1 & 0 & 0 & 0 \\
      0 & 1 & 0 & 0 \\
      0 & 0 & 1 & 0\\
     Tx & Ty & Tz & 1\\
    \end{pmatrix}
    =
    \begin{pmatrix}
      x+Tx & y+Ty & z+Zz & 1 \\
    \end{pmatrix}
}

 

下の座標に移動量であるTが加算されています。

 

ワールド行列からオフセット値の取り出し方

ワールド行列(W)を見るとオフセット行列で使用した移動量Tがそのまま残っています。

行オーダー

T(x,y,z) = W[3].xyz

列オーダー

T(x,y,z) = mul(W , float(0,0,0,1)).xyz

 

スケール行列

原点を中心として拡縮させるための行列です。

                                          

{\displaystyle 
   \begin{pmatrix}
      x & y & z & 1 
    \end{pmatrix}
    \begin{pmatrix}
      Sx & 0 & 0 & 0 \\
      0 & Sy & 0 & 0 \\
      0 & 0 & Sz & 0\\
      0 & 0 & 0 & 1\\
    \end{pmatrix}
    =
      \begin{pmatrix}
      SxX & SyY & SzZ & 1
    \end{pmatrix}
    }

Sが乗算されています。

 

ワールド行列からスケール値の取り出し方

回転成分を無視するために長さを求めます。

 

行オーダー

Sx = Length( W[0] )

Sy = Length( W[1] )

Sz = Length( W[2] )

列オーダー

Sx = Length( mul(W , float(1,0,0,0)) )

Sy = Length( mul(W , float(0,1,0,0)) )

Sz Length( mul(W , float(0,0,1,0)) )

乗算を使用するため、計算コストと誤差が生じるので注意です。

 

回転行列

ローカル座標にある点を1つの軸を中心軸として回転させるための行列です。

 

X軸回転

{\displaystyle 
 \begin{pmatrix}
      x & y & z & 1 
    \end{pmatrix}
    \begin{pmatrix}
      1 & 0 & 0 & 0 \\
      0 & cos & sin & 0 \\
      0 & -sin & cos & 0\\
     0 & 0 & 0 & 1
    \end{pmatrix}
    =
     \begin{pmatrix}
      x & yCos-zSin & ySin+zCos & 1 
    \end{pmatrix}
}
軸となっているX座標は変わらず、YとZで回転しています。

 

Y軸回転

{\displaystyle 
   \begin{pmatrix}
      x & y & z & 1 
    \end{pmatrix}
    \begin{pmatrix}
      cos & 0 & -sin & 0 \\
      0 & 1 & 0 & 0 \\
      sin & 0 & cos & 0\\
      0 & 0 & 0 & 1
    \end{pmatrix}
    =
      \begin{pmatrix}
      xCos+zSin & y & -xSin+zCos & 1
    \end{pmatrix}
}

  

Z軸回転 

{\displaystyle 
 \begin{pmatrix}
      x & y & z & 1 
    \end{pmatrix}
    \begin{pmatrix}
      cos & sin & 0 & 0 \\
      -sin & cos & 0 & 0 \\
      0 & 0 & 0 & 0\\
     0 & 0 & 0 & 1
    \end{pmatrix}
     =
      \begin{pmatrix}
      xCosーySin & xSin+yCos & z & 1
    \end{pmatrix}
}

 

ワールド行列からローカル座標でどこを向いているか  

ワールド行列からワールド座標の回転量を求めるのは制約の厳しさと計算コストから、実用的ではありません。

しかし、ローカル座標でどこを向いているかは簡単に抜き出せます。

行オーダー

localRx(x,y,z) = W[0].xyz

localRy(x,y,z) = W[1].xyz

localRz(x,y,z) = W[2].xyz

列オーダー

localRx(x,y,z) = mul(W , float(1,0,0,0)) ).xyz

localRy(x,y,z) = mul(W , float(0,1,0,0)) ).xyz

localRz(x,y,z) = mul(W , float(0,0,1,0)) ).xyz

 

回転行列の原理

Z軸の回転行列 解説

2Dでの説明になりますが、実質、Z軸の回転になります。

f:id:Ny_Program:20190914212853p:plain

MVP行列による座標変換について : より引用

OPとOQの長さをrとします。

OPとx軸のなす角度が45度=π/4なのでP(x,y)についてこのような式ができます。

x = r x cos(π/4) 

y = r x sin(π/4)

 

Q(x',y')に対しても同じように式ができます。

x = r x cos(π/4 + π/6) 

y = r x sin(π/4 + π/6)

一つ上の式を加法定理で解くとこのように変形できます。

x = r x ( cos(π/4) x cos(π/6) - sin(π/4) x sin(π/6) )

y = r x ( sin(π/4) x cos(π/6) + cos(π /4) x sin(π/6) )

 

加法定理で変形した式と①連立方程式としてときます。

①で r x cos(π/4)にxを代入

x = cos(π/6)x - sin(π/6)y

②で r x sin(π/4)にxを代入

y = sin(π/6)x + cos(π/6)y

 

これをθに置き換え一般化します。

x = cosθx - sinθy

y = sinθx + cosθy

 

行列にするとこのようになります。

{\displaystyle 
    \begin{pmatrix}
    x & y \\
    \end{pmatrix}
    \begin{pmatrix}
    cos & sin \\
    -sin & cos
    \end{pmatrix}
}

 

Z軸の回転行列と一致しているのが分かります。

 

ビュー行列

ビュー行列は先ほどのワールド行列でワールド空間に変換したオブジェクトをカメラから見た座標に変換する行列です。

先ほどのワールド行列とほぼ同じですが、いくつか違う点があります。

まずカメラについて考えます。

カメラにはスケールという概念がありません。なので回転行列と移動行列で再現できます。ただ、カメラの行列をそのままワールド空間にあるオブジェクトにかけても、カメラから見た座標にはなりません。

今回やりたいことはカメラから見た相対座標を求めることなのでカメラの行列の逆行列をかけます。これがビュー行列になります。

 

Xx、Yx、Zxはそれぞれの回転軸の回転

P・X P・Y P・Zは座標

上がカメラ行列で下がビュー行列になります。

f:id:Ny_Program:20190914224422p:plain

ビュー座標変換 : より引用 

ビュー行列から座標とローカル座標での回転の取り出し方

カメラ行列からビュー行列に変換する際に逆行列にしている関係で、行オーダーと列オーダーを逆にすればワールド行列と同じように取得する事ができます。

 

 

プロジェクション行列

ビュー行列によりカメラから見たオブジェクトの座標、角度、スケールが決まりました。現在のイメージです。

 

f:id:Ny_Program:20190915203326g:plain

その70 完全ホワイトボックスなパースペクティブ射影変換行列:より引用

 

ただ、シーンにある全てのオブジェクトを描画することはありません。

視界に入ったオブジェクトのみを描画します。視界を表現するために画像のような視錐台に入るオブジェクトのみを描画します。

また、奥のものほど小さく見える法則を実現する必要があります。

プロジェクション行列の目標はこのような幅と高さが2、奥行きが1の空間に変換することです。この座標系を正規デバイス座標系と言います。

 

f:id:Ny_Program:20190915205556g:plain

その70 完全ホワイトボックスなパースペクティブ射影変換行列:より引用

 

 イメージはこんな感じです。

f:id:Ny_Program:20190915205757g:plain

MVP行列による座標変換について : より引用

 

STEP1 縦と横の比率を合わせる

f:id:Ny_Program:20190915203326g:plain

その70 完全ホワイトボックスなパースペクティブ射影変換行列:より引用 

 

 

現在、台はスクリーンのwidth : heightになっています。

これを1:1になるように幅を高さに合わせるために幅 X {\displaystyle\frac{height}{width}}をします。

 

f:id:Ny_Program:20190916141005g:plain

その70 完全ホワイトボックスなパースペクティブ射影変換行列:より引用

 

計算式を行列にするとこうなります。

{\displaystyle 
    \begin{pmatrix}
      {\displaystyle\frac{height}{width}}& 0 & 0 & 0 \\
      0 & 1 & 0 & 0 \\
      0 & 0 & 1 & 0\\
      0 & 0 & 0 & 1\\
    \end{pmatrix}
}

 STEP2 台の幅高さを(2、2)に

視錐台の幅高さをクリップ空間の幅と高さと同じ(2、2)にします。

STEP1で 視錐台のどこかに赤い枠のように幅高さが(2、2)になっている部分が存在するはずです。この赤い枠より奥の部分は広いので狭くし、近い部分は狭いので広くします。

f:id:Ny_Program:20190916152503g:plain

その70 完全ホワイトボックスなパースペクティブ射影変換行列:より引用

 

下の図は視錐台を横から見た図になります。

高さを2にするためにsyを半分の1にします。その為にはsyの逆数である{\displaystyle\frac{1}{sy}}を掛けます。syを求めるには画角{\displaystyle\frac{θ}{2}}直角三角形から求めます。

sy = z x tanθ({\displaystyle\frac{θ}{2}})

 

syを逆数にするために上の式のzを({\displaystyle\frac{1}{z}})にtanをcotに変換します。

(cotは{\displaystyle\frac{1}{tanx}}のことです。)

syの逆数 = ({\displaystyle\frac{1}{z}}) x cot({\displaystyle\frac{θ}{2}})

f:id:Ny_Program:20190916154507g:plain

その70 完全ホワイトボックスなパースペクティブ射影変換行列:より引用

行列にするとこうなります。
図は高さだけの説明でしたが幅も同じように2にできます。
{\displaystyle 
    \begin{pmatrix}
     {\displaystyle\frac{1}{z}} cot \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix}& 0 & 0 & 0 \\
      0 & {\displaystyle\frac{1}{z}} cot \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix}& 0 & 0 \\
      0 & 0 & 1 & 0\\
      0 & 0 & 0 & 1\\
    \end{pmatrix}
}

 

ただ問題が一つあります。それは行列内にZが含まれていることです。

行列はCPUで計算し、定数としてシェーダーに渡すので、WVP行列を乗算するバーテックスシェーダーでしか分からないZを使用することができません。

なので、一旦行列の外に出しておきます。

{\displaystyle 
    \begin{pmatrix}
     cot \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix}& 0 & 0 & 0 \\
      0 &cot \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix}& 0 & 0 \\
      0 & 0 & 1 & 0\\
      0 & 0 & 0 & 1\\
    \end{pmatrix}
     \begin{pmatrix}
     {\displaystyle\frac{1}{z}}& 0 & 0 & 0 \\
      0  & {\displaystyle\frac{1}{z}}& 0 & 0 \\
      0 &  0  & 1 & 0\\
      0 & 0 & 0 & 1\\
    \end{pmatrix}
}

 

STEP3 -nZだけずらして(fZ-nZだけ縮める) 

 

f:id:Ny_Program:20210509151510g:plain


   

その70 完全ホワイトボックスなパースペクティブ射影変換行列:より引用

 

STEP1とSTEP2で高さと幅を2にすることができました。

あとはnZを原点まで移動させればクリップ空間になります。

 

 

原点z=0にnZをくっつけるには-nZ移動させます。

奥行きを1にするにはfZ-nZ倍縮小します。

{\displaystyle 
    \begin{pmatrix}
      1 & 0 & 0 & 0 \\
      0 & 1 & 0 & 0 \\
      0 & 0 & 1 & 0\\
      0 & 0 & -nZ & 1\\
    \end{pmatrix}
     \begin{pmatrix}
      1 & 0 & 0 & 0 \\
      0 & 1 & 0 & 0 \\
      0 & 0 & {\displaystyle\frac{1}{fz-nz}} & 0\\
      0 & 0 & 0 & 1\\
    \end{pmatrix}
    =
     \begin{pmatrix}
      1 & 0 & 0 & 0 \\
      0 & 1 & 0 & 0 \\
      0 & 0 & {\displaystyle\frac{1}{fZ-nZ}} & 0\\
      0 & 0 & {\displaystyle\frac{-nZ}{fZ-nZ}} & 1\\
    \end{pmatrix}
}

 

STEP1,2,3を全部かけたものがプロジェクション行列になります。

{\displaystyle 
    \begin{pmatrix}
      {\displaystyle\frac{height}{width}}& 0 & 0 & 0 \\
      0 & 1 & 0 & 0 \\
      0 & 0 & 1 & 0\\
      0 & 0 & 0 & 1\\
    \end{pmatrix}
     \begin{pmatrix}
     cot \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix}& 0 & 0 & 0 \\
      0 &cot \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix}& 0 & 0 \\
      0 & 0 & 1 & 0\\
      0 & 0 & 0 & 1\\
    \end{pmatrix}
        \begin{pmatrix}
      1 & 0 & 0 & 0 \\
      0 & 1 & 0 & 0 \\
      0 & 0 & {\displaystyle\frac{1}{fZ-nZ}} & 0\\
      0 & 0 & {\displaystyle\frac{-nZ}{fZ-nZ}} & 1\\
    \end{pmatrix}
}

{\displaystyle 
  =
  \begin{pmatrix}
    {\displaystyle\frac{height}{width}}{\displaystyle\frac{1}{z}} cot \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix}& 0 & 0 & 0 \\
      0 & \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix} & 0 & 0 \\
      0 & 0 & {\displaystyle\frac{1}{fZ-nZ}} & 0\\
      0 & 0 & {\displaystyle\frac{-nZ}{fZ-nZ}} & 1\\
    \end{pmatrix}
}

 

試しに{\displaystyle 
    \begin{pmatrix}
      x & y & z & 1 
    \end{pmatrix}
}を変換してみます。

{\displaystyle 
     \begin{pmatrix}
      x & y & z & 1 
    \end{pmatrix}
  \begin{pmatrix}
    {\displaystyle\frac{height}{width}}{\displaystyle\frac{1}{z}} cot \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix}& 0 & 0 & 0 \\
      0 & \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix} & 0 & 0 \\
      0 & 0 & {\displaystyle\frac{1}{fZ-nZ}} & 0\\
      0 & 0 & {\displaystyle\frac{-nZ}{fZ-nZ}} & 1\\
    \end{pmatrix}
    =
      \begin{pmatrix}
       {\displaystyle\frac{x'}{z}} & {\displaystyle\frac{y'}{z}} & z' & 1
    \end{pmatrix}
}

 

結果をみてわかる通り、XYZ成分のZ値を割る必要があるのですがプロジェクション行列はそれを達成することができません。

プロジェクション行列を掛けた状態をクリップ座標系と言います。

 

 

クリップ座標系を正規デバイス座標系に変換する方法を説明します。

STEP2で説明した通り、行列の中にはZの情報がありません。

これを解決するには行列のどこかにZ座標を格納する必要があります。現在使われていないw成分を使います。

{\displaystyle 
  =
  \begin{pmatrix}
    {\displaystyle\frac{height}{width}}{\displaystyle\frac{1}{z}} cot \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix}& 0 & 0 & 0 \\
      0 & \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix} & 0 & 0 \\
      0 & 0 & {\displaystyle\frac{1}{fZ-nZ}} & 1\\
      0 & 0 & {\displaystyle\frac{-nZ}{fZ-nZ}} & 0\\
    \end{pmatrix}
}

w成分の1の位置がずれました。これで再度変換をしてみます。

{\displaystyle 
 \begin{pmatrix}
      x & y & z & 1 
    \end{pmatrix}
  \begin{pmatrix}
    {\displaystyle\frac{height}{width}}{\displaystyle\frac{1}{z}} cot \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix}& 0 & 0 & 0 \\
      0 & \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix} & 0 & 0 \\
      0 & 0 & {\displaystyle\frac{1}{fZ-nZ}} & 1\\
      0 & 0 & {\displaystyle\frac{-nZ}{fZ-nZ}} & 0\\
    \end{pmatrix}
    =
      \begin{pmatrix}
       {\displaystyle\frac{x'}{z}} & {\displaystyle\frac{y'}{z}} & z' & z 
    \end{pmatrix}
}

計算結果のw成分にZが格納されています。ラスタライザーはここにZが格納されている前提で、W成分を除算してくれます。XYZ成分をW成分で除算してみます。

{\displaystyle 
    \begin{pmatrix}
      {\displaystyle\frac{x'}{z}} & {\displaystyle\frac{y'}{z}} & {\displaystyle\frac{z'}{z}}
    \end{pmatrix}
}

しかし、Z成分にzが除算されてしまっています。

この問題を解決したものが最終的なプロジェクション行列になります。

{\displaystyle 
 \begin{pmatrix}
      x & y & z & 1 
    \end{pmatrix}
  \begin{pmatrix}
    {\displaystyle\frac{height}{width}}{\displaystyle\frac{1}{z}} cot \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix}& 0 & 0 & 0 \\
      0 & \begin{pmatrix}{\displaystyle\frac{θ}{2}}\end{pmatrix} & 0 & 0 \\
      0 & 0 & fZ{\displaystyle\frac{1}{fZ-nZ}} & 1\\
      0 & 0 & fZ{\displaystyle\frac{-nZ}{fZ-nZ}} & 0\\
    \end{pmatrix}
      =
      \begin{pmatrix}
       x' & y' & fZ・z' & z 
    \end{pmatrix}
}

結果のZ成分に視錐台のfZがついてしまっていますが、これでOKです。

z'が正規デバイス座標系のZ値で0〜1になるので、0〜1 x fZ = 0〜fZをとります。除算するw成分のzは視錐台での座標でnZ〜fZをとり、最終的なzは0〜fZ / nZ〜fZ = 0〜1になります。

このzは深度バッファに格納され描画の優先順などに使用されます。

 

 

 

長くなってしまったので今回はここまでです。

最後まで読んでいただきありがとうございます。