Unity
Shader
視線追跡
4
どのような問題がありますか?

この記事は最終更新日から1年以上が経過しています。

投稿日

更新日

UnityのPlane上に注視点ヒートマップを作る

目的

VR空間での視線情報の可視化。
個人的覚え書き。

ヒートマップのリポジトリ:https://github.com/sakamo1290/FixationMap

方針

Shaderで注視点を中心に赤→緑→青と色分けする。
時間経過とともに青→緑→赤となるようにする。
demo.gif

色の変化

注視点を中心に赤→緑→青とする。

HSV変換を用いる。
HSVは色相(Hue),彩度(Saturation),明度(Value)の3成分からなる色空間のこと。
色相は具体的な色を定義する要素。色が環状に並んでいるため0°~360°で表される。(今回の実装では0~1に正規化している)
hsv
ここで示されているように色相は0°~240°にかけて赤→緑→青と変化するためこれを利用する。
正規化されているため色相が0なら赤,0.66なら青になる。
HSV変換の実装は このページを参考にした。

GazeMap.Shader
            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                float4 worldPos : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4x4 _TransformMatrix;
            float4 _CubePos;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                //uv座標をワールド座標に変換
                o.worldPos = mul(_TransformMatrix, float4(-(v.uv.x - 0.5) * 10, 0, -(v.uv.y - 0.5) * 10, 1));
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                UNITY_APPLY_FOG(i.fogCoord, col);
                //ヒートマップの効果範囲(仮置き)
                float maxDia = 0.5;
                //マッピングするための重み
                //1.5秒で赤くなるように135で割る(fps:90)
                float probability = (clamp(maxDia - distance(i.worldPos, _CubePos), 0, maxDia)) / 135;
                //hsvに変換
                float3 hsv = rgb2hsv(float3(col.r, col.g, col.b));
                //1frame前のヒートマップに重畳
                float h = hsv.y == 0 ? 0.66 - probability : hsv.x - probability < 0 ? 0 : hsv.x - probability;
                //効果範囲内なら色を変える
                return maxDia> distance(i.worldPos, _CubePos) ? float4(hsv2rgb(float3(h, 1, 1)), 1) : col;
            }

時間経過とともに青→緑→赤

bufferで1つ前のフレームの計算結果を使う

MainGazeMap.cs
    public Material iniMat;
    public Material paintMat;
    public Transform gazePoint;

    Material mainMaterial;
    RenderTexture mainTex;
    void Start()
    {
        mainTex = new RenderTexture(770, 1000, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default);
        Graphics.Blit(mainTex, mainTex, iniMat);

        mainMaterial = GetComponent<Renderer>().material;
        mainMaterial.SetTexture("_MainTex", mainTex);
    }
    void FixedUpdate()
    {
        //bufferを作成
        RenderTexture buf = RenderTexture.GetTemporary(770, 1000); ;
        //paintMatを更新
        MatUpdate();
        //mainTexとpaintMatを使ってbufferに描画
        Graphics.Blit(mainTex, buf, paintMat);
        //bufferの内容をmainTexに上書き
        Graphics.Blit(buf, mainTex);

        RenderTexture.ReleaseTemporary(buf);
    }
    void MatUpdate()
    {
        paintMat.SetVector("_CubePos", new Vector4(gazePoint.position.x, gazePoint.position.y, gazePoint.position.z, 1));
        paintMat.SetTexture("_MainTex", mainTex);
        paintMat.SetMatrix("_TransformMatrix", transform.localToWorldMatrix);
    }

動かす

3DオブジェクトのPlaneを配置して名前をGazeMapにする。
Cubeを作って名前をGazePointとしてGazeMap上に置く。
MainGazeMap.csをアタッチして初期化マテリアルとペイントマテリアルを配置する。
unity image.PNG
初期化マテリアルはRendering ModeをFadeにする。
defaltMat.PNG
ペイントマテリアルは作成したGazeMap.Shaderをあてる。
paintMat.PNG
これで動かすと下の画像のようになる
demo.gif

視野を考慮してマッピング範囲を決める

人間の視野は中心2°が鮮明に、5°までをぼんやりと認識している。
そのため中心部分の重みを大きくしてより効果的な可視化を行いたい。
GazeSpan.png
そのために正規分布を変形して重みづけをする。
平均μ,標準偏差σの正規分布の式は

f(x)=12πσ2exp((xμ)22σ2)

今回は注視点を中心にするため平均は0、必要なのは確率密度ではなく重みづけがしたいだけだから係数は1とする。

すると重みづけの式は

f(x)=exp(x22σ2)

この式のxは描画点の角度に相当するためσを求めればよい。
上の図から今回は角度が1°の時に重みが半分になるようにする。
そのために半値全幅(full width at half maximum, FWHM)からσを求める。

FWHM=22ln2σ2.3548σ

FWHMは1°/2.5°より0.4となり、また正規分布が2.5°付近で0に収束するように5をかけると

σ=0.4×52.3548=0.84933

指数の肩の整数部分を計算すると

12σ2=12(0.84933)2=0.693135

最終的な重み関数の式は

f(x)=exp(0.6931×x2)

これを用いてGazeMap.ShaderとMainGazeMap.csを修正する。
視線の開始位置の情報を加える。

GazeMap.Shader
            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                UNITY_APPLY_FOG(i.fogCoord, col);
                //対象ピクセルの角度を計算
                //180÷3.1415 = 57.297を使ってラジアンを度数法に変換
                float angle = atan2(distance(i.worldPos, _CubePos), distance(i.worldPos, _GazeOri)) * 57.297;
                //正規分布を使って中心に重みづけする。
                float probability = exp(-0.6931 * angle * angle)/135;
                float3 hsv = rgb2hsv(float3(col.r, col.g, col.b));
                float h = hsv.y == 0 ? 0.66 - probability : hsv.x - probability < 0 ? 0 : hsv.x - probability;
                //視野範囲内ならマッピングをする
                return angle < 2.5 ? float4(hsv2rgb(float3(h, 1, 1)), 1) : col;
            }
MainGazeMap.cs
    public Transform gazeOrigin;
    void MatUpdate()
    {
        paintMat.SetVector("_CubePos", new Vector4(gazePoint.position.x, gazePoint.position.y, gazePoint.position.z, 1));
        //追加
        paintMat.SetVector("_GazeOri", new Vector4(gazeOrigin.position.x, gazeOrigin.position.y, gazeOrigin.position.z, 1));
        paintMat.SetTexture("_MainTex", mainTex);
        paintMat.SetMatrix("_TransformMatrix", transform.localToWorldMatrix);
    }

修正して動かすと画像のようになる。
円周付近の色の変化が遅くなっていることがわかる。
gaussian.gif

参考

【Unity】RGBをHSVに変換して明るさとかを変えるシェーダー
UnityでRenderTextureをファイルに保存
Blignaut, Pieter. (2010). Visual span and other parameters for the generation of heatmaps. Eye Tracking Research and Applications Symposium (ETRA). 125-128. 10.1145/1743666.1743697.

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
ユーザー登録ログイン
sakamo1290
大阪大学情報科学研究科D2

コメント

この記事にコメントはありません。
あなたもコメントしてみませんか :)
ユーザー登録
すでにアカウントを持っている方はログイン
記事投稿イベント開催中
エンジニア夏休み企画!~自由研究や読書感想文を発表しよう~
~
Go強化月間~開発する上で知っておくべき知見を共有しよう~
~
4
どのような問題がありますか?
ユーザー登録して、Qiitaをもっと便利に使ってみませんか

この機能を利用するにはログインする必要があります。ログインするとさらに下記の機能が使えます。

  1. ユーザーやタグのフォロー機能であなたにマッチした記事をお届け
  2. ストック機能で便利な情報を後から効率的に読み返せる
ユーザー登録ログイン
ストックするカテゴリー