ARのSNS作ってみる オブジェクト描写編

# 完成イメージ
　AR空間にメッセージを投稿でき、投降した内容を自身と他者が端末を介して見ることが出来るという完成イメージをしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267761/8f2786fd-35c6-d383-0b68-5ae8dd46e193.png)

# 技術選定
フレームワーク：Android Studio
言語：kotlin
DB：mysql（今の構想だとリレーションいらないので、別の使うかも）
ライブラリ等：ARCore、Geospatial API、Canvas

　対象端末はAndroidを想定しているので、とりあえずAndroid Studioを使い、言語とDBは気分です。AR機能を使うので、ARCoreを利用し、ユーザが作成したオブジェクトを他ユーザも参照できるようにするため、オブジェクトの絶対位置（緯度、経度、高度）を取得できるGeospatial APIを利用する想定です。またユーザが入力した文字列は画像化してオブジェクトに張り付けるので、Canvasも利用します。

# 必須機能
①ユーザがポストしたい内容を入力できる。

②画面をタップしたらオブジェクトを描写できる。

③入力された内容が記載されたオブジェクトを描写できる。

④入力された内容（文字列）の長さによってオブジェクトを伸び縮みできる。

⑤作成されたオブジェクトの絶対位置をデータベースで永続化できる。

⑥永続化されたオブジェクトから自身の現在位置の周辺にあるものを取得して描写できる。

⑦現在位置が一定数以上変わったら再度永続化されたオブジェクトを取得して描写できる。

　他にも実装したい細かな機能はありますが、利用可能な状態だけを考えるのであれば上記の機能で十分かなと考えています。まだ作成途中なので、上記の各機能の説明は数記事に分割して投稿しようと思います。今回の記事では①と②について説明します。

 ## ユーザにポストしたい内容を入力させる
　この機能に関しては単純にEditTextを持つActivityを作成するだけ、特に言うこともないので詳細な説明は省きます。
　AR画面から遷移でき、以下の「メモする」をタップしたら、ARの画面に戻り入力した内容のオブジェクトが描写できるようになるイメージです。
 
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267761/e3c39e17-fa0a-8d70-cd02-ef48374400ad.png)




 ## 画面をタップしたらオブジェクトを描写される
 　AR空間にオブジェクトを配置する上で必要な要素として以下があります。

・オブジェクト
　AR空間に配置するもの。3Dモデル、画像、動画など。
   
・平面検出
　現実世界の水平面や垂直面を認識して、オブジェクトを配置するための基盤になる。
  
・点群
　現実世界の物体の表面を構成する点の集合。平面が検出できない複雑な形状の物体に有効。オブジェクトを配置するための基盤になる。

・アンカー
　オブジェクトを現実世界の特定の位置に固定するためのもの。

　上記の要素を使いAR空間にオブジェクトを配置していきます。
　
　まずは平面、点群の検出を行い視覚化することで、オブジェクトが配置位置を明確にします。平面、点群の検出自体はデフォルトで有効になっていると思うので、検出した結果を受け取り、画面に描写を行います。AR空間への描写はOpenGLを利用するのですが、一から書くと分けわからないので、公式が用意しているサンプルコードを利用します。複雑なところは隠蔽してくれています。

 https://github.com/google-ar/arcore-android-sdk/tree/main/samples/hello_ar_kotlin

　このサンプルコードで内の「HelloArRenderer」クラスの「onDrawFrame」にて平面と点群の描写を行っています。このメソッドはフレーム毎で呼び出されるため、常に適した平面と点群が描写されます。

``` kotlin
//平面の描写
planeRenderer.drawPlanes(
  render,
  session.getAllTrackables<Plane>(Plane::class.java),
  camera.displayOrientedPose,
  projectionMatrix
)

//点群の描写
frame.acquirePointCloud().use { pointCloud ->
  if (pointCloud.timestamp > lastPointCloudTimestamp) {
    pointCloudVertexBuffer.set(pointCloud.points)
    lastPointCloudTimestamp = pointCloud.timestamp
  }
  Matrix.multiplyMM(modelViewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0)
  pointCloudShader.setMat4("u_ModelViewProjection", modelViewProjectionMatrix)
  render.draw(pointCloudMesh, pointCloudShader)
}
```
　この描写をすると以下のようになります。白色の三角形が平面で青色の点が点群です。
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267761/65672280-3a0e-7b78-59c5-1b86a09e988a.png)

　次にオブジェクトの描写です。これも先ほどのサンプルコードを見ていきます。サンプルコードではMeshクラス、Shaderクラス、Textureクラスを利用してオブジェクトの描写を行っています。
* Meshクラス
　オブジェクトの形状の情報を持つクラスです。サンプルコードだと以下のように定義されています。ここで渡しているobjファイルを変えれば好きな形のオブジェクトを配置できます。objファイルはBlenderなどで作成できます。
    ``` kotlin
    virtualObjectMesh = Mesh.createFromAsset(render, "models/pawn.obj")
    ```
  　
* Textureクラス
　テクスチャ（オブジェクトに張り付ける画像）を作成、保持するクラスです。サンプルコードだと以下のような画像をテクスチャとしてして設定していると思います。
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267761/aab78ba5-aa4e-fe2c-3fe7-cb08bd51fb1b.png)
 　
　3Dオブジェクトの場合、単純に画像を張り付けることが出来ないため、UV展開したものに画像を貼り付けます。サンプルコードのオブジェクトのUV展開図が以下であるため、上記のような画像をテクスチャとして設定しています。
 
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267761/b36dd4f0-2315-07ff-6127-12b2ee67e664.png)
 　
* Shaderクラス
　OpenGLのシェーダーを扱うためのクラス。バーテックスシェーダー（頂点ごとに実行）やフラグメントシェーダー（ピクセルごとに実行）の設定や、uniform変数（シェーダープログラムに渡す値）を設定します。

 
　
　基本的に上記３つのクラスを使いオブジェクトの生成、描写を行います。出力するオブジェクトを変えたいだけであれば、Meshクラスに渡すobjファイルとTextureクラスに渡す画像を差し替えれば変わります。

　話がやや脱線しましたが、今回のサブタイトルにある「画面をタップしたらオブジェクトを描写される」について進めていきます。これはそこまで複雑ではなく、タップ時にアンカーを追加し、そのアンカーにオブジェクトを固定すれば良いです。サンプルコード上の「handleTapメソッド」がタップからアンカーを追加する処理で、onDrawFrameにてアンカーでループしているのタップされた分だけオブジェクトが作成されます。今回の場合、投稿したい内容が設定されていない状態だとオブジェクトを作成できなくしたいので、その条件だけ追加しています。
　これで画面をタップしたら以下のような形でオブジェクトが描写されます。

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267761/c01bc282-7f94-7ae3-2f38-76c62d1f0b02.png)


 # まとめ
 　今回はここまでにします。次回は「入力された内容が記載されたオブジェクトを描写できる」と「入力された内容（文字列）の長さによってオブジェクトを伸び縮みできる」まで進められたと思っています。オブジェクトの形が変わるとUV展開図も変わってしまうので、そこをどうするかが鬼門になってくると思います。ここ間違っている、こうした方がいいんじゃないか等のアドバイスあれば嬉しいです。