🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C#

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

Unity

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

Q&A

解決済

1回答

7754閲覧

unity マウスクリックした部分の色を抽出する

pigma689

総合スコア11

C#

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

Unity

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

0グッド

0クリップ

投稿2019/08/26 14:59

unityで、マウスクリックした部分の画素の色を取得したい。
http://tsubakit1.hateblo.jp/entry/20131203/1386000440
など、いろいろ調べて参考にした結果、以下のように記述しました。
しかし、実行すると

ReadPixels was called to read pixels from system frame buffer, while not inside drawing frame.
UnityEngine.Texture2D:ReadPixels(Rect, Int32, Int32)

[d3d11] attempting to ReadPixels outside of RenderTexture bounds! Reading (77, 1877, 78, 1878) from (951, 563)
のエラーが出てしまいました。
また
ReadPixcels(Rect,distX,distY,: int, recalculateMipMaps: bool = true): void;
のdistX,distYの部分の意味が分かっていませんし、エラーを出しつつ得られたcolorの値はすべて同じでした。

エラーの意味とReadPixcelsの解説、正しいcolorの値が得られない理由を教えていただけないでしょうか。

using UnityEngine; public class GetColor : MonoBehaviour { public Color32 color; private Texture2D tex = null; void Start() { tex = new Texture2D(Screen.width, Screen.height, TextureFormat.RGBA32, false); } void Update() { if (Input.GetMouseButtonDown(0)) { Vector2 pos = Input.mousePosition; Vector2 objPos = Camera.main.ScreenToWorldPoint(pos); tex.ReadPixels(new Rect(objPos.x, objPos.y, 1, 1), 0, 0); color = tex.GetPixel(0, 0); Debug.Log(color.ToString()); } } }

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

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

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

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

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

guest

回答1

0

ベストアンサー

ReadPixelsの引数のsourceは読み取り元(今回の場合は画面)のピクセル単位での読み取り範囲、destXdestYは書き込み先(今回の場合はtex)のピクセル単位の位置を表します。おそらくすでにリファレンスをご覧になったかと思いますが、パラメーター説明の「読み込み元のビューの長方形領域」はまだマシな気がしますが「テクスチャ内の読みこむピクセルの水平位置/垂直位置」という表現は何だか意味不明ですね...
英語版だと「Horizontal pixel position in the texture to place the pixels that are read」...つまり「読み込まれたピクセルを置く(書き込む)、テクスチャ内の水平ピクセル位置」と説明されており、こっちのほうがしっくりくるように思います。

エラー自体は取得範囲が不適切であることを言っているようですが、ご質問者さんのコードでテクスチャのサイズやposの座標系をテラシュールブログさんのものと変えているのは、それを修正しようと試みてのことでしょうか?
今回検討するべきなのはReadPixelsを行うタイミングのように思います。Updateだと描画が終わっておらず早すぎるでしょうから、テラシュールブログさんと同様にOnPostRenderを使ってはいかがでしょう。
ただし、試してみたかぎりではOnPostRender内だとGetMouseButtonDownを正しく取れないっぽいので、Updateも併用する形がいいんじゃないかと思います。

C#

1using UnityEngine; 2 3public class GetColor : MonoBehaviour 4{ 5 public Color32 color; 6 private GetColorHelper helper; 7 8 void Start() 9 { 10 // GetColorスクリプト自体をカメラにアタッチしてもいいなら、GetColorにUpdateと 11 // OnPostRenderを両方実装して連携させることができるかと思いますが、GetColorは 12 // 別のオブジェクトにアタッチしたい...といった事情がある場合は、GetColor本体と 13 // カメラ用コンポーネントの2つに担当を分けて連携させることになるかと思います 14 // 今回の例では補助コンポーネントとしてGetColorHelperを用意し、これをメインカメラに 15 // アタッチしてOnPostRender部分を担当させることにしました 16 helper = Camera.main.GetComponent<GetColorHelper>(); 17 if (helper == null) 18 { 19 helper = Camera.main.gameObject.AddComponent<GetColorHelper>(); 20 } 21 } 22 23 void Update() 24 { 25 if (Input.GetMouseButtonDown(0)) 26 { 27 color = helper.Tex.GetPixel(0, 0); 28 Debug.Log(color.ToString()); 29 } 30 } 31 32 [RequireComponent(typeof(Camera))] 33 private class GetColorHelper : MonoBehaviour 34 { 35 public Texture2D Tex { get; private set; } 36 37 void Awake() 38 { 39 Tex = new Texture2D(1, 1, TextureFormat.RGBA32, false); 40 } 41 42 void OnPostRender() 43 { 44 Vector2 pos = Input.mousePosition; 45 46 // 座標系がOpenGL方式かDirectX方式かによって上下が変わるのを考慮する 47 if (SystemInfo.graphicsUVStartsAtTop) 48 { 49 pos.y = Screen.height - 1 - pos.y; 50 } 51 52 // マウスポインタが画面外にあるとReadPixelsに失敗するため、座標を範囲内におさめる 53 float x = Mathf.Clamp(pos.x, 0.0f, Screen.width - 1); 54 float y = Mathf.Clamp(pos.y, 0.0f, Screen.height - 1); 55 56 Tex.ReadPixels(new Rect(x, y, 1, 1), 0, 0); 57 } 58 } 59}

追記
ご指摘ありがとうございます。当初のコードはWindows版でしか試していなかったのですが、確かにMac版でMetalを使用している場合は座標の上下が逆転してしまいますね...
その場しのぎ的ですみませんが、下記のようにMetalの場合に座標の上下反転を行わせないようにしてはどうでしょうか。
もし他にうまくいかない条件が見つかりましたらコメントください(とはいえMacとWindows以外にすぐ試せる環境を持っておらず、お力になれないかもしれませんが...)。

C#

1using UnityEngine; 2using UnityEngine.Rendering; 3 4namespace GetPixelColor 5{ 6 public class GetColor : MonoBehaviour 7 { 8 public Color32 color; 9 private GetColorHelper helper; 10 11 void Start() 12 { 13 helper = Camera.main.GetComponent<GetColorHelper>(); 14 if (helper == null) 15 { 16 helper = Camera.main.gameObject.AddComponent<GetColorHelper>(); 17 } 18 } 19 20 void Update() 21 { 22 if (Input.GetMouseButtonDown(0)) 23 { 24 color = helper.Tex.GetPixel(0, 0); 25 Debug.Log(color.ToString()); 26 } 27 } 28 29 [RequireComponent(typeof(Camera))] 30 private class GetColorHelper : MonoBehaviour 31 { 32 public Texture2D Tex { get; private set; } 33 34 void Awake() 35 { 36 Tex = new Texture2D(1, 1, TextureFormat.RGBA32, false); 37 } 38 39 void OnPostRender() 40 { 41 Vector2 pos = Input.mousePosition; 42 43 // 座標系がOpenGL方式かDirectX方式かによって上下が変わるのを考慮する 44 // Metalの場合、例外的に反転は行わないようにした 45 if (SystemInfo.graphicsUVStartsAtTop && SystemInfo.graphicsDeviceType != GraphicsDeviceType.Metal) 46 { 47 pos.y = Screen.height - 1 - pos.y; 48 } 49 50 float x = Mathf.Clamp(pos.x, 0.0f, Screen.width - 1); 51 float y = Mathf.Clamp(pos.y, 0.0f, Screen.height - 1); 52 53 Tex.ReadPixels(new Rect(x, y, 1, 1), 0, 0); 54 } 55 } 56 } 57}

また、Planeに貼り付けた色が取れないという現象は私の試した限りでは再現させられなかったのですが(Planeであっても他の3Dオブジェクトと違いはないはずなので、Cubeで成功したのならPlaneでもいける気がするのですが...)、ImageなどUIオブジェクトの色を取れないという現象は確認できました。
おそらくCanvasの「Render Mode」が「Screen Space - Overlay」になっているんじゃないでしょうか?どうやらこのモードの場合、UIの描画はOnPostRenderよりも後のタイミングになるため失敗したものと思われます。もしRender Modeを変えてもかまわないのなら、これを「Screen Space - Camera」や「World Space」 にすればOnPostRenderより先にUI描画が完了し、色を取ってくることができるでしょう。
あるいは、下記のようにWaitForEndOfFrameのタイミングならScreen Space - Overlayでもいけそうな感じでした。どうしてもScreen Space - Overlayでないと困るようでしたらこちらも試してみてもいいかと思います。

C#

1using System.Collections; 2using UnityEngine; 3 4namespace GetPixelColor 5{ 6 public class GetColor : MonoBehaviour 7 { 8 public Color32 color; 9 private GetColorHelper helper; 10 11 void Start() 12 { 13 helper = Camera.main.GetComponent<GetColorHelper>(); 14 if (helper == null) 15 { 16 helper = Camera.main.gameObject.AddComponent<GetColorHelper>(); 17 } 18 } 19 20 void Update() 21 { 22 if (Input.GetMouseButtonDown(0)) 23 { 24 color = helper.Tex.GetPixel(0, 0); 25 Debug.Log(color.ToString()); 26 } 27 } 28 29 [RequireComponent(typeof(Camera))] 30 private class GetColorHelper : MonoBehaviour 31 { 32 public Texture2D Tex { get; private set; } 33 34 void Awake() 35 { 36 Tex = new Texture2D(1, 1, TextureFormat.RGBA32, false); 37 } 38 39 IEnumerator Start() 40 { 41 var waitForEndOfFrame = new WaitForEndOfFrame(); 42 43 while (true) 44 { 45 yield return waitForEndOfFrame; 46 47 Vector2 pos = Input.mousePosition; 48 49 float x = Mathf.Clamp(pos.x, 0.0f, Screen.width - 1); 50 float y = Mathf.Clamp(pos.y, 0.0f, Screen.height - 1); 51 52 Tex.ReadPixels(new Rect(x, y, 1, 1), 0, 0); 53 } 54 } 55 } 56 } 57}

追記
まず、下記のようなメインテクスチャ・メインカラーで塗るだけのシンプルなUnlitシェーダーを用意しました。

ShaderLab

1Shader "Unlit/UnlitAlbedo" 2{ 3 Properties 4 { 5 _MainTex ("Texture", 2D) = "white" {} 6 _Color ("Color", Color) = (1, 1, 1, 1) 7 } 8 SubShader 9 { 10 Tags { "RenderType"="Opaque" } 11 LOD 100 12 13 Pass 14 { 15 CGPROGRAM 16 #pragma vertex vert 17 #pragma fragment frag 18 19 #include "UnityCG.cginc" 20 21 struct appdata 22 { 23 float4 vertex : POSITION; 24 float2 uv : TEXCOORD0; 25 }; 26 27 struct v2f 28 { 29 float2 uv : TEXCOORD0; 30 float4 vertex : SV_POSITION; 31 }; 32 33 sampler2D _MainTex; 34 float4 _MainTex_ST; 35 fixed4 _Color; 36 37 v2f vert(appdata v) 38 { 39 v2f o; 40 o.vertex = UnityObjectToClipPos(v.vertex); 41 o.uv = TRANSFORM_TEX(v.uv, _MainTex); 42 return o; 43 } 44 45 fixed4 frag(v2f i) : SV_Target 46 { 47 return tex2D(_MainTex, i.uv) * _Color; 48 } 49 ENDCG 50 } 51 } 52}

通常のレンダリングの場合下図のように見えるシーンを...

図1

この代替シェーダーでシーンをレンダリングした場合、下図のように環境の映り込みや陰影がない単純な色塗り状態の映像が得られます。実際にはこの映像を画面上に表示するわけではなく、RenderTexture上に描画して色取り元とすることになりますね。

図2

また、この追加レンダリングはわざわざ画面全体について行う必要はなく、色を取りたい地点の1ピクセル分だけの大きさがあれば十分なはずです。そこで、このレンダリングの時だけ一時的に投影行列に手を加えて注目点だけを拡大し、それを1×1サイズのRenderTextureに描画することにしました。描画面積が大幅に節約でき、レンダリングコストを低減できると思われます。

色取りスクリプトは下記のようにしてみました。補助スクリプトは不要になったため廃止しています。

C#

1using System.Collections; 2using UnityEngine; 3 4public class AlbedoColorPicker : MonoBehaviour 5{ 6 public Color32 color; 7 public Renderer targetRenderer; 8 9 private Camera mainCamera; 10 private Shader alternativeShader; 11 private Texture2D pixelTexture; 12 13 private void Start() 14 { 15 this.mainCamera = Camera.main; 16 this.alternativeShader = Shader.Find("Unlit/UnlitAlbedo"); 17 this.pixelTexture = new Texture2D(1, 1, TextureFormat.RGBA32, false); 18 } 19 20 private void Update() 21 { 22 if (Input.GetMouseButtonDown(0)) 23 { 24 // マウスポインタの位置を狙う変換行列を作り... 25 var screenScale = new Vector2(Screen.width, Screen.height); 26 var pixelPosition = ((Vector2)Input.mousePosition * 2.0f) - screenScale; 27 var pixelMatrix = Matrix4x4.TRS( 28 -pixelPosition, 29 Quaternion.identity, 30 new Vector3(screenScale.x, screenScale.y, 1.0f)); 31 32 // projectionMatrixの後段に追加の変換を加え、alternativeShaderを使ってレンダリングする 33 var targetTexture = this.mainCamera.targetTexture; 34 var projectionMatrix = this.mainCamera.projectionMatrix; 35 var renderTexture = RenderTexture.GetTemporary(1, 1); 36 this.mainCamera.targetTexture = renderTexture; 37 this.mainCamera.projectionMatrix = pixelMatrix * projectionMatrix; 38 this.mainCamera.RenderWithShader(this.alternativeShader, null); 39 this.mainCamera.targetTexture = targetTexture; 40 this.mainCamera.ResetProjectionMatrix(); 41 42 // renderTextureの内容をpixelTextureに読み取り、さらにpixelTextureから色を取り出す 43 var activeTexture = RenderTexture.active; 44 RenderTexture.active = renderTexture; 45 this.pixelTexture.ReadPixels(new Rect(0, 0, 1, 1), 0, 0); 46 RenderTexture.active = activeTexture; 47 RenderTexture.ReleaseTemporary(renderTexture); 48 this.color = this.pixelTexture.GetPixel(0, 0); 49 50 // 色設定対象がセットされていれば、それに取得された色をセットする 51 if (this.targetRenderer != null) 52 { 53 this.targetRenderer.material.color = this.color; 54 } 55 } 56 } 57}

動かしてみると下図のようになりました。取った色に陰影付けの影響が及んでいないことをアピールするため、色の適用先はPlaneではなく画面右側にあるSphereにしています。

図3

投稿2019/08/26 22:25

編集2019/10/17 13:38
Bongo

総合スコア10811

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

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

pigma689

2019/08/31 16:02

windowsでうまく動いていたのですが、macで動かすと正しいcolorの値が得られませんでした。UIのimageやPlaneに貼ったテクスチャの色をうまく読み取ることができていないようです。試しに3DオブジェクトのCubeを置いて色を変えてみたところ、こちらは正常に色を読み込めています。 画像の色が取れないようです。これはなぜなのでしょうか 改善策等教えていただけないでしょうか
pigma689

2019/08/31 16:06

なぜか座標がズレているような感じで画像を表示されていない部分をクリックすると画像の色要素が取れてしまったりしています
Bongo

2019/09/01 00:23

対症療法的ですが修正を加えてみました。これならどうでしょうか?
pigma689

2019/09/01 08:29

正しく色を取れるようになりました!ありがとうございます。 Planeに取得した色を反映させるようにしたのですが、そのPlaneをクリックするとcolorの値が変わってしまいます。 これはなぜなのでしょうか。
Bongo

2019/09/02 01:27 編集

なるほど...すぐには原因が思いつきませんが、のちほど(今はUnityが手元にないのでしばらく後になってしまいますがご容赦ください)再現させられるか試してみようと思います。念のため、その「Planeに取得した色を反映させる」という部分のコードもご提示いただけるでしょうか?もしかすると原因特定のヒントになるかもしれません。
pigma689

2019/09/02 05:19

Pleneに当たっているライトの影響で取得した色より白くなってしまい、さらにその部分をクリックすることでどんどん白に近づいてしまったようです。 ライトの影響を受けず、かつ色をスクリプトから動的に変更する手段はあるのでしょうか...
Bongo

2019/09/02 06:17

なるほど、そういう意味でしたか。ご希望としては「ゲーム画面上のPlaneの見た目は通常通り照明効果のかかった(つやつやの材質なら光沢が出る)状態にしたい、しかしクリック時に取得される色は純粋なアルベド色そのままであってほしい」といった感じでしょうか?だとすると描画過程に一工夫加える必要がありそうですね... 案としては「通常のレンダリングとは別に、一時的にシーン上のオブジェクトのマテリアルをUnlitシェーダーに差し替えてRenderTexture上に描画し、色はそこから取ってくる」とか「カメラをDeferredモードにして、色は拡散色バッファーから取ってくる」とかでしょうかね。 何かいい手が思いつきましたら追記いたします。
pigma689

2019/09/03 13:13

Unlitシェーダーを使うとスクリプトから色を変えられなくなってしまいますよね... 照明効果自体もう必要ないと考えていて、かといってライトを無効にするだけでは暗くなってしまうし、どうしたらいいのでしょうか
Bongo

2019/09/03 14:58

「一時的にUnlitシェーダーに差し替え」の方針で試してみたので追記しましたが、あんな感じではどうでしょうか? ただし、シェーダー差し替え描画にRenderWithShaderを使っている...つまり特定のカメラ上でのレンダリングに基づいているため、先のコメントで申し上げた「Screen Space - OverlayでレンダリングしているUIの色は取れない」という弱点は残ってしまいましたが...
Bongo

2019/09/05 22:46

「照明効果自体もう必要ないと考えていて」とのお返事を見落としておりました。確かにUnityが用意しているUnlitシェーダーには「単色塗り」と「テクスチャ塗り」はあっても、二つを併せ持つシェーダーはないようですね。 でしたら、追記の中で申し上げたUnlitAlbedoをそのままマテリアルのシェーダーとして使用してみてはいかがでしょうか?これは両者を複合させたような形になっていますので、テクスチャを貼った上でさらにcolorプロパティから色をセットできるはずです。最終的にレンダリングされる色は、提示しましたコードの通りテクスチャ色と単色を乗算合成した色になります。
pigma689

2019/09/09 06:26

イメージした通りにできました。 丁寧に教えてくださり、本当にありがとうございました!
Bongo

2019/10/17 13:39

すみません、別の方のご質問( https://teratail.com/questions/217389 )にてコードを再利用するために見直していたところ、今さらながらピクセル拡大行列の拡大量が足りておらず、2×2ピクセル相当までしか拡大できていなかったことに気付きました。 まさしく1ピクセル単位での色取りが求められないのであればさほど問題にはならないとは思うのですが、念のため修正しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問