レンダリングイメージ上の1点より、ワールド座標位置を計算 (スクリプト)

ワールド座標からレンダリングイメージ上の位置とZ(Depth)値を計算」の逆の計算を行います。
入力情報はレンダリングイメージ上の1点を表す座標位置(sx, sy)です。
なお、ここでのカメラは透視投影で魚眼などの変形処理がない状態である必要があります。


import numpy
import math

scene = xshade.scene()

# -------------------------------------------------.
# 近クリップ面までの距離を計算.
def m_calcNearClip (zDist):
  wvMat = numpy.matrix(scene.world_to_view_matrix)
  wdMat = numpy.matrix(scene.world_to_device_matrix)
  nearDist = 0.0

  # ビュー座標でのzDist距離をワールド座標に変換.
  vDir = numpy.array([0.0, 0.0, zDist, 1.0])
  wDir = numpy.dot(vDir, wvMat.I)
  wDir = numpy.array([wDir[0,0], wDir[0,1], wDir[0,2],wDir[0,3]])

  # ワールド座標でのwDirベクトルをデバイス座標に変換.
  v4 = numpy.dot(wDir, wdMat)
  v4 = [v4[0,0], v4[0,1], v4[0,2], v4[0,3]]
  z = v4[2]
  w = v4[3]
  if w > z:
    nearDist = w - z

  return nearDist

def calcNearClip ():
  nearDist = m_calcNearClip(-5000.0)
  if math.fabs(nearDist) < 1e-7:
    nearDist = m_calcNearClip(5000.0)

  return nearDist

# -------------------------------------------------.
# マルチパスの"ZDepth"より、ビュー座標でのZ距離値を取得.
def getZDepth (x, y):
  zDist = 0.0
  depthLayer = scene.rendering.image_layer('ZDepth')
  width  = depthLayer.image.size[0]
  height = depthLayer.image.size[1]

  if depthLayer != None and x >= 0.0 and x < width and y >= 0.0 and y < height:
    col = depthLayer.image.get_pixel_rgba(x, y)
    zDist = col[0]

  return zDist

# -------------------------------------------------.
# レンダリングイメージ上の位置.
sx = 299.0
sy = 428.0

# 近クリップ面までの距離を計算.
nearPlane = calcNearClip()

# (sx, sy)の位置でのZ値を取得.
zDepth = getZDepth(sx, sy)
if zDepth <= 0.0:
  # Depthとの衝突がない.
  print "none Depth."

# (sx, sy)より、デバイス座標値を計算(xyzwの4要素必要).
dz = 0.0
dw = dz + nearPlane
dx = sx * dw
dy = sy * dw
dPos = [dx, dy, dz, dw]

# ワールド座標からデバイス座標に変換する行列.
wdMat = numpy.matrix(scene.world_to_device_matrix)

# ワールド座標からビュー座標に変換する行列.
wvMat = numpy.matrix(scene.world_to_view_matrix)

# デバイス座標からワールド座標への変換行列.
dwMat = wdMat.I

# ビュー座標からデバイス座標に変換する行列を計算.
vdMat = wvMat.I * wdMat

# デバイス座標からビュー座標への変換行列.
dvMat = vdMat.I

# デバイス座標からビュー座標に変換し、長さ1の方向ベクトルを計算.
vDir = numpy.dot(dPos, dvMat)
vDir = [vDir[0,0], vDir[0,1], vDir[0,2], vDir[0,3]]
vLen = numpy.linalg.norm(numpy.array([vDir[0], vDir[1], vDir[2]]))
if vLen > 0.0:
  vDir = [vDir[0] / vLen, vDir[1] / vLen, vDir[2] / vLen, 1.0]

# ビュー座標上でzDepth離れた位置にする.
vDir[0] *= zDepth
vDir[1] *= zDepth
vDir[2] *= zDepth

# ビュー座標をワールド座標に変換.
wPos = numpy.dot(vDir, wvMat.I)
wPos = [wPos[0,0], wPos[0,1], wPos[0,2], wPos[0,3]]

print "wPos = " + str(wPos)

「近クリップ面までの距離を計算」については、「透視投影のカメラが持つ、近クリップ面までの距離を計算」をご参照くださいませ。
「zDepth = getZDepth(sx, sy)」として、レンダリングイメージ上の(sx, sy)の位置からDepth値(ビュー座標での衝突がある距離)を取得します。

デバイス座標ではxyzwの4つの要素で位置を表します。
カメラ位置から一番近い投影面(近クリップ面)までの距離をnearPlaneとしたとき、以下の式が成り立ちます(ただし、透視投影の場合)。

w = z + nearPlane

(sx, sy)の位置でのZ値(dz)を0.0とし、近クリップ面上の位置にします。
この場合、「dz=0.0」「dw = dz + nearPlane」で計算できます。
さらに、計算されたW値(dw)を使用してX値とY値を計算します。


dz = 0.0
dw = dz + nearPlane
dx = sx * dw
dy = sy * dw

このデバイス座標での位置をビュー座標に変換します。


vDir = numpy.dot(dPos, dvMat)
vDir = [vDir[0,0], vDir[0,1], vDir[0,2], vDir[0,3]]
vLen = numpy.linalg.norm(numpy.array([vDir[0], vDir[1], vDir[2]]))
if vLen > 0.0:
  vDir = [vDir[0] / vLen, vDir[1] / vLen, vDir[2] / vLen, 1.0]

長さが1.0となるように正規化しています。
これに、取得したビュー座標での距離(zDepth)をかけてビュー座標上の衝突がある位置に変換します。


# ビュー座標上でzDepth離れた位置にする.
vDir[0] *= zDepth
vDir[1] *= zDepth
vDir[2] *= zDepth

最後に、ビュー座標からワールド座標に変換します。


# ビュー座標をワールド座標に変換.
wPos = numpy.dot(vDir, wvMat.I)
wPos = [wPos[0,0], wPos[0,1], wPos[0,2], wPos[0,3]]

この計算されたwPosが(sx, sy)でのレンダリングイメージ上の位置からワールド座標に変換した位置となります。

この記事のタイトルとURLをコピーする
Translate »