[Unity]Shader GraphのPropertyの値をC#Scriptで変更する(非エンジニア)

Unity version 2020.3.16f1

Scriptだけ読みたい人は目次で8を選択してください

1、URPとHDRP

Shader Graphを使いたい場合はRenderPipelineをURPかHDRPにする必要がある
プロジェクトを作成する際に選択することができる
ここではURPを選択している

画像1

作成済みのプロジェクトでもPackageManagerからRPをインストールすることができる
メニューのWindow >> PackageManager
PackageManagerのPackagesのプルダウンメニューからUnity Registryを選択High Definition RPかUniversal RPを選んでInstallボタンを押すとインストールできる
※RPをインストールするとすでに作成してあるShaderに不具合が生じるのでRPに対応したShaderに変更する必要がある
なるべくプロジェクト作成時にRPを選択しておくのが無難

画像2

2、Shader Graphを作る

Projectウィンドウで右クリックから
Create >> Shader >> Universal Render Pipeline

Lit Shader Graph
Sprite Lit Shader Graph
Sprite Unlit Shader Graph
Unlit Shader Graph
のいずれかを選択する
ここではSprite Unlit Shader Graphを使用する
Graph InspectorのGraph Settingsで後から変更することも可能

名称にGraphとついていないものはシェーディング言語で記述する必要がある

3、ノードを作成

作成したShader Graphをダブルクリックするとウィンドウが開く
ウィンドウ上で右クリックからCreate Nodeを選択するとノードが選べる
(スペースキーがショートカット)
探すのは面倒なので基本は検索ボックスに入力して選ぶ
※Create Nodeをクリックしてもノードの選択項目が表示されない場合は一度ウィンドウ上で左クリックをしてみる

以下のノードを作成する
・Texture 2D Asset テクスチャを設定
・Sample Texture 2D テクスチャの入出力
・Float (Vector1) float型の値
・Color 色
・Multiply 乗算

ノードについては以下を参考にしてみてください
Unityのシェーダーグラフについて
https://gametukurikata.com/shader/shadergraph

4、ノードの設定

画像3
  1. Texture 2D Assetに貼り付けたいテクスチャを設定する
    (ここで貼り付けるのは透過用テクスチャで黒い部分が透過する)

  2. Texture 2D AssetのOutとSample Texture 2DのTexture(T2)を繋ぐ

  3. Sample Texture 2DのRGBA(4)とMultiplyのAを繋ぐ

  4. FloatのOutとMultiplyのBを繋ぐ

  5. MultiplyのOutとFragmentのAlphaを繋ぐ

  6. ColorのOutをFragmentのBase Colorに繋ぐ

  7. ウィンドウ左上のSaveAssetを押す

※テクスチャをそのまま貼り付けたい場合はSample Texture 2DのRGBA(4)をBase Colorに繋ぐ
テクスチャの貼り付き方は3Dツールでテクスチャ展開したものが反映されるので3Dモデルをインポートする場合はテクスチャ展開しておく

5、Convert to Property

Scriptで値を変更できるようにノードをPropertyにする

  1. ノードを選択して右クリックからConvert to >> Propertyを選択すると
    ウィンドウの左上にある小さいウィンドウにPropertyができる
    ※小さいウィンドウが表示されていない場合はウィンドウの右上にあるBlackboardをクリックすると表示される

  2. Propertyを選択するとウィンドウ右上にあるGraph InspectorのNode SettingsにあるReferenceを任意の名前にする(Scriptで記述する際に使う)
    ※Graph Inspectorが表示されてない場合はウィンドウ右上にあるGraph Inspectorをクリックすると表示される
    ここではReference名を以下のようにしておく(最近のバージョンではNameを変更すればReference名も変更される)
    Texture 2D Assetノード→ _Texture2D
    Colorノード→ _Color
    Floatノード→ _Vector1

6、マテリアルを作成

  1. Projectウィンドウで右クリックからCreate >> Materialを選択

  2. 作成したmaterialにShaderGraphをドラッグ&ドロップ

  3. Hierarchyウィンドウで右クリックから3D Object >> Cylinderを選択
    (Cylinder以外でも問題ないです)オブジェクト名を変更した場合はScriptで記述している箇所があるのでそちらも変更する

  4. 作成したCylinderを選択しInspectorウィンドウのMeshRendererのMaterialsに作成したmaterialを設定

7、差し替えるテクスチャを準備

  1. Projectで右クリックからCreate >> Folderを選択

  2. フォルダ名をResourcesにする(この名前は変更してはいけない)

  3. Resourcesフォルダで右クリックからCreate >> Folderを選択

  4. フォルダ名をTextureにする(この名前は変更できるがScript上で記述している箇所があるので変更の際はそちらも変更する)

  5. テクスチャをTextureフォルダにドラッグ&ドロップしてインポートする
    (テクスチャ名は任意ですがここではeffect_gradation1としておく
    Script上で記述している箇所があるので変更の際はそちらも変更する)

8、C#Scriptの記述

オブジェクトのマテリアルの色や透過を変化させて最後に消す処理

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SampleScript : MonoBehaviour // クラス名はファイル名と同じにする
{
   void Start()
   {
       // 変数の宣言とデータの代入
       // オブジェクトの取得 "Cylinder"はオブジェクト名
       GameObject obj = GameObject.Find("Cylinder");
       // オブジェクトにAddComponentされているMeshRendererを取得
       MeshRenderer meshR = obj.GetComponent<MeshRenderer>();
       // Textureの読み込み
       Texture tex = Resources.Load<Texture>("Texture/effect_gradation1");
       
       // マテリアルに上記で読み込んだTextureを適用
       meshR.material.SetTexture("_Texture2D", tex);
       
       // コルーチンの開始
       StartCoroutine(Effect(meshR, obj));
   }
   // コルーチン
   private IEnumerator Effect(MeshRenderer argument_meshR, GameObject argument_obj)
   {
       // 変数の宣言と初期値の代入
       float t = 0; // t 時間経過
       float alpha = 1; // 透過用の値
       float r = 1.0f; // 色用の値 赤
       float g = 1.0f; // 色用の値 緑
       float b = 1.0f; // 色用の値 青
       float scale = 0.0f; //縦Scale用の値
       
       // ループ処理
       while(true){
           // 値を加算 Time.deltaTimeはフレーム間の時間経過を取得する
           t += 1.0f * Time.deltaTime;
           // 値を減算 徐々に透過していく
           alpha -= 1.0f * Time.deltaTime;
           // 値を減算 徐々に色が変化していく
           b -= 1.0f * Time.deltaTime;
           // 色をマテリアルに適用するための変数
           Color color = new Color(r, g, b);
           
           // アルファ用の値をマテリアルに適用
           argument_meshR.material.SetFloat("_Vector1", alpha);
           // 色用の値をマテリアルに適用
           argument_meshR.material.SetColor("_Color", color);
           
           // 値を加算 徐々に大きくなる
           scale += 1.0f * Time.deltaTime;
           // Scaleの値をオブジェクトのY軸に適用
           argument_obj.transform.localScale = new Vector3(1, scale, 1);
           
           if(t >= 1) // t が1以上になったら以下の処理を実行
           {
               Destroy(argument_obj); // オブジェクトを削除
               
               yield break; // ループを抜ける
           }
           
           // 次のフレームでもループを継続する
           // この記述がないと1フレームでループを繰り返すので毎フレーム処理にならない
           yield return null;
       }
   }
}

Scriptの各記述の意味

GameObject obj;
GameObject型の変数をobjという名前で宣言

MeshRenderer meshR;
MeshRenderer型の変数をmeshRという名前で宣言

Texture tex;
Texture型の変数をtexという名前で宣言

Resources.Load<Texture>("Texture/effect_gradation1")
ResourcesフォルダにあるTexture型のファイルを読み込む
<>内で型を指定している
"Texture/effect_gradation1"はResourcesフォルダ以下の場所とファイル名を指定している
ここではTextureフォルダにあるeffect_gradation1というファイルの指定

GameObject.Find("Cylinder")
Hierarchyにあるオブジェクトの中からCylinderという名前のオブジェクトを見つける
一意の名前でない場合は親オブジェクトも追記する必要がある
例 "ParentObject/Cylinder"

obj.GetComponent<MeshRenderer>()
objという変数に入っているオブジェクトにAddComponentしているMeshRendererを取得する
※ここではすでに存在しているオブジェクトから取得しているので問題ないが
Resources.LoadしただけでInstantiate(実体化)していないオブジェクトからはGetComponentできないのでnullが代入されてしまう
例えば以下だと問題ないが

GameObject obj = Instantiate(Resources.Load<GameObject>("Prefab/Cylinder"));
​MeshRenderer meshR = obj.GetComponent<MeshRenderer>();
Texture tex = Resources.Load<Texture>("Texture/effect_gradation1");
meshR.material.SetTexture("_Texture2D", tex);

以下だと実行時にエラーが出る(違うのは1行目だけ)
読み込みだけ先にやっておいてInstantiateを別の箇所で行おうとするとこのように記述してしまうかもしれない
この記述でも.materialを.sharedMaterialに変更するとエラーが起きなくなる

GameObject obj = Resources.Load<GameObject>("Prefab/Cylinder");
MeshRenderer meshR = obj.GetComponent<MeshRenderer>();
Texture tex = Resources.Load<Texture>("Texture/effect_gradation1");
meshR.material.SetTexture("_Texture2D", tex);

以下のようにすればエラーが起きない(4行目を変更)
この場合AddComponentされているマテリアルを変更するのではなく元になっているマテリアルファイル自体を変更するため同じマテリアルを使用しているオブジェクト全てに結果が反映されてしまうので注意

GameObject obj = Resources.Load<GameObject>("Prefab/Cylinder");
​MeshRenderer meshR = obj.GetComponent<MeshRenderer>();
Texture tex = Resources.Load<Texture>("Texture/effect_gradation1");
meshR.sharedMaterial.SetTexture("_Texture2D", tex);

meshR.material.SetTexture("_Texture2D", tex);
meshRという変数に入っているMeshRendererのmaterialに設定されているテクスチャをtexという変数に入っているテクスチャに変更する
"_Texture2D"はShader GraphでProperty化したノードのReference名

StartCoroutine(Effect(meshR, obj));
Effectというコルーチンを開始する
(meshR, obj)はコルーチンに渡す変数 引数と呼ぶ

private IEnumerator Effect(MeshRenderer argument_meshR, GameObject argument_obj){ 処理 }
コルーチン
毎フレーム処理や数秒間隔で処理をするなどができる
()内は引数の型と引数名
引数はコルーチン内でのみ使用できる
ここでは引数名を一意のものにしているが、必ずしも一意にする必要はない

while(true){ 処理 }
ループ処理
yield break;でループを抜ける if文など一定条件でループから抜けるようにする

while(true) {
   // 処理
   yield return null;
}

秒数毎に処理を繰り返す場合はWaitForSecondsを使う
以下は3秒毎に処理を繰り返す

while(true) {
   // 処理 = 処理してから3秒待つ
   yield return new WaitForSeconds (3.0f);
   // 処理 = 3秒待ってから処理
}

WaitForSecondsを大量に使う場合はキャッシュした方が良いらしい
キャッシュの方法は以下を参考にしてみてください

コルーチンの中断
コルーチンを途中で中断したい場合はコルーチンを変数に代入する必要がある

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SampleScript : MonoBehaviour // クラス名はファイル名と同じにする
{
   // コルーチン型の変数を宣言
   private Coroutine aaa;
   
   void Start()
   {
       // aaa という変数にコルーチンを代入しつつ BBBコルーチンを開始
       aaa = StartCoroutine(BBB());
   }
   void Update()
   {
       // Aキーを押した時
       if(Input.GetKeyDown(KeyCode.A))
       {
           // aaa という変数に入っているコルーチンを止める
           StopCoroutine(aaa);
       }
   }
   // コルーチン
   private IEnumerator BBB ()
   {
       while(true) {
           // 処理
           yield return null;
       }
   }
}

コルーチンについて詳しくは以下を参考にしてみてください
【C#/Unity】コルーチン(Coroutine)とは何なのか
https://spirits.appirits.com/doruby/8712/?cn-reloaded=1

float t = 0;
float型の変数をtという名前で宣言 初期値は0
浮動小数点数型の変数
少数点以下の値を代入する際は数字の後ろにfをつける

詳しい説明は以下を参考にしてみてください
浮動小数点って何?
https://qiita.com/angel_p_57/items/24078ba4aa5881805ab2

Color color = new Color(r, g, b);
Color型の変数をcolorという名前で宣言 初期値はrとgとbという変数の値
(値は3つあり 左から赤緑青を0~1で指定する)

argument_meshR.material.SetFloat("_Vector1", alpha);
argument_meshRという引数に渡されたMeshRendererのマテリアルの値をalphaという変数に入っている値に変更する
"_Vector1"はShader GraphでProperty化したノードのReference名

argument_meshR.material.SetColor("_Color", color);
argument_meshRという引数に渡されたMeshRendererのマテリアルの色をcolorという変数に入っている値に変更する
"_Color"はShader GraphでProperty化したノードのReference名

argument_obj.transform.localScale = new Vector3(1, scale, 1);
argument_objという引数に渡されたオブジェクトのローカルスケールを新しいスケールの値で更新する

Destroy(argument_obj);
argument_objという引数に渡されたオブジェクトを削除する

Time.deltaTime
フレーム毎の経過時間を取得する
処理落ち対策に入れるっぽい

詳しい説明は以下を参考にしてみてください
【Unity】Time.deltaTimeの正しい使い方わかってる?適当に掛ければいいてもんじゃない!
https://qiita.com/toRisouP/items/930100e25e666494fcd6

メインループ マニアック解説〜これからのTime.deltaTime〜 - Unityステーション
https://www.youtube.com/watch?v=TP7N57r5Tqw

9、Scriptを空のGameObjectにつける

  1. Hierarchyウィンドウで右クリックからCreate Emptyを選択

  2. 作成した空のGameObjectにProjectウィンドウの Scriptファイルをドラック&ドロップしてAddComponentする
    GameObjectのInspectorウィンドウからAddComponentしても良い

Scriptを直接3DモデルにAddComponentしたい場合は以下のように記述する
単純なエフェクトならそれをプレファブ化するだけで良いかもしれない

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SampleScript : MonoBehaviour // クラス名はファイル名と同じにする
{
   void Start()
   {
       MeshRenderer meshR = this.GetComponent<MeshRenderer>();
       Texture tex = Resources.Load<Texture>("Texture/effect_gradation1");
       meshR.material.SetTexture("_Texture2D", tex);
       
       StartCoroutine(Effect(meshR));
   }

   private IEnumerator Effect(MeshRenderer argument_meshR)
   {
       float t = 0;
       float alpha = 1;
       float r = 1.0f;
       float g = 1.0f;
       float b = 1.0f;
       float scale = 0.0f;
       
       while(true){
           t += 1.0f * Time.deltaTime;
           alpha -= 1.0f * Time.deltaTime;
           b -= 1.0f * Time.deltaTime;
           Color color = new Color(r, g, b);
           
           argument_meshR.material.SetFloat("_Vector1", alpha);
           argument_meshR.material.SetColor("_Color", color);
           
           scale += 1.0f * Time.deltaTime;
           this.transform.localScale = new Vector3(1, scale, 1);
           
           if(t >= 1)
           {
               Destroy(this.gameObject);
               yield break;
           }
           yield return null;
       }
   }
}

補足:マテリアルの差し替え

マテリアルの差し替えは以下のように記述する

obj.GetComponent<Renderer>().material = Resources.Load("Material/materialA",typeof(Material)) as Material;

参考元

Material-SetFloat - Unity スクリプトリファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Material.SetFloat.html

手を動かして学ぶUnity Universal Render Pipeline - part https://developers.wonderpla.net/entry/2020/09/04/143426

【Unity】ShaderGraph導入
https://oyamaxr.hatenablog.com/entry/2018/07/21/145038

【Unity】ScriptからMaterialにTextureを設定する
https://mslgt.hatenablog.com/entry/2017/01/18/073342

勉強中なので説明が間違っている可能性があります自分でも調べて確認してみてください

この記事が気に入ったらサポートをしてみませんか?