【Unity】Pcxで読み込んだ点群をWebGL上で表示する

##はじめに
あくまでこの記事はさんざん悩んだ末にようやく実現できた機能を、自分向けの備忘録として残しておくため作成する記事です。

そのため、要所要所は端折って表記することを予めご了承ください。もし、どうしてもここが分からない！などの質問がございましたら、コメントしていただけると対応出来るかもしれません。

また、点群を表示するにはこちらのアセット[【Pcx】](https://github.com/keijiro/Pcx)が必要となります。
更に、この記事で紹介する動作を実現するにあたって、こちらの記事[【UnityのWebGLで点群を表示してみる】](https://qiita.com/mechamogera/items/be339e86ef0e54d19bdb#comment-b61c11242899fbdcbd41)が大変参考になりました。アセット作成者のKeijiro Takahashi様、記事作成者のmechamogera様にはこの場を借りてお礼申し上げます。


##Step1,点群を表示し、プラットフォームをWebGLに変更する
まず[【Pcx】](https://github.com/keijiro/Pcx)をUnityで開き、TestフォルダにあるBeeシーンを開きます。

このままシーンを再生するとカメラとオブジェクトが動いてしまうので、それぞれのAnimatorのからを外し、
BeeにアタッチされているシェーダーカラーのAを0にしてグレーぐらいの色にしておきます。カメラの位置は全体が映るように自由に調整して、ClearFlagsをSkyboxに変更しておきます。(こんな具合）
![PointSizeUnity上.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/60858213-8322-3b1a-66f1-07c8ce60ee9d.png)

次にプラットフォームをWebGLに変更します、念のため変更方法を記述しておきます。

1、File→BuildSteeingを開き、PlatformのWebGLを選択してSwitchPlatformを押す
![WebGLBuild_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/ec0478c8-f162-593a-42da-812634fe00a9.jpeg)

2、エラーが表示され、このままではビルドできないので、
　 Edit→ProjectSettings→Player→OtherSettings→RenderingのColorSpaceをLinearからGammaに変更する
![ProjectSettingGamma.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/4591c6f4-8a70-8804-ecc7-108147350d90.png)

これでひとまず、ビルド自体はできるようになりました。ですが、この状態でビルドしても、WebGL上ではなにも表示されません。
次は「そもそもなぜWebGL上では点群が表示されないのか？」についてです。

##Step2,なぜWebGL上では点群が表示されないのか？
「**なぜWebGL上で点群が表示されないのか？**」という点ですが、これは単純に「**点群を表示するためのシェーダーが用意されていないから**」です。（ﾀﾌﾞﾝﾈ）

もともとUnityで点群を表示する為にPcxというアセットが必要な訳で、Unityは点群に対応していないといえます。

そしてWebGL上で点群を表示するにはGLSLで書かれたシェーダーが必要になります。
これがなかなかの曲者で、Unity上でGLSLで書かれたシェーダーを適応すると、シェーダーが参照できずにピンク色になってしまいます。（かなり気持ち悪い）

【GLSLで書かれたシェーダーを適応した状態】
![GLSL.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/8a0925a8-b848-22ea-e261-48e82a9f7600.png)
ですが、ビルド時にこのGLSLで書かれたを適応することで、WebGL上でも点群を表示することができるようになります。
察しのいい方はお気付きかもしれませんが、シーンを再生こそできますが、シェーダーは参照エラーを起こしているようなものなので、**Unity上ではPointSizeの変更が適応されません。**（WebGL上では適応されます）

~~いちいちビルドして確認する必要があるので、調整がめちゃくちゃめんどうです。誰かいい方法を思いついたら教えてください。~~
調査した結果、関係性が判明したので後述しておきます。

仕組みをなんとなく理解したところで、本題のWebGL上で点群を表示するShaderとScriptです。

##Step3,WebGL上で点群を表示するShaderとScript
まず、【ビルド時に適応するGLSLで書かれたシェーダー】と【このシェーダーをWebGLビルド時のみに適応＆PointSizeを調整するスクリプト】を作成します。

<details><summary>【Shader&Script】　（click here) </summary><div>
【Shader】

```glsl:CustomPointCloud_GL.shader
Shader "Custom/PointCloud_GL"
{
    Properties
    {
        _PointSize("PointSize", Float) = 0
    }

    SubShader
    {
        Pass
        {
            LOD 200

            GLSLPROGRAM
            #pragma multi_compile __ HEIGHTMAP_ON

            #ifdef VERTEX

            out vec4 Color;
            uniform sampler2D _HeightTex;
            uniform float _PointSize;

            float ToneMap(float rawValue)
            {
                float contrast = 3.8;
                float exposure = 0.001;
                float a = exposure * pow(rawValue, contrast);
                return a / (1.0f + a);
            }

            void main()
            {
                gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
                gl_PointSize = _PointSize * (1.0 / gl_Position.w);

                #ifdef HEIGHTMAP_ON
                float index = ToneMap(gl_MultiTexCoord0.x);
                Color = texture2D(_HeightTex, vec2(index, 0));
                #else
                Color = gl_Color;
                #endif
            }

            #endif

            #ifdef FRAGMENT

            in vec4 Color;

            void main()
            {
                vec2 coord = gl_PointCoord - vec2(0.5);
                if (length(coord) > 0.5)
                {
                    discard;
                }

                gl_FragColor = Color;
            }

            #endif

            ENDGLSL
        }
    }
}
```
【Script】

```C#:PCoptimisation.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PCoptimisation : MonoBehaviour
{
    [Range(0f,100f)]
    public float PointSize; 
    void Start()
    {
#if !UNITY_EDITOR && UNITY_WEBGL
        gameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Custom/PointCloud_GL");
#endif
        GetComponent<Renderer>().material.EnableKeyword("_PointSize");
        if(gameObject.GetComponent<MeshRenderer>().material.shader.name == "Custom/PointCloud_GL")
        {
            GetComponent<Renderer>().material.SetFloat("_PointSize", PointSize);
        }
    }
}
```
</div>　</details>
###解説
Shaderの_PointSizeですが、ここの数値を変更してもビルド後の点の大きさは変化しません。

```glsl
    Properties
    {
        _PointSize("PointSize", Float) = 0 ←この数値をいじっても意味がないので0にしている
    }
```
そのため、スクリプトの方でシェーダーのPointSizeを取得し、値を渡しています。
そのほかの部分は借り物なのでよく理解していません。（使えればオッケーなんです）
<br>


スクリプトの方の解説です、まずPointSizeに渡す値をFloatで宣言しています。
（Rangeをつけてスライダーを表示していますが、正直必要ないので消しても構いません。）

```C#
    [Range(0f,100f)]
    public float PointSize; 
```

<br>
ここはUnity上ではPointCloud/Diskシェーダーで表示していたいので、ビルド時のみCustom/PointCloud_GLシェーダーを適応するという処理です。

```C#
    void Start()
    {
#if !UNITY_EDITOR && UNITY_WEBGL
        gameObject.GetComponent<MeshRenderer>().material.shader = Shader.Find("Custom/PointCloud_GL");
#endif 
```
<br>
まずシェーダーを取得し、その内部で宣言されている"_PointSize"の変数名を有効化します。
その後、先ほども記述したようにスクリプトからPointSizeに値を渡します。

if文で括っているのは、これがないとシーン再生時にPointCloud/DiskのPointSizeを変更してしまい、大きくなりすぎるので、ビルド時のみ適応するという処理です。

```C#
        GetComponent<Renderer>().material.EnableKeyword("_PointSize");
        if(gameObject.GetComponent<MeshRenderer>().material.shader.name == "Custom/PointCloud_GL")
        {
            GetComponent<Renderer>().material.SetFloat("_PointSize", PointSize);
        }
     }
```
<br>
さて、PointCloud/Diskとスクリプトで渡す値の関係性ですが、mmとm表記のようです。
なので、PointCloud/Diskでちょうどいい点の大きさをシーンを再生しながら調べ、スクリプトにはその数字を1,000倍した値を渡します。
![mmとm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/f2105a03-4447-a5fb-2aeb-0df3003941ef.png)

スクリプトの方はWebGL上で表示したい点群オブジェクトにアタッチしておきます。
Shaderの方は設定する必要があるので、次はそちらを説明します。

##Step4,作成したShaderをProjectSettingsに登録する
先ほど作成したCustomPointCloud_GL.shaderをProjectSettingsに登録します。
この設定をしない限り、WebGL上で点群が表示されることはありません。

ProjectSettings→Graphics→Always Induded Shadersに登録します。
初めはSizeが6になっていると思うので、これを7にして増えたElement6にCustomPointCloud_GL.shaderをドラッグ＆ドロップします。
![ProjectSettingBildShader.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/47fa76fd-ce81-5b59-041b-cd9a5ab2f7d9.png)

これでようやくビルドする準備が整いました。
しかし、ビルドすると恐らく発生すると思ううのですが、このようなエラーが発生します。
![よくわからんBildError.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/91447369-cb33-d334-e5b3-8bbd3d391894.png)
ですが慌ててはいけません、このエラーはよくわかっていませんが、一回目は必ず出ます。（場合によっては複数回出るかもしれません）
しかしながら、もう一度Buildを押すと何故か問題なくBuildされます。謎ですね。

あと初歩的なミスですが、よくやってしまうのがBuildする際にBuildしたいシーンを追加することを忘れてしまうことです。
![Bild設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/3585abd3-8281-1e91-cfe3-3d4210287615.png)


とにもかくにも、これでWebGL上で点群を表示できるようになりました。
![GLSL.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421073/81f9a957-5e59-e66f-d7c1-67f9c5ff6b08.png)

##さいごに
冒頭でも書きましたが、Unityで点群を扱おうとする人は少ないと思いますが、私自身この機能を実現する為に先人の知恵に助けられたので、それらを集結させた形というところです。

この知識がだれかの役に立つことを願います。