😺

CUBEの世界(宇宙猫)

2025/02/04に公開

はじめに

概要

◇CUBEの中の世界🐉🐍に向けて10[m]*10[m]*10[m]のワールドを作成する経緯について記述します。特にSkyboxのマテリアルをAwesome skybox shader graph in UnitySimple way to make stars using shader graph in Unityを参考に自作します

ワールドを作るきっかけ

2025年1月20日Xにて、この投稿をみちゃったいました。やらない理由がなかったのでとりあえず何かかかわろうと思いました。(1月19日にデスゲームジャム向けワールド投稿したのに...)
https://x.com/tamondayon/status/1881137134360564164

開発環境(ワールド作り)

  • Unity 2021.3.4f1
  • Windows10
  • Cluster Creator Kit 2.30.0
    • 特に決めていない、最新であればよい
      * Universal PR 12.1.7
      • Unityレジストリからインストール
      • ↑ClusterではUniversal PR未対応とのこと(N敗)
  • Shader Graph 12.1.7
    • Unityレジストリからインストール

ワールドづくり

ワールドコンセプト

放映中のアニメ「チ。-地球の運動について-」に影響されていると思います。地球の運動について見知りたいというより、水星と金星の見え方について自分の目で確認したいと思いました。この期待を胸に、応募基準を満たすようなものを作ろう、と最初は思っていました。

太陽系

特に難しいところはありません。
太陽を中心にぐるぐる回しちゃってください。
困った点は規模感です。まずプレイヤーからの視点で描画される範囲(視体積)を超えてしまうことがありました。どうやらこの範囲はUnityで1,000([m]のはず)までしか描画されないようです。地球の直径は12,742 [km]らしいので、実寸大で表示しようとすると何も描画されなってしまいます。
今回は実模型である必要はなくファンタジーもりもりにするため、惑星のサイズや公転、自転およそでたらめな値にしました。

金星につけたスクリプトです。自転、公転、半径だけ書き換える原始的なコードだと思います。
惑星なんて回転してたらいいんです。

const RotateManager = ((() => {
    const ROTATE_ANGULAR_VELOCITY = 0.06 * 0.5;
    const Rotate = ($, deltaTime) => {
        const currentRotation = $.getRotation();
        const degreeDifference = ROTATE_ANGULAR_VELOCITY * deltaTime;
        const rotationDifference = new Quaternion().setFromEulerAngles(new Vector3(0, degreeDifference, 0));
        const newRotation = rotationDifference.clone().multiply(currentRotation);

        $.setRotation(newRotation);
    };

    const REVOLUTE_ANGULAR_VELOCITY = 0.067 * 0.5;
    const RADIUS = 150;
    let _angle = 0;
    const Revolute = ($, deltaTime) => {
        const currentPosition = $.getPosition();
        _angle += REVOLUTE_ANGULAR_VELOCITY * deltaTime;
        const newX = RADIUS * Math.sin(_angle);
        const newZ = RADIUS * Math.cos(_angle);
        const newPosition = new Vector3(newX, currentPosition.y, newZ);

        $.setPosition(newPosition);
    };
    return { Rotate, Revolute };
})());

$.onUpdate(deltaTime => {
    RotateManager.Rotate($, deltaTime);
    RotateManager.Revolute($, deltaTime);
});

CUBEの中の世界の応募概要

スタート地点も範囲内に設定 必須ということでした。
今回CUBEが初期位置から移動し続けています。スタート地点もCUBEの中にあるので一緒に移動します。でもしかすると同期ずれでCUBEからはみ出すかもしれません。その救済措置はとっているのですがそのような処理が地味に面倒くさかったです。

(CUBEの中って、一般的にCUBEの中ですよねぇ...境界をとったところでそれから内外とするのは実は明らかじゃないなぁってCUBEを作っていると思いました。)

宇宙の背景

太陽視点だったら惑星が公転自転していたらいいと思うんですが、実際銀河視点だと太陽系が移動していたと思っています(一般教養どこ行った~)。
この移動を惑星の座標を毎フレーム移動させればよいです、、、とはなりません。先ほどの応募概要の通りスタート地点も移動する必要があります。同期ずれでCUBE内にプレイヤーが配置されないケアを平行移動分も考えるのはつらいです(つらい)。今回は背景となるskyboxに恒星を浮かべて平行移動したらそれっぽくならないかなぁと思いました。ここで初めてシェーダーについて触る機会を得ました。

シェーダー

一番最初に出会ったのはモダンOpenGLshaderだと思います。("モダン"だなんてつけちゃうなんて悲しいです。。。)普段から使うものでもなく、Clusterでワールドを作っているとlight,Unlightとか指定するくらいでたまに水面のシェーダーが、、、とかネットの海をさまようくらいです。
名前の通り、ライティング、シェーディング、アニメーション、テセレーション、汎用処理を行うミニプログラムという理解でいます。
cluster向け・Shader Graph(シェーダーグラフ)の事始めではライティングやアニメーションをプログラムして幻想的なサンプルとともにシェーディングとその作成ツールShader Graphが記述されています。

別にシェーダーなんて書かなくてもいいんですが、時間に対してちょっとずつ明るくするとかちょっとずつ移動するとかいったことを絵で表現しようとすると、1分で1800枚の絵を準備して切り替えることになるんですかね(30[fps] * 60[s])。つらい。アニメーターの動画屋さんに外注しよう、そうしよう。
何かしら決まりきった手順で(プログラムで)一枚の絵を魅力的にする場合シェーダーが必要になる感じでしょうね。

さて、使用できるシェーダーの通りclusterではUnityのBuilt-in Render Pipelineを使用しており、URP、HDRPは競合してしまいピンク色になってしまうとのことです。本屋さんでとりあえず買ってきた教科書にそのまま沿って作ってしまうとClusterでは使えないので注意が必要です。(1敗。さよなら、5000円の本)

Skyboxシェーダの作成

先ほどの記事に照会がありますが、こちらの動画に沿って星空のskyboxを用意します。Awesome skybox shader graph in Unity
Simple way to make stars using shader graph in Unity
Skyboxとして利用できるシェーダーの形態はいくつかあるみたいですし、動画の通り作ったシェーダーがどの形態に属しているかわかっていないです。すみません。
パノラマスカイボックス

  • Shader Graphを作成
  • PositionノードとNormalizeノード
  • 貼り付け
    • 作ったShaderで右クリックし、作成>マテリアルで生成できる
    • skyboxに設定できる
  • 設定自体は動画に沿って実装するだけで十分作成できます
  • (追記)SkyboxのShaderGraph
    • ミニマムのノード、そんなものはありませんでした。(マスターノードがあれば、指定したオブジェクト毎レンダリングプロセスにのっとってくれるのかな)

Skybox作成に使った各ノードについて調査

Node Library
を参考に、星空を作るために使ったノードの調査をします

Vertex

デフォルト Object Spaceを入力にしている
shaderを設定しているObjectとは別の座標値や法線にするならここに別の入力を指定すればよさそう

Position Node

  • Position Node


    メッシュの頂点またはフラグメントの位置にアクセスできる。ノードが属するグラフセクションの有効なシェーダーステージ(?)に依存している。出力値はメッシュの頂点またはフラグメントの位置座標。
    座標空間はobject(?), View(?), World(?), Tangent(?)がある。
  • シェーダーステージとはシェーダーパイプライン内で、特定の ノード や ポート(?) が含まれる部分 (例えば Vertex や Fragment など) を指す。
  • ポート
    とはノード の入力あるいは出力を定義する。エッジをポートに接続することで、データがShaderGraphのネットワーク内を伝達できる。
  • 座標空間
    ShaderGraphの座標空間で丁寧に説明いただいているようです。
    公式ドキュメントが見つからないので、自分の理解と予想で書き直しています。。。(タスケテー)
    • オブジェクト空間
      オブジェクトの回転や拡大に向いているはず
    • ワールド空間
      ワールド空間内での回転や移動に向いているはず
    • 接空間
      UV空間上に法線情報を付与されてると思われるので、陰影処理ができるはず。UV空間での平行移動とかもここでやるのでは、、、
    • ビュー空間
    • ラスタライズ
    • フラグメント
  • 結局Space
    • object
      • 設置したオブジェクトのローカル座標系のメッシュ値を入力とする。オブジェクトを移動しても変わらない
    • World
      • 設置したオブジェクトのワールド座標系のメッシュの値を入力とする。オブジェクトを移動すると変わる
    • その他のタイプ
      • なるようになる。時が来たら試そう。。。

Normalize Node

入力ベクトルを正規化して長さ1にする

Split Node

入力ベクトルを 4 つの Vector (R、G、B、A) に分割出力

  • 動画中では空間座標(X,Y,Z)ベクトルからR,G,B,A成分に分けている。高さ方向のY成分はG(Green)成分に出力されている

Clamp Node

入力値を、Min と Max で定義される最小値と最大値の間の範囲にある値だけを返す

  • 動画中では空間の高さ方向のうち、上(0~1)と下(-1~0)にそれぞれ出力している

Negate Node

入力値の正負の符号を反転した値を出力する

  • 動画中では下(-1~0)を(1~0)とに出力している

Add Node

入力値Aと入力値Bのの和を出力している

  • 動画では上(0~1)と下(1~0)の和をとって、水平面が0になるように上~水平面~下が(1~0~1)になるようにしている
  • もしくは、上、水平面、下それぞれに色値を用意して(色値無しは黒)それらの値を足し合わせて最終的な上下空間の色を出力するために使っている

Power Node

入力値Aを入力値B乗して出力する

  • 動画では正規化された成分値を0~1になるようにしている。

Multiply Node

入力 A に入力 B を掛けた結果を出力する

  • 上成分や水平面成分や下成分が0~1として表現されていて、それぞれと固定色を掛けている

Fragment

最終的にFragmentのBaseColorの入力にしている

(コラム)Shader Graph導入プロジェクトの不具合

Shader Graph導入すると、シェーダーのstandardのcutoffによる透明化が失敗しました。悲しい
Clusterワールド制作・Shader Graphでcutoutが反映されない不具合の回避策!に気をつけましょう(N敗)

かっこいいSkyboxのシェーダー作成

(【Unity】Shader GraphでStylizedなSkyboxシェーダを実装する【Advent Calendar 12/5】)[https://media.colorfulpalette.co.jp/n/n136e0ef5e1e7]
太陽、雲、時間経過を実装している。最後の時間経過はC#を使っているのでCLusterで再利用できるかわからないけど、Timer Nodeでcosとか入力に使えるし、グラデーションも何かしらの値を0~1に滑らかに指定する方法を↑でやっているので、原理的にはShader Graphでできそう(できるとは言っていない)

Discussion