TouchDesigner小ネタ集 02 ~水面反射~
ネットワーク全体図
TouchDesigner小ネタ集 01 ~音に反応した表現~
オーディオビジュアルの基本は音ハメ
(音が無いとなにもわからんな・・・)
ということで音に反応するsphereの作り方。
今回使用するOP(オペレーター)
音解析用
- Audio File In
- Audio Band
- Analyze
- Trigger
おまけ
- Audio Device Out
- Audio Spectrum
- Trail
ネットワーク全体図
全体の流れ
やりたいこととしては、音源ファイルから曲を再生し、低音域が鳴っている瞬間に3Dモデルのサイズを大きくしてあげたい・・・!
心臓が脈打つような何かが作れます。
音解析用は全て特定の周波数帯の音に反応したものを作るためのOPで、おまけに書かれているものは視覚的にデバッグしやすくするためのOPです。
- Audio File Inで音源を再生
- Audio Device Outで音が聴こえるように
- 音源の周波数を見て、不必要な周波数帯を削りたいのでAudio Spectrumでモニタリング
- Audio Bandで不必要な周波数帯を削る
- Analyzeで入力信号の最大値を取得
- Triggerで閾値を設定
不必要な周波数帯を削って低音成分だけ抽出
周波数ですが、ドラムだと大体こんな感じになります。
楽器 | 中心周波数 |
---|---|
バスドラ | 80 ~ 100Hz |
スネア | 800 ~ 1.2kHz |
シンバル | 220 ~ 12kHz |
なので、Audio Bandで100Hz以下を軽く持ち上げてあげて、それ以上の周波数はがっつり削ってあげます。
そうすることでAnalyzeを繋いでキックだけ抽出することができるようになります。
Analyzeは入力信号の最大値を取得するのに使用します。
FunctionをMaximumに設定することで最大値を取得することができます。
今回はTrailから最大値が0から1.5ぐらいを行き来することがわかりました。
なので、TriggerでTrigger Thresholdを0.9ぐらいに設定してあげることで低音が強くなった時に値が1を出力するようにします。
ここでコツとして、Attack Length、Sustain Level、Release Levelをを0に設定することで瞬間的に1を出力するようになります。
この辺もTrailを見ながら調整しましょう。
あとはTrailから出力される値をGeometoryのScaleなどに設定してあげれば完成です。
このままだと0から1で値が変動し、3Dモデルが見えなくなる瞬間が出てきてしまうのでお好みでMathのRangeで値を0.5から1とかにずらしてあげるといいかもしれません。
ランタイムで環境光の影響を反映させる方法
キーワード
- 環境光
- 環境マップ
- Cubemap
- Reflection Probe
skyboxを動的に変更すると見た目がおかしくなる!?
ランタイムでskyboxを変更したところ、環境光が適切に反映されず、一部見た目がおかしい結果となる症状に遭遇しました。
今回はSkyboxによる環境光の影響を適切に反映してあげることで見た目をよくしたいと思います。
Unityにおける環境マップ
UnityではSkyboxにSkybox用のシェーダーを割り当てたマテリアルを設定してあげることで特に追加の設定は必要なく、環境光をオブジェクトに反映させてげることができます。
変化がわかりやすいように新規でStandardShaderを割り当てたマテリアルを作成し、Metallic
とSmoothness
を1にしてパチンコ玉のような見た目にします。
DefaultのSkyboxではこのようにパチンコ玉にSkyboxが映り込んでいるのを確認することができます。
こちらのskyboxを違うskyboxに変更するとこういった結果が得られます。
これは全天球画像から作られたskyboxで、劇場の椅子がしっかりと映り込んでいます。
このようにEditor停止中では適切な結果を得ることができるのですが、skyboxから行われる環境光の計算はランタイムでは動的に切り替わりません。
このskyboxの切り替え時にどういった処理が走っているかというと、環境マップとしてcubemapの生成を行います。
ランタイムで更新(cubemapの再生成)するにはそれなりのコストがかかるため、エディタ停止中には動作し、ランタイムでは動作しないというのはUnityエディタでそのコストを吸収しているためだと思われます。
試しにLighting ウィンドウのAuto Generate
をオフにすると、環境光の影響が反映されなくなります。
そして、その右側にあるGenerate Lighting
をクリックすると、シーン名フォルダが生成され、その中にcubemapが生成されます。
つまりこれをEditor停止中に内部的に生成しているということです。
この時生成したcubemapをskybox変更時にEnviroment Reflections
のSource
をCustomに変更し、設定することで環境光の影響を受けるようにすることができます。
動的にcubemapを生成し、環境光の影響を適切に反映されるようにするには
今回はskybox変更時に、cubemapを生成し環境マップとして設定してあげる方向で修正を行いたいと思います。
cube mapの生成するにはいくつかの方法があります。
1つ目は、Reflection Probe
を配置する。2つ目は、Camera.RenderToCubemap()
を使用する方法です。
Reflection Probe
前者は非常に簡単でHierarchyからCreate
、Light
、Reflection Probe
と選択し、シーンに配置するだけです。
いくつかパラメータがあるのですが、Type
をRealtime、Reflesh Mode
をEvery frameにするとマイフレーム更新することができます。
マイフレームではかなりのコストがかかるので、ReflectionProbe.RenderProbe()
を呼ぶことで任意のタイミングで更新をかけることができます。
Camera.RenderToCubemap()
こちらはスクリプトを作成する必要があります。
参考までにcube mapを生成するスクリプト、生成したcube mapを設定するスクリプトを載せておきます。
RenderCubemap.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class RenderCubemap : MonoBehaviour { static public Cubemap Generate() { GameObject cubeMapCamera = new GameObject("CubemapCamera"); Cubemap cubemap = new Cubemap(64, TextureFormat.RGBA32, true); var camera = cubeMapCamera.AddComponent<Camera>(); cubeMapCamera.transform.position = Vector3.zero; cubeMapCamera.transform.rotation = Quaternion.identity; int layerMask = 0; camera.cullingMask = layerMask; camera.allowHDR = true; camera.RenderToCubemap(cubemap); DestroyImmediate(cubeMapCamera); RenderSettings.defaultReflectionMode = UnityEngine.Rendering.DefaultReflectionMode.Custom; RenderSettings.customReflection = cubemap; return cubemap; } }
CubemapTester.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CubemapTester : MonoBehaviour { [ContextMenu("Change Cubemap")] public void ChangeCubemap() { RenderSettings.defaultReflectionMode = UnityEngine.Rendering.DefaultReflectionMode.Custom; RenderSettings.customReflection = RenderCubemap.Generate(); } }
使い方はCubemapTester.csのChangeCubemap()を実行します。
このように、Reflection ProbeもしくはCamera.RenderToCubemap()を使用し、任意のタイミングで更新をかけてあげることで動的に環境光を反映させることができるようになりました。
まとめ
cubemapの解像度や、HDRの有効無効の設定が異なるので上記の2つの方法での結果が少しずつことなっていますが、自分のプロジェクトに最適な方法を使用することをお勧めします。
見た目にかかわる部分なので、各種パラメータいじってみて理想の絵作りを追及してください!
UnityでglTFを使用する方法
3Dモデルデータを用意
今回は検証用に3Dスキャナーでスキャンしたobjデータを用意。
フォルダの中には3つのファイルがあります。
- mesh.obj (3Dモデルデータ) - mesh.mtl (マテリアルデータ) - material0_basecolor.jpg (テクスチャデータ)
Blenderの下準備
KhronosGroup製のglTF-Blender-Exporter
レポジトリをダウンロードします。
\glTF-Blender-Exporter-master\scripts\addons\io_scene_gltf2
をBlenderのアドオンディレクトリに追加します。
BlenderのアドオンディレクトリはWindowsならC:\Program Files\Blender Foundation\Blender\2.79(バージョン番号)\scripts\addons
にあります。
次にBlenderを開き、アドオンを有効にします。
File
、User Preferences
と進み、Add-ons
タブを選択後、検索窓に「gltf」と入力するとimport-Export: glTF 2.0 format
が出てくるので有効にします。
さらにEnable experimental glTF export settings
も有効にします。
Blenderにobjデータをインポート
Blenderにobjデータをインポートします。
File
、Import
、obj
と進み、先ほどのobjデータを選択します。
真っ白な状態でインポートされました。
これからShaderの設定を行います。
「glTF Metallic Roughness」シェーダーをリンクします。
File
、Link
と進み、先ほどのglTF-Blender-Exporter-master
内にあるglTF-Blender-Exporter-master\pbr_node\glTF2.blend\NodeTree\glTF Metallic Roughness
を選択します。
これで「glTF Metallic Roughness」シェーダーが使用可能になります。
シェーダーの設定の前にレンダリングエンジンをCycles Render
に変更します。
その後、MateriaのついているMeshを選択し、Materialの設定変更を行います。
Surface
からUse Nodes
をクリックしたら、「glTF Metallic Roughness」シェーダー
を選択します。
設定項目の中からBase Color
、Image Texture
と進み、テクスチャファイルを選択します。
ここまで設定しても見た目は変わりませんが、ここで3Dモデルの表示モードをTexture
に切り替えることで適切に設定されていることを確認することができます。
glTF形式で出力
最後にglTF形式での出力を行います。
File
、Export
と進み、任意のディレクトを選択します。
出力されたデータを見てみると、3つのデータから構成されていることがわかります。
- TestGLTF.gltf (glTFデータ) - TestGLTF.bin (頂点データ) - material0_basecolor.jpg (テクスチャデータ)
Blenderでの作業はこれで以上です。
UnityでglTFファイルを読み込む
Unityを開き、Asset StoreからSktechfab for Unity
をインポートします。
もしくはsketchfabのUnityGLTFレポジトリからunitypackage形式でインストールすることも可能です。
インポートに成功すると‘Sketchfab‘タブが表示されるのでImport glTF
を選択するとglTF Importer
が表示されます。
Import file from disk
をクリックし、先ほどのglTFファイルを選択後Import
ボタンをクリックすることで処理が始まります。
少し待ってシーン上に3Dモデルが表示されれば成功です。
作品アーカイブ
2015
Spatial Jockey
音楽に合わせて空間をリアルタイムに制御するHMD使用ソフト。
MIDIコントローラーを使用し、ジョッキーがリアルタイムで空間をコントロール。
ローカルネットワークを構築し、複数台のHMDとの同期が可能です。
HMD装着者は音楽に合わせて変化する空間に身をゆだねる。
- Oculus DK2
- Leap Motion
- Kinect v2
- Unity
NOWHERE TEMPLE beta
「Spatial Jockey」をプラットフォームに、現代オカルティズム研究者バンギ・アブドゥル氏とのコラボレーション作品。
HMDを使用し、実践魔術の儀式空間(Astral plane)と変性意識を体験するプログラム「NOWHERE TEMPLE beta」のデモンストレーションを行いました。
- Oculus DK2
- Unity
映像作家udocorg (鵜戸庚司) 個展「れいより40℃も高熱」360°映像コンテンツ
個展開始直後に会場で360度動画の撮影を行い、スティッチ加工、編集を行いました。
- QBiC MS-1 × 4台 (360度カメラ)
- Gear VR
「ISLAND IS ISLANDS」 VRコンテンツ
ファッションデザイナー・中里周子×写真家・小林健太による二人展「ISLAND IS ISLANDS」VRコンテンツ作成。
オブジェクトの3Dモデル作成にPhoto Scanの技術を使用。
- Photo Scan
- Oculus DK2
- Unity
近距離通信アプリ 「AirMeet」
JPHACKS 2015応募作品
IBM賞受賞
- iBeacon通信
- node.js
- Swift
2016
「ようこそ、ISETAN宇宙支店へ ~わたしたちの未来の百貨店~」VRコンテンツ
ファッションブランド『ノリコナカザト』の伊勢丹TOKYO解放区企画店でのVRコンテンツ作成。
- Oculus DK2
- Unity
2017
ドット絵アーティスト・タカクラカズキ個展『有無ヴェルト』VRコンテンツ
- Vive Tracker × 2台
- HTC Vive
- Unity
chloma × STYLY HMD collection 展示/販売会 『chloma OS Umwelt』MRコンテンツ
- Photo Scan
- Microsoft HoloLens
- Unity
2018
Microsoft HoloLensアプリケーション 「chloma x STYLY HMD collection」
- Photo Scan
- Microsoft HoloLens
- Unity
Daydreamアプリケーション 「Nyoro The Snake & Seven Island」
- Daydream
- Unity
VTuber バートン
第1回VTuberハッカソン応募作品
HTC VIVE賞受賞
panora.tokyo
- Vive Tracker × 6個 (モーションキャプチャ)
- HTC Vive (モーションキャプチャ)
- iPhoneX (フェイシャルトラッキング)
- Unity
Faceモデル作成アプリ (制作中)
- iPhoneX
- Unity
リアルタイムIBLでARを現実空間に馴染ませる
IBLとは
Image based lighting (イメージ・ベースド・ライティング)
画像から照明条件を算出して、ライティングを行う技術です。
2000年前後に登場した技術で、映画製作にも使われ、実写合成ではミラーボールを使用して高精細な全天球画像を作成します。
Unity5はBeastからEnlightenに移行したため、簡単にIBLを設定できるようになりました。
IBLの設定方法
Editor上のWindow -> Lighting -> Settingsから設定することができます。
Skybox Materialを変更するだけです。
これでSkyboxが環境光に影響を与えるようになります。
試しに高精細なHDR全天球画像を配布しているHDR LabsのsIBL Archiveを使ってみます。
- Texture Shapeを2DからCubeに変更
- Create -> Materialを作成
- ShaderをSkybox/Cubemapに変更
- 1で変更したTextureをMaterialに設定
- LigtingのSettingsウィンドウのSkybox Materialに設定
結果
このように、先程のSphereと比べて赤みがかった色合いになりました。
SkyboxのCubemapとして使用した画像が影響しているのがわかります。
また、SphereのMaterial設定をMetalicを1、Smoothnessを1にすることでSkyboxの映り込みを確認することができます。
ARKit x IBL
さて、ここまではIBLの説明でしたが、「IBLとARKitを組み合わせて実在感をアップさせよう!」というのが今回の主題です。
現状のARコンテンツの大きな特徴は「キャラクターの実在感」だと個人的には考えています。
今までは平面ディスプレイ越しに接してきたキャラクターが、自分のいる現実世界に存在する、自分の机の上で踊っている・・・
しかし、ただそのまま現実空間に3Dモデルを表示するだけだと、3Dモデルが馴染まずにパキッった見え方になってしまいます。 そこで、今回はIBLを使用することで現実空間のライト状況を3Dモデルに反映し、キャラクターをより現実に溶け込ませてあげましょう。
大まかな流れ
- 現実世界の明るさに合わせて、Unity空間の明るさを調整
- カメラでキャプチャーした画像を基にIBLを行い、色合いの調整
- キャラクターが現実に馴染む!実在感が増す!
明るさ
ARKitにはUnityARAmbientというコンポーネントが用意されています。
これを使うことでカメラ越しに捉えた現実の明るさを基にUnity空間に存在するDirectional Lightのintensityを調整してくれます。
Lightコンポーネントが付いているGameObjectにアタッチするだけなので別途実装の必要はありません。
色合い (IBL)
では、先程説明したIBLをARKitと組み合わせて使用したいと思います。
先に大まかな実装の流れを説明すると
- UnityARVideoコンポーネントが取得しているyCbCrのテクスチャを取得する
- 変換行列を用いてRGBに変換する (先程の説明で言う所のダウンロードしてきたHDR画像)
- 極座標変換を行い、Skyboxに設定する (IBL)
注意!
今回実装するIBLはあくまで擬似的なものです。
というのも端末のカメラで取得できるイメージは単眼カメラで撮影したもので、そこからSkyboxを生成するというのは実際不可能だからです。
なんとなく現実空間の色合いをキャラクターに反映させたいというのが目標なのでカメラで撮影した画像に極座標変換を行うことで無理やり全天球画像にしてしまいます。
カメラで撮影した画像の取得
ARFakeIBL.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.XR.iOS; public class ARFakeIBL : MonoBehaviour { [SerializeField] // ARKitで用意されているYUVMaterialを設定 private Material _yuvMat; [SerializeField] // 疑似IBLスカイボックス用マテリアルとして使用 private Material _yuvSkyboxMat; [SerializeField] private int _frameInterval; private int _frameCount; void Update() { if (_frameCount == _frameInterval) { Texture textureY = _yuvMat.GetTexture("_textureY"); Texture textureCbCr = _yuvMat.GetTexture("_textureCbCr"); _yuvSkyboxMat.SetTexture("_TextureY", textureY); _yuvSkyboxMat.SetTexture("_TextureCbCr", textureCbCr); DynamicGI.UpdateEnvironment(); _frameCount = 0; } else { _frameCount++; } } }
ARFakeIBLスクリプトを適当なGameObjectにアタッチします。
このスクリプトはARKitで使用しているYUVMaterialが持つyCbCRのテクスチャを今回自作するSkybox用シェーダーに受け渡しする役割を持ちます。
yuvSkyboxMatには後で説明するIBLSkyboxシェーダーから作成したマテリアルを設定してください。
Update内で処理は行っていますが、負荷との相談でframeIntervalを20ぐらいに設定しておくと良いと思います。
IBL用Skyboxシェーダー
IBLSkybox.shader
Shader "Skybox/IBLSkybox" { Properties { _TextureY("TextureY", 2D) = "white" {} _TextureCbCr("TextureCbCr", 2D) = "black" {} } SubShader { Tags { "RenderType"="Background" "Queue"="Background" } Pass { ZWrite Off Cull Off Fog { Mode Off } CGPROGRAM #pragma fragmentoption ARB_precision_hint_fastest #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #define PI 3.141592653589793 struct appdata { float4 position : POSITION; float3 texcoord : TEXCOORD0; }; struct v2f { float4 position : SV_POSITION; float3 texcoord : TEXCOORD0; }; sampler2D _TextureY; sampler2D _TextureCbCr; static const float4x4 ycbcrToRGBTransform = float4x4( float4(1.0, +0.0000, +1.4020, -0.7010), float4(1.0, -0.3441, -0.7141, +0.5291), float4(1.0, +1.7720, +0.0000, -0.8860), float4(0.0, +0.0000, +0.0000, +1.0000) ); inline float2 ToRadialCoords(float3 coords) { float3 normalizedCoords = normalize(coords); float latitude = acos(normalizedCoords.y); float longitude = atan2(normalizedCoords.z, normalizedCoords.x); float2 sphereCoords = float2(longitude, latitude) * float2(0.5/UNITY_PI, 1.0/UNITY_PI); return float2(0.5,1.0) - sphereCoords; } v2f vert (appdata v) { v2f o; o.position = UnityObjectToClipPos (v.position); o.texcoord = v.texcoord; return o; } half4 frag (v2f i) : COLOR { float2 uv = ToRadialCoords(i.texcoord); float y = tex2D(_TextureY, uv).r; float4 ycbcr = float4(y, tex2D(_TextureCbCr, uv).rg, 1.0); return mul(ycbcrToRGBTransform, ycbcr); } ENDCG } } }
ToRadialCoords関数でuv座標を極座標変換しています。
この関数自体はビルトインシェーダーのSkybox/Panoramicの中で行われている変換処理と同一のものです。
そして、変換された座標からyCbCrテクスチャ上の色をフェッチし、RGBに変換して返すといったシンプルなシェーダーになっています。
RGBに変換するための行列ですが、一般的に使用されるyCbCr → RGB変換のための値ではうまくいきませんでした。
なので素直にARKitで用意されているUnlit/ARCameraShader内に記述されている変換行列をそのまま使用しています。
最後にこのシェーダーから作成したマテリアルをLigting/SettingsウィンドウのSkybox Materialに設定します。
結果
現実の状況を3Dモデルに反映することで、そのキャラクターがよりその場に存在する実在感が生まれました。
How to fix Unity ARKit Remote
この記事は
を英語での解説が欲しいとリクエストがあったので英語でまとめたものです。
要点だけまとめてあるので、詳しい解説はリンク先のものを見てください。
It does not work properly!
What is wrong?
Since the data transmitted can not be handled on the receiving side, the sending side can not send it!
This problem occurs because iPhoneX has a high resolution.
How to fix
Using the MemoryStream, compress the data by DeflateStream before sending it on the sending side.
The point is, ...
- Compress the data on the data sending side .
- Decompress the data on the data receiving side.
Compress & Decompress
/// <summary> /// Compress using deflate. /// </summary> /// <returns>The byte compress.</returns> /// <param name="source">Source.</param> public static byte[] ConvertByteCompress(byte[] source) { using (MemoryStream ms = new MemoryStream()) using (DeflateStream compressedDStream = new DeflateStream(ms, CompressionMode.Compress, true)) { compressedDStream.Write(source, 0, source.Length); compressedDStream.Close(); byte[] destination = ms.ToArray(); Debug.Log(source.Length.ToString() + " vs " + ms.Length.ToString()); return destination; } } /// <summary> /// Decompress using deflate. /// </summary> /// <returns>The byte decompress.</returns> /// <param name="source">Source.</param> public static byte[] ConvertByteDecompress(byte[] source) { using (MemoryStream input = new MemoryStream(source)) using (MemoryStream output = new MemoryStream()) using (DeflateStream decompressedDstream = new DeflateStream(input, CompressionMode.Decompress)) { decompressedDstream.CopyTo(output); byte[] destination = output.ToArray(); Debug.Log("Decompress Size : " + output.Length); return destination; } }
How to use
UnityRemoteVideo.cs
public void OnPreRender() { ~ abridgement ~ //connectToEditor.SendToEditor (ConnectionMessageIds.screenCaptureYMsgId, YByteArrayForFrame(1-currentFrameIndex)); connectToEditor.SendToEditor(ConnectionMessageIds.screenCaptureYMsgId, ByteConverter.ConvertByteCompress(YByteArrayForFrame(1 - currentFrameIndex))); //connectToEditor.SendToEditor (ConnectionMessageIds.screenCaptureUVMsgId, UVByteArrayForFrame(1-currentFrameIndex)); connectToEditor.SendToEditor(ConnectionMessageIds.screenCaptureUVMsgId, ByteConverter.ConvertByteCompress(UVByteArrayForFrame(1 - currentFrameIndex))); }
ARKitRemoteConnection.cs
void ReceiveRemoteScreenYTex(MessageEventArgs mea) { if (!bTexturesInitialized) return; //remoteScreenYTex.LoadRawTextureData(mea.data); remoteScreenYTex.LoadRawTextureData(ByteConverter.ConvertByteDecompress(mea.data)); remoteScreenYTex.Apply (); UnityARVideo arVideo = Camera.main.GetComponent<UnityARVideo>(); if (arVideo) { arVideo.SetYTexure(remoteScreenYTex); } } void ReceiveRemoteScreenUVTex(MessageEventArgs mea) { if (!bTexturesInitialized) return; //remoteScreenUVTex.LoadRawTextureData(mea.data); remoteScreenUVTex.LoadRawTextureData(ByteConverter.ConvertByteDecompress(mea.data)); remoteScreenUVTex.Apply (); UnityARVideo arVideo = Camera.main.GetComponent<UnityARVideo>(); if (arVideo) { arVideo.SetUVTexure(remoteScreenUVTex); } }
UnityRemoteVideo.cs and ARKitRemoteConnection.cs are scripts prepared beforehand by Unity ARKit Plugin.
The compression & expansion processing is the script that I created this time.
Caution!
In order to use CopyTo method of DeflateStream class I am using .NET Framework 4.6 instead of .NET Framework 3.5.
Building the Unity ARKit Remote scene uses .NET Framework 3.5. Please change to .NET Framework 4.6 after building and run Editor.
Run
You are now able to use it successfully.
If there is something you do not understand please comment:)