teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

4

画像ファイルからのキューブマップ復元について追記

2019/10/11 02:27

投稿

Bongo
Bongo

スコア10816

answer CHANGED
@@ -256,4 +256,19 @@
256
256
  UnityEngine.Object.Destroy(tex);
257
257
  }
258
258
  ```
259
- 保存された画像からキューブマップを作る場合、書き出し時と逆に[Cubemap.SetPixels](https://docs.unity3d.com/jp/560/ScriptReference/Cubemap.SetPixels.html)の`face`をそれぞれの[CubemapFace](https://docs.unity3d.com/jp/560/ScriptReference/CubemapFace.html)に切り替えながら計6回セットしてやることになるかと思います。
259
+ 保存された画像からキューブマップを作る場合、書き出し時と逆に[Cubemap.SetPixels](https://docs.unity3d.com/jp/560/ScriptReference/Cubemap.SetPixels.html)の`face`をそれぞれの[CubemapFace](https://docs.unity3d.com/jp/560/ScriptReference/CubemapFace.html)に切り替えながら計6回セットしてやることになるかと思います。
260
+
261
+ **画像ファイルからキューブマップを作る件について追記**
262
+
263
+ 提示いたしましたコードの`cubemapLeft`や`cubemapRight`は[Cubemap](https://docs.unity3d.com/560/Documentation/ScriptReference/Cubemap.html)型ではなく「[dimension](https://docs.unity3d.com/560/Documentation/ScriptReference/RenderTexture-dimension.html)を[Cube](https://docs.unity3d.com/560/Documentation/ScriptReference/Rendering.TextureDimension.Cube.html)にした`RenderTexture`型」です。これは`Texture2D`と普通の`RenderTexture`との関係と同様で、前者は画像ファイルなどCPU側のデータをGPU側でのテクスチャとして使えるように送り込む用途、後者はGPU側でテクスチャ上にレンダリングを行う用途に適していると言えるでしょう。
264
+ 今回のケースでは画像ロード先のキューブマップが`RenderTexture`である必要はないので、`Cubemap`に変更してはいかがでしょうか。
265
+ ```C#
266
+ private Cubemap cubemapLeft;
267
+ private Cubemap cubemapRight;
268
+ ```
269
+ そして初期化時も`Cubemap`を作るようにしてやれば、`cubemapLeft`や`cubemapRight`に対して`SetPixels`が使えるようになるはずです。
270
+ ```C#
271
+ this.cubemapLeft = new Cubemap(1024, TextureFormat.RGB24, false);
272
+ this.cubemapRight = new Cubemap(1024, TextureFormat.RGB24, false);
273
+ ```
274
+ それ以降の円筒投影する`Graphics.Blit(this.cubemapRight, this.equirectangularTexture, this.equirectangularMaterial);`の部分では`cubemapLeft`や`cubemapRight`が`RenderTexture`であっても`Cubemap`であっても区別せず扱えるはずですので、コードはそのままでいいかと思います。

3

保存した画像からキューブマップを復元することについて追記

2019/10/11 02:27

投稿

Bongo
Bongo

スコア10816

answer CHANGED
@@ -255,4 +255,5 @@
255
255
 
256
256
  UnityEngine.Object.Destroy(tex);
257
257
  }
258
- ```
258
+ ```
259
+ 保存された画像からキューブマップを作る場合、書き出し時と逆に[Cubemap.SetPixels](https://docs.unity3d.com/jp/560/ScriptReference/Cubemap.SetPixels.html)の`face`をそれぞれの[CubemapFace](https://docs.unity3d.com/jp/560/ScriptReference/CubemapFace.html)に切り替えながら計6回セットしてやることになるかと思います。

2

キューブマップのファイル出力について追記

2019/10/09 20:11

投稿

Bongo
Bongo

スコア10816

answer CHANGED
@@ -224,4 +224,35 @@
224
224
  注意点として、前回の独自シェーダーによる方法ではピクセル単位の正確さで展開されるのに対し、今回の方法では展開に使うメッシュをある程度細かくしないとゆがみが目立ってしまう弱点があります。
225
225
  たとえば`sphereNetDivision`を4に下げると、こんな風になってしまいます...
226
226
 
227
- ![図](30eb4042c900fe89326bf2b7fe1f005d.gif)
227
+ ![図](30eb4042c900fe89326bf2b7fe1f005d.gif)
228
+
229
+ **キューブマップをファイルに保存する件について追記**
230
+ 以前申し上げましたように、キューブマップには6つの面があります。保存メソッドを下記のようにしてみるとどうでしょうか?
231
+ ```C#
232
+ void savePng(string filename, int count, RenderTexture saveRT)
233
+ {
234
+ CubemapFace[] cubemapFaces =
235
+ {
236
+ CubemapFace.PositiveX, CubemapFace.NegativeX,
237
+ CubemapFace.PositiveY, CubemapFace.NegativeY,
238
+ CubemapFace.PositiveZ, CubemapFace.NegativeZ
239
+ };
240
+ Texture2D tex = new Texture2D(saveRT.width, saveRT.height, TextureFormat.RGB24, false);
241
+
242
+ foreach (CubemapFace face in cubemapFaces)
243
+ {
244
+ Graphics.SetRenderTarget(saveRT, 0, face);
245
+ tex.ReadPixels(new Rect(0, 0, saveRT.width, saveRT.height), 0, 0);
246
+ tex.Apply();
247
+
248
+ // Encode texture into PNG
249
+ byte[] bytes = tex.EncodeToPNG();
250
+
251
+ var name = @"C:\Users\Username\Desktop\unity\Captures\" + filename + count.ToString("0") + face.ToString() + ".png";
252
+ //Write to a file in the project folder
253
+ File.WriteAllBytes(name, bytes);
254
+ }
255
+
256
+ UnityEngine.Object.Destroy(tex);
257
+ }
258
+ ```

1

代替案を追記

2019/10/09 20:01

投稿

Bongo
Bongo

スコア10816

answer CHANGED
@@ -1,2 +1,227 @@
1
1
  では、インスペクター上で参照をセットしておくことは可能でしょうか?
2
- スクリプトに`[SerializeField] private Shader equirectangularShader;`のようにフィールドを追加して、インスペクター上の欄にプロジェクトビューからシェーダーファイルをドラッグ&ドロップし、マテリアル作成部分では`this.equirectangularMaterial = new Material(this.equirectangularShader);`という風にするのではどうでしょう。
2
+ スクリプトに`[SerializeField] private Shader equirectangularShader;`のようにフィールドを追加して、インスペクター上の欄にプロジェクトビューからシェーダーファイルをドラッグ&ドロップし、マテリアル作成部分では`this.equirectangularMaterial = new Material(this.equirectangularShader);`という風にするのではどうでしょう。
3
+
4
+ **追記**
5
+ すみませんが、独自のシェーダーを持ち込む方法はわかりませんでした...
6
+ アプローチを変えて、「[Unity5.6.2f1でメインカメラの両目の映像をレンダリングしたい](https://teratail.com/questions/215439)」で申し上げた正距円筒図法展開をビルトインシェーダーのみで行えないか検討してみました。
7
+ デフォルトの状態ですとAlways Included Shadersに`Hidden/CubeCopy`が入っています。もしそのゲームでそこがいじられていなければ、それを使うことができるんじゃないかともくろみました。
8
+
9
+ `CubeCopy`は単純に入力されたUV座標からキューブマップをサンプリングして出力するだけのシェーダーですので、円筒図法展開に利用するには入力するメッシュをちょっと工夫してやることになるでしょう。
10
+ そこで頂点座標は四角い展開図の形、UV座標は3次元の球体の形のメッシュを作り、それを[DrawMeshNow](https://docs.unity3d.com/jp/560/ScriptReference/Graphics.DrawMeshNow.html)で描画することにしました。
11
+ 前回の`StereoEquirectangularTextureUpdater`をベースにしており相変わらず`SerializeField`やらを使っていますので、ご質問者さんの用途には直接利用できないかもしれません。使えそうな部分だけご参考にしていただきたく思います。
12
+
13
+ ```C#
14
+ using System;
15
+ using System.Collections.Generic;
16
+ using UnityEngine;
17
+
18
+ public class StereoEquirectangularTextureUpdaterBuiltinShader : MonoBehaviour
19
+ {
20
+ public enum Arrangement
21
+ {
22
+ TopAndBottom,
23
+ SideBySide
24
+ }
25
+
26
+ public enum FieldOfView
27
+ {
28
+ Full,
29
+ Half
30
+ }
31
+
32
+ [SerializeField] private StereoCubemapUpdater stereoCubemapUpdater;
33
+ [SerializeField] private RenderTexture equirectangularTexture;
34
+ [SerializeField] private bool useLocalDirection;
35
+ [SerializeField] private Arrangement arrangement;
36
+ [SerializeField] private FieldOfView fieldOfView;
37
+ [SerializeField] private int sphereNetDivision = 16;
38
+
39
+ private RenderTexture cubemapLeft;
40
+ private RenderTexture cubemapRight;
41
+ private Material equirectangularMaterial;
42
+ private Mesh sphereNet;
43
+ private List<Vector3> sphereNetUvs;
44
+ private List<Vector3> rotatedSphereNetUvs;
45
+ private Mesh hemisphereNet;
46
+ private List<Vector3> hemisphereNetUvs;
47
+ private List<Vector3> rotatedHemisphereNetUvs;
48
+
49
+ // 球面の展開図を表すメッシュを生成するメソッド
50
+ // divisionを大きくするほど正確に展開できるはずだが、その分頂点数が増えるので
51
+ // ローカル展開の場合のUV更新にかかるコストが大きくなると思われる
52
+ private Mesh CreateSphereNet(int division, bool asHemisphere, out List<Vector3> uvs)
53
+ {
54
+ var deltaAngle = Mathf.PI / division;
55
+ var divisionX = (asHemisphere ? 1 : 2) * division;
56
+ var divisionY = division;
57
+ var divisionXPlus1 = divisionX + 1;
58
+ var divisionYPlus1 = divisionY + 1;
59
+ var deltaX = 2.0f / divisionX;
60
+ var deltaY = 2.0f / divisionY;
61
+ var x = new float[divisionXPlus1];
62
+ var phi0 = -(asHemisphere ? 0.5f : 1.0f) * Mathf.PI;
63
+ var sinPhi = new float[x.Length];
64
+ var cosPhi = new float[x.Length];
65
+ var y = new float[divisionYPlus1];
66
+ var theta0 = -0.5f * Mathf.PI;
67
+ var sinTheta = new float[y.Length];
68
+ var cosTheta = new float[y.Length];
69
+ for (var i = 0; i <= divisionX; i++)
70
+ {
71
+ var angle = phi0 + (deltaAngle * i);
72
+ sinPhi[i] = Mathf.Sin(angle);
73
+ cosPhi[i] = Mathf.Cos(angle);
74
+ x[i] = (deltaX * i) - 1.0f;
75
+ }
76
+
77
+ for (var i = 0; i <= divisionY; i++)
78
+ {
79
+ var angle = theta0 + (deltaAngle * i);
80
+ sinTheta[i] = Mathf.Sin(angle);
81
+ cosTheta[i] = Mathf.Cos(angle);
82
+ y[i] = (deltaY * i) - 1.0f;
83
+ }
84
+
85
+ var vertices = new Vector3[divisionXPlus1 * divisionYPlus1];
86
+ uvs = new List<Vector3>(vertices.Length);
87
+ for (var j = 0; j <= divisionY; j++)
88
+ {
89
+ var o = j * divisionXPlus1;
90
+ for (var i = 0; i <= divisionX; i++)
91
+ {
92
+ vertices[o + i] = new Vector3(x[i], y[j], 0.0f);
93
+ uvs.Add(new Vector3(sinPhi[i] * cosTheta[j], sinTheta[j], cosPhi[i] * cosTheta[j]));
94
+ }
95
+ }
96
+
97
+ var triangles = new int[divisionX * divisionY * 6];
98
+ for (var j = 0; j < divisionY; j++)
99
+ {
100
+ var o = j * divisionX;
101
+ for (var i = 0; i < divisionX; i++)
102
+ {
103
+ var k = (o + i) * 6;
104
+ triangles[k] = (divisionXPlus1 * j) + i;
105
+ triangles[k + 1] = (divisionXPlus1 * (j + 1)) + i;
106
+ triangles[k + 2] = triangles[k] + 1;
107
+ triangles[k + 3] = triangles[k + 1] + 1;
108
+ triangles[k + 4] = triangles[k + 2];
109
+ triangles[k + 5] = triangles[k + 1];
110
+ }
111
+ }
112
+
113
+ var net = new Mesh();
114
+ net.vertices = vertices;
115
+ net.SetUVs(0, uvs);
116
+ net.triangles = triangles;
117
+ net.MarkDynamic();
118
+ return net;
119
+ }
120
+
121
+ private void Start()
122
+ {
123
+ this.cubemapLeft = this.stereoCubemapUpdater.CubemapLeft;
124
+ this.cubemapRight = this.stereoCubemapUpdater.CubemapRight;
125
+ this.equirectangularMaterial = new Material(Shader.Find("Hidden/CubeCopy"));
126
+ this.sphereNet = this.CreateSphereNet(this.sphereNetDivision, false, out this.sphereNetUvs);
127
+ this.hemisphereNet = this.CreateSphereNet(this.sphereNetDivision, true, out this.hemisphereNetUvs);
128
+ this.rotatedSphereNetUvs = new List<Vector3>(this.sphereNetUvs);
129
+ this.rotatedHemisphereNetUvs = new List<Vector3>(this.hemisphereNetUvs);
130
+ }
131
+
132
+ private void LateUpdate()
133
+ {
134
+ // 描画先座標の移動・伸縮はMatrix4x4で表現する
135
+ Matrix4x4 leftVertexTransform;
136
+ Matrix4x4 rightVertexTransform;
137
+ switch (this.arrangement)
138
+ {
139
+ case Arrangement.TopAndBottom:
140
+ leftVertexTransform = Matrix4x4.TRS(
141
+ new Vector3(0.0f, 0.5f, 0.0f),
142
+ Quaternion.identity,
143
+ new Vector3(1.0f, 0.5f, 1.0f));
144
+ rightVertexTransform = Matrix4x4.TRS(
145
+ new Vector3(0.0f, -0.5f, 0.0f),
146
+ Quaternion.identity,
147
+ new Vector3(1.0f, 0.5f, 1.0f));
148
+ break;
149
+ case Arrangement.SideBySide:
150
+ leftVertexTransform = Matrix4x4.TRS(
151
+ new Vector3(-0.5f, 0.0f, 0.0f),
152
+ Quaternion.identity,
153
+ new Vector3(0.5f, 1.0f, 1.0f));
154
+ rightVertexTransform = Matrix4x4.TRS(
155
+ new Vector3(0.5f, 0.0f, 0.0f),
156
+ Quaternion.identity,
157
+ new Vector3(0.5f, 1.0f, 1.0f));
158
+ break;
159
+ default:
160
+ throw new ArgumentOutOfRangeException();
161
+ }
162
+
163
+ // ローカル展開の場合はUV座標を回転しメッシュに再設定する
164
+ var rotation = this.stereoCubemapUpdater.transform.rotation;
165
+ switch (this.fieldOfView)
166
+ {
167
+ case FieldOfView.Full:
168
+ if (this.useLocalDirection)
169
+ {
170
+ for (var i = 0; i < this.sphereNetUvs.Count; i++)
171
+ {
172
+ this.rotatedSphereNetUvs[i] = rotation * this.sphereNetUvs[i];
173
+ }
174
+
175
+ this.sphereNet.SetUVs(0, this.rotatedSphereNetUvs);
176
+ }
177
+ else
178
+ {
179
+ this.sphereNet.SetUVs(0, this.sphereNetUvs);
180
+ }
181
+
182
+ this.sphereNet.RecalculateBounds();
183
+ break;
184
+ case FieldOfView.Half:
185
+ if (this.useLocalDirection)
186
+ {
187
+ for (var i = 0; i < this.hemisphereNetUvs.Count; i++)
188
+ {
189
+ this.rotatedHemisphereNetUvs[i] = rotation * this.hemisphereNetUvs[i];
190
+ }
191
+
192
+ this.hemisphereNet.SetUVs(0, this.rotatedHemisphereNetUvs);
193
+ }
194
+ else
195
+ {
196
+ this.hemisphereNet.SetUVs(0, this.hemisphereNetUvs);
197
+ }
198
+
199
+ this.hemisphereNet.RecalculateBounds();
200
+ break;
201
+ default:
202
+ throw new ArgumentOutOfRangeException();
203
+ }
204
+
205
+ // メッシュをレンダーテクスチャ上にレンダリング
206
+ if (this.equirectangularMaterial.SetPass(0))
207
+ {
208
+ var activeTexture = RenderTexture.active;
209
+ RenderTexture.active = this.equirectangularTexture;
210
+ var net = this.fieldOfView == FieldOfView.Full ? this.sphereNet : this.hemisphereNet;
211
+ GL.PushMatrix();
212
+ GL.LoadProjectionMatrix(Matrix4x4.identity);
213
+ this.equirectangularMaterial.mainTexture = this.cubemapLeft;
214
+ Graphics.DrawMeshNow(net, leftVertexTransform);
215
+ this.equirectangularMaterial.mainTexture = this.cubemapRight;
216
+ Graphics.DrawMeshNow(net, rightVertexTransform);
217
+ GL.PopMatrix();
218
+ RenderTexture.active = activeTexture;
219
+ }
220
+ }
221
+ }
222
+ ```
223
+
224
+ 注意点として、前回の独自シェーダーによる方法ではピクセル単位の正確さで展開されるのに対し、今回の方法では展開に使うメッシュをある程度細かくしないとゆがみが目立ってしまう弱点があります。
225
+ たとえば`sphereNetDivision`を4に下げると、こんな風になってしまいます...
226
+
227
+ ![図](30eb4042c900fe89326bf2b7fe1f005d.gif)