Browsed by
Month: 2023年6月

[DirectX12] Compute Shader の Thread とシステム値

[DirectX12] Compute Shader の Thread とシステム値

Compute Shader で起動するスレッド数は、コマンドバッファに記録する Dispatch() の引数と、シェーダー内に記述する numthreads の Thread Group 数によって決まる。Dispatch() の引数は、アプリ実行時に動的に変更することが可能だが、numthreads への指定はシェーダーコンパイル時に決まるため、こちらは固定の数になる。

Compute Shader で起動したスレッドは、後述するシステムセマンティックの ID によって、シェーダー内で一意の ID を取得することが出来る。その ID によって処理やデータを分岐して処理する。

Dispatch

実行する Thread Group の数を X, Y, Z で指定する。例えば 1,1,1 で Dispatch した場合は、1つの Thread Group が起動する。

numthreads

コンピュートシェーダーが Dispatch された(起動した)ときの、一つの Thread Group 内で起動するスレッド数を numthreads で指定する。シェーダーコンパイル時に値が決まるため、動的な変更はできない。

System Value

Compute Shader で取得できるシステムセマンティックは4つある。

uint3 gidSV_GroupID
uint3 dtidSV_DispatchThreadID
uint3 gtidSV_GroupThreadID
uint giSV_GroupIndex

SV_GroupID (uint3)

スレッドが、どこの Thread Group で起動しているかを取得できる。Dispatch に指定した値によって変化する。
例えば、Dispatch(2,1,1) を呼び出すと、2*1*1 = 2 Thread Group が起動して、SV_GroupID の値としては(0,0,0)と(1,0,0)という値がスレッド内で得られる。

SV_GroupThreadID (uint3)

スレッドが、Thread Group 内のどの Thread かを表す。numthread に指定した値によって変化する。
例えば、numthreads(3,2,1) が指定された場合は 3*2*1 = 6 Thread 起動する。SV_GroupThreadID としては (0-2, 0-1, 0) 範囲の値が取得できる。

SV_DispatchThreadID (uint3)

Dispatch と numthread の組み合わせによって変化する。

SV_GroupIndex (uint)

起動例 1

Distach(4, 4, 4) / numthread[1,1,1]

SV_GroupID には (0,0,0)~(3,3,3) の値が来る。
SV_GroupID から、一意な Index を取得したい場合は、下記で変換できる。

// index = (gid.z * dispatch_y * dispatch_x) + (gid.y * dispatch_x) + gid.z
uint index = (gid.z * 4 * 4) + (gid.y * 4) + gid.z;

index は 0~63 までの値になる。

[DirectX12] View Matrix での変換は何をしているか

[DirectX12] View Matrix での変換は何をしているか

View 行列を掛けることによって、カメラを基準にした相対座標に変換される。

# ローカルの頂点座標
LocalPos = [0.0,0.0,0.0])

# Model のワールド座標
WorldPos = [1.0,0.0,3.0]

# カメラポジション
eye = [1.0, 0.0,-3.0]
at  = [1.0,0.0,0.0]
up  = [0.0,1.0,0.0]

この座標を使い View 変換行った結果は下記になる。

[0.0, 0.0, 6.0]

Model が(1.0, 0.0, 3.0)、Eye が(1.0, 0.0, -3.0)のところにあるため、カメラから見た相対座標は Z 軸が 6.0 だけ離れているという事。

カメラの向いている方向

View 行列から、カメラの視線の Direction を取得できる。まず、View 行列の計算式は下記の通り。

zaxis = normal(At - Eye)
xaxis = normal(cross(Up, zaxis))
yaxis = cross(zaxis, xaxis)

 xaxis.x           yaxis.x           zaxis.x          0
 xaxis.y           yaxis.y           zaxis.y          0
 xaxis.z           yaxis.z           zaxis.z          0
-dot(xaxis, eye)  -dot(yaxis, eye)  -dot(zaxis, eye)  1

式を見れば分かるが、zaxisは Eye から見た注視点へのベクトルになる(正規化済み)。

eye = [1.0, 0.0,-3.0]
at  = [1.0,0.0,0.0]

この場合の View Matrix の zaxis は (0.0, 0.0, 1.0) になる。

eye = [1.0, 0.0,3.0]
at  = [1.0,0.0,0.0]

この場合はマイナス軸方向を向くことになるので、zaxis は (0.0, 0.0, -1.0) の値になる。

View Matrix

例1

XMVECTOR Eye = XMVectorSet( 0.0f, 0.0f, 3.0f, 0.0f );
XMVECTOR At = XMVectorSet( 0.0f, 0.0f, 0.0f, 0.0f );
XMVECTOR Up = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f );
g_View = XMMatrixLookAtLH( Eye, At, Up );

こちらで定義した場合は、下記のような Matrix が作られる。

{-1.0, 0.0, 0.0, 0.0}
{0.0, 1.0, 0.0, 0.0}
{0.0, 0.0, -1.0, 0.0}
{0.0, -0.0, 3.0, 1.0}

例2

XMVECTOR Eye = XMVectorSet( 1.0f, 0.0f, 3.0f, 0.0f );
XMVECTOR At = XMVectorSet( 0.0f, 0.0f, 0.0f, 0.0f );
XMVECTOR Up = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f );
g_View = XMMatrixLookAtLH( Eye, At, Up );

Eye の X 位置が 1.0f にした状態。

{-0.948, 0.0, -0.316, 0.0}
{0.0, 1.0, 0.0, 0.0}
{0.316, 0.0, -0.948, 0.0}
{-5.96046448e-08, -0.0, 3.16, 1.0}
[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/

[JIRA]プロジェクトを作成

[JIRA]プロジェクトを作成

プロジェクトテンプレートは「スクラム」
プロジェクトのタイプは「チーム管理対象」が使いやすい。
スクラムの場合は、バックログの項目が存在する。

テンプレート「カンバン」
プロジェクトのタイプは「チーム管理対象」でも、シンプルで良い。
ボードの管理のみ。