注意:長い答え!
私は Love2D で同様のプロジェクトを実行しましたが、完全に高速に実行されるため、OpenGL (とにかく公開されていない) を使用するのではなく、Lua で自分で計算を行うことに問題はありません。
コメントに反して、がっかりしないでください。3D の向きと視点の背後にある計算は、実際には非常に単純です。
オリエンテーションの場合、クォータニオンはおそらくやり過ぎです。回転を伴う 3D プロジェクションを行うにはVec2
、 、Vec3
、およびCamera
クラスのみが必要であることがわかりました。数学的には微妙な違いがいくつかありますが、実際にはベクトルのベクトルは完全に適切な変換行列を作成し、変換行列は完全に適切な向きを作成します。ベクトルのベクトルである行列には、両方を処理するために 1 つのクラスを記述するだけでよいという利点があります。
ベクトルを投影するv
には、カメラの 3 つのパラメーターを考慮します。
loc
、Vec3
カメラの位置のa
trans
、カメラの向きの
逆の a ( of とMat3by3
も呼ばれる)Vec3
Vec3
- (免責事項:小さな丸め誤差が蓄積される可能性があるため、カメラの向きに行列を使用することは技術的に有害であると考えられていますが、実際の使用では問題ありません)
zoom
、遠近法を決定するために使用される倍率。のz
(カメラに相対的な) はzoom
2D と同等です。つまり、パースペクティブからのスケーリングはありません。
投影は次のように機能します。
function Camera:project(v)
local relv -- v positioned relative to the camera, both in orientation and location
relv = self.trans * (v - self.loc) -- here '*' is vector dot product
if relv.z > 0 then
-- v is in front of the camera
local w -- perspective scaling factor
w = self.zoom / relv.z
local projv -- projected vector
projv = Vec2(relv.x * w, relv.y * w)
return projv
else
-- v is behind the camera
return nil
end
end
これは、Vec2(0, 0)がウィンドウの隅ではなく中央に対応すると仮定しています。その設定は簡単な翻訳です。
trans
恒等行列として開始する必要がVec3(Vec3(1, 0, 0), Vec3(0, 1, 0), Vec3(0, 0, 1))
あります。方向が変更されるたびに微調整を行いながら、段階的に計算されます。
マトリックスの基本を既に知っていると思いますが、そうでない場合の考え方は次のとおりです。マトリックスはベクトルのベクトルであり、少なくともこの場合は座標系と考えることができます。各ベクトルは、座標系の 1 つの軸と考えることができます。マトリックスの要素 (ベクトルであり、マトリックスの列と見なされます) を変更することにより、その座標系における座標の意味を変更します。通常の使用法では、ベクトルの最初の要素は右に移動することを意味し、2 番目の要素は上に移動することを意味し、3 番目の要素は前方に移動することを意味します。ただし、マトリックスを使用すると、各コンポーネントを任意の方向に向けることができます。内積の定義は
function Vec3.dot(a, b) return a.x * b.x + a.y + b.y + a.z * b.z end
行列の場合
Vec3(axis1, axis2, axis3)
内積の定義が与えられると、ベクトルで点を打った行列は次のようになりますv
。
axis1 * v.x + axis2 * v.y + axis3 * v.z
つまり、 の最初の要素はの移動数を表し、2 番目の要素はの移動数を表し、3 番目の要素はの移動数を表し、表現された場合の最終結果は になりv
ます。行列の座標ではなく、標準座標で。行列にベクトルを掛けると、ベクトルの成分の意味が変わります。本質的に、それは「右にあったものはすべて、右に少なくなり、より前にある」などのステートメントの数学的表現です。ある文では、行列が空間を変換します。axis1
axis2
axis3
v
目の前のタスクに戻ると、「ピッチ」(x 軸の周りを意味する) の回転theta
を行列を使用して角度で表すには、次のように記述できます。
function pitchrotation(theta)
return Vec3(
-- axis 1
-- rotated x axis
-- we're rotating *around* the x axis, so it stays the same
Vec3(
1,
0,
0
),
-- axis 2
-- rotated y axis
Vec3(
0,
math.cos(theta),
math.sin(theta)
),
-- axis 3
-- rotated z axis
Vec3(
0,
-math.sin(theta),
math.cos(theta)
)
)
end
「ヨー」(y軸周り)の場合:
function yawrotation(theta)
return Vec3(
-- axis 1
-- rotated x axis
Vec3(
math.cos(theta),
0,
math.sin(theta)
),
-- axis 2
-- rotated y axis
-- we're rotating *around* the y axis, so it stays the same
Vec3(
0,
1,
0
),
-- axis 3
-- rotated z axis
Vec3(
-math.sin(theta),
0,
math.cos(theta)
)
)
end
最後に、(z 軸を中心に) 「ロール」します。これは、フライト シミュレーションで特に役立ちます。
function rollrotation(theta)
return Vec3(
-- axis 1
-- rotated x axis
Vec3(
math.cos(theta),
math.sin(theta),
0
),
-- axis 2
-- rotated y axis
Vec3(
-math.sin(theta),
math.cos(theta),
0
),
-- axis 3
-- rotated z axis
-- we're rotating *around* the z axis, so it stays the same
Vec3(
0,
0,
1
)
)
end
それが x、y、z 軸に与える影響を頭の中で視覚化すると、これらすべてのコサインとサイン、および符号反転が意味をなすようになるかもしれません。彼らはすべて理由があってそこにいます。
最後に、これらの回転を適用するパズルの最後のステップに到達します。行列の優れた機能は、それらを簡単に合成できることです。変換は非常に簡単に変換できます。各軸を変換するだけです。A
既存の行列を行列で変換するにはB
:
function combinematrices(a, b)
return Vec3(b * a.x, b * a.y, b * a.z) -- x y and z are the first second and third axes
end
これが意味することは、カメラに変更を適用したい場合は、このマトリックス結合メカニズムを使用して、フレームごとに向きを少し回転させるだけでよいということです。カメラ クラスのこれらの関数を使用すると、変更を簡単に行うことができます。
function Camera:rotateyaw(theta)
self.trans = combinematrices(self.trans, yawrotation(-theta))
end
射影のために、トランスをカメラの向きの反対にしたいので、負のシータを使用します。同様の機能をピッチとロールで作成できます。
これらの構成要素がすべて整ったら、Lua で 3D グラフィックス コードを作成する準備が整います。あなたは実験したいと思うでしょうzoom
- 私は通常 を使用します500
が、それは実際にはアプリケーションに依存します.
OpenGL なしでは実現できない 1 つの欠けている部分は、深度テストです。ワイヤフレームのポイント以外を描画している場合、すべてが正しい順序で描画されることを確認する良い方法はありません。並べ替えはできますが、それは非効率的です。また、OpenGL が行うように、ピクセルごとに並べ替える必要があるまれなケースを処理できません。
ハッピーコーディング!お役に立てば幸いです。