では、インスペクター上で参照をセットしておくことは可能でしょうか?
スクリプトに[SerializeField] private Shader equirectangularShader;
のようにフィールドを追加して、インスペクター上の欄にプロジェクトビューからシェーダーファイルをドラッグ&ドロップし、マテリアル作成部分ではthis.equirectangularMaterial = new Material(this.equirectangularShader);
という風にするのではどうでしょう。
追記
すみませんが、独自のシェーダーを持ち込む方法はわかりませんでした...
アプローチを変えて、「Unity5.6.2f1でメインカメラの両目の映像をレンダリングしたい」で申し上げた正距円筒図法展開をビルトインシェーダーのみで行えないか検討してみました。
デフォルトの状態ですとAlways Included ShadersにHidden/CubeCopy
が入っています。もしそのゲームでそこがいじられていなければ、それを使うことができるんじゃないかともくろみました。
CubeCopy
は単純に入力されたUV座標からキューブマップをサンプリングして出力するだけのシェーダーですので、円筒図法展開に利用するには入力するメッシュをちょっと工夫してやることになるでしょう。
そこで頂点座標は四角い展開図の形、UV座標は3次元の球体の形のメッシュを作り、それをDrawMeshNowで描画することにしました。
前回のStereoEquirectangularTextureUpdater
をベースにしており相変わらずSerializeField
やらを使っていますので、ご質問者さんの用途には直接利用できないかもしれません。使えそうな部分だけご参考にしていただきたく思います。
C#
1using System;
2using System.Collections.Generic;
3using UnityEngine;
4
5public class StereoEquirectangularTextureUpdaterBuiltinShader : MonoBehaviour
6{
7 public enum Arrangement
8 {
9 TopAndBottom,
10 SideBySide
11 }
12
13 public enum FieldOfView
14 {
15 Full,
16 Half
17 }
18
19 [SerializeField] private StereoCubemapUpdater stereoCubemapUpdater;
20 [SerializeField] private RenderTexture equirectangularTexture;
21 [SerializeField] private bool useLocalDirection;
22 [SerializeField] private Arrangement arrangement;
23 [SerializeField] private FieldOfView fieldOfView;
24 [SerializeField] private int sphereNetDivision = 16;
25
26 private RenderTexture cubemapLeft;
27 private RenderTexture cubemapRight;
28 private Material equirectangularMaterial;
29 private Mesh sphereNet;
30 private List<Vector3> sphereNetUvs;
31 private List<Vector3> rotatedSphereNetUvs;
32 private Mesh hemisphereNet;
33 private List<Vector3> hemisphereNetUvs;
34 private List<Vector3> rotatedHemisphereNetUvs;
35
36 // 球面の展開図を表すメッシュを生成するメソッド
37 // divisionを大きくするほど正確に展開できるはずだが、その分頂点数が増えるので
38 // ローカル展開の場合のUV更新にかかるコストが大きくなると思われる
39 private Mesh CreateSphereNet(int division, bool asHemisphere, out List<Vector3> uvs)
40 {
41 var deltaAngle = Mathf.PI / division;
42 var divisionX = (asHemisphere ? 1 : 2) * division;
43 var divisionY = division;
44 var divisionXPlus1 = divisionX + 1;
45 var divisionYPlus1 = divisionY + 1;
46 var deltaX = 2.0f / divisionX;
47 var deltaY = 2.0f / divisionY;
48 var x = new float[divisionXPlus1];
49 var phi0 = -(asHemisphere ? 0.5f : 1.0f) * Mathf.PI;
50 var sinPhi = new float[x.Length];
51 var cosPhi = new float[x.Length];
52 var y = new float[divisionYPlus1];
53 var theta0 = -0.5f * Mathf.PI;
54 var sinTheta = new float[y.Length];
55 var cosTheta = new float[y.Length];
56 for (var i = 0; i <= divisionX; i++)
57 {
58 var angle = phi0 + (deltaAngle * i);
59 sinPhi[i] = Mathf.Sin(angle);
60 cosPhi[i] = Mathf.Cos(angle);
61 x[i] = (deltaX * i) - 1.0f;
62 }
63
64 for (var i = 0; i <= divisionY; i++)
65 {
66 var angle = theta0 + (deltaAngle * i);
67 sinTheta[i] = Mathf.Sin(angle);
68 cosTheta[i] = Mathf.Cos(angle);
69 y[i] = (deltaY * i) - 1.0f;
70 }
71
72 var vertices = new Vector3[divisionXPlus1 * divisionYPlus1];
73 uvs = new List<Vector3>(vertices.Length);
74 for (var j = 0; j <= divisionY; j++)
75 {
76 var o = j * divisionXPlus1;
77 for (var i = 0; i <= divisionX; i++)
78 {
79 vertices[o + i] = new Vector3(x[i], y[j], 0.0f);
80 uvs.Add(new Vector3(sinPhi[i] * cosTheta[j], sinTheta[j], cosPhi[i] * cosTheta[j]));
81 }
82 }
83
84 var triangles = new int[divisionX * divisionY * 6];
85 for (var j = 0; j < divisionY; j++)
86 {
87 var o = j * divisionX;
88 for (var i = 0; i < divisionX; i++)
89 {
90 var k = (o + i) * 6;
91 triangles[k] = (divisionXPlus1 * j) + i;
92 triangles[k + 1] = (divisionXPlus1 * (j + 1)) + i;
93 triangles[k + 2] = triangles[k] + 1;
94 triangles[k + 3] = triangles[k + 1] + 1;
95 triangles[k + 4] = triangles[k + 2];
96 triangles[k + 5] = triangles[k + 1];
97 }
98 }
99
100 var net = new Mesh();
101 net.vertices = vertices;
102 net.SetUVs(0, uvs);
103 net.triangles = triangles;
104 net.MarkDynamic();
105 return net;
106 }
107
108 private void Start()
109 {
110 this.cubemapLeft = this.stereoCubemapUpdater.CubemapLeft;
111 this.cubemapRight = this.stereoCubemapUpdater.CubemapRight;
112 this.equirectangularMaterial = new Material(Shader.Find("Hidden/CubeCopy"));
113 this.sphereNet = this.CreateSphereNet(this.sphereNetDivision, false, out this.sphereNetUvs);
114 this.hemisphereNet = this.CreateSphereNet(this.sphereNetDivision, true, out this.hemisphereNetUvs);
115 this.rotatedSphereNetUvs = new List<Vector3>(this.sphereNetUvs);
116 this.rotatedHemisphereNetUvs = new List<Vector3>(this.hemisphereNetUvs);
117 }
118
119 private void LateUpdate()
120 {
121 // 描画先座標の移動・伸縮はMatrix4x4で表現する
122 Matrix4x4 leftVertexTransform;
123 Matrix4x4 rightVertexTransform;
124 switch (this.arrangement)
125 {
126 case Arrangement.TopAndBottom:
127 leftVertexTransform = Matrix4x4.TRS(
128 new Vector3(0.0f, 0.5f, 0.0f),
129 Quaternion.identity,
130 new Vector3(1.0f, 0.5f, 1.0f));
131 rightVertexTransform = Matrix4x4.TRS(
132 new Vector3(0.0f, -0.5f, 0.0f),
133 Quaternion.identity,
134 new Vector3(1.0f, 0.5f, 1.0f));
135 break;
136 case Arrangement.SideBySide:
137 leftVertexTransform = Matrix4x4.TRS(
138 new Vector3(-0.5f, 0.0f, 0.0f),
139 Quaternion.identity,
140 new Vector3(0.5f, 1.0f, 1.0f));
141 rightVertexTransform = Matrix4x4.TRS(
142 new Vector3(0.5f, 0.0f, 0.0f),
143 Quaternion.identity,
144 new Vector3(0.5f, 1.0f, 1.0f));
145 break;
146 default:
147 throw new ArgumentOutOfRangeException();
148 }
149
150 // ローカル展開の場合はUV座標を回転しメッシュに再設定する
151 var rotation = this.stereoCubemapUpdater.transform.rotation;
152 switch (this.fieldOfView)
153 {
154 case FieldOfView.Full:
155 if (this.useLocalDirection)
156 {
157 for (var i = 0; i < this.sphereNetUvs.Count; i++)
158 {
159 this.rotatedSphereNetUvs[i] = rotation * this.sphereNetUvs[i];
160 }
161
162 this.sphereNet.SetUVs(0, this.rotatedSphereNetUvs);
163 }
164 else
165 {
166 this.sphereNet.SetUVs(0, this.sphereNetUvs);
167 }
168
169 this.sphereNet.RecalculateBounds();
170 break;
171 case FieldOfView.Half:
172 if (this.useLocalDirection)
173 {
174 for (var i = 0; i < this.hemisphereNetUvs.Count; i++)
175 {
176 this.rotatedHemisphereNetUvs[i] = rotation * this.hemisphereNetUvs[i];
177 }
178
179 this.hemisphereNet.SetUVs(0, this.rotatedHemisphereNetUvs);
180 }
181 else
182 {
183 this.hemisphereNet.SetUVs(0, this.hemisphereNetUvs);
184 }
185
186 this.hemisphereNet.RecalculateBounds();
187 break;
188 default:
189 throw new ArgumentOutOfRangeException();
190 }
191
192 // メッシュをレンダーテクスチャ上にレンダリング
193 if (this.equirectangularMaterial.SetPass(0))
194 {
195 var activeTexture = RenderTexture.active;
196 RenderTexture.active = this.equirectangularTexture;
197 var net = this.fieldOfView == FieldOfView.Full ? this.sphereNet : this.hemisphereNet;
198 GL.PushMatrix();
199 GL.LoadProjectionMatrix(Matrix4x4.identity);
200 this.equirectangularMaterial.mainTexture = this.cubemapLeft;
201 Graphics.DrawMeshNow(net, leftVertexTransform);
202 this.equirectangularMaterial.mainTexture = this.cubemapRight;
203 Graphics.DrawMeshNow(net, rightVertexTransform);
204 GL.PopMatrix();
205 RenderTexture.active = activeTexture;
206 }
207 }
208}
注意点として、前回の独自シェーダーによる方法ではピクセル単位の正確さで展開されるのに対し、今回の方法では展開に使うメッシュをある程度細かくしないとゆがみが目立ってしまう弱点があります。
たとえばsphereNetDivision
を4に下げると、こんな風になってしまいます...
キューブマップをファイルに保存する件について追記
以前申し上げましたように、キューブマップには6つの面があります。保存メソッドを下記のようにしてみるとどうでしょうか?
C#
1void savePng(string filename, int count, RenderTexture saveRT)
2{
3 CubemapFace[] cubemapFaces =
4 {
5 CubemapFace.PositiveX, CubemapFace.NegativeX,
6 CubemapFace.PositiveY, CubemapFace.NegativeY,
7 CubemapFace.PositiveZ, CubemapFace.NegativeZ
8 };
9 Texture2D tex = new Texture2D(saveRT.width, saveRT.height, TextureFormat.RGB24, false);
10
11 foreach (CubemapFace face in cubemapFaces)
12 {
13 Graphics.SetRenderTarget(saveRT, 0, face);
14 tex.ReadPixels(new Rect(0, 0, saveRT.width, saveRT.height), 0, 0);
15 tex.Apply();
16
17 // Encode texture into PNG
18 byte[] bytes = tex.EncodeToPNG();
19
20 var name = @"C:\Users\Username\Desktop\unity\Captures\" + filename + count.ToString("0") + face.ToString() + ".png";
21 //Write to a file in the project folder
22 File.WriteAllBytes(name, bytes);
23 }
24
25 UnityEngine.Object.Destroy(tex);
26}
保存された画像からキューブマップを作る場合、書き出し時と逆にCubemap.SetPixelsのface
をそれぞれのCubemapFaceに切り替えながら計6回セットしてやることになるかと思います。
画像ファイルからキューブマップを作る件について追記
提示いたしましたコードのcubemapLeft
やcubemapRight
はCubemap型ではなく「dimensionをCubeにしたRenderTexture
型」です。これはTexture2D
と普通のRenderTexture
との関係と同様で、前者は画像ファイルなどCPU側のデータをGPU側でのテクスチャとして使えるように送り込む用途、後者はGPU側でテクスチャ上にレンダリングを行う用途に適していると言えるでしょう。
今回のケースでは画像ロード先のキューブマップがRenderTexture
である必要はないので、Cubemap
に変更してはいかがでしょうか。
C#
1private Cubemap cubemapLeft;
2private Cubemap cubemapRight;
そして初期化時もCubemap
を作るようにしてやれば、cubemapLeft
やcubemapRight
に対してSetPixels
が使えるようになるはずです。
C#
1this.cubemapLeft = new Cubemap(1024, TextureFormat.RGB24, false);
2this.cubemapRight = new Cubemap(1024, TextureFormat.RGB24, false);
それ以降の円筒投影するGraphics.Blit(this.cubemapRight, this.equirectangularTexture, this.equirectangularMaterial);
の部分ではcubemapLeft
やcubemapRight
がRenderTexture
であってもCubemap
であっても区別せず扱えるはずですので、コードはそのままでいいかと思います。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/10/08 20:16
2019/10/08 20:29 編集
2019/10/08 20:38
2019/10/08 20:41
2019/10/09 15:59
2019/10/11 02:28
2019/10/11 23:03