🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

4455閲覧

シェーダーファイルをShader.Findで読み込むとビルド後動作しない

masa_000

総合スコア18

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2019/10/08 19:15

編集2019/10/10 18:37

C#

1private Material equirectangularMaterial; 2this.equirectangularMaterial = new Material(Shader.Find(@"Hidden\CubeToEquirectangular")); 3

Shader.Findという関数を使ってシェーダーファイルを読み込もうとしています。ビルド前は上記のように書いてうまくいっているのですがビルド後に実行するとNullReferenceExceptionになり実行できません。
調べてみるとShader.Findはビルド後はこういう動作をするので、Inspecterでhttps://qiita.com/shirai/items/3d2e3ff9e0d9a55a2e23のようにすると解決するらしいのですが、なんとかスクリプト上でビルド後もシェーダーファイルを読み込むことはできないのでしょうか?ビルドされたものに処理を差し込みたいのでスクリプト上で解決したいのです。

Unity5.6.2f1です。よろしくお願いします。

追記
処理が差し込めないのはStereoEquirectangularTextureUpdaterだけなので、StereoCubemapUpdaterで作ったCubemapLeft,CubemapRightをファイルかなにかに出力して、後でそのファイルをシェーダーファイルを読み込める環境で処理すればできないでしょうか。
それで質問なのですが、StereoCubemapUpdaterでつくったCubemapLeft,CubemapRightをStereoEquirectangularTextureUpdaterで再度利用するためにはどんな形式で出力して保存すればいいのでしょうか?

C#

1 void savePng(String filename, int count, RenderTexture saveRT) 2 { 3 4 Texture2D tex = new Texture2D(saveRT.width, saveRT.height, TextureFormat.RGB24, false); 5 RenderTexture.active = saveRT; 6 tex.ReadPixels(new Rect(0, 0, saveRT.width, saveRT.height), 0, 0); 7 tex.Apply(); 8 9 // Encode texture into PNG 10 byte[] bytes = tex.EncodeToPNG(); 11 UnityEngine.Object.Destroy(tex); 12 13 var name = @"C:\Users\Username\Desktop\unity\Captures\" + filename + count.ToString("0") + ".png"; 14 //Write to a file in the project folder 15 File.WriteAllBytes(name, bytes); 16 17 } 18

とりあえず上記の関数でCubemapLeftをpngにしてみたのですが、下のような画像になってしまってキューブマップではないと思います。
イメージ説明

本当に度々申し訳ありません。わかりにくい文章で恐縮ですが時間があるときでよいので回答よろしくお願いします。

追記

C#

1void Update() 2{ 3 Texture2D tex; 4 private Color[] CubeMapColors; 5 string path = @"C:\Users\Username\Desktop\unity\Captures\"; 6 CubemapFace[] cubemapFaces = 7 { 8 CubemapFace.PositiveX, CubemapFace.NegativeX, 9 CubemapFace.PositiveY, CubemapFace.NegativeY, 10 CubemapFace.PositiveZ, CubemapFace.NegativeZ 11 }; 12 foreach (CubemapFace face in cubemapFaces) 13 { 14 name = path + @"Right\" + "frame" + framecount.ToString("0") + face.ToString() + ".png"; 15 tex = ReadTexture(name, 1024, 1024); 16 CubeMapColors = tex.GetPixels(); 17 cubemapRight.SetPixels(CubeMapColors, face); 18 cubemapRight.Apply(); 19 } 20} 21byte[] ReadPngFile(string path) 22{ 23 FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read); 24 BinaryReader bin = new BinaryReader(fileStream); 25 byte[] values = bin.ReadBytes((int)bin.BaseStream.Length); 26 27 bin.Close(); 28 29 return values; 30} 31 32Texture2D ReadTexture(string path, int width, int height) 33{ 34 byte[] readBinary = ReadPngFile(path); 35 36 Texture2D texture = new Texture2D(width, height,TextureFormat.RGB24, false); 37 texture.LoadImage(readBinary); 38 39 return texture; 40}

すみませんが最後にもう一つだけ質問してよろしいでしょうか。
Cubemap.SetPixelsを使って6枚の画像をRenderTextureにセットするスクリプトを書いてみたのですが、RenderTextureにうまくセットできていないようです。ファイルから画像を読み込むことはできているようです。Cubemap.SetPixelsの使い方かヒントを教えていだだけないでしょうか。
質問ばかりで申し訳ありませんが時間のあるときでいいのでよろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

では、インスペクター上で参照をセットしておくことは可能でしょうか?
スクリプトに[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.SetPixelsfaceをそれぞれのCubemapFaceに切り替えながら計6回セットしてやることになるかと思います。

画像ファイルからキューブマップを作る件について追記

提示いたしましたコードのcubemapLeftcubemapRightCubemap型ではなく「dimensionCubeにしたRenderTexture型」です。これはTexture2Dと普通のRenderTextureとの関係と同様で、前者は画像ファイルなどCPU側のデータをGPU側でのテクスチャとして使えるように送り込む用途、後者はGPU側でテクスチャ上にレンダリングを行う用途に適していると言えるでしょう。
今回のケースでは画像ロード先のキューブマップがRenderTextureである必要はないので、Cubemapに変更してはいかがでしょうか。

C#

1private Cubemap cubemapLeft; 2private Cubemap cubemapRight;

そして初期化時もCubemapを作るようにしてやれば、cubemapLeftcubemapRightに対して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);の部分ではcubemapLeftcubemapRightRenderTextureであってもCubemapであっても区別せず扱えるはずですので、コードはそのままでいいかと思います。

投稿2019/10/08 19:58

編集2019/10/11 02:27
Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

masa_000

2019/10/08 20:16

度々、申し訳ありません。 他の人がビルドしたものに使いたいと思っているのでインスペクターを操作することは一切できません。できるのはスクリプトに書くことができることだけです。
masa_000

2019/10/08 20:29 編集

インスペクターに触れない状況というのは意味不明に思われるかと思うので、どうゆうことか説明します。ビルド済みUnityのゲームがあり、そのゲームには自作のスクリプトを差し込むframeworkがあって、frameworkのPluginフォルダにdllファイルを入れると起動時に実行されます。そのためインスペクターやSerializeFieldは使うことができないのです。
Bongo

2019/10/08 20:38

なるほど、ではResourcesフォルダを使うことは許されるでしょうか? http://arit-clt.com/2016/09/15/3197/ の後半で説明されているようなやり方で、シェーダーファイルをResourcesフォルダ内に置いておいてスクリプトからロードするのはどうでしょう。これもダメだとなるとやっかいですね...
Bongo

2019/10/08 20:41

すみません、コメントを見逃してしまいました。DLLファイルの形にしないといけないのですね。となるとResourcesも使えないかも... もし何か案が思いついたら追記します。
Bongo

2019/10/09 15:59

独自シェーダーを読み込ませるのは困難そうでしたので代替案を考えてみましたが、いかがでしょうか。 もしこれでもダメだとなると、あとは完全にシェーダーに頼らず展開するとかでしょうかね? おそらく絶望的に重くなりそうな気配がしますが、仕方ないかもしれません...
Bongo

2019/10/11 02:28

画像ファイルの内容をキューブマップとして使えるようにする件については、cubemapLeftとcubemapRightはCubemapではなくRenderTextureですので、対策としては画像ファイルから読み込んだ色データをRenderTextureであるcubemapRight上にレンダリングしてやるか、cubemapRightの型をCubemapに変えてやるかのいずれかになるでしょう。今回は状況的に後者の方が適切だろうと思います。
masa_000

2019/10/11 23:03

無事完成しました。正直自分のできる限界を超えていたのでBongoさんの助けがなければ完成することはできませんでした。何度も何度も教えていただき本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問