Texture2Dをななめに切り出す方法を探しています。
4点を基準に画像を切り取る関数などはないでしょうか?
GetPixelsの引数も一つの座標と幅、高さしかないためどう斜めに切り取ればいいかわかりません。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

回答3件
0
ベストアンサー
Unityの備え付けの機能で目的に適いそうなものは見つけられませんでした...
ですが、テクスチャを4点で切り取るというのは面白そうでしたので、どうにかならないかと思い、下記のような方法を試してみましたが、ご参考になりますでしょうか?
ホモグラフィ変換で検索すると役に立ちそうなサイトがいろいろ出てきましたので、それらを参考にしました。
下のような画像をp、q、r、sの4点で切り抜きたいとすると、
下の式のような、元の四隅(0, 0)、(0, 1)、(1, 1)、(1, 0)を与えると切り抜きたい四隅p、q、r、sを得られるようなa、b、c、d、e、f、g、hが分かればいいのかと思います。
上の式のx、yに(0, 0)、(0, 1)、(1, 1)、(1, 0)を代入すると、まずc、fを
と確定でき、
の関係が得られます。これらを変形すると、
の形になってg、hを確定でき、芋づる式にa、b、c、dが決定します。
Unityのデフォルトのシェーダーを一部改造して、生のUV座標の代わりに上記行列で変換した座標を使うようにしました。スプライト用シェーダーをベースにしましたが、他のものでも構わないかと思います。
Shader "Sprites/Homography" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint", Color) = (1,1,1,1) [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 [HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1) [HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1) [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 [HideInInspector] _BottomLeftTopLeft ("BottomLeftTopLeft", Vector) = (0,0,0,1) // 追加...左下と左上の隅 [HideInInspector] _TopRightBottomRight ("TopRightBottomRight", Vector) = (1,1,1,0) // 追加...右上と右下の隅 [HideInInspector] _HomographyMatrixR0 ("HomographyMatrixR0", Vector) = (1,0,0,0) // 追加...UV変換行列0行目 [HideInInspector] _HomographyMatrixR1 ("HomographyMatrixR1", Vector) = (0,1,0,0) // 追加...UV変換行列1行目 [HideInInspector] _HomographyMatrixR2 ("HomographyMatrixR2", Vector) = (0,0,1,0) // 追加...UV変換行列2行目(実際にはこの行はいつも一定なので、削除してもいいかと思います) [HideInInspector] _HomographyMatrixR3 ("HomographyMatrixR3", Vector) = (0,0,0,1) // 追加...UV変換行列3行目 } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } Cull Off Lighting Off ZWrite Off Blend One OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex SpriteVert #pragma fragment SpriteFrag #pragma target 2.0 #pragma multi_compile_instancing #pragma multi_compile _ PIXELSNAP_ON #pragma multi_compile _ ETC1_EXTERNAL_ALPHA // 以下、#include "UnitySprites.cginc"の代わりにUnitySprites.cgincの内容をペーストし、一部修正を加える #include "UnityCG.cginc" #ifdef UNITY_INSTANCING_ENABLED UNITY_INSTANCING_CBUFFER_START(PerDrawSprite) // SpriteRenderer.Color while Non-Batched/Instanced. fixed4 unity_SpriteRendererColorArray[UNITY_INSTANCED_ARRAY_SIZE]; // this could be smaller but that's how bit each entry is regardless of type float4 unity_SpriteFlipArray[UNITY_INSTANCED_ARRAY_SIZE]; UNITY_INSTANCING_CBUFFER_END #define _RendererColor unity_SpriteRendererColorArray[unity_InstanceID] #define _Flip unity_SpriteFlipArray[unity_InstanceID] #endif // instancing CBUFFER_START(UnityPerDrawSprite) #ifndef UNITY_INSTANCING_ENABLED fixed4 _RendererColor; float4 _Flip; #endif float _EnableExternalAlpha; CBUFFER_END // Material Color. fixed4 _Color; float4 _HomographyMatrixR0; // 追加...UV座標変換用の行列0行目 float4 _HomographyMatrixR1; // 追加...UV座標変換用の行列1行目 float4 _HomographyMatrixR2; // 追加...UV座標変換用の行列2行目(実際にはこの行はいつも一定なので、削除してもいいかと思います) float4 _HomographyMatrixR3; // 追加...UV座標変換用の行列3行目 struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float3 texcoord : TEXCOORD0; // 変更...float2をfloat3に UNITY_VERTEX_OUTPUT_STEREO }; v2f SpriteVert(appdata_t IN) { v2f OUT; UNITY_SETUP_INSTANCE_ID (IN); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); #ifdef UNITY_INSTANCING_ENABLED IN.vertex.xy *= _Flip.xy; #endif OUT.vertex = UnityObjectToClipPos(IN.vertex); OUT.texcoord = mul(float4x4(_HomographyMatrixR0, _HomographyMatrixR1, _HomographyMatrixR2, _HomographyMatrixR3), float4(IN.texcoord, 0.0, 1.0)).xyw; // 変更...IN.texcoordを変換してからx、y、wを渡す OUT.color = IN.color * _Color * _RendererColor; #ifdef PIXELSNAP_ON OUT.vertex = UnityPixelSnap (OUT.vertex); #endif return OUT; } sampler2D _MainTex; sampler2D _AlphaTex; fixed4 SampleSpriteTexture (float2 uv) { fixed4 color = tex2D (_MainTex, uv); #if ETC1_EXTERNAL_ALPHA fixed4 alpha = tex2D (_AlphaTex, uv); color.a = lerp (color.a, alpha.r, _EnableExternalAlpha); #endif return color; } fixed4 SpriteFrag(v2f IN) : SV_Target { fixed4 c = SampleSpriteTexture (IN.texcoord.xy / IN.texcoord.z) * IN.color; // 変更...受け取ったIN.texcoordのx、yをzで割ってサンプリング座標とする c.rgb *= c.a; return c; } ENDCG } } CustomEditor "HomographyShaderGUI" // 追加...パラメータ設定用のカスタムUI }
また、シェーダーのパラメータ設定用スクリプトを作り、その中で先の式により変換行列を作ってシェーダーに渡しました。
using UnityEditor; using UnityEngine; public class HomographyShaderGUI : ShaderGUI { public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties) { var material = materialEditor.target as Material; var bottomLeftTopLeft = FindProperty("_BottomLeftTopLeft", properties).vectorValue; var topRightBottomRight = FindProperty("_TopRightBottomRight", properties).vectorValue; EditorGUI.BeginChangeCheck(); var bottomLeft = EditorGUILayout.Vector2Field( "Bottom Left", new Vector2(bottomLeftTopLeft.x, bottomLeftTopLeft.y)); var topLeft = EditorGUILayout.Vector2Field( "Top Left", new Vector2(bottomLeftTopLeft.z, bottomLeftTopLeft.w)); var topRight = EditorGUILayout.Vector2Field( "Top Right", new Vector2(topRightBottomRight.x, topRightBottomRight.y)); var bottomRight = EditorGUILayout.Vector2Field( "Bottom Right", new Vector2(topRightBottomRight.z, topRightBottomRight.w)); if (EditorGUI.EndChangeCheck()) { material.SetVector( "_BottomLeftTopLeft", new Vector4(bottomLeft.x, bottomLeft.y, topLeft.x, topLeft.y)); material.SetVector( "_TopRightBottomRight", new Vector4(topRight.x, topRight.y, bottomRight.x, bottomRight.y)); var matrix = HomographyMatrix(bottomLeft, topLeft, topRight, bottomRight); material.SetVector("_HomographyMatrixR0", matrix.GetRow(0)); material.SetVector("_HomographyMatrixR1", matrix.GetRow(1)); material.SetVector("_HomographyMatrixR2", matrix.GetRow(2)); material.SetVector("_HomographyMatrixR3", matrix.GetRow(3)); } base.OnGUI(materialEditor, properties); } /// <summary> /// Vector2の外積を求める /// </summary> /// <param name="a">左項</param> /// <param name="b">右項</param> /// <returns>外積</returns> private static float Cross(Vector2 a, Vector2 b) { return (a.x * b.y) - (b.x * a.y); } /// <summary> /// 4隅の座標を変換する行列を求める /// </summary> /// <param name="bl">(0, 0)の変換先</param> /// <param name="tl">(0, 1)の変換先</param> /// <param name="tr">(1, 1)の変換先</param> /// <param name="br">(1, 0)の変換先</param> /// <returns>変換行列</returns> private static Matrix4x4 HomographyMatrix(Vector2 bl, Vector2 tl, Vector2 tr, Vector2 br) { var cf = bl; // cとfはblの座標で確定 Vector2 ghMatR0; Vector2 ghMatR1; Inverse(br - tr, tl - tr, out ghMatR0, out ghMatR1); var v = (bl + tr) - (tl + br); var gh = new Vector2(Vector2.Dot(ghMatR0, v), Vector2.Dot(ghMatR1, v)); // まずgとhを求める var ad = ((gh.x + 1.0f) * br) - bl; // gを使ってaとdを求める var be = ((gh.y + 1.0f) * tl) - bl; // hを使ってbとeを求める var result = Matrix4x4.identity; // Unityで扱うには4次元ベースの方が便利そうなので、Matrix4x4に整形しました result.SetColumn(0, new Vector4(ad.x, ad.y, 0.0f, gh.x)); result.SetColumn(1, new Vector4(be.x, be.y, 0.0f, gh.y)); result.SetColumn(2, new Vector4(0.0f, 0.0f, 1.0f, 0.0f)); result.SetColumn(3, new Vector4(cf.x, cf.y, 0.0f, 1.0f)); return result; } /// <summary> /// 2x2行列の逆行列を求める /// </summary> /// <param name="column0">元の行列の0列目</param> /// <param name="column1">元の行列の1列目</param> /// <param name="inverseRow0">逆行列の0行目</param> /// <param name="inverseRow1">逆行列の1行目</param> private static void Inverse(Vector2 column0, Vector2 column1, out Vector2 inverseRow0, out Vector2 inverseRow1) { var determinant = Cross(column0, column1); inverseRow0 = new Vector2(column1.y, -column1.x) / determinant; inverseRow1 = new Vector2(-column0.y, column0.x) / determinant; } }
動かしてみるとこんな感じです。
投稿2017/08/19 00:44
編集2017/08/23 02:16総合スコア10816
0
xstarteraさんのコメントに対して追記しようとしましたが、またもやうまくいかなかったため別回答にて追記します(一応字数制限はクリアしたつもりでしたが...何がいけなかったんでしょうか?)。
4つのノブを動かしてUV座標を決めるパターンを作ってみました。ノブのドラッグ時に新しいUV座標を求め、マテリアルのホモグラフィ行列を更新する方式としています。
- ResultLabel、OriginalLabel...板の上方に浮かんでいる文字
- ResultQuad...後述のResultControllerをアタッチした板、マテリアルは後述のUnlit/Homographyを使用
- OriginalQuad...元のテクスチャを表示する板、マテリアルは改変なしのUnlit/Textureを使用
- Line...ノブを繋ぐ線を描画するためのラインレンダラーをアタッチ
- TopLeftKnob〜BottomLeftKnob...4色の球体、それぞれ後述のKnobControllerがアタッチされており、あらかじめインスペクタ上でCornerToControlを対応する頂点の値にセットしておく(併せてLine、OriginalQuad、ResultQuadもそれぞれヒエラルキー上の各オブジェクトをセット)
TopLeftKnob〜BottomLeftKnob用スクリプト
C#
1using System.Linq; 2using UnityEngine; 3 4public class KnobController : MonoBehaviour 5{ 6 public enum CornerToControl 7 { 8 TopLeft, 9 TopRight, 10 BottomRight, 11 BottomLeft 12 } 13 14 private static readonly Vector2[] UvForCorner = 15 { 16 new Vector2(0.0f, 1.0f), 17 new Vector2(1.0f, 1.0f), 18 new Vector2(1.0f, 0.0f), 19 new Vector2(0.0f, 0.0f) 20 }; 21 22 public CornerToControl Corner; 23 public LineRenderer Line; 24 public GameObject OriginalQuad; 25 public GameObject ResultQuad; 26 private Vector3 originalQuadOrigin; 27 private Vector3 originalQuadUVector; 28 private Vector3 originalQuadVVector; 29 private ResultController resultController; 30 31 /// <summary> 32 /// 直線と交差する平面上のUV座標を得る 33 /// </summary> 34 /// <param name="rayOrigin">直線の起点のワールド座標</param> 35 /// <param name="rayDirection">直線のワールド方向</param> 36 /// <param name="planeU">平面のワールドUベクトル</param> 37 /// <param name="planeV">平面のワールドVベクトル</param> 38 /// <param name="planeOrigin">平面原点のワールド座標</param> 39 /// <returns>UV座標(交点のワールド座標はplaneU * u + planeV * v + planeOrigin)</returns> 40 private static Vector2 GetUv( 41 Vector3 rayOrigin, 42 Vector3 rayDirection, 43 Vector3 planeU, 44 Vector3 planeV, 45 Vector3 planeOrigin) 46 { 47 var matrix = Matrix4x4.identity; 48 matrix.SetColumn(0, planeU); 49 matrix.SetColumn(1, planeV); 50 matrix.SetColumn(2, -rayDirection); 51 return matrix.inverse * (rayOrigin - planeOrigin); 52 } 53 54 private void OnMouseDrag() 55 { 56 this.UpdateKnobPosition(Input.mousePosition); 57 } 58 59 private void Start() 60 { 61 this.resultController = this.ResultQuad.GetComponent<ResultController>(); 62 63 // OriginalQuadのメッシュの4頂点から原点・Uベクトル・Vベクトルを調べ、ノブの初期位置を合わせる 64 var originalMesh = this.OriginalQuad.GetComponent<MeshFilter>().sharedMesh; 65 var vertices = originalMesh.vertices.Select(v => this.OriginalQuad.transform.TransformPoint(v)).ToArray(); 66 var uvs = originalMesh.uv; 67 var vertexCount = vertices.Length; 68 var bottomLeft = Vector3.zero; 69 var topLeft = Vector3.up; 70 var bottomRight = Vector3.right; 71 for (var i = 0; i < vertexCount; i++) 72 { 73 var uv = uvs[i]; 74 if (uv == UvForCorner[(int)CornerToControl.BottomLeft]) 75 { 76 bottomLeft = vertices[i]; 77 } 78 else if (uv == UvForCorner[(int)CornerToControl.TopLeft]) 79 { 80 topLeft = vertices[i]; 81 } 82 else if (uv == UvForCorner[(int)CornerToControl.BottomRight]) 83 { 84 bottomRight = vertices[i]; 85 } 86 } 87 88 this.originalQuadOrigin = bottomLeft; 89 this.originalQuadUVector = bottomRight - bottomLeft; 90 this.originalQuadVVector = topLeft - bottomLeft; 91 var knobUv = UvForCorner[(int)this.Corner]; 92 for (var i = 0; i < vertexCount; i++) 93 { 94 if (uvs[i] == knobUv) 95 { 96 this.UpdateKnobPosition(Camera.main.WorldToScreenPoint(vertices[i])); 97 break; 98 } 99 } 100 101 this.UpdateLine(); 102 } 103 104 private void UpdateKnobPosition(Vector3 screenPosition) 105 { 106 // ノブの位置を更新 107 // UV座標はレイキャストで求めてもいいのですが、レイがOriginalQuadを外れても対応できるよう、自前で算出することにしました 108 var ray = Camera.main.ScreenPointToRay(screenPosition); 109 var newUv = GetUv( 110 ray.origin, 111 ray.direction, 112 this.originalQuadUVector, 113 this.originalQuadVVector, 114 this.originalQuadOrigin); 115 // ノブの位置はレイ交点とレイ原点の中間としました 116 this.transform.position = Vector3.Lerp( 117 ray.origin, 118 (this.originalQuadUVector * newUv.x) + (this.originalQuadVVector * newUv.y) + this.originalQuadOrigin, 119 0.5f); 120 // ノブと対応するResultControllerの頂点に求めたUV座標をセット 121 switch (this.Corner) 122 { 123 default: 124 case CornerToControl.TopLeft: 125 this.resultController.TopLeft = newUv; 126 break; 127 case CornerToControl.TopRight: 128 this.resultController.TopRight = newUv; 129 break; 130 case CornerToControl.BottomRight: 131 this.resultController.BottomRight = newUv; 132 break; 133 case CornerToControl.BottomLeft: 134 this.resultController.BottomLeft = newUv; 135 break; 136 } 137 this.UpdateLine(); // ラインレンダラーの頂点位置を更新 138 } 139 140 private void UpdateLine() 141 { 142 this.Line.SetPosition((int)this.Corner, this.transform.position); 143 } 144}
ResultQuad用スクリプト
ここに以前の回答で例示したホモグラフィ変換行列算出メソッドを引っ越しました。4頂点のUV座標はプロパティとし、set中でマテリアル更新メソッドを呼ぶことにしました。
C#
1using UnityEngine; 2 3[RequireComponent(typeof(Renderer))] 4public class ResultController : MonoBehaviour 5{ 6 private Vector2 bottomLeft; 7 private Vector2 bottomRight; 8 private Material resultMaterial; 9 private Vector2 topLeft; 10 private Vector2 topRight; 11 12 public Vector2 BottomLeft 13 { 14 get { return this.bottomLeft; } 15 set 16 { 17 this.bottomLeft = value; 18 this.UpdateTexture(); 19 } 20 } 21 22 public Vector2 BottomRight 23 { 24 get { return this.bottomRight; } 25 set 26 { 27 this.bottomRight = value; 28 this.UpdateTexture(); 29 } 30 } 31 32 public Vector2 TopLeft 33 { 34 get { return this.topLeft; } 35 set 36 { 37 this.topLeft = value; 38 this.UpdateTexture(); 39 } 40 } 41 42 public Vector2 TopRight 43 { 44 get { return this.topRight; } 45 set 46 { 47 this.topRight = value; 48 this.UpdateTexture(); 49 } 50 } 51 52 private static float Cross(Vector2 a, Vector2 b) 53 { 54 return (a.x * b.y) - (b.x * a.y); 55 } 56 57 private static Matrix4x4 HomographyMatrix(Vector2 bl, Vector2 tl, Vector2 tr, Vector2 br) 58 { 59 var cf = bl; 60 Vector2 ghMatR0; 61 Vector2 ghMatR1; 62 Inverse(br - tr, tl - tr, out ghMatR0, out ghMatR1); 63 var v = (bl + tr) - (tl + br); 64 var gh = new Vector2(Vector2.Dot(ghMatR0, v), Vector2.Dot(ghMatR1, v)); 65 var ad = ((gh.x + 1.0f) * br) - bl; 66 var be = ((gh.y + 1.0f) * tl) - bl; 67 var result = Matrix4x4.identity; 68 result.SetColumn(0, new Vector4(ad.x, ad.y, 0.0f, gh.x)); 69 result.SetColumn(1, new Vector4(be.x, be.y, 0.0f, gh.y)); 70 result.SetColumn(2, new Vector4(0.0f, 0.0f, 1.0f, 0.0f)); 71 result.SetColumn(3, new Vector4(cf.x, cf.y, 0.0f, 1.0f)); 72 return result; 73 } 74 75 private static void Inverse(Vector2 column0, Vector2 column1, out Vector2 inverseRow0, out Vector2 inverseRow1) 76 { 77 var determinant = Cross(column0, column1); 78 inverseRow0 = new Vector2(column1.y, -column1.x) / determinant; 79 inverseRow1 = new Vector2(-column0.y, column0.x) / determinant; 80 } 81 82 private void Start() 83 { 84 this.topLeft = new Vector2(0.0f, 1.0f); 85 this.topRight = new Vector2(1.0f, 1.0f); 86 this.bottomRight = new Vector2(1.0f, 0.0f); 87 this.bottomLeft = new Vector2(0.0f, 0.0f); 88 } 89 90 private void UpdateTexture() 91 { 92 // 現在の4頂点から変換行列を求め、マテリアルにセット 93 if (this.resultMaterial == null) 94 { 95 this.resultMaterial = this.GetComponent<Renderer>().material; 96 } 97 this.resultMaterial.SetMatrix( 98 "_HomographyMatrix", 99 HomographyMatrix(this.bottomLeft, this.topLeft, this.topRight, this.bottomRight)); 100 } 101}
ResultQuadのマテリアル用シェーダー
HLSL
1Shader "Unlit/Homography" 2{ 3 Properties 4 { 5 _MainTex ("Texture", 2D) = "white" {} 6 } 7 SubShader 8 { 9 Tags { "RenderType"="Opaque" } 10 LOD 100 11 12 Pass 13 { 14 CGPROGRAM 15 #pragma vertex vert 16 #pragma fragment frag 17 // make fog work 18 #pragma multi_compile_fog 19 20 #include "UnityCG.cginc" 21 22 struct appdata 23 { 24 float4 vertex : POSITION; 25 float2 uv : TEXCOORD0; 26 }; 27 28 struct v2f 29 { 30 float3 uv : TEXCOORD0; // float2をfloat3に 31 UNITY_FOG_COORDS(1) 32 float4 vertex : SV_POSITION; 33 }; 34 35 sampler2D _MainTex; 36 float4 _MainTex_ST; 37 float4x4 _HomographyMatrix; // 変換行列は行ごとに分割せず丸ごと送る 38 39 v2f vert (appdata v) 40 { 41 v2f o; 42 o.vertex = UnityObjectToClipPos(v.vertex); 43 o.uv = mul(_HomographyMatrix, float4(TRANSFORM_TEX(v.uv, _MainTex), 0.0, 1.0)).xyw; // uvを変換してからx、y、wを渡す 44 UNITY_TRANSFER_FOG(o,o.vertex); 45 return o; 46 } 47 48 fixed4 frag (v2f i) : SV_Target 49 { 50 // sample the texture 51 fixed4 col = tex2D(_MainTex, i.uv.xy / i.uv.z); // i.uvのx、yをzで割ってサンプリング座標とする 52 // apply fog 53 UNITY_APPLY_FOG(i.fogCoord, col); 54 return col; 55 } 56 ENDCG 57 } 58 } 59}
投稿2017/12/30 00:40
編集2017/12/30 01:22総合スコア10816
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

0
先の回答へ追記を行おうとしましたが、うまくいかなかったため別回答にて追記します。
「ななめに切り出す」の意図を読み違えていたかもしれません。スプライトエディターでアウトラインを編集する方が適切でしょうか?こちらでしたらシェーダーを用意する必要がなく、より簡単に実現できるかと思います。こちらのサイトの記事もご参考になりそうです。
スクリプトからアウトラインを変更する場合はOverrideGeometryが使えそうです。
投稿2017/08/19 22:59
総合スコア10816
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/12/28 16:28
2017/12/29 16:25

あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/08/20 04:42