前提・実現したいこと
Unityで、影を用いたインタラクティブなゲームを制作しております。
そこで、下図のようにウェブカメラで人を撮影し、その撮影された画像の黒い部分だけに当たり判定を与えて、キャラクターが影の上に登ったりなどができることを目標としています。
コライダーを既存のコンポーネントで適用するのではなく、スクリプトで適用すればいいのかと
初心者ながら想像しているのですが、実際どのように組み始めていけば良いか見当がつかず
質問させていただきました。
説明が足らない場合はお手数ですが質問していただけますと助かります。
宜しくお願い致します。
補足情報(FW/ツールのバージョンなど)
Unity2019.2.5f1 Personal
macOS Sierra
おわりに
回答をしてくださったお二人ともベストアンサーにしたかったのが正直です。
どちらも実装して試してみましたが、Bongo様の回答は私の技術力を大きく上回る内容でしたが、
むしろそのことによって触れてこなかったシェーダーの世界でもアプローチが可能であることなど
新しい発見が伴うものでした。
ありがとうございました。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答2件
0
ベストアンサー
映像が更新されるたびにPolygonCollider2Dを付け直すというのも候補になるかもしれません。ですがやはり負荷は大きいでしょうね...
スプライトに付けるスクリプトの例
C#
1using System.Collections; 2using UnityEngine; 3 4[RequireComponent(typeof(SpriteRenderer), typeof(PolygonCollider2D))] 5public class Silhouette : MonoBehaviour 6{ 7 private static readonly int Threshold = Shader.PropertyToID("_Threshold"); 8 9 [SerializeField] private int width = 320; 10 [SerializeField] private int height = 240; 11 [SerializeField] private int floorHeight = 16; 12 [SerializeField] private int resolutionMultiplier = 1; 13 [SerializeField] private Color color = Color.black; 14 [SerializeField][Range(0.0f, 1.0f)] private float threshold = 0.5f; 15 [SerializeField] private Shader silhouetteShader; 16 17 private new SpriteRenderer renderer; 18 private new PolygonCollider2D collider; 19 private Sprite sprite; 20 private Texture2D spriteTexture; 21 private Material silhouetteMaterial; 22 private WebCamTexture webCamTexture; 23 24 private IEnumerator Start() 25 { 26 // WebCamTextureをセットアップ 27 if (WebCamTexture.devices.Length <= 0) 28 { 29 Destroy(this); 30 yield break; 31 } 32 this.width = Mathf.Max(this.width, 100); 33 this.height = Mathf.Max(this.height, 100); 34 this.webCamTexture = new WebCamTexture(WebCamTexture.devices[0].name, this.width, this.height); 35 this.webCamTexture.Play(); 36 while (this.webCamTexture.width < 100) 37 { 38 yield return null; 39 } 40 41 // この後の処理で使うテクスチャ幅・高さを求める 42 // resolutionMultiplierを大きくするとコライダーが精密になるが、負荷は大きくなる 43 this.width = this.webCamTexture.width * this.resolutionMultiplier; 44 this.height = this.webCamTexture.height * this.resolutionMultiplier; 45 var pixelsPerUnit = (float)this.height; 46 var mainCamera = Camera.main; 47 if (mainCamera.orthographic) 48 { 49 pixelsPerUnit = (0.5f * Screen.height * this.resolutionMultiplier) / mainCamera.orthographicSize; 50 } 51 52 // スプライトをセットアップ 53 this.renderer = this.GetComponent<SpriteRenderer>(); 54 this.collider = this.GetComponent<PolygonCollider2D>(); 55 this.spriteTexture = new Texture2D(this.width, this.height, TextureFormat.ARGB32, false); 56 this.sprite = Sprite.Create( 57 this.spriteTexture, 58 new Rect(0, 0, this.width, this.height), 59 Vector2.zero, 60 pixelsPerUnit); 61 this.renderer.sprite = this.sprite; 62 63 this.silhouetteMaterial = new Material(this.silhouetteShader); 64 while (true) 65 { 66 // WebCamTextureの内容を2値化 67 this.silhouetteMaterial.color = this.color; 68 this.silhouetteMaterial.SetFloat(Threshold, this.threshold); 69 var renderTexture = RenderTexture.GetTemporary(this.width, this.height, 0, RenderTextureFormat.ARGB32); 70 Graphics.Blit(this.webCamTexture, renderTexture, this.silhouetteMaterial, 0); 71 var activeTexture = RenderTexture.active; 72 RenderTexture.active = renderTexture; 73 74 // 床を描く 75 // WebCamTextureのBlitで何も映っていない場合、後のコライダー生成時に 76 // デフォルトの形状で生成されてしまうため、それを防止するのが主目的 77 var floorY = ((float)(this.floorHeight * this.resolutionMultiplier) / this.height); 78 GL.PushMatrix(); 79 this.silhouetteMaterial.SetPass(1); 80 GL.Begin(GL.QUADS); 81 GL.LoadOrtho(); 82 GL.Color(this.color); 83 GL.Vertex(Vector3.zero); 84 GL.Vertex3(0.0f, floorY, 0.0f); 85 GL.Vertex3(1.0f, floorY, 0.0f); 86 GL.Vertex(Vector3.right); 87 GL.End(); 88 GL.PopMatrix(); 89 90 // 結果をスプライト用テクスチャに読み取る 91 this.spriteTexture.ReadPixels(new Rect(0, 0, this.width, this.height), 0, 0); 92 RenderTexture.active = activeTexture; 93 RenderTexture.ReleaseTemporary(renderTexture); 94 this.spriteTexture.Apply(); 95 96 // PolygonCollider2Dを再作成し、不透明部分に沿った形状を作らせる 97 Destroy(this.collider); 98 this.collider = this.gameObject.AddComponent<PolygonCollider2D>(); 99 yield return null; 100 } 101 } 102}
映像処理用シェーダーの例
ShaderLab
1Shader "Hidden/Silhouette" 2{ 3 Properties 4 { 5 _MainTex ("Texture", 2D) = "white" {} 6 _Color ("Color", Color) = (0, 0, 0, 1) 7 _Threshold ("Threshold", Range(0.0, 1.0)) = 0.5 8 } 9 SubShader 10 { 11 Cull Off ZWrite Off ZTest Always 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 v2f vert (appdata v) 34 { 35 v2f o; 36 o.vertex = UnityObjectToClipPos(v.vertex); 37 o.uv = v.uv; 38 return o; 39 } 40 41 sampler2D _MainTex; 42 float4 _Color; 43 float _Threshold; 44 45 fixed4 frag (v2f i) : SV_Target 46 { 47 float y = dot(tex2D(_MainTex, i.uv).rgb, float3(0.298912, 0.586611, 0.114478)); 48 return fixed4(_Color.rgb, max(sign(y - _Threshold), 0.0)); 49 } 50 ENDCG 51 } 52 53 Pass 54 { 55 CGPROGRAM 56 #pragma vertex vert 57 #pragma fragment frag 58 59 #include "UnityCG.cginc" 60 61 struct appdata 62 { 63 float4 vertex : POSITION; 64 }; 65 66 struct v2f 67 { 68 float4 vertex : SV_POSITION; 69 }; 70 71 v2f vert (appdata v) 72 { 73 v2f o; 74 o.vertex = UnityObjectToClipPos(v.vertex); 75 return o; 76 } 77 78 float4 _Color; 79 80 fixed4 frag (v2f i) : SV_Target 81 { 82 return fixed4(_Color.rgb, 1.0); 83 } 84 ENDCG 85 } 86 } 87}
部屋の明かりを消し、ディスプレイの光で手を照らして撮影してみた様子
大きな解像度でもなめらかに動かすには、Unity標準の物理シミュレーションに頼らない独自の衝突判定メカニズムを考えるべきなのかもしれません。
高速化する余地としてはBurstを使ってみるとか、まだプレビュー段階ですがEntity Component SystemやUnity Physicsを使ってみるとかですかね(私はまだ使用経験がありませんが...)?
こういったDOTSのパラダイムは従来のUnityのスタイルとは異なる部分が多くとっつきにくいかもしれませんが、効果はかなり大きいようですから、どうしても高速化したいとお考えでしたら検討してみる価値はあるかと思います。
投稿2019/10/22 22:22
総合スコア10811
0
本当ならばPolygonColliderの頂点を自由に移動させるのがベストだとは思いますが、質問のような形を自由に変えたり、飛び地ができる(Colliderが2つに分かれる)ような環境では
・ポリゴンの頂点がどの位置から移動してきたのかを知る必要がある。
・ポリゴンの頂点同士の間隔が離れすぎたときに補完。近すぎる時に削除しなければならない
・飛び地ができていないか確認する必要がある。
このように、製作が難しいと思うので、正方形のColliderの2次元配列を作り、黒い場所だけActiveにするのが単純明快かと思います。
ただ、colliderをpixel毎に作成していると数十万は下らない数のコライダーが必要になると思うので
・コライダーを大きくし、チェック頻度を下げる
・黒と白の境界面にのみコライダーを作成する
など工夫は必要になりますが。
一応あらすじだけ載せておきます。
C#
1Texture2D capture;//カメラのキャプチャ画像 2 3BoxCollider2D[,] colliderMap = new BoxCollider2D[capture.Widge, capture.Height]; 4 5const byte THRESHOLD = 0x80;//閾値。RGBをHSLに変換する方法を知らなかったので(汗) 6 7void Start() 8{ 9 for(int i = 0; i < colliderMap.widge; i++) for(int j = 0; j < colliderMap.height; j++) 10 { 11 colliderMap[i, j] = AddComponent<BoxCollider2D>(); 12 colliderMap[i, j].size = new Vector2(0.1f, 0.1f);//任意 13 } 14} 15 16void Update() 17{ 18 for(int i = 0; i < capture.widge; i++) for(int j = 0; j < capture.height; j++) 19 { 20 Color pixel = capture.GetPixel(i, j); 21 colloderMap[i, j].transform.SetActive( 22 pixel.r < THRESHOLD || 23 pixel.g < THRESHOLD || 24 pixel.b < THRESHOLD);//もしRGBともに閾値より明るければ辺り判定を消す 25 } 26}
注意:コピペで実行しないでください。フリーズまたは動作が遅くなる可能性があります
投稿2019/10/22 06:22
総合スコア368
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/10/24 11:42
2019/10/24 12:13
2019/10/26 14:40
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/10/26 14:42
2019/10/28 12:11
2019/10/28 12:48
2019/11/11 13:47
2019/11/11 14:31