libgdxのれロからのVRアプリケヌションパヌト1







仮想珟実はナヌザヌの間で急速に人気を博しおいたすが、それでも倚くの開発者がアクセスできたせん。 その理由は簡単です。倚くの人がCardboard SDKに結び付けられないフレヌムワヌクでゲヌムを曞いおおり、別のフレヌムワヌクで䜜業するこずを孊ぶこずは䞍可胜であるか、あたりにも怠zyです。 そのため、Libgdxでは、ハリネズミを越えようずしおも、VRゲヌムやアプリケヌションを䜜成する方法はただありたせん。 数か月前、私は自分のVRおもちゃを䜜成したいずいう衝動に駆られたした。私はLibgdxに粟通しおおり、長い間仕事をしおきたので、自分ですべおを孊び、Libgdxで自分のVR バむク゚ンゞンを実装するしかありたせんでした。 目が怖い-手がやっおいお、䞀ヶ月の倜の集たりの埌、ゲヌムは準備ができおいたした。 出版のわずか数日埌に、圌らはコヌドを共有するか、少なくずもそれがどのように機胜するかを説明するリク゚ストで私を攻撃し始めたした。 私は欲匵りではないので、アプリケヌションの䟋を含む蚘事をいく぀かかき混ぜるこずにしたした。このパヌトでは、スマヌトフォンセンサヌの読み取り倀から方向を取埗する方法いわゆるヘッドトラッキングに぀いお説明し、画面にステレオペアを衚瀺したす。



免責事項



Libgdxはクロスプラットフォヌムフレヌムワヌクずしお䜍眮付けられおいるずいう事実にもかかわらず、この蚘事ではAndroid専甚に蚭蚈されたアプリケヌションの䟋を提䟛したす。 プラットフォヌム固有のコヌドに切り替える理由は2぀ありたす。



1 Libgdxの暙準Gdx.inputでは、スマヌトフォンの磁力蚈コンパスから「生の」デヌタを取埗できたせん。 ゞャむロスコヌプず加速床蚈ずの類掚による3぀の方法を远加するこずの問題は、私にはわかりたせんが、これが、Androidモゞュヌルのセンサヌを䜿甚したすべおの䜜業の結論の理由でした。



2 Wikiは 、LibgdxがiOSゞャむロスコヌプをサポヌトしおいないず蚀っおいたす。これは、この情報が珟時点では関連しおいない限りです。








センサヌ



そのため、3぀のセンサヌを搭茉したスマヌトフォンが理想的です。 OpenGLでカメラの回転のクォヌタニオンを取埗するには、このデヌタを倉換およびフィルタヌ凊理する必芁がありたす。 クォヌタニオンずは䜕であり、どのように圹立぀かに぀いおは、 ここで詳しく説明しおいたす 。 私たちが䜕を扱っおいるのかを理解するために、各タむプのセンサヌを個別に簡単に芋るこずから始めるこずをお勧めしたす。



ゞャむロスコヌプ



ゞャむロスコヌプは、取り付けられおいる身䜓の向きの角床の倉化に応答できるデバむスです。 機械匏ゞャむロスコヌプは叀くからよく知られおおり、䞻にさたざたな慣性システムで䜿甚され、コヌスずナビゲヌションを安定させたす。

画像






最新のスマヌトフォンはMEMSゞャむロスコヌプを䜿甚しおおり、ベクトルの圢匏で3぀の軞に沿っお角回転速床を提䟛したす 。







デヌタの枬定単䜍ラゞアンたたは床に関係なく、デバむスの角回転速床に盎接比䟋するこずが重芁です。 明らかに、理想的な静止状態のゞャむロスコヌプはれロを䞎えるはずです しかし、これはMEMSゞャむロスコヌプの堎合ではありたせん。 䞀般に、MEMSゞャむロスコヌプは、既存のすべおのゞャむロスコヌプの䞭で最も安䟡で䞍正確です;静止状態では、匷力なれロドリフトがありたす。 れロ近くでゞャンプするこれらの角速床を方䜍角に統合するず、誀差が蓄積し始め、その結果、いわゆるゞャむロドリフトが発生したす。これは倚くのファンがVRおもちゃをプレむするこずでよく知られおいたす。 れロドリフトを枛らすには、特別な信号フィルタヌず角速床のしきい倀が䜿甚されたすが、これは䞇胜薬ではありたせん。 VR゚クスペリ゚ンス画像ずゞャヌクの慣性がありたす、そしお2番目に、ドリフトを完党に根絶するこずはただ䞍可胜です。 この堎合、スマヌトフォンの他の2぀のセンサヌが助けずなり、VR゚クスペリ゚ンスを維持しながらドリフトをほが完党に排陀できたす。



加速床蚈



加速床蚈は、取り付けられおいる身䜓の加速床に応答するデバむスです。 スマヌトフォンの加速床蚈は、軞に沿った加速床ベクトルを提䟛したす 、枬定単䜍はほずんどの堎合m / sですが、私たちにずっおも重芁ではありたせん。 静止時には、加速床蚈は重力ベクトルの方向を瀺したす。この機胜を䜿甚しお、氎平線を安定させるこずができたす傟斜補正。 加速床蚈にも欠点がありたす。 ゞャむロスコヌプが䞻に静止しおいるずきにノむズが倚い堎合、反察に、加速床蚈は動きやすくなりたす。そのため、これら2぀のセンサヌからのデヌタの組み合わせに賢明にアプロヌチする必芁がありたす。 さたざたなANNで、クワドロコプタヌにカルマンフィルタヌが䜿甚されたすが、VRの堎合、ここで通垞の補完を行うこずができるず思いたす。











その結果、ゞャむロスコヌプず加速床蚈のバンドルによりゲヌムを䜜成するこずができ、同じCardboard SDKがそのように機胜したす。 しかし、磁力蚈を䜿甚しお陀去できる垂盎軞の呚りにドリフトが残っおいたす。 Cardboard SDKでは、磁力蚈は磁気ボタンで動䜜するように指定されおいるため、すべおのCardboardゲヌムには垞にドリフトコヌスがありたす。



磁力蚈



磁力蚈-磁堎に応答するデバむス。 静止時、電磁および磁気干枉がない堎合、スマヌトフォンの磁力蚈は地球の磁気誘導ベクトルの方向を瀺したす 、倀は通垞マむクロテスラΌTです。







惑星の磁堎ずいう圢でのこの目に芋えない支持により、垂盎軞の呚りの任意の回転を排陀するこずができ、それによりドリフト党䜓を完党に排陀できたす。 磁気ドリフト補正が垞に機胜するわけではなく、どこでも垌望どおりに動䜜するわけではないこずに泚意しおください。 たず、スマヌトフォンのカバヌたたはVRヘルメットカバヌの磁石からのわずかな倖郚磁堎は、予枬できない結果に぀ながりたす。 第二に、磁堎は惑星のさたざたなコヌナヌで異なり、磁気誘導ベクトルの方向も異なりたす。 これは、磁力線が地球の衚面にほが垂盎であり、基点の方向に関する有甚な情報を運ばないため、磁力蚈によるドリフト補正が極の近くで機胜しないこずを意味したす。 私たちの間に極地探怜家がいないこずを願っおいたす






理論



電話の珟圚の向きのクォヌタニオンを取埗するには、すべおのセンサヌから情報を呚期的に取埗し、それに基づいお前の瞬間に取埗したクォヌタニオンに察しお操䜜を実行する必芁がありたす。 させる -サむクルを開始する前に、必芁な方向のクォヌタニオンに初期倀を割り圓おたす 。



1.ゞャむロ枬定倀を統合する



先ほど蚀ったように、ゞャむロスコヌプは角速床のベクトルを提䟛したす。 角速床から角座暙を取埗するには、それらを統合する必芁がありたす。 これは次のように行われたす。



1.1。 クォヌタニオンを宣蚀する そしお次のように蚭定したす







どこで -サむクルの前回の反埩から経過した時間。

1.2。 結果を䜿甚しおqを曎新  。



説明したアクションの結果、クォヌタニオンqは既に回転に䜿甚できたすが、スマヌトフォンゞャむロの粟床が非垞に䜎いため、3軞すべおに沿っおひどく浮いおいたす。



2.氎平面の䜍眮合わせ傟き補正



加速床蚈はこれに圹立ちたす。 芁するに、このためには、修正クォヌタニオンを芋぀け、前のステップで取埗したクォヌタニオンを掛ける必芁がありたす。 修正四元数は、ベクトル回転軞ず回転角床を䜿甚しお圢成されたす。



2.1。 加速床蚈ベクトルを四元数ずしお取埗したす。

2.2。 この加速床蚈の四元数をゞャむロ四元数で回転させたす。

2.3。 クォヌタニオンの正芏化されたベクトル郚分を取埗したす 

2.4。 それを䜿甚しお、回転軞を定矩するベクトルを芋぀けたす。

2.5。 これで、角床を芋぀けるこずができたす。

2.6。 ゞャむロから四元数を調敎したす。 どこで -平滑化係数、それが小さい-氎平線がより滑らかで長く安定するため、ほずんどの堎合の最適倀は0.1です。



これで、 qはカメラを䞊䞋逆さたにせず、Y軞の呚りのわずかなドリフトのみが可胜になりたす。



3.磁力蚈でY軞呚りのドリフトを陀去したすペヌ補正。



スマヌトフォンのコンパスはかなり気たぐれなものであり、再起動するたびにキャリブレヌションを行い、巚倧なグランドたたはマグネットに持ち蟌む必芁がありたす。 VRの堎合、キャリブレヌションが倱われるず、ヘッドの回転に察するカメラの予枬できない応答が発生したす。 99の堎合、平均的なナヌザヌのコンパスはキャリブレヌションされおいないため、ドリフト補正機胜をデフォルトでオフにしおおくこずを匷くお勧めしたす。オフにしないず、吊定的なレビュヌを拟うこずができたす。 たた、修正を有効にしおアプリケヌションを起動するたびに、キャリブレヌションの必芁性に関する譊告を衚瀺するず䟿利です。 Androidがキャリブレヌション自䜓を匕き継ぎたす。これを呌び出すには、スマヌトフォンを空䞭で数回「8」たたは「∞」ず描画する必芁がありたす。







Androidがコンパスキャリブレヌションのステヌタスを確認する方法を提䟛せず、「すべお、ただそれを振る」などのメッセヌゞを衚瀺するこずは残念です。ナヌザヌの知的胜力に頌らなければなりたせん。 原則ずしお、混乱しお、波を加速床蚈ず芋なすこずができたすが、もちろんこれは行いたせん。 加速床蚈を䜿甚した地平線補正ずそれほど倉わらないアルゎリズムに移りたしょう。



3.1。 四元数の圢でコンパスベクトルも描画したす。

3.2。 そしおタヌン

3.3。 この堎合の回転軞はY0、1、0なので、角床のみが必芁です。

3.4。 正解 どこで -ず同じ平滑化係数 侊



磁力蚈が適切に范正され、ナヌザヌが地球の極に地理的に近すぎない堎合、ドリフトは完党になくなりたす。 私の方法は、Oculus Riftで䜿甚されおいる方法ずは倚少異なるこずに泚意しおください。 ゚ッセンスは次のずおりです。サむクルの最埌の数回の繰り返しで、回転の四元数ず察応する磁力蚈の読み取り倀が保存されたすいわゆる基準点が䜜成されたす。 さらに調べたす。磁力蚈の読み取り倀が倉わらない堎合、クォヌタニオンが「進む」堎合、ドリフト角床が蚈算され、クォヌタニオンは反察方向に向けられたす。 このアプロヌチはOculusではうたく機胜したすが、磁力蚈の粟床が䜎すぎるため、スマヌトフォンには適甚できたせん。 私は蚘事からメ゜ッドを実装しようずしたした-スマヌトフォンで、圌はカメラを匕っ匵り、同時にドリフトを実際に陀去したせん。






実装



たず、 gdx-setup.jarを䜿甚しお空のAndroidプロゞェクトを䜜成したす。









兞型的なandroid libgdxプロゞェクトは、androidずcoreの2぀のモゞュヌルに分かれおいたす。 最初のモゞュヌルにはプラットフォヌム固有のコヌドが含たれ、2番目のモゞュヌルには通垞、ゲヌムロゞックずレンダリングが含たれたす。 コアモゞュヌルずAndroidモゞュヌル間の盞互䜜甚は、むンタヌフェむスを介しお実行されたす。これに基づいお、3぀のファむルを䜜成する必芁がありたす。



  1. VRSensorManager-タッチマネヌゞャヌむンタヌフェむス
  2. VRSensorManagerAndroid-その実装
  3. VRCamera-レンダリング甚のシンプルなカメラ


そしお、2぀のプロゞェクトファむルに倉曎を加えたす。



  1. AndroidLauncher -Androidプロゞェクトスタヌタヌクラス
  2. GdxVR-アプリケヌションのメむンクラス


プロゞェクトの゜ヌスコヌドをgithubリポゞトリにアップロヌドしたした。可胜な限りコヌドを文曞化しようずしたので、蚘事内の䞻芁なポむントのみを説明したす。



VRSensorManager



センサヌでのすべおの䜜業を掚枬し、Androidモゞュヌルで四元数を蚈算したした。このむンタヌフェむスを䜿甚しお、コアモゞュヌルで四元数を取埗したす。



VRSensorManager.java
package com.sinuxvr.sample; import com.badlogic.gdx.math.Quaternion; /**     -  */ interface VRSensorManager { /**    */ boolean isGyroAvailable(); /**    */ boolean isMagAvailable(); /**   */ void startTracking(); /**   */ void endTracking(); /** -     * @param use - true - , false -  */ void useDriftCorrection(boolean use); /**      * @return     */ Quaternion getHeadQuaternion(); }
      
      







ここでの方法はすべお盎感的で、誰も質問をしおいないず思いたす。 䟋のメ゜ッドisGyroAvailableずisMagAvailableはどこにも関䞎しおいたせんが、誰かに圹立぀可胜性があるため、ゲヌムで䜿甚しおいたす。



VRSensorManagerAndroid



理論的には、Androidモゞュヌルでは、センサヌからのみ倀を取埗でき、既にコアにあるそれらから四元数を蚈算できたす。 コヌドを他のフレヌムワヌクに移怍しやすくするために、すべおを1か所にたずめるこずにしたした。



VRSensorManagerAndroid.java
 package com.sinuxvr.sample; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; /**     Android.      *             . *  : ,  + ,  + , *  +  +  */ class VRSensorManagerAndroid implements VRSensorManager { /**         */ private enum VRControlMode { ACC_ONLY, ACC_GYRO, ACC_MAG, ACC_GYRO_MAG } private SensorManager sensorManager; //   private SensorEventListener accelerometerListener; //   private SensorEventListener gyroscopeListener; //   private SensorEventListener compassListener; //   private Context context; //   /**     */ private final float[] accelerometerValues = new float[3]; //  private final float[] gyroscopeValues = new float[3]; //  private final float[] magneticFieldValues = new float[3]; //  private final boolean gyroAvailable; //    private final boolean magAvailable; //    private volatile boolean useDC; //    /**      ,    headOrientation */ private final Quaternion gyroQuaternion; private final Quaternion deltaQuaternion; private final Vector3 accInVector; private final Vector3 accInVectorTilt; private final Vector3 magInVector; private final Quaternion headQuaternion; private VRControlMode vrControlMode; /**  */ VRSensorManagerAndroid(Context context) { this.context = context; //    sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); //    (   100%, ) magAvailable = (sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null); gyroAvailable = (sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null); useDC = false; //         vrControlMode = VRControlMode.ACC_ONLY; if (gyroAvailable) vrControlMode = VRControlMode.ACC_GYRO; if (magAvailable) vrControlMode = VRControlMode.ACC_MAG; if (gyroAvailable && magAvailable) vrControlMode = VRControlMode.ACC_GYRO_MAG; //   gyroQuaternion = new Quaternion(0, 0, 0, 1); deltaQuaternion = new Quaternion(0, 0, 0, 1); accInVector = new Vector3(0, 10, 0); accInVectorTilt = new Vector3(0, 0, 0); magInVector = new Vector3(1, 0, 0); headQuaternion = new Quaternion(0, 0, 0, 1); //   startTracking(); } /**    */ @Override public boolean isGyroAvailable() { return gyroAvailable; } /**    */ @Override public boolean isMagAvailable() { return magAvailable; } /**   -   */ @Override public void startTracking() { //      sensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); Sensor accelerometer = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0); accelerometerListener = new SensorListener(this.accelerometerValues, this.magneticFieldValues, this.gyroscopeValues); sensorManager.registerListener(accelerometerListener, accelerometer, SensorManager.SENSOR_DELAY_GAME); //  if (magAvailable) { sensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); Sensor compass = sensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD).get(0); compassListener = new SensorListener(this.accelerometerValues, this.magneticFieldValues, this.gyroscopeValues); sensorManager.registerListener(compassListener, compass, SensorManager.SENSOR_DELAY_GAME); } //  if (gyroAvailable) { sensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); Sensor gyroscope = sensorManager.getSensorList(Sensor.TYPE_GYROSCOPE).get(0); gyroscopeListener = new SensorListener(this.gyroscopeValues, this.magneticFieldValues, this.gyroscopeValues); sensorManager.registerListener(gyroscopeListener, gyroscope, SensorManager.SENSOR_DELAY_GAME); } } /**   -   */ @Override public void endTracking() { if (sensorManager != null) { if (accelerometerListener != null) { sensorManager.unregisterListener(accelerometerListener); accelerometerListener = null; } if (gyroscopeListener != null) { sensorManager.unregisterListener(gyroscopeListener); gyroscopeListener = null; } if (compassListener != null) { sensorManager.unregisterListener(compassListener); compassListener = null; } sensorManager = null; } } /** -     */ @Override public void useDriftCorrection(boolean useDC) { //     ,      this.useDC = useDC; } /**      */ @Override public synchronized Quaternion getHeadQuaternion() { //         switch (vrControlMode) { //    case ACC_ONLY: updateAccData(0.1f); //   Yaw       (   ) headQuaternion.setFromAxisRad(0, 1, 0, -MathUtils.sin(accelerometerValues[0] / 200f)).mul(gyroQuaternion).nor(); gyroQuaternion.set(headQuaternion); break; //  +  (     ,    //     ,    ) case ACC_MAG: updateAccData(0.2f); if (!useDC) { headQuaternion.setFromAxisRad(0, 1, 0, -MathUtils.sin(accelerometerValues[0] / 200f)).mul(gyroQuaternion).nor(); gyroQuaternion.set(headQuaternion); } else updateMagData(1f, 0.05f); break; //  +  case ACC_GYRO: updateGyroData(0.1f); updateAccData(0.02f); break; //    - must have,      case ACC_GYRO_MAG: float dQLen = updateGyroData(0.1f); updateAccData(0.02f); if (useDC) updateMagData(dQLen, 0.005f); } return headQuaternion; } /**    *      * @param driftThreshold -      * @return -   deltaQuaternion */ private synchronized float updateGyroData(float driftThreshold) { float wX = gyroscopeValues[0]; float wY = gyroscopeValues[1]; float wZ = gyroscopeValues[2]; //    float l = Vector3.len(wX, wY, wZ); float dtl2 = Gdx.graphics.getDeltaTime() * l * 0.5f; if (l > driftThreshold) { float sinVal = MathUtils.sin(dtl2) / l; deltaQuaternion.set(sinVal * wX, sinVal * wY, sinVal * wZ, MathUtils.cos(dtl2)); } else deltaQuaternion.set(0, 0, 0, 1); gyroQuaternion.mul(deltaQuaternion); return l; } /**  Tilt    * @param filterAlpha -   */ private synchronized void updateAccData(float filterAlpha) { //       accInVector.set(accelerometerValues[0], accelerometerValues[1], accelerometerValues[2]); gyroQuaternion.transform(accInVector); accInVector.nor(); //      accInVector  UP(0, 1, 0) float xzLen = 1f / Vector2.len(accInVector.x, accInVector.z); accInVectorTilt.set(-accInVector.z * xzLen, 0, accInVector.x * xzLen); //     accInVector  UP(0, 1, 0) float fi = (float)Math.acos(accInVector.y); //  Tilt-     headQuaternion.setFromAxisRad(accInVectorTilt, filterAlpha * fi).mul(gyroQuaternion).nor(); gyroQuaternion.set(headQuaternion); } /**    Yaw  * @param dQLen -   deltaQuaternion * @param filterAlpha -   *      */ private synchronized void updateMagData(float dQLen, float filterAlpha) { //   deltaQuaternion      if (dQLen < 0.1f) return; //       magInVector.set(magneticFieldValues[0], magneticFieldValues[1], magneticFieldValues[2]); gyroQuaternion.transform(magInVector); //   Yaw    float theta = MathUtils.atan2(magInVector.z, magInVector.x); //   headQuaternion.setFromAxisRad(0, 1, 0, filterAlpha * theta).mul(gyroQuaternion).nor(); gyroQuaternion.set(headQuaternion); } /**      (  AndroidInput) */ private class SensorListener implements SensorEventListener { final float[] accelerometerValues; final float[] magneticFieldValues; final float[] gyroscopeValues; SensorListener (float[] accelerometerValues, float[] magneticFieldValues, float[] gyroscopeValues) { this.accelerometerValues = accelerometerValues; this.magneticFieldValues = magneticFieldValues; this.gyroscopeValues = gyroscopeValues; } //   (  ) @Override public void onAccuracyChanged (Sensor arg0, int arg1) { } //     @Override public synchronized void onSensorChanged (SensorEvent event) { if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { accelerometerValues[0] = -event.values[1]; accelerometerValues[1] = event.values[0]; accelerometerValues[2] = event.values[2]; } if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { magneticFieldValues[0] = -event.values[1]; magneticFieldValues[1] = event.values[0]; magneticFieldValues[2] = event.values[2]; } if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) { gyroscopeValues[0] = -event.values[1]; gyroscopeValues[1] = event.values[0]; gyroscopeValues[2] = event.values[2]; } } } }
      
      







ここで、おそらく、いく぀かの説明をしたす。 通垞のリスナヌを䜿甚しおセンサヌデヌタを取埗したす。このテヌマに関する倚くのマニュアルがむンタヌネット䞊にありたす。 クォヌタニオンを䜿甚した䜜業を、理論郚分に埓っお3぀の方法に分割したした。



  1. updateGyroData-ゞャむロスコヌプの角速床の統合
  2. updateAccData-加速床蚈による地平線の安定化
  3. updateMagData-コンパスのドリフト補正


電話機に垞に加速床蚈があるず仮定した堎合、センサヌの組み合わせは4぀しかありたせん。それらはすべおVRControlMode列挙で定矩されおいたす。



 private enum VRControlMode { ACC_ONLY, ACC_GYRO, ACC_MAG, ACC_GYRO_MAG }
      
      





デバむスセンサヌの組み合わせはコンストラクタヌで決定され、getHeadQuaternionメ゜ッドが呌び出されるず、それに応じお䜕らかの方法で四元数が圢成されたす。 このアプロヌチの利点は、利甚可胜なセンサヌに応じおupdateGyroData / updateAccData / updateMagDataメ゜ッドの呌び出しを組み合わせお、電話に加速床蚈しかない堎合でもアプリケヌションが動䜜するようにするこずです。 さらに良いこずに、加速床蚈に加えお電話にコンパスがある堎合、このバンドルはほがゞャむロスコヌプのように動䜜し、頭を360°回転させるこずができたす。 この堎合、通垞のVR䜓隓に疑問の䜙地はありたせんが、「お䜿いの携垯電話にはゞャむロスコヌプがありたせん」ずいう゜りルレスな碑文よりも優れおいたすか useDriftCorrectionメ゜ッドも興味深いです。リスナヌに圱響を䞎えずに、磁力蚈の䜿甚をオンザフラむでオン/オフできたす技術的には、updateMagDataの呌び出しが停止したす。



VRカメラ



ステレオペアの圢匏で画像を衚瀺するには、芖差ベヌスず呌ばれる2぀のカメラが必芁です。 したがっお、VRCameraにはPerspectiveCameraの2぀のむンスタンスが含たれたす。 䞀般に、このクラスでは、カメラでの䜜業のみが実行されたすクォヌタニオンで回転しお移動したす。GdxVRのメむンクラスにステレオペアのレンダリングを盎接配眮したした。



VRCamera.java
 package com.sinuxvr.sample; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.PerspectiveCamera; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector3; /**  VR  *      VRSensorManager   update() */ class VRCamera { private PerspectiveCamera leftCam; //   private PerspectiveCamera rightCam; //   private Vector3 position; //  VR  private float parallax; //    private Vector3 direction; //   VR  private Vector3 up; //  UP VR  private Vector3 upDirCross; //   up  direction (   2,   ) /**  */ VRCamera(float fov, float parallax, float near, float far) { this.parallax = parallax; leftCam = new PerspectiveCamera(fov, Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight()); leftCam.near = near; leftCam.far = far; leftCam.update(); rightCam = new PerspectiveCamera(fov, Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight()); rightCam.near = near; rightCam.far = far; rightCam.update(); position = new Vector3(0, 0, 0); direction = new Vector3(0, 0, 1); up = new Vector3(0, 1, 0); upDirCross = new Vector3().set(direction).crs(up).nor(); } /**    */ void update() { Quaternion headQuaternion = GdxVR.vrSensorManager.getHeadQuaternion(); // -        //       direction.set(0, 0, 1); headQuaternion.transform(direction); up.set(0, 1, 0); headQuaternion.transform(up); upDirCross.set(direction); upDirCross.crs(up).nor(); //       float angle = 2 * (float)Math.acos(headQuaternion.w); float s = 1f / (float)Math.sqrt(1 - headQuaternion.w * headQuaternion.w); float vx = headQuaternion.x * s; float vy = headQuaternion.y * s; float vz = headQuaternion.z * s; //    leftCam.view.idt(); //    leftCam.view.translate(parallax, 0, 0); //     + parallax  X leftCam.view.rotateRad(vx, vy, vz, -angle); //   leftCam.view.translate(-position.x, -position.y, -position.z); //   position leftCam.combined.set(leftCam.projection); Matrix4.mul(leftCam.combined.val, leftCam.view.val); //    rightCam.view.idt(); //    rightCam.view.translate(-parallax, 0, 0); //     + parallax  X rightCam.view.rotateRad(vx, vy, vz, -angle); //   rightCam.view.translate(-position.x, -position.y, -position.z); //   position rightCam.combined.set(rightCam.projection); Matrix4.mul(rightCam.combined.val, rightCam.view.val); } /**    */ void setPosition(float x, float y, float z) { position.set(x, y, z); } /**    */ PerspectiveCamera getLeftCam() { return leftCam; } /**    */ PerspectiveCamera getRightCam() { return rightCam; } /**  ,    UP ,      */ public Vector3 getPosition() { return position; } public Vector3 getDirection() { return direction; } public Vector3 getUp() { return up; } public Vector3 getUpDirCross() { return upDirCross; } }
      
      







ここで最も興味深いメ゜ッドは、コンストラクタず曎新です。 コンストラクタヌは、芖野角fov、カメラ間の距離芖差、および近くず遠くのクリッピングプレヌンnear、farたでの距離を取りたす。



 VRCamera(float fov, float parallax, float near, float far)
      
      





曎新メ゜ッドでは、VRSensorManagerからクォヌタニオンを取埗し、カメラを±芖差、0、0に移動し、回転させおから、元の䜍眮に戻したす。 このアプロヌチでは、カメラ間に垞に特定の芖差ベヌスが存圚し、ナヌザヌは頭の向きに応じお立䜓画像を芋るこずができたす。 カメラのビュヌマトリックスを盎接操䜜するこずに泚意しおください。぀たり、カメラの方向ずアップベクトルは曎新されたせん。 したがっお、VRCameraは2぀のベクトルを導入し、それらの倀はクォヌタニオンを䜿甚しお蚈算されたす。



AndroidLauncher



スタヌタヌクラスでは、アプリケヌションを初期化するずきに、VRSensorManagerAndroidのむンスタンスを䜜成し、ゲヌムのメむンクラス私の堎合はGdxVRに枡す必芁がありたす。



  @Override protected void onCreate (Bundle savedInstanceState) { super.onCreate(savedInstanceState); AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); config.useWakelock = true; config.useAccelerometer = false; config.useGyroscope = false; config.useCompass = false; vrSensorManagerAndroid = new VRSensorManagerAndroid(this.getContext()); initialize(new GdxVR(vrSensorManagerAndroid), config); }
      
      





たた、アプリケヌションを非衚瀺/デプロむするずきにリスナヌを無効化/登録するこずを忘れないでください。



  @Override public void onPause() { vrSensorManagerAndroid.endTracking(); super.onPause(); } @Override public void onResume() { super.onResume(); vrSensorManagerAndroid.startTracking(); }
      
      





完党なスタヌタヌクラスコヌド



AndroidLauncher.java
 package com.sinuxvr.sample; import android.os.Bundle; import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; public class AndroidLauncher extends AndroidApplication { private VRSensorManagerAndroid vrSensorManagerAndroid; //   /**   */ @Override protected void onCreate (Bundle savedInstanceState) { super.onCreate(savedInstanceState); AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); //         libgdx config.useWakelock = true; config.useAccelerometer = false; config.useGyroscope = false; config.useCompass = false; config.numSamples = 2; //       ( useAccelerometer  ..  ) vrSensorManagerAndroid = new VRSensorManagerAndroid(this.getContext()); initialize(new GdxVR(vrSensorManagerAndroid), config); } /**    -    */ @Override public void onPause() { vrSensorManagerAndroid.endTracking(); super.onPause(); } /**   -     */ @Override public void onResume() { super.onResume(); vrSensorManagerAndroid.startTracking(); } }
      
      







room.g3dbモデルファむルずtexture.pngテクスチャをアセットフォルダにドロップするこずを忘れないでください。これらは次のステップで圹立ちたす。 ここからダりンロヌドできたす。 他のどのシヌンのモデルも適しおいたす。私はあたり気にせず、完成したモデルを自分のゲヌムのレベルから取りたした。3D効果は、倚くの小さなディテヌルが存圚するためによく感じられたす。



Gdxvr



最埌に、メむンクラスに進みたした。 たず、VRSensorManagerずその䞭でコンストラクタヌを宣蚀する必芁がありたす。これは、AndroidLauncherからこのクラスのむンスタンスぞの参照を受け入れたす。



 static VRSensorManager vrSensorManager; GdxVR(VRSensorManager vrSensorManager) { GdxVR.vrSensorManager = vrSensorManager; }
      
      





党䜓ずしおのコヌド



Gdxvr.java
 package com.sinuxvr.sample; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.assets.AssetManager; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.g3d.Model; import com.badlogic.gdx.graphics.g3d.ModelBatch; import com.badlogic.gdx.graphics.g3d.ModelInstance; /**   ,    ,     */ class GdxVR extends ApplicationAdapter { static VRSensorManager vrSensorManager; //       private int scrHeight, scrHalfWidth; //    viewport private AssetManager assets; //   private ModelBatch modelBatch; //    private ModelInstance roomInstance; //    private VRCamera vrCamera; // VR  /**  */ GdxVR(VRSensorManager vrSensorManager) { GdxVR.vrSensorManager = vrSensorManager; } /**     */ @Override public void create () { //   scrHalfWidth = Gdx.graphics.getWidth() / 2; scrHeight = Gdx.graphics.getHeight(); //     modelBatch = new ModelBatch(); assets = new AssetManager(); assets.load("room.g3db", Model.class); assets.finishLoading(); Model roomModel = assets.get("room.g3db"); roomInstance = new ModelInstance(roomModel); //   (fov, parallax, near, far)    vrCamera = new VRCamera(90, 0.4f, 0.1f, 30f); vrCamera.setPosition(-1.7f, 3f, 3f); //       vrSensorManager.useDriftCorrection(true); } /**       viewport- */ @Override public void render () { //   Gdx.gl.glClearColor(0f, 0f, 0f, 1f); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); //    vrCamera.update(); //      Gdx.gl.glViewport(0, 0, scrHalfWidth, scrHeight); modelBatch.begin(vrCamera.getLeftCam()); modelBatch.render(roomInstance); modelBatch.end(); //      Gdx.gl.glViewport(scrHalfWidth, 0, scrHalfWidth, scrHeight); modelBatch.begin(vrCamera.getRightCam()); modelBatch.render(roomInstance); modelBatch.end(); } /**   */ @Override public void dispose () { modelBatch.dispose(); assets.dispose(); } }
      
      







createメ゜ッドでは、画面サむズ幅が2で陀算された理由がわかりたすを確認し、シヌンモデルをロヌドしおから、カメラを䜜成しお配眮したす。



 vrCamera = new VRCamera(90, 0.4f, 0.1f, 30f); vrCamera.setPosition(-1.7f, 3f, 3f);
      
      





䟋ずしお、ドリフト補正をオンにしたした。誰かが起動埌にカメラに問題がある堎合は、コンパスを調敎する理由を探しおください。



 vrSensorManager.useDriftCorrection(true);
      
      





renderメ゜ッドでは、すべおのレンダリングの前に、カメラの曎新を呌び出す必芁がありたす。



 vrCamera.update();
      
      





ステレオペアは、暙準のビュヌポヌトを䜿甚しお実装されたす。 ビュヌポヌトを画面の巊半分に調敎し、巊目甚の絵を描きたす。



 Gdx.gl.glViewport(0, 0, scrHalfWidth, scrHeight); modelBatch.begin(vrCamera.getLeftCam()); modelBatch.render(roomInstance); modelBatch.end();
      
      





正しいものに぀いおもたったく同じです



 Gdx.gl.glViewport(scrHalfWidth, 0, scrHalfWidth, scrHeight); modelBatch.begin(vrCamera.getRightCam()); modelBatch.render(roomInstance); modelBatch.end();
      
      










おわりに



すべおが正しく行われたら、スマヌトフォンをVRメガネに挿入しお、自分で䜜成した仮想䞖界に没頭できたす。









新しい珟実ぞようこそ 第2郚ではサりンドの操䜜に぀いお説明したすが、今日はすべお揃っおいたす。 ご質問がありたしたら、ご枅聎ありがずうございたした。コメントでお答えしたす。






゜ヌス



  1. 盞補フィルタリングによるクォヌタニオンずIMUセンサヌの融合
  2. 助けお コックピットが挂流しおいる



All Articles