Browsed by
Category: Python

[Python] Numpy で3D座標変換 その3

[Python] Numpy で3D座標変換 その3

Depth から World Position を復元

3D座標変換その2 で (320. , 33.06740882, 0.9986713, 1.0)という値から、World Position を復元する。
このベクトルの値は SV_POSITION の値として、頂点シェーダーから出力され、ピクセルシェーダーに入ってきた値を float4 の値を想定している。

ここに乗せている Python コードは「3D座標変換その2」に書いているコードの後ろに足せば、動くようにしている。

Depth から World Position を求めるのは以下の通り

  • ピクセルシェーダーに来た座標を NDC 空間に戻す
  • View と Proj の逆行列を求める
  • NDC 空間の座標に、ViewProj の逆行列を掛ける
    • ViewProj 行列の逆行列を求める
    • もしくは View と Proj の逆行列をそれぞれ求めて、InvProj から順番にかけていく。
  • 求めたベクトルの X,Y,Z を W で割る

NDC 空間に戻す

uv = np.array([v1[0,0]/screenWidth, v1[0,1]/screenHeight])
uv = (uv * 2.0) - 1.0
ndc = np.array([uv[0], uv[1], v1[0,2], 1.0])
ndc[1] = ndc[1] * -1.0

SV_Position の XY にはピクセルのスクリーン位置が返ってくる。その値を描画したスクリーンサイズで割ることで、[0,1] 範囲の UV を求めている。そして、NDC の XY 座標は [-1,1] の範囲を取るため、2倍して1引くことで NDC での座標に変換している。

ndc 変数に値をセットしているが、XY は先ほど UV の値から求めた座標を入れて、Z は SV_Position の値をそのまま代入している。(これは、DirectX の NDC の Z が [0,1] のためそのまま使用。[-1,1] の環境では、別途変換が必要)

View Proj の逆行列を掛ける

viewProjInv = np.linalg.inv(view*proj)
wp = np.matmul(ndc, viewProjInv)

#projInv = np.linalg.inv(proj)
#viewInv = np.linalg.inv(view)
#wp = np.matmul(ndc, projInv)
#wp = np.matmul(wp, viewInv)

wp = wp/wp[0,3]

ViewProj の逆行列をかけるか、もしくは、View, Proj の逆行列をそれぞれ求めて、projInv から順番にかけていく。最後に W で割れば、World Position が復元できた。

[[0.  2.5 2.  1. ]]
[Python] Numpy Matrix

[Python] Numpy Matrix

Import

import numpy as np

行列の定義

viewport = np.mat(
    [
        [1, 0, 0, 0]
        [0, 1, 0, 0],
        [0, 0, 1, 0],
        [0, 0, 0, 1]
    ]
)

行列の掛け算

v1 = np.matmul(v1, viewport)

掛ける方向に注意。np.array と np.matrix や np.matrix と np.matrix でも可能。

逆行列を求める

viewProjInv = np.linalg.inv(proj)

要素にアクセス

mat = np.matrix([[0,1]])
print(mat[0,1]) #1

[0,1] という形でカンマで区切るので注意。
他の言語の感覚で mat[0][1] という形でアクセスしようとしてはまった。

[Python] Numpy で3D座標変換 その2

[Python] Numpy で3D座標変換 その2

import numpy as np

# ローカルの頂点座標
v1 = np.array([-1.0,1.0,-1.0,1.0])

# Model のワールド座標
wPost = np.array([1.0,1.5,3.0])

# カメラポジション
eye = np.array([0.0, 0.0,-5.0])
at = np.array([0.0,0.0,0.0])
up = np.array([0.0,1.0,0.0])

# スクリーンサイズ
width = 640
height = 480

# Near/Far Z
zn = 0.01
zf = 100.0

# 移動行列
trans = np.mat(
    [
        [1.0,0.0,0.0,0.0],
        [0.0,1.0,0.0,0.0],
        [0.0,0.0,1.0,0.0],
        [wPost[0],wPost[1],wPost[2],1.0],
    ]
)

v1 = np.matmul(v1,trans)

# zaxis
a = at - eye
b = np.linalg.norm(at-eye)
zaxis = a / b

# xaxis
d = np.cross(up, zaxis)
e = np.linalg.norm(d)
xaxis = d / e

# yaxis
yaxis = np.cross(zaxis, xaxis)

view = np.mat(
        [
            [xaxis[0], yaxis[0], zaxis[0], 0.0],
            [xaxis[1], yaxis[1], zaxis[1], 0.0],
            [xaxis[2], yaxis[2], zaxis[2], 0.0],
            [-np.dot(xaxis, eye), -np.dot(yaxis, eye), -np.dot(zaxis,eye),1.0]
        ]
    )

povy = 0.785398163
yscale = 1.0 / np.tan(povy/2.0)

aspect = width / height
xscale = yscale / aspect

proj = np.mat(
    [
        [xscale, 0, 0, 0],
        [0, yscale, 0, 0],
        [0, 0,  zf / (zf-zn), 1 ],
        [0, 0, -zn * zf / (zf-zn), 0]
    ]
)

v1 = np.matmul(v1,view)
v1 = np.matmul(v1,proj)

w = width/2
h = height/2
viewport = np.mat(
    [
        [w, 0, 0, 0],
        [0, -h, 0, 0],
        [0, 0, 1, 0],
        [w, h, 0, 1]
    ]
)

v1 = v1 / v1[0,3]
v1 = np.matmul(v1, viewport)
v1

Numpy で実行した時の出力結果は下記になる。

matrix([[320.        ,  33.06740882,   0.9986713 ,   1.        ]])

X, Y が座標変換後のスクリーン座標。(320, 33) 位置になる。
Z が Depth Buffer に書かれる値。Z 0.99867

World 座標の Transform で (1.0, 1.5, 3.0) の位置に変換している。

[Python] Numpy で3D座標変換 その1

[Python] Numpy で3D座標変換 その1

World 行列

Scale

Transform

trans = np.mat(
    [
        [1.0,0.0,0.0,0.0],
        [0.0,1.0,0.0,0.0],
        [0.0,0.0,1.0,0.0],
        [1.0,2.0,3.0,1.0],
    ]
)

v1 = np.array([-1.0,1.0,-1.0,1.0])
v1 = np.matmul(v1,trans)
v1

Rotation

View 行列

eye = np.array([0.0, 0.0,-5.0])
at = np.array([0.0,0.0,0.0])
up = np.array([0.0,1.0,0.0])

# zaxis
a = at - eye
b = np.linalg.norm(at-eye)
zaxis = a / b

# xaxis
d = np.cross(up, zaxis)
e = np.linalg.norm(d)
xaxis = d / e

# yaxis
yaxis = np.cross(zaxis, xaxis)

view = np.mat(
        [
            [xaxis[0], yaxis[0], zaxis[0], 0.0],
            [xaxis[1], yaxis[1], zaxis[1], 0.0],
            [xaxis[2], yaxis[2], zaxis[2], 0.0],
            [-np.dot(xaxis, eye), -np.dot(yaxis, eye), -np.dot(zaxis,eye),1.0]
        ]
    )

view

Projection 行列

povy = 0.785398163
yscale = 1.0 / np.tan(povy/2.0)

aspect = 1.333333333
xscale = yscale / aspect

zn = 0.01
zf = 100.0

proj = np.mat(
    [
        [xscale, 0, 0, 0],
        [0, yscale, 0, 0],
        [0, 0,  zf / (zf-zn), 1 ],
        [0, 0, -zn * zf / (zf-zn), 0]
    ]
)

proj

頂点を計算

座標 (-1.0, 1.0, -1.0) の頂点を View, Proj 変換を行う。

v1 = np.array([-1.0,1.0,-1.0,1.0])
v1 = np.matmul(v1,view)
v1 = np.matmul(v1,proj)

v1 頂点を変換して、頂点シェーダーからの出力として、SV_POSITION に渡される値は下記になる。

[-1.81066017,  2.41421356,  3.99039904,  4.        ]

なお、この時点での座標は w が 4.0 の同次座標と呼ばれるものになっている。w 4.0 を使って xyz を割る(正規化する)ことにより、座標は NDC(Normalized Device Coordinate)と呼ばれる空間に移る。

この空間は xy が [-1, 1] で、z が [0, 1] の範囲の座標になる。(OpenGL の場合は z は [-1, 1] まで)w で割った後の座標は下記になる。

[-0.45266504,  0.60355339,  0.99759976]

xy が [-1,1] で、z が [0,1] の範囲に収まっている。この値を、次に説明する Viewport のスケール行列を掛けることにより、スクリーン上の座標へとスケールする。

ラスタライザで Pixel 位置を計算

頂点は v1 / v1[0,3] で、w で割っている。その後に Viewport のスケール行列を掛ける。

w = 640.0/2
h = 480.0/2
viewport = np.mat(
    [
        [w, 0, 0, 0],
        [0, -h, 0, 0],
        [0, 0, 1, 0],
        [w, h, 0, 1]
    ]
)

v1 = v1 / v1[0,3]
v1 = np.matmul(v1, viewport)
v1

v1 の結果として、下記になる。

[175.14718614,  95.14718618,   0.99759976,   1.        ]

この結果により座標変換を行った頂点は Viewport 640×480 の 175, 96 位置にラスタライザされる。 Depth Buffer に書かれるのは、この時の3つ目の要素 0.9976 あたりがこの時の深度値。

参考

https://shikihuiku.github.io/post/projection_matrix/

[Python] Numpy で行列演算

[Python] Numpy で行列演算

行列の掛け算

numpy.ndarray を 2つ定義して np.matmul() で計算する。

import numpy as np
data1 = np.array(
    [
        [0, 1, 2], 
        [3, 4, 5],
        [6, 7, 8],
    ]
)

data2 = np.array(
    [
        [0, 1, 2], 
        [3, 4, 5],
        [6, 7, 8],
    ]
)

result = np.matmul(data1, data2)
print(result)

実行結果

[[ 15  18  21]
 [ 42  54  66]
 [ 69  90 111]]

逆行列を求める

逆行列は np.linalg.inv() を使用する

import numpy as np

data1 = np.array([
    [0,1,3],
    [3,4,5],
    [6,7,8]]
)
                
data1_inv = np.linalg.inv(data1)

print(data1_inv)
print(np.matmul(data1,data1_inv))

実行結果

[[ 1.         -4.33333333  2.33333333]
 [-2.          6.         -3.        ]
 [ 1.         -2.          1.        ]]
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
[Python] Anaconda で Numpy を使用する

[Python] Anaconda で Numpy を使用する

Numpy のインストール

新規で Python 環境を作成し Open Terminal でターミナルを立ち上げる。

conda install numpy

このコマンドで Numpy がインストールできる。

実行

Jupyter Notebook を立ち上げる。

Jupyter Notebook

Python3 の実行環境を作成して、下記コードを入力する。

import numpy as np
data = np.array(
    [
        [0, 1, 2], 
        [3, 4, 5],
        [6, 7, 8],
    ]
)

print(data)
print(type(data))

Shift + Enter で実行してエラーが無ければ成功。

[Windows] Anaconda の導入

[Windows] Anaconda の導入

インストール

公式サイトのダウンロードボタンから、インストール用の exe をダウンロードして実行する。インストール時は全てデフォルトのまま Next を押して進めていく。途中の

Register Anaconda 3 as my default Python

にチェックをが入っていると、他の Python ツールでも Anaconda の Base 環境がデフォルトとして使用される。インストール環境によっては稀に問題が起こるケースもあるのでチェックには注意。

新規環境を作成

Environments タブを選択すると下のほうに「Create」ボタンがあるのでそれを押す。「Create new environment」ウインドウが立ち上がるので、作成する環境の名前を入力して、Python にチェックを入れて Create ボタンを押す。

Jupyter notebook を起動

新規作成した環境の矢印ボタンから「Open Terminal」を選択。下記コマンドを入力する。

Jupyter notebook

明示的にインストールしなくても、Jupyter notebook が使用できる。web サーバーが起動して既定のブラウザが立ち上がる。

Python コードを記述

Python3 の実行環境を作成する。

Python コードを記述して、Shift + Enter を押せば実行できる。