- 投稿日:2019-12-24T23:39:11+09:00
AmplifyShaderEditorのPostProcessであそぶ
はじめに
ある日「カメラエフェクト作りたいな・・・」と思いAmplifyShaderEditor(以下ASE)でPostProcessが作れると知り、ASE自体ぺろぺろした程度しか知識はありませんが、調べたことをメモしていきます。
動作環境
Windows10
Unity 2019.2.17f1
AmplifyShaderEditor 1.7.5準備
①新規でシェーダーを作る
ASEポスプロ用シェーダーはCreate→AmplifyShader→Legacy→PostProcessの順で新規作成できます。ここでは仮に「pp_01」と名付けました
あと、マテリアルも作成しシェーダーをpp_01に設定しておきましょう。
②Cameraにマテリアルをつけたい!
とりあえずカメラにシェーダーをかけないとプレビューできないので、ASEが丁寧に用意してくれたPostProcessExampleをカメラにAddComponentします。
そして先ほど作ったマテリアルをPostProcessMatに設定しましょう。スクリプトをアクティブにして準備完了です!
シェーダーを作る
今は画面が真っ白なので、ここから画面を映していきます。
ダランディさんが制作したキョンシー娘の女の子を使わせていただきました!ダランディさんありがとうございます!!!!
モデリングしたhttps://t.co/78qmjZ0RZQ pic.twitter.com/LJL6GmcTXi
— ダランディ (@darandy) December 22, 2019とりあえず画面を映す
Template parameterを用意しましょう、そこからTextureSampleをつなぎ描画させます。
あそぶ
PixelateUVでモザイクっぽさをだす
Texture CoordinatesからPixelateUVをつなぎ、モザイク化させてみました、PixelateUVのPixel XとYにfloatノードをつなげば解像度を変えれます。
にしても、これはなかなか遊べそう・・・。
階調化させる
TextureSampleからPosterizeをつなぎ階調化させました、PowerにFloatを繋ぎ階調化数を変えれます。
ここまできたらアレしかない
PosterizeからGlayScale(白黒化)ノードを繋ぎ、HSV to RGBで「Hue」「Saturation」をいじり緑色にします、ゲームボーイっぽさが出ました。
まわす
まわしました(?)TextureCoordinatesからRotator(回転)を繋いでRotatorノードのAnchorにVector2を繋ぎ中心点をX0.5、Y0.5に設定します。
TimeParameterとFloatをMultipyして速度を調節します。
おわりに
ぎこちない文章で申し訳なかったのですが、参考にしてもらえればと思います。
みんなもポスプロで遊ぼう!
- 投稿日:2019-12-24T22:23:56+09:00
Unity2019の2D Animationでいらすとやを走らせた
はじめに
前回の記事ではUnity2018の2D Animationでいらすとやの素材を使ってスプライトアニメーションを作りました。
1年経ってUnity2019.3が出て、2D Animationもとうとう preview が外れたらしいので改めてまとめました。
今回は前回より少し頑張って走るアニメーションです。
(色々荒いのが見えてますが、これが私の限界です...)なお環境は
- Unity 2019.3
- 2D Animation 3.0.8
です。
作り方
2D Animationを[Window] > [Package Manager]からimportしてください。
動かしたい画像をUnityのプロジェクトにドラッグ&ドロップなどで追加します。
その際、inspectorから画像のTexture TypeがSprite (2D and UI)
になるようにします。inspectorの[Sprite Editor]を押すと編集windowが開きます。
window左上のプルダウンからSkinning Editor
を選択します。[Create Bones] から設定したいようにボーンを追加していきます。
また、ボーンを消したい場合は ⌘+[delete] で消せます。(windowsなら ctrl+deleteかな?)画像の輪郭線を
Geometry
から設定します。[Auto Geometry] を選択し、3つのパラメータをいじって[Generate For Selected]を押すと、輪郭が設定できます。
Weights
にチェックを入れておくと同時に設定してくれるようです。値をいじって再生成して合わせるなり、[Edit Geometry]から自分で編集するなりして下さい。
ちなみにですが、この時点でボーンを動かしてみることが出来ます。
また、編集も⌘+[z]でUndoできるなど、機能のパワーアップが見られます。
ここから先はあまり変わっておらず、空のGameObjectをシーンに追加して、先ほど編集した画像を空のGameObjectにドラッグします。
そして、inspectorの [Add Component] から
Sprite Skin
を追加して、[Create Bones] すればOKです。しかし、
鞄を持った腕を動かすとあられもない姿になります。
一枚絵にボーンを設定しているので、肩のボーンを動かすと顔にも影響してしまいます。おまけに鞄が体にめり込んでしまっています。
流石にこの辺りが一枚絵で行う限界のようです。走るアニメーションのためパーツ分けを行いました。
(2D Animationの説明はここでほとんど終わりになります)クオリティアップのために
パーツ分け
パーツ分けには PhotoShop を使いました。ほぼ初めて使うツールでしたが、なんとかなりました。
無料でやるなら GIMP などで作業することになると思います。パーツ分けは Live2Dのページを参考にしました。
基本的には腕や顔など切り分けたい箇所が含むように四角で選択。選択部分を別レイヤにコピーして、腕など目的の部分以外を消しゴムツールで削いでいく。そして全パーツできたら1枚の画像に合わせるという手順で作りました。(もっと確立された方法があると思いますが)
今回は諦めましたが、さらにクオリティを高めるなら、
- 腕や胴の接合部は丸くする
- 角ばってると動かした時に不自然見える
- 消し残しを徹底的に消す
- あとでUnityでSpriteを分ける時に、ゴミのピクセルも拾ってしまう
必要もあるかと思いました。
Unityにインポート
PhotoShopで出力した画像をUnityのプロジェクトに持ってきます。
そして1枚の画像を複数のSpriteに分けるのですが、Sprite ModeをMultiple
にしないとSprite Editorの[Slice]が選択できないので注意してください。前述のゴミピクセルがあるとここのSpriteが必要以上に増えます。
(不要な領域は⌘+[delete]で消せます)ボーンの設定もこの時行いますが、一枚絵では無くなったので設定するボーンも減ります。基本的には
頭など関節の無いSprite
- ボーンを1本だけ設定する
腕など関節を設定したいSprite
- ボーンを設定する
- GeometryとWeightも設定する
ことになると思います。関節のないSpriteでも、ボーンを設定しないと回転中心が画像の真ん中になってしまうので、ボーンを設定した方が意図通りに回転させやすいです。
顔だとボーンを設定すれば首中心に回転させられますが、無ければ鼻のあたりが回転の中心になってしうといった具合です。組み立てる
前回同様、空のGameObjectに画像を設定します。しかし今回はSpriteが分かれているので、Spriteの数だけ追加してやる必要があります。
Sprite Skin
の設定とボーンの作成もこの時にやっておきましょう。この時は人の形をしていません。自力で人の形に直す必要があります。この時ボーンの親子関係も考慮した階層にした方があとで楽かもしれません。
この時点で Prefab 化しておくのが無難でしょう。
この時少し混乱したのが、先ほど
Sprite Skin
を設定したGameObjectを回転させてもボーンの回転にはならないということです。ボーンの末端を軸に動かしたいのであれば、子の Transform を回転させてください。これで肩を回しても顔などに影響しなくなりました。
Sorting Order
しかし、腕が体にめり込む問題が依然としてあります。
Sprite Skin
には Order in Layer の設定があり、Sprite同士の前後関係を定義できます。整数で設定しますが、数が大きいほど前に来ます。負の値も設定可能です。デフォルトでは
default
のLayerにSpriteが属しているので、そのLayerの中での前後関係になります。別途Layerも追加定義すれば、レイヤ同士の前後関係も定義できるようです。胴を基準に
0
として以下のように設定しました。
- 右手と右足:-1
- 胴:0
- 顔と左足:1
- 左手と鞄:2
あとはUnityでキーフレームアニメーションを作れば最初のアニメーションの完成です。
感想
最近のスマホゲームのアニメーションを作ってる人たちは凄まじいなと思いました。
- 投稿日:2019-12-24T21:25:38+09:00
パスに沿ってなめらかに一定速度でオブジェクトを移動させる実装の解説
まえがき
問題
ゲームだと、パスに沿ってキャラを移動させたい時があります。
ただ、単純に実装すると速度がバラバラになってしまいます。目的
今回は曲線でもだいたい一定の速度で動かせるようにする実装を解説します。
UnityならDoTweenのDoPathやCinemachineのCinemachinePathを使用すれば実装しなくても可能ですが、ベジェ曲線のみで、他の曲線を使うことはできません。
自分で実装すれば、好きな曲線を使えます。
ただ、今回は解説のため、ベジェ曲線を使います。https://ja.wikipedia.org/wiki/ベジェ曲線
Vector3 Bezier3(float t, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4) { var d = 1 - t; return d * d * d * p1 + 3 * d * d * t * p2 + 3 * d * t * t * p3 + t * t * t * p4; }参考
この記事はCinemachineのCinemachinePathBaseを参考に作っています。
github解説
単純な実装
これはベジェ曲線の引数tに$Time.t$を渡すだけの実装です。
明らかに速度がおかしいです。
0-0.1と0.5-0.6の移動距離が違うのと、
スタート位置から1つ目のパスまで1秒、そこからゴールまで1秒かけているのが原因です。void Update() { var t = Time.time; var indexA = Mathf.FloorToInt(Mathf.Min(Paths.Length - 1, t)); var indexB = Mathf.FloorToInt(Mathf.Min(Paths.Length - 1, indexA+1)); if (indexA == indexB) return; transform.position = CalcPos(t); } Vector3 CalcPos(float t) { var indexA = Mathf.FloorToInt(Mathf.Min(Paths.Length - 1, t)); var indexB = Mathf.FloorToInt(Mathf.Min(Paths.Length - 1, indexA+1)); return Bezier3( t-indexA, Paths[indexA].Pos, Paths[indexA].Pos + Paths[indexA].Tangent, Paths[indexB].Pos - Paths[indexB].Tangent, Paths[indexB].Pos); }改善した物
この問題はtを0-ベジェ曲線の長さで扱えれば速度をだいたい一定にすることができます。
やっていることは、tを少しずつ動かして、進んだ距離を測る。
進んだ距離からtを返すテーブルを作るという事です。以上です。
こうすると、距離からtに変換する関数を作成できるので、だいたい一定の速度で移動できるようになります。
コード
[SerializeField] int Segment; [SerializeField] PathContainer Paths; float PathLength; //セグメントの総数 int NumKeys; float[] DistanceToTArray; float DistanceStepSize; void Start() { Build(); } void Build() { PathLength = 0; NumKeys = (Paths.Length-1) * Segment+1; var tToDistance = CalcTToDistance(); DistanceToTArray = CalcDistanceToT(tToDistance); } void Update() { transform.position = CalcPos(DistanceToT(Time.time*PathLength/2)); } //距離からtに変換 float DistanceToT(float distance) { float d = distance / DistanceStepSize; int index = Mathf.FloorToInt(d); if(index>=DistanceToTArray.Length-1)return DistanceToTArray[DistanceToTArray.Length-1]; float t = d - index; return Mathf.Lerp(DistanceToTArray[index], DistanceToTArray[index+1], t); } //tをSegmentに分割して進んだ距離を配列に入れて返す float[] CalcTToDistance() { var tToDistance = new float[NumKeys]; var pp = Paths[0].Pos; float t = 0; for (int n = 1; n < NumKeys; n++) { t += 1f / Segment; Vector3 p = CalcPos(t); float d = Vector3.Distance(pp, p); PathLength += d; pp = p; tToDistance[n] = PathLength; } return tToDistance; } //距離をSegmentに分割してその位置のtを配列に入れて返す float[] CalcDistanceToT(float[] tToDistance) { var distanceToT = new float[NumKeys]; distanceToT[0] = 0; DistanceStepSize = PathLength/(NumKeys-1); float distance = 0; int tIndex=1; for (int i = 1; i < NumKeys; i++) { distance += DistanceStepSize; var d = tToDistance[tIndex]; while (d < distance && tIndex < NumKeys - 1) { tIndex++; d = tToDistance[tIndex]; } var prevD = tToDistance[tIndex - 1]; float delta = d - prevD; float t = (distance - prevD) / delta; distanceToT[i] = (1f/Segment)*(t + tIndex - 1); } return distanceToT; } Vector3 CalcPos(float t) { var indexA = Mathf.FloorToInt(Mathf.Min(Paths.Length - 1, t)); var indexB = Mathf.FloorToInt(Mathf.Min(Paths.Length - 1, indexA+1)); return Bezier3( t-indexA, Paths[indexA].Pos, Paths[indexA].Pos + Paths[indexA].Tangent, Paths[indexB].Pos - Paths[indexB].Tangent, Paths[indexB].Pos); }
あとがき
以上です。
動作するコードはこちらです。
https://github.com/nakajimakotaro/PathSmoothMove
- 投稿日:2019-12-24T19:32:30+09:00
AnimationCurveを使わずにVRMをC#で走らせてみる【Unity】
これは Unity Advent Calendar 2019 の25日目の記事です。
昨日は @youri_ssk さんによる 2.5Dキャラクターアニメーション - Mirror Animation Playable でした。Animation Curveを使わずにC#だけでVRMを走らせてみる
Animation Curveを使えば、便利なGUIで3Dアニメーションを作ることができます!
そう!普通の人ならAnimation Curveを使いましょう!!(もちろん、BlenderとかUnity外のツールでもいいです)
しかし!Qiita読者の皆さんは プログラマー なんです!
プログラマーだったらプログラミングで3Dアニメーションを作りたいですよね!
というわけで、VRMアバターをC#で動かしてみます!
最終的にはこんな感じのアニメーションが作れました。
動作環境
以下の環境でやりました。
- Windows 10 64bit
- Unity 2019.2.11f1
- UniVRM 0.54.0
初期状態
とりあえずVRMファイルをシーンに読み込んでみます。
今回は 今里尚吾 くんにご協力いただきます!(VRoid Studioで作りました!)
Hierarchyを見てみたらこんな感じでした!
股関節を30度に曲げてみる
まずは、股関節を曲げてみましょう。
以下のスクリプトを作成します。
using System; using UnityEngine; public class PendulumRunning : MonoBehaviour { // transformを保管する変数 private Transform cHips; private Transform lUpperLeg; private Transform rUpperLeg; void Start() { // 腰のtransformを取得 cHips = transform.Find("Root") .Find("J_Bip_C_Hips"); // 股関節のtransformを取得 lUpperLeg = cHips.Find("J_Bip_L_UpperLeg"); rUpperLeg = cHips.Find("J_Bip_R_UpperLeg"); } void FixedUpdate() { // 脚を30°傾ける lUpperLeg.rotation = Quaternion.AngleAxis(-30.0f, Vector3.right); rUpperLeg.rotation = Quaternion.AngleAxis(30.0f, Vector3.right); } }作成できたら、シーン内のVRMにアタッチします。
脚が前後に開きました!
振り子のように足を揺らしてみる
せっかくなので、アニメーションさせたいですよね。
ということで、振り子のように揺らしてみます!
void FixedUpdate() { // 1秒周期の振り子を用意する float pendulum = (float)Math.Sin(Time.time * Math.PI); // 股関節を右軸(x軸)を中心に±60°幅で揺らす lUpperLeg.localRotation = Quaternion.AngleAxis(-60.0f * pendulum, Vector3.right); rUpperLeg.localRotation = Quaternion.AngleAxis(60.0f * pendulum, Vector3.right); }いいかんじ!
膝を曲げてみる
膝も曲げてみたくなったので、
LowerLeg
を追加してみました。あと係数とかを微調整したのがこちらです。public class PendulumRunning : MonoBehaviour { private Transform cHips; private Transform lUpperLeg; private Transform rUpperLeg; private Transform lLowerLeg; private Transform rLowerLeg; void Start() { cHips = transform.Find("Root") .Find("J_Bip_C_Hips"); lUpperLeg = cHips.Find("J_Bip_L_UpperLeg"); rUpperLeg = cHips.Find("J_Bip_R_UpperLeg"); // 膝のtransformを取得 lLowerLeg = lUpperLeg.Find("J_Bip_L_LowerLeg"); rLowerLeg = rUpperLeg.Find("J_Bip_R_LowerLeg"); } void FixedUpdate() { float pendulum = (float)Math.Sin(Time.time * Math.PI); // 股関節の動きを少し変更 lUpperLeg.localRotation = Quaternion.AngleAxis(-60.0f * pendulum - 20.0f, Vector3.right); rUpperLeg.localRotation = Quaternion.AngleAxis(60.0f * pendulum - 20.0f, Vector3.right); // 膝を揺らす lLowerLeg.localRotation = Quaternion.AngleAxis(-60.0f * pendulum + 60.0f, Vector3.right); rLowerLeg.localRotation = Quaternion.AngleAxis(60.0f * pendulum + 60.0f, Vector3.right); } }膝も曲がりました。
全身動かしてみる
同じ要領で、全身の関節の動かしてみます。色々調整したら、以下のようなコードになりました。
public class PendulumRunning : MonoBehaviour { private Transform cHips; private Transform lUpperLeg; private Transform lLowerLeg; private Transform rUpperLeg; private Transform rLowerLeg; private Transform cSpine; private Transform cChest; private Transform cUpperChest; private Transform lShoulder; private Transform lUpperArm; private Transform lLowerArm; private Transform rShoulder; private Transform rUpperArm; private Transform rLowerArm; void Start() { // 全身の関節のtransformを取得 cHips = transform.Find("Root") .Find("J_Bip_C_Hips"); lUpperLeg = cHips.Find("J_Bip_L_UpperLeg"); lLowerLeg = lUpperLeg.Find("J_Bip_L_LowerLeg"); rUpperLeg = cHips.Find("J_Bip_R_UpperLeg"); rLowerLeg = rUpperLeg.Find("J_Bip_R_LowerLeg"); cSpine = cHips.Find("J_Bip_C_Spine"); cChest = cSpine.Find("J_Bip_C_Chest"); cUpperChest = cChest.Find("J_Bip_C_UpperChest"); lShoulder = cUpperChest.Find("J_Bip_L_Shoulder"); lUpperArm = lShoulder.Find("J_Bip_L_UpperArm"); lLowerArm = lUpperArm.Find("J_Bip_L_LowerArm"); rShoulder = cUpperChest.Find("J_Bip_R_Shoulder"); rUpperArm = rShoulder.Find("J_Bip_R_UpperArm"); rLowerArm = rUpperArm.Find("J_Bip_R_LowerArm"); } void FixedUpdate() { // 速度を3倍に変更 float pendulum = (float)Math.Sin(Time.time * Math.PI * 3.0f); // 脚を揺らす lUpperLeg.localRotation = Quaternion.AngleAxis(-60.0f * pendulum - 20.0f, Vector3.right); rUpperLeg.localRotation = Quaternion.AngleAxis(60.0f * pendulum - 20.0f, Vector3.right); lLowerLeg.localRotation = Quaternion.AngleAxis(-30.0f * pendulum + 60.0f, Vector3.right); rLowerLeg.localRotation = Quaternion.AngleAxis(30.0f * pendulum + 60.0f, Vector3.right); // 腰にひねりを加える cHips.localRotation = Quaternion.AngleAxis(10.0f * pendulum, Vector3.up) * Quaternion.AngleAxis(10.0f, Vector3.right); // 胸は腰と反対にひねる cChest.localRotation = Quaternion.AngleAxis(-10.0f * pendulum, Vector3.up); cUpperChest.localRotation = Quaternion.AngleAxis(-20.0f * pendulum, Vector3.up); // 腕を揺らす lUpperArm.localRotation = Quaternion.AngleAxis(60.0f * pendulum + 30.0f, Vector3.right) * Quaternion.AngleAxis(70.0f, Vector3.forward); rUpperArm.localRotation = Quaternion.AngleAxis(-60.0f * pendulum + 30.0f, Vector3.right) * Quaternion.AngleAxis(-70.0f, Vector3.forward); lLowerArm.localRotation = Quaternion.AngleAxis(-60.0f * pendulum + 60.0f, Vector3.up); rLowerArm.localRotation = Quaternion.AngleAxis(-60.0f * pendulum - 60.0f, Vector3.up); } }だんだんそれっぽくなってきました。
ただ、重心が上下しないのはちょっと違和感がありますね。
重心を上下させてみた
重心も上下させてみます。
public class PendulumRunning : MonoBehaviour { private Transform cHips; private Transform lUpperLeg; private Transform lLowerLeg; private Transform rUpperLeg; private Transform rLowerLeg; private Transform cSpine; private Transform cChest; private Transform cUpperChest; private Transform lShoulder; private Transform lUpperArm; private Transform lLowerArm; private Transform rShoulder; private Transform rUpperArm; private Transform rLowerArm; // 腰の初期位置を保管する変数 private Vector3 firstHipsPosition; void Start() { cHips = transform.Find("Root") .Find("J_Bip_C_Hips"); lUpperLeg = cHips.Find("J_Bip_L_UpperLeg"); lLowerLeg = lUpperLeg.Find("J_Bip_L_LowerLeg"); rUpperLeg = cHips.Find("J_Bip_R_UpperLeg"); rLowerLeg = rUpperLeg.Find("J_Bip_R_LowerLeg"); cSpine = cHips.Find("J_Bip_C_Spine"); cChest = cSpine.Find("J_Bip_C_Chest"); cUpperChest = cChest.Find("J_Bip_C_UpperChest"); lShoulder = cUpperChest.Find("J_Bip_L_Shoulder"); lUpperArm = lShoulder.Find("J_Bip_L_UpperArm"); lLowerArm = lUpperArm.Find("J_Bip_L_LowerArm"); rShoulder = cUpperChest.Find("J_Bip_R_Shoulder"); rUpperArm = rShoulder.Find("J_Bip_R_UpperArm"); rLowerArm = rUpperArm.Find("J_Bip_R_LowerArm"); // 腰の初期値を取得する firstHipsPosition = cHips.localPosition; } void FixedUpdate() { float pendulum = (float)Math.Sin(Time.time * Math.PI * 3.0f); lUpperLeg.localRotation = Quaternion.AngleAxis(-60.0f * pendulum - 20.0f, Vector3.right); rUpperLeg.localRotation = Quaternion.AngleAxis(60.0f * pendulum - 20.0f, Vector3.right); lLowerLeg.localRotation = Quaternion.AngleAxis(-30.0f * pendulum + 60.0f, Vector3.right); rLowerLeg.localRotation = Quaternion.AngleAxis(30.0f * pendulum + 60.0f, Vector3.right); cHips.localRotation = Quaternion.AngleAxis(10.0f * pendulum, Vector3.up) * Quaternion.AngleAxis(10.0f, Vector3.right); cChest.localRotation = Quaternion.AngleAxis(-10.0f * pendulum, Vector3.up); cUpperChest.localRotation = Quaternion.AngleAxis(-20.0f * pendulum, Vector3.up); lUpperArm.localRotation = Quaternion.AngleAxis(60.0f * pendulum + 30.0f, Vector3.right) * Quaternion.AngleAxis(70.0f, Vector3.forward); rUpperArm.localRotation = Quaternion.AngleAxis(-60.0f * pendulum + 30.0f, Vector3.right) * Quaternion.AngleAxis(-70.0f, Vector3.forward); lLowerArm.localRotation = Quaternion.AngleAxis(-60.0f * pendulum + 60.0f, Vector3.up); rLowerArm.localRotation = Quaternion.AngleAxis(-60.0f * pendulum - 60.0f, Vector3.up); // 周期が半分の振り子を用意する float halfPendulum = (float)Math.Sin(Time.time * Math.PI * 3.0f * 2.0f); // 腰の位置を上下させる cHips.localPosition = firstHipsPosition + new Vector3(0.0f, 0.04f * halfPendulum, 0.0f); } }ちょ、ちょっとだけ、マシになったかな?
ゲシュタルト崩壊してきたので、このあたりで止めておきます。
一応、AnimationCurveは使わずにC#だけでアニメーションを作成することができました!
AnimationClipに保存してみる
せっかくだからAnimationClipに保存してみます。
Unityの GameObjectRecorder というAPIを使えば、シーン実行中のアニメーションをAnimationClipに保存できます。
以下のスクリプトを作成し、シーン内のVRMにアタッチしてください。
using UnityEngine; using UnityEditor.Animations; public class RecordTransformHierarchy : MonoBehaviour { public AnimationClip clip; private GameObjectRecorder m_Recorder; void Start() { m_Recorder = new GameObjectRecorder(gameObject); m_Recorder.BindComponentsOfType<Transform>(gameObject, true); } void LateUpdate() { if (clip == null){ return; } m_Recorder.TakeSnapshot(Time.deltaTime); } void OnDisable() { if (clip == null){ return; } if (m_Recorder.isRecording) { m_Recorder.SaveToClip(clip); } } }適当なフォルダーに空のAnimationClipファイルを作成します。
ここでは仮に
pendulum-running
というファイル名にします。これを
RecordTransformHierarchy
のClip
に割り当てます。これでシーンを実行すれば、AnimationClipに動きが保管されます!
FBX Exporter等を使えば、FBXに変換することもできますね!
さいごに
C#でもAnimationClipを作成することができました!
そのことに、果たして意味があるかはわかりませんが、個人的には
Quaternion
の勉強をできたのが収穫です。本記事作成にあたり、以下の記事を参考にさせていただきました。ありがとうございました。
- 投稿日:2019-12-24T17:34:35+09:00
【Unity】ParticleSytemのCustomDataを使うときの処理をcgincにまとめる
開発環境 : Unity2018.4.3f1
こちらの記事ではUnityのShaderでParticleSystemのCustomDataを利用するときの処理を共通化についてになります。
具体的にはテクスチャのUVスクロールをCustomDataで実現する場合になります。cgincで共通のコードを定義
UnityのShaderを書く際に重複コードになりそうな処理をcgincにまとめておくことができます。
今回は次のようなコードを定義しました。#ifndef My_PARTICLE_INCLUDED #define My_PARTICLE_INCLUDED // CustomDataのプロパティ設定 #define SET_CUSTOM_DATA_PROPERTY(name) uniform int name##U; \ uniform int name##USwizzle; \ uniform int name##V; \ uniform int name##VSwizzle; // 該当のCustomDataを取得 #define GET_CUSTOM_DATA(name) half2(customData[name##U][name##USwizzle],customData[name##V][name##VSwizzle]) #endif
SET_CUSTOM_DATA_PROPERTY
は4つプロパティを定義している箇所をまとめています。
GET_CUSTOM_DATA
はプロパティで設定した値を使ってCustomDataから必要なパラメータを取得しています。Shaderで利用する
プロパティ定義する
[HideInInspector]_MainU ("_mainucoord", int) = 2 [HideInInspector]_MainUSwizzle ("_mainuswizzle", int) = 0 [HideInInspector]_MainV ("_mainvcoord", int) = 2 [HideInInspector]_MainVSwizzle ("_mainvswizzle", int) = 0cgincをSubShaderのPassに定義します。
#include "my_Particle.cginc"Passにプロパティを定義します。
ここでSET_CUSTOM_DATA_PROPERTY
を使います。SET_CUSTOM_DATA_PROPERTY(_Main);UVスクロール
バーテックスShaderでの処理を次のようになります。
customData1とcustomData2
はCustomDataのストリームです。half4x4 customData = { v.customCoords1, v.customCoords2, half4(0,0,0,0), half4(0,0,0,0)}; half2 mainCustomCoord = GET_CUSTOM_DATA(_Main); o.uv.xy = TRANSFORM_TEX(v.texcoords.xy,_MainTex) + mainCustomCoord;最後に
cgincに定義をしておけば複数のShaderで使い回すことができますので、共通化して利用できそうな処理があればまとめておくと良いです。
- 投稿日:2019-12-24T13:19:07+09:00
AzureKinectの赤外線画像をUnityで見る
概要
小ネタです。
AzureKinectから得られた赤外線画像をUnityで見れるようにしようとしたけど、
UnityのTextureに変換する所に一瞬つまづいたので、その解決策の忘備録も含めています。結果
以下のようにUnityで赤外線画像が見れるようになります。
#unity #AzureKinect
— unagi (@UnagiHuman) December 24, 2019
UnityでAzureKinectの赤外線画像を表示してみた。 pic.twitter.com/BD1X4PVpOVAzureKinectから赤外線画像を取得してUnityで見れる形にする
https://github.com/microsoft/Azure-Kinect-Sensor-SDK/issues/490
IRデータの取り扱い方は上のリンク先の投稿を参考にしました。IR画像のサイズはdepthのwidth,heightと同じで、データ的には16bitでIRの輝度が書かれています。
このデータをUnityのRenderTextureに変換するにはデータを変換する必要があります。
この変換はshader内でやってしまうのが一番速度が速いです。以下に、Unity上でKinectからIRデータを取得してUnityのRenderTextureに変換するコードを乗っけます。
また、あくまでサンプルなので簡潔に書いているため実用的なソースではありません。本来はAzureKinect側からデータを受け取る側を別スレッドにすべきだし、またKinectは最高30FPSなのでUpdateでデータの処理をするのは無駄なのでそこら辺の考慮もすべきです。
using UnityEngine; using UnityEngine.Experimental.Rendering; using Device = Microsoft.Azure.Kinect.Sensor.Device; /// <summary> /// IR画像取得サンプル /// </summary> public class TestIR : MonoBehaviour { /// <summary> /// ColorFormatをR8_UNormに設定したRenderTexture(width,heightはDepth画像に合わせる) /// </summary> [SerializeField] private RenderTexture _irTexture = null; [SerializeField] private Shader _unprojectIRshader = null; [SerializeField] private float _maxIRValue = 10000; [SerializeField] private float _minIRVaue = 0; private Material _material; private Texture2D _tempIR; private Device _kinectDevice; // Start is called before the first frame update void Start() { //GraphicsFormat.R8_UNorm _material = new Material(_unprojectIRshader); _tempIR = new Texture2D(_irTexture.width, _irTexture.height, GraphicsFormat.R16_UNorm, 0); _kinectDevice = Device.Open(); } // Update is called once per frame void Update() { //irのデータ配列を取得 var capture = _kinectDevice.GetCapture(); var irData = capture.IR.Memory; //シェーダーに放り込むソース用のテクスチャにロード _tempIR.LoadRawTextureData(irData.ToArray()); _tempIR.Apply(); capture.Dispose(); //変換元のソースのテクスチャをセット _material.SetTexture("_IrTexture", _tempIR); //IR輝度の最大値(画像の見え方の調整) _material.SetFloat("_MaxValue", _maxIRValue); //IR輝度の最小値(画像の見え方の調整) _material.SetFloat("_MinValue", _minIRVaue); var prevRT = RenderTexture.active; //変換先のテクスチャをセット(ColorFormatをR8_UNorm) Graphics.SetRenderTarget(new RenderBuffer[1]{ _irTexture.colorBuffer }, _irTexture.depthBuffer); Graphics.Blit(null, _material, 0); RenderTexture.active = prevRT; } private void OnDestroy() { if (_material != null) Destroy(_material); if (_tempIR != null) Destroy(_tempIR); } }以下はshader側の処理です。
Shader "UnprojectIR" { CGINCLUDE #include "UnityCG.cginc" texture2D _IrTexture; float _MaxValue; float _MinValue; //16bitをR8_Normフォーマットに変換(0~1.0) inline float GetIR(texture2D source, int tx, int ty, int vmax, int vmin) { int v = source[uint2(tx, ty)] * 65535; float colorValue = 0.0f; if (v <= vmin) { colorValue = 0.0f; } else if (v >= vmax) { colorValue = 1.0f; } else { colorValue = (float)(v - vmin) / (float)(vmax - vmin); } return colorValue; } void Vertex( float4 position : POSITION, out float4 positionOut : SV_Position, inout float2 texCoord : TEXCOORD0 ) { positionOut = UnityObjectToClipPos(position); } void Fragment( float4 position : SV_Position, float2 texCoord : TEXCOORD0, out float colorOut : SV_Target0 ) { uint w, h; _IrTexture.GetDimensions(w, h); // Texture index uint tx = texCoord.x * w; uint ty = texCoord.y * h; float ir = GetIR(_IrTexture, tx, ty, _MaxValue, _MinValue); //output write colorOut = ir; } ENDCG SubShader { Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex Vertex #pragma fragment Fragment ENDCG } } }まとめ
UnityでAzureKinectの赤外線を見る方法を調べた。これを利用して以前の別記事でやったような再帰性反射材をつかったトラッキングをやってみたい。
- 投稿日:2019-12-24T11:59:37+09:00
EPSONスマートグラスMOVERIOにアプリをビルドしてみた
Unityのアセットを使ってMOVERIOにAPPビルドしてみたメモ
MOVERIOはAndroid5.1のようなのでSDKも同じバージョンを用意するようにしましたBT-350を体験してみた感想
BT-350を使ってみた感じは、中身はAndoridタブレットのUIでした
トラックパッドなコントローラーあるですが使いづらい感じでした。。
表示は輝度も高く見やすいですが、歩きながらだと、注視点が画面になるので、ぶつかりそうで危ない感じですた
映画に出てくるような、スマートグラス感は無いですAndroid5.1のビルド環境を準備
Unity HubなどでAndroid書き出し環境のモジュールをインストール
AndroidStudioでSDKをインストール
Tools > SDK Manager > AndroidSDK でAndroid 5.1 (Lollipop)をインストール
Unityでサンプルプロジェクトをビルド
参考サイト
スマートグラス・スマートヘッドセット
https://www.epson.jp/products/smartglasses/Moverio BT-300 Unity Plugin
https://assetstore.unity.com/packages/tools/integration/moverio-bt-300-unity-plugin-83627
- 投稿日:2019-12-24T11:36:58+09:00
WebGLでビルドされたUnityとコンソールアプリケーション間で通信したい
WebGLのUnityとコンソールアプリを通信したい
ブラウザ上に表示されるWebGLビルドされたUnityのアプリケーションと,ローカルで起動したコンソールアプリケーションを通信できるようにしたいときってありますよね.
スタンドアロンなUnityアプリケーションとコンソールアプリケーションを通信するだけであれば,TCPソケットはもちろん,Websocketでも接続できます.それから推察するに,WebGLビルドされたUnityのアプリケーションとコンソールアプリケーションもWebsocketを使えば通信できそうですが,なぜかそうはいかないようです(参考).
当エントリーでは,WebGLビルドされたUnityのアプリケーションとコンソールアプリケーションを通信できるように頑張った結果を報告します.もしかしたらよりエレガントな解決策があるかもしれません.
以下環境設定.
- Unity: 2019.1.8f1
- chrome: 79.0
- Windows: 10 pro 1903解決策
*.jslib
にWebsocketで通信するjavascriptを書き,Unityのスクリプト(C#)ではjavascriptが受け取ったメッセージをUnityの空間に持ち上げるようにする.具体的には?
1. ファイルの用意
まずUnityの
Assets
ディレクトリ以下に,ファイルを二つ作ります.ファイル名はなんでもいいのですが,便宜上以下のものに決めます.階層構造は一応順守で.
-Assets/Plugins/Connector.jslib
-Assets/Scripts/Main.cs
2.
Connector.jslib
の編集WebGLビルドされたUnityはこのファイルを通じてjavascriptの空間にアクセスできるようです.詳細は以下をURLを.
- https://qiita.com/gtk2k/items/1c7aa7a202d5f96ebdbf
- https://docs.unity3d.com/jp/540/Manual/webgl-interactingwithbrowserscripting.htmlこのファイルを以下のように編集し,まずはjavascript上でWebsocketのクライアントを作成します.
Connector.jslibvar ws; var messages; mergeInto(LibraryManager.library, { Initialize: function (server) { ws = new WebSocket( Pointer_stringify(server) ); messages = new Array(); ws.onmessage = function (evt) { var message = evt.data; messages.push( message ); }; ws.onopen = function () { console.log("system::open") }; ws.onclose = function () { console.log("system::close") }; }, GetMessage: function () { var msg = ""; if( messages.length > 0 ) { msg = messages.shift(); } var buffer = _malloc( lengthBytesUTF8(msg) + 1 ); writeStringToMemory(msg, buffer); return buffer; }, SendMessage: function (message) { if (ws) { ws.send( Pointer_stringify(message) ); } }, Close: function () { if (ws) { ws.close(); } } });3.
Main.cs
の編集次に
Main.cs
を以下のように編集します.Main.csusing System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using UnityEngine; public class Main : MonoBehaviour { private GameObject logger; private Queue<string> ReceivedMessages = null; private static string server = @"ws://localhost:2333/ConnectionTest"; [DllImport( "__Internal" )] private static extern void Initialize( string server ); [DllImport( "__Internal" )] private static extern string GetMessage(); [DllImport( "__Internal" )] private static extern void Send( string message ); [DllImport( "__Internal" )] private static extern void Close(); private void OnDestroy() { Close(); } void Start() { logger = GameObject.Find( "Logger" ); ReceivedMessages = new Queue<string>(); Initialize( server ); } private static string Formatter( string message ) => $"message from server:\n{message}"; void Update() { string message = GetMessage(); if( message != "" ) { ReceivedMessages.Enqueue( message ); Debug.Log( $"Received -> {message}" ); } if( ReceivedMessages.Any() ) { string line = ReceivedMessages.Dequeue(); logger.GetComponent<TextMesh>().text = Formatter( line ); } } }Unityの
Scene
上にテキストを表示できるGameObject
を配置し,受け取ったメッセージを表示するようにしておきました.下図参照.
ちなみにこのスクリプトは,javascript上に定義された関数をインポートして呼び出しているので,WebGLビルド以外ではそれら関数が呼び出せずエラーを吐くはずです.つまり,WebGLビルドしないと動作確認できません.
変数
server
には,Websocketサーバーが起動しているPCのIPアドレスを入力します.今はlocalhost
としていますが,LAN内の別PC (192.168.11.*
)を指定しても接続できていました.4. UnityをWebGLビルドし実行,接続確認
ソースコードの編集が終わったのち,UnityをWebGLビルドします.方法は適当に調べてください.
作成された
index.html
をchromeで開くと,下図のようにUnityのプレーヤーの初期化時にエラーが生じて実行できない(Firefoxでは実行できるとかなんとか)ようです.エラーメッセージ見ると,ウェブサーバーにアップロードしろ的な感じなので,ビルドにより生成されたすべてのファイルをウェブサーバーにアップロードします.
そして,Websocketサーバーとしての機能を持つコンソールアプリケーションをあらかじめ作成しておき,起動します.Websocketサーバーはnode.jsで作る人が多いようですが,筆者はC#信者なのでC#で作りました.ライブラリとしてwebsocket-sharpにお世話になりました.
ローカルでWebsocketサーバーを立ち上げたのち,ブラウザから先ほどの
index.html
にアクセスします.その後ローカルのサーバーからメッセージを送信すると,ブラウザ上のUnityにメッセージが表示されました(下GIF参照).このGIFの上半分は
index.html
にアクセスしているブラウザの様子で,下半分はローカルに起動しているWebsocketサーバーの様子です.Websocketサーバーのコンソールに文字を入力し,エンターキーを入力すると,それまでの文字列がブラウザ上のUnityに送信されていることが確認できます.ブラウザ上のUnity上に表示されている文字が多少見切れていますが,全画面表示にするといい感じに表示されています.おわりに
エレガントな方法かどうかはわかりませんが,ブラウザのUnityアプリケーションとローカルのコンソールアプリケーションが通信できるようになりました.
そういえば,ここによればjavascriptの空間からC#のスクリプト上に定義されたメソッドを呼ぶこともできるようなので,うまくやればメッセージがあるかどうかを無意味にビジーウェイトしなくてもよくなりそうです.
後日暇を見つけて,作成したUnityのスクリプトなどをGithub上にアップロードしてようと思います.Unityのエディター上でも動作確認できるような工夫もしていたり,Websocketのサーバーアプリケーションもアップロードしておこうかなあとも思っていたり.
これでなんか面白いアプリケーション作れないかな...
- 投稿日:2019-12-24T08:31:24+09:00
[Unity] Box Projectionで屋内シーンの反射による映り込みの品質を向上する
これは 【unityプロ技】 Advent Calendar 2019の24日目の記事です。
Unity標準の ReflectionProbe のオプションにある Box Projection を設定することで、屋内シーンの反射の破綻を軽減して、映り込みの品質を向上する方法を紹介します。
百聞は一見に如かずということで、 Box ProjectionをON/OFF した動画を用意しました。
Box Projectionで屋内シーンの反射による映り込みの品質を向上する
— がむ (@gam0022) December 23, 2019
という記事を書きました!
▶️https://t.co/gz0CpeizHb
カメラの位置を考慮して、Cubemapの映り込みの位置を正しく補正する手法です。
設定が簡単なわりに、劇的に見た目のリッチ感UPできるのでオススメです!#Unity3D #shader #HLSL pic.twitter.com/o75TDgPBL2
- Box Projection: OFF では、カメラの位置が無視されて、家具などの映り込みの位置がずれてしまっています。
- Box Projection: ON では、カメラの位置が正しく考慮されて、家具などの映り込みの位置が正しくなっています!
環境
- 2018.4.13f1 (LTS)
- Build-in Rendering Pipeline および LWRP(Light Weight Render Pipeline)
Box Projectionとは、どんな技術なのか?
公式マニュアルにもBox Projectionの解説はあるのですが、ちょっと初心者には理解しずらかったので、簡単に解説をします。
端的に言うと、Cubemap(ReflectionProbe)のテクスチャをサンプリングするための反射ベクトルの向きを、カメラの位置と部屋の広さを定義するBoundingboxを考慮して補正する手法です。
普通のCubemapとの違いは、反射ベクトルの計算の違いだけなので、GPUの処理負荷もそれほど高くはありません。
反射はしていますが、レイトレをするわけではないので、RTXも不要です!
Parallax-Corrected Cubemapの解説
※Box Projectionの理論に興味がない人は、この節はスキップして本題から読んでください。
Unity標準のStandardShaderでBox Projectionは実装済みなので、理論は理解しなくても使うことはできます。
UnityではBox Projectionという名称ですが、Parallax-Corrected Cubemap とも呼ばれる手法です。
Parallax-Corrected Cubemapの具体的な理論と実装については、このスライドに非常に詳しく解説してあります。
この図は上記のスライドから引用しました。
この図を用いて簡単にParallax-Corrected Cubemapの解説を行います。基本的な考え方はそれほど難しくはありません。
まず各記号の説明をします。
- $\vec{V}$: カメラのビューベクトル
- $C$: ReflectionProbeの位置
- $\vec{N}$: 法線
- $\vec{R}$: 反射ベクトル(補正前)
- $\vec{R'}$: 反射ベクトル(補正後)
- $P$: $\vec{R}$と壁の交差点
$\vec{R}$ 方向をサンプリングすれば、 普通のCubemap ですが、
$\vec{R'}$ 方向をサンプリングすると視差が考慮された Parallax-Corrected Cubemap になります。そして、図が示すように $\vec{R}$ を補正した $\vec{R'}$ は
\vec{R'} = P - Cによって計算できます。
$C$ は既知なので、 $\vec{R}$と壁の交差点である $P$ を計算すれば、$\vec{R'}$ が求まります。
ベクトルとAABBの交差判定のアルゴリズムを用いれば、$P$ は計算ができます。
HLSLによる Parallax-Corrected Cubemap の反射ベクトル $\vec{R'}$ を求める実装例です(MITライセンス)。
// Parallax-Corrected Cubemap // https://seblagarde.files.wordpress.com/2012/08/parallax_corrected_cubemap-siggraph2012.pdf float3 calcParallaxCorrectedCubemapReflect(float3 worldPos, float3 worldReflect, float3 cubemapPos, float3 boxMin, float3 boxMax) { // AABB と 反射ベクトル の交差点 worldIntersectPos を計算します float3 firstPlaneIntersect = (boxMax - worldPos) / worldReflect; float3 secondPlaneIntersect = (boxMin - worldPos) / worldReflect; float3 furthestPlane = max(firstPlaneIntersect, secondPlaneIntersect); float dist = min(min(furthestPlane.x, furthestPlane.y), furthestPlane.z); float3 worldIntersectPos = worldPos + worldReflect * dist; // Cubemapから交差点に向かうベクトルが ParallaxCorrected された反射ベクトルになります return worldIntersectPos - cubemapPos; }壁の近似はAABBに限定しなくても交差判定ができれば何でも良いのですが、計算コストと品質のバランスを考慮してAABBが一般的に用いられるようです。
Box Projectionに対応した自作シェーダーの紹介
当初は、Parallax-Corrected Cubemapに対応したUnityシェーダーを実装するという記事を執筆する予定だったのですが、Unity標準のStandardShaderで実装済みであることを記事用のシェーダーの実装が完成してから知りました
供養のために、Box ProjectionとLightmapに対応した自作シェーダーのリンクを紹介します。
[本題] Box Projectionの設定
それでは、ここからが本題です!
Box Projectionの設定をして、最初の動画のようなシーンを作る方法をチュートリアル形式で紹介していきます!
0: 屋内のアセットを用意する
まずは屋内シーンのアセットを用意します。
今回はUnityアセットストアから Pack Gesta Furniture #1 をダウンロードして使わせていただきました
![]()
1-1: ReflectionProbeを配置する
まずは ReflectionProbe を配置します。
普通は部屋の中心あたりに適当に配置すればOKです。
今回は映り込みの品質を向上するために、ちょっとカメラの位置に寄せました。
1-2: ReflectionProbe の BoxSize と BoxOffset を設定する
部屋の広さにぴったり合うように、BoxSize と BoxOffset を設定します。
Wireframeモードにしたほうが、作業がしやすいかもしれません。
また、BoxSize と BoxOffset は ReflectionProbe の影響範囲の指定も兼ねているので、床のMeshに少しオーバーラップするようにサイズを調整する必要があります。
オーバーラップしないと、ReflectionProbe の影響範囲外となってしまいます。
1-3: Box Projection等の設定とBake
ReflectionProbeの設定を変更します。
- Box Projection: ON
- Type: Baked
- その他はお好みでOK
設定が完了したら、 [Bake] ボタンを押して、Cubemapのテクスチャを生成します。
2: 床のマテリアルの設定
床のマテリアルを反射が見えやすく調整します。
- StandardShader を設定(ReflectionProbeに対応していれば何でも良い)
- Metalic: お好みですが、フローリングの床感を出すなら 0.2 前後あたりがいい感じです
- Smoothness: 1.0 に近づくほど反射がハッキリ見えます
- Normal Map: なしにするか、影響度を 0.01 など小さな値にするほど反射がハッキリ見えます
以上でいい感じに反射するようになると思います。
まるで年末の大掃除でピカピカに磨き上げられた綺麗なフローリング床みたいですね!
トラブルシューティング
記事の執筆中に遭遇したトラブルをまとめます。
ReflectionProbe に映らないオブジェクトがある
- 対象の GameObject は static になっていますか?
- ReflectionProbe の CullingMask は Everything になっていますか?
再生したら反射が消えた
staticバッチングが原因の可能性があります。床のMeshだけ static を OFF にしましょう。
Box Projection が有効にならない
Project Settings > Graphics > Reflection Probes Box Projection
が ON になっていることを確認してください。まとめ
Unity標準ReflectionProbeのBox Projectionと標準シェーダーだけで、屋内シーンの反射による映り込みを大きく改善できました。
映り込みが綺麗にできると、光沢感やリッチ感を表現できますし、設定もそこまで手間がかからないので、コストパフォーマンスが高いと思います!
みなさんも是非 Box Projection を使いこなしてみてください!
- 投稿日:2019-12-24T00:31:02+09:00
3Dマークアップ言語とIoTを組み合わせてみる
目的
ちょっと実用的な例として、WifiにつながるArduino系マイコンボードから、温度湿度などの環境情報をARで空間に提示する仕組みを作ってみます。
道具立て
使うマイコンボードは、ESPr® One。
Wifiがついているので、こういう時に、使いやすくて便利な奴です。
APモードで動かし、Hololensからつなぎにいきます。
センサーには、BME280を使います。これは、Amazonで安く買いました。
適当にブレッドボードを使うなどして、SPIでつないで動くようにします。仕組み
マイコンで取得した温度湿度気圧を、マークアップにしてHTTPサーバーとして出力。Wifi経由でHoloLensからアクセスしてレンダリングします。
プログラム
以下のプログラムを、ArduinoIDEで書き込みました。直近5個の値を保存してグラフにします。HoloLensの方は、5秒間隔で再アクセスするようにUnity側のプログラムを作っています。
server.ino#include <Wire.h> #include <SPI.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> #include <ESP8266WiFi.h> #include <ESP8266WebServer.h> #define WIFI_SSID "orthoverseAP" #define WIFI_PW "test" #define BME_SCK 14 #define BME_MISO 12 #define BME_MOSI 13 #define BME_CS 16 #define SEALEVELPRESSURE_HPA (1013.25) Adafruit_BME280 bme(BME_CS); // hardware SPI unsigned long delaytimeBegin; ESP8266WebServer server(80); IPAddress ip(192, 168, 1, 1); IPAddress subnet(255, 255, 255, 0); float temp[5] = {0, 0, 0, 0, 0}; float press[5] = {0, 0, 0, 0, 0}; float hum[5] = {0, 0, 0, 0, 0}; unsigned long timeBegin = 0; void setup() { Serial.begin(9600); Serial.println("BME280 test"); bool status; status = bme.begin(); if (!status) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } else { Serial.println("BME280 found"); } Serial.println("-- Default Test --"); Serial.println("WiFi initializing"); WiFi.mode(WIFI_AP); WiFi.softAPConfig(ip, ip, subnet); bool apst = WiFi.softAP(WIFI_SSID, WIFI_PW); if (apst) { Serial.println("Ready WiFi"); } else { Serial.println("Something wrong WiFi"); } server.on("/", []() { Serial.println("Access"); String html = "<homl><head><title>3D Graph of Environment Sensor</title></head><body><a-scene wx=0.5 wy=0.001 wz=0.4>"; for (int i = 0; i < 5; i++) { html += "<a-cylinder height=" + String((temp[i] - 20) / 30) + " r=0.05 x=" + String((i - 2.0) / 10.0) + " y=" + String((temp[i] - 20) / 30) + " z=-0.1 color=red />"; html += "<a-cylinder height=" + String((press[i] - 1000) / 400000) + " r=0.05 x=" + String((i - 2.0) / 10.0) + " y=" + String((press[i] - 1000) / 400000) + " z=0 color=blue />"; html += "<a-cylinder height=" + String((hum[i]) / 200) + " r=0.05 x=" + String((i - 2.0) / 10.0) + " y=" + String((hum[i]) / 200) + " z=0.1 color=green />"; } html += "<a-text size=0.1 x=0.25 y=0.1 z=-0.1 color=red>"+ String(temp[4]) +"C</a-text>"; html += "<a-text size=0.1 x=0.25 y=0.1 z=0 color=blue>"+ String(press[4]/100.0F) +"hPa</a-text>"; html += "<a-text size=0.1 x=0.25 y=0.1 z=0.1 color=green>"+ String(hum[4]) +"%</a-text>"; html += "</a-scene></body></homl>"; server.send( 200, "text/html", html); }); Serial.println("WebServer setup done"); server.begin(); Serial.println("webserver begin"); timeBegin = millis(); } void loop() { server.handleClient(); if (timeBegin < millis() - 5000) { for (int i = 0; i < 4; i++) { temp[i] = temp[i + 1]; press[i] = press[i + 1]; hum[i] = hum[i + 1]; } temp[4] = bme.readTemperature(); press[4] = bme.readPressure(); hum[4] = bme.readHumidity(); timeBegin = millis(); } } void printValues() { Serial.print("Temperature = "); Serial.print(bme.readTemperature()); Serial.println(" *C"); Serial.print("Pressure = "); Serial.print(bme.readPressure() / 100.0F); Serial.println(" hPa"); Serial.print("Approx. Altitude = "); Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA)); Serial.println(" m"); Serial.print("Humidity = "); Serial.print(bme.readHumidity()); Serial.println(" %"); Serial.println(); }結果
ちょっと値の振れ幅が少なくて、面白みのないグラフになっていますが、IoT機器の情報をHoloLensで表示することができました。
あともっと表示のかっこよさを考えないといけないですね。ちなみに、写真は2018年のMakerFaireTokyoで会社のブースで展示していた時のものです。
- 投稿日:2019-12-24T00:17:01+09:00
【Unity(C#)】UniRxを使ってスキップ機能付きのテキストを1文字ずつ表示させる実装を試したけど微妙だった
UniRx
Unityで利用できるReactiveExtensionらしいです。
この説明だと私自身も意味がわからないので、参考リンク1のお言葉を頂戴して説明します。直訳すると「反応性拡張」で、イベントに対する反応を拡張するためのライブラリ
何かしらのイベント(ボタンを押した、プレーヤーが移動したなど何でも)に対する反応を
簡単に書くことができるっぽいです。私自身、ソースコードを見に行って、
"はいはいなるほどね"と言えるレベルまで理解できていません。Rxに採用されているデザインパターンで、Observerパターンというのがあるのですが、
それに関しても人に説明できるところまで理解でき次第、まとめようと思っています。今回は、使いながら覚えていきましょう
という意図で調べながら作ったらなかなか残念な仕上がりだったのでそれをメモに残します。テキストを1文字ずつ表示
既に何年も前に実装されている先駆者様がいらっしゃいました。
【参考リンク】: UniRXでuGUIのテキストをアニメーションさせる文字を1文字ずつ表示する機能自体は参考リンクで完結しているので、
今回はスキップ機能(文字をいっきに最後まで表示させる機能)をUniRxで実装してみます。コード
テキストコンポーネントを持つオブジェクトにアタッチusing UniRx; using System; using UnityEngine; using UniRx.Triggers; using UnityEngine.UI; public class TextPerOneWrite : MonoBehaviour { [SerializeField] float m_textInterval = 0.2f; [SerializeField] KeyCode m_keyCode = KeyCode.Space; Text m_windowText; IDisposable m_textDispose; IDisposable m_updateDispose; void Start() { m_windowText = this.gameObject.GetComponent<Text>(); m_windowText.text = ""; //実行サンプル 文字を1文字ずつ出す ShowPerOne("ウホウホバナナヨコセ"); ShowPerOne("ウホウホバナナヨコセ(早口)", 0.1f); } void ShowPerOne(string commentText) { m_windowText.text = ""; if (m_textDispose != null) { m_textDispose.Dispose(); m_updateDispose.Dispose(); } m_textDispose = Observable.Interval(TimeSpan.FromSeconds(m_textInterval)) .Take(commentText.Length) .Select(_ => 1) .Scan((accumulation, newValue) => accumulation + newValue) .DoOnCompleted(() => m_updateDispose.Dispose()) .SubscribeToText(m_windowText, length => commentText.Substring(0, length)) .AddTo(this); //特定のキー入力で文字を1文字ずつ出す機能を止める m_updateDispose = this.UpdateAsObservable() .FirstOrDefault(_ => Input.GetKeyDown(m_keyCode)) .Subscribe(_ => { m_textDispose.Dispose(); m_windowText.text = commentText; }); } void ShowPerOne(string commentText, double textInterval) { m_windowText.text = ""; if (m_textDispose != null) { m_textDispose.Dispose(); m_updateDispose.Dispose(); } m_textDispose = Observable.Interval(TimeSpan.FromSeconds(textInterval)) .Take(commentText.Length) .Select(_ => 1) .Scan((accumulation, newValue) => accumulation + newValue) .DoOnCompleted(()=>m_updateDispose.Dispose()) .SubscribeToText(m_windowText, length => commentText.Substring(0, length)) .AddTo(this); //特定のキー入力で文字を1文字ずつ出す機能を止める m_updateDispose = this.UpdateAsObservable() .FirstOrDefault(_ => Input.GetKeyDown(m_keyCode)) .Subscribe(_ => { m_textDispose.Dispose(); m_windowText.text = commentText; }); } }特定のキーを押すと1文字ずつ流れる文字を一気にスキップして表示することができます。
Observable.Interval
引数に時間を指定してあげるとその指定した間隔で値を流す(処理を実行)ことができます。
ちなみに
TimeSpan.FromSeconds
というのはどうやらC#の機能のようで
時間を指定するときによく使うみたいです。【参考リンク】:TimeSpan.FromSeconds(Double) メソッド
Scan
Scanというオペレーターは前回発行された値と今発行された値の畳み込みを行うことができます。
平たく言うと重ねて足し合わせるイメージです。.Scan((accumulation, newValue) => accumulation + newValue)
accumulation
は蓄積、累算などの意味を持つので、言葉の意味で覚えるとわかり易いです。
累算した値に、受け取った値(newValue)を足しているというわけですね。【参考リンク】:UniRxを使ってみる
ストリームの寿命管理
uniRxは非常に便利ですが、
使う上で気を付けることの1つにストリームの寿命管理があります。ストリームというのは
メッセージが伝達される経路、仕組み、機構のこと
らしいです。(またの名をObservableというらしい)
【引用元】:ObserverパターンからはじめるUniRx
このストリームというのが役目を終了した(もう必要でなくなった)段階で
購読を終了してあげる必要があります。そうしてあげないと、パフォーマンスが低下したり、
もう存在していないGameObjectを参照してエラーが起きたりします。【参考リンク】:UniRx入門 その2 - メッセージの種類/ストリームの寿命
Dispose
ストリームの購読終了を任意のタイミングで行うことが可能です。
一回変数に入れて、好きなタイミングで呼び出したらいいんじゃないでしょうか。
AddTo
AddTo
というメソッドを利用して、
先述したもう存在していないGameObjectの参照を未然に防ぐことができます。引数に与えたGameObjectが削除された際に、自動的にDisposeを呼び出してくれます。
OnCompleted
このメッセージが発行され
Subscribe
まで到達すると購読が終了するらしいです。今回どこにも
OnCompleted
を書いてませんが、
どうやらTake
によって指定回数分メッセージが通った際に発行され、
最後のSubscribeに到達しているようです。
(違うかもしれないんで、使ってておかしいと思ったらまた書き直します)ストリームいっぱいできちゃう問題
前回のストリームが実行中であっても次に作成したストリームを同じ変数に突っ込めば
前回のストリームを止めた上で次のストリームを実行できそう!ダメでした。なので、変数に既に何かしらが格納されているかチェックして
もし入っていたらストリームを止めてます。if (m_textDispose != null) { m_textDispose.Dispose(); m_updateDispose.Dispose(); }
m_textDispose = Observable.Interval
のように1つの変数に格納したからといって、
ストリームが1つしか作成されるわけではない、前回のストリームは止まらない...というのがわかりました。ストリームがストリームを監視するのはあまりよくない
強い人に見て頂いた際にご指摘頂きましたが、けっこうごちゃごちゃしてしまっています。
自分で作っていても感じたことなのですが、
・ストリームの処理を条件分岐したい
・ストリームの挙動を途中で動的に変えたいというような要望をUniRx使用時に盛り込むと、
”ストリームB”で”ストリームA”を監視するような状態になるので
条件分岐や変えたい挙動の数だけストリームが増えてしまいます。なので、今回のように
Observable.Interval
をUpdateAsObservable
から任意のタイミングで止めるというのは
本来便利なRxを使っているにもかかわらずややこしくなってしまっています。初心者あるあるらしいので、次回からはその辺りも意識してみようと思います。
参考リンク
UniRx入門シリーズ 目次
UniRx オペレータ逆引き
UniRxを紐解く「Take(1)とFirst()の違い」
【Unity】【UniRx】Observable.DoXxx()系のメソッドの挙動まとめ
【Unity】UniRx入門リンク集
- 投稿日:2019-12-24T00:00:42+09:00
Unityでクリスマスを飾ろう!(UnityLight2Dの話)
Unity #3 Advent Calendar 2019 12/24・・・僕の番だ!シャンシャンシャンシャン
今日はクリスマスイヴですね。
去年はクリスマスの時期にUnityを使ってクリスマスカードを作ろうの話を投稿しましたが今年もしますよUnityでクリスマスカードを作ろう
作りましょう。
- 使ってみたかったUnityの機能があるので試す
- Unity Light2Dとドット絵で作ろう
- WebGLでかきだしてUnityRoomにあげる
- UnityRoomのURLをペッと送ればOK
Unity Light2D
このLight2DはUnity2019.2から使用が可能です。なんとなくクリスマスのイルミネーションピカピカさせっかーと思い使おうとしました。
UnityのLight2Dを使う準備
Window->PackageManagerからLightweight RPをインストールします。
Projectウインドウ上でPipeline Assetと2D RendererをCreateします。
Pipeline AssetのRenderer TypeをCustomに変更し、Dataに先程作成した2D Rendererを設定します。
その後、projectSettingのGraphicsにあるScriptable Renderer Pipeline Assetに先ほど作成したPipeline Assetを設定します。
これで、Hierarch上でLight2Dが作成できるようになりました!
Unity Light2Dでできる表現
こちらに動画を上げました。
https://www.youtube.com/watch?v=2knzJoysZ1s法線マップを使ってちょっとだけ立体表現してみる
そもそも法線とは?
面に垂直なベクトルのことです。下の図で言うと、黄色の面から垂直に出てる青い矢印が法線です。
(画像はwikipediaより)
法線マップとは?
オブジェクトの法線ベクトルのX, Y, Z座標に対応したRGB画像のことです。ノーマルマップともいいます。
左の図の法線マップを立方体に張り付けると、立方体が法線マップに応じて凸凹しているのが分かります。
(画像は http://cpetry.github.io/NormalMap-Online/ より)
簡単に法線マップを作ろう
NormalMap-Onlineを使います。
面白いので、サイトにいって遊んでみてください。
実際に作ってみた
こいつを立体的にしてみましょう。
ここに影が入るかな~と思ったところを暗くしたHeightMapとやらをつくって変換してみました。SpriteにNormalMapを設定
Sprite EditorのSecondary Textureから設定します。
Nameの部分は、ラベルの右にある▼から選びます。
※手入力でいれるとうまくいきませんでした(これに気づくのに30分くらいかかった人)
Light2Dの設定を行う
Light2Dの設定にUse Normal Mapにチェックをいれるだけで・・・
こちらに動画をあげています。
https://www.youtube.com/watch?v=C_MHtLP36zcってことでUnityRoomにクリスマスカードをうpしました。
とある場所をクリックすることで開封(ライトアップ)します。
https://unityroom.com/games/xmascard2019みなさん、良きクリスマスを!