切り絵が趣味の僕がプログラムや創作物について書き溜めるブログ

絵やモデリングやプログラミングなど僕が作った物について。アニメの感想や心動かされたきっかけなどを書き溜めるブログです。Boothで3Dアバターの販売もしていますhttps://paw.booth.pm/

VRC Udonトランプ自動配布機能付きのコード解説 パート1

こんぱう~~~

"VRC Udonトランプ自動配布機能付き"のコードについて解説いたします。

f:id:kiriesto:20220215195945p:plain

■基本的なUdon Behaverの設定の仕方

[Add Component]からUdon Behavivourを選択。

f:id:kiriesto:20220227095208p:plainProgram SouirceをUdon C# Program Assetにして[New Program]を押下。

f:id:kiriesto:20220227095458p:plain

[Create Script]を押下。

f:id:kiriesto:20220227100115p:plain

SynchronizationをManualに変更して、Program Scriptからスクリプトファイルを開くことができます。私はVScodeを使用するように設定しています。

f:id:kiriesto:20220227101027p:plain

qiita.comスクリプトファイルの説明

dealbuttonとCubeで使用しているスクリプトファイルがどんな風になっているのか説明します。
完成したスクリプトファイルをUnityで適用すると以下のようになります。

dealbutton

f:id:kiriesto:20220227094210p:plain

Cube

f:id:kiriesto:20220227094624p:plain

サンプルコード

 dealbuttonのコード


using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using System;

public class card : UdonSharpBehaviour
{

    [SerializeField] GameObject m_obj;
    [SerializeField] GameObject textObject;
 
    [SerializeField] GameObject playCardCenter;
    Vector3 worldPos_start;
    Quaternion worldRot_start;
    Vector3 worldScale_start;
    public float radius = 1.0f;

    public int temp(int x){//配列の中身をシャッフルする関数
        int i;
        int temp;
        int value1;
        int value2;
        for(i = 0; i <= 10000; i++){
            value1 = UnityEngine.Random.Range(0, x.Length);
            value2 = UnityEngine.Random.Range(0, x.Length);

            temp = x[value1];
            x[value1] = x[value2];
            x[value2] = temp;
        }
        return x;
    }
   
    void Start () {//開始時にカードの配置を取得する
        worldPos_start = m_obj[0].transform.position;
        worldRot_start = m_obj[0].transform.rotation;
        worldScale_start = m_obj[0].transform.lossyScale;
    }

    public override void Interact()
    {
        int entry_num = textObject.GetComponent<text>().entry_num;//別のオブジェクトから参加人数を獲得
        int i;
        i = (entry_num == 0)? 1:entry_num;
        int user_num = new int[i];//カードを配るプレーヤーの順番を保持する変数

        for(i = 0; i<entry_num; i++){//user_numの初期値
            user_num[i] = i;
        }
        user_num = this.temp(user_num);//カードを配るプレイヤーの順番を入れ替えている

        int trampList = new int {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53};

        trampList = this.temp(trampList);//カードをシャッフル

        // transformを取得
        Transform myTransform = this.transform;
 
        // ワールド座標を基準に、座標を取得
        //Vector3 worldPos = myTransform.position;
        //worldPos.x -= 0.219f;    // ワールド座標を基準にした、x座標が入っている変数
        //worldPos.y += 0.0f;    // ワールド座標を基準にした、y座標が入っている変数
        //worldPos.z -= 1.0f;    // ワールド座標を基準にした、z座標が入っている変数

        if(entry_num == 0){
            for( i = 0; i<trampList.Length; i++){
                Networking.SetOwner(Networking.LocalPlayer, m_obj[i].gameObject);//カードのオーナーリクエストを送信

                m_obj[trampList[i]].transform.rotation = worldRot_start;//カードの設置場所にもどす(位置)
                m_obj[trampList[i]].transform.position = worldPos_start;//カードの設置場所にもどす(角度)
            }
        }else{
            for( i = 0; i<trampList.Length; i++){
                Networking.SetOwner(Networking.LocalPlayer, m_obj[i].gameObject);//カードのオーナーリクエストを送信

                m_obj[trampList[i]].transform.rotation = Quaternion.Euler(0.0f, 0.0f, 0.0f);// カードの回転を初期化
                m_obj[trampList[i]].transform.position = playCardCenter.transform.position;// ワールド座標での座標を設定

                var angle = 360/entry_num*(user_num[i%entry_num]);
                var sin = Math.Sin(angle * (Math.PI / 180));
                var cos = Math.Cos(angle * (Math.PI / 180));

                m_obj[trampList[i]].transform.Translate *1;//カードをずらす

Cubeのコード


using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;

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

public class text : UdonSharpBehaviour
{
 
 
 
    public GameObject score_object = null; // Textオブジェクト
    [UdonSynced(UdonSyncMode.None)] public int entry_num = 2; // スコア変数
    Text score_text;

    // 初期化
    void Start () {
        // オブジェクトからTextコンポーネントを取得
        score_text = score_object.GetComponent<Text> ();
        // テキストの表示を入れ替える
        score_text.text = "参加人数:" + entry_num;
    }

    public override void Interact()
    {
         var player = Networking.LocalPlayer;
         score_text = score_object.GetComponent<Text> ();

        if (player.IsOwner(this.gameObject))
        {
            CountUp();
        }
        else
        {
            // Owner以外が押したら、Ownerにカウントアップさせるように命令する
            SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, nameof(CountUp));
        }
    }

    public override void OnDeserialization()
    {
        score_text.text = "参加人数:" + entry_num;   // データ表示更新
    }

    public void CountUp()
    {
        entry_num += 1;
        if(entry_num >= 9){
            entry_num = 0;
        }                                 // データ更新
        RequestSerialization();                     // 同期更新
        score_text.text = "参加人数:" + entry_num;   // データ表示更新
    }

}

usingでUdonSharpだったり必要なモノを使えるようにしています。
[SerializeField]を付けることでUnityの画面で値を入力できるようになります。

参考文献:【初心者Unity】[SerializeField]ってなに? | TECH PROjin

Gameobjectは配列という意味になります。
[SerializeField] GameObject m_obj;
はトランプを格納するので54の大きさの配列になります。

 

    Vector3 worldPos_start;
    Quaternion worldRot_start;
    Vector3 worldScale_start;

はトランプの初期の位置、角度、大きさを格納する箱です。

 

public float radius = 1.0f;
トランプを配るときのプレイヤーの距離を調節するためのモノです。
小さいと近くになって、大きくすると遠ざかります。

 

    void Start () {//開始時にカードの配置を取得する
        worldPos_start = m_obj[0].transform.position;
        worldRot_start = m_obj[0].transform.rotation;
        worldScale_start = m_obj[0].transform.lossyScale;
    }

void Start()は開始時に呼び出されるところなのでここで初期値を設定しています。

 

public int temp(int[] x)ではトランプをシャッフルするのに使っています。
・ランダムに二枚のカードを選ぶ
・交換する
この二つの動作を10000回くりかえしてトランプを混ぜたことにしています。

 

 public override void Interact() はこのコードを適応したモノがつかまれたときに呼び出されます。

Networking.SetOwner(Networking.LocalPlayer, m_obj[i].gameObject);//カードのオーナーリクエストを送信
これがないと誰かがトランプを掴んでしまったらトランプの位置を動かすことができません。

*1:float)sin*1.2f*radius,0.2f,(float)cos*1.2f*radius);//カードを配る

                m_obj[trampList[i]].transform.rotation = Quaternion.Euler(0.0f, angle, 0.0f);//カードを立てる

                m_obj[trampList[i]].transform.Translate ( -0.01f * (Mathf.Floor(i/entry_num)-54/entry_num/2)* worldScale_start.x, 0.0f, 0.001f * Mathf.Floor(i/entry_num

VRC Udonトランプ自動配布機能付きのコード解説 パート0

こんぱう~~~!!

f:id:kiriesto:20220215195945p:plain

ここではVRChatで使えるトランプを作成します。
カードを作って、手で持てるようにして、自動でカードを配ってくれるようにする。
そんな感じにトランプを作成します。

この記事の目的はプログラミングをなんとなく触ったことあるけど、U#はわからないっていうかた、どうしようしたらいいのか分からないってかたが読んでくれて少しでも手助けが出来たら嬉しいなぁと思って書いています。では、一緒に頑張っていきましょう!!(サンプルをBoothにて公開中:https://paw.booth.pm/items/3650297

 

事前準備

以下の3点をUnityにインポートしておいてください。ここでは詳しくは説明しません。

・VRCSDK3-WORLD(必須)
 (https://vrchat.com/home/download)
・UdonSharp(必須) 
 (初心者向け / Udon Sharp(U#) ダウンロード、インポート方法の詳細解説)
・CyanEmu(あると便利)
 (https://github.com/CyanLaser/CyanEmu)

トランプ自動配布機能付きの概要

用意するモノ一覧
・トランプ54枚(一枚一枚、別オブジェクトで用意する)
・シャフルボタン
 - シャフル文字を表示するモノ
 - ボタンの形
 - トランプを配るときの中心になるオブジェクト
・参加人数ボタン
 - 参加人数を表示するモノ
 - ボタンの形Part2以降に作成するコード
・シャフル用のコード
・参加人数を管理するコード

概要
参加人数が0人のときにシャッフルボタンを押すトランプを初期位置に戻す。

1-8人のときにシャッフルボタンを押すと人数に合わせてトランプを配ってくれる。
(もちろん、トランプは自動でシャッフルされている)

 

■トランプの作成と設定

54枚すべてのカードを作成して(blenderとか)、Add ComponentからBox ColliderとVRC Pickup、VRC Object Syncを入れます。Rigid bodyはVRC Pickupを入れたら一緒に入った気がします。なかったらこれも入れてください。

・VRC Object Syncwでトランプの位置が同期されるようになります。

・VRC Pickupでトランプをトリガーできるようになります。

・「Is Trigger」にチェックを入れると物理的接触がなくなります。チェックを入れてないとトランプにのれたり、歩いているとトランプにぶつかったりします。

・「Is Kinematic」にチェックを入れるとAddForceや重力の影響を受けなくなります。チェックを入れないと空間をトランプが飛んでいく原因になります。

この設定をすべてのトランプに実施します。
Shiftを押しながらすべてのオブジェクトを選択してから、設定すると全部同じ設定にできるはずです。

f:id:kiriesto:20220220152433p:plain

■シャッフルボタンについて

f:id:kiriesto:20220223191020p:plain

f:id:kiriesto:20220223185519p:plain

・buttonは空のオブジェクトでCanvasとdealbuttonまとめるためのモノです。

Canvasはdealの文字を表示するためのモノです。Canvasで文字の表示する場所をつくってTextで表示する文字を設定しています。

・dealbuttonは黄色の直方体です。ボタンを押したらトランプを配るようにプログラミングしています。(プログラミングの内容については次回以降に詳しく説明します。)

dealbuttonにはBox ColliderとUdon Behavivourを追加します。

Udon Behavivourは自作したコードを使用するためのものです。
Udon BehavivourのSynchonization MethodをManualにします。
・Continuousは連続同期
・Manualは手動同期

f:id:kiriesto:20220223191806p:plain

■参加人数ボタンについて

f:id:kiriesto:20220223200751p:plain

f:id:kiriesto:20220223201513p:plain

・numは空のオブジェクトでCanvasとCubeまとめるためのモノです。

Canvasで参加人数を表示しています。

・Cubeで参加人数の加算処理を行っています。Cubeを押したらCanvasに変更した参加人数を表示するようにしています。(プログラミングの内容については次回以降に詳しく説明します。)

Box ColliderとUdon Behavivourを追加します。

Udon BehavivourのSynchonization MethodをManualにします。

f:id:kiriesto:20220223202340p:plain

■トランプを配るときの中心になるオブジェクト

playCardCenterという名前で空のオブジェクトを作成します。

 

これでコードを書くための準備ができました。

次回からどんな処理を行っているのか解説していきます。
本日はお疲れさまでした!!

 

いい夢を~~~~

 

2021年を振り返ってみて

2021年を振り返ってみようと思います。

まずは引っ越しですね。
学生生活が終了し、会社に勤めるため東京都にお引越し。
携帯もiPhoneからAndroidに乗り換えました。

 

楽天モバイルの一年間無料期間でPCもVRテザリングでここまでやってきました。

最初はネットの回線を引くつもりでしたが、借りた場所が工事ダメらしくて...。
幸いにも最低限使えるレベルだったのでよかったです。
たまに回線が切れたりするのが難点でしたが...。
無料期間が来年の3月に切れるのでどうするのか考え中です。

 

つぎに大きかったのは、自分でお金を稼げるようになったので投資にもチャレンジしました。
S&P500という投資信託を買い足すだけなんですけどね。
また、学生ではなくなり会社勤めになったことで自分の使える時間が少ないなかでもブログやアバター制作を続けるという目標を年初めに建てました。

ブログは月一回更新という状態で目標を達成いたしました。

アバター制作はBoothで販売できるような形で完成はしていないものの空いた時間を使って服装はどうするか、テクスチャは?など、少しづつではあったけど創作活動を続けることができてとてもよかったです。

ゲームワールド制作の企画は頓挫していしまいました...><

 

ちょくちょくTillさんからUnityのコード作ってよと依頼が来るのは嬉しいですね。
だいたい、できないのでyuuさんに丸投げするのですが...。
もり上がるベットのシェーダーは作れなくてごめんよ(_ _)

 

会社の仕事に引っ張られて思うように趣味に力を注げなかったかなと思います。
資格の勉強があったので平日は割とそっちがメインになったり、仕事後は何もやる気がでなくて...って感じでした。

そう考えると会社行きながらアバター改変している方々はどうなってんだーーーって感じです。
なぞです謎。

 

ClusterのゲームジャムをPawファミリーで参加してすごろくゲームを作成して、特別賞を受賞したのはとってもいい思い出になりました。次は冬にやるとかいっていたけどいつなんだろう??

 

2021年はあまりVRChatのイベントに出店しなかったなー。
制作が上手くすすでいないことで出すものがないといえばないんだけれど...。
2022年はいくつか出店したいですね。
4コマ漫画でPawファミリーの日常とか書きたいんだけどなぁ。
起承転結をつくるのと、絵が描けなくて...挑戦しようとしたんですけど...う~~~><

 

来年はどんな一年にしたいですかね
願わくばPawファミリーのみんなとおしゃべりできるこの場所(ディスコード)を維持しつつ、いろんなイベントに参加していきたいです。

Super Tower Defense

Super Tower Defenseの序盤の金策を教えてもらったのでご紹介したいと思います
(教えてくださった方に記事にしてもいいと承諾をもらいました)

 

序盤

・まずはマシンガンを設置する
てか、マシンガンしか選択しかない
・4500たまったらマシンガンを強化する(1凸) f:id:kiriesto:20211130061257j:plainレールガンを購入(12500)
レールガンを購入(12500)
レールガンを二回強化する(片方のレールガンのみ2凸)
・WAVE32になるまでレーダーを2凸にして、レーダーを設置するを繰り返す f:id:kiriesto:20211130061611j:plainマシンガン(2凸)を壊して、ドッスンを設置する
ドッスンの強化はしなくていい f:id:kiriesto:20211130061725j:plain

WAVE55になったら(総マシンの設置台数が19台になってるはず)、もう片方のレールガンを二回強化する(全てのレールガンが2凸)
(火力不足のため) f:id:kiriesto:20211130062403j:plain 途中で一体だけめちゃ硬いのが来るけどここは無視、ライフで受け止めてください

・WAVE69までいったら序盤は終了
すべてのマシンを壊して新たに設置しなおしてください
どうやって設置するかセンスが問われます

中盤

この設置が終盤まで生き残れるかに大きくかかわるのでとても大事です
マシンを設置するときにぎりぎりをせめてあぁ置けない!!って何度もなりました

いろいろ試してみて、僕が感じたコツは4つ
・大砲は3台はほしい(完凸) 120万
・レーダーは6個ほしい(できれば完凸、1凸でも可) すべて完凸55.5万、すべて1凸25.5万
ドッスンは2個ほしい(完凸) 7.5万
ドッスンドッスンは発動タイミングがずれるように設置しよう

以上を満たしていればWAVE80ぐらいはいけそうです
金額は183万あれば余裕ですね、レーダーを1凸にした場合は153万で済みます。これでも全然問題ないです

今後はレーダーを増設したり、大砲を設置していきましょう
ドッスンは2個で十分そうです
あとは火力が大事!!

終盤

お金が溜まって好きなように配置できるようになります
配置したらやることがないのでひたすら眺めるだけ f:id:kiriesto:20211130062801j:plain ライフが削られれば終われるけど、終わってほしくないーーーみたいな葛藤がうまれます
ここまで来たら自分の配置を信じてただひたすらライフが削られるのを待つだけ
ここで、あるあるなのがライフ削られたから、「ヤバい配置を変えなくちゃ!!」といって、配置を変えると組み方がよくなくて一瞬で終わってしまうことがあります
僕もなんどか「変更しなければもう2~5WAVEぐらいいけたよなぁ」という時があります f:id:kiriesto:20211130062826j:plain 最後に不可解な現象が起きました
他プレイヤーからはライフが削られているのに、僕目線だと削られていない現象が起きました
あとはQuest2を使っていたからなのか??
fpsを下げて処理落ちをすると寿命がのびる??ってことがあるのかもしれません
そのときのfpsは12とかだった気がします

なんやかんやで僕は最大WAVE250目までいきました
WAVE100を超えてくると1WAVEに一分ぐらいかかるのでめっちゃ時間がかかりました6時間以上だったと思います
昼ぐらいからやって24:00を超えてたような..

もうこれ以上は一人ではできません><
交代制にしないと無理ですね
てか、これ以上はどう配置していいか分から~~~~ん>< f:id:kiriesto:20211130062839j:plain

 

武器金額早見表
名称 購入 強化1 強化2
マシンガン 2500 4500 15000
ミサイル 7500 5000 40000
ドッスン 10000 12500 15000
レールガン 12500 17500 35000
レーダー 12500 30000 50000
大砲 50000 100000 250000

メモ:
ドッスンは複数設置するときはドッスンするタイミングをずらす必要がある
・マシンガンは単体攻撃
・ミサイル、大砲は範囲攻撃
・レールガンは直線貫通攻撃
・レーダーは無凸で範囲拡大、1凸で攻撃速度上昇、2凸でもらえる金額が増加

簡単!!SVGを用いたひらひら舞う紅葉パーティクル

こんぱう!!
今回はSVGファイルをつかってモデリングをパパッと終わらせて、ひらひらと舞いながら落ちていくパーティクルの作り方を紹介したいと思います

f:id:kiriesto:20211002105943p:plain

 

1. 素材となるSVGファイルの取得

まずは紅葉のSVGファイルをとってきましょうCc0のやつだと著作権とか考えなくていいので楽ですね(個人利用のみなら なんでもいいかも、利用規約はしっかり読んでおこう!!)

私はパブリックドメインQ:著作権フリー画像素材集で探してきました

https://publicdomainq.net/images/201809/19s/publicdomainq-0026445vmx.jpg

ベクターのダウンロード を押してSVGファイルをダウンロードします

f:id:kiriesto:20211002111914p:plain
ダウンロードが出来ましたら、Blenderに取り込んでいきます

2. BlenderSVGファイルをインポート

Blenderを開いたらプリファレンスをクリックして
f:id:kiriesto:20211002222549p:plain

アドオンでsvgと検索して出てきたモノにチェックを入れてアドオンを有効化しますf:id:kiriesto:20211002222752p:plain

ファイル/インポート/Scalable Vector Graphicsを選択f:id:kiriesto:20211002223058p:plain

読み込みたいSVGファイルを選択してSVGをインポートf:id:kiriesto:20211002223442p:plain

紅葉のSVGファイルをBlenderに取り込むことができましたf:id:kiriesto:20211002224303p:plain

3. 紅葉を調整する

いらない部分を選択してxキーで削除できます
f:id:kiriesto:20211003162746p:plain

葉脈も削除して一枚の葉っぱになりました
葉身と葉柄が分かれているので両方選択してからCtrl+jで一つにくっつけます
f:id:kiriesto:20211003162846p:plainf:id:kiriesto:20211003163314p:plain

このままだとパスなので オブジェクト/変換/メッシュ と選択してメッシュに変換しますf:id:kiriesto:20211003163539p:plain

tabキーを押して編集モードにするとこんな感じ

f:id:kiriesto:20211003163811p:plain

このままだとポリゴン数も多いので、mキーで距離でマージで頂点数を少なくしたり、形や向きを整えます。マテリアルの名前なども変更しておきましょうf:id:kiriesto:20211003164157p:plain

4. FBXファイルで出力

オブジェクトを選択してから ファイル/エクスポート/FBX を選択f:id:kiriesto:20211003164941p:plain

選択したオブジェクトにチャック。
カメラとランプをshiftキーを選択したままクリックで選択を解除する。
これでFBXをエクスポートをクリック

f:id:kiriesto:20211003165128p:plain

5. UnityにFBXファイルを持っていく

出力したFBXファイルをUnityにドラッグ&ドロップで持っていきます

f:id:kiriesto:20211003170257p:plain

Hierarchyで右クリックして、Effects/Particle System を選択する

f:id:kiriesto:20211003170402p:plain

f:id:kiriesto:20211003170518p:plain

Inspectorの設定する
以下に私の設定をのせておきます
自分好みに変えるのも面白いと思います

f:id:kiriesto:20211003170950p:plain

f:id:kiriesto:20211003171025p:plain

f:id:kiriesto:20211003171111p:plain

パーティクルを再生すると紅葉が舞い落ちてきます!!

最後まで読んでいただきありがとうございます
何かわからないことやつまづいた点があれば教えてくださると嬉しいです!!
これからもいろいろな情報を発信していくのでTwitterのフォローやブログの読者になってください

いい夢見ろよ!!

 

【blender】黒いヤツを倒す!急ぎで倒したい人向け殺虫スプレーの作り方!!

こんぱう~~~

画像


今回はいつ現れるかわからないヤツを倒すための道具をモデリングしていきます
一家に一つは絶対に必須です!!

paw.booth.pm

 

 

1. 参考画像を用意する

Googleでゴキ〇ェットと検索して、よさげな画像を入手する

入手出来たら、blenderに背景画像として挿入しましょう
追加>画像>背景をクッリク

f:id:kiriesto:20210904133614p:plain


ウィンドウから挿入したい画像を選択して”下絵を読み込み”をクッリク

f:id:kiriesto:20210904133914p:plain

 

画像が挿入されました、向きや位置を調整してモデリングしやすい位置に画像を配置しましょう
(Gキーで移動、Rキーで回転、Sキーで拡大縮小)

f:id:kiriesto:20210904134044p:plain


深度を前にして、
不透明度にチェックをして値を小さくしておきましょう

f:id:kiriesto:20210904134509p:plain

この設定にすることで、下の画像のように背景がオブジェクトの前に透けて表示されるのでこの後のモデリングがしやすくなります

f:id:kiriesto:20210904134749p:plain

2. モデリング

まずは缶の部分から作っていきます、こんな感じに缶の周りをなぞるように頂点を配置していきましょう

f:id:kiriesto:20210904135411p:plain

配置ができたら、スパナアイコン>モディファイヤーを追加>スクリューをクリック

f:id:kiriesto:20210904135638p:plain

f:id:kiriesto:20210904135808p:plain

そうすると平面だった缶が360度回転されて三次元になりました!!

ビューのステップ数やレンダーを変えるとポリゴン数が変化するのでお好みで調整してください

f:id:kiriesto:20210904140005p:plain

モディファイヤーを適用しましょう

f:id:kiriesto:20210904140301p:plain

左の画像を右の画像のようにシームをマークしてとりあえず缶は完成

f:id:kiriesto:20210904140431p:plain f:id:kiriesto:20210904140511p:plain

上の部分もいい感じになるようにモデリングします

f:id:kiriesto:20210904140738p:plain


UV展開をして

f:id:kiriesto:20210904141018p:plain

テクスチャを書いて

f:id:kiriesto:20210904141114p:plain

なんやかんやして、完成!!!

f:id:kiriesto:20210904141211p:plain



スプレーが出るようにUnityでパーティクルも作りました~~~
これでVR上で安心して寝れます!!

f:id:kiriesto:20210904141257p:plain

 

ではでは、今日はこの辺で
いい夢見ろよ!!


【4日目】あっという間に完成、時間経過で色が変化する!?(シェーダー入門)

こんぱう~~~


今回は時間経過で色が変化していくテクスチャを作っていきます。
↓完成するとこんな感じになります↓

youtu.be

paw.booth.pm

(Boothで販売中です)

今回作るシェーダーの目的

 元のテクスチャの彩度、明度は変更しないで色相のみ時間経過で変化させる!!


では、さっそく作っていこう!!

 ・用意するもの

・夜空のテクスチャ(別になんでもよい)

・Unity

・Unityの起動と下準備 

f:id:kiriesto:20210717153015p:plain

さっそく下の四つをそろえていきましょう
(三日目で準備したものと画像以外は同じですね)

・色相を変えたい画像(私は夜空にした)

・Unlit Shader (gameColorという名前にした)

・3DオブジェクトのPlane

・マテリアル

 画像を数枚(フォルダーからマウスでピッてやれば入る)、
Unlit Shader(右クリックCreate>Shader>Unlit Shaderで作成できる)、
3DオブジェクトのPlane(Hierarchyで右クリック>3D Object>Plane)とマテリアル(右クリック>Create>Material )も作成しておきましょう。

Projectのmaterialをドラッグ&ドロップ(マウスでピッてやる)でHierachyのPlaneに入れるとマテリアルを適用できます。

materialのshaderを作ったUnlit Shaderにしておきましょう。


・色について

まずはプログラミングする前に、色について少し知っておきましょう。

色の表し方には大まかに二つ存在します。RGBとHSVです。

  • RGB
    RGBはそれぞれR(赤)、G(緑)、B(青)を指しており、「光の三原色」と呼ばれています。だいたいのペイントソフトだとRGBの値をそれぞれ0~255であらわされており、この数値の違いによってさまざまな色を作っています。

    例えば、黒だと R=0, G=0, B=0つまり RGB=(0, 0, 0)のときになります。

    f:id:kiriesto:20210717155207p:plain

    例えば、白だと R=255, G=255, B=255つまり RGB=(255, 255, 255)のときなので

    f:id:kiriesto:20210717155305p:plain

    赤だとRGB=(255,  0,  0)

    f:id:kiriesto:20210717155345p:plain

    緑はRGB=(0,  255,  0)

    f:id:kiriesto:20210717155440p:plain

    青はRGB=(0,  0,  255)
    f:id:kiriesto:20210717155447p:plain
  • HSV
    HSVはH(色相)、S(彩度)、V(明度)の三つの成分から色を表しています。

    f:id:kiriesto:20210717155844p:plain

    ペイントソフトだとこんな感じにあらわされて、
    ・Hは色相で0~360までの値をとって、値の大きさによって赤になったり青になったりします。

    ・Sは彩度と言って色をどれだけ強く入れるかを表しています。0~100までの値をとって表されます。この画像の場合、Sを100にすると真っ赤になるわけです。

    ・Vは明度で黒っぽくするか白っぽくするかみたいなのを調整できます。これも0~100までの値をとって表されます。0だと黒、100だと白になります。

・Unityでの色の扱い方

Unityで色を扱う場合、fixed4型に変換して使います。
では、fixed4型とはどうゆうことなのか?

fixed4型はRGBαに対応する値を格納する型になります。とる値の範囲は0~1となっており、先ほど説明したRGBの範囲0~255を255で割った値をUnityでは取り扱っています。最後のαは色の透明度を表しており、0だと透明、1だと不透明になります。

今回はαの値についてはいじらないので常に1にしてあります。

・なぜ、RGBとHSVの話をしたのか

今回の目的は色味を変えることが目的なのでRGBで色味を変えようと思うとR、G、Bとそれぞれの値をいい感じに変化させないといけなくなり、同時に3つの値をどのように変化させるかを考えないといけません。それだと大変なのでRGBではなくHSVにすることで、Hの色相だけを変化させることで、値を1つだけ変更するだけで色味を変えられるのでとっても楽になります。
なので、RGBとHSVについてお話しました。
次から、Shaderを書いていきましょう!!

 ・Shaderを書く

f:id:kiriesto:20210618202511p:plain

右クリックして、Show in Explorerを押してコードがあるファイルに行きます。
その後、テキストエディターを開きましょう!


開いたら、ブラウザでgoogleを開いてもらって”unity rgb hsv 変換”とかで調べるとよさげなコードが落ちていました

 参考にさせていただいたブログになります。

techblog.kayac.com

「RGBからHSVに変換する処理」と「HSVをRGBに変換する処理」が書いてあったので少し書き換えて使わせていただきます。

 

使い方は、
 夜空の画像(RGB)
    ↓ 「RGBからHSVに変換」
 夜空の画像(HSV
    ↓
 色相にタイム(frac(T))を加算(色の変化のため)
    ↓ 「HSVからRGBに変換」
 色が変わった夜空の画像(RGB)
    ↓
  完成!!

という感じになります。

ではどんな風に書いているのか詳しく見ていきましょう。
(記事の一番最後に完成したシェーダーを置いておきます)

f:id:kiriesto:20210717192711p:plain
140行目 時間を取得(10で割って色の変わる速度を調節しています)
143行目 メイン画像を取得(夜空の画像)
145行目 関数rgb2hsv()を使ってRGBをHSVに変換
146行目 col.rがH(色相)になっているのでそれにTを足して色相を変化させています
147行目 関数hsv2rgb()を使ってHSVをRGBにもどして、描画させます

以上、関数rgb2hsv()と関数hsv2rgb()の中身はほぼコピペなので説明できませんが、他に何かわからないことがあればコメントいただけたらと思います!!

今日も最後まで読んでくださりありがとうございました(_  _)

それではいい夢を~~~
  前回の記事

kiriesto.hatenablog.com

サンプルコード

Shader "Unlit/gameColor"
{
    Properties
    {
        _MainTex ("Texture"2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }


        // RGB->HSV変換
        float4 rgb2hsv(float4 rgb)
        {
            float3 hsv;

            // RGBの三つの値で最大のもの
            float maxValue = max(rgb.r, max(rgb.g, rgb.b));
            // RGBの三つの値で最小のもの
            float minValue = min(rgb.r, min(rgb.g, rgb.b));
            // 最大値と最小値の差
            float delta = maxValue - minValue;
            
            // V(明度)
            // 一番強い色をV値にする
            hsv.z = maxValue;
            
            // S(彩度)
            // 最大値と最小値の差を正規化して求める
            if (maxValue != 0.0){
                hsv.y = delta / maxValue;
            } else {
                hsv.y = 0.0;
            }
            
            // H(色相)
            // RGBのうち最大値と最小値の差から求める
            if (hsv.y > 0.0){
                if (rgb.r == maxValue) {
                    hsv.x = (rgb.g - rgb.b) / delta;
                } else if (rgb.g == maxValue) {
                    hsv.x = 2 + (rgb.b - rgb.r) / delta;
                } else {
                    hsv.x = 4 + (rgb.r - rgb.g) / delta;
                }
                hsv.x /= 6.0;
                if (hsv.x < 0)
                {
                    hsv.x += 1.0;
                }
            }
            
            return float4 (hsv, rgb.a);
        }
        
        // HSV->RGB変換
        float3 hsv2rgb(float3 hsv)
        {
            float3 rgb;

            if (hsv.y == 0){
                // S(彩度)が0と等しいならば無色もしくは灰色
                rgb.r = rgb.g = rgb.b = hsv.z;
            } else {
                // 色環のH(色相)の位置とS(彩度)、V(明度)からRGB値を算出する
                hsv.x *= 6.0;
                float i = floor (hsv.x);
                float f = hsv.x - i;
                float aa = hsv.z * (1 - hsv.y);
                float bb = hsv.z * (1 - (hsv.y * f));
                float cc = hsv.z * (1 - (hsv.y * (1 - f)));
                if( i < 1 ) {
                    rgb.r = hsv.z;
                    rgb.g = cc;
                    rgb.b = aa;
                } else if( i < 2 ) {
                    rgb.r = bb;
                    rgb.g = hsv.z;
                    rgb.b = aa;
                } else if( i < 3 ) {
                    rgb.r = aa;
                    rgb.g = hsv.z;
                    rgb.b = cc;
                } else if( i < 4 ) {
                    rgb.r = aa;
                    rgb.g = bb;
                    rgb.b = hsv.z;
                } else if( i < 5 ) {
                    rgb.r = cc;
                    rgb.g = aa;
                    rgb.b = hsv.z;
                } else {
                    rgb.r = hsv.z;
                    rgb.g = aa;
                    rgb.b = bb;
                }
            }
            return rgb;
        }
        

            fixed4 frag (v2f i) : SV_Target
            {
                float T = _Time.y/10;
                

                fixed4 col = tex2D(_MainTex, i.uv);
                
                col = rgb2hsv( col );
                col.r = frac(col.r + T);
                col.rgb = hsv2rgb(col.rgb);

                // sample the texture
                
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}