uniform int _Points_Length = 0;
については、おそらくお気になさる必要はないんじゃないかと思います。
恥ずかしながら私はああいったユニフォーム変数はマテリアルやスクリプトから設定してやることが前提だと思い込んでおり、シェーダーコード上で何らかの値で初期化する...というのはやったことがありませんでした。そこで調べてみたものの、いまいち明確な情報が得られなかったのですが「Initial values for variables in CG shader not being set - Unity Forum 」の意見を見ますに、シェーダーコード上の初期化時の値は無視されるんじゃないかと思います。コンパイル結果を見てみても、初期化時の値はコンパイル結果の中には登場しないようでした。
ご提示の「Arrays & shaders: heatmaps in Unity - Alan Zucconi 」だとStep 1の部分では_Points_Length
の初期化を行っていませんが、Step 2のシェーダーコード全容では唐突に初期化を行っており、これについて解説はないように見えます。なぜこのような形になっているのかは、すみませんが著者の方にたずねてみないと分かりませんね...
本題のヒートマップですが、元のコードではワールド座標をもとに影響度を計算しているところを、代わりにUV座標を使うようにしてみてはいかがでしょうか。
まず、シェーダーコードは下記のように変更しました。
ShaderLab
1 Shader "Hidden/Heatmap"
2 {
3 Properties
4 {
5 _HeatTex("Texture", 2D) = "white" {}//テクスチャを定義する型
6 }
7 SubShader
8 {
9 //描画タイプの設定Queue(レンダリング順)Transparent(透明)
10 Tags{ "Queue" = "Transparent" }
11 Blend SrcAlpha OneMinusSrcAlpha // Alpha blend
12
13 Pass
14 {
15 CGPROGRAM
16 //V/F Shaderの記述(ライトの影響を受けない)
17 #pragma vertex vert
18 #pragma fragment frag
19
20 //モデルの頂点座標を参照
21 struct vertInput
22 {
23 float4 pos : POSITION;
24 float2 uv : TEXCOORD0; // 頂点単位の入力情報にUV0を追加する
25 };
26 //vertexからfragmentへ変換するのに必要な情報
27 struct vertOutput
28 {
29 float4 pos : SV_POSITION; // 現行のUnityのしきたりにならい、セマンティクスを「POSITION」から「SV_POSITION」に変更
30 float2 uv : TEXCOORD0; // 名前が「worldPos」だと、後述の変更内容にそぐわないのでリネームする
31 };
32
33 //vertexシェーダー(input→output位置の計算)
34 vertOutput vert(vertInput input)
35 {
36 vertOutput o;
37 o.pos = UnityObjectToClipPos(input.pos);//頂点の座標位置を決定
38 o.uv = input.uv; // UV0は特に加工する必要はないので、そのままfragに伝達する
39 return o;
40 }
41
42 uniform int _Points_Length;
43 uniform float4 _Points[100]; // (x, y) = position, z = radius(半径), w = intensity(強度)
44 sampler2D _HeatTex;
45
46 //fragmentシェーダー(計算されたエリアの塗りつぶし)
47 half4 frag(vertOutput output) : SV_Target // 現行のUnityのしきたりにならい、セマンティクスを「COLOR」から「SV_Target」に変更
48 {
49 // すべてのpointsをループ
50 half h = 0;
51 for (int i = 0; i < _Points_Length; i++)
52 {
53 // 各pointsの貢献度を算出
54 // 配列を_Pointsだけに節約したのでこちらも修正を加えたが、計算ロジックは変更なし
55 half di = distance(output.uv, _Points[i].xy);
56 half ri = _Points[i].z;
57 half hi = 1 - saturate(di / ri);
58 h += hi * _Points[i].w;
59 }
60 // 熱量に応じて(0~1)に変換
61 h = saturate(h);
62 half4 color = tex2D(_HeatTex, float2(h, 0.5));
63 return color;
64 }
65 ENDCG
66 }
67 }
68 Fallback "Diffuse"
69 }
そして、スクリプト側は下記のようにしました。
lang
1 using UnityEngine;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.IO;
5 using UnityEngine.Networking;
6
7 public class Heatmap : MonoBehaviour
8 {
9 const int max_position = 3;
10 public string boomin;
11 private Vector4[] positions = new Vector4[max_position * max_position];
12 public List<float> inputDataList = new List<float>();
13 public Material material;
14 public float radius = 0.15f;
15 private float timeleft;
16
17 void Update()
18 {
19 timeleft -= Time.deltaTime;
20 if (timeleft <= 0.0)
21 {
22 timeleft = 10.0f;
23 StartCoroutine(textLoad());
24 }
25 }
26
27 IEnumerator textLoad()
28 {
29 // 古いデータは削除することにした
30 inputDataList.Clear();
31
32 string filepath = Application.streamingAssetsPath + "/test.txt";
33 if (filepath.Contains("://") || filepath.Contains(":///"))
34 {
35 // WWWは古い機能なのでUnityWebRequestを使え...と警告が出るため改修
36 UnityWebRequest request = UnityWebRequest.Get(filepath);
37 yield return request.SendWebRequest();
38 if (request.result != UnityWebRequest.Result.Success)
39 {
40 Debug.Log(request.error);
41 yield break;
42 }
43 string result = request.downloadHandler.text;
44 Debug.Log("WWW" + result);
45 StringReader sr = new StringReader(result);
46 //var result = File.OpenText(filepath);
47 while ((boomin = sr.ReadLine()) != null)
48 {
49 inputDataList.Add(float.Parse(boomin));
50 }
51 }
52 else
53 {
54 StreamReader result = File.OpenText(filepath);
55 while ((boomin = result.ReadLine()) != null)
56 {
57 inputDataList.Add(float.Parse(boomin));
58 }
59 Debug.Log("WWW" + result);
60 }
61
62 // inputDataListのロードが終わってからマテリアルデータを更新したいので
63 // UpdateTextureをコルーチンの末尾に引っ越し
64 UpdateTexture();
65 }
66
67 private void UpdateTexture()
68 {
69 for (int j = 0; j < max_position; j++)
70 {
71 for (int i = 0; i < max_position; i++)
72 {
73 // UV空間上で右上起点に点を並べることを前提に改修
74 int k = i + j * max_position;
75 if (k >= inputDataList.Count)
76 {
77 break;
78 }
79 float lengthX = 1.0f - (i + 0.5f) / max_position;
80 float lengthY = 1.0f - (j + 0.5f) / max_position;
81
82 //positions 位置(x,y)、z=半径、w=強度
83 // 調整を容易にするため、半径は固定値0.15fの代わりにpublicフィールドとする
84 positions[k] = new Vector4(lengthX, lengthY, radius, inputDataList[k]);
85 }
86 }
87 //Points_Lengthの整数値を設定
88 material.SetInt("_Points_Length", Mathf.Min(inputDataList.Count, max_position * max_position));
89 //ベクトル配列のPointsを設定
90 material.SetVectorArray("_Points", positions);
91 }
92 }
グラデーション画像として下図のようなものを用いたところ...
半径がデフォルトの0.15だと...
0.3だと...
0.45だと...
となりました。
また、ご提示いただいたイメージ図から勝手に想像しますと、方式をちょっと変更してしまってもいいんじゃないかと思います。
まずスクリプトは下記のようにして...
lang
1 using UnityEngine;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.IO;
5 using UnityEngine.Networking;
6
7 public class Heatmap2 : MonoBehaviour
8 {
9 public string boomin;
10 public List<float> inputDataList = new List<float>();
11 public Material material;
12
13 Texture2D heatmap;
14 float[] heatmapData;
15 float timeleft;
16
17 void Update()
18 {
19 timeleft -= Time.deltaTime;
20 if (timeleft <= 0.0)
21 {
22 timeleft = 10.0f;
23 StartCoroutine(textLoad());
24 }
25 }
26
27 // データのロードは変更なし
28 IEnumerator textLoad()
29 {
30 inputDataList.Clear();
31
32 string filepath = Application.streamingAssetsPath + "/test.txt";
33 if (filepath.Contains("://") || filepath.Contains(":///"))
34 {
35 UnityWebRequest request = UnityWebRequest.Get(filepath);
36 yield return request.SendWebRequest();
37 if (request.result != UnityWebRequest.Result.Success)
38 {
39 Debug.Log(request.error);
40 yield break;
41 }
42 string result = request.downloadHandler.text;
43 Debug.Log("WWW" + result);
44 StringReader sr = new StringReader(result);
45 while ((boomin = sr.ReadLine()) != null)
46 {
47 inputDataList.Add(float.Parse(boomin));
48 }
49 }
50 else
51 {
52 StreamReader result = File.OpenText(filepath);
53 while ((boomin = result.ReadLine()) != null)
54 {
55 inputDataList.Add(float.Parse(boomin));
56 }
57 Debug.Log("WWW" + result);
58 }
59
60 UpdateTexture();
61 }
62
63 // データをテクスチャとして供給するように変更
64 void UpdateTexture()
65 {
66 int dataCount = inputDataList.Count;
67 int textureSize = Mathf.CeilToInt(Mathf.Sqrt(dataCount));
68 int dataCapacity = textureSize * textureSize;
69 if ((heatmap == null) || (heatmap.width != textureSize))
70 {
71 Destroy(heatmap);
72 heatmap = new Texture2D(textureSize, textureSize, TextureFormat.RFloat, true)
73 {
74 wrapMode = TextureWrapMode.Clamp
75 };
76 heatmapData = new float[dataCapacity];
77 material.SetTexture("_Heatmap", heatmap);
78 }
79 for (int i = 0; i < dataCapacity; i++)
80 {
81 int j = dataCapacity - 1 - i;
82 heatmapData[j] = inputDataList[Mathf.Min(i, dataCount - 1)];
83 }
84 heatmap.SetPixelData(heatmapData, 0);
85 heatmap.Apply();
86 }
87 }
シェーダーは下記のようにしたところ...
ShaderLab
1 Shader "Hidden/Heatmap2"
2 {
3 Properties
4 {
5 [NoScaleOffset] _HeatTex("Texture", 2D) = "white" {}
6 }
7 SubShader
8 {
9 Tags{ "Queue" = "Transparent" }
10 Blend SrcAlpha OneMinusSrcAlpha
11
12 Pass
13 {
14 CGPROGRAM
15 #pragma vertex vert
16 #pragma fragment frag
17
18 struct vertInput
19 {
20 float4 pos : POSITION;
21 float2 uv : TEXCOORD0;
22 };
23
24 struct vertOutput
25 {
26 float4 pos : SV_POSITION;
27 float2 uv : TEXCOORD0;
28 };
29
30 vertOutput vert(vertInput input)
31 {
32 vertOutput o;
33 o.pos = UnityObjectToClipPos(input.pos);
34 o.uv = input.uv;
35 return o;
36 }
37
38 sampler2D _HeatTex;
39 sampler2D _Heatmap;
40
41 half4 frag(vertOutput output) : SV_Target
42 {
43 float h = saturate(tex2D(_Heatmap, output.uv).r);
44 return tex2D(_HeatTex, float2(h, 0.5));
45 }
46 ENDCG
47 }
48 }
49 Fallback "Diffuse"
50 }
下図のような見た目になりました。
字数節約のためコード中のコメントは簡素になっています。もし疑問点がありましたらお気軽にコメントください。
##範囲外を透明に抜く件について
まず、testファイル上のデータを下記のようにして、プラス方向とマイナス方向に範囲をはみ出すようにしました。
-0.25
-0.08333333333
0.08333333333
0.25
0.41666666666
0.58333333333
0.75
0.91666666666
1.08333333333
シェーダーは最初のHidden/Heatmap
をベースに検討することにし、マテリアルのRadius
は0.45としました。
まず第1案としては、グラデーションテクスチャの両端を透明にしてしまうのはどうでしょうか?
両端1ピクセルを透明にすれば...
0.0以下や1.0以上の部分が透明になるでしょうし...
両端の透明度をなだらかに変化させれば...
境界をなめらかにすることができるかと思います。
また第2案として、グラデーションテクスチャには変更を加えず、シェーダーコードの下記部分を...
ShaderLab
1 h = saturate(h);
2 half4 color = tex2D(_HeatTex, float2(h, 0.5));
3 return color;
下記のように変更し、h
が0.0を下回ったり1.0を上回ったフラグメントを破棄してしまう手もあるかと思います。
ShaderLab
1 half clampedH = saturate(h);
2 clip(-abs(h - clampedH));
3 half4 color = tex2D(_HeatTex, float2(clampedH, 0.5));
4 return color;
こちらの場合だと、0.0ぴったりや1.0ぴったりは範囲内におさまっていると判定させることができるでしょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/08/27 06:20
2021/08/28 20:11
2021/08/30 11:46