回答編集履歴

1

別の案を追記

2018/02/17 21:55

投稿

Bongo
Bongo

スコア10807

test CHANGED
@@ -3,3 +3,235 @@
3
3
  まだうまくいくか試してはいないのですが、淡青色部分と濃青色部分にスプライトを分けて、2つを重ね描きするような方針でどうでしょうか?
4
4
 
5
5
  拡縮の際は、内側の淡青色部分の大きさを「濃青色部分の大きさ - (境界幅 × 2)」にして、濃青色部分よりも境界の幅の分だけ小さくする感じで...
6
+
7
+
8
+
9
+ ### [追記]
10
+
11
+ ご提示の画像のようなシンプルな外観のピンなら、先に申し上げた内部・外部の2枚スプライト方式で問題ないかと思いますが、外部に装飾を加えたりして派手にすると、拡大すると装飾が隠れてしまったり、縮小すると本来は見えない部分が見えてしまったりするかと思います。
12
+
13
+
14
+
15
+ 別パターンとして、スプライトは当初の通り内部・外部を統合した1枚とし、Sprite Rendererの本来のSprites-Defaultsマテリアルの代わりにカスタムマテリアルを使って、拡縮率に応じてテクスチャのサンプリング位置をずらして外周部の太さを維持するという手も使えそうです。
16
+
17
+
18
+
19
+ 勉強を兼ねて、下記のようなSprites-Defaultsを改造したマテリアルを作成してみました。
20
+
21
+
22
+
23
+ ```
24
+
25
+ Shader "Sprites/Pin" // 名前を変更
26
+
27
+ {
28
+
29
+ Properties
30
+
31
+ {
32
+
33
+ [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
34
+
35
+ _Color ("Tint", Color) = (1,1,1,1)
36
+
37
+ [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
38
+
39
+ [HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1)
40
+
41
+ [HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
42
+
43
+ [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {}
44
+
45
+ [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0
46
+
47
+
48
+
49
+ // 下記追加プロパティの各値はUV座標系で指定する
50
+
51
+ _HeadX ("Head X", Float) = 0.5 // ピン頭X
52
+
53
+ _HeadY ("Head Y", Float) = 0.68 // ピン頭Y
54
+
55
+ _InnerRadius ("Inner Radius", Float) = 0.23 // 淡色部半径
56
+
57
+ _OuterRadius ("Outer Radius", Float) = 0.32 // 濃色部半径
58
+
59
+ _TipDist ("Tip Distance", Float) = 0.68 // ピン頭中心からピン先端までの距離
60
+
61
+ }
62
+
63
+
64
+
65
+ SubShader
66
+
67
+ {
68
+
69
+ Tags
70
+
71
+ {
72
+
73
+ "Queue"="Transparent"
74
+
75
+ "IgnoreProjector"="True"
76
+
77
+ "RenderType"="Transparent"
78
+
79
+ "PreviewType"="Plane"
80
+
81
+ "CanUseSpriteAtlas"="True"
82
+
83
+ }
84
+
85
+
86
+
87
+ Cull Off
88
+
89
+ Lighting Off
90
+
91
+ ZWrite Off
92
+
93
+ Blend One OneMinusSrcAlpha
94
+
95
+
96
+
97
+ Pass
98
+
99
+ {
100
+
101
+ CGPROGRAM
102
+
103
+ #pragma vertex SpriteVert
104
+
105
+ #pragma fragment PinFrag // フラグメントシェーダーを差し替え
106
+
107
+ #pragma target 2.0
108
+
109
+ #pragma multi_compile_instancing
110
+
111
+ #pragma multi_compile _ PIXELSNAP_ON
112
+
113
+ #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
114
+
115
+ #include "UnitySprites.cginc"
116
+
117
+
118
+
119
+ #define EPSILON 0.00001 // 分母に0が来る可能性のある場合にエラーを回避するための微小値(プロパティ等の入力値由来の除算部分は正常な値が入っているものとみなして対策を省略)
120
+
121
+
122
+
123
+ float _HeadX; // ピン頭X
124
+
125
+ float _HeadY; // ピン頭Y
126
+
127
+ float _TipDist; // ピン頭中心からピン先端までの距離
128
+
129
+ float _InnerRadius; // 淡色部半径
130
+
131
+ float _OuterRadius; // 濃色部半径
132
+
133
+
134
+
135
+ // SpriteFragに代わりこちらを使う
136
+
137
+ fixed4 PinFrag(v2f IN) : SV_Target
138
+
139
+ {
140
+
141
+ // 以下、先端をT、頭の中心をO、Tから頭に接線を引いた時の接点をC、現在の注目点をPとして表す
142
+
143
+ // 座標はOを原点とする...よってOは(0, 0)を示す
144
+
145
+ float2 headPosition = float2(_HeadX, _HeadY); // 頭の中心のUV座標
146
+
147
+ float cosTheta = clamp(_OuterRadius / _TipDist, -1.0, 1.0); // 線分OTと線分OCがなす角のコサイン
148
+
149
+ float sinTheta = sqrt(1.0 - cosTheta * cosTheta); // 線分OTと線分OCがなす角のサイン
150
+
151
+ float2 dBoundary = float2(sinTheta, -cosTheta); // 頭領域と先端領域を分ける半直線の向き
152
+
153
+ float3x3 m = unity_ObjectToWorld; // モデル変換行列
154
+
155
+ float2 scale = float2(length(float3(m[0][0], m[1][0], m[2][0])), length(float3(m[0][1], m[1][1], m[2][1]))); // モデル変換行列から求めた水平・垂直の拡縮率
156
+
157
+ float2 pP = IN.texcoord - headPosition; // Pの座標
158
+
159
+ float2 signPPX = float2(sign(pP.x), 1.0); // PのX成分の符号...float2に掛けるのを簡潔にするため、こちらもfloat2にした
160
+
161
+ pP *= signPPX; // 左右対称なので、単純にするために右半分だけで計算し、最後に符号を合わせることにする
162
+
163
+ float pDist = length(pP); // PのOからの距離
164
+
165
+ float2 dP = pP / max(pDist, EPSILON); // Oから見たPの向き
166
+
167
+ float cosPhi = -dP.y; // 線分OTと線分OPがなす角のコサイン
168
+
169
+ float cosPsi = dot(dBoundary, dP); // 線分OCと線分OPがなす角のコサイン
170
+
171
+ float scaleP = length(normalize(dBoundary / scale) * scale); // 線分OP方向における拡縮率
172
+
173
+ float borderWidth = _OuterRadius - _InnerRadius; // 境界の幅
174
+
175
+ float scaledBorderWidth = borderWidth / scaleP; // 拡縮率補正後の境界幅
176
+
177
+ float scaledInnerRadius = _OuterRadius - scaledBorderWidth; // 拡縮率補正後の淡色部半径
178
+
179
+ float radiusMultiplier = lerp(1.0, 1.0 / max(cosPsi, EPSILON), step(cosTheta, cosPhi)); // 半径の補正係数...Pが頭領域にあるなら1.0、先端領域なら1.0 / cosPsi
180
+
181
+ float pInnerRadius = _InnerRadius * radiusMultiplier; // Pにおける淡色部半径
182
+
183
+ float pScaledInnerRadius = scaledInnerRadius * radiusMultiplier; // Pにおける拡縮率補正後の淡色部半径
184
+
185
+ float pOuterRadius = _OuterRadius * radiusMultiplier; // Pにおける濃色部半径
186
+
187
+ float pIsInOuterRegionOrOutOfBounds = step(pScaledInnerRadius, pDist); // Pが補正後淡色部半径よりも外側か
188
+
189
+ float pIsOutOfBounds = step(pOuterRadius, pDist); // Pが濃色部半径よりも外側か
190
+
191
+ float pScaledDistForInnerRegion = pDist * pInnerRadius / pScaledInnerRadius; // 淡色部の場合のPのOからの拡縮率補正後距離
192
+
193
+ float pScaledDistForOuterRegion = pInnerRadius + (pDist - pScaledInnerRadius) * (pOuterRadius - pInnerRadius) / (pOuterRadius - pScaledInnerRadius); // 濃色部の場合のPのOからの拡縮率補正後距離
194
+
195
+ float pScaledDistForOutOfBounds = pDist; // 形状範囲外の場合のPのOからの拡縮率補正後距離
196
+
197
+ float pScaledDist = lerp(lerp(pScaledDistForInnerRegion, pScaledDistForOuterRegion, pIsInOuterRegionOrOutOfBounds), pScaledDistForOutOfBounds, pIsOutOfBounds); // 合成したPのOからの拡縮率補正後距離
198
+
199
+ float2 samplingPoint = dP * (pScaledDist * signPPX) + headPosition; // テクスチャ色のサンプリング位置...符号を戻して、頭中心の座標を足してUV座標とする
200
+
201
+
202
+
203
+ fixed4 c = SampleSpriteTexture(samplingPoint) * IN.color;
204
+
205
+ c.rgb *= c.a;
206
+
207
+ return c;
208
+
209
+ }
210
+
211
+ ENDCG
212
+
213
+ }
214
+
215
+ }
216
+
217
+ }
218
+
219
+ ```
220
+
221
+
222
+
223
+ 伸縮のために拡縮率を調べる必要がありますが、これはオブジェクトのモデル変換行列を手がかりとしています。そのため、Sprite RendererのSizeではなく、TransformのScaleを操作して拡縮すると伸縮するという動作になっています。
224
+
225
+ 十分調査していないのですが、Sprite RendererのSizeを操作すると、変換行列による拡縮ではなく、ポリゴンの頂点モデル座標が直接書き換えられるのかもしれません。そうだとすると、そこから拡縮率を求めるのは一筋縄ではいかなそうに思えました(ジオメトリーシェーダーを挟んで、そこで求めることができるかもしれませんが、コードが複雑になりそうです)。
226
+
227
+ モデル変換行列から拡縮率を調べている都合上、そのスプライトオブジェクトのScaleだけでなく、親以上の階層のScaleを操作しても同じく伸縮が起こります。場合によってはこの動作は都合が悪いかもしれませんが、その場合はやはり先に述べた2枚スプライト方式を使うということになるでしょうか。
228
+
229
+
230
+
231
+ 拡縮してみると下図のようになります。左3つはカスタムマテリアル方式、中3つは2枚スプライト方式、右3つは特別な処理をしていない通常の拡縮です。
232
+
233
+ 2枚スプライト方式だと、拡縮率が1未満では内側領域に隠れていた外側領域内部が見えるようになり、拡縮率が1より大きいと外側領域が内側領域によって隠れていきます。
234
+
235
+
236
+
237
+ ![プレビュー](5671ec0bc3e852d0fcc1eca75a0b2471.gif)