質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.48%
Unity

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

Q&A

解決済

2回答

379閲覧

unity shaderで音を出す

kaErita_E

総合スコア14

Unity

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

0グッド

0クリップ

投稿2017/11/12 03:31

編集2017/11/12 08:31

unityのshaderで「~~~なら音をだす」という記述の仕方が分かりません。

追記
http://noshipu.hateblo.jp/entry/2016/04/21/183439
こちらのブログを参考に、thetaからのリアルタイムビューを全天球にはりつけるプログラムに、「全天球上にある色が表示されたら、音をだす」というプログラムを追加したいのですが、リアルタイムビューをテクスチャにしてsphereにはっているため、この段階になにか追記するのは遅延が起こりガタガタの映像になってしまうと考えています。
なのでshaderでなにかできないかと思い質問しました。

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

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

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

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

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

fiveHundred

2017/11/12 06:00

なぜ、shaderでやろうとしているのでしょうか?。shaderはグラフィック関係のものなので、普通はスクリプトでやるものだと考えるはずですが。
kaErita_E

2017/11/12 06:54

コメントありがとうございます。スクリプトだと重くなり遅延が気になってしまうためshaderで行えないかと考えています。
fiveHundred

2017/11/12 08:03

そうであれば、「遅延を無くす方法」を聞いた方がよいです。また、スクリプトの書き方や設定に問題がある場合もあるので、それらも質問文に書いたほうがよいでしょう。質問の方法については、以下のURLを参考にしてください。https://teratail.com/help/question-tips
fiveHundred

2017/11/12 08:04

また、「Unity サウンド 遅延」で検索すると色々な方法がヒットしますが、これらは試されたのでしょうか?
kaErita_E

2017/11/12 08:32

コメントありがとうございます。遅延は音にたいしてではなく映像にたいしてになります。追記させて頂きました。
fiveHundred

2017/11/12 08:44

「この段階になにか追記するのは遅延が起こりガタガタの映像になってしまうと考えています。」とのことですが、これは実際に試されたことなのでしょうか?。また、shaderなら大丈夫だと思う理由は何なのでしょうか?
kaErita_E

2017/11/12 08:46

コメントありがとうございます。このブログをかいているかたに質問したところそのような回答をいただいたためです。
guest

回答2

0

ベストアンサー

シェーダーはGPUと関係の深い、グラフィックス処理やある種の並列計算に特化したミニプログラムといった感じですので、シェーダーに何かコードを書いて音を鳴らせるかと言われると、私もfiveHundredさんやsakura_hanaさんがおっしゃるように難しいんじゃないかと考えています。

想像するにブログの方のお返事は

  1. 天球をレンダリングする
  2. レンダリング結果に特定の色が含まれているか判定する
  3. 色が存在すれば、音を鳴らす

の2番目のステップで、もし「レンダリング結果全体をReadPixelsでCPU側に持ってきて、一つ一つピクセルの色を調べて...」などとしてしまうと遅いだろうから、シェーダーを活用して判定した方がいいだろう、というような意図だったんではないでしょうか。

自身の勉強も兼ねて、下記のような方法での色判定を試してみました。

  • 画面をレンダリングする。天球画像の代用としてシーンに赤い球を配置し、カメラに赤色が写り込んでいるかを判定することにした。
  • ポストプロセッシングエフェクトのやり方を参考に、カメラにアタッチしたスクリプトのOnRenderImage内で色判定処理を行う。
  • レンダリング結果を適当な2の冪乗サイズの一時レンダーテクスチャに写す。この時色判定を行い、目的の色であれば白、そうでなければ黒として写す。

※色判定はピクセル色と目的色の距離が一定以下なら真とすることにしましたが、他にも色相の一致度を見るなどでもいいと思います。

  • 先の白黒画像を縦横1/2の一時レンダーテクスチャに写す。この時、白黒画像からサンプリングした色が真っ黒でなければ、サンプリングされた4テクセルの内1つ以上白テクセルがあるものと考え、出力する色を白とする。そうでなければ黒とする。
  • これを繰り返し1×1サイズまで縮小したら、これをReadPixelsで読み取ってGetPixelで色を調べ、目的色が見つかったかどうかを判定する。

何だか回りくどいことをしていますが、CPU側で毎フレーム何万〜何十万ピクセルも色を調べるよりはマシなのではないかと思います(パフォーマンス比較はしていませんが...)。使える環境がやや狭まるでしょうが、コンピュートシェーダーならもっとスマートに作れるかもしれません。

※余談ですが、「シェーダーで音を鳴らす」のではなく「シェーダーで音データを加工する」ケースはあり得ると思います。RGB画像は3チャンネル2次元の波、ステレオ音声は2チャンネル1次元の波だと思えば、画像加工も音声加工も似たようなことをやっている気がしませんかね?

[追記]
当初のカメラスクリプトではテクスチャ生成時にテクスチャフォーマットを指定していましたが、それをやめてサイズだけ指定するようにしました(最初のコードはMac版OpenGL上でしか動作確認していなかったのですが、後で試したらMetalやWindows版で正しい結果が得られませんでした...)。また併せて、シェーダーコードで返していたフラグメントの色をfixed4(result, 0.0, 0.0, 1.0)からfixed4(result, result, result, 1.0)に変えました。動作上は変える必要はないのですが、縮小過程をフレームデバッガで見た時に、前者だと目的色部分が赤色になってしまいます。上記のコード概要説明では抽出結果を白だの黒だのと表現していますので、それに合わせるようにしてみました。

  • カメラのスクリプト

C#

1using UnityEngine; 2 3[RequireComponent(typeof(Camera))] 4public class CameraController : MonoBehaviour, ISerializationCallbackReceiver 5{ 6 [SerializeField] private int level0TextureSize = 512; // 目的色抽出時の初期テクスチャサイズ 7 [SerializeField] private Color referenceColor; // 目的色 8 [SerializeField, Range(0.0f, 1.0f)] private float referenceColorThreshold; // 目的色判定のための距離のしきい値 9 [SerializeField] private AudioClip enterSound; // 目的色が画面内に入った時に鳴らす音 10 [SerializeField] private AudioClip exitSound; // 目的色が画面外へ出た時に鳴らす音 11 12 private Transform cameraTransform; 13 private Vector3 previousMousePosition; 14 private Material material; 15 private int referenceColorId; 16 private int referenceColorThresholdId; 17 private bool colorFound; 18 private Texture2D queryResultTexture; // 1×1サイズに縮小されたレンダーテクスチャの中身を受け取るためのテクスチャ 19 20 // シリアライズ時にフィールドを適切な値に整形するようにした 21 public void OnBeforeSerialize() 22 { 23 this.level0TextureSize = Mathf.NextPowerOfTwo(Mathf.Max(this.level0TextureSize, 1)); 24 this.referenceColorThreshold = Mathf.Max(this.referenceColorThreshold, 0.0f); 25 } 26 27 public void OnAfterDeserialize() 28 { 29 } 30 31 private void Start() 32 { 33 this.cameraTransform = this.GetComponent<Camera>().transform; 34 } 35 36 // 動作確認用にマウス操作でカメラを動かせるようにした 37 private void Update() 38 { 39 if (Input.GetMouseButton(0)) 40 { 41 var mousePosition = Input.mousePosition; 42 43 if (Input.GetMouseButtonDown(0)) 44 { 45 this.previousMousePosition = mousePosition; 46 } 47 48 var mouseDeltaPosition = mousePosition - this.previousMousePosition; 49 50 this.cameraTransform.Rotate(0.0f, mouseDeltaPosition.x, 0.0f, Space.World); 51 this.cameraTransform.Rotate(-mouseDeltaPosition.y, 0.0f, 0.0f, Space.Self); 52 this.previousMousePosition = mousePosition; 53 } 54 } 55 56 private void OnRenderImage(RenderTexture src, RenderTexture dest) 57 { 58 if (this.material == null) 59 { 60 this.material = new Material(Shader.Find("Hidden/Color Extractor")); 61 this.referenceColorId = Shader.PropertyToID("_ReferenceColor"); 62 this.referenceColorThresholdId = Shader.PropertyToID("_ReferenceColorThreshold"); 63 } 64 65 if (this.queryResultTexture == null) 66 { 67 this.queryResultTexture = new Texture2D(1, 1); 68 } 69 70 var scaledSize = this.level0TextureSize; 71 var previousLevelTexture = RenderTexture.GetTemporary(scaledSize, scaledSize); 72 73 this.material.SetColor(this.referenceColorId, this.referenceColor); 74 this.material.SetFloat(this.referenceColorThresholdId, this.referenceColorThreshold); 75 Graphics.Blit(src, previousLevelTexture, this.material, 0); // パス0...入力イメージから目的色を抽出し、結果をlevel0TextureSize四方のテクスチャに出力 76 77 while (scaledSize > 1) 78 { 79 scaledSize >>= 1; 80 81 var scaledTexture = RenderTexture.GetTemporary(scaledSize, scaledSize); 82 83 Graphics.Blit(previousLevelTexture, scaledTexture, this.material, 1); // パス1...抽出結果を段階的に1ピクセル四方まで縮小 84 RenderTexture.ReleaseTemporary(previousLevelTexture); 85 previousLevelTexture = scaledTexture; 86 } 87 88 var currentActiveTexture = RenderTexture.active; 89 90 RenderTexture.active = previousLevelTexture; 91 this.queryResultTexture.ReadPixels(new Rect(0.0f, 0.0f, 1.0f, 1.0f), 0, 0); 92 RenderTexture.active = currentActiveTexture; 93 RenderTexture.ReleaseTemporary(previousLevelTexture); 94 95 var queryResult = this.queryResultTexture.GetPixel(0, 0).r > 0.0f; // 抽出結果をTexture2Dに読み取り、その色を見て目的色が含まれていたか判定 96 97 if (queryResult) 98 { 99 if (!this.colorFound) 100 { 101 // 目的色が画面内に入った 102 this.colorFound = queryResult; 103 this.OnColorEnter(); 104 } 105 } 106 else 107 { 108 if (this.colorFound) 109 { 110 // 目的色が画面外へ出た 111 this.colorFound = queryResult; 112 this.OnColorExit(); 113 } 114 } 115 116 Graphics.Blit(src, dest); // 出力イメージは入力イメージをそのまま送る 117 } 118 119 private void OnColorEnter() 120 { 121 Debug.Log("Enter"); 122 123 if (this.enterSound != null) 124 { 125 AudioSource.PlayClipAtPoint(this.enterSound, this.cameraTransform.position); 126 } 127 } 128 129 private void OnColorExit() 130 { 131 Debug.Log("Exit"); 132 133 if (this.exitSound != null) 134 { 135 AudioSource.PlayClipAtPoint(this.exitSound, this.cameraTransform.position); 136 } 137 } 138}
  • シェーダーコード
Shader "Hidden/Color Extractor" { Properties { _MainTex ("Texture", 2D) = "white" {} _ReferenceColor ("Reference Color", Color) = (1.0, 0.0, 0.0, 1.0) _ReferenceColorThreshold ("Reference Color Threshold", Range(0.0, 1.0)) = 0.5 } SubShader { Cull Off ZWrite Off ZTest Always // 抽出パス Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _ReferenceColor; float _ReferenceColorThreshold; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag(v2f i) : SV_Target { // ピクセル色と目的色の距離がしきい値未満なら1、さもなくば0 float result = sign(max(_ReferenceColorThreshold - length(_ReferenceColor.rgb - tex2D(_MainTex, i.uv).rgb), 0.0)); return fixed4(result, result, result, 1.0); } ENDCG } // 縮小パス Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag(v2f i) : SV_Target { // 近傍4テクセルの中心からサンプリング、ピクセル色が0より大きければ(1のテクセルが1つ以上あれば)1、さもなくば0 float result = sign(tex2D(_MainTex, i.uv).r); return fixed4(result, result, result, 1.0); } ENDCG } } }
  • 実行時の様子

プレビュー

フレームデバッガ

投稿2017/11/17 18:23

編集2017/11/18 10:26
Bongo

総合スコア10807

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

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

0

Unityで音を出すにはAudioSourceを操作する必要があります。
シェーダーからAudioSourceにアクセスする方法が恐らくありませんので、「シェーダーのみで動作」というのはまず無理でしょう。

  1. シェーダーで色が存在しているかチェック
  2. 存在していたらシェーダー内の特定パラメータを特定の値にする
  3. C#スクリプトにて、一定周期でシェーダーの特定パラメータをチェック
  4. 該当の値だったら音を鳴らす

とやれば出来るかもしれませんが、結局遅延する可能性は高いです。
色ではなく図形だったらVuforia等AR技術が使える(フレームレートも然程問題無い)ので
そちらも検討してみてはいかがでしょうか。

投稿2017/11/16 02:00

sakura_hana

総合スコア11427

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問