質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

5453閲覧

Unity 数値データからカラーマップの描画について(特にシェーダーについて知りたい)

masutake0

総合スコア3

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2021/08/11 07:04

前提・実現したいこと

テキストファイルにある数値データを読み込んで、数値ごとに対応する色となるようなカラーマップ(下図のような)を作製したいです。数値データを読み込むことはできており、目標までかなり近いところまで来ているとは思うのですが、シェーダーがよくわからず苦戦しています。
右上から順番に数値が大きくなっているイメージ
↑右上から順番に数値が大きくなっているイメージ

試したこと

こちらを参考にしてテキストファイルにある数値を読み込んでそれに対応する描画を行っています。
下図がその結果です。
イメージ説明
これを行った手順を以下に示します。
まず、Quadオブジェクトを生成して以下のC#スクリプトをアタッチします。

C#

1using UnityEngine; 2using System.Collections; 3using System.Collections.Generic; 4using System.IO; 5using UnityEngine.Networking; 6 7public class Heatmap : MonoBehaviour 8{ 9 public Vector4[] positions; 10 public Vector4[] properties; 11 public string boomin; 12 public List<float> inputDataList = new List<float>(); 13 public Material material; 14 private string result; 15 private float timeleft; 16 int count = 100; 17 int max_position = 3; 18 void Update() 19 { 20 positions = new Vector4[count]; 21 properties = new Vector4[count]; 22 timeleft -= Time.deltaTime; 23 if (timeleft <= 0.0) 24 { 25 timeleft = 10.0f; 26 StartCoroutine(textLoad()); 27 UpdateTexture(); 28 } 29 } 30 31 IEnumerator textLoad() 32 { 33 string filepath = Application.streamingAssetsPath + "/test.txt"; 34 if (filepath.Contains("://") || filepath.Contains(":///")) 35 { 36 WWW www = new WWW(filepath); 37 yield return www; 38 var result = www.text; 39 Debug.Log("WWW" + result); 40 StringReader sr = new StringReader(result); 41 //var result = File.OpenText(filepath); 42 while ((boomin = sr.ReadLine()) != null) 43 { 44 inputDataList.Add(float.Parse(boomin)); 45 } 46 } 47 else 48 { 49 var result = File.OpenText(filepath); 50 while ((boomin = result.ReadLine()) != null) 51 { 52 inputDataList.Add(float.Parse(boomin)); 53 } 54 Debug.Log("WWW" + result); 55 } 56 } 57 58 private void UpdateTexture() 59 { 60 for (int j = 0; j < max_position; j++) 61 { 62 for (int i = 0; i < max_position; i++) 63 { 64 int k = i + j * max_position; 65 float lengthX = i * 2f / max_position - 1f; 66 float lengthY = j * 2f / max_position - 1f; 67 68 //positions 位置(x,y,z) 69 positions[k] = new Vector4(lengthX, lengthY, 0, 0); 70 //properties x=半径、y=強度 71 properties[k] = new Vector4(0.15f, inputDataList[k], 0, 0); 72 } 73 } 74 //Points_Lengthの整数値を設定 75 material.SetInt("_Points_Length", count); 76 //ベクトル配列のPointsを設定 77 material.SetVectorArray("_Points", positions); 78 //ベクトル配列のPropertiesを設定 79 material.SetVectorArray("_Properties", properties); 80 } 81}

次にマテリアルに以下のシェーダーを対応させます。
マテリアルのテクスチャには参照したいテクスチャを設定しています。
そのマテリアルをQuadにアタッチします。

ShaderLab

1Shader "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 }; 25 //vertexからfragmentへ変換するのに必要な情報 26 struct vertOutput 27 { 28 float4 pos : POSITION; 29 float2 worldPos : TEXCOORD0; 30 }; 31 32 //vertexシェーダー(input→output位置の計算) 33 vertOutput vert(vertInput input) 34 { 35 vertOutput o; 36 o.pos = UnityObjectToClipPos(input.pos);//頂点の座標位置を決定 37 o.worldPos = mul(unity_ObjectToWorld, input.pos).xyz; 38 return o; 39 } 40 41 uniform int _Points_Length = 0; 42 uniform float4 _Points[100]; // (x, y, z) = position 43 uniform float4 _Properties[100]; // x = radius(半径), y = intensity(強度) 44 sampler2D _HeatTex; 45 46 //fragmentシェーダー(計算されたエリアの塗りつぶし) 47 half4 frag(vertOutput output) : COLOR 48 { 49 // すべてのpointsをループ 50 half h = 0; 51 for (int i = 0; i < _Points_Length; i++) 52 { 53 // 各pointsの貢献度を算出 54 half di = distance(output.worldPos, _Points[i].xyz); 55 half ri = _Properties[i].x; 56 half hi = 1 - saturate(di / ri); 57 h += hi * _Properties[i].y; 58 } 59 // 熱量に応じて(0~1)に変換 60 h = saturate(h); 61 half4 color = tex2D(_HeatTex, float2(h, 0.5)); 62 return color; 63 } 64 ENDCG 65 } 66 } 67 Fallback "Diffuse" 68}

そして、Assets配下にStreamingAssetsを作成してその中に数値が入ったテキストファイルを入れています。
テキストファイルの中身は以下のようになっています。

0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9

###発生している問題
記載したC#スクリプトとシェーダーは参考にした記事のものをいじって使用しており、シェーダーに関してはどう直せばいいかわからない状況です。特にシェーダー内の「_Points_Length」という変数は「_Points_Length = 0」としているのにfor文の最大値として使用されており、ループしないのでは?という疑問があります。
シェーダーはデバックができないので、こういう変数でも詰まってしまっているのでどなたかご教授いただきたいです。
よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

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

1Shader "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

1using UnityEngine; 2using System.Collections; 3using System.Collections.Generic; 4using System.IO; 5using UnityEngine.Networking; 6 7public 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}

グラデーション画像として下図のようなものを用いたところ...

図1

半径がデフォルトの0.15だと...

図2

0.3だと...

図3

0.45だと...

図4

となりました。
また、ご提示いただいたイメージ図から勝手に想像しますと、方式をちょっと変更してしまってもいいんじゃないかと思います。
まずスクリプトは下記のようにして...

lang

1using UnityEngine; 2using System.Collections; 3using System.Collections.Generic; 4using System.IO; 5using UnityEngine.Networking; 6 7public 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

1Shader "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}

下図のような見た目になりました。

図5

字数節約のためコード中のコメントは簡素になっています。もし疑問点がありましたらお気軽にコメントください。

##範囲外を透明に抜く件について

まず、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ピクセルを透明にすれば...

グラデーション1

0.0以下や1.0以上の部分が透明になるでしょうし...

結果1

両端の透明度をなだらかに変化させれば...

グラデーション2

境界をなめらかにすることができるかと思います。

結果2

また第2案として、グラデーションテクスチャには変更を加えず、シェーダーコードの下記部分を...

ShaderLab

1h = saturate(h); 2half4 color = tex2D(_HeatTex, float2(h, 0.5)); 3return color;

下記のように変更し、hが0.0を下回ったり1.0を上回ったフラグメントを破棄してしまう手もあるかと思います。

ShaderLab

1half clampedH = saturate(h); 2clip(-abs(h - clampedH)); 3half4 color = tex2D(_HeatTex, float2(clampedH, 0.5)); 4return color;

こちらの場合だと、0.0ぴったりや1.0ぴったりは範囲内におさまっていると判定させることができるでしょう。

結果3

投稿2021/08/11 21:58

編集2021/08/28 20:11
Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

masutake0

2021/08/27 06:20

返信遅くなってしまい申し訳ございません。 的確な回答ありがとうございます。 私のやりたいことはまさにBongoさんが回答してくださった通りで、 こちらでも確認することができました。 スクリプトではファイル内の値を0-1の範囲でクランプするという形になっているかと思います。 できれば値が範囲外の場合は透明にしたいと思っているのですが、 指針だけでもご教授いただけないでしょうか。
Bongo

2021/08/28 20:11

範囲外を透明にする案を検討してみました。 「範囲外」ということですと、後者の案のように1.0を超えたり0.0に満たない部分だけを抜いた方が望ましいでしょうかね?
masutake0

2021/08/30 11:46

ご回答してくださり、ありがとうございます。 言葉足らずで申し訳ありません。 値が1を超えるか0に満たない場合に透明にするのが理想でした。 ただ、前者の場合でも問題はなく、参照するカラースケールの両端を透明にすればいいという発想がなかったため、大変助かりました。 とても丁寧に回答してくださり本当にありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問