LoginSignup
4
5

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-08-12

目的

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)=\frac{1}{\sqrt{2πσ^2}}exp(-\frac{(x-μ)^2}{2σ^2})

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

すると重みづけの式は

f(x)=exp(-\frac{x^2}{2σ^2})

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

FWHM=2\sqrt{2ln2}σ\approx2.3548σ

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

σ=\frac{0.4×5}{2.3548} = 0.84933

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

\frac{1}{2σ^2}=\frac{1}{2(0.84933)^2}=0.693135

最終的な重み関数の式は

f(x)=exp(-{0.6931×x^2})

これを用いて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.

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5