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ページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答1件
0
ベストアンサー
ReadPixels
の引数のsource
は読み取り元(今回の場合は画面)のピクセル単位での読み取り範囲、destX
とdestY
は書き込み先(今回の場合は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}
通常のレンダリングの場合下図のように見えるシーンを...
この代替シェーダーでシーンをレンダリングした場合、下図のように環境の映り込みや陰影がない単純な色塗り状態の映像が得られます。実際にはこの映像を画面上に表示するわけではなく、RenderTexture上に描画して色取り元とすることになりますね。
また、この追加レンダリングはわざわざ画面全体について行う必要はなく、色を取りたい地点の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にしています。
投稿2019/08/26 22:25
編集2019/10/17 13:38総合スコア10811
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/08/31 16:02
2019/08/31 16:06
2019/09/01 00:23
2019/09/01 08:29
2019/09/02 01:27 編集
2019/09/02 05:19
2019/09/02 06:17
2019/09/03 13:13
2019/09/03 14:58
2019/09/05 22:46
2019/09/09 06:26
2019/10/17 13:39