ShaderGraphで灯篭的なものをつくる
トゥーンシェーダで影がドットのものを作りたくなったので、ShaderGraphで作ってみることにしました。
ShaderGraphとは、シェーダを線のようなもので繋いで作成していくものです。
今回はその練習として、UVスクロールのShaderを作ってみます。要するにテクスチャがアニメーションして動くものですね。
インストールの準備をする
※注意 ShaderGraphの使い方をググると、ProjectManagerからShaderGraphをインストールしようなどの項目が出てきますが、2018年11月下旬において、それを行うとShaderGraphで作ったファイルが保存できません。
現状このやり方でできますので、注意が必要です。
テンプレートではなく新しくプロジェクトを作って、ProjectManagerから各種ファイルをインストールしても同様の結果でした。
すでに作ったプロジェクトを「Edit」メニュー→Render Piepeline→Update Project Materials To Lightweight Materialsを選んで、「Processed」を押してアップデートしても同様でした。
1.Unityのプロジェクトを作る。このとき、「Template」を「Lightweight RP(Preview)」にする
2.テンプレート画面が出てくるので、「Assets」を右クリックして、「Create」→「Scene」で新しいシーンをつくる
3.「GameObject」メニュー→「3D Object」→「Cube」などでテストモデルを作成する
4.「Assets」にドットのテクスチャをドラッグ&ドロップで読み込ませておく
Shaderをつくる
1.「Asstes」を右クリックし、「Create」→「Shader」→「PBR Graph」を選択して「RollShaderGraph」など名前をつける
2.「RollShaderGraph」をクリックした状態で、右クリックし、「Create」→「Material」でマテリアルを作成して「RollMat」など名前をつける
3.「RollShaderGraph」をダブルクリックし、ShaderGraphを立ち上げる
ノードをつくる
1.テクスチャを表示する
→テクスチャのRGB(4)を「PBR Master(最終出力)」の「Albedo」につなぐ
2.テクスチャを手動でスクロールさせる
2-1.ShaderGraphの画面で右クリックして「Create Node」を選択→「Input」→「Basic」→「Slider」でスライダーを表示する
2-2.下記のようにノードを接続する
全体:
部分:
2-3.「Slider」のスライダを動かして、テクスチャが動けばOK
3.自動でテクスチャがスクロールさせる
3-1.「Sider」をクリックし、右クリックして「Convert to Property」を選択する。これでInspectorからSliderの値をコントロールできる
3-2.どこでもいいので右クリックして、「Create Node」で「Multiply」を選択する
3-3.同じように「Time」を選択する
3-4.「Property(元Slider)」から「Combine」に伸びている線をクリックし、右クリックで「Delete」を選んで消す
3-5.「Time」「Property」「Multiply」を下記のように接続する
3-6.「Multiply」と「Combine」を下記のように接続する
3-7.ShaderGraph画面左上の「Save Asset」を押して適用する
最終的に、
全体:
部分:
ShaderGraphで作ったシェーダを適用する
1.「RollMat」を選択し、「Shader」のところを「RollShaderGraph」にする
2.「RollMat」をオブジェクトにドラッグ&ドロップする
3.「RollMat」をクリックし、「Isnpector」の「Vector1」で回転速度を変える。0なら停止状態
※マシンスペックによっては、Sceneビューのみ回転していてGameビューは停止状態ということもあり。Playボタンを押せばちゃんと動くので問題はない
スクリプトでライトをコントロールする
こちらでも扱っていますが、たまに忘れるのでメモ代わりに。
prince9.hatenablog.com
注意点としては、スクリプトを直接ライトにつけると、プレイ時にエラーが出ます。
空のオブジェクトを作って、「public GameObject slight」などにしてライトをドラッグ&ドロップする必要があります。
準備
下記の3点を作ります。
・空のゲームオブジェクト(「GameObject」メニュー→「Create Empty」
・明るさをコントロールしたいライト
・スクリプト(「Assets」を右クリックして、C# Script)
スクリプト
1.スクリプトを空のゲームオブジェクトにドラッグ&ドロップする
2.空のゲームオブジェクトをクリックし、Inspectorの「slight」にコントロールしたいライトをドラッグ&ドロップする
using System.Collections; using System.Collections.Generic; using UnityEngine; public class LightScript : MonoBehaviour { //以下2つ必須 public GameObject slight; float LIntensity; // Use this for initialization void Start () { LIntensity = slight.GetComponent<Light>().intensity; } // Update is called once per frame void Update () { //ライトの明るさ LIntensity = 100f; slight.GetComponent<Light>().intensity = LIntensity; } }
同じプロジェクト間、違うスクリプトでのデータの受け渡し
同じプロジェクトの別のスクリプトで変数とそのデータを使いまわしたいことがあります。
そのときのデータを受け渡し(正確には読み取り)のメモです。
準備
1.「GameObject」メニュー→「Create Empty」で空のオブジェクトを2つ作る
2.それぞれの名前を「Send」「Recieve」などにする。同じ名前にしないよう気をつける
3.「Assets」を右クリックしてスクリプトを2つ作り、「SendScript」「RecieveScript」などと名前をつける
4.「Send」オブジェクトには「SendScript」を、「Recieve」オブジェクトには「RecieveScript」をドラッグ&ドロップする
5.「SendScript」と「RecieveScript」をダブルクリックしてスクリプトを書く
スクリプト 「SendScript」データを送る方(参照先)
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SendScript : MonoBehaviour { //この値を「RecieveScript」に送る(参照する) public int ScoreData = 100; void Start () { } // Update is called once per frame void Update () { } }
スクリプト 「RecieveScript」データを受け取る方(処理する先)
using System.Collections; using System.Collections.Generic; using UnityEngine; public class recieveScript : MonoBehaviour { //この2つの変数が必要 GameObject scoreBox; SendScript script; // Use this for initialization void Start () { //Sendというオブジェクトを探す scoreBox = GameObject.Find ("Send"); script = scoreBox.GetComponent<SendScript>(); //新しく変数を作って、「SendScript」の変数「ScoreData」を入れる int score = script.ScoreData; Debug.Log ("スコアは" + score); } // Update is called once per frame void Update () { } }
VRMキャラクターの表情を一定時間ランダムに変える
こちら2つの応用編です。コルーチン使用部分はたぶん力技。
prince9.hatenablog.com
prince9.hatenablog.com
マイクの最大音量を計測し、そのときにランダムに表情を変えるというスクリプトを書いてる途中のメモです。
キーボードを押すごとにランダムに表情が変わり、2秒後に元に戻ります。
準備
こちらを参考にして、必要な表情を作成しておきます。
prince9.hatenablog.com
スクリプト
「BlendList = new List
using System.Collections; using System.Collections.Generic; using UnityEngine; using VRM; public class FaceKeep : MonoBehaviour { private VRMBlendShapeProxy proxy; public List<string> BlendList; private string rBlend; //「処理を止める処理」を停止するか否か bool isRunning = false; // Use this for initialization void Start () { //使いたい表情のリスト BlendList = new List<string>(){"Neutral","Smile","Setunai","SHINKEN"}; } // Update is called once per frame void Update () { if (proxy == null) { proxy = GetComponent<VRMBlendShapeProxy>(); } else { //上矢印キーをキーコードで判断 if(Input.GetKeyDown(KeyCode.UpArrow)) { //ランダムにリストから表情を選ぶ rBlend = BlendList[ Random.Range(0, BlendList.Count) ]; Debug.Log(rBlend); proxy.SetValue(rBlend, 1.0f); //時を止める処理 StartCoroutine(TimeStop()); } else { proxy.SetValue(BlendList[0], 1.0f); } } } IEnumerator TimeStop() { //「処理を止める処理」を開始する if( isRunning ) { yield break; } isRunning = true; //2秒時を止める yield return new WaitForSeconds(2.0f); //2秒後表情を通常に戻す proxy.SetValue(rBlend, 0f); proxy.SetValue(BlendList[0], 1.0f); //「処理を止める処理」を停止する(動き出す) isRunning = false; } }
一定以上のボリュームのときに大きさが変わる
音量が一定以上になったときにキャラクターの表情を変えたいと思い、そのテストとしてやってみました。
一定以上のボリュームになったら、オブジェクトのサイズと色が変わります。
ただ実際に実装したいのが「表情」なので、サイズと色が変わったらそれを2秒保持する(=2秒次の処理を止める)ことを行なっています。
瞬間的に表情が変わり続けるのはどうかと思うので・・・
数秒後に処理を行う、など時間を扱う処理である「コルーチン」を使いましたが、力技なような気がしてます。
大きさの変化に関して参考にさせて頂いたのがこちら。
tips.hecomi.com
前準備 再生したいサウンドファイルを設定する
スクリプトを書いて、オブジェクトにコードをドラッグ&ドロップします。
そうするとInspectorに「Audio Source」の項目ができるので、▶︎をクリックして「AudioClip」に再生したいサウンドファイルをドラッグ&ドロップします。
スクリプト
状態を保持する時間を作っているので、音との同期が確実に正確ではありませんが、実験ではまあまあでした。
using System.Collections; using System.Collections.Generic; using UnityEngine; /* ボリュームに合わせてオブジェクトの大きさを変えたい場合に必要 using System.Linq; */ [RequireComponent(typeof(AudioSource))] public class SoundMoveScript : MonoBehaviour { //256を1024にするともっと細かく検出できる float[] wData = new float[256]; //「処理を止める処理」を停止するか否か bool isRunning = false; private AudioSource audioS; // Use this for initialization void Start () { audioS = GetComponent<AudioSource>(); } // Update is called once per frame void Update () { /* ボリュームの変化に合わせてオブジェクトの大きさを変える場合はここだけ。他の変数との兼ね合いで変数名を変えてますが、例ほぼそのままです AudioListener.GetOutputData(wData, 1); var volume = wData.Select(x => x*x).Sum() / wData.Length; transform.localScale = Vector3.one * volume * 10; */ float vol = GetMaxVolume(); Debug.Log(vol); //ボリューム最大値が0.5以上のとき if(vol >= 0.5) { gameObject.GetComponent<Renderer>().material.color = Color.blue; transform.localScale = new Vector3(4, 4, 4); } else { //0.5以上になったときの処理を停止する=状態を保持して、次の状態へ移行する StartCoroutine(TimeStop()); } } float GetMaxVolume() { float a = 0; audioS.GetOutputData(wData,0); foreach(float vMax in wData) { //最大値を取り出す a += Mathf.Max(vMax); } return a/256.0f; } IEnumerator TimeStop() { //「処理を止める処理」を開始する if( isRunning ) { yield break; } isRunning = true; //2秒止める(状態を保持する) yield return new WaitForSeconds(2.0f); //次の処理へ移行 transform.localScale = new Vector3(1, 1, 1); gameObject.GetComponent<Renderer>().material.color = Color.red; //2秒止める(状態を保持する) yield return new WaitForSeconds(2.0f); //「処理を止める処理」を停止する(動き出す) isRunning = false; } }
VRMで数秒後に表情を変化させる
音楽のアタックに合わせて表情を変えようとしてる途中の産物です。
笑顔の表情の数秒後に元の表情に戻したい場合の処理をメモしました。
表情をつくる
VRoidや購入したりダウンロードしたVRMファイルはあらかじめいくつかの表情がプリセットとして登録されています。
そのプリセットを呼び出す場合は、
proxy.SetValue("Neutral", 1.0f);
となります。この場合、「Neutral」がプリセット名(BlendShapeName)になります。
プリセットを作成する場合は、
1.「Hierarchy」のキャラクターをクリックして、Playボタンを押す
2.「Inpector」→「VRM Blend Shape Proxy」の「Blend Shape Avatar」→「Blend Shape(Blend Shape Avatar)」をダブルクリックする
3.「Add BlendShapeClip」を押して、作りたい表情の名前を入力する。その後Saveを押して保存する。
このときの名前がプリセット名となります
4.「▶︎ Face」の▶︎を押して展開し、スライダを動かして表情をつくる
5.表情を作成したら、「Apply」を押して登録する
6.スクリプトの中で呼び出すときは、
proxy.SetValue("Smile", 1.0f);
とする。この場合は「Smile」が3.で登録したプリセット名
VRMで表情を変化させる
ご本家の通り、下記の2つが必要になります。
BlendShapeを操作する - dwango on GitHub
・proxy = GetComponent
・proxy.SetValue("Smile", 1.0f) 表情ON
・proxy.SetValue("Smile", 0f) 表情OFF
例として、キーボードで表情を変える例が下記です。
using System.Collections; using System.Collections.Generic; using UnityEngine; using VRM; public class FaceChange : MonoBehaviour { private VRMBlendShapeProxy proxy; // Use this for initialization void Start () { } // Update is called once per frame void Update () { if (proxy == null) { proxy = GetComponent<VRMBlendShapeProxy>(); } else { //キーボード入力 if(Input.GetKey(KeyCode.UpArrow)) { //表情呼び出し proxy.SetValue("Neutral", 1.0f); } else { proxy.SetValue("Neutral", 0f); } if(Input.GetKey(KeyCode.DownArrow)) { proxy.SetValue("Smile", 1.0f); } else { proxy.SetValue("Smile", 0f); } if(Input.GetKey(KeyCode.RightArrow)) { proxy.SetValue("Setunai", 1.0f); } else { proxy.SetValue("Setunai", 0f); } if(Input.GetKey(KeyCode.LeftArrow)) { proxy.SetValue("SHINKEN", 1.0f); } else { proxy.SetValue("SHINKEN", 0f); } } } }
一定時間後に表情を変える
笑顔から一定時間後に元の表情に戻したい場合の処理がこちらです。
TimeCount -= Time.deltaTime;を使っています。
using System.Collections; using System.Collections.Generic; using UnityEngine; using VRM; public class FaceKeep : MonoBehaviour { private VRMBlendShapeProxy proxy; //3秒後に表情を戻す(別の表情にする) float TimeCount = 3; // Use this for initialization void Start () { } // Update is called once per frame void Update () { if (proxy == null) { proxy = GetComponent<VRMBlendShapeProxy>(); } else { proxy.SetValue("Smile", 1.0f); //表情処理終章後にカウント開始 TimeCount -= Time.deltaTime; //カウントが0になったら=この場合は3秒経ったら if(TimeCount <= 0) { proxy.SetValue("Smile", 0f); //表情変化 proxy.SetValue("Neutral", 1.0f); } } } }
マウスドラッグでオブジェクトを動かす
ググってみて動かしてみたら、どれをやっても動かない。英語で検索してみたところ、解決は「Main Camera」のタグを「MainCamera」にするとのこと。どの日本語サイトもそれについては言及しておらず、動かない!とマウスを投げる方がいそうなので、メモしました。
カメラのタグを「MainCamera」にする
Hierarchyの「Main Camera」をクリックし、Inspectorの「Tag」を「MainCamera」にする
スクリプトを書く
下記のスクリプトを書きます。「Vector3 mousePos」のZの値は適当でOKです。マウスはXY軸にしか動かないからですが、ないとエラーになってしまいます。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class MouseDragScript : MonoBehaviour { private void OnMouseDrag() { //10の値は適当。マウスはXY方向にしか動かないので、Z軸は適当な値でOK。値なしだと動かない Vector3 mousePos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10); Vector3 objectPos = Camera.main.ScreenToWorldPoint(mousePos); transform.position = objectPos; } // Use this for initialization void Start () { } // Update is called once per frame void Update () { } }