【WebAR】8th Wallでの半透明オブジェクト表示の代替案（前編）

# 概要
今回、株式会社GENEROSITY様でインターンシップをさせていただき、8thwallを使用して、以下のような箱の中身が見えるWebARを制作しました。制作期間は2週間程度でした。

<iframe width="527" height="937" src="https://www.youtube.com/embed/oxkmIJ3l4bQ" title="Apple Cardboard" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

https://www.8thwall.com/

制作においては、透明のマテリアルや、半透明のマテリアルの表現に苦戦しました。
それも単に透明なだけでなく、今回のシーンには __リンゴ飴(半透明の飴+不透明のリンゴ)に加え、アップルジュース(透明の液体＋透明のガラス)__ が含まれていたので、さらに難易度が上がりました。

本来、リアルタイムレンダリングでは描画順の設定により透明/半透明のオブジェクトは問題が発生しがちなので避けられます。しかし、どうしてもリンゴ飴とアップルジュースを出したかったことから、頑張って実装することにしました。

シェーダーを書いての対処もできるのだと思いますが、私はHTML,CSS,Javascriptについて全くの初心者だったので、今回は8thwallのモジュールとBlenderを使って、なるべく情報量の多いリッチな見た目にしていきたいと思います。

# 目次
[01_開発環境](#01_開発環境)
[02_Blenderでモデルを用意する](#02_Blenderでモデルを用意する)
[03_8thWall標準のガラスマテリアルを適用してみる](#03_8thWall標準のガラスマテリアルを適用してみる)
[04_8thWallでRealtimeReflectionsの設定をする](#04_8thWallでRealtimeReflectionsの設定をする)
～前編はここまで～<font color=darkgray>
05_HDR素材を用意する
06_Blenderでベイク用シェーダーの設定をする
07_BlenderでBake-A-Nodeを使用しベイクする
08_Photoshopでレタッチをする
09_Blenderでシェーダーを再設定する
10_8thWallにアップロード&配置
11_出力結果
12_まとめ
13_参考
</font>

# 01_開発環境

* Windows11
* 8thwall (HTML,CSS,Javascript,A-frame)
* Blender 4.0.2 
  アドオン追加 → Bake-A-Node
* Adobe Photoshop 24.0 
* テストデバイス
　iPhone 13 Pro Max

# 02_Blenderでモデルを用意する

何はともあれモデルが無いと始まらないので、まずはモデルを用意していきます。


今回必要なモデルは以下の5つです
・リンゴ飴のリンゴ部分
・リンゴ飴の飴部分
・リンゴ飴の棒(割り箸)
・リンゴジュースのボトル
・リンゴジュースのコップ


リンゴは購入することにしましたが、他はモデリングします。

### アセットの購入

リンゴのモデルはこちらを使用させていただきました。

https://www.cgtrader.com/3d-models/food/fruit/red-apple-759782ef-668e-4ceb-b221-a66afd1fa320

:::note warn
アセットの中には何故か伝播が1.0になっており、読み込んだ際に透けるものがあるので注意します。このアセットも同様です。
:::

#### その他モデリング

リンゴに棒を差し、飴をまとわせます。アップルジュースも簡単にモデリングします。

![モデリング結果_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/714061d8-20c8-00a4-ca20-7230c168f76d.png)

デシメート&UV展開も済ませておきます。
8th Wallの軽量化についてはこちらの記事が参考になります。

https://qiita.com/iwaken71/items/a735847da783a8367bbd

終わったらシェーディングに取り掛かります。

# 03_8thWall標準のガラスマテリアルを適用してみる

8thwallにはガラス表現のモジュールが既にあります。
色付きガラスも表現できるそうなので、一旦こちらを使用してみましょう。

![半透明ガラス_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/baf07693-a95c-585b-f6a1-1f7f3b3daaf4.png)

こちらのリンクをクリックし、Clone Projectをクリックします。

https://www.8thwall.com/playground/glass-materials-aframe

![クローンプロジェクト_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/d7737559-d241-ba46-13f6-564a51ffa47c.png)

ワークスペース、URL、ライセンスタイプを表示し、Createボタンを押します。

![デモユース_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/b5aa0ad0-1210-bdbb-03f3-1f29b8ff376a.png)

![ガラスプロジェクト画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/c34f1518-efcc-132f-354e-a9dc313d8ec2.png)

表示されたら、```body.html```の中で```correctGlass```に指定されている```glass_converted.glb```をダウンロードして、Blenderで開きます。

![コレクトグラス_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/4aa05f86-b6f9-b8d8-2728-04af57e73ae5.png)

![blenderグラスマテリアル_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/22a439ef-e2ae-8701-a6ba-ac7165cd61d5.png)

こちらのモデルに適用されているマテリアルをそのまま自分のモデルに適用させていきましょう。

適用できたら8th Wallに移り、アセットをシーンに配置していきます。

> 回転については、Zが前方に来るようにモデリングしなかったため、オブジェクトを配置する際は```rotation="90 0 0"``` のようにrotationのXの値を+90しました。


### しかし

これだけでは求めているような見た目にはなりませんでした。後ろの背景によって中身の半透明の色が正しく見えたり見えなかったりするほか、あまりガラスらしくキラキラとした見た目にはなっていません。今回の制作においては、他のアセットと同じように、もっと情報量が欲しいところです。

```plantuml
@startuml
<style>
root {
  Padding 0
  Margin 0
}
footer {
  FontColor black
  FontSize 18
}
</style>
footer  どちらも同じアセット_位置を変えたのみ
title <img:https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/416e4046-498e-4f28-cc54-1fa5ec2c705e.png>
@enduml
```

試しにメタリックと粗さを上げて再配置してみると以下のようになりました。

```plantuml
@startuml
<style>
root {
  Padding 0
  Margin 0
}
footer {
  FontColor black
  FontSize 18
}
</style>
footer  左がガラス部分_右が液体部分のマテリアル
title <img:https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/c447097b-99a2-961e-70d8-d50e66128823.png>
@enduml
```


```plantuml
@startuml
<style>
root {
  Padding 0
  Margin 0
}
footer {
  FontColor black
  FontSize 18
}
</style>
footer  どちらも同じアセット_位置を変えたのみ
title <img:https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/29b2d81e-66c1-8059-cb27-17f9143ce2fc.png>
@enduml
```


この方向での調整では情報量の向上はあまり見込め無そうです。

### 調べたところ…… 

https://aframe.io/docs/0.9.0/components/material.html#environment-maps

反射していないのは、環境マップが与えられていないことが原因でした。


そのため、同じくキューブマップであるこちらを適用してみます。

https://www.8thwall.com/8thwall/cubemap-aframe

# 04_8thWallでRealtimeReflectionsの設定をする
先ほどのRealtime Reflectionsのプロジェクトを元に、反射の設定をします。

### モジュールの追加
Modulesの横のプラスボタンを押します。


![モジュール追加前_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/caa6a4cb-ea5c-9f9f-9723-6f9b5f5c9a7f.png)

中央左のReflections Moduleをクリックします。

![モジュール一覧画面_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/df7f0e00-9f0d-099c-895a-cb0fc0b0b05a.png)

importをクリックします。

![モジュールのインポート_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/7182a82f-2245-5589-7a43-6b378ee6be98.png)

追加されました。

![モジュール設定_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/342786f4-a29f-8d71-7d24-a4fcf9541f9a.png)

Filesの横のプラスマークからNewFileをクリックし、``` responsive-immersive.js ``` と命名します。

![fileの追加_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/f6a67291-5f9e-2c96-d274-ed7d3f1a5a9a.png)

以下のコードを追加します。8th Wallのサンプルプロジェクトのプログラムを元にして、ID指定部分を少しいじってリンゴ飴の飴部分(飴と飴以外で別のアセットにしています)とジュースのアセットを登録します。また、デバック用に```console.log```を付け加えています。
```responsive-immersive.js
// This component is an example of how to separate behavior by device category
// using 8th Wall Engine sessionAttributes
console.log('reflection')  // eslint-disable-line no-console
const responsiveImmersiveComponent = {
  init() {
    console.log('reflectionが読み込まれました')  // eslint-disable-line no-console
    const onAttach = ({sessionAttributes}) => {
      const realtimeBall1 = document.getElementById('appleCandyCandy')
      const realtimeBall2 = document.getElementById('appleJuice')
      const s = sessionAttributes

      // eslint-disable-next-line max-len
      const isDesktop = !s.cameraLinkedToViewer && !s.controlsCamera && !s.fillsCameraTexture && !s.supportsHtmlEmbedded && s.supportsHtmlOverlay && !s.usesMediaDevices && !s.usesWebXr
      // eslint-disable-next-line max-len
      const isHMD = s.cameraLinkedToViewer && s.controlsCamera && !s.fillsCameraTexture && s.supportsHtmlEmbedded && !s.supportsHtmlOverlay && !s.usesMediaDevices && s.usesWebXr
      // eslint-disable-next-line max-len
      const isMobile = !s.cameraLinkedToViewer && !s.controlsCamera && s.fillsCameraTexture && !s.supportsHtmlEmbedded && s.supportsHtmlOverlay && s.usesMediaDevices && !s.usesWebXr

      if (isDesktop) {
        console.log('デスクトップ版が読み込まれました')  // eslint-disable-line no-console
        // Desktop specific behavior goes here
        realtimeBall1.setAttribute('reflections', 'type: static')
        realtimeBall2.setAttribute('reflections', 'type: static')
      } else if (isHMD) {
        console.log('HMD版が読み込まれました')  // eslint-disable-line no-console
        // Head mounted display specific behavior goes here
        const isVRHMD = this.el.sceneEl.xrSession.environmentBlendMode === 'opaque'
        // eslint-disable-next-line max-len
        const isARHMD = this.el.sceneEl.xrSession.environmentBlendMode === 'additive' || 'alpha-blend'
        realtimeBall1.setAttribute('reflections', 'type: static')
        realtimeBall2.setAttribute('reflections', 'type: static')
      } else if (isMobile) {
        console.log('モバイル版が読み込まれました')  // eslint-disable-line no-console
        // Mobile-specific behavior goes here
        realtimeBall1.setAttribute('reflections', 'type: realtime')
        realtimeBall2.setAttribute('reflections', 'type: realtime')
      }
    }
    const onxrloaded = () => {
      XR8.addCameraPipelineModules([{'name': 'responsiveImmersive', onAttach}])
      console.log('onxrloadedが読み込まれました')  // eslint-disable-line no-console
    }
    window.XR8 ? onxrloaded() : window.addEventListener('xrloaded', onxrloaded)
    console.log('reflectionが完全に読み込まれました')  // eslint-disable-line no-console
  },
}
export {responsiveImmersiveComponent}
console.log('reflectionのページの最後です')  // eslint-disable-line no-console

```

```app.js```に以下の記述を加え、```responsive-immersive.js```でexportした```responsiveImmersiveComponent```を```body.html```で使用できるようにします。

```ruby:app.js
import {responsiveImmersiveComponent} from './responsive-immersive'
AFRAME.registerComponent('responsive-immersive', responsiveImmersiveComponent)

```
```body.html```に情報が渡ったので、<a-scene>のタグの中```responsive-immersive```を置きます。
```ruby:body.html
    responsive-immersive
```
これでentityでリアルタイム反射の設定が出来るようになりました。ライトとアセットは以下のような設定になっています。(アセットは他にもあるので一部抜粋)

```ruby:body.html
  <a-entity
    xr-light
    light="type: directional;
       castShadow: true;
       shadowMapHeight:2048;
       shadowMapWidth:2048;
       shadowCameraTop: 10;
       target: #group;
       shadowRadius: 10"
       intensity="1.0"
    xrextras-attach="target: group; offset: 0 15 0;"
    shadow>
  </a-entity>
```

```ruby:body.html
    <a-entity 
      id="group">

      <a-entity 
        id="appleCandyCandy"
        gltf-model="#appleCandy-Candy-glb"
        position= "-0.45 0.9 0.0"
        rotation="90 0 0" 
        scale="2.5 2.5 2.5"   
        animation-mixer = "timeScale: 0"
        shadow></a-entity>
        
      <a-entity 
        id="appleJuice"
        gltf-model="#appleJuice-glb" 
        position= "-0.4 0.4 0.5"
        rotation="90 0 0" 
        scale="2.8 2.8 2.8"   
        reflections="type: static"
        animation-mixer = "timeScale: 0"
        shadow></a-entity
        
    </a-entity>
```

:::note warn
注意
```responsive-immersive.js```で指定する
```responsive-immersive.js
      const realtimeBall1 = document.getElementById('appleCandyCandy')
      const realtimeBall2 = document.getElementById('appleJuice')
```
といったID(ここでは```appleCandyCandy```や```appleJuice```)は、entity内で指定したID
```body.html
      <a-entity 
        id="appleCandyCandy"
        
      <a-entity 
        id="appleJuice"
```
であることに注意してください。アセットを最初に読み込んだ時のID
```body.html
    <a-asset-item id="appleCandy-Candy-glb" 
     src="assets/appleCandy_Candy_05.glb"></a-asset-item>
    <a-asset-item id="appleJuice-glb"
     src="assets/glass_test_forQiita_12.glb"></a-asset-item>
```

(ここでは```appleCandy-Candy-glb```や```appleJuice-glb```)ではないので注意。
:::

### 出力結果の比較

![GlassComparisonVideo_02-ezgif.com-optimize.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644489/0614bce4-f289-a191-4470-b6837aebf6fa.gif)

反射に関しては少しずつ感じになってきました。環境マップはあった方がよさそうです。しかし、やはり他のアセットに比べて情報量が足りないため、__カラーテクスチャによって情報量を追加していくことにします。__

### 後編に続きます

https://qiita.com/MEMEMIYA/items/cbe9b7ae321157d46e91